JD match: the noun pass reports a term as missing when the résumé says it in other words
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 45/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- typescript
Research direction
Start in src/lib/jd-match/coverage.ts at computeCoverageFromCorpus, then inspect CoverageResult and its consumers in types.ts and JdMatch.tsx. Review the decision and lane boundaries described in #607 and #156 before choosing the deterministic matching behavior. Done means the listed acceptance criteria hold, including explicit fixtures, deliberate score weighting documented in the module docblock, and one remaining coverage implementation.
Written by the indexing model from the issue text.
Description
Problem
The Missing list is the part of JD match a user acts on — it is the edit queue. So a term that lands there wrongly does not just cost accuracy, it sends someone to make a résumé change that accomplishes nothing.
The noun pass produces exactly that failure. In computeCoverageFromCorpus (src/lib/jd-match/coverage.ts) the two term sources are matched by different rules:
const hit =
term.source === "skill"
? corpusMentionsSkill(corpus, term.id)
: corpusMentionsPhrase(corpus, term.display);
corpusMentionsSkill resolves through the alias dictionary in skills.ts, so a JD asking for js matches a résumé that says JavaScript. corpusMentionsPhrase matches the JD's surface phrase literally against the corpus. A noun-pass term the résumé expresses in different words is therefore reported as missing, and JdMatch.tsx renders it under Missing (N) indistinguishably from a term the résumé genuinely never addresses.
Concretely, a JD phrase like on-call rotation against a résumé saying production support rotation, or distributed systems against large-scale backend services, is currently a miss.
We have already decided this is a defect once
#607 fixed the same class in the skill-guidance lane — guidance was suggesting skills the résumé already stated in other words. The reasoning there applies unchanged here; only the lane is different. This issue is that decision carried into the noun pass.
Why not just wait for the semantic path
#156's WebLLM requirement matching is the better long-term answer and this issue does not compete with it. But the keyword path is not going away: JdMatchResult types it as a first-class path, and types.ts records it as "also the semantic path's fallback." It is what runs when the model is unavailable, still loading, or declined. A fallback that reports confident false misses is worse than one that is merely coarse, because the user cannot tell which mode produced the list.
Proposal
Narrow the gap in the deterministic path, without pretending it becomes semantic matching.
Two options, and I lean toward the first:
(a) Match noun phrases on a normalized head, not the literal string. Stem/lemmatize and drop determiners and modifiers before comparing, so distributed system and distributed systems stop being different terms, and a JD phrase whose head noun appears in the corpus under a light variation is credited. Cheap, deterministic, testable, no dictionary to maintain.
(b) Extend the alias dictionary to cover common non-skill phrases. More precise where it applies, but it is an open-ended maintenance surface, and skills.ts is deliberately a skill dictionary — its DICTIONARY_VERSION contract and the aliasToId canonicalization exist for chips and guidance, and widening it into general JD prose would change what that version number means.
Either way, a third behaviour is worth having independently: when a term is neither clearly covered nor clearly absent, say so. A "possibly covered — your résumé says X" bucket is more honest than forcing a binary, and it is directly actionable in a way a false miss is not. CoverageResult is { covered, missing } today, so this is a shape change and needs to be weighed against every current consumer, including the score.
Scoring
Any change here moves score. That is correct — the current score is inflated in the other direction by counting real coverage as missing — but it must be a deliberate, recorded move rather than a silent drift, since the number appears in the UI and users compare it across runs. If a middle bucket lands, decide its weight explicitly rather than defaulting it to zero or to NOUN_WEIGHT.
Out of scope
- Changing the skill pass. It already resolves aliases and is not the defect.
- Semantic matching — #156.
- Auto-editing the résumé or inserting terms. We show the gap; the user decides what goes in their résumé.
- Boilerplate exclusion — separate concern, see #793.
Acceptance criteria
- A JD noun phrase the résumé states in different words is not reported under Missing
- A JD term the résumé genuinely does not address is still reported under Missing
- Where the match is uncertain, the UI distinguishes it from a confident miss rather than picking a side silently
- The effect on
scoreis deliberate, documented in the module docblock, and covered by a test that pins the new weighting - Fixtures cover the plural/inflection case and the different-wording case explicitly
- There remains exactly one coverage implementation —
computeCoverageFromCorpusstays the only matcher, per its own docblock
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 45
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from offlinecv/OfflineCV
-
refactor testing
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
chore
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Maintainers usually reply within 1 day
-
improvement ux:job-search
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
All issues in offlinecv/OfflineCV
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
diegosouzapw/OmniRoute#14869 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 94/100
Maintainers usually reply within 1 day
-
status: waiting triage
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
freeCodeCamp/freeCodeCamp#70412 ·
Maintainers usually reply within 1 day
-
Mend: dependency security vulnerability untriaged
Difficulty 1/5 Under an hour Newbie friendliness 88/100
opensearch-project/OpenSearch-Dashboards#12816 ·
Maintainers usually reply within 1 day