Skip to content

academic-drain: mech-first shortcut — skip VLM OCR on pages with a clean text layer - #150

Merged
mecattaf merged 1 commit into
mainfrom
academic-drain-mech-first
Aug 5, 2026
Merged

academic-drain: mech-first shortcut — skip VLM OCR on pages with a clean text layer#150
mecattaf merged 1 commit into
mainfrom
academic-drain-mech-first

Conversation

@mecattaf

@mecattaf mecattaf commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Closes #147.

resolvePage() now runs mech first; when poppler and mupdf both clear a word floor and agree with each other (Dice, reusing compare.sh), the page resolves as source:mech with no render and no GPU node. Raster moves inside the VLM lane, so shortcut pages produce no PNG at all. Fallback to the existing vlm8b → compare → vlm32b ladder is unchanged for scanned pages, sparse text layers, and engine disagreement.

Threshold calibration (2,374 already-drained pages)

thr floor coverage mech-vs-VLM <700‰ residual
930 0 90.9% 2.1%
930 200 77.8% 0.3%
970 0 69.1% 2.4%

The floor, not the Dice gate, is the safety-critical half: the bad tail (figure pages, covers, LinkedIn-style print-to-PDFs) self-agrees at a perfect 1000‰ on a text layer that doesn't cover the visual content, but runs sparse. All 6 residual pages at 930/200 resolved vlm32b-disputed under the old flow anyway. The floor also keeps compare.sh's #127 truncation guard voucher-eligible on every shortcut page.

Also fixed

drain.sh's supersede handler only matched args-changed-mid-run; shipping a new flow script pins out every persisted run-id with script-changed-mid-run, which burned the 3-consecutive-failure fuse instead of rotating. Now both variants rotate.

Live validation on the coordinator

Two 11-page papers drained end-to-end on the built package: 51s and 101s wall-clock vs ~13min at the old pace. 21/22 pages resolved mech; the one page under the word floor (159 words) correctly took the VLM lane (exactly one render produced, resolved vlm8b); supersede rotation observed once on the stale-pinned paper; absorb epilogue committed both to notes.

At ~78% shortcut coverage the remaining 3,003-paper / ~193k-page queue drops from an estimated ~145–170 GPU-days to roughly 3–4 weeks of GPU-bound work, dominated by the genuinely-scanned minority.

🤖 Generated with Claude Code

…oven text layer (#147)

paper-e2e.js resolvePage() now runs mech first and resolves source:mech
without rendering or any GPU node when both engines clear a word floor and
agree with each other; raster moves inside the VLM lane. compare.sh grows an
optional [min-words] arg for the floor. drain.sh passes
mechSelfAgreementPermille=930 mechMinWords=200 and bumps max-nodes to
7*pages+30.

Calibration on 2,374 already-drained pages: the 930/200 gate shortcuts 78%
of pages with 0.3% residual mech-vs-VLM disagreement; the floor, not the
Dice threshold, is the safety-critical half (the bad tail — figure/cover/
UI-chrome pages — self-agrees at 1000 permille but runs sparse). All
residuals were vlm32b-disputed under the old flow anyway.

drain.sh's supersede handler now also matches script-changed-mid-run:
shipping a new flow script invalidates every persisted run-id pin, which
previously burned the 3-failure fuse instead of rotating.

Live validation (2026-08-04): two 11-page papers drained end-to-end on the
new flow — 51s and 101s wall-clock vs ~13min at the old pace; 21/22 pages
resolved mech, the one sub-floor page (159 words) correctly took the VLM
lane; supersede rotation observed on a stale-pinned paper; absorb epilogue
committed both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mecattaf
mecattaf merged commit b5cfc43 into main Aug 5, 2026
@mecattaf
mecattaf deleted the academic-drain-mech-first branch August 5, 2026 12:16
mecattaf added a commit that referenced this pull request Aug 6, 2026
Tom's ruling, 2026-08-06: tables route to the VLM lane. This commit is that
ruling's only record — the issue describing it was deliberately deleted.

The defect. On a source:mech page the text layer is extracted column-major:
every value in a table survives, and the row/column association does not. The
mech-first shortcut (#150, b5cfc43) cannot see this. Its gate asks whether
two mechanical engines agree, and poppler and mupdf linearize a table
identically — they agree perfectly, at 1000 permille, on a reading in which
the numbers no longer belong to the rows they are printed in. Nothing
downstream recovers it, and the result reads as authoritative in the canonical
corpus. Side by side: paper 28666de6 page 7 (mech: column headers, then all
row labels, then a bare column of 24 values) against e3969f95 pages 5-9
(vlm8b: proper GFM tables).

The fix. After the self-agreement gate passes, tables.sh looks for table
evidence in the mechanical text; if it finds any, the page falls through to
the VLM lane, which sees the page as a picture and emits a real GFM table.
Mech-side table extractors (camelot, pdfplumber) were considered and not
chosen.

Two signals, calibrated 2026-08-06 against 2,181 already-drained pages that
cleared the word floor and went to the VLM anyway, labelled by whether the VLM
output actually contains a GFM table (240 do):

  caption alone       76.2% recall  2.0% false positive
  caption OR run>=4   85.0% recall  3.6% false positive   <- shipped
  caption OR run>=6   82.1% recall  3.1% false positive

The second signal — consecutive bare-numeric lines — is the direct fingerprint
of column-major linearization, which emits one cell per line. It buys 9 points
of recall for 1.6 of false positives, and the trade is not symmetric: a false
positive costs one VLM page, a false negative costs a silently scrambled table
in the corpus. Per-line numeric *density* was tried and dropped — it fired on
author-affiliation numbering and stats-in-prose, and every sampled hit it
added was wrong.

Cost, honestly. Measured over every page that has resolved source:mech to
date: 463 of 2,662, so 17.4% of would-be-shortcut pages now take the VLM lane.
That is above the ~12% quoted when the ruling was made. The caption signal
alone is 15.6% on this corpus, not 12%, so most of the gap predates the
numeric-run half, which adds the last 1.8 points. Flagging the number, not
re-deciding the ruling.

Verified on the live flow (paper 28666de6, scratch state root, new package):
the gate's per-page verdicts reproduce the standalone prediction exactly —
pages 6, 7, 8, 9, 11 carry evidence and are the only five rastered, the other
nine shortcut on mech as before.

Also here: backfill-tables.sh, the one-time repair driver for pages already
receipted as source:mech on a linearized table (462 pages across 142 papers at
ship time). It re-runs whole papers through paper-e2e.js on a fresh flow-run
id rather than carrying a second copy of the per-page ladder, and re-absorbs
what absorb.py cannot (a repair overwrites an already-placed note). Not wired
to any timer; running it is an operator step after this ships.

Per-page worst case is now 8 nodes, and the assembler stamps
tally-flow-e2e-2026-08-06 so a repaired paper.md is distinguishable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

academic-drain: brute-force VLM OCR on every page — skip pages with a clean digital text layer

1 participant