Skip to main content

Restruct

PyPI version PyPI downloads PyPI wheel

CI Python versions License

Extract structured JSON from single-column resumes.

PDF (OCR) · DOCX · PNG · JPEG

Runs on your machine. No resume upload and no API key.


Animated walkthrough: a single-column resume is supported while multi-column and sidebar layouts are not reliable; Restruct reads text or OCR, detects document structure, extracts fields, and writes a stable resume.json file.

Restruct is built for flat, single-column resumes with one top-to-bottom reading order. It finds the document's sections and extracts names, contact details, jobs, dates, schools and skills.

The output follows one published JSON schema. A missing section does not become a missing key, and a layout the parser cannot read reliably is reported instead of silently rearranged.

  • Inputs: PDF, including scanned PDFs through OCR; DOCX; PNG; and JPEG.
  • Layout scope: one content column. Sidebars, multiple columns, text inside graphics and nested tables are detected as unsupported layouts rather than repaired by guesswork.
  • Local processing: after the model files are installed, resume extraction makes no network request.
  • Stable output: sixteen sections in a fixed order, validated against resume.schema.json.
  • Inspectable results: reconstruct the extracted JSON or draw the detected fields on the source page.

Why Restruct exists

Restruct grew out of my work on Open LinkedOut, a lightweight, local-first job scraping and matching system. Small local models consumed too much RAM and disk space while still hallucinating resume details. Restruct uses document structure and explicit patterns first. Local models are used only when the document itself does not settle the meaning of a span.


Built with

Python DistilBERT all-MiniLM-L6-v2 Tesseract OCR

PyMuPDF python-docx Local First


Install

uv add restruct-cv
uv run restruct --install-models

About those two commands

  • The distribution is restruct-cv; the command it installs is restruct. The unqualified name was already taken on PyPI by an unrelated project.
  • --install-models downloads the two models Restruct uses: 352 MB, once. They are ordinary local files, verified by checksum as they arrive.
  • It is the only command that touches the network. Everything after it works offline.

Scanned PDFs, PNG files and JPEG files contain pixels rather than readable text, so they also need Tesseract. A native-text PDF or DOCX does not. Each PNG or JPEG is treated as one resume page, with its original proportions and camera-orientation metadata preserved.

brew install tesseract                 # macOS
apt-get install tesseract-ocr          # Debian / Ubuntu

Use it

uv run restruct resume.pdf -o .

That writes resume.json next to you, and prints nothing.

-o takes a file or a directory:

You write You get
-o out.json exactly that file
-o . resume.json in the current directory
-o results/ resume.json inside results/

A directory gets <resume>.json, named after the input, so extracting several resumes into one place doesn't have each one overwrite the last.


See what was understood

uv run restruct resume.pdf -o . --reconstruct

A resume redrawn from the extracted JSON: name, titles and contact line at the top, then SUMMARY and EXPERIENCE sections with job title, employer, location, dates and bullets.

This draws the extracted result back out as a readable page, reconstruction.pdf plus a PNG per page, built only from what was extracted, with the original layout thrown away.

That's the whole point:

  • A bullet filed under the wrong section is obvious at a glance here, and invisible in a wall of JSON.
  • A date read as a job title shows up in the wrong line of the header.
  • Anything Restruct could not place is drawn in red under UNPLACED, rather than quietly dropped.

It is deliberately not a facsimile. Imitating the original layout would hide the very errors it exists to reveal.

You can also draw a result you already have, without re-extracting anything:

uv run restruct resume.json --reconstruct

Check it is ATS-friendly

uv run restruct resume.pdf -o . --ats

The same resume with coloured boxes drawn over it: name, job titles, phone, email and location in the header, then boxes around each section heading, each skill group and each bullet.

Alongside the JSON you get an overlay of every page, showing exactly what a parser could read and where it thought each section began and ended.

  • Boxes in the wrong places, or missing, are the layouts machines choke on: side-by-side columns, text inside a graphic, a table nested in a table.
  • Each box is labelled with the field it became: name, job_title, bullet, skill_group.
  • Heavier boxes are model conclusions. Lighter ones are things the document stated outright, so you can tell a guess from a fact.

