https://github.com/amareshhebbar/hiresignal
Ranks 100K candidates against a Senior AI Engineer JD in ~35s on CPU — multi-signal scoring, honeypot detection, semantic embeddings. INDIA RUNS Hackathon Track 1.
https://github.com/amareshhebbar/hiresignal
candidate-ranking embeddings faiss hackathon india-runs india-runs-2026 information-retrieval machine-learning nlp python redrob sentence-transformers
Last synced: about 13 hours ago
JSON representation
Ranks 100K candidates against a Senior AI Engineer JD in ~35s on CPU — multi-signal scoring, honeypot detection, semantic embeddings. INDIA RUNS Hackathon Track 1.
- Host: GitHub
- URL: https://github.com/amareshhebbar/hiresignal
- Owner: amareshhebbar
- Created: 2026-06-25T08:41:50.000Z (15 days ago)
- Default Branch: main
- Last Pushed: 2026-06-25T09:36:12.000Z (15 days ago)
- Last Synced: 2026-06-25T11:14:27.731Z (15 days ago)
- Topics: candidate-ranking, embeddings, faiss, hackathon, india-runs, india-runs-2026, information-retrieval, machine-learning, nlp, python, redrob, sentence-transformers
- Language: Python
- Homepage: https://huggingface.co/spaces/AmareshHebbar/hiresignal
- Size: 78.1 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
```
██╗ ██╗██╗██████╗ ███████╗███████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██╗
██║ ██║██║██╔══██╗██╔════╝██╔════╝██║██╔════╝ ████╗ ██║██╔══██╗██║
███████║██║██████╔╝█████╗ ███████╗██║██║ ███╗██╔██╗ ██║███████║██║
██╔══██║██║██╔══██╗██╔══╝ ╚════██║██║██║ ██║██║╚██╗██║██╔══██║██║
██║ ██║██║██║ ██║███████╗███████║██║╚██████╔╝██║ ╚████║██║ ██║███████╗
╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝
```
**Intelligent Candidate Discovery & Ranking**
*INDIA RUNS Hackathon — Track 1 (Data & AI Challenge) — Redrob AI × Hack2Skill*
[](https://python.org)
[](tests/)
[](#compute)
[](#)
[](https://huggingface.co/spaces/AmareshHebbar/hiresignal)
---
## What it does
Ranks 100,000 candidate profiles against a Senior AI Engineer job description in **~35 seconds on CPU** — no GPU, no API calls, no network during ranking.
Goes well beyond keyword matching. Scores each candidate across four dimensions, detects ~85 honeypot/fraudulent profiles, and generates a specific natural-language explanation for every ranking decision.
```
$ make rank
[1/6] loading dataset/candidates.jsonl
100,000 candidates loaded (7.1s)
[2/6] honeypot detection
85 flagged (0.4s)
[3/6] scoring all candidates
done (10.6s)
[4/6] semantic refinement on top 500
done (11.0s)
[5/6] selecting top 100
[6/6] writing submission.csv
done (0.0s)
done total=29.0s
[ 1] CAND_0041669 Recommendation Systems Engineer 8.0yrs
[ 2] CAND_0011687 Senior NLP Engineer 7.8yrs
[ 3] CAND_0064326 Search Engineer 7.6yrs
[ 4] CAND_0052682 NLP Engineer 6.6yrs
[ 5] CAND_0017960 Recommendation Systems Engineer 7.7yrs
$ make validate
Submission is valid.
```
---
## Demo
> **[Live sandbox on HuggingFace Spaces →](https://huggingface.co/spaces/AmareshHebbar/hiresignal)**

## Output

## Pipeline



---
## Architecture
```
candidates.jsonl (100,000 profiles)
│
▼
┌────────────────────────────────┐
│ HONEYPOT DETECTION │
│ │
│ expert skill + 0 months use │
│ career months > 1.6× YoE │
│ 9+ expert skills listed │
│ start_date before 2005 │
│ is_current=True + end_date │
│ │
│ → 85 flagged, score = 0.001 │
└────────────────┬───────────────┘
│ 99,915 clean candidates
▼
┌──────────────────────────────────────────────┐
│ MULTI-SIGNAL SCORING │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ SKILL MATCH │ │ CAREER TRAJECTORY │ │
│ │ 40% weight │ │ 35% weight │ │
│ │ │ │ │ │
│ │ proficiency × │ │ title quality │ │
│ │ duration × │ │ company type │ │
│ │ endorsements × │ │ YoE band (6-8yr) │ │
│ │ assessments │ │ prod AI evidence │ │
│ └─────────────────┘ │ location │ │
│ └──────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ BEHAVIORAL SIGNALS 25% weight │ │
│ │ │ │
│ │ last_active_date response_rate │ │
│ │ notice_period github_activity │ │
│ │ profile_completeness verifications │ │
│ └──────────────────────────────────────┘ │
└─────────────────────┬────────────────────────┘
│ sorted, top 500
▼
┌────────────────────────────────┐
│ SEMANTIC REFINEMENT │
│ paraphrase-MiniLM-L6-v2 │
│ │
│ JD text → embedding │
│ candidate profile → embedding │
│ cosine similarity → +20% blend│
└────────────────┬───────────────┘
│ re-sorted
▼
┌────────────────────────────────┐
│ TOP 100 + REASONING │
│ │
│ per-candidate explanation │
│ references actual facts │
│ honest about gaps │
└────────────────────────────────┘
│
submission.csv
```
---
## Scoring model
| Component | Weight | What it measures |
|---|---|---|
| Skill match | 40% | JD-required skills weighted by proficiency × duration × endorsements × assessment scores |
| Career trajectory | 35% | Title quality, company type, YoE band, production AI evidence, location |
| Behavioral signals | 25% | Availability, responsiveness, engagement — treats inactivity as an availability gate |
| Semantic similarity | +20% blend | Sentence-transformer cosine sim on top-500 only — stays within 5-min budget |
### Why behavioral signals are a gate, not a bonus
The JD explicitly says:
> *"A perfect-on-paper candidate who hasn't logged in for 6 months and has a 5% recruiter response rate is, for hiring purposes, not actually available."*
So a candidate with great skills but 200+ days inactive gets significantly down-weighted — not penalized slightly.
### Why pure consulting careers get penalized
The JD explicitly names TCS, Infosys, Wipro, Accenture, Cognizant, Capgemini as patterns they want to move away from. A candidate whose entire career is at these firms gets a `0.2` company score regardless of their skill list.
---
## Honeypot detection
The dataset contains ~85 impossible profiles designed to catch naive rankers. We detect them with five logical impossibility checks:
```python
# 1. Expert proficiency + 0 months of actual use on 2+ skills
expert_zero = [s for s in skills
if s["proficiency"] == "expert" and s.get("duration_months", 1) == 0]
if len(expert_zero) >= 2:
return True
# 2. Sum of career months > 1.6× claimed years_of_experience
if total_career_months > yoe * 12 * 1.6 and total_career_months > 36:
return True
# 3. 9+ skills listed as "expert" — keyword stuffer pattern
if sum(1 for s in skills if s["proficiency"] == "expert") >= 9:
return True
# 4. Job start_date before 2005 for someone with < 20 YoE
if yoe < 20 and any(int(job["start_date"][:4]) < 2005 for job in career):
return True
# 5. is_current=True but end_date is set — logical contradiction
if any(j.get("is_current") and j.get("end_date") for j in career):
return True
```
All honeypots score `0.001` and never appear in top-100. The original code had a sixth rule (`skill.duration_months > yoe * 12`) that was incorrectly flagging 9,231 legitimate candidates — caught and fixed during development.
---
## Project structure
```
hiresignal/
│
├── rank.py entry point (7 lines)
├── Makefile make rank / validate / test / setup
├── requirements.txt
├── app.py Gradio demo for HuggingFace Spaces
│
├── src/
│ ├── pipeline.py orchestrates all 6 steps
│ ├── loader.py reads candidates.jsonl line by line
│ ├── jd.py JD constants — skill lists, title sets, cities
│ │
│ ├── filters/
│ │ └── honeypot.py 5 impossibility checks
│ │
│ ├── scoring/
│ │ ├── skills.py skill match scorer
│ │ └── career.py career trajectory scorer
│ │
│ ├── signals/
│ │ └── behavioral.py 23 Redrob platform signals
│ │
│ ├── embed/
│ │ └── encoder.py sentence-transformer on top-500
│ │
│ └── output/
│ ├── writer.py CSV output + validation asserts
│ └── reasoning.py per-candidate reasoning strings
│
├── scripts/
│ ├── download_model.py one-time model download
│ └── check_output.py inspect top-10 with full profile details
│
├── tests/
│ ├── test_honeypot.py 5 tests
│ └── test_scoring.py 5 tests
│
└── dataset/
├── sample_candidates.json first 50 candidates
├── sample_submission.csv format reference
├── validate_submission.py official format validator
└── candidate_schema.json JSON schema for candidate objects
```
---
## Quickstart
```bash
git clone https://github.com/amareshhebbar/hiresignal
cd hiresignal
make setup
source .venv/bin/activate
make model
make rank
make validate
make test
```
Manual:
```bash
python rank.py --candidates dataset/candidates.jsonl --out submission.csv
python dataset/validate_submission.py submission.csv
```
---
```
Platform Fedora Linux, 8-core CPU
Memory ~2.5 GB peak
Runtime ~29s (rule-based only) / ~35s (with cached embeddings)
Network none during ranking
GPU not used
```
Pre-computation (`make model`) downloads the sentence-transformer model (~90MB) once to `.model_cache/`. The ranking step itself has zero network dependency — satisfies the submission spec constraint.
---
## Tests
```bash
make test
# ===== test session starts =====
# tests/test_honeypot.py::test_clean_passes PASSED
# tests/test_honeypot.py::test_expert_zero_months PASSED
# tests/test_honeypot.py::test_career_months_inflated PASSED
# tests/test_honeypot.py::test_too_many_expert_skills PASSED
# tests/test_honeypot.py::test_is_current_with_end_date PASSED
# tests/test_scoring.py::test_ai_engineer_scores_above_zero PASSED
# tests/test_scoring.py::test_more_skills_scores_higher PASSED
# tests/test_scoring.py::test_disqualifying_title_gets_very_low PASSED
# tests/test_scoring.py::test_ideal_yoe_band_boosts_career PASSED
# tests/test_scoring.py::test_india_location_preferred PASSED
# ===== 10 passed in 0.07s =====
```
---
## Submission
| Field | Value |
|---|---|
| Track | Track 1 — Data & AI Challenge |
| Hackathon | INDIA RUNS — Redrob AI × Hack2Skill |
| Deadline | July 2, 2026 |
| Reproduce | `python rank.py --candidates ./dataset/candidates.jsonl --out ./submission.csv` |
| Sandbox | [huggingface.co/spaces/AmareshHebbar/hiresignal](https://huggingface.co/spaces/AmareshHebbar/hiresignal) |
---
Built by [G V Amaresh](https://linkedin.com/in/gvamaresh) · [HuggingFace](https://huggingface.co/AmareshHebbar) · [LinkedIn](https://linkedin.com/in/gvamaresh)
< docs pass retry: 2026-07-03T04:44:14Z -->