Personal System
JobHunter
A job-search system built to reject roles rather than recommend them, because the expensive mistake is applying to jobs that were never winnable.
- 328 employer career pages monitored
- 425 roles in the audited dataset
- 100 unwinnable roles rejected, with reasons logged
- ~75-second complete scan
- No database, no backend, no runtime AI
Every job platform is built to maximise what it shows you. That is the right objective for a marketplace and the wrong one for a person actually applying. The expensive failure in a job search is not missing a role. It is spending a week on an application that was unwinnable from the first line of the posting.
So I built the opposite. JobHunter optimises for precision and rejection. It monitors employer career pages directly, reads what each posting actually requires, and throws away the roles that fail a hard constraint. Hidden experience ceilings, language requirements, visa and sponsorship limits, clearance or citizenship rules, seniority mismatches. Then it tells me exactly why it threw each one away, so I can argue with it.
How a posting travels through the system
- Employer ATS + job sources
- Normalize
- Relevance filter
- Region resolution
- Qualification gate
- Salary + requirement parsing
- Deterministic scoring
- Merge
- Deduplicate
- Dashboard
What it monitors
- 328 employer career pages, covering 195 Greenhouse boards, 105 Ashby boards and 28 Lever boards.
- 164 of those employers were discovered automatically rather than added by hand.
- A complete scan hits 336 endpoints and finishes in about 75 seconds.
- The audited dataset was 425 postings across 194 companies, with 421 of the 425 requirement-verified.
- Three regions and four currencies, so a salary in one market can be compared honestly against another.
The qualification gate, which is the part that matters
- Relevance scoring tells you a job looks like your job. It cannot tell you whether you would be rejected in the first screen. The qualification gate is the component that answers the second question.
- It reads the posting body, extracts the hard requirements, and rejects the posting if a requirement cannot be met. Experience ceilings, language, work authorisation, seniority.
- Every rejection is recorded with its reason. In the audited run, 100 roles were rejected this way. Because the reasons are logged, I can audit the system's judgement instead of trusting it.
- It also collapses multi-location requisitions, 38 of them in the audited run, so one job posted in six cities is one decision rather than six.
Decision: titles lie
- A role came through with a near-perfect title match and scored highly. Buried in the body was a 12+ year experience requirement. Unwinnable, and the system had recommended it.
- The fix was not to delete that one role. That would have left the same failure waiting behind every other posting.
- The product logic changed instead: read the body, extract every experience requirement, take the strictest one, reject anything above the ceiling, and log the reason. That rule is what produced the 100 rejections in the audited run.
Decision: the false-positive flood
- Technical Program Manager and engineering-adjacent roles were scoring well, because the title matching was too broad. The result was a queue full of roles I had no business applying for.
- The rule that fixed it was a domain anchor: a title match only counts if the role is anchored in a domain that is actually mine.
- 208 of 209 engineering-flavoured false positives disappeared. One rule, not a hand-maintained blocklist.
Decision: unknown must stay unknown
- If the system cannot read a posting body, it does not assume the role qualifies. It marks the requirements unverified and says so.
- This sounds obvious and almost every system gets it wrong, because defaulting to 'qualified' makes the output look better.
- Uncertainty is data. A result I know is unverified is more useful than a result that quietly guessed.
Decision: debug the system, not the symptom
- One company's board appeared dead. The obvious read was a broken integration for that employer.
- It was a timeout. One particularly large feed failing under concurrent load. The employer was fine.
- The fix went into the shared fetching logic, where every other large feed benefits from it, rather than into a company-specific workaround. Fixing the symptom would have left the same bug waiting for the next big employer.
How it is built, and how it was delivered
- The product itself is deterministic and rules-based. There is no LLM in it, no agents, no embeddings, no machine-learning model. Given the same postings it produces the same decisions every time, which is the only reason the rejection log is worth auditing.
- It runs with zero third-party Python dependencies, no database and no backend. 52 offline test assertions pin the rules in place so a change to one cannot quietly break another.
- The development process was AI-assisted. I defined the problem, specified the rules and constraints, diagnosed the failures, decided the trade-offs, validated the output against real postings, and I maintain it.
- The product is deterministic. The development process was AI-assisted. Those are two separate statements and both are true.
The point
JobHunter is small. The method is the point: observe the failure, find the actual cause, choose a system rule rather than a one-off patch, implement it through AI-assisted development, validate it against real data, and pin the decision down with a test. That is the same loop I run on internal business tools, IT operations and process automation.