Skip to content

fix: gate low-quality PDF extraction before OCR fallback - #105

Merged
JordanCoin merged 5 commits into
mainfrom
codex/issue-61-pdf-quality
Oct 19, 2026
Merged

JordanCoin merged 5 commits into
mainfrom
codex/issue-61-pdf-quality

Conversation

@JordanCoin

@JordanCoin JordanCoin commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Closes #61

Summary

  • apply configurable whole-document quality gates to direct PDF extraction
  • persist and forward PDF extraction thresholds, then force the fallback path to OCR after gate rejection
  • add sparse, symbol-heavy, whitespace-heavy, configuration, and fallback regression coverage

Verification

  • pytest tests/test_pdf_extract_json.py tests/test_document_pages.py -q (37 passed)
  • ruff check openfoia/config.py openfoia/pipeline/pdf_extract.py openfoia/pipeline/ingest.py tests/test_pdf_extract_json.py tests/test_document_pages.py
  • ruff format --check openfoia/config.py openfoia/pipeline/pdf_extract.py openfoia/pipeline/ingest.py tests/test_pdf_extract_json.py tests/test_document_pages.py
  • independent reviewer: Pass; no blockers

Evidence

Follow-up commits (Claude): e4c8959 failing tests, 43681b3 fix, cbb7902 bench script, 6f5b857 changelog.

What changed after review. The whole-document gates used to return None, which sent the whole file to OCR. Now they only judge plain output from an older pdf-extract with no per-page verdicts. With verdicts, garbled and image_only pages still go to OCR one at a time, the same as on main. Text that fails a gate is still sent to OCR, but if tesseract is missing or OCR returns nothing, the direct text is kept and the install-extras ocr hint prints once. docs ocr now reads the configured thresholds.

Real corpus (180 PDFs, 16,603 pages, pdf-extract 0.7.0; 1 file fails to extract under every policy). Kept = non-whitespace text-layer chars kept; OCR = pages sent to OCR.

python scripts/pdf_quality_bench.py ~/Code/glyph-api/test-pdfs
group files pages main kept / OCR pr kept / OCR fixed kept / OCR
complex 11 96 57,236 / 61 57,236 / 61 57,236 / 61
diversity 119 15,418 14,372,562 / 7,263 14,372,562 / 7,263 14,372,562 / 7,263
external 3 102 289,126 / 5 289,126 / 5 289,126 / 5
failing 3 30 60,382 / 3 60,382 / 3 60,382 / 3
foia 11 776 99,543 / 656 99,543 / 656 99,543 / 656
generated 4 145 1,972,025 / 1 1,972,025 / 1 1,972,025 / 1
passing 14 17 6,379 / 0 6,379 / 0 6,379 / 0
scrambled_cmap 10 14 14,265 / 4 14,265 / 4 14,265 / 4
tagged 5 5 839 / 1 839 / 1 839 / 1
total 180 16,603 16,872,357 / 7,994 16,872,357 / 7,994 16,872,357 / 7,994

files where the policy differs from main: pr 0, fixed 0
not extracted: exit 1 1

On this corpus none of the three policies differ: every file is either over the gates or already routed per page by its verdicts. So the corpus shows no gain from the gates, and no harm from them either. The harm shows up on tabular FOIA responses, which these hand-written fixtures reproduce:

python scripts/pdf_quality_bench.py tests/fixtures/pdf_quality
file pages main kept / OCR pr kept / OCR fixed kept / OCR
foia_log.pdf 1 1,355 / 0 0 / 1 1,355 / 0
mostly_blank.pdf 1 0 / 1 0 / 1 0 / 1
numeric_table.pdf 2 1,534 / 0 0 / 2 1,534 / 0
one_text_nine_scans.pdf 10 130 / 9 0 / 10 130 / 9
prose.pdf 1 324 / 0 324 / 0 324 / 0

As submitted, the PR OCR'd a FOIA log (alpha ratio 0.07) and a budget table (0.01), throwing away 2,889 exact text-layer chars. It also sent a 1-text-page + 9-scan file to OCR in full, all 10 pages. The fix matches main on all five.

Tests: 500 passed before, 508 passed after (pytest tests/ -q --deselect tests/test_extraction.py, 20 skipped). The fixture tests run through the real binary when pdf-extract is on PATH and skip cleanly when it is not.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 06:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

JordanCoin and others added 4 commits October 19, 2026 11:40
… gates

Red against 21564da. Real-binary fixtures (skipped without pdf-extract on
PATH): tabular text_layer pages keep their digits, one text page plus nine
scans OCRs only the scans. Fake-binary cases: a gate rejection with OCR
missing or empty keeps the direct text and hints once, garbage plain text
without verdicts still falls back to OCR, and `docs ocr` honours configured
thresholds. Existing gate tests now expect a flagged result, not None.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wdqj51cPquyTRKmoJLQV6y
The whole-document gates returned None for any failure, so ingest OCR'd
every page: tabular FOIA logs (alpha ratio well under 0.20) lost their
exact text-layer digits, one bad page sent a whole document to OCR, and
without tesseract the document was stored with no text while the
ImportError was swallowed.

- extract_text: with per-page verdicts only min_chars applies (verdicts
  already route garbled/image_only pages one by one). Plain output that
  fails the per-page or alpha gates comes back with quality_reasons set
  instead of None.
- ingest: gated text goes to whole-document OCR; if OCR is missing,
  fails, or finds nothing, the direct text is kept and the install-extras
  hint prints once.
- OCREngine.process_pdf (docs ocr): unset pdf_extract_* settings come
  from config instead of hardcoded defaults, and gated text is kept when
  OCR is unavailable or empty.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wdqj51cPquyTRKmoJLQV6y
Runs the real `pdf-extract --json` over a directory and reports, per file
and per top-level folder, text-layer chars kept and pages sent to OCR under
main's policy, #105 as submitted, and the fixed gates. Stdlib only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wdqj51cPquyTRKmoJLQV6y
@JordanCoin
JordanCoin merged commit 4a766f2 into main Oct 19, 2026
7 checks passed
@JordanCoin
JordanCoin deleted the codex/issue-61-pdf-quality branch October 19, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve PDF extraction fallback with quality gates (chars/page + alpha ratio)

2 participants