Document repair lab: five one-change fixes, measured before and after
In five one-change repairs of one synthetic resume, rebuilding two columns or a layout table as one column raised Luceria's reading-order score from 54.5% to 95.5%; moving the contact line from a Word page header into the body made the email and phone readable; removing a running header cleared the repeated-contact flag; and a PDF whose text was drawn as outlines went from refused, with no text layer, to fully read once it was exported as real text. Token recall was already complete in three of the five before files, so most repairs change order and fields rather than whether the words are found.
Watch it
- Watch a reading-order repair: two columns rebuilt as one
A 38-second recording with no sound; every step is also written out on the page.
Results
Environment: Node v24.16.0 on Windows x64; lib/extract.ts with pdf-parse 2.4.5 (PDF) and mammoth 1.12.0 (Word); fixtures generated with docx 9.6.1, pdfkit 0.18.0 and fontkit 2.0.4; one run.
- Fixtures measured
- 12
- Two controls and five before/after pairs of one fictional resume.
- Repairs measured
- 5
- Each changes one stated variable.
- Reading order, two columns to one column (PDF)
- 54.5% → 95.5%
- The remaining gap is the stacked contact details, which never match the single expected contact line.
- Reading order, layout table to one column (Word)
- 54.5% → 95.5%
- Same content and styles; the table cells were read sidebar first.
- Email and phone readable, header to body (Word)
- no → yes
- The page header was not extracted, so contact details placed only there were lost.
- Repeated-contact flag, running header removed (PDF)
- raised → not raised
- Text recall was complete either way; the header only duplicated the contact line.
- Text extracted, outlines to real text (PDF)
- refused → 100.0% token recall
- The outlined file has no text layer, so the extractor refused it instead of returning an empty result.
- Fixture hashes reproduced by --verify
- 12 of 12
- Same machine, separate processes and two time zones; another machine not yet tested.
| Repair | Symptom | Variable changed | Token recall | Reading order | Email / phone | Flags removed |
|---|---|---|---|---|---|---|
| r1 | order-jumbled | Column layout: two columns drawn as two text streams → one column in reading order | 100.0% → 100.0% | 54.5% → 95.5% | yes / yes → yes / yes | none |
| r2 | order-jumbled | Document structure: a two-cell layout table → the same paragraphs in one column in reading order | 100.0% → 100.0% | 54.5% → 95.5% | yes / yes → yes / yes | none |
| r3 | contact-missing | Contact location: page header → document body | 95.5% → 100.0% | 95.5% → 100.0% | no / no → yes / yes | none |
| r4 | duplicate-text | Running header: contact line repeated on every page → none (contact once in the body) | 100.0% → 100.0% | 100.0% → 100.0% | yes / yes → yes / yes | repeated-contact |
| r5 | empty-or-image | Text layer: glyph outlines with no text objects → real text at the same positions | 0.0% → 100.0% | 0.0% → 100.0% | no / no → yes / yes | none |
| Fixture | Role | Extracted | Token recall | Reading order | Email / phone | SHA-256 |
|---|---|---|---|---|---|---|
| control-docx | control | yes | 100.0% | 100.0% | yes / yes | adba30f38b3d |
| control-pdf | control | yes | 100.0% | 100.0% | yes / yes | 089a0aefdae0 |
| r1-before | before | yes | 100.0% | 54.5% | yes / yes | e26a9e2e18b1 |
| r1-after | after | yes | 100.0% | 95.5% | yes / yes | 1f812df5eab2 |
| r2-before | before | yes | 100.0% | 54.5% | yes / yes | 93c6be019f71 |
| r2-after | after | yes | 100.0% | 95.5% | yes / yes | ebfaa47a2e95 |
| r3-before | before | yes | 95.5% | 95.5% | no / no | 55e47ffdef72 |
| r3-after | after | yes | 100.0% | 100.0% | yes / yes | adba30f38b3d |
| r4-before | before | yes | 100.0% | 100.0% | yes / yes | 2c25030556a0 |
| r4-after | after | yes | 100.0% | 100.0% | yes / yes | 2c3ff223cc6d |
| r5-before | before | no (no text layer) | 0.0% | 0.0% | no / no | 8def9a235951 |
| r5-after | after | yes | 100.0% | 100.0% | yes / yes | 62ee08b2c4e9 |
Method
Fixtures
Twelve synthetic files of the same fictional resume used by the parser benchmark: one Word and one PDF control, and five before and after pairs. Each pair changes exactly one stated variable: column layout, a layout table, where the contact line sits, a running header, or whether the text exists as text at all. In the column and table repairs the sidebar sections move into reading order, because that is what rebuilding in one column means.
The text-layer pair draws the same lines with the same embedded font (Liberation Sans, SIL Open Font License) at identical positions, once as filled glyph outlines with no text objects, the way a scan or an outlined export looks to a parser, and once as real text.
Extraction and measures
Each file is read by lib/extract.ts, the extractor behind Luceria's upload route (pdf-parse for PDF, mammoth for Word). Token recall, reading order, bullets, headings and contact readability are computed by the same code as the parser benchmark (scripts/research/shared/measure.ts), and the layout flags come from the parser preview tool's heuristics. A file the extractor refuses for having almost no text is recorded as refused, not scored as an empty pass.
Reproducibility
PDF metadata dates are fixed and Word files are re-packed with fixed archive dates, so the recorded library versions produce byte-identical files. The manifest lists every file's SHA-256 hash, the generator and parser versions and their licences, and the font file's hash. Running the lab with --verify regenerates every file in memory and compares hashes and measures with the published copies; it reproduced all twelve in separate processes and under a second time zone on the measuring machine. Another machine has not yet been tested.
Limitations
- One fictional resume and twelve synthetic files. Each repair changes one stated variable; real documents combine several problems and vary in fonts and authoring software.
- This measures Luceria's own extractor. It does not test any employer's applicant tracking system, which may use a different parser with different behaviour, and it cannot predict hiring outcomes.
- Outlined text stands in for scans and photographs: a parser without optical character recognition, which Luceria does not run, treats them the same way.
- Reading order uses the parser benchmark's measure, which credits a line the extractor split in two when a fragment holds more than 30 characters of it; scored strictly on whole lines the figures could be lower, and this study does not publish its extracted text. Contact details stacked on three lines never match the single expected contact line, so the after scores for the column repairs stop at 95.5 percent.
- Hashes are reproducible with the recorded library versions only; a different version of docx, pdfkit, fontkit or JSZip may produce different bytes with the same text.
Corrections
None so far. Spotted an error? Tell us and it will be checked against the data and recorded here.
Related documentation
- Greenhouse: unsuccessful resume parse (vendor documentation of the same layout causes for its own parser)Greenhouse Software · checked 4 November 2026
- Lever: understanding resume parsing (image files cannot be parsed; unselectable text is likely unparseable)Lever · checked 4 November 2026
Common questions
Will these fixes make an employer's system read my resume?
They remove the specific problems measured here for Luceria's parser. Employer systems use different parsers, so check your own file with the parser preview and, where an employer documents its own upload rules, follow those.
Why change only one thing per repair?
So the measured difference belongs to that change. Real files often combine problems; fix them one at a time and re-check after each.
Keep going
- See what the parser reads from your own file
- Parser benchmark: nine layouts measured
- Tables, columns and graphics in a resume
- Where contact details belong
Published 6 November 2026 · Results are generated by code from version-controlled fixtures; the data files above are the record of what was measured. Write-up drafted with AI assistance and checked against Luceria’s editorial standards.