Scans and photographs go through the same path. A PNG or JPEG is placed on a page of its own proportions and read as a scan, and OCR is rebuilt into the same geometry a native PDF produces, so a photographed page gets an overlay that looks like any other:

A scanned resume page with the same style of extraction overlay drawn on it.


What you get back

resume.json is plain data: no bounding boxes, no model names, no confidence scores.

{
  "schema_version": "1.0",
  "header_profile": { "name": "…", "emails": ["…"], "phones": ["…"] },
  "summary": null,
  "experience": [{ "job_titles": ["…"], "companies": ["…"], "bullets": ["…"] }],
  "education": [],
  "…": "…",
  "others": []
}
  • Sixteen sections, always present, always in the same order.
  • null when the resume has none, [] when the section exists but yielded nothing, never absent.
  • resume.schema.json is the published contract, and every release is validated against it.
  • A section Restruct isn't confident about goes to others, with its original heading kept, rather than being guessed into the wrong place.

Restruct is deliberately careful about what it claims. v1 targets single-column resumes, and a layout whose reading order can't be recovered is recorded as such, never silently repaired into something that reads plausibly and is wrong.


How accuracy is measured

A parser's failures matter more than its average. Restruct is scored per field, not with a single headline number that hides which fields to trust.

uv run tools/dev.py scorecard

This currently reports field-level precision, recall and F1 (name, dates, job titles, ...) and section-routing accuracy - the percent of content assigned to the right one of 16 fixed sections versus landing in others. Scored against 10 hand-labeled resumes in tests/labels/: enough to catch regressions, not enough to claim broad accuracy yet.

Next release: strict exact-match accuracy (no containment leniency) and OCR character/word error rate, once the labeled corpus is large enough to make those numbers meaningful rather than noisy.


From Python

from restruct import extract_resume

Failures are raised as typed exceptions, so embedding Restruct in a service doesn't cost you your process. The command-line tool turns those into exit codes instead, grouped by decade:

Code Meaning
0 success
1x input: not found, unsupported format, unreadable document
2x environment: models missing, Tesseract missing, download failed
3x extraction
4x output

Examples

Three extracted resumes are committed under examples/, one per kind of input:

Example Source What's in it
7.anomaly/ native PDF, 3 pages JSON and a page overlay per page
9.ocr/ scanned PDF the same shapes, recovered by OCR
11/ DOCX JSON only, because a DOCX has no page to draw on

Worth a look before installing anything.


Contributing

Contributions are welcome. See CONTRIBUTING.md for the setup, the test suite and the accuracy scorecard, and CLAUDE.md for the design decisions behind each module.

⚠️ Please do not submit real resumes, or labels derived from them. The fixtures in resumes-synthetic/ are synthetic and safe to commit.


License

MIT

Metadata

Release files for restruct-cv 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for restruct-cv 0.3.0
File Size Uploaded
restruct_cv-0.3.0.tar.gz 125.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for restruct-cv 0.3.0
File Interpreter ABI Platform
restruct_cv-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 282.7 kB

Release files / restruct_cv-0.3.0.tar.gz

Download URL restruct_cv-0.3.0.tar.gz
Size 125.9 kB
Tags Source
SHA-256 checksum
How to use checksums
736190ebfa4200f4f81b12bd1ab10154a7761ec68cc7a53d5ed4397ecfc38d65
BLAKE2b-256 checksum
How to use checksums
1fc875ba788069f068fe524808108e3735f30f4b6e0357364e7970a27a5199bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / restruct_cv-0.3.0-py3-none-any.whl

Download URL restruct_cv-0.3.0-py3-none-any.whl
Size 156.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fd02c106f842e631ffda93575bda0d88eefa6deee7468098313bcb36173fa53e
BLAKE2b-256 checksum
How to use checksums
475c760e306332e89131e52fdcfca12688fefc57de1ebd9ac4504276452cbd7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.2.3

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page