Skip to content

fix(parser): GPA and honors are dropped on parse and lost from the export #883

Description

@s-annam

Problem

GPA and Latin honors are silently dropped on parse. They never reach the canonical résumé, so they are absent from the reconstructed résumé, from the export, from the JSON Resume attachment, and from JD matching. A source entry like:

The University of Texas at Dallas                                   May 2024
B.S. in Computer Science, cum laude, GPA: 3.72/4.00

round-trips to a degree and an institution. cum laude and 3.72/4.00 are gone, with no flag and no finding — the user is not told anything was lost.

Evidence

  • ResumeEducation (src/lib/score/types.ts) has no gpa and no honors field.
  • It does have description?, but nothing writes it: src/lib/heuristics/extract/education.ts never assigns a description, and openresume.ts passes extractEducation's value straight through. The field is read (src/lib/jd-match/coverage.ts folds it into coverage text) and never populated — a dead input.
  • GPA appears in extract/education.ts only as a negative signal: isEntryLead and isInlineDatedProgram both reject a line matching /^(GPA[:\s]|Minor\b|Major\b)/i so it does not split off a phantom entry. Correct as far as it goes — the line is stopped from becoming an entry, but nothing then collects it.
  • src/lib/pdf/ats-resume-model.ts builds an education entry's bullets from edu.coursework only, so even a populated description would not reach the PDF.

So this is not a rendering gap. The data is discarded at Tier 1.

Why it matters beyond the export

GPA is a screening field. A new-grad résumé that states a 3.9 and re-exports without it is materially weaker, and the anonymous score has no visibility into it either. Latin honors is the same class of signal. Both are stated by the candidate on the page in front of us; dropping them is data loss the user did not ask for.

Proposal

  1. Model — add gpa?: string and honors?: string to ResumeEducation. Keep GPA a string, not a number: 3.72/4.00, 3.9/4.0, 8.4/10, First Class and 2:1 are all real, and normalising to a float throws away the scale and invents precision. Store what the résumé said.
  2. Extract — collect them off the degree line and off a standalone annotation line, at the same place the existing /^(GPA[:\s]|Minor\b|Major\b)/i guards already recognise the shape, so recognition lives in one predicate rather than two that can drift. Honors: the Latin set plus with honors / with distinction.
  3. Edit — expose both as EditableFields on the education entry in ReconstructedEducationSkills.tsx, with the "+ add" affordance the other cleared fields use, so a parse that missed one is fixable.
  4. Export — compose the sub-line as <degree>, <honors>, GPA: <gpa>, matching the shape the source used. Round-trip: the composed line must re-parse back into the same three fields.
  5. JSON Resumeeducation[].score carries GPA in the spec; map it in to-json-resume.ts. Honors has no spec field; do not invent one.

Open question for whoever picks this up

Whether description should be deleted or populated. It is currently a field the model declares, nothing writes, and one consumer reads — which is worse than not having it, because coverage.ts looks like it handles education prose and in fact never can. Decide one way; do not leave it as is.

Acceptance criteria

  • GPA: 3.72/4.00 and cum laude on a degree line parse into gpa / honors and survive parse → export → re-parse unchanged.
  • A standalone GPA: 3.9/4.0 annotation line under an entry is captured too, and still does not split off a phantom entry.
  • Non-4.0 scales (8.4/10), bare values (3.9), and classifications (First Class, 2:1) are preserved verbatim, not normalised.
  • Both fields are editable in the reconstructed résumé, including adding one the parse missed.
  • GPA maps to education[].score in the JSON Resume export.
  • description is resolved — either populated or removed along with its coverage.ts read.
  • Round-trip corpus green, no new KNOWN_FAILURES rows.
  • npm run verify exits 0.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingux:parsingUX program: parsing accuracy as the user experiences it

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions