Methodology
How a match score is calculated, how the dataset was assembled, and what this tool does and does not claim to know.
Scoring model
A score answers one question: what share of this role's stated requirements does the resume evidence?
coverage = sum of weights of required skills the candidate has
----------------------------------------------------
sum of weights of all skills the role requires
score = 40 + coverage x 55
+ freshness bonus (posted today +3, within 3 days +2, within a week +1)
- seniority penalty (2 levels apart -4, 3 or more -7, adjacent -1)
+ breadth bonus (up to +2 for meeting many distinct requirements)
clamped to 35-97
| Band | Label | Reading |
|---|---|---|
| 90 and above | Strong | Meets nearly all stated requirements. Apply first. |
| 80 to 89 | Good | Meets most requirements; one or two genuine gaps. |
| Below 80 | Partial | Adjacent role. Worth reading, expect to argue transferable skills. |
Roles at 88 and above are additionally flagged as strong matches in the shortlist.
Skill weighting
Skills are not counted equally, and the weights are not hand-picked.
Each skill's weight is derived from the dataset using inverse document frequency. A skill demanded by only a handful of roles is discriminating, so it carries more weight. A skill demanded by nearly every role carries little signal and is weighted down.
idf(skill) = ln(1 + total_roles / (1 + roles_requiring_skill))
then normalised across the dataset to the band 0.5 - 3.0
The consequence is that nobody has to decide whether, say, a cloud platform is more important than a design tool. The market decides, and the weights shift automatically as the dataset changes.
Highest-weighted skills first. This list is computed at runtime, not stored.
Domain neutrality
The engine must not favour one profession. This is enforced by test, not by intent.
An earlier version of this tool was biased. It hardcoded weights for one field's tooling, and its dataset contained roles from that field only. A data governance resume scored 96 while every other profession averaged 60. Both causes were removed:
- The scoring file names no tool, vendor or profession anywhere. Weights come from dataset frequency.
- The skill vocabulary covers every domain to comparable depth, so no field loses points merely for being less well described.
- The dataset spans 16 domains, and no single domain may exceed 40 per cent of it.
A test suite runs fifteen resumes from fifteen different professions through the real engine on every build. It fails the build if the spread between the best and worst top score exceeds 25 points, if any profession fails to see its own field in its top five results, or if the previously favoured domain regains an advantage.
| Measure | Before | After |
|---|---|---|
| Top-score spread across 15 professions | 54 points | 19 points |
| Professions seeing their own field in the top 5 | 0 of 15 | 15 of 15 |
| Hardcoded skill weights | 21 | 0 |
| Domains in the dataset | 1 | 16 |
Data coverage
Every role is a real posting with a working source URL. Nothing is synthetic.
Sources. Reed.co.uk, LinkedIn Jobs (Netherlands, Singapore, United Kingdom), Indeed Netherlands, MyCareersFuture Singapore, Greenhouse and employer career sites. Each posting was fetched individually rather than taken from a search-results snippet.
Salary. Recorded only where the posting published a figure. Roles without one show "Not disclosed" rather than an estimate. Disclosure varies sharply by market: Singapore mandates a range, the United Kingdom usually publishes one, and Dutch employers frequently do not.
Dates. Some boards publish only a relative age such as "2 weeks ago". Those records are marked approx in the roles table and are accurate to within a few days.
| Domain | Roles | Share |
|---|---|---|
| Loading. | ||
Privacy
- No account, no sign-in, no cookies set by this application.
- Results are held in session storage and are discarded when the tab closes.
- The matching service stores nothing. It is a stateless request-response function.
- Your name and email address, if detected, are used only to label the shortlist in your own browser.
There is a server-side parsing endpoint for clients that cannot decompress a PDF locally. It is not used by this page, and it retains nothing after the response is returned.
Known limitations
Stated plainly, because they affect how you should read a score.
- Keyword evidence, not judgement. The engine detects whether a skill is named. It cannot tell two years of incidental exposure from eight years of depth.
- Scanned PDFs have no text layer. Image-only documents yield nothing; the app detects that case and asks you to paste the text instead.
- Unlisted skills score zero. If a posting does not name a requirement, meeting it earns nothing. Sparse postings therefore score generously.
- A snapshot, not a live feed. The dataset was captured on one date. Roles close, and the oldest entries are the most likely to have gone.
- Recruiter postings hide the employer. Many entries name an agency; the actual employer is often undisclosed until you apply.
- Seniority inference is shallow. It reads job titles and tenure. An unusual title may be mapped to the wrong level.
API endpoints
The matching service is a public read-only API.
| Method | Path | Returns |
|---|---|---|
GET | /api/health | Liveness, dataset counts |
GET | /api/jobs | Full dataset. Accepts ?country= and ?domain= |
GET | /api/skills | Skill taxonomy, grouped by domain, with derived weights |
POST | /api/match | Ranked matches for a skill set or resume text |
POST | /api/parse | Server-side resume parsing (fallback path) |
Architecture
- Frontend
- Static assets on Cloudflare Pages. No framework, no build step, no runtime dependencies.
- Matching service
- Stateless Cloudflare Worker. Scoring the dataset costs roughly 1 to 2 ms of CPU against the free tier's 10 ms budget.
- Resume parsing
- Runs in the browser. This keeps the Worker inside its CPU budget and means the file is never uploaded.
- State
- None on the server. The shortlist lives in your browser's session storage.
- Credentials
- Stored as encrypted Cloudflare Workers secrets and GitHub Actions secrets. None are present in either repository, which a test enforces on every build.
- Delivery
- GitHub Actions on push to main. Tests gate the deploy, and a smoke test verifies the live endpoint afterwards.