Talento Rosimar: reading résumés in the browser
An HR system for a food distributor. The hard part is the reader: it turns PDF, Word, scans and photos into forms that a person only has to review.
- Client
- Distribuciones Rosimar S.A.S., Barranquilla
- My part
- Development of the system, in a team of two. 50 of the repository's 119 commits1 are mine.
- Period
- 2026, ongoing
- Stack
- React, TypeScript, pdf.js, Tesseract.js, mammoth.js, Express, MariaDB
- Code
- Private
The problem
Rosimar receives résumés in every format: PDFs with text, scans, photos taken with a phone and Word files. Copying names, contact details, experience and education by hand is slow and error-prone. The system had to read those documents without paying for each one and without relying on an external language model.
It also had to keep working offline. That's why text recognition runs inside the browser (it's a PWA) and the Tesseract files are served from the application itself, not from a CDN.
How it works
The reader is a deterministic four-stage process. It uses no language model and has no cost per document.
- 1
Extraction
pdf.js for PDFs with text, Tesseract.js (WASM) for photos and scans, mammoth.js for Word.
- 2
Layout
Rebuilds lines and detects columns from the empty vertical gutter, so a sidebar is never mixed with the content.
- 3
Segmentation
Recognizes headings with a Spanish and English lexicon that tolerates OCR errors. With no headings, it classifies by content.
- 4
Extractors
One per field: names, contact, ID, dates, experience, education, languages. They use the DANE municipality list and a job-title dictionary.
What I measured, and what I hadn't
The first number I had was 165 of 165 fields correct2. It was true but misleading: that bench is ten PDFs generated by code, all with a perfect text layer, and that is not the path Rosimar uses. What arrives in practice is scans and photos, and that path hadn't been measured.
I built a second bench with 40 synthetic Colombian résumés turned into degraded scans (noise, skew, poor lighting, recompression), a third with 17 real photos and a fourth with 12 contracts. Latest documented measurement: September 30, 2026.
| Bench | Documents | Result |
|---|---|---|
| Digital PDFs, with a text layer | 10 | 165 of 165 fields2 |
| Synthetic scans | 40 | 89.8%3 (63.4%4 on the first measurement) |
| Contracts | 12 | 90.0%3 |
| Real photos | 17 | 75.9%3 |
Only 8 of the 17 photos are résumés; the rest are contracts, certificates and handwritten forms.
5/14
Classifier regression: 19.6%
Six of every nine résumés came out as an unstructured document and reached the form empty.
Chart data
| # | Overall accuracy (%) | % |
|---|---|---|
| 1 | First measurement | 63.4% |
| 2 | Local threshold (Sauvola) | 68.5% |
| 3 | Polarity detection | 72.5% |
| 4 | Email reconstruction | 73.9% |
| 5 | Classifier regression | 19.6% |
| 6 | Classifier fixed | 64.1% |
| 7 | Preprocessing by text | 68.4% |
| 8 | Phone by fragment | 69.1% |
| 9 | Orientation detection | 73.3% |
| 10 | Résumés without headings | 75.5% |
| 11 | Fourth round | 76.0% |
| 12 | Sixth round | 75.8% |
| 13 | Sixteenth, baseline | 78.2% |
| 14 | Sixteenth, final | 89.8% |
What showed up when I measured the real path
- pdf.js 6 uses a JavaScript function that Chrome 141 and earlier don't have, so scanned PDFs couldn't be read. A twelve-line fallback fixed it.
- A global Otsu threshold wiped out the hard scans. With a local Sauvola threshold, one document went from 8 to 2,528 characters read4.
- Tesseract has trouble with the at sign: 20 of the 40 documents4 ended up with no email until the value was rebuilt.
- A change in the document classifier dropped overall accuracy from 73.9% to 19.6%4. It was caught by running the bench again.
- An average of 83% similarity on names in the photos hid that only two of the 175 had a name to check, and on those two the reader failed. It was fixed afterwards.
Limits
- Real photos (75.9%) are still far from the synthetic scans (89.8%3).
- Handwritten forms don't read well. There is a TrOCR pilot that hasn't been tested against that bench yet.
- On a laptop with a Ryzen 7, each document takes between 16 seconds (photos) and 58 seconds (contracts)6.
- The scans are synthetic: they resemble real ones but don't replace them.
One idea stays with me from this project: a 100% on the wrong bench is worse than an honest 75.9%, because the first one says there's nothing to fix.
Sources for the figures
- 1
Commit authorship in cv-parser
git shortlog of rosimar1/cv-parser (private) · October 9, 2026
Fabian Gonzalez (my git user) 50, another team developer 41, Claude 27 and ImgBot 1: 119 in total.
- 2
Reader accuracy bench on digital PDFs
rosimar1/cv-parser, README and docs/MEDICION_LECTOR.md (private) · August 2026
Ten PDFs generated with jsPDF, all with a perfect text layer: this measurement does not go through OCR. It is compared against 165 fields of a versioned ground truth.
- 3
Reader measurement on scans, contracts and real photos
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, sixteenth round (private) · September 30, 2026
The 89.8% on scans was measured with parallel reads that were dropped afterwards; on contracts, the serial version gave 90.0% and the parallel one 89.9%. Of the 17 real photos, only 8 are résumés.
- 4
Accuracy over time on 40 synthetic scans
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, Resultado section (private) · August and September 2026
Includes the classifier regression (19.6%) and the later milestones on the same bench. The characters read (8 and 2,528) are from document CV_04, hard profile. The 20 documents with no email come from the section on the at sign.
- 5
The averaging mirage on names in the photos
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, sixth round (private) · September 2026
Only 2 of the 17 documents declare names in their ground truth; on those two the reader failed (0% and 20% similarity) until it was fixed.
- 6
Time per document
rosimar1/cv-parser, docs/MEDICION_LECTOR.md, sixteenth round (private) · September 30, 2026
15.7 s per photo and 57.8 s per contract, on a laptop with a Ryzen 7 5700U. With the same code, a contract took 48.8 s in the morning and 56.9 s in the afternoon.