Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[parser] education — an em-dash separator on the institution line is never split: the degree is lost on one shape, the location on another

Open
#831 0 comments 0 reactions 0 assignees View on GitHub

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

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

batch:parser-education bug gaal ux:parsing

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

  1. src/lib/heuristics/regex.ts — widen DEGREE_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. precedes B.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 precede B.A., M.F.A. before M.A., B.B.A. before B.A., LL.M. before LL.B. is irrelevant but keep them adjacent. Verify no new branch can match inside an ordinary word (each is \b-anchored and dot-optional, so BBA and B.B.A. both match — that is intended).

  2. src/lib/heuristics/extract/education.ts — add a dash boundary to stripInstitutionLocation. A new DASH_US_RE mirroring MIDDOT_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-Champaign uses an ASCII hyphen, but Wilkes–Barre is drawn with an en dash on some templates) and must not split. Add the international counterpart DASH_INTL_RE alongside MIDDOT_INTL_RE.

  3. 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 how COUNTRY_GAZETTEER is consulted — do NOT accept an arbitrary capitalized word, or Ohio Valley State University — Board Member would split). Normalise the emitted location to whatever the page drew; do not expand or abbreviate it.

  4. 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.institution
    • tests/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).

  5. Lower UNFILED_TRUTH_CEILING in src/lib/heuristics/corpus.test.ts:186 by 3 (10 → 7). Its docblock says explicitly: "File the issue and flip the entry to open; 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_RE matches B.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 in src/lib/heuristics/regex.test.ts.
  • DEGREE_RE still matches every credential it matched before — no alternation reorder regresses B.Sc. → B.S (the file's existing NOTE names this exact failure).
  • name-set-apart-tagline.pdf parses institution = "State University" and degree = "B.F.A." with field = "Graphic Design".
  • single-column-title-below-anchor.pdf parses institution = "Ohio Valley State University" and location = "Columbus, Ohio".
  • A dash with no surrounding whitespace does NOT split: "Wilkes–Barre College" stays one institution. Unit test in src/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 — the SPACE1_US_RE guard is untouched.
  • npx vitest run src/lib/heuristics/corpus.test.ts passes: the truth scoreboard's education.institution and education.degree recall do not fall on any fixture, and TRUTH_ANNOTATED_FIELD_FLOOR still holds.
  • npx vitest run src/lib/heuristics/corpus-roundtrip.test.ts passes with no new KNOWN_FAILURES baseline 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:baselines reports 3 fewer unfiled entries.
  • npm run verify passes.
Dominant language
TypeScript
Stars
11
Forks
4
Avg merge
1d 24m
Merged PRs (30d)
71

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from offlinecv/OfflineCV

All issues in offlinecv/OfflineCV

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.