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
Full coverage reaches 97, never 100. A resume cannot evidence everything a posting asks for, and interviews exist for a reason. A score is a ranking signal, not a probability of being hired.
Score bands
BandLabelReading
90 and aboveStrongMeets nearly all stated requirements. Apply first.
80 to 89GoodMeets most requirements; one or two genuine gaps.
Below 80PartialAdjacent 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.

Most discriminating skills in the current dataset -
Loading derived weights.

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.

Measured effect of the fix
MeasureBeforeAfter
Top-score spread across 15 professions54 points19 points
Professions seeing their own field in the top 50 of 1515 of 15
Hardcoded skill weights210
Domains in the dataset116

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.

Roles by domain
DomainRolesShare
Loading.

Privacy

Your resume file never leaves your browser. PDF and DOCX text extraction runs locally in this page. Only the resulting list of detected skill names is sent to the matching service.
  • 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.

MethodPathReturns
GET/api/healthLiveness, dataset counts
GET/api/jobsFull dataset. Accepts ?country= and ?domain=
GET/api/skillsSkill taxonomy, grouped by domain, with derived weights
POST/api/matchRanked matches for a skill set or resume text
POST/api/parseServer-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.