[parser] A comma in a role title corrupts our own export → re-parse round trip: the title is cleaved and company/team slide one slot
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- testing-qa, tooling
Research direction
Start with splitRoleComma in src/lib/heuristics/extract/experience-disambiguate.ts and its use from mapTitleFirst, then inspect composeRoleHeader and the Kilo case in role-header-cases.ts. Run src/lib/pdf/role-header-production-domain.test.ts and the #466 cases first; done means the Kilo row is invertible, the empty-company dialect still passes, snapshots are reviewed, and npm run verify is green.
Written by the indexing model from the issue text.
Description
Problem
A role title containing a comma — Director, Marketing / Engineer, Sr., one of the most
common real résumé header shapes — is corrupted by our own export → re-parse round trip.
The title is cleaved at the comma and every later field slides one slot over.
Measured on the real production leg (buildAtsResumeModel → renderAtsResumePdf →
runCascade):
| in | out |
|---|---|
{ title: "Kilo Engineer, Sr.", company: "Globex" } |
{ title: "Kilo Engineer", company: "Sr.", team: "Globex" } |
The company is gone, replaced by the title's own tail; the real company becomes the team.
Why it happens
The exporter composes the default dialect as Title · Company, Location · Team
(src/lib/resume-format/role-header.ts → composeRoleHeader; on main this is inline in
src/lib/pdf/ats-resume-model.ts, the empty-company branch at :790 and the default join at
:811). ORG_COMMA is load-bearing: it is what marks the company↔location boundary for the
re-parser.
On the way back, splitRoleComma
(src/lib/heuristics/extract/experience-disambiguate.ts:386, used from mapTitleFirst
at :780) cleaves segment 0 at its comma — it cannot tell a Title, Team empty-company
header (#466, where the comma IS the separator) from a title that simply contains a comma.
Both are <text>, <text> in segment 0.
So the two dialects are genuinely ambiguous as composed: the format has no way to say
"this comma is user text, not a separator."
Scope
- Round trip only. A first parse of a third-party PDF is not affected — this is the
re-parse of a résumé our own Download-PDF drew. - Affects the reconstructed-résumé round trip (
corpus-roundtrip.test.tslane) and
anything that re-ingests an exported file (saved-library re-parse, the render-hop gate).
Where it is already pinned
PR #922 added src/lib/pdf/role-header-production-domain.test.ts, which runs the real leg.
The corruption is recorded as measured data in
src/lib/resume-format/__test-utils__/role-header-cases.ts →
PRODUCTION_DIVERGENT_CASES, row Kilo, and stated as clause 4 of the invertible domain
in role-header.ts's docblock. It is pinned as current behaviour, not as desired behaviour
— so a fix must move that row out of PRODUCTION_DIVERGENT_CASES and into
INVERTIBLE_CASES, which is exactly the regression signal.
The same table records two further divergences, both far narrower and both arguably
acceptable losses:
- a team containing a middot loses everything after the first middot (row
India); - untrimmed field padding does not survive (row
Lima).
This issue is about the comma case, because that one is a common shape and loses a whole
field.
Directions for a fix (not yet decided)
- Disambiguate at compose time. Only the empty-company dialect uses a bare
Title, Team; the default dialect already has a middot before the org run.splitRoleComma
could be gated on the absence of a middot segment, which is information the parser
already has. - Escape or avoid the comma at compose time for the empty-company dialect — pick a
separator user text cannot collide with. Changes rendered bytes, so it needs its own
reviewed snapshot sweep (same constraint as the date-range dialects, #649 step 3). - Prefer the longer title. When segment 0 splits at a comma AND a later segment exists,
the tail is much more likely to be title text than a company.
Option 1 looks cheapest and touches no rendered byte; it needs checking against the #466
regression the comma dialect exists to fix, and against real fixtures, not just the
synthetic rows.
Acceptance criteria
-
{ title: "Kilo Engineer, Sr.", company: "Globex" }round-trips exactly through the
realbuildAtsResumeModel → renderAtsResumePdf → runCascadeleg. - The
Kilorow moves fromPRODUCTION_DIVERGENT_CASEStoINVERTIBLE_CASES, and
clause 4 ofrole-header.ts's domain docblock is narrowed or dropped accordingly. - The #466 empty-company
Title, Teamdialect still round-trips (its own rows stay in
INVERTIBLE_CASES). - Corpus snapshots reviewed: any byte change is deliberate and re-baselined in the same
PR. -
npm run verifygreen.
Found during the PR review of #922, which discovered and documented the divergence but
correctly left the fix out of scope.
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 66
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
-
gaal 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
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Maintainers usually reply within 1 day
All issues in offlinecv/OfflineCV
Similar issues
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 88/100
StabilityNexus/Fate-EVM-Frontend#153 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
code-yeongyu/oh-my-openagent#9039 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Tencent/teamai-cli#862 ·
Maintainers usually reply within 1 day
-
bug good first issue hacktoberfest redis
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#1164 ·
Maintainers usually reply within 1 day
-
flake
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day