[parser] education — an em-dash separator on the institution line is never split: the degree is lost on one shape, the location on another
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 58/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- typescript
- Domain
- testing-qa, tooling
Research direction
Start with DEGREE_RE and its tests in src/lib/heuristics/regex.ts, then trace stripInstitutionLocation and its tests in src/lib/heuristics/extract/education.ts. Run the named fixture and corpus tests before changing the truth sidecars. Done means dotted credentials and spaced em/en-dash locations parse correctly, guarded non-locality cases remain intact, and the listed verification commands pass without new baseline rows.
Written by the indexing model from the issue text.
Description
Context — what a knownWrong entry is
Each corpus fixture under tests/fixtures/pdfs/ may carry a hand-authored ground-truth sidecar (*.truth.json, minted under #654). Its knownWrong block records, per field, a place where the parser disagrees with what the page actually draws. Every entry carries a status:
open— a live bug, and the cited issue must still be open;accepted— a written-down by-design tradeoff;unfiled— measured, but nobody has filed the bug yet (issue: null).
unfiled is capped: UNFILED_TRUTH_CEILING = 10 in src/lib/heuristics/corpus.test.ts:186. That ceiling is currently saturated at 10/10, so no newly measured disagreement can be recorded until some are filed and flipped to open. This issue files three of them.
What's wrong
Two education fixtures draw an em dash on the institution line. Neither shape survives the parse, and they fail in two different directions.
Shape A — Institution — Degree on one line: the degree is LOST
tests/fixtures/pdfs/unknown/name-set-apart-tagline.pdf, drawn (pdftotext -layout):
EDUCATION
State University — B.F.A. in Graphic Design
2013 - 2017
Observed: institution holds the entire line "State University — B.F.A. in Graphic Design", and degree comes back EMPTY.
Recorded today as two unfiled entries in tests/fixtures/pdfs/unknown/name-set-apart-tagline.truth.json, on education.degree and education.institution (same root, two fields).
Shape B — Institution — City, State on one line: the location is ABSORBED
tests/fixtures/pdfs/unknown/single-column-title-below-anchor.pdf draws:
Ohio Valley State University — Columbus, Ohio
Observed: institution = "Ohio Valley State University — Columbus, Ohio"; location stays unset.
Recorded today as an unfiled entry on education.institution in tests/fixtures/pdfs/unknown/single-column-title-below-anchor.truth.json.
Note the asymmetry that makes this a defect and not a preference: on that same fixture the EXPERIENCE extractor peels the em-dash location correctly (Ohio Valley State University — IT Service Desk — Columbus, Ohio yields location populated). Education lags a contract experience already honours.
Root cause — two distinct roots, same file
Root 1 (Shape A) — DEGREE_RE has no 3-letter dotted credentials
src/lib/heuristics/regex.ts:641-645:
export const DEGREE_RE =
/\b(B\.?A\.?|B\.?Sc\.?|B\.?S\.?|B\.?Eng\.?|B\.?E\.?|B\.?Tech\.?|M\.?A\.?|M\.?Sc\.?|M\.?S\.?|M\.?Eng\.?|M\.?B\.?A\.?|Ph\.?D\.?|M\.?D\.?|J\.?D\.?|Bachelor|Master|Doctor|Associate)(?:\s+of\s+[A-Za-z ]{2,40})?/;
B.F.A. matches nothing: the B\.?A\.? branch consumes B and the first ., then requires A where the string has F. The same hole swallows M.F.A., B.B.A., B.C.A., LL.B., LL.M., D.D.S., B.F.Tech.
With no DEGREE_RE hit, the line is not a degree line at all. INSTITUTION_HINTS (regex.ts:647) still matches University, so the whole line is classified as an institution line and stored verbatim — the em dash never gets a chance to matter. Fixing the separator alone would not fix Shape A.
Root 2 (Shape B) — stripInstitutionLocation knows four boundaries, none of them a dash
src/lib/heuristics/extract/education.ts:602-687. The boundaries it tries, in order:
| Regex | Boundary | Line |
|---|---|---|
COMMA_US_RE |
, |
~609 |
SPACE_US_RE |
2+ spaces (column gap) | ~614 |
MIDDOT_US_RE |
· (our own exporter's separator, #291/#294) |
~618 |
SPACE1_US_RE |
1 space, guarded (#366, LaTeX two-column) | ~633 |
INTL_RE / MIDDOT_INTL_RE |
comma / middot, country gazetteer | ~665 |
An em dash (—, U+2014) or en dash (–, U+2013) is in none of them.
There is a second, independent miss on the same string: every US branch requires a two-letter USPS code (([A-Z]{2})$), and this fixture draws the state spelled out — Columbus, Ohio. So even a dash-aware branch would not fire without spelled-out-state support. The international path can't rescue it either: COUNTRY_GAZETTEER holds countries, not US states.
docs/canonical-resume-model.md:270-273 already flags this class as knowingly unhandled — "a source résumé using | or — between title and company parses worse today" — and scopes it out of the export separator contract. This issue is that scoped-out parser-input half, for education.
Implementation plan
-
src/lib/heuristics/regex.ts— widenDEGREE_RE. Add the dotted 3-letter credentials, longest-first so they win over their own prefixes (the file's existing NOTE explains why ordering is load-bearing —B.Sc.precedesB.S.):/\b(B\.?F\.?A\.?|M\.?F\.?A\.?|B\.?B\.?A\.?|B\.?C\.?A\.?|LL\.?M\.?|LL\.?B\.?|D\.?D\.?S\.?|B\.?A\.?|B\.?Sc\.?| … )/B.F.A.must precedeB.A.,M.F.A.beforeM.A.,B.B.A.beforeB.A.,LL.M.beforeLL.B.is irrelevant but keep them adjacent. Verify no new branch can match inside an ordinary word (each is\b-anchored and dot-optional, soBBAandB.B.A.both match — that is intended). -
src/lib/heuristics/extract/education.ts— add a dash boundary tostripInstitutionLocation. A newDASH_US_REmirroringMIDDOT_US_RE's shape, matching an em/en dash with surrounding whitespace:const DASH_US_RE = /\s+[—–]\s+([A-Z][A-Za-z.\-]+(?:\s+[A-Z][A-Za-z.\-]+)*),\s*([A-Z]{2})$/;Require whitespace on both sides — a bare
–with no spaces is a hyphenated proper noun (Urbana-Champaignuses an ASCII hyphen, butWilkes–Barreis drawn with an en dash on some templates) and must not split. Add the international counterpartDASH_INTL_REalongsideMIDDOT_INTL_RE. -
Same file — accept a spelled-out US state. The four US branches hard-code
([A-Z]{2})$. Introduce one shared trailing-region alternation that accepts either a USPS code or a full state name, validated against a state-name set (mirror howCOUNTRY_GAZETTEERis consulted — do NOT accept an arbitrary capitalized word, orOhio Valley State University — Board Memberwould split). Normalise the emittedlocationto whatever the page drew; do not expand or abbreviate it. -
Flip the three ground-truth entries to
status: "open"with this issue's number:tests/fixtures/pdfs/unknown/name-set-apart-tagline.truth.json→education.degree,education.institutiontests/fixtures/pdfs/unknown/single-column-title-below-anchor.truth.json→education.institution
If the fix actually lands in the same PR, delete the three entries instead and correct nothing else in the truth values — a truth file records what the page draws and is never edited to match the parser (see each file's
provenance). -
Lower
UNFILED_TRUTH_CEILINGinsrc/lib/heuristics/corpus.test.ts:186by 3 (10 → 7). Its docblock says explicitly: "File the issue and flip the entry toopen; then lower this." Other issues filed from the same audit also lower it — expect a rebase conflict on that one line and resolve by taking the LOWER number.
Acceptance criteria
-
DEGREE_REmatchesB.F.A.,BFA,M.F.A.,B.B.A.,B.C.A.,LL.B.,LL.M.,D.D.S., with a unit test per credential insrc/lib/heuristics/regex.test.ts. -
DEGREE_REstill matches every credential it matched before — no alternation reorder regressesB.Sc.→B.S(the file's existing NOTE names this exact failure). -
name-set-apart-tagline.pdfparsesinstitution = "State University"anddegree = "B.F.A."withfield = "Graphic Design". -
single-column-title-below-anchor.pdfparsesinstitution = "Ohio Valley State University"andlocation = "Columbus, Ohio". - A dash with no surrounding whitespace does NOT split:
"Wilkes–Barre College"stays one institution. Unit test insrc/lib/heuristics/extract/education.test.ts. - A dash followed by a non-locality does NOT split:
"State University — Board of Regents"stays one institution. -
multi-degree-coursework.pdf(the #366 fixture) still splits institution/location on the 1-space boundary — theSPACE1_US_REguard is untouched. -
npx vitest run src/lib/heuristics/corpus.test.tspasses: the truth scoreboard'seducation.institutionandeducation.degreerecall do not fall on any fixture, andTRUTH_ANNOTATED_FIELD_FLOORstill holds. -
npx vitest run src/lib/heuristics/corpus-roundtrip.test.tspasses with no newKNOWN_FAILURESbaseline rows. Adding a row to make this green is a regression, not a fix (src/lib/heuristics/corpus-roundtrip.known-failures.json_readme). -
npm run check:baselinesreports 3 fewer unfiled entries. -
npm run verifypasses.
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- Avg merge
- 1d 24m
- Merged PRs (30d)
- 71
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
-
chore gaal
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
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Maintainers usually reply within 1 day
-
Download PDF: preview the exact exported PDF, all pages, before saving itPossibly taken @s-annam claimed this today. Openfeature gaal ready-for-agent ux:edit-export
offlinecv/OfflineCV#1077 · 1 assignee ·
Maintainers usually reply within 1 day
-
chore gaal refactor
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Maintainers usually reply within 1 day
All issues in offlinecv/OfflineCV
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
rohitg00/agentmemory#1428 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
boxlite-ai/boxlite#1729 ·
Maintainers usually reply within 1 day
-
detectors enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
SM260845/readme-gen#1 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
angular/angularfire#3774 ·
Maintainers usually reply within 2 days