Building an open-source Reducto alternative on a general-purpose model: the full record¶
Every stage of the project from 11 to 14 November 2026: what we set out to prove, what we built, each idea we tested, what we kept, what we threw away and why, and what the complete public benchmarks finally said.
All numbers are our own measurements unless marked "Reducto published". They come from the project's report files and its experiment ledger (272 entries, listed in Appendix D). Per-document scores for the final benchmarks are in results/.
The short version¶
We set out to build an open-source, self-hostable, drop-in replacement for Reducto, a YC-backed document parsing API, on a general-purpose model (Claude Haiku 5.5) instead of a purpose-trained one. The thesis was that a general model plus a good software layer around it can match a specialised model, at lower cost, and keep improving as the general models improve.
5,300+pages across 5 public benchmarks, run on all three systems
272experiments in the ledger: 75 dropped, 80 kept, 55 options, 62 checks and combine steps
≈ 1/2our engine's price per page against Reducto r-1's $0.01
1/5 – 1/9plain Haiku's price per page against Reducto r-1
- Plain Haiku already reads pages as well as Reducto's own model. On the complete public benchmarks it is level on tables and general pages, and better on text, for a fifth to a ninth of the price.
- Where plain Haiku loses is page structure: where each block sits, what type it is, which form answer belongs to which question, and which text is a header or footer.
- Our engine closes most of that structure gap (layout +23 to +35 points over plain Haiku, forms +6, general pages +2), at about half Reducto's price. It is still about 6 points behind Reducto on layout, behind on formulas and on headers and footers, and several times slower.
- Most clever ideas failed. Of everything tried, the large majority either did nothing or made things worse. Several gains seen on small samples disappeared on the full benchmarks.
- The biggest single correction: on 100 tables our engine looked ahead of Reducto; on all 1,000 tables it was level with plain Haiku and slightly behind Reducto.
Final comparison table (complete public benchmarks)¶
Our engine = profile engine-final-4, shipped as engine. Plain Haiku = one Haiku 5.5 read per page at high effort, shipped as model-only. Reducto = r-1 through its live API. Green = best in the row. "Level" means the gap is inside the 95% interval.
Reading the content¶
| Benchmark | Measure | Reducto r-1 | Plain Haiku | Our engine | Verdict |
|---|---|---|---|---|---|
| RD-TableBench (1,000 tables) | table score | 88.9 | 88.2 | 88.2 | level |
| olmOCR-bench (1,403 pages) | overall (official score) | 80.9 | 82.2 | 84.2 | engine ahead of both |
| olmOCR-bench | multi-column pages | 65.6 | 79.8 | 81.0 | both ours far ahead |
| olmOCR-bench | long tiny text | 84.2 | 93.2 | 93.9 | both ours ahead |
| olmOCR-bench | maths (arXiv) | 85.3 | 87.7 | 88.3 | ours ahead |
| olmOCR-bench | maths in old scans | 80.8 | 87.6 | 86.9 | level |
| olmOCR-bench | old scans | 49.0 | 47.5 | 50.4 | level |
| olmOCR-bench | tables | 92.7 | 91.7 | 92.2 | level |
| OmniDocBench (1,651 pages) | text error (lower is better) | 0.095 | 0.081 | 0.081 | ours ahead |
| OmniDocBench | tables | 87.9 | 85.4 | 87.0 | level |
| OmniDocBench | formula error (lower is better) | 0.133 | 0.158 | 0.156 | Reducto ahead |
| OmniDocBench | reading-order error (lower is better) | 0.161 | 0.167 | 0.170 | level |
Structure of the page¶
| Benchmark | Measure | Reducto r-1 | Plain Haiku | Our engine | Verdict |
|---|---|---|---|---|---|
| olmOCR-bench | headers and footers left out | 90.1 | 70.4 | 81.2 | Reducto ahead; engine +11 over plain |
| FUNSD (199 scanned forms) | question paired with its answer | 78.6 | 71.6 | 77.4 | engine level with Reducto; +6 over plain |
| FUNSD | words read | 97.6 | 97.2 | 97.3 | level |
| OmniDocBench | layout: block types right | 84.6 | 55.6 | 78.3 | Reducto ahead; engine +23 over plain |
| OmniDocBench | layout: boxes in the right place | 67.2 | 26.5 | 61.1 | Reducto ahead; engine +35 over plain |
Extraction from long documents (the same 10 documents, standard Extract, no deep mode)¶
| Measure | Reducto r-1 | Plain Haiku | Our engine | Verdict |
|---|---|---|---|---|
| rows found | 53.9 | 75.4 | 75.9 | not conclusive on 10 documents |
| wrong rows avoided (precision) | 90.8 | 93.1 | 92.4 | level |
| values correct in found rows | 92.9 | 92.8 | 90.6 | level |
Cost and speed per page¶
| Benchmark | Reducto $ | Plain Haiku $ | Engine $ | Reducto s | Plain Haiku s | Engine s |
|---|---|---|---|---|---|---|
| RD-TableBench | 0.0100 | 0.0012 | 0.0044 | 4.1 | 8.4 | 50.8 |
| olmOCR-bench | 0.0100 | 0.0018 | 0.0029 | 5.4 | 28.3 | 23.6 |
| FUNSD | 0.0100 | 0.0011 | 0.0035 | 4.6 | 9.3 | 21.8 |
| OmniDocBench | 0.0100 | 0.0021 | 0.0045 | 5.8 | 26.0 | 34.7 |
| LongExtract | 0.0200 | 0.0152 | 0.0191 | 66 / doc | 2,382 / doc | 2,136 / doc |
Times are median seconds per page (per document for LongExtract), measured on a heavily loaded laptop, so ours are slower than a quiet server would be. Pages that failed for both our systems (12 oversized images, 40–47 pages Anthropic's content filter blocked) count as empty in these scores.
The lessons¶
1. Small samples lie, in both directions. On 100 tables the engine beat Reducto (89.8 / 91.8 against 89.2 / 91.5). On 1,000 it was level with plain Haiku and 0.7 behind Reducto. The tuned gains were partly tuning to those 100 tables. Rule we adopted: a claim needs the complete benchmark, and the part of it we never looked at.
2. AI judges are noisy and sometimes plainly wrong. A hand check found the Sonnet judge misread 27,174,839 as 27,174,339 and gave a page 10/10, and it scored a wrong NPI column as a minor issue. The same output moved 0.3 points between judge runs. We switched to official deterministic graders and answer keys.
3. Measure cost through the real API, not a CLI. Claude Code's CLI adds about 33,000 tokens of overhead per call, which made Haiku look about 4 times more expensive than it is ($0.006 against $0.0008 for the same page).
4. Asking the same model twice does not fix its mistakes. Two Haiku reads repeat the same errors, so voting between them mostly fails. What did work was a different view of the same page: zoomed strips, where a digit changes only when two zoomed reads agree against the page read.
5. Fight the model's format and you lose accuracy. Pointer output, compact table syntax, smaller images and OCR-evidence prompting all scored below a plain read. Unfamiliar output formats raise the model's hidden thinking and lower its accuracy.
6. The model is already good at reading; the gaps are structure. Haiku almost never misreads characters on digital pages (1 serious text error in 74 pages). Its losses were table structure, checkboxes, block types and layout positions: exactly the things a software layer can supply.
7. Know your noise floor before celebrating. Two identical runs of plain Haiku differ by 2 to 14 points on some measures (business grid 2.5–4.7, answer-key precision up to 13.8, OmniDocBench table score up to 12.8). A gain smaller than that is not a gain.
8. "Never worse than vanilla" needs a guard on every rule. Almost every structure rule that helped somewhere hurt somewhere else (forms flattening real tables, rotation turning handwritten letters upside down, handwriting routing hurting printed forms). The kept versions all carry guards.
1. The question and the rules¶
11 November 2026, morning
The question: can an open-source layer on a general-purpose model replace Reducto, whose product rests on its own trained model (r-1)?
The thesis: general-purpose models keep improving for free, so a great layer on a general model should beat a purpose-built model over time. The work is the layer, not training a model.
Rules set at the start¶
- It cannot be a subpar product: every Reducto feature, the same API, a "change one URL" switch.
- Accuracy first, then cost, then speed.
- Anthropic as the only frontier lab; open-source models allowed; open source and self-hostable.
- Route by what a page looks like (digital or scanned, tables, handwriting), never by industry or document type.
- "Never make it worse": nothing may lower the plain model's own quality.
- No monetisation for now.
2. Researching Reducto¶
11 November, 06:58–07:20 UTC
| Topic | What we found |
|---|---|
| Prices (Reducto published) | r-1 $0.01 per page; r-1 mini $0.001 (launched 5 Nov 2026); Extract $0.02 per page including parsing; Deep Extract $0.04; Split $0.02; Classify $0.0075; Edit $0.06 |
| Products | Parse, Extract, Split, Classify, Edit, Upload, async jobs, webhooks: 20 routes in a public OpenAPI spec. Pipelines deprecated. |
| Openings for an open alternative | r-1 cannot run on-premises; self-hosting is Enterprise-only; zero data retention and HIPAA are paid tiers; users cannot choose the model |
| Licence traps in open tools | Marker, Surya, Chandra have restricted weights; MinerU 2.5 weights AGPL; PyMuPDF and Chunkr AGPL. Excluded from the product. |
3. First signal: RD-TableBench, 100 tables¶
Before the build · Reducto's own table benchmark and official grader
| System | Score |
|---|---|
| Haiku 5.5 | 91.4 |
| Sonnet 5.5 | 91.4 |
| Reducto (their saved 2024 outputs) | 91.0 |
| GPT (Codex gpt-6-luna) | 88.1 |
| Azure | 82.4 |
| AWS Textract | 78.4 |
| GPT-4o (2024) | 75.1 |
Later correctionThe 91.4 for Haiku was one lucky draw with a table-specific prompt. Re-measured through the API it was about 88.9, and on all 1,000 tables plain Haiku scored 88.2. This was the first of several sample results that did not survive.
4. First neutral benchmark: 140 olmOCR-bench pages¶
11 November, 07:21–09:05 · 28 pages each of tables, old scans, multi-column, tiny text, headers/footers · Reducto run through Studio ($1.76)
| System | Overall | Tables | Multi-column | Tiny text | Headers/footers | Old scans |
|---|---|---|---|---|---|---|
| Haiku 5.5 | 83.6 | 92.7 | 88.8 | 83.9 | 91.7 | 45.1 |
| Reducto r-1 | 79.4 | 87.8 | 69.2 | 87.9 | 93.1 | 41.5 |
| Reducto r-1 mini | 77.9 | 88.6 | 69.2 | 87.1 | 94.4 | 30.3 |
- Haiku's biggest win was multi-column reading order (88.8 against 69.2). It held on the full benchmark (79.8 against 65.6).
- Everyone was bad on old handwritten scans.
- The PDF text layer was unreliable: only 56 of 93 pages had a usable one.
- A cheap check (flag pages where two sources disagree) caught 77% of failures on 28% of pages.
Sample vs fullOn the full 1,403 pages the lead narrowed: plain Haiku 82.2 against Reducto 80.9, inside the margin.
5. Small open models on a rented GPU¶
11 November, 09:40–11:26 · RunPod RTX 4090, $1.53
Why: Haiku looked (through the CLI) like about $0.007 a page, above r-1 mini's $0.001. Could small open models carry a cheap tier?
| System | 5-category mean | Cost per page | Speed |
|---|---|---|---|
| Haiku 5.5 | 80.4 | ≈ $0.007 (CLI, overstated) | ≈ 25 s |
| Reducto r-1 | 75.9 | $0.010 | ≈ 0.3 s |
| Reducto r-1 mini | 73.9 | $0.001 | ≈ 0.35 s |
| GLM-OCR (0.9B, MIT) | 71.3 | ≈ $0.0002 | ≈ 0.75 s |
| PaddleOCR-VL-1.6 (0.9B, Apache) | 70.4 | ≈ $0.0004 | ≈ 1.8 s |
- Simulated "small model, escalate hard pages to Haiku": escalating 31% of pages gave 75.4 at $0.0024 a page, tying r-1 at a quarter of its price.
- No mix beat r-1 mini on price and quality at the same time.
- PaddleOCR's word boxes covered 79% of words on scans, against Tesseract's 57%. That word-box finding returned later as the OCR used for citations.
Verdict[dropped] Small open models are a cost play only; they never matched Haiku's quality. Also dropped: the GPU itself (later RunPod had no capacity), so everything since runs on a laptop CPU.
6. The strategy: a layer around the model¶
11 November, 11:30–11:58
The project lead's point: everything so far was plain model inference, while Reducto is a pipeline plus a product. Agreed principles:
- The value is the layer around the model: preparing the input, routing, checking the output, repairing only what fails.
- Route on what the page looks like, never on industry. A healthcare document can be a typed lab report or a handwritten form.
- Never make the model's own reading worse.
Finding: image size mattersRendering pages at exactly the largest size Claude keeps lifted Haiku on tiny text from 84 to 95, and overall from 80.4 to 81.6. [kept] in every profile since. It also exposed that Haiku's table output varies between runs.
7. Building the product in an afternoon¶
11 November, 12:00–14:12 · a "spine" agent, then 5 parallel Fable 5.2 agents · Apache-2.0
| Piece | What was built | Result |
|---|---|---|
| Spine | document model (document → page → region → line → word), swappable model adapters, an 8-stage pipeline, Reducto serializer, benchmark harness | reproduced every baseline exactly |
| API | all 20 Reducto routes, SQLite job queue, webhooks, Docker | Reducto's own Python SDK works unchanged against our server, changing only base_url |
| Tasks | Extract (schema guarantees, citations, deep extract), Split, Classify, Edit (fillable PDFs, flat PDFs with a schema, DOCX) | Extract got 12/12 rows with all 34 values cited |
| Citations | word and cell positions for every block | ≈ 93% coverage on digital pages, 80–82% on scans |
| Assembly and formats | 6 chunk modes, table formats, tables across pages, DOCX / XLSX / PPTX / CSV / EML input | passes its own synthetic set |
| Reading quality | evidence-based choice between reads, table checks, escalation | 84.3 on the 140 pages (plain 81.6, r-1 75.9), tuned on the same pages |
Tests grew from 50 to 630.
8. Reducto's own benchmarks and judge¶
11 November, 13:25–13:33
The project lead asked whether 75.9 was Reducto's number. It was ours. So we collected what Reducto itself publishes:
| Benchmark (Reducto published) | What it says | Could we run it? |
|---|---|---|
| ParseArena (2,000 pages, AI judge) | r-1 1st of 21 with a 63.3% win rate; LlamaParse agentic+ 58.0%, Mistral OCR 41.4%, Azure 32.7%, Textract 22.1%. Claude and GPT not included. | No: private data |
| K1Bench (196 tax forms, human-graded) | r-1 1st: 85.2% of pages free of material error | No: private data |
| LongExtract | Deep Extract 99.3% field accuracy | Yes, later (section 25) |
| RD-TableBench | 90.2% for their 2024 pipeline; no r-1 number | Yes (sections 23, 26) |
| Parse Judge | the open-sourced judge behind ParseArena | Ported, run with Sonnet as judge |
We also built a 147-page public business-document set: 45 finance, 42 tax, 22 legal, 13 insurance, 12 healthcare, 8 government; 85 digital and 62 scanned.
9. Judge round 1: our layer made Haiku worse¶
11 November, 14:30–17:45 · score out of 10; "clean" = no serious error
| System | Score | Clean pages |
|---|---|---|
| Plain Haiku | 8.01 | 74.1% |
| Reducto r-1 | 7.85 | 71.4% |
| Ours, version 1 | 7.56 | 66.7% |
| Reducto r-1 mini | 6.99 | 59.2% |
Why the first layer lostA table swap without a layout model (19 losses), and markdown conversion flattening merged cells (12 losses). Both were fixed; the net gain afterwards was small.
Further attempts: Haiku finding tables then re-reading crops (changed 3 pages); Sonnet as tie-breaker between two reads (helped). The final round-1 version scored 8.14 with 77.6% clean pages against plain Haiku's 7.98 and r-1's 7.85, but at $0.09 a page, nine times r-1, three quarters of it Sonnet.
Verdict[dropped] two reads plus a Sonnet arbiter: the gain was small and the cost absurd. Lesson: two reads by the same model repeat the same mistakes.
10. Redesign and the night engine¶
11 November 17:52 → 12 November 01:41
Landscape check: MarkItDown, PaddleOCR, MinerU, Docling, Marker, olmOCR, Unstructured, LiteParse, Zerox and Chunkr are all parsers. None is a full Reducto replacement: no Reducto-compatible API, no Extract / Split / Classify / Edit, no citations as standard.
Agreed design: Haiku reads every page. The layer gives better input, checks the output against independent evidence, and repairs only what fails. Plain Haiku stays the permanent baseline.
The night engine (Haiku only)¶
- One Haiku read at native size.
- A form-aware second read when checkboxes appear inside tables.
- On scans: enlarged strips read twice; a character changes only when both zoomed reads agree against the page read. This fixed about 18–19 of 24 known wrong digits.
[dropped] correcting text from the PDF's own text layer: only 2 of 17 errors on digital pages were misread characters.
| Pages (judge, CLI) | Engine | Plain Haiku | r-1 |
|---|---|---|---|
| All 147 | 8.24, 83.0% clean | 7.86, 73.5% | 7.68, 72.1% |
| Digital (85) | 8.65, 92.9% | 8.13, 81.2% | 8.05, 80.0% |
| Scanned with tables (33) | 7.39, 63.6% | 7.00, 51.5% | 6.00, 39.4% |
| Scanned without tables (29) | 8.03 | 8.03 | 8.52 (old cursive) |
| Handwriting (18) | 6.00 | 6.11 | 5.83 |
Where r-1 really beat Haiku: 1830s cursive promissory notes (9/10/9 against 5/3/6). Enlarging the image did not help.
11. The real API: cost, effort and handwriting¶
12 November, 06:38–08:10 · first runs through Anthropic's API instead of the CLI
Cost was overstated 4 timesHaiku 5.5 costs $0.10 per million input tokens and $0.50 per million output. Through the API a bank-statement page cost $0.0008 and 3.4 s, against about $0.006 through the CLI. The CLI adds about 33,000 tokens per call.
| Setting | Score | Cost per page | Verdict |
|---|---|---|---|
| Engine, default (medium) effort | – | 0.0052 | – |
| Engine, low effort | 7.64 | 27% cheaper | [dropped] below plain Haiku |
| Engine, high effort | 8.09 | 0.0070 | [kept] high effort |
The key honest finding¶
| System (API, high effort, judged like for like) | Score | Clean | Serious errors | Cost per page |
|---|---|---|---|---|
| Plain Haiku | 8.22 | 79.6% | 62 | $0.0033 |
| Engine | 8.22 | 81.6% | 55 | $0.0070 |
| Reducto r-1 | 7.69 | 70.1% | 91 | $0.0100 |
Most of the lead over r-1 came from the model, not the layer. The engine helped only on scanned tables (64% against 48% clean). [dropped] the form-aware second read: no measurable benefit at double the cost.
Handwriting: Sonnet on 18 pages (judged by Opus to avoid self-grading)¶
| System | Score | Clean | Serious errors |
|---|---|---|---|
| Sonnet 5.5 | 8.06 | 72.2% | 12 |
| Reducto r-1 | 5.67 | 33.3% | 46 |
| Engine (Haiku) | 5.61 | 22.2% | 54 |
| Plain Haiku | 5.61 | 22.2% | 51 |
Sonnet costs about $0.029 a handwritten page. [kept] as the handwriting route. Caveat: 18 pages, judged by an AI, a method dropped later.
12. A parallel track: layers that did not help¶
12 November · Reducto's Parse Judge, Sonnet 5.5
| Idea | What it does | Result | Verdict |
|---|---|---|---|
| Pointer output | Haiku points at numbered text-layer segments instead of retyping | 7.30 against plain 8.14 (74 digital pages); left 23% of segments unplaced; 56 s against 25 s | [dropped] |
| Pointer + fill | re-inserts unplaced segments | 7.09 | [dropped] |
| OCR evidence + model patches (scans) | OCR words as the base, Haiku patches them | 5.65 against plain 7.71 (62 scans); 2.6× cost, 3× slower | [dropped] |
| Smaller image (1,400 px) | cheaper input | 7.38 against 8.25; 19 serious table errors | [dropped] |
| Compact table syntax | fewer output tokens | below a plain read | [dropped] |
| Low effort | less hidden thinking | 8.25 against 8.06, 29% cheaper, 37% faster (digital pages) | promising there; later rejected on tables in the judge-free set |
| No-model rules route for prose pages | plain text pages handled without a model | 9.08 against Haiku 9.88; covered 35% of a 10-K | noted, not shipped |
Why pointing lostThe PDF text layer itself lies: words without spaces, doubled glyphs from fake bold, watermarks mixed in, struck-through text in redlines, adjacent form values merged. On digital pages plain Haiku almost never misreads characters (1 serious text error in 74 pages), so pointing solves a problem Haiku does not have and imports the layer's defects.
13. End-to-end reality check¶
12 November, 08:14–10:26
The project lead pushed back: the judge only scores text and table structure. It does not score boxes, block types, chunking or multi-page behaviour, which are what Reducto actually returns. So we compared our real JSON to r-1's real JSON on the same pages:
| Aspect | Result |
|---|---|
| Response structure, every field 3 levels deep | identical |
| Reading order | 97% agreement |
| Bounding boxes | median overlap 0.80; 70% overlap by half or more; 10% of blocks without a real box |
| Block types | only 50% agreement: we labelled almost everything Text and never produced Header, Footer or Key Value (r-1: 244, 260, 306) |
| Speed | 115–427 s per file against Reducto's 4–15 s, about 30× slower |
The project lead: "what is the USP of our product? It's almost non-existent." Agreed answer: the reading layer added little over plain Haiku; the value is the whole product. A real technical edge would have to come from page structure.
14. Building everything end to end, against Reducto's real responses¶
12 November · four workstreams: structure, parse options and file types, tasks, handwriting route and multi-page · harness sends identical HTTP requests to Reducto, our engine and plain Haiku
| 147 business pages | First pass | After the structure work |
|---|---|---|
| Block types agreeing with r-1 | 50% | 75% |
| Header / Footer / Key Value blocks produced | 0 / 0 / 0 | 106 / 121 / 98 |
| Whole-page boxes (a block with no real position) | 9% | 3% |
| Median box overlap with r-1 | 0.80 | 0.82 |
| Handwriting pages (judge) | 5.67, 22% clean | 7.22, 44% clean (Sonnet route) |
| Consistent table grids | ours 89%, plain 87%, Reducto 95% |
Judge on all 147 pages after this work: ours 8.24 (82% clean), plain 8.03 (77%), r-1 7.71 (73%). Median time per page: Reducto 4.8 s, ours 23.6 s.
15. Extract, Split, Classify and Edit against Reducto's real responses¶
12 November · 29 cases · deterministic comparisons
| Task | Cases | Result | Cost of one pass |
|---|---|---|---|
| Extract | 10 business forms + 3 long documents with answer keys | values agree on 10 of 10 forms except small wording (e.g. "Bank of America, N.A." against "BANK OF AMERICA"); long documents: all 694 / 279 / 155 rows found by both, our leaf accuracy 100% | Reducto $2.92, ours $0.50 |
| Split | 5 document packets | 5 of 5 correct for both | |
| Classify | 6 | 6 of 6 correct for both | |
| Edit (fill a form) | 5 (fillable, flat scan, flat digital, DOCX) | all expected values present for both; ours wrote real form fields only after a later fix |
Added afterwards as options: a relevance filter for long documents, asking for nearby pages, example outputs, warnings for required fields not found.
16. Why we dropped the AI judge¶
12–13 November
Looking at sample pages by hand exposed the judge:
- On a 1935 Federal Reserve page the judge misread 27,174,839 as 27,174,339 and gave our output 10/10.
- On a CMS-1500 healthcare claim our NPI column was wrong and the judge scored it as minor.
- The same output moved between judge runs (8.14 → 8.06; r-1 8.42 → 8.14), so differences under about 0.3 on 72 pages were noise.
- Questions of "is this a Key Value block or a table?" were judged inconsistently.
The project lead: "we don't want a model to judge… we want a proper benchmark." From here every number comes from official graders and answer keys.
17. The judge-free development set¶
13 November · one command scores everything, no model called while scoring
| Component | Items | Scored by |
|---|---|---|
| RD-TableBench sample | 100 tables | Reducto's official table grader |
| olmOCR-bench sample | 140 pages | official test classes |
| OmniDocBench sample | 100 pages | official evaluation code, unmodified |
| DocLayNet | 102 pages | block types and boxes against human labels |
| FUNSD | 50 forms | question–answer links |
| LongExtract | 3 documents | row recall and leaf accuracy |
| Business pages | 147 | table grid consistency, printed totals, header/footer text kept, block-type answer key (30 pages, grown to 100 later, model-written, not human-checked) |
Method rules: each item is assigned to "dev" or "held" by its hash; tune on dev, report both, never tune on held. Every idea is a switch, off by default. "Never worse than plain Haiku beyond noise" is the bar for keeping it. Saved model reads are replayed, so a downstream change costs nothing to re-test.
Baseline¶
| Measure (dev / held) | Reducto r-1 | Plain Haiku | Engine before this round |
|---|---|---|---|
| RD-TableBench | 89.2 / 91.5 | 87.7 / 89.4 | 87.8 / 89.4 |
| olmOCR mean | 77.6 / 74.4 | 77.2 / 79.2 | 77.3 / 79.0 |
| DocLayNet type recall | 84.5 / 84.4 | 72.9 / 64.4 | 72.9 / 64.4 |
| FUNSD links | 87.6 / 86.8 | 80.6 / 82.8 | 79.5 / 79.4 |
| Business consistent grids | 95.8 / 94.2 | 87.6 / 84.7 | 90.5 / 87.2 |
| Business answer-key recall | 97.2 / 93.3 | 65.7 / 66.0 | 68.5 / 67.0 |
| Cost per business page | $0.0100 | $0.0033 | $0.0087 / $0.0101 |
The noise floorTwo identical runs of plain Haiku on independent reads differed by 0–1 point on tables and olmOCR, 3–6 points on business grids, types and answer keys, and 8–14 points on OmniDocBench table scores and the new answer key's precision. Every later verdict was judged against this.
18. Round 1: five workstreams in parallel¶
13 November · every idea scored alone on top of plain Haiku · full list in the appendix
Workstream 1: layout (an open layout model on CPU)¶
| Idea | Result | Verdict |
|---|---|---|
| Docling "heron" layout model | 94% type recall, box overlap 0.91 on DocLayNet dev | [kept] as the detector |
| PP-DocLayout-plus | 74% recall, 0.75 overlap; label set maps worse | [dropped] |
| Block types from layout, unguarded | +17 type recall, but losses on FUNSD and business key | [option] |
| Block types from layout, guarded | +14.5 / +12.8 type recall, +11 / +5 headers/footers, nothing worse | [kept] |
| Layout boxes | box overlap +0.03 / +0.02 | [kept] with guarded types |
| Split blocks along layout regions | FUNSD links −2.7, business key precision −7.6 (cut form fields apart) | [dropped] then a later guarded version kept |
| Re-read form regions from crops, replacing blocks | flattened 21 business tables into lines | [dropped] |
| Fill missing text from crop reads | 935 crops, 841 already present; reading order worse | [dropped] |
| Assemble the page from region reads | best layout numbers, but tiny text −11.8, old scans −2 | [option] |
Workstream 2: forms¶
| Idea | Result | Verdict |
|---|---|---|
| Fillable-PDF values and checkbox states from the file | on 26 filled forms: typed values 2–3% → 100%, checkbox states 57–64% → 100%; no model call | [kept] |
| Checkbox crop re-reads | the page read was already 94–99.5% right; crops fixed 1, broke 1–2 | [dropped] |
| Form grids → Key Value (kv_rules) | right on answer-key grids, but r-1 itself keeps many form grids as tables | [option], row version kept later |
| Label: value lines joined (kv_lines:join) | FUNSD +0.8, free | [kept] |
| Form region re-read | FUNSD +1.9 dev, business losses | [dropped] |
Workstream 3: checks and repairs¶
| Idea | Result | Verdict |
|---|---|---|
| Table grid repair with the draft table in the prompt, exact acceptance | 35 of 47 broken tables repaired with no cell text changed; business grids 87.6 → 98.6 / 84.7 → 95.3 (r-1 95.8 / 94.2) | [kept] |
| Grid repair by fresh crop read | only 15 of 47; a fresh read differs in some digit | [dropped] |
| Padding broken grids | +10 points at no cost, but proves nothing about which column a value belongs to | [option] |
| Printed-totals check with zoomed cell re-read | never worse; 2 real fixes on the whole set | [kept] |
| Completeness from PDF text / crops / scans | few lines restored, no measure moved; one wrong insertion | [dropped] |
| Flags for doubtful tables | tables with a grid flag score 79.8 against 89 unflagged | [kept] (no content change) |
Workstream 4: OCR, preparation, speed¶
| Idea | Result | Verdict |
|---|---|---|
| Tesseract, with PP-OCR filling what it cannot place | blocks with a measured box on hard scans 76% → 91% | [kept] |
| PP-OCR alone or first | looser boxes (median overlap 0.84 → 0.70) | [dropped] |
| Turn sideways pages upright | 0 wrong turns on 128 test images (but see section 19) | [kept] with a guard |
| Deskew | angle right, reading not better | [option] |
| Zoom vote gated by numbers and legibility, started early | keeps all 9 verified digit corrections; scans 39 s → 27 s | [kept] |
| Medium effort instead of high | 35% cheaper but business grids −8.2, answer-key recall −9.9, totals 176 against 235 | [dropped] |
| Medium effort only for easy pages | loose threshold failed; strict threshold inside noise | [option] |
| 16 pages read in parallel, shared rate-limit backoff | no rate limit at about 60 calls in flight; a document 43 s → 24 s | [kept] |
| Batch API for async jobs | exactly half price, but 2,576 s instead of 31 s | [option] |
19. Round 1 combined: engine-final¶
13 November · pieces added one at a time on the same saved reads
- Step 0: everything off = plain Haiku (637 of 639 responses identical).
- Step 1, OCR and preparation: the rotation method turned 5 upright pages upside down (handwritten letters, vertical Chinese handwriting). Workstream 4's "0 wrong turns" had been measured without handwriting letters. A guard was added; after it, exactly the 3 truly sideways pages are turned. OmniDocBench layout box overlap 0.30 → 0.61.
- Step 2, checks: business grids 87.6 → 98.6 / 84.7 → 95.3. Workstream 1's own table repair was compared on the same reads and lost (89.0 / 87.3); left out.
- Step 3, layout: DocLayNet type recall +14.7 / +12.6, headers/footers +13.2 / +5.3, nothing worse.
- Step 4, forms: fillable values and joined lines. An interaction was found and fixed: a crop of the page render cannot show values typed into fields, so such regions are never re-read.
- Step 5, handwriting route to Sonnet: 66 of 639 pages routed, including 13 printed FUNSD forms with one "[illegible]" mark; FUNSD links −3.4. A print guard cut routed pages to 52.
- Steps 6–7: form-grid rows rule and a guarded layout split, both kept.
An incident: the test disk filled (each server kept 0.5–1.2 GB), runs died half-way and were repeated; this later became the automatic clean-up job.
Result against plain Haiku and r-1 (dev / held)¶
| Measure | Plain Haiku | engine-final | Reducto r-1 |
|---|---|---|---|
| RD-TableBench | 87.7 / 89.4 | 87.8 / 89.5 | 89.2 / 91.5 |
| olmOCR mean | 77.2 / 79.2 | 80.5 / 80.6 | 77.6 / 74.4 |
| OmniDocBench layout type recall | 57.5 / 58.4 | 82.2 / 65.3 | 85.7 / 65.7 |
| OmniDocBench layout box overlap | 0.301 / 0.269 | 0.636 / 0.486 | 0.657 / 0.504 |
| Business answer key (100 pages), precision / recall | 71.8 / 64.8 | 78.3 / 71.6 | 91.5 / 91.8 |
The first answer key flattered usOn the first 30 hand-picked pages plain Haiku scored 85.9 / 84.5 precision; on 70 new pages, 60.5 / 70.3. The largest confusion: 134 form fields typed as Text where the key says Key Value.
20. Round 2: tables, forms and product gaps¶
13 November · three workstreams plus a combine step · result: engine-final-2
A. Tables and formulas¶
| Idea | Result | Verdict |
|---|---|---|
| Re-read every table on its own; keep the second read only if its cells agree | 277 tables re-read, 105 accepted; RD-TableBench +2.06 / +0.21; olmOCR table tests +6.3 | [kept] (see section 23) |
| Re-read tables the page read missed, with a forcing prompt | 3 accepted, all real tables | [kept] |
| Formula matrices re-read | 1 region; formula error 0.184 → 0.070 | [kept] |
| Clean-up of table markup | RD +0.04 / +0.43, free | [kept] |
| Merge tables by layout or continuation | joins tables the key keeps apart; OmniDocBench tables −5 to −6 | [dropped] |
| Accept re-reads by "fit", "strict" or "blind" | blind swaps errors; strict accepts almost nothing | [dropped] |
| Read tables and forms from region crops | olmOCR table tests +12.7 but printed totals −5 / −14 | [dropped] |
| Zoom only the table area | lost 2 of 9 verified corrections | [dropped] |
B. Forms and structure¶
| Idea | Result | Verdict |
|---|---|---|
| Split one-line running heads into word groups (hfsplit) | answer-key recall +3.6 / +1.9 on three read sets | [kept] |
| Read form regions from a crop as evidence only (formread) | FUNSD links +4.7 / +1.0 | [kept] |
| Key Value lines are Text on pages without forms (kv=page) | small gains, nothing lower | [kept] |
| Fill missing boxes from free layout regions (boxfill) | small gains | [kept] |
| Field-like text inside any form region → Key Value | FUNSD +4.5 but OmniDocBench types −7.5 (exercise lists look like forms) | [dropped] |
| Headers by position or type size | recall and r-1 agreement lower | [dropped] |
| Figure blocks from layout detections | DocLayNet +1, but OmniDocBench precision lower on held | [option] |
| Print guard for the handwriting route | no FUNSD form goes to Sonnet any more | [kept] |
C. Product gaps¶
Automatic disk clean-up (one machine had 18 GB of page renders that were never deleted), real form fields in Edit output for flat and scanned PDFs, Figure blocks, and other response fields.
Combined result (engine-final-2)¶
- RD-TableBench 89.8 / 91.8 against r-1 89.2 / 91.5. Ahead on the sample, which turned out not to hold (section 23).
- olmOCR table tests 93.7 / 90.0 against r-1 87.3 / 88.3.
- FUNSD links 86.6 / 84.1 against r-1 87.6 / 86.8.
- Business block types still 16–23 points behind r-1.
- Cost per business page $0.0119 (r-1 $0.0100); median 29 s.
21. Round 3: cheap fixes on a separate branch¶
13 November · only changes that add no model cost or reduce it
| Idea | Result | Verdict |
|---|---|---|
| Header/footer detection (hfdet), page-number split (pnsplit), one-column "tables" are prose, header tables, "(a)" labels | business answer-key recall +6.7 / +1.9, nothing lower | [kept] |
| Caption / list / long / figure label rules | nothing, or every moving measure down | [dropped] |
| Second zoom pass only over disputed strips | identical output, 19 fewer strip calls | [kept] |
| Zoom strips at low or medium effort | lost 2 of 9 corrections, made one wrong change | [dropped] |
| Start form reads together with the page read | identical output; form pages wait 39 s → 27 s | [kept] |
| Re-read only tables that show a warning sign | half the re-reads but RD dev −1.3 | [option] |
| Per-page spending cap | works; steps skipped are recorded | [option] |
| Only the handwritten region to Sonnet | the layout model finds no region on 10 of 15 handwritten ledgers | [dropped] |
| Sonnet at lower effort on handwriting | not run (owner's priorities) | [not done] |
Result: business answer-key recall 81.0 / 76.7 (r-1 92.7 / 91.2). Profile engine-final-3-full.
22. The form-grid probe: is this the main lever?¶
13 November · offline, saved responses only, no model call
Question: Haiku often writes a form as a big table. If we split those form tables into fields, would we close the block-type gap with r-1?
Answer: noSplitting mixed form tables was worth about +1 point on dev and 0 on held. Even a perfect split of every table and Key Value block would stop at about 86%, 5–7 points under r-1. About half of all missed blocks were not inside our tables at all. [parked] by the owner.
23. Full RD-TableBench: the sample gains vanish¶
14 November, 01:38–02:37 · all 1,000 tables, official grader, live API calls, no cached reads
| System | All 1,000 | 900 never in the sample | 100 sample tables | No table returned | $ per table | Median s |
|---|---|---|---|---|---|---|
| Reducto r-1 | 88.85 | 88.68 | 90.38 | 2 | 0.0100 | 4.1 |
| Engine (engine-final-3-full) | 88.21 | 88.04 | 89.76 | 7 | 0.0050 | 45.4 |
| Plain Haiku | 88.20 | 88.18 | 88.37 | 4 | 0.0012 | 8.4 |
What it meansEngine minus plain Haiku: +0.01. Engine minus Reducto: −0.63 (interval −1.57 to +0.28). Plain Haiku ties r-1 at an eighth of its price, and the engine added nothing on unseen tables. About 10 of the engine's 15 worst losses were self-inflicted: structure rules turning real tables into Key Value blocks or splitting them, and 57 printed tables wrongly sent to Sonnet as handwriting.
The run also exposed a server crash under parallel requests. The project lead: "does this tell us that the whole experiment failed?" Our answer: no for the product, yes for the idea that the reading layer improves tables. This led to the conclusion "plain Haiku is good enough as the reader; invest in the software around it", tested on the other benchmarks before deciding.
24. Round 4: the crash and the guards¶
14 November · tag final-round-4 · profile engine-final-4
| Problem | Cause found | Fix |
|---|---|---|
| Server crash under parallel requests | not a race: OpenCV 5's arm64 resize reads one row past the end of the source image on a slight downscale, and dies when the image ends at a memory-page edge | resize works on a padded copy (pixels identical); one worker thread per local model; a test reproduces the crash |
| Real tables turned into Key Value | the form-grid rule judged cell text only; counts and checkboxes looked like fields (5 of 1,000 tables, −0.41 on the mean) | leave a table alone where the layout model sees a table; columns of amounts count as table evidence |
| Printed tables sent to Sonnet | the handwriting signal fired on faint print | route only if the second OCR cannot read the page as print: 52 → 14 of the 57 |
| Tables "cut into pieces" | the page read itself, not the engine | none needed |
The re-read of every table, measured on 250 tables: +1.25 points (interval +0.24 to +2.44), but only +0.76 on 150 random unseen tables, inside noise. Estimated full-engine score about 89.1. Proof: 437 mixed requests at 16 at once, 0 failures.
25. Four more complete public benchmarks¶
14 November · Reducto, plain Haiku and the engine on every page · full numbers in the final table above
olmOCR-bench (1,403 pages)¶
Engine 84.2, plain Haiku 82.2, Reducto 80.9. Engine minus Reducto +3.3 (interval +1.8 to +4.9); engine minus plain +2.0 (+1.2 to +2.9); the same on the 1,263 pages never in our sample (+3.0 and +1.8). Almost all of the engine's gain is the free header/footer step (+10.8 over plain). Reducto stays ahead on headers and footers (90.1 against 81.2).
FUNSD (199 scanned forms)¶
Engine 77.4, plain Haiku 71.6, Reducto 78.6. On the 149 forms never seen: engine 74.9, plain 68.8, Reducto 76.3. The form gain held on unseen forms (+6.1, interval +1.9 to +10.3). Engine level with Reducto (−1.4, interval −4.7 to +1.6).
OmniDocBench (1,651 pages)¶
Layout is where the engine earns its keep: block types 78.3 (plain 55.6, Reducto 84.6), boxes 61.1 (plain 26.5, Reducto 67.2). The same on the 1,551 pages never in the sample. Text: both ours 0.081 against Reducto's 0.095. Formulas: Reducto better.
Plain Haiku beat Reducto on text for handwritten notes, slides, research reports, newspapers and Chinese text. Reducto won on magazines, academic papers, textbooks, exam papers and English text.
LongExtract (whole documents)¶
The 50 documents total 19,528 pages (one alone is 11,622), about $390 on Reducto. Within budget: Reducto on the 32 shortest, ours on the 10 shortest.
- Reducto's rows found on its 32 documents: 37.2%. It returned only part of long tables (e.g. 258 of 1,560 rows, 37 of 1,295) and no rows at all on 5 documents.
- On the 10 common documents: rows found 53.9 (Reducto), 75.4 (plain), 75.9 (engine), with wide intervals.
Failures found along the way¶
- Reducto rejected 8 oversized images; our server rejected 12 (our 50-megapixel limit).
- 40–47 pages came back empty from our side because Anthropic's content filter blocked the output ("Output blocked by content filtering policy"), mostly textbooks, newspapers and magazines. Reducto had none. Our server returns these silently as empty with no fallback; the owner set this aside for now.
- A one-hour stall of the server mid-run, with no error in the log; pages later retried.
26. Full RD-TableBench on the final engine¶
14 November, 17:00–20:06 · separate server pinned to final-round-4 · $4.41
| System | Score on 1,000 tables | $ per table |
|---|---|---|
| Reducto r-1 | 88.85 | 0.0100 |
| Plain Haiku | 88.20 | 0.0012 |
| Engine, round 4 (shipped) | 88.17 | 0.0044 |
| Engine, round 3 | 88.21 | 0.0050 |
The estimate did not holdThe fix agent's estimate of about 89.1 rested on 250 tables, mostly the 100 the engine had been tuned on. The final engine is level with plain Haiku on tables (−0.02) and 0.7 behind Reducto, inside the margin. Four different table measurements now agree: on tables, all three systems are level.
27. Product checks: citations, confidence, structured output¶
14 November
Citations, against Reducto's real response on the same certificate¶
| Aspect | Before the fix | Reducto | After the fix |
|---|---|---|---|
| Values | 8 of 8 identical | – | 8 of 8 identical |
| Cited text | the whole row or block | just the value | just the value |
| Effective-date box | covered both dates (a repeated "01" matched the neighbouring date) | exact | exact |
| Block type | "Table" everywhere | Key Value / Text | unchanged (parked form work) |
| Time | 42 s | 7.7 s | – |
| Cost | $0.004 | $0.02 list | $0.004 |
Confidence¶
Ours is a fixed tier (0.95 / 0.8 / 0.7 / 0.3) based on how cleanly the text was found on the page; Reducto's varies per field. Anthropic does not expose token probabilities, so a real score would combine free checks with agreement between two extractions. Planned as a measurement first; not built.
Anthropic's built-in structured output¶
- Saves no passes: our own loop made the same number of model calls.
- Our switch for it was broken: 10 of 13 schemas rejected until adjusted; then values came back empty with citations on, because the built-in schema forbids the source tags we need.
- A complaint from a team building extraction in-house, confirmed in Anthropic's docs: at most 24 optional fields and 16 "value or empty" fields per request, and "Schema is too complex for compilation" beyond internal limits. A large extraction schema will not fit.
- [dropped] as the default; our check-and-repair loop stays.
28. What was decided¶
- Default: the engine on Haiku 5.5 at high effort, exactly as benchmarked, with Sonnet 5.5 for handwritten pages. Shipped as
engine; the plain read ships asmodel-only. - Users choose: mode, page model, handwriting model, extraction model, effort and other model options, with any Anthropic model and OpenAI-style models. Benchmarks claimed only for Haiku 5.5.
- Compare against Reducto only: Mistral OCR (about $21) and LlamaParse (about $255 at its best tier) were dropped as too costly for an open-source project.
- Set aside for now: the fallback for pages Anthropic blocks, the long-list extraction instability (one run lost about 650 of 694 rows), form block types, formulas, speed.
- Next: clean-up for open source (two profiles instead of 25, research code out of the package), reliability (time limits per page, warnings, concurrency), a Python library, per-request model choice, cost reporting.
Appendix A: sample vs full, every reversal¶
| Claim on a sample | On the complete data |
|---|---|
| Haiku 91.4 against Reducto 91.0 on 100 tables (one draw, table prompt) | Plain Haiku 88.2 against Reducto 88.85 on 1,000 |
| Engine 89.8 / 91.8 against Reducto 89.2 / 91.5 on the 100-table sample (round 2) | Engine 88.2 against Reducto 88.85 on 1,000; level with plain Haiku |
| Round-4 estimate of about 89.1 for the full engine (250 tables) | 88.17 on all 1,000 |
| Haiku 83.6 against Reducto 79.4 on 140 olmOCR pages | 82.2 against 80.9 on 1,403: the lead narrowed into the margin |
| Plain Haiku business answer key 85.9 / 84.5 precision on the first 30 pages | 60.5 / 70.3 on 70 new pages |
| Workstream 4: "0 wrong turns" for page rotation on 128 test images | 5 upright handwritten pages turned upside down on the full development set |
| Judge: engine 8.24 against plain 7.86 (CLI, default effort) | Through the API at high effort: 8.22 against 8.22 |
| Low effort "costs nothing in quality" (judge, digital pages) | Judge-free: business grids −8.2, answer-key recall −9.9; rejected |
| Engine forms: FUNSD 86.6 / 84.1 against r-1 87.6 / 86.8 (sample) | Held: 77.4 against 78.6 on 199 forms, +6 over plain Haiku on unseen forms |
| Engine layout: close to r-1 on 100 OmniDocBench pages | Held: 78.3 against 84.6 types, 61.1 against 67.2 boxes on 1,651 |
What survivedThe engine's gains on layout, forms, headers and footers, and general pages all held on the complete benchmarks, including the parts never used for tuning. The table gains did not.
Appendix B: what the shipped engine contains¶
| Step | What it does | Model cost | Where it was proven |
|---|---|---|---|
| Native-size page image | render at the largest size the model keeps | none | tiny text 84 → 95 (sample) |
| Haiku 5.5 page read, high effort | the reading itself | 1 call per page | medium effort lost on tables |
| Rotation with guard | turn sideways pages upright | none | 3 of 639 pages turned, all correctly |
| Open layout model (Docling heron, CPU) | block types and boxes as evidence | none | layout +23 / +35 (full OmniDocBench) |
| Tesseract + PP-OCR word positions | citations and box placement | none | boxes on hard scans 76% → 91% |
| Zoom vote on scans | a digit changes only when two zoomed reads agree | strip reads on gated scans | 9 verified digit corrections |
| Handwriting route | Sonnet 5.5 for handwritten pages, with a print guard | ≈ $0.03 per handwritten page | 18 pages (judge); weakest evidence |
| Fillable-PDF values | typed values and checkbox states from the file | none | 2–3% → 100% on 26 filled forms |
| Form rules and form-region reads | pair labels with values | 1 crop read per form region | FUNSD +6 (full, unseen) |
| Header/footer and page-number rules | separate running heads, feet and numbers | none | olmOCR headers/footers +11 (full) |
| Grid repair, totals check, flags | fix broken table grids; check printed totals | calls only for broken tables | business grids 87.6 → 98.6 |
| Table re-read (every table) | second read kept only if cells agree | ≈ $0.0015 per table | not significant on full RD-TableBench |
| Formula matrices, missed tables | targeted re-reads | rare | small sample gains |
Appendix C: money spent (approximate)¶
| Where | About | Notes |
|---|---|---|
| Reducto API credits | $100 | early comparisons about $27 (including the 1,000-table run), full benchmarks $71.37 ($29.71 parse, $41.66 extract) |
| Anthropic API | $65 | development rounds about $28, full benchmarks $28.47, 1,000-table re-run $4.41, round 4 $1.70, demos and checks under $1 |
| RunPod GPU | $1.56 | small open models, day 1 |
| Claude subscription | – | agents and the AI judge (about $50–70 of API-equivalent judging on days 1–2) |
Appendix D: all 272 experiments in the ledger¶
Every idea scored on the judge-free development set, in the order it was run, with the verdict and the reason recorded at the time. "dev / held" numbers are the two halves of the set. Earlier stages (sections 3–15) were judged by an AI or measured directly and are described above.
272 experiments: [kept 80] [dropped 75] [option 55] [other 62] (checks, baselines, combine steps)
| # | Date | Workstream | Experiment | What changed | Verdict | Why | Biggest moves vs plain Haiku |
|---|---|---|---|---|---|---|---|
| 1 | 2026-11-12 | ws1-layout | ws1_model_heron |
DOCFORGE_REGIONS=(model alone): Docling heron layout model alone on the DocLayNet pages, no reader (blocks = detections) | [keep (as detector)] | 94% type recall and IoU 0.91 on dev, far above r-1; chosen detector | dev dln.type_recall +21.5; dev dln.type_precision +17.9; held dln.type_recall +26.8; held dln.iou50 +21.1 |
| 2 | 2026-11-12 | ws1-layout | ws1_model_ppdoclayout_plus |
DOCFORGE_REGIONS=(model alone): PP-DocLayout_plus-L alone on the same pages | [drop] | type recall 74% and IoU 0.75: its label set and box conventions do not map to DocLayNet/Reducto types as well as heron | dev dln.type_recall_lenient +7.2; dev dln.iou50 +4.4; held dln.type_recall_lenient +7.4; held dln.iou50 +4.4 |
| 3 | 2026-11-12 | ws1-layout | ws1_off |
DOCFORGE_REGIONS=: layout-api profile through the HTTP server with no switch (must equal vanilla) | [check] | identical to vanilla on the DocLayNet dev pages | dev cost.doclaynet.s_page +26.7; dev dln.type_recall +0.0 |
| 4 | 2026-11-12 | ws1-layout | ws1_a_off |
DOCFORGE_REGIONS=: in-process replay with no switch on all dev components (must equal vanilla) | [check] | no measure differs from vanilla: replay tool is faithful | dev cost.doclaynet.s_page -0.4; dev tables.score +0.0 |
| 5 | 2026-11-12 | ws1-layout | ws1_a_types |
DOCFORGE_REGIONS=types: block types from the matched layout region (every type change allowed); reading untouched | [option] | +17 points DocLayNet type recall, but small losses on FUNSD links and the business answer key | dev cost.funsd.s_page +19.7; dev dln.type_recall +17.4 |
| 6 | 2026-11-12 | ws1-layout | ws1_a_types_keepfurn |
DOCFORGE_REGIONS=types,deny=Header>|Footer>: types from layout, but a block the reader typed Header/Footer keeps that type | [option] | olmOCR headers_footers +11 but business key recall -2.2 | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 7 | 2026-11-12 | ws1-layout | ws1_a_boxes |
DOCFORGE_REGIONS=boxes: block box from the one-to-one matched layout region; types untouched | [needs-work] | IoU +0.03 but IoU>=0.5 share -0.2 on dev | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 8 | 2026-11-12 | ws1-layout | ws1_a_both |
DOCFORGE_REGIONS=types,boxes: types and boxes from layout (DocLayNet dev only) | [needs-work] | sum of the two; same small IoU50 loss as boxes | dev dln.type_recall +17.4; dev cost.doclaynet.s_page +17.0 |
| 9 | 2026-11-12 | ws1-layout | ws1_a_split |
DOCFORGE_REGIONS=types,boxes,split: types + boxes + text blocks cut at line breaks between layout regions | [drop] | best DocLayNet numbers but FUNSD links -2.7 and business key precision -7.6 | dev cost.funsd.s_page +19.9; dev dln.type_recall +17.5 |
| 10 | 2026-11-12 | ws1-layout | ws1_b_grid |
DOCFORGE_REGIONS=tables=grid: every detected table re-read from its crop; replaces the page read when the crop grid is consistent | [option] | small gains on tables/olm/omni, but 5 fewer reconciling totals on business | dev cost.tables.s_page +22.7; dev cost.funsd.s_page +20.5 |
| 11 | 2026-11-12 | ws1-layout | ws1_b_numbers |
DOCFORGE_REGIONS=tables=numbers: as grid, and the crop numbers must agree better with independent words (PDF text / OCR) | [drop] | RD-TableBench -0.4, nothing gained elsewhere | dev cost.tables.s_page +22.6; dev cost.funsd.s_page +20.6 |
| 12 | 2026-11-12 | ws1-layout | ws1_b_broken |
DOCFORGE_REGIONS=tables=broken: crop re-read only where the page read has an inconsistent grid; accepted when the crop grid is consistent | [keep candidate] | grid_ok +1.4 on business, no measure worse on dev | dev cost.funsd.s_page +19.7; dev cost.omni.s_page +17.7 |
| 13 | 2026-11-12 | ws1-layout | ws1_c_forms |
DOCFORGE_REGIONS=forms: form-like regions re-read from a crop with a form prompt, guarded (keeps words, more label:value lines) | [drop] | FUNSD links +1.7 but it flattens 21 business tables into lines (same_type -3.8) | dev cost.funsd.s_page +22.7; dev cost.business.s_page +21.7 |
| 14 | 2026-11-12 | ws1-layout | ws1_c_forms_nt |
DOCFORGE_REGIONS=forms=notables: as forms, but never where the page read has a table in the region | [drop] | the guard removes every replacement: no change at all | dev cost.funsd.s_page +20.3; dev cost.business.s_page +17.4 |
| 15 | 2026-11-12 | ws1-layout | ws1_e_complete |
DOCFORGE_REGIONS=complete: text regions with no matching output text are re-read from a crop and inserted | [drop] | 458 crop calls on dev for +0.08 FUNSD word recall; reading order on OmniDocBench worse (+0.018) | dev cost.tables.s_page +25.5; dev cost.funsd.s_page +22.6 |
| 16 | 2026-11-12 | ws1-layout | ws1_tr_1 |
DOCFORGE_REGIONS=types,allow=Text>List Item: only this type change from layout: Text>List Item | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 17 | 2026-11-12 | ws1-layout | ws1_tr_2 |
DOCFORGE_REGIONS=types,allow=Title>Section Header: only this type change from layout: Title>Section Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 18 | 2026-11-12 | ws1-layout | ws1_tr_3 |
DOCFORGE_REGIONS=types,allow=Key Value>Text: only this type change from layout: Key Value>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 19 | 2026-11-12 | ws1-layout | ws1_tr_4 |
DOCFORGE_REGIONS=types,allow=Text>Section Header: only this type change from layout: Text>Section Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 20 | 2026-11-12 | ws1-layout | ws1_tr_5 |
DOCFORGE_REGIONS=types,allow=Section Header>Text: only this type change from layout: Section Header>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 21 | 2026-11-12 | ws1-layout | ws1_tr_6 |
DOCFORGE_REGIONS=types,allow=List Item>Text: only this type change from layout: List Item>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 22 | 2026-11-12 | ws1-layout | ws1_tr_7 |
DOCFORGE_REGIONS=types,allow=Header>Text: only this type change from layout: Header>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 23 | 2026-11-12 | ws1-layout | ws1_tr_8 |
DOCFORGE_REGIONS=types,allow=Footer>Text: only this type change from layout: Footer>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.business.s_page -17.2 |
| 24 | 2026-11-12 | ws1-layout | ws1_tr_9 |
DOCFORGE_REGIONS=types,allow=Text>Header: only this type change from layout: Text>Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 25 | 2026-11-12 | ws1-layout | ws1_tr_10 |
DOCFORGE_REGIONS=types,allow=Title>Header: only this type change from layout: Title>Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 26 | 2026-11-12 | ws1-layout | ws1_tr_11 |
DOCFORGE_REGIONS=types,allow=Title>Text: only this type change from layout: Title>Text | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 27 | 2026-11-12 | ws1-layout | ws1_tr_12 |
DOCFORGE_REGIONS=types,allow=Section Header>List Item: only this type change from layout: Section Header>List Item | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 28 | 2026-11-12 | ws1-layout | ws1_tr_13 |
DOCFORGE_REGIONS=types,allow=Text>Footer: only this type change from layout: Text>Footer | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 29 | 2026-11-12 | ws1-layout | ws1_tr_14 |
DOCFORGE_REGIONS=types,allow=Key Value>List Item: only this type change from layout: Key Value>List Item | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 30 | 2026-11-12 | ws1-layout | ws1_tr_15 |
DOCFORGE_REGIONS=types,allow=Key Value>Footer|Key Value>Header|Key Value>Section Header: only this type change from layout: Key Value>Footer / Key Value>Header / Key Value>Section Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 31 | 2026-11-12 | ws1-layout | ws1_tr_16 |
DOCFORGE_REGIONS=types,allow=Section Header>Header|Section Header>Footer|List Item>Footer|List Item>Header: only this type change from layout: Section Header>Header / Section Header>Footer / List Item>Footer / List Item>Header | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 32 | 2026-11-12 | ws1-layout | ws1_tr_17 |
DOCFORGE_REGIONS=types,allow=Header>Section Header|Header>Key Value|Footer>Section Header|Footer>List Item: only this type change from layout: Header>Section Header / Header>Key Value / Footer>Section Header / Footer>List Item | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 33 | 2026-11-12 | ws1-layout | ws1_tr_18 |
DOCFORGE_REGIONS=types,allow=Text>Key Value|Section Header>Key Value: only this type change from layout: Text>Key Value / Section Header>Key Value | [see sweep] | single transition, dev only | dev cost.funsd.s_page +19.7; dev cost.doclaynet.s_page +17.0 |
| 34 | 2026-11-12 | ws2-forms | checkbox_crops |
each checkbox group read twice from enlarged crops; a mark changes only when both crop reads disagree with the page read | [drop] | on 347 boxes with exact truth (flattened filled forms) the page read is 94.3/99.5% right; crops changed 2 of 262 paired marks, fixed 1 and broke 1-2, at $0.0025 per form page | |
| 35 | 2026-11-12 | ws2-forms | fillable (provisional) |
fillable PDFs: field values and checkbox/radio states read from the file; marks and values corrected in the read, |
[keep] | provisional: no measure lower on either split, same_type +0.4/+4.3; on 26 filled forms values 2-3% -> 100% and mark states 57-64% -> 100%; no model call | dev bus.tables -12.0; dev cost.business.s_page +4.1; held bus.tables -22.0; held cost.business.s_page +6.8 |
| 36 | 2026-11-12 | ws2-forms | kv_rules (earlier rule, provisional) |
table that is a form grid -> one Key Value block, no model call | [needs-work] | provisional: bus.same_type -0.33 on dev although 37 tables became Key Value; current rule not yet run on business | dev bus.tables -30.0; dev cost.business.s_page +18.2 |
| 37 | 2026-11-12 | ws2-forms | kv_lines (provisional) |
open label: takes the next short block as its value; CAPS LABEL value lines typed Key Value on form pages | [needs-work] | provisional: FUNSD links +1.55 and key precision +0.6 on dev, nothing lower; held and other components not run yet | dev cost.business.s_page +18.6; dev cost.funsd.s_page +8.9 |
| 38 | 2026-11-12 | ws2-forms | region_reread (provisional) |
run of form-grid tables re-read from a crop with the form prompt, accepted only when the words agree | [needs-work] | provisional: FUNSD links +1.94 on dev but bus.same_type -0.86 and grid_ok -0.77 | dev bus.tables -16.0; dev cost.business.s_page +13.2 |
| 39 | 2026-11-12 | ws4-ocr-prep-speed | zoom_always (vanilla + zoom vote on every scan) |
read option zoom_vote on vanilla-api: each scanned page is read again as 3 enlarged strips, and as 4 more when anything differs; digits change only on a 2-of-3 agreement | [option] | no measure below vanilla; +1 reconciled total on held (475 vs 474 all), 18 numbers changed on 12 pages; costs +0.0029 USD/page (+0.010 on scanned tables) and +8 s/page of model time | dev cost.business.s_page +57.7; dev bus.same_type +0.2; held cost.business.s_page +69.8; held bus.totals_ok +1.0 |
| 40 | 2026-11-12 | ws4-ocr-prep-speed | zoom_gate=table_numbers |
strips only when the page read has a table with 4+ multi-digit numbers | [option] | same output as zoom_always on every measure and every changed number; saves 8% of its cost (0.0058 vs 0.0063 USD/page), all of it on scans without tables | dev cost.business.s_page +36.3; dev bus.same_type +0.2; held cost.business.s_page +37.7; held bus.totals_ok +1.0 |
| 41 | 2026-11-12 | ws4-ocr-prep-speed | zoom_gate=ocr_disagree |
strips only when a number of the page read is not confirmed by a local OCR word | [drop] | Tesseract misses numbers on almost every scan, so the gate almost never closes: 0.0061 vs 0.0063 USD/page, same accuracy | dev cost.business.s_page +15.2; dev bus.same_type +0.2; held cost.business.s_page +16.7; held bus.totals_ok +1.0 |
| 42 | 2026-11-12 | ws4-ocr-prep-speed | zoom_gate=table_and_disagree |
strips only when both: a numeric table and an unconfirmed number | [option] | same accuracy as zoom_always at the lowest cost of the four (0.0057 USD/page); only 0.0001 below table_numbers and depends on OCR quality | dev cost.business.s_page +13.7; dev bus.same_type +0.2; held cost.business.s_page +15.9; held bus.totals_ok +1.0 |
| 43 | 2026-11-12 | ws4-ocr-prep-speed | zoom_prefetch=1 (first strip set starts with the page read) |
the 3 strips of a scan are started together with the page read instead of after it | [keep] | identical content to zoom_always; model time on scans 27.3 vs 39.2 s/page (mean), cost +0.00001 USD/page from strips that were not needed; only matters when the zoom vote is on | dev cost.business.s_page +14.2; dev bus.same_type +0.2; held cost.business.s_page +15.3; held bus.totals_ok +1.0 |
| 44 | 2026-11-12 | ws4-ocr-prep-speed | reader effort medium instead of high (all pages) |
DOCFORGE_READER=anthropic_api:haiku#medium | [drop] | dev only: cost -35% and about half the time, olmOCR +1.8, but business reconciled totals 176 vs 235, grid_ok -8.2, answer-key recall -9.9, OmniDocBench table TEDS -8.9, RD-TableBench -0.4: tables get worse | dev bus.totals_found -85.0; dev bus.totals_ok -59.0 |
| 45 | 2026-11-12 | 3-verify | grid_norm |
Verify check grid=norm: tables whose rows differ in width are normalised deterministically where only one reading exists (single-cell row widened, trailing empty cells removed); no model call | [keep] | Free and never worse on any measure, but repairs only 3 of 47 broken tables (business grid_ok +0.7 dev / +1.3 held); useful as the first step before the repair prompt. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 46 | 2026-11-12 | 3-verify | grid_pad |
Verify check grid=pad: norm plus empty cells appended to short rows (the grid a browser draws); no model call | [option] | Business grid_ok +10.3 dev / +11.3 held at zero cost and no measure worse, but padding proves nothing about which column a value belongs to; keep as an opt-in for consumers that need rectangular tables. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 47 | 2026-11-12 | 3-verify | grid_crop |
Verify check grid=crop: a broken table is re-read from a full-resolution crop with the strict table prompt; accepted only if the grid is consistent and numbers/words match the page read | [drop] | Only 15 of 47 broken tables repaired (grid_ok +3.4 dev / +4.7 held): a fresh crop read usually differs in some digit or word from the page read and is rejected; same cost as the repair prompt, which repairs far more. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 48 | 2026-11-12 | 3-verify | grid_draft |
Verify check grid=draft (acceptance words>=0.9, the first setting): crop re-read with the broken draft in the prompt (repair prompt); accepted only if consistent and same numbers, >=90% same words | [needs-work] | Business grid_ok 87.6->99.3 dev / 84.7->96.7 held (r-1 95.8 / 94.2), RD-TableBench +0.0; but at words>=0.9 three of 21 repaired dev tables lost one or two words, so acceptance was tightened to exact (see grid_norm_draft_strict); same_type -0.1 dev is one repaired UB-04 header table that no longer fuzzy-matches r-1's flattened version. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 49 | 2026-11-12 | 3-verify | grid_norm_draft |
grid=norm+draft at acceptance words>=0.9: deterministic normalisation first, repair prompt for the rest | [needs-work] | Business grid_ok 100.0 dev / 96.7 held; superseded by the exact-words acceptance rule (three dev repairs lost a word or two at 0.9). | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 50 | 2026-11-12 | 3-verify | numbers |
Verify check numbers=1: printed column/row totals that miss by a digit-sized amount -> zoomed re-read of the rows involved; a cell changes only if the re-read differs and the change makes the total reconcile without breaking another; else flagged | [keep] | Never worse on any quality measure; 12 crop reads on the whole devset ($0.018): 0 cells changed on dev, 2 cells on held (1935 Fed bulletin p044: 5,390->5,399 and 396->399, both make a row total reconcile), business totals_ok 239->241 held (r-1 223). Small effect because vanilla has few digit-sized misses. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 51 | 2026-11-12 | ws4-ocr-prep-speed | words=tesseract>rapidocr (PP-OCR fills what Tesseract cannot place) |
Ground uses Tesseract words first and PP-OCRv6 (RapidOCR, ONNX, Apache-2.0) words for the remaining blocks; second OCR only on pages without a trusted text layer | [keep] | measured boxes 76.4 -> 90.7% of blocks on hard scans, estimated 20.5 -> 8.2%, IoU>=0.5 with r-1 70.9 -> 78.0%, median IoU 0.839 -> 0.845; costs about 8x Tesseract CPU per scanned page | |
| 52 | 2026-11-12 | ws4-ocr-prep-speed | words=tesseract+rapidocr (PP-OCR first) |
Ground tries PP-OCR words before Tesseract | [drop] | more measured boxes (90.4%) but median IoU with r-1 falls 0.839 -> 0.696: PP-OCR word boxes are looser where Tesseract also works | |
| 53 | 2026-11-12 | ws4-ocr-prep-speed | words=rapidocr (PP-OCR replaces Tesseract) |
PP-OCR words for every stage | [drop] | same looser boxes (median IoU 0.696) and fewer measured boxes than the pair (89.0%) | |
| 54 | 2026-11-12 | 3-verify | complete_text |
Verify check complete=text: on born-digital PDFs, lines of the trusted text layer (triage text_layer_exact, not doubled, on the page, seen by local OCR at the same place) that are absent from the read and are plain words outside tables/figures are restored from the file text | [drop] | 9 lines restored on 4 of 389 PDF pages, no measure improved (header/footer text retained stays 95.2 / 90.9), and one restored bottom-margin legend line typed Footer lowers business key_precision dev 85.9 -> 85.3. The Haiku read almost never drops text that a trusted text layer has. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 55 | 2026-11-12 | 3-verify | complete_crop |
Verify check complete=crop: same pointer as complete=text, but the place is re-read by Haiku from an enlarged crop and only the model's line is inserted | [drop] | 8 crop reads ($0.005), 3 lines restored on one page (labels of a diagram), no measure moved in either direction. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 56 | 2026-11-12 | 3-verify | complete_scan |
Verify check complete=scan: on pages without a trusted text layer, Tesseract lines (confident words) with no match in the read point at a band; Haiku re-reads the band from an enlarged crop; a crop line is inserted only if the read lacks it and the OCR line agrees with it | [drop] | 40 crop reads ($0.015), 19 lines restored on 9 of 639 pages: 4 running headers (correct), 13 lines that are text inside screenshots/advertisements (not text by the answer keys: DocLayNet type_precision 70.6 -> 69.9 dev, 67.2 -> 66.8 held; 2 of 3 OmniDocBench inserts not in its key), header/footer text retained unchanged. Guarded margins-only variant measured separately. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 57 | 2026-11-12 | 3-verify | flags |
Verify check flags=1 alone (detection only, no model call): blocks with an inconsistent table grid, a digit-sized totals miss, or (in this run) OCR/text-layer lines without a match get confidence low and are listed in extra.checks | [keep] | Changes no content and no measure; 109 flags on 80 of 639 pages. RD-TableBench tables with a grid flag score 79.8 (n=4) and with a totals flag 73.5 (n=1) against 89.2 unflagged (n=90): those flags mark worse tables. Completeness flags (52 of the 109) do not: flagged pages have fewer failed olmOCR tests (0.44 vs 0.90 per page) and lower OmniDocBench text edit distance, so they are now off by default (flags_completeness=true turns them on). | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 58 | 2026-11-12 | ws1-layout | ws1_model_heron |
DOCFORGE_REGIONS=(model alone): Docling heron layout model alone on the DocLayNet pages, no reader (blocks = detections) | [keep (as detector)] | 94% type recall and IoU 0.91 on dev, far above r-1; chosen detector | dev dln.type_recall +21.5; dev dln.type_precision +17.9; held dln.type_recall +26.8; held dln.iou50 +21.1 |
| 59 | 2026-11-12 | ws1-layout | ws1_model_ppdoclayout_plus |
DOCFORGE_REGIONS=(model alone): PP-DocLayout_plus-L alone on the same pages | [drop] | type recall 74% and IoU 0.75: its label set and box conventions do not map to DocLayNet/Reducto types as well as heron | dev dln.type_recall_lenient +7.2; dev dln.iou50 +4.4; held dln.type_recall_lenient +7.4; held dln.iou50 +4.4 |
| 60 | 2026-11-12 | ws1-layout | ws1_a_off |
DOCFORGE_REGIONS=: in-process replay with no switch on all six components, both halves (must equal vanilla) | [check] | no measure differs from vanilla on dev or held: the replay tool is faithful | dev cost.doclaynet.s_page -0.4; dev tables.score +0.0; held cost.omni.s_page -1.9; held cost.doclaynet.s_page -1.1 |
| 61 | 2026-11-12 | ws1-layout | ws1_http_default |
DOCFORGE_REGIONS=types=guarded,boxes,tables=broken,forms=notables: the recommended default through the real HTTP server on port 8831 (DocLayNet, FUNSD, business dev) | [check] | 151 of 151 results identical to the replay responses | dev dln.type_recall +14.5; dev dln.type_precision +13.1 |
| 62 | 2026-11-12 | ws1-layout | ws1_a_types |
DOCFORGE_REGIONS=types: block types from the matched layout region, every type change allowed; reading untouched | [option] | DocLayNet type recall +17 dev / +15 held, but worse on the business answer key (-0.7/-1.2 precision), r-1 agreement on held (-1.4) and one FUNSD link per half | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held dln.type_precision +16.9 |
| 63 | 2026-11-12 | ws1-layout | ws1_a_types_keepfurn |
DOCFORGE_REGIONS=types,deny=Header>|Footer>: types from layout, but a block the reader typed Header/Footer keeps that type | [drop] | fixes olmOCR headers_footers (+11 / +5) but the business key and FUNSD losses stay; superseded by types=guarded | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 64 | 2026-11-12 | ws1-layout | ws1_a_guarded |
DOCFORGE_REGIONS=types=guarded: types from layout only where the content agrees or the detection is confident (rules in structure.guarded, chosen on dev from the 18-transition sweep and three references) | [keep (core of the default)] | no measure worse on dev or held; DocLayNet type recall +14.5 / +12.8, type precision +13 / +14, olmOCR headers_footers +11 / +5, business key and r-1 agreement up | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 65 | 2026-11-12 | ws1-layout | ws1_a_boxes |
DOCFORGE_REGIONS=boxes: block box from the one-to-one matched layout region, except page footers and captions (word box is tighter there); Figure blocks take the picture box; types untouched | [keep (with guarded)] | IoU +0.03 / +0.02; alone it loses one block on the lenient type measure on dev (-0.17), not together with guarded types | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 66 | 2026-11-12 | ws1-layout | ws1_a_both |
DOCFORGE_REGIONS=types,boxes: all type changes + boxes | [option] | as types: the unguarded type changes lose on business and FUNSD | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held dln.type_precision +17.0 |
| 67 | 2026-11-12 | ws1-layout | ws1_a_gboxes |
DOCFORGE_REGIONS=types=guarded,boxes: guarded types + layout boxes | [keep (core of the default)] | no measure worse on dev or held; guarded gains plus IoU +0.03 / +0.02; no model call, only the layout model | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 68 | 2026-11-12 | ws1-layout | ws1_a_split |
DOCFORGE_REGIONS=types,boxes,split: all type changes + boxes + text blocks cut at line breaks between layout regions (first version of split, before the form guard) | [drop] | FUNSD links -2.7 / -1.0, business key precision -7.6 / -1.4: it cut form fields apart | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.8; held dln.type_recall +19.4; held cost.omni.s_page +18.2 |
| 69 | 2026-11-12 | ws1-layout | ws1_a_gsplit |
DOCFORGE_REGIONS=types=guarded,boxes,split: guarded types + boxes + first version of split | [drop] | FUNSD links -1.9 / -0.7 (fields cut apart) | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.8; held cost.omni.s_page +18.1; held dln.type_recall +16.0 |
| 70 | 2026-11-12 | ws1-layout | ws1_a_gsplit2 |
DOCFORGE_REGIONS=types=guarded,boxes,split: split with the form guard (Key Value blocks and boxes inside a form are never cut; pipe tables untouched) | [superseded] | no loss on dev; on held one OmniDocBench page got a paragraph cut before its last line (text edit +0.001) | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held dln.type_recall +15.6 |
| 71 | 2026-11-12 | ws1-layout | ws1_a_gsplit3 |
DOCFORGE_REGIONS=types=guarded,boxes,split: split, and two boxes of one type cut a block only when both score >= 0.8 | [superseded] | no loss, but drops most of the held gain of split (IoU +0.028 instead of +0.044) | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.4 |
| 72 | 2026-11-12 | ws1-layout | ws1_a_gsplit4 |
DOCFORGE_REGIONS=types=guarded,boxes,split: split as shipped: form guard; an unsure same-type box cuts only where the text ends a sentence or a list item starts | [option (held not clean)] | no measure worse on dev or held and the best layout numbers (held: type recall +15.4, IoU +0.043, IoU>=0.5 +4.3), but its last rule was written after seeing one held page, so the held figures are not an untouched test | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held dln.type_recall +15.4 |
| 73 | 2026-11-12 | ws1-layout | ws1_b_grid |
DOCFORGE_REGIONS=tables=grid: every detected table re-read from its crop; replaces the page read when the crop grid is consistent | [option] | mixed: grid_ok +1.4 / +2.7 and small table gains, but totals_ok -5 on dev, olmOCR table_tests -1.7 and multi_column -1.3 on held: a second read of a table swaps errors | dev cost.tables.s_page +32.7; dev cost.business.s_page +24.7; held cost.tables.s_page +29.1; held cost.omni.s_page +23.4 |
| 74 | 2026-11-12 | ws1-layout | ws1_b_numbers |
DOCFORGE_REGIONS=tables=numbers: as grid, and the crop numbers must agree better with the independent words (PDF text / OCR) | [drop] | RD-TableBench -0.4 on dev; almost nothing replaced elsewhere | dev cost.tables.s_page +32.8; dev cost.business.s_page +24.5; held cost.tables.s_page +29.1; held cost.omni.s_page +23.4 |
| 75 | 2026-11-12 | ws1-layout | ws1_b_broken |
DOCFORGE_REGIONS=tables=broken: crop re-read only where the page read has an inconsistent grid; accepted when the crop grid is consistent | [keep (part of the default)] | never worse; business grid_ok +1.4 / +2.7; 15 crops on 639 pages, 8 repairs accepted | dev cost.tables.s_page +25.7; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 76 | 2026-11-12 | ws1-layout | ws1_b_broken_numbers |
DOCFORGE_REGIONS=tables=broken_numbers: broken, and the crop numbers must agree with the word evidence at least as well | [option] | never worse, but accepts 6 of the 8 repairs that broken accepts (grid_ok +1.4 / +2.0 against +1.4 / +2.7): the number check only removes good repairs here | dev cost.tables.s_page +25.6; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 77 | 2026-11-12 | ws1-layout | ws1_c_forms |
DOCFORGE_REGIONS=forms: form-like regions re-read from a crop with a form prompt, guarded (keeps words, more label:value lines) | [drop] | FUNSD links +1.7 / +0.3 but it turns business tables into label lines: 21 / 10 tables lost, r-1 agreement -3.8 / -1.9, key precision -6.0 on held | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +22.6; held cost.omni.s_page +18.7; held cost.olm.s_page +15.6 |
| 78 | 2026-11-12 | ws1-layout | ws1_c_forms_nt |
DOCFORGE_REGIONS=forms=notables: as forms, but never where the page read has a table in the region | [keep (small; part of the default)] | never worse; no change on dev, held FUNSD links +0.7 and business key +0.4; 49 crops on 639 pages, 6 accepted | dev cost.tables.s_page +25.3; dev cost.funsd.s_page +20.2; held cost.omni.s_page +18.8; held cost.olm.s_page +15.5 |
| 79 | 2026-11-12 | ws1-layout | ws1_e_complete |
DOCFORGE_REGIONS=complete: text regions with no matching output text are re-read from a crop and inserted | [drop] | 935 crops on 639 pages, 841 found the text already present; 76 insertions make OmniDocBench reading order worse (+0.018 dev), DocLayNet precision -0.2, business key precision -0.3 held | dev cost.tables.s_page +25.5; dev cost.funsd.s_page +22.6; held cost.omni.s_page +30.4; held cost.olm.s_page +23.4 |
| 80 | 2026-11-12 | ws1-layout | ws1_d_assemble |
DOCFORGE_REGIONS=assemble: page built only from parallel region reads (the page read is not used); olmOCR and DocLayNet only | [option] | best layout numbers of all (DocLayNet type recall 92.5, IoU 0.88 on dev: above r-1) and olmOCR tables +12.7, but long_tiny_text -11.8 and old_scans -2.9 on dev; 3 to 4 times the cost of the page read | dev cost.olm.s_page +29.4; dev cost.doclaynet.s_page +23.6; held cost.olm.s_page +64.3; held cost.doclaynet.s_page +23.7 |
| 81 | 2026-11-12 | ws1-layout | ws1_x_gb_broken |
DOCFORGE_REGIONS=types=guarded,boxes,tables=broken: combination: guarded types + boxes + broken-grid table repair | [keep] | sum of the parts, no measure worse on dev or held | dev cost.tables.s_page +25.7; dev cost.funsd.s_page +19.7; held cost.omni.s_page +18.1; held cost.olm.s_page +15.3 |
| 82 | 2026-11-12 | ws1-layout | ws1_x_gb_broken_forms |
DOCFORGE_REGIONS=types=guarded,boxes,tables=broken,forms=notables: combination: guarded types + boxes + broken-grid table repair + guarded form crops | [keep: recommended default] | no measure worse on dev or held; every gain of the single switches adds up; verified through the real HTTP server (151 pages identical to the replay) | dev cost.tables.s_page +25.6; dev cost.funsd.s_page +20.2; held cost.omni.s_page +18.8; held cost.olm.s_page +15.5 |
| 83 | 2026-11-12 | ws1-layout | ws1_x_all |
DOCFORGE_REGIONS=types=guarded,boxes,split,tables=broken,forms=notables: recommended default + split | [option (held not clean)] | no measure worse and the best numbers without extra reads, but split had one rule written after a held page was seen | dev cost.tables.s_page +25.6; dev cost.funsd.s_page +20.2; held cost.omni.s_page +18.8; held cost.olm.s_page +15.6 |
| 84 | 2026-11-12 | 3-verify | grid_norm_draft_strict |
Verify check grid=norm+draft with exact acceptance (the default now): deterministic normalisation, then a crop re-read with the broken draft in the prompt; a repair is accepted only if every row has the same width and the number multiset, word multiset and checkbox marks equal the page read's | [keep] | 35 of 47 broken tables repaired with no cell text changed; business grid_ok 87.6 -> 97.2 dev, 84.7 -> 95.3 held (r-1 95.8 / 94.2), RD-TableBench +0.0 / +0.0, nothing else moves except same_type dev 73.4 -> 73.3, which is one repaired two-level header table that no longer fuzzy-matches r-1's flattened table (not a type error). Cost +$0.0007 dev / +$0.0012 held per business page (44 crop reads on 639 pages). | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 85 | 2026-11-12 | 3-verify | grid_norm_draft_crop_strict |
grid=norm+draft+crop with exact acceptance: plain crop re-read as a third step for tables the repair prompt did not fix | [drop] | The third step repairs no further table (still 35 of 47) and adds 10 calls. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 86 | 2026-11-12 | 3-verify | grid_norm_draft_strict |
CORRECTION of the previous grid_norm_draft_strict line (its text said grid_ok 97.2 dev; the measured value is 98.6): grid=norm+draft with exact acceptance (same number multiset, word multiset and checkbox marks as the page read) | [keep] | 35 of 47 broken tables repaired with no cell text changed; business grid_ok 87.6 -> 98.6 dev, 84.7 -> 95.3 held (r-1 95.8 / 94.2); RD-TableBench +0.0 / +0.0; same_type dev 73.4 -> 73.3 is one repaired two-level header table that no longer fuzzy-matches r-1's flattened table; +$0.0007 dev / +$0.0012 held per business page. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 87 | 2026-11-12 | 3-verify | numbers_all |
numbers=1;numbers_only_near=false: every printed total that does not reconcile (not only digit-sized misses) gets the zoomed re-read; same acceptance rule | [drop] | 61 crop reads ($0.115) instead of 12 ($0.018) for exactly the same two accepted cell changes (business totals_ok 239 -> 241 held, dev unchanged): totals that miss by more than a digit are totals the rule does not understand, not misreads. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 88 | 2026-11-12 | ws4-ocr-prep-speed | zoom with gates: pre-read OCR numbers >= 20, prefetch 1, post-read table_legible |
read options zoom_vote + zoom_pre_gate=20 (strips only when local OCR sees 20+ numbers) + zoom_prefetch=1 (first 3 strips start with the page read) + zoom_gate=table_legible (second strip set and vote only when the read has a numeric table and no [illegible] mark) | [keep] | no measure below vanilla on 5 components, both halves; keeps all 9 hand/OCR-verified digit corrections of the always-on vote on printed scans and none of its 9 unverifiable changes on handwriting; OmniDocBench held text edit 0.136 -> 0.116 and table TEDS 41.5 -> 43.7; business 0.0055 USD/page vs 0.0063 always-on and 0.0034 vanilla; model time 23.6 s/page vs 29.9 always-on and 21.8 vanilla | dev cost.business.s_page +12.2; dev cost.olm.s_page +7.9; held cost.business.s_page +13.1; held cost.olm.s_page +8.7 |
| 89 | 2026-11-12 | 3-verify | complete_scan_margins |
complete=scan;complete_margins_only=true: the scan completeness check restricted to the top and bottom 7.5% of the page (guard against text inside pictures) | [option] | The guard removes the DocLayNet loss: no quality measure is worse. But only 4 running headers are restored on 639 pages (0 on dev; 3 olmOCR and 1 OmniDocBench page on held, all confirmed by r-1 or the key) for 26 crop reads ($0.009), and no measure improves (header/footer text retained stays 95.2 / 90.9). Not worth a default. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 90 | 2026-11-12 | 3-verify | combo_grid_numbers_flags |
RECOMMENDED: Verify checks grid=norm+draft;numbers=1;flags=1 on top of vanilla (table grid repair with exact acceptance, printed-totals check with zoomed re-read, confidence flags for what stays unresolved); extract cases included | [keep] | Business grid_ok 87.6 -> 98.6 dev, 84.7 -> 95.3 held (r-1 95.8 / 94.2); totals_ok 235 / 239 -> 235 / 241 (r-1 230 / 223); RD-TableBench +0.0; extract 100 / 100 unchanged; every other quality measure identical to vanilla except same_type dev 73.4 -> 73.3 (one correctly repaired two-level header table no longer fuzzy-matches r-1's flattened one). 56 crop reads on 639 pages: +$0.0008 dev / +$0.0014 held per business page, under +$0.0003 elsewhere. 15 pages keep a low-confidence flag. | dev bus.grid_ok +11.0; dev cost.extract.s_page +7.2; held bus.grid_ok +10.7; held cost.extract.s_page +5.8 |
| 91 | 2026-11-12 | ws4-ocr-prep-speed | FINAL: reader effort medium (the API default) instead of high, all pages |
DOCFORGE_READER=anthropic_api:haiku#medium; supersedes the provisional dev-only line | [drop] | 35% cheaper, but business tables get worse far beyond read-to-read noise (dev: consistent grid -8.2 vs noise +2.5, answer-key recall -9.9 vs -2.2, totals 176 vs 235; held: grid -6.0, tables found -14, RD-TableBench -1.2); text pages equal or better (olmOCR +1.8 / +0.1) | dev bus.totals_found -85.0; dev bus.totals_ok -59.0; held cost.business.s_page +55.3; held cost.omni.s_page -17.9 |
| 92 | 2026-11-12 | ws4-ocr-prep-speed | NOISE FLOOR: a second independent high-effort read of the dev half (same engine as vanilla) |
reader sample dev2 instead of dev1; nothing else changes | [option] | not a variant: shows how far two identical runs differ. Differences of this size between a variant and vanilla are not evidence: grid +2.5, key recall -2.2, key precision -1.2, olm multi_column -3.6, old_scans +2.9, OmniDocBench table TEDS -8.1, formula edit -0.106; RD-TableBench +0.1, totals 0 | dev cost.omni.s_page -15.8; dev omni.table_teds_page -10.4 |
| 93 | 2026-11-12 | ws4-ocr-prep-speed | effort_route=medium for easy pages (fewer than 15 numbers and number share < 0.06 in the pre-read words) |
read option effort_route: pages whose text layer / OCR words hold few numbers are read at medium effort, the rest at high (composite of saved medium and high reads; 31% of pages routed) | [option] | every difference from vanilla is inside read-to-read noise on both halves (held: grid -2.4, RD-TableBench -1.1, key recall -0.8; dev: grid +1.9, key recall +2.3), but it saves only 4 to 9% of the cost: not worth a default | dev omni.table_teds_page -9.1; dev omni.table_teds -5.5; held omni.table_teds_page +15.5; held cost.business.s_page +11.6 |
| 94 | 2026-11-12 | ws4-ocr-prep-speed | effort_route=medium for easy pages (fewer than 40 numbers and share < 0.12) |
same switch, wider rule (56% of pages routed) | [drop] | saves 13 to 20% but business held falls beyond noise: consistent grid -4.8, tables found -16, answer-key recall -4.7, precision -2.6 | dev omni.table_teds_page -9.4; dev cost.business.s_page -7.4; held cost.business.s_page +31.4; held bus.tables -16.0 |
| 95 | 2026-11-12 | ws2-forms | fillable |
fillable PDFs: field values and checkbox/radio states read from the file as exact evidence; marks and values corrected in the read, |
[keep] | final: no measure lower on any component or split (same_type +0.38/+4.34, key precision 0/+1.22, grid_ok +1.89/+0.49); on 26 filled forms typed values 2-3% -> 100% and checkbox states 57-64% -> 100%; no model call | dev bus.tables -12.0; dev cost.business.s_page +4.1; held bus.tables -22.0; held cost.business.s_page +6.8 |
| 96 | 2026-11-12 | ws2-forms | kv_rules |
flat/scanned forms: a table whose cells are self-contained fields (checkbox, blank, box number, label line over value; no header row, no numeric column) becomes one Key Value block; no model call | [option] | final: bus.same_type -0.32/-0.35 because r-1 itself keeps 24 of the 55 form grids as Table (31 Key Value); right on all 4 answer-key grids it converts, converts no table on RD-TableBench/OmniDocBench/olmOCR/DocLayNet; fails the never-worse bar on agreement with r-1 | dev bus.tables -30.0; dev cost.business.s_page +3.6; held bus.tables -29.0; held cost.business.s_page +3.0 |
| 97 | 2026-11-12 | ws2-forms | kv_rules:rows |
as kv_rules but one Key Value block per row of the converted grid (r-1's block size) | [option] | held key recall +3.9 and key precision +1.2 with r-1's Key Value block count, but bus.same_type -0.92 on held | dev bus.tables -30.0; dev bus.matched -7.5; held bus.tables -29.0; held bus.matched -7.1 |
| 98 | 2026-11-12 | ws2-forms | kv_lines |
open label: takes the next short block as its value; CAPS LABEL value lines typed Key Value on form pages | [drop] | final: FUNSD links +1.55/+0.68 but on held business key precision -1.22, key recall -1.06, same_type -0.33 (the caps half) | dev cost.doclaynet.s_page +10.3; dev cost.business.s_page +10.0; held cost.business.s_page +10.6; held cost.omni.s_page +8.4 |
| 99 | 2026-11-12 | ws2-forms | kv_lines:caps |
only: CAPS LABEL value lines typed Key Value on form pages | [drop] | FUNSD links +0.78/+0.68 but held business key precision -1.22, key recall -1.06, same_type -0.29 | dev cost.business.s_page +10.5; dev cost.funsd.s_page +3.5; held cost.business.s_page +10.4; held cost.funsd.s_page +3.5 |
| 100 | 2026-11-12 | ws2-forms | kv_lines:join |
only: a Key Value block ending in an open label: takes the next short text block as its value | [option] | FUNSD links +0.78 and key precision +0.61 on dev, nothing on held except one block of r-1 agreement lost (same_type -0.04); free | dev cost.omni.s_page +12.6; dev cost.business.s_page +6.8; held cost.omni.s_page +12.3; held cost.business.s_page +6.7 |
| 101 | 2026-11-12 | ws2-forms | region_reread |
run of form-grid tables re-read from a crop with the form prompt, merged only when the words agree (recall>=0.92, new<=0.15) | [drop] | final: FUNSD links +1.94 dev / 0 held; business held key precision -2.07, matched -1.03, same_type -0.85/-0.37; 28 of 53 crop reads rejected; $0.053 for 53 crops | dev bus.tables -16.0; dev cost.business.s_page +4.9; held bus.tables -22.0; held cost.business.s_page +10.0 |
| 102 | 2026-11-12 | ws2-forms | fillable + kv_lines:join |
both switches | [option] | passes the bar: no measure lower than vanilla on any component or split (same_type +0.38/+4.27, FUNSD dev +0.78, key precision +0.61/+1.22); join adds only dev gains over fillable alone, so default stays fillable | dev bus.tables -12.0; dev cost.business.s_page +11.3; held bus.tables -22.0; held cost.business.s_page +10.5 |
| 103 | 2026-11-12 | ws2-forms | fillable + kv_rules |
both switches | [option] | held same_type +4.95, grid_ok +2.3/+1.9, answer-key Key Value hits 14/17 and 30/47, but dev same_type -0.32 (kv_rules against r-1's Tables) | dev bus.tables -36.0; dev cost.business.s_page +5.7; held bus.tables -38.0; held cost.business.s_page +5.6 |
| 104 | 2026-11-12 | ws2-forms | fillable + kv_rules + kv_lines:join |
three switches | [option] | sum of the parts: FUNSD dev +0.78, held same_type +4.88, but dev same_type -0.32 | dev bus.tables -36.0; dev cost.business.s_page +10.0; held bus.tables -38.0; held cost.business.s_page +10.1 |
| 105 | 2026-11-12 | 3-verify | grid_crop |
CORRECTION of the text of the earlier grid_crop line (same measures): plain crop re-read of a broken table with the strict table prompt | [drop] | 15 of 47 broken tables repaired (grid_ok +3.4 dev / +4.7 held). Of the 32 rejected re-reads 14 came back with an inconsistent grid again, 10 with digits that differ from the page read, 5 tables could not be located, 3 other; the repair prompt with the draft repairs far more at the same cost. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 106 | 2026-11-12 | 3-verify | flags |
CORRECTION of the text of the earlier flags line (same measures): flags=1 alone, detection only | [keep] | 108 flags (not 109) on 80 of 639 pages, 52 of them completeness flags; RD-TableBench with a grid flag 79.8 (n=4), totals flag 73.5 (n=1), unflagged 89.2 (n=90); completeness flags do not mark worse pages and are off by default now. | dev extract.leaf_accuracy -100.0; dev extract.row_recall -100.0; held extract.leaf_accuracy -100.0; held extract.row_recall -100.0 |
| 107 | 2026-11-12 | ws4-ocr-prep-speed | COMBINED ws4 profile vanilla-api-ws4: words tesseract>rapidocr + prepare rotate + gated zoom with prefetch + 16 page workers |
profile vanilla-api-ws4 (all kept ws4 switches together) on the 147 business pages | [keep] | no measure below vanilla; 9 verified digit corrections; the two sideways pages are read upright (held answer-key precision +1.6, recall +0.7; same type as r-1 +0.7 / +0.9); blocks with a measured box 88.3 -> 93.5%, estimated 9.7 -> 5.7%; 0.0055 USD/page (vanilla 0.0034, current engine 0.0094, r-1 0.0100) | dev cost.business.s_page +2.5; dev bus.same_type +0.7; held cost.business.s_page +9.7; held bus.key_precision +1.6 |
| 108 | 2026-11-12 | ws4-ocr-prep-speed | triage.prepare=rotate (turn pages upright before reading) |
90/180/270 turn from the PDF text layer, EXIF, or PP-OCR + Tesseract orientation on pixels; PDF pages get /Rotate | [keep] | 0 wrong turns on 128 test images and 197 devset pages; 91 of 96 turned images put upright (5 left as is); the two sideways business pages are fixed (words 0.971 -> 0.998, measured boxes 3/21 -> 17/17); upright pages keep their bytes and reads | |
| 109 | 2026-11-12 | ws4-ocr-prep-speed | triage.prepare=deskew (straighten skewed scans) |
skew angle from a projection profile; a skewed scan is replaced by its straightened raster | [option] | angle estimate is right (116/128 within 0.5 degrees, the rest are natively skewed forms) but reading does not improve beyond read-to-read noise: recall 0.985 -> 0.989, order 0.924 -> 0.962 (noise 0.998 / 0.987), measured boxes 84% -> 83% | |
| 110 | 2026-11-12 | ws4-ocr-prep-speed | read.page_workers=16 (DOCFORGE_PAGE_WORKERS) with shared rate-limit backoff |
pages of one document read 16 at a time instead of 6; on 429/529 all threads wait for retry-after | [keep] | no rate limit seen with about 60 calls in flight; Fed H.4.1 43.0 -> 23.8 s, USPS 10-K unchanged (31 s, bound by its slowest page); scheduled from measured call seconds: 23.5 -> 17.9 s and 13.3 -> 13.3 s | |
| 111 | 2026-11-12 | ws4-ocr-prep-speed | read.zoom_prefetch=2 (both strip sets start with the page read) |
all 7 strips of a scan start with the page read | [drop] | the second set is needed on few pages: cost 0.0035 -> 0.0060 USD/page on Brown v. Board and no time gain over prefetch 1 (91 s -> 133 s on the loaded machine; 10.5 s for both when scheduled from call seconds) | |
| 112 | 2026-11-12 | ws4-ocr-prep-speed | DOCFORGE_BATCH_ASYNC=1 (Message Batches API for async jobs) |
async non-priority parse jobs send their reads as one batch per round through POST /v1/messages/batches | [option] | real batch of 10 page reads: 10 succeeded, cost exactly half (0.00667 vs 0.01334 USD), but 2,576 s from submit to results against 31 s through the normal API | |
| 113 | 2026-11-12 | ws4-ocr-prep-speed | FINAL COMBINED ws4 profile vanilla-api-ws4 (supersedes the earlier combined line: boxes of turned pages are now reported on the original page) |
profile vanilla-api-ws4: words tesseract>rapidocr + triage.prepare=rotate + zoom_pre_gate=20 + zoom_prefetch=1 + zoom_gate=table_legible + 16 page workers; 147 business pages | [keep] | no measure below vanilla; 9 verified digit corrections; the two sideways pages read upright and their boxes match r-1 again (IoU 0.5 to 0.9 instead of about 0); measured boxes 88.3 -> 93.5%, estimated 9.7 -> 5.7%, IoU>=0.5 with r-1 73.2 -> 75.9%; 0.0055 USD/page (vanilla 0.0034, current engine 0.0094, r-1 0.0100) | dev cost.business.s_page +3.5; dev bus.same_type +0.7; held cost.business.s_page +3.6; held bus.key_precision +1.6 |
| 114 | 2026-11-12 | combine | noise: second independent vanilla read (sample dev2), whole devset |
vanilla-api profile, reader sample dev2 instead of dev1: an identical run on independent page reads; 237 dev reads were WS4's, 395 new reads for held and for DocLayNet/FUNSD (/bin/zsh.77) | [check] | the spread between cb0_off (dev1) and cb_read2 (dev2) is the noise floor of every measure: see field noise; e.g. business grid 2.5/4.7, key recall 2.2/6.0, OmniDocBench table TEDS 8.1/12.8, DocLayNet type recall 4.4/2.2, FUNSD links 2.1/1.4; RD-TableBench 0.0/0.2 and totals 0/1 are stable | dev bus.key70_precision +13.8; dev omni.table_teds_page -10.4; held cost.omni.s_page -16.1; held omni.table_teds +15.0 |
| 115 | 2026-11-12 | combine | step 0: engine-final with every piece switched off |
profile engine-final, DOCFORGE_WORDS=tesseract and stage options that switch off rotate, zoom, handwriting route, forms, checks, regions; DOCFORGE_NO_LIVE=1 | [check] | 637 of 639 responses identical to the saved vanilla responses; the other 2 (omni_jiaocaineedrop_chap02.pdf_16, dln_laws_and_regulations_dbf12799fa21) had a failed page read in the baseline run and a complete cached read now, so the baseline vanilla is understated on those; cb0_off is the same-reads base of every later step | dev cost.business.s_page +2.7; dev dln.iou50 +2.0; held omni.table_teds_page +6.7; held cost.business.s_page +2.7 |
| 116 | 2026-11-13 | combine | step 1 as recommended by WS4: words tesseract>rapidocr + prepare=rotate (method auto) + gated zoom |
profile vanilla-api-ws4's switches on the same reads as cb0_off | [needs-work] | rotation method auto turned 5 UPRIGHT pages by 180 or 270 degrees (olmOCR old_scans 84, 85, 35, 91: handwritten letters, Tesseract orientation detection wrong with confidence 2.4 to 10.3; one OmniDocBench page of vertical Chinese handwriting) and their new reads are mostly [illegible]: olmOCR old_scans held -2.8 (noise 1.4), OmniDocBench held text edit +0.011 (noise 0.002). Everything else of the set is a gain: OmniDocBench layout IoU 0.30 -> 0.61 dev, 0.27 -> 0.46 held from the PP-OCR fill. Guard: prepare_method=safe (next line) | dev omnilay.iou50 +36.5; dev omnilay.type_recall_lenient +20.5; held omnilay.iou50 +23.5; held cost.omni.s_page +21.0 |
| 117 | 2026-11-13 | combine | step 1: + WS4 set (words tesseract>rapidocr, prepare=rotate with prepare_method=safe, gated and prefetched zoom, 16 page workers) |
on cb0_off's reads: PP-OCR fill for word positions, guarded rotation, zoom vote with pre-gate 20 / prefetch 1 / gate table_legible | [keep] | nothing lower beyond noise. OmniDocBench layout (new measure): box IoU 0.30 -> 0.61 dev and 0.27 -> 0.46 held, type precision +14 / +19, type recall +20 / +6: Tesseract cannot place Chinese text, PP-OCR can. Business key70 +0.8/+2.7 precision, totals +0/+1, olmOCR old_scans +1.4/+1.4 (zoom corrections). Guarded rotation turns exactly the 3 sideways pages of 639 (auto turned 5 upright ones as well); on 96 turned copies safe = auto (90 right, 4 left, 0 wrong). Only minus: DocLayNet held IoU -0.005 and IoU>=0.5 -0.7 (noise 0.032 / 4.8): on pages with an untrusted text layer the fill sometimes gives a block a larger box (7 pages up, 6 down) | dev omnilay.iou50 +36.5; dev omnilay.type_recall_lenient +20.5; held omnilay.iou50 +23.4; held omnilay.type_precision +19.8 |
| 118 | 2026-11-13 | combine | step 2: + WS3 checks (grid=norm+draft;numbers=1;flags=1) |
step 1 plus Verify checks: grid repair with the draft in the prompt and exact acceptance, zoomed re-read of digit-sized total misses, confidence flags | [keep] | business consistent grids 87.6 -> 98.6 dev and 84.7 -> 95.3 held (noise 2.5 / 4.7; r-1 95.8 / 94.2); all components 292 -> 309 of 315 and 262 -> 279 of 287; totals +0 / +1; no measure lower; 19 new crop reads (/bin/zsh.075) because the zoom vote had changed 9 drafts | dev omnilay.iou50 +36.5; dev omnilay.type_recall_lenient +20.5; held omnilay.iou50 +23.4; held omnilay.type_precision +19.8 |
| 119 | 2026-11-13 | combine | table repair alternative: step 1 + WS1 tables=broken instead of WS3 checks |
step 1 plus ground.regions=tables=broken (crop re-read of a table with a broken grid, accepted when the crop grid is consistent), no Verify checks | [drop] | same reads as cb2_ws3: business consistent grids 89.0 / 87.3 against 98.6 / 95.3 with WS3's repair (all components 295 and 267 against 309 and 279), and its acceptance does not require the same cell texts. WS3's repair is the one in engine-final; tables=broken stays a switch | dev omnilay.iou50 +36.5; dev omnilay.type_recall_lenient +20.5; held omnilay.iou50 +23.4; held omnilay.type_precision +19.8 |
| 120 | 2026-11-13 | combine | step 3: + WS1 layout (types=guarded,boxes,forms=notables; heron on CPU), without its table repair |
step 2 plus the Docling heron layout model: guarded block types, layout boxes, guarded form crops | [keep] | no measure lower. DocLayNet type recall +14.7 / +12.6 (noise 4.4 / 2.2), type precision +13.1 / +14.1, IoU +0.031 / +0.025; OmniDocBench layout (not seen by the layout model's training the way DocLayNet was) type recall +3.7 / +1.2, precision +3.5 / +3.0, IoU +0.027 / +0.022; olmOCR headers_footers +13.2 / +5.3; business type agreement with r-1 +3.9 / +1.6, key precision +2.1 / +1.2, second key +1.6 / +0.4. Interaction with the OCR fill: on vanilla word boxes the same layout switches lowered OmniDocBench layout type recall on held by 5.6 (58.4 -> 52.8, inside noise 6.3); with the fill they raise it | dev omnilay.iou50 +37.4; dev omnilay.type_recall_lenient +24.8; held omnilay.iou50 +24.2; held omnilay.type_precision +22.8 |
| 121 | 2026-11-13 | combine | interaction: both table repairs (WS3 grid repair, then WS1 tables=broken on what is left) |
step 3 with ground.regions=types=guarded,boxes,tables=broken,forms=notables | [drop] | no devset measure differs from step 3: after WS3's repair the WS1 repair has nothing left that it can fix; it only adds crop reads. Left out | dev omnilay.iou50 +37.4; dev omnilay.type_recall_lenient +24.8; held omnilay.iou50 +24.2; held omnilay.type_precision +22.8 |
| 122 | 2026-11-13 | combine | step 4a: + WS2 fillable |
step 3 plus read.forms=fillable (values and checkbox states from the PDF's form fields) | [keep] | both answer keys together (100 pages): precision +2.7 / +3.0, recall +1.8 / +2.5; second key precision +3.7 / +6.8; agreement with r-1 -0.4 dev (noise 0.5), +3.7 held. Interaction with WS1 forms=notables: once fillable has turned a form's tables into Key Value blocks, the layout form-crop re-read is no longer held back by its no-tables guard and re-reads the region (insurance_dol_ca1_p002: 4 blocks -> 35; first key held precision -4.5 on that one page, recall +6.4). A crop of the render cannot show values typed into the fields, so regions with filled fields are now never re-read (regions/forms.py guard, unit-tested) | dev omnilay.iou50 +37.4; dev omnilay.type_recall_lenient +24.8; held omnilay.iou50 +24.2; held omnilay.type_precision +22.8 |
| 123 | 2026-11-13 | combine | step 4: + kv_lines:join |
step 4a plus read.forms=fillable,kv_lines:join | [keep] | FUNSD links +0.8 / 0.0, first key precision +0.6 / 0.0, nothing lower beyond 0.3 (second key recall held -0.3: one block); costs nothing | dev omnilay.iou50 +37.4; dev omnilay.type_recall_lenient +24.8; held omnilay.iou50 +24.2; held omnilay.type_precision +22.8 |
| 124 | 2026-11-13 | combine | step 5: + handwriting route to Sonnet 5.5 (unguarded, as in engine-api) |
step 4 plus read.handwriting_route | [needs-work] | 66 of 639 pages go to Sonnet. Gains: OmniDocBench held text edit 0.116 -> 0.098 (noise 0.002), olmOCR old_scans +1.4 dev. Losses beyond noise: FUNSD links -1.0 / -3.4 (noise 2.1 / 1.4; 13 printed FUNSD forms with one or two [illegible] marks are re-read by Sonnet, which pairs fewer labels with values), business agreement with r-1 -1.0 dev (noise 0.5). Guard in the next line | dev omnilay.iou50 +37.2; dev omnilay.type_recall_lenient +25.5; held omnilay.iou50 +24.0; held omnilay.type_precision +23.3 |
| 125 | 2026-11-13 | combine | step 6: + kv_rules:rows (on top of step 5) |
step 5 plus read.forms=fillable,kv_lines:join,kv_rules:rows (form grids -> one Key Value block per row) | [keep] | decided on the answer keys: both keys together strict precision +3.7 / +1.3 and strict recall +1.4 / +1.8, lenient +0.5 / +0.1 and +0.6 / +1.0; the second key (written without looking at any output) strict +5.7 / +0.7 and +2.3 / +2.2; no data table converted (RD-TableBench, olmOCR tables, OmniDocBench tables, totals unchanged); agreement with r-1 -0.3 dev (noise 0.5), +0.6 held; consistent grids +0.8 / +0.4 | dev bus.tables -38.0; dev omnilay.iou50 +37.2; held bus.tables -38.0; held omnilay.iou50 +24.0 |
| 126 | 2026-11-13 | combine | step 7: + layout split (on top of step 5) |
step 5 plus ground.regions=types=guarded,boxes,split,forms=notables (text blocks cut at line breaks between layout regions; no character changes) | [keep] | fresh evidence, never used to tune split: second answer key precision +1.4 / +0.4 and recall +3.8 / +1.0; OmniDocBench layout IoU +0.004 / +0.005 and IoU>=0.5 +0.4 / +0.8. Also DocLayNet IoU +0.019 / +0.023, IoU>=0.5 +2.6 / +3.4, type recall 0 / +2.5; FUNSD links unchanged (form guard holds). Minus: agreement with r-1's types -0.5 / -0.3 (noise 0.5 / 4.3), OmniDocBench layout type precision -0.4 dev (noise 5.0): none beyond noise | dev omnilay.iou50 +37.6; dev omnilay.type_recall_lenient +25.5; held omnilay.iou50 +24.8; held omnilay.type_precision +23.3 |
| 127 | 2026-11-13 | combine | step 5g: handwriting route with the print guard (handwriting_illegible_max_agreement=0.5) |
step 5 with the guard: an [illegible] mark routes a page to Sonnet only when local OCR does not confirm the page read | [keep] | 52 pages go to Sonnet instead of 67 (FUNSD 5 instead of 13, business 16 instead of 21; 15 of the 18 business pages marked as handwriting still go). Against no route (step 4): FUNSD links +0.2 dev, -2.3 held (unguarded -1.0 / -3.4; noise 2.1 / 1.4), agreement with r-1 -0.6 / -0.1 (noise 0.5 / 4.3), OmniDocBench held text edit 0.116 -> 0.098 (noise 0.002), olmOCR old_scans +1.4 dev, long_tiny_text -1.3 dev (noise 2.6). The held FUNSD loss that remains is on faint forms that OCR cannot read either; kept because the route was asked for and its handwriting gain was judged earlier (no scripted handwriting truth in the devset) | dev omnilay.iou50 +37.2; dev omnilay.type_recall_lenient +25.5; held omnilay.iou50 +24.0; held omnilay.type_precision +23.3 |
| 128 | 2026-11-13 | combine | engine-final: step 5g + kv_rules:rows + split (the final profile) |
profile engine-final as committed: words tesseract>rapidocr, guarded rotation, gated zoom, WS3 checks, WS1 guarded types + boxes + split + form crops, WS2 fillable + kv_lines:join + kv_rules:rows, guarded handwriting route | [keep] | against same-reads vanilla nothing is lower beyond noise on both halves; FUNSD links held -1.7 (noise 1.4) from the handwriting route. Against r-1: ahead on olmOCR mean, long tiny text, multi column, OmniDocBench text edit, printed totals; behind on RD-TableBench (-1.4 / -2.0), FUNSD links, both answer keys, OmniDocBench layout boxes, DocLayNet lenient types; rest level or mixed. Business 0.0098 USD/page (r-1 0.0100), 31.5 model s/page mean. Extract 100 / 100. Filled-form truth set: 100% of values and marks | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +24.8 |
| 129 | 2026-11-13 | combine | engine-final live: business pages on fresh reads (sample live1) through the HTTP server, 4 requests at a time |
same profile, reader sample live1 (no cached read); plus 3 multi-page documents | [check] | 147 of 147 responses, none empty, no crash; 28.7 s/page wall mean, 20.0 median; 0.0093 USD/page. Against the replay: most measures inside the noise floor; beyond it on dev: second key recall -9.9 (noise 4.8), totals -6, grid -3.0 (noise 2.5), second key precision +12.8: a third read differs from the first by more than the first two differed. Multi-page: USPS 10-K 10 pages 16.6 s, scan packet 10 pages 42.6 s (5 pages by Sonnet), Brown 14 pages 13.3 s (12 of 29 calls replayed) | dev bus.totals_found -29.0; dev bus.key70_precision +20.4; held bus.tables -28.0; held bus.grid_ok +12.9 |
| 130 | 2026-11-13 | r2b-forms | engine-final at tag final-round-1 from the private snapshot, real HTTP server on port 8852 (baseline of round 2 B) |
nothing; base regions types=guarded,boxes,split,forms=notables | [check] | 639 of 639 responses identical to cb_final; scores equal except OmniDocBench held table TEDS 45.6 vs 43.7 (same responses: the official matcher is not deterministic on dense pages under load) | dev cost.omni.s_page +42.7; dev bus.tables -37.0; held cost.omni.s_page +45.8; held bus.tables -38.0 |
| 131 | 2026-11-13 | r2b-forms | the same by in-process replay after Ground (r2b/replay.py) |
nothing | [check] | 636 of 639 responses identical to the HTTP run; the 3 others are the 3 turned pages (boxes differ by 0.002 to 0.02); base of every replayed variant | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 132 | 2026-11-13 | r2b-forms | hfsplit: one-line running heads and feet cut into their word groups |
regions +hfsplit | [keep (provisional, one read set)] | dev/held: both keys recall +3.6/+1.9 (noise 3.8/4.6), first key recall +3.9/+1.4 (noise 2.2/6.0), DocLayNet IoU +0.009/+0.012, IoU>=0.5 +1.0/+1.7, OmniDocBench layout IoU +0.008/+0.008 (noise 0.003/0.010); lower inside noise: DocLayNet type precision -0.2/-0.6 (a wrong Header block cut in two counts twice), held first key precision -0.6 | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.7 |
| 133 | 2026-11-13 | r2b-forms | kvsplit, first version (any Key Value block; pieces retyped by the layout rules) |
regions +kvsplit | [superseded] | dev first key precision -3.5 (noise 1.2), held OmniDocBench layout precision -1.3 (noise 0.5): the pieces of a Key Value block match text detections one to one and the guarded rule made them Text; fixed in r2b_kvsplit2 | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.9 |
| 134 | 2026-11-13 | r2b-forms | kv=form+any: field-like Text inside any detected form region becomes Key Value |
regions +kv=form+any | [drop] | FUNSD links +4.5/+2.4 but OmniDocBench layout type precision -7.5/-3.5 and recall -7.1/-0.8 (noise 5.0/0.5, 1.5/6.3): the layout model calls exercise and note pages form regions | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 135 | 2026-11-13 | r2b-forms | kv=form: the same only where the page read itself shows a field in the region |
regions +kv=form | [keep (provisional, one read set)] | FUNSD links +1.7/+2.0 (noise 2.1/1.4), both keys precision +10.8/+0.5 (noise 10.3/2.7), same type as r-1 +0.5/+0.3; nothing moves on OmniDocBench, DocLayNet, olmOCR, tables; held key recall -1.6 (noise 4.6): short notes inside form regions that the key calls Text | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 136 | 2026-11-13 | r2b-forms | kv=form+heads: also Header / Section Header made from Text by a position or type-size rule |
regions +kv=form+heads | [drop] | against kv=form: dev both keys precision +1.4 but recall -0.4/-0.6 and same type as r-1 -1.0/-0.7 | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 137 | 2026-11-13 | r2b-forms | kv=form+page: on a page with no form region and no checkbox the reader's Key Value lines are Text |
regions +kv=form+page | [keep (provisional)] | against kv=form nothing lower: DocLayNet type recall +0.2/+0.1, precision +0.2/+0.1, OmniDocBench layout held recall +2.3, precision +0.5; business and FUNSD identical | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.9 |
| 138 | 2026-11-13 | r2b-forms | figures, first version (Figure block with the text [Figure]) |
regions +figures | [superseded] | DocLayNet type recall +1.5/+1.1, OmniDocBench layout recall +0.7/+0.9 and IoU +0.014/+0.010, but olmOCR old_scans held -4.2 (noise 1.4) and business key precision -0.5/-1.8: the placeholder text; r2b_figures2 has no content | dev bus.tables -37.0; dev dln.type_recall +15.8; held bus.tables -38.0; held dln.type_recall +16.8 |
| 139 | 2026-11-13 | r2b-forms | boxfill: blocks without a located box take the free layout region with their words |
regions +boxfill | [keep (small)] | second key recall +0.6/+0.3, DocLayNet held IoU +0.001; held DocLayNet type precision -0.1 (1 block); nothing else moves | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 140 | 2026-11-13 | r2a-tables | engine-final replayed (no round-2 switch) |
profile engine-final, in-process replay from documents saved after the plain Ground step; OmniDocBench TEDS timeout raised | [baseline] | 639 of 639 responses identical to cb_final (HTTP); omni held table TEDS 43.7 -> 45.6 and per-page 67.1 -> 73.0 only because one large table had timed out in the official TEDS code on the loaded machine and scored 0 (now 0.87) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 141 | 2026-11-13 | r2a-tables | tables: clean=1 |
cell hygiene, deterministic: picture/icon descriptions removed from table cells, ul/ol/li and p inside cells become plain lines with a bullet | [keep] | RD-TableBench +0.04 / +0.43 (noise 0.04 / 0.19); no other measure moves on any component; no model call | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 142 | 2026-11-13 | r2a-tables | tables: merge=layout |
two consecutive table blocks joined when one layout table region holds both and the column counts match | [drop] | the layout model also draws one region around tables the keys keep apart: RD-TableBench dev -0.56, OmniDocBench held table TEDS -4.9 and table edit +0.019 (noise 0.011); 15 joins on 8 pages, none of them the split RD table | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -43.0; held omnilay.iou50 +23.0 |
| 143 | 2026-11-13 | r2a-tables | tables: merge=cont |
merge=layout plus: a table with the same columns directly below, without a column header of its own, is joined | [drop] | joins the split RD table (held +0.82) but RD dev -0.56, OmniDocBench table TEDS -6.2 / -4.9 and struct -6.6 on dev (noise 0.6): stacked tables with equal columns are usually separate tables | dev bus.tables -40.0; dev omnilay.iou50 +37.4; held bus.tables -43.0; held omnilay.iou50 +23.0 |
| 144 | 2026-11-13 | r2a-tables | formulas=tag |
equation number written as \quad (n) at the end of a displayed formula becomes \tag{n} (deterministic) | [drop] | formula edit distance held 0.1325 -> 0.1321: no effect (the official code already scores the formula text almost the same) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 145 | 2026-11-13 | r2a-tables | tables: reread=missed |
layout shows a table or index region where the page read has running text: region re-read with a table prompt that may answer NOT_A_TABLE; accepted only as a real grid with the page read's words whose cells stand in columns on the page | [option] | 8 candidates on 639 pages, 2 accepted, both right (RD menu table 0 -> 0.91: RD held +1.78, no_table 1 -> 0; a DocLayNet index page); superseded by missed_prompt=force, which also finds the olmOCR table the model declined | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 146 | 2026-11-13 | r2a-tables | tables: reread=missed;missed_prompt=force |
as reread=missed with the plain table prompt (no NOT_A_TABLE way out): the acceptance rules alone decide | [keep] | 8 candidates, 3 accepted, all three real tables: RD held +1.76 (no_table 1 -> 0), olmOCR table tests held +6.7 (83.3 -> 90.0; r-1 88.3), DocLayNet held type precision +2.0; the 5 text regions (two olmOCR index pages, one OmniDocBench page, two DocLayNet regions) are rejected by the rules; no measure worse beyond noise; 8 calls, 0.009 USD on 639 pages | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 147 | 2026-11-13 | r2a-tables | tables: reread=dropped |
layout shows a table where the page read has no table and OCR words there are missing from the page read: crop read added when OCR confirms its words | [drop] | 11 candidates (the newspaper pages), 0 accepted: Tesseract on newspaper print confirms under 60% of the crop's words, or the model says it is not a table; nothing moves | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 148 | 2026-11-13 | r2a-tables | tables: reread=dropped;dropped_precision=0.4 |
the same with OCR confirmation lowered to 40% | [drop] | a few listings added on one newspaper page; OmniDocBench held table TEDS -0.2, struct +0.7, all inside noise: no gain | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 149 | 2026-11-13 | r2a-tables | tables: reread=broken |
tables still broken after workstream 3's repair: table-only re-read, accepted when consistent with the same numbers and 95% of the same words | [keep] | 3 candidates left after WS3's repair, 2 repaired (1 RD, 1 olmOCR): RD dev +0.35; nothing else moves; additive to verify.checks grid=norm+draft | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 150 | 2026-11-13 | r2a-tables | formulas=matrix |
a matrix the page read wrote as an HTML table inside a layout formula region (no table region there) is read again from the crop with a formula prompt; accepted when digits and letters are the same | [keep] | 1 region on 639 pages: OmniDocBench dev formula edit 0.184 -> 0.070 (r-1 0.073; noise 0.106), reading order -0.011; held unchanged; 1 call | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 151 | 2026-11-13 | r2a-tables | formulas=lines |
tall displayed formulas re-read from the crop to keep printed line breaks | [option] | 3 candidates, 2 accepted on a DocLayNet page (no formula measure there), the OmniDocBench one rejected (crop held other text): no measured effect | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 152 | 2026-11-13 | r2a-tables | tables: reread=all;accept=content;min_area=0.5 |
tables that cover half the page or more are read again from the whole page image with the table-only prompt; accepted with a consistent grid, exactly the same numbers and 95% of the same words (both ways) | [option] | RD-TableBench +2.06 / +0.17 (noise 0.04 / 0.19); nothing worse beyond noise; superseded by the same rule for every table | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 153 | 2026-11-13 | r2a-tables | tables: reread=all;accept=content |
every table with a matching layout region is read again with the table-only prompt (crop, or whole page when the table covers half of it); accepted with a consistent grid, exactly the same numbers and 95% of the same words | [keep] | 277 tables re-read, 105 accepted: RD-TableBench +2.06 / +0.21, olmOCR table tests dev +6.3 (87.3 -> 93.7), no measure worse beyond noise on any component, printed totals and business grids unchanged; +0.0008 USD per page on average | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.0 |
| 154 | 2026-11-13 | r2a-tables | tables: reread=all;accept=evidence |
accept=content plus: where numbers differ, the re-read is taken on a born-digital page when the exact text layer agrees better with its numbers | [option] | 3 more business tables accepted (page read had numbers the text layer lacks); every measure equal to accept=content: correct by construction but not measured by the devset | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 155 | 2026-11-13 | r2a-tables | tables: reread=all;accept=fit |
accept=content and the re-read's located cells must fit columns and rows of the page at least as well | [drop] | the fit of located cells does not tell the better grid: RD +0.64 / -0.07 against +2.06 / +0.21 for content alone | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.0 |
| 156 | 2026-11-13 | r2a-tables | tables: reread=all;accept=strict |
accept=content and the cells must fit strictly better | [drop] | almost nothing accepted (12 of 277): RD +0.35 / 0.0 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 157 | 2026-11-13 | r2a-tables | tables: reread=all;accept=blind |
reference: any consistent re-read grid of comparable size replaces the page read | [drop] | swaps errors: RD dev +2.18 but held -0.74, printed totals dev -5, olmOCR table tests +9.5 / -1.7, multi_column held -1.3 | dev omnilay.iou50 +37.5; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.0 |
| 158 | 2026-11-13 | r2a-tables | regions: route=tables |
page read kept for text; every layout table region read from its crop (strict crop prompt) replaces the page-read blocks in it, no acceptance rule | [drop] | olmOCR table tests +12.7 / +5.0 (100 / 88.3) but printed totals -5 / -14, business grids -2.8 / -4.7, FUNSD word recall dev -0.7: crop reads of scanned tables change digits | dev bus.tables -38.0; dev omnilay.iou50 +37.5; held bus.tables -42.0; held omnilay.iou50 +23.0 |
| 159 | 2026-11-13 | r2b-forms | formread: form regions read from a crop as evidence only (types and label/value joins on the page read's blocks) |
regions +formread; 186 crop reads on 639 pages, 184 replayed from the round-1 cache | [keep (provisional, one read set)] | FUNSD links +4.7/+1.0 (noise 2.1/1.4), both keys precision +1.2/+1.6, first key precision +0.7/+2.3; lower inside noise: OmniDocBench layout precision -0.2/-0.4, held second key recall -0.8 | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 160 | 2026-11-13 | r2a-tables | regions: route=tables,route_guard |
the same; a crop must keep 90% of the page read's words | [drop] | olmOCR table tests +12.7 / +6.7 but printed totals still -5 / -14 and RD held -0.42: a word-level guard does not protect digits | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -42.0; held omnilay.iou50 +23.0 |
| 161 | 2026-11-13 | r2a-tables | regions: route=tables+forms |
table and form regions from crops, rest from the page read | [drop] | as route=tables, plus RD held -1.7 (2 tables lost), agreement with r-1 types -2.4 / -2.6, FUNSD word recall dev -0.9 | dev bus.tables -59.0; dev omnilay.iou50 +39.1; held bus.tables -53.0; held omnilay.iou50 +23.2 |
| 162 | 2026-11-13 | r2a-tables | regions: route=full,route_min=0.4 |
pages whose table and form regions cover 40% of the page (known before the read) are built from region reads only (assemble); other pages unchanged | [drop] | FUNSD links +5.2 / +3.7 and business key recall +16.6 / +3.5, but header/footer text -6.2 / -6.1, key precision held -15.8, printed totals -5 / -14, RD held -2.4, agreement with r-1 -4.1 / -6.6; 5.4 calls per page on those pages | dev bus.tables -74.0; dev omnilay.iou50 +39.4; held bus.tables -57.0; held omnilay.iou50 +23.4 |
| 163 | 2026-11-13 | r2a-tables | tables: reread=missed+broken+all;accept=content;missed_prompt=force;clean=1;formulas=matrix |
the kept switches together (replay) | [keep] | RD-TableBench 89.8 / 91.8 (+2.06 / +2.39; r-1 89.2 / 91.5), no_table 0 / 0, olmOCR table tests 93.7 / 90.0 (+6.3 / +6.7; r-1 87.3 / 88.3), formula edit dev 0.070 (r-1 0.073); no measure worse beyond noise; USD per page +0.0009 / +0.0008 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 164 | 2026-11-13 | r2b-forms | forms (round-1 switch without its table guard): the crop read REPLACES the region's blocks; comparison for formread |
base regions with forms instead of forms=notables | [drop] | FUNSD links +0.6/-0.3, same type as r-1 -1.3/-1.4 (noise 0.5/4.3), both keys precision -2.2/-2.2, header/footer text dev -1.3: evidence-only (formread) is better on every one of them | dev bus.tables -48.0; dev dln.type_recall +14.3; held bus.tables -43.0; held dln.type_recall +15.7 |
| 165 | 2026-11-13 | r2a-tables | engine-final without verify grid repair (reference) |
verify.checks = numbers=1;flags=1 (workstream 3's grid=norm+draft switched off) on the 26 pages where it acted | [baseline] | reference for the comparison below: business consistent grids 100.0 / 97.3 -> 89.8 / 88.4 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 166 | 2026-11-13 | r2a-tables | tables: reread=broken INSTEAD of verify grid=norm+draft |
broken tables re-read with the plain table-only prompt (no draft), accepted when consistent with the same numbers and 95% of the same words | [drop] | repairs 8 of 14 candidates: business grids 91.7 / 90.2 against 100.0 / 97.3 for workstream 3's draft repair (23 repaired): the draft prompt stays the default; reread=broken is kept only as a second step after it (RD dev +0.35) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 167 | 2026-11-13 | r2b-forms | kvsplit, final: only Key Value blocks inside a detected form region; the pieces stay Key Value |
regions +kvsplit | [option (provisional)] | dev: both keys recall +2.0, precision +2.9, first key recall +3.3 (noise 2.2); held: both keys precision +1.1, recall -0.1; lower inside noise: same type as r-1 held -0.6, DocLayNet type precision held -0.6, OmniDocBench layout precision -0.2/-0.2, first key precision dev -0.4 | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.9 |
| 168 | 2026-11-13 | r2b-forms | figures without content (a box-only Figure block), min area 0.15% of the page |
regions +figures | [superseded] | olmOCR and business unchanged now; DocLayNet type recall +1.5/+1.1, OmniDocBench layout recall +0.7/+0.9, IoU +0.014/+0.010; OmniDocBench layout precision -0.9/-1.4 (noise 5.0/0.5) | dev bus.tables -37.0; dev dln.type_recall +15.8; held bus.tables -38.0; held dln.type_recall +16.8 |
| 169 | 2026-11-13 | r2b-forms | figures, final: box-only Figure block, min area 0.5% of the page |
regions +figures | [option] | DocLayNet type recall +1.0/+0.8, lenient +1.2/+0.8 (noise 0.9/1.9), IoU +0.009/+0.008; OmniDocBench layout recall +0.9/+0.7, IoU +0.013/+0.008 (noise 0.003/0.010); OmniDocBench layout type precision -0.5/-1.3 (held beyond noise 0.5): ornaments that its labels count as part of a title or header. Same idea as workstream C's shape option layout_figures: keep one of the two | dev bus.tables -37.0; dev dln.type_recall +15.3; held bus.tables -38.0; held dln.type_recall +16.6 |
| 170 | 2026-11-13 | r2b-forms | kv=form+page with formread (first code) |
regions +kv=form+page,formread | [superseded] | FUNSD links +5.0/+2.0, both keys precision +11.2/+1.0; held both keys recall -2.1 (noise 4.6) | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.9 |
| 171 | 2026-11-13 | r2b-forms | hfsplit + kv=form+page + formread + boxfill (first code) |
regions +hfsplit,kv=form+page,formread,boxfill | [superseded] | see r2b_core3 | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 172 | 2026-11-13 | r2b-forms | all six switches (first code) |
regions +hfsplit,kvsplit,kv=form+page,formread,boxfill,figures | [superseded] | see r2b_all3 | dev bus.tables -37.0; dev dln.type_recall +16.2; held bus.tables -38.0; held dln.type_recall +17.1 |
| 173 | 2026-11-13 | r2b-forms | kv=form+page, final field test (a note that ends in a full stop and a URL are not fields) |
regions +kv=form+page | [keep (provisional, one read set)] | FUNSD links +1.6/+2.0 (noise 2.1/1.4), both keys precision +11.2/+0.4, same type as r-1 +0.9/+0.6, OmniDocBench layout held recall +2.3, precision +0.5, DocLayNet precision +0.2/+0.1; held key recall -1.6 (noise 4.6), second key recall held -2.0 (noise 3.5) | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.9 |
| 174 | 2026-11-13 | r2b-forms | hfsplit + kv=form+page + formread + boxfill (before the 4-line rule of formread) |
regions +hfsplit,kv=form+page,formread,boxfill | [superseded] | see r2b_core3 | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 175 | 2026-11-13 | r2b-forms | all six switches (before the 4-line rule of formread) |
regions +hfsplit,kvsplit,kv=form+page,formread,boxfill,figures | [superseded] | see r2b_all3 | dev bus.tables -37.0; dev dln.type_recall +15.7; held bus.tables -38.0; held dln.type_recall +16.8 |
| 176 | 2026-11-13 | r2b-forms | handwriting print guard: read.handwriting_illegible_sure_agreement=0.33 (min density 0.03) and handwriting_max_ocr_agreement=0.33 |
read options; base regions | [keep] | no FUNSD form goes to Sonnet (5 -> 0); FUNSD links -0.2/+2.4 (noise 2.1/1.4): held 83.4, no longer behind vanilla (82.8); manifest handwriting pages to Sonnet 15 of 18 -> 15 of 18; 2 other printed pages no longer routed (CMS-1500 sample, one RD-TableBench image); nothing else moves beyond 0.3; no new model call | dev bus.tables -36.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 177 | 2026-11-13 | r2c-product-gaps | layout_figures: Figure blocks from layout picture detections |
shape stage option layout_figures (off by default): every heron picture detection that no block accounts for becomes a Figure block; an unlocated described figure takes the nearest free picture box | [option] | on the 22 business pages that hold all 23 of r-1's Figure blocks: Figure blocks 8 -> 24, r-1's figures matched at IoU >= 0.5 4 -> 15 of 23, every other block unchanged on 22 of 22 pages, no model call; not scored on the devset and 61 business pages not replayed, so not proposed as a default yet | |
| 178 | 2026-11-13 | r2a-tables | engine-final replayed (no round-2 switch) [batch] |
profile engine-final, in-process replay from documents saved after the plain Ground step; OmniDocBench TEDS timeout raised | [baseline] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. the reference for every r2a line below; differs from cb_final on 6 pages because another workstream's change altered engine-final's default output between 11:45 and 12:40, and on one OmniDocBench table whose TEDS had timed out (0 -> 0.87) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 179 | 2026-11-13 | r2a-tables | tables: clean=1 [batch] |
cell hygiene, deterministic: picture/icon descriptions removed from table cells, ul/ol/li and p inside cells become plain bulleted lines | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. RD-TableBench +0.04 / +0.43 (noise 0.04 / 0.19); no other measure moves; no model call | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 180 | 2026-11-13 | r2a-tables | tables: merge=layout [batch] |
two consecutive table blocks joined when one layout table region holds both and the column counts match | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. the layout model draws one region around tables the keys keep apart: RD-TableBench dev -0.56, OmniDocBench held table edit +0.019 (noise 0.011) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -43.0; held omnilay.iou50 +23.1 |
| 181 | 2026-11-13 | r2a-tables | tables: merge=cont [batch] |
merge=layout plus: a table with the same columns directly below without a column header of its own is joined | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. joins the split RD table (held +0.82) but RD dev -0.56, OmniDocBench table TEDS structure dev -6.6 (noise 0.6), table edit +0.044 / +0.019 | dev bus.tables -40.0; dev omnilay.iou50 +37.4; held bus.tables -43.0; held omnilay.iou50 +23.1 |
| 182 | 2026-11-13 | r2a-tables | formulas=tag [batch] |
equation number at the end of a displayed formula becomes \tag{n} (deterministic) | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. no measure moves | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 183 | 2026-11-13 | r2a-tables | formulas=matrix [batch] |
a matrix the page read wrote as an HTML table inside a layout formula region is read again from the crop with a formula prompt; accepted when digits and letters are the same | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 1 region on 639 pages, 1 call: OmniDocBench dev formula edit 0.184 -> 0.070 (r-1 0.073; noise 0.106); held unchanged; nothing worse | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 184 | 2026-11-13 | r2a-tables | formulas=lines [batch] |
tall displayed formulas re-read from the crop to keep printed line breaks | [option] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 3 candidates, 2 accepted on a DocLayNet page, the OmniDocBench one rejected: no measured effect | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 185 | 2026-11-13 | r2a-tables | tables: reread=missed [batch] |
layout table / index region where the page read has running text: re-read with a prompt that may answer NOT_A_TABLE; accepted only as a real grid with the page read's words whose cells stand in columns | [option] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 8 candidates, 2 accepted, both right: RD held +1.76, no_table 1 -> 0; superseded by missed_prompt=force | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 186 | 2026-11-13 | r2a-tables | tables: reread=missed;missed_prompt=force [batch] |
the same with the plain table prompt: the acceptance rules alone decide | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 8 candidates, 3 accepted, all real tables: RD held +1.76 (no_table 1 -> 0), olmOCR table tests held +6.7 (83.3 -> 90.0; r-1 88.3); the 5 text regions are rejected; nothing worse beyond noise; 0.009 USD on 639 pages | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 187 | 2026-11-13 | r2a-tables | tables: reread=dropped [batch] |
layout shows a table where the page read has nothing and OCR sees words the read lacks: crop read added when OCR confirms its words | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 11 candidates on the newspaper pages, 0 accepted; nothing moves | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 188 | 2026-11-13 | r2a-tables | tables: reread=dropped;dropped_precision=0.4 [batch] |
the same with OCR confirmation lowered to 40% | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. no measure moves beyond noise | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.4 |
| 189 | 2026-11-13 | r2a-tables | tables: reread=broken [batch] |
tables still broken after workstream 3's repair: table-only re-read, accepted when consistent with the same numbers and 95% of the same words | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 2 of the 3 remaining candidates repaired: RD dev +0.35; nothing else moves | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 190 | 2026-11-13 | r2a-tables | engine-final without verify grid repair (reference) [batch] |
verify.checks = numbers=1;flags=1 on the 26 pages where grid=norm+draft acted | [baseline] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. business consistent grids 100.0 / 97.3 -> 89.8 / 88.4 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 191 | 2026-11-13 | r2a-tables | tables: reread=broken INSTEAD of verify grid=norm+draft [batch] |
broken tables re-read with the plain table-only prompt (no draft) | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. business grids 91.7 / 90.2 against 100.0 / 97.3 for workstream 3's draft repair: the draft repair stays the default | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 192 | 2026-11-13 | r2a-tables | tables: reread=all;accept=content;min_area=0.5 [batch] |
tables covering half the page or more re-read from the whole page image; accepted with a consistent grid, exactly the same numbers and 95% of the same words | [option] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. RD-TableBench +2.06 / +0.17; nothing worse; superseded by the same rule for every table | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 193 | 2026-11-13 | r2a-tables | tables: reread=all;accept=content [batch] |
every table with a matching layout region re-read with the table-only prompt; accepted with a consistent grid, exactly the same numbers and 95% of the same words | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 277 re-read, 105 accepted: RD-TableBench +2.06 / +0.21, olmOCR table tests dev +6.3 (87.3 -> 93.7), no measure worse beyond noise, printed totals and business grids unchanged; +0.0009 / +0.0007 USD per page | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 194 | 2026-11-13 | r2a-tables | tables: reread=all;accept=evidence [batch] |
accept=content plus: where numbers differ, a born-digital page's exact text layer decides | [option] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. 3 more business tables accepted; every measure equal to accept=content | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 195 | 2026-11-13 | r2a-tables | tables: reread=all;accept=fit [batch] |
accept=content and the located cells must fit columns and rows at least as well | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. RD +0.64 / -0.07 against +2.06 / +0.21 for content alone | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 196 | 2026-11-13 | r2a-tables | tables: reread=all;accept=strict [batch] |
accept=content and the cells must fit strictly better | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. RD +0.35 / 0.0: almost nothing accepted | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 197 | 2026-11-13 | r2a-tables | tables: reread=all;accept=blind [batch] |
reference: any consistent re-read grid of comparable size replaces the page read | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. swaps errors: RD +2.18 / -0.74, printed totals dev -5, olmOCR table tests +9.5 / -1.7, multi_column held -1.3, FUNSD word recall dev -0.4 | dev omnilay.iou50 +37.5; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 198 | 2026-11-13 | r2a-tables | regions: route=tables [batch] |
page read kept for text; every layout table region read from its crop replaces the page-read blocks in it, no rule | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. olmOCR table tests +12.7 / +5.0 but printed totals -5 / -14, business grids -2.8 / -4.7, FUNSD word recall dev -0.75, multi_column held -1.3 | dev bus.tables -38.0; dev omnilay.iou50 +37.5; held bus.tables -42.0; held omnilay.iou50 +23.0 |
| 199 | 2026-11-13 | r2a-tables | regions: route=tables,route_guard [batch] |
the same; a crop must keep 90% of the page read's words | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. olmOCR table tests +12.7 / +6.7 but printed totals -5 / -14, RD held -0.42, OmniDocBench held table edit +0.018 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -42.0; held omnilay.iou50 +23.0 |
| 200 | 2026-11-13 | r2a-tables | regions: route=tables+forms [batch] |
table and form regions from crops, rest from the page read | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. printed totals -5 / -14, RD held -1.7, key precision -1.9 / -14.9, agreement with r-1 -2.4 / -2.6, FUNSD word recall dev -0.9; business key recall dev +10.5 | dev bus.tables -59.0; dev omnilay.iou50 +39.1; held bus.tables -53.0; held omnilay.iou50 +23.2 |
| 201 | 2026-11-13 | r2a-tables | regions: route=full,route_min=0.4 [batch] |
pages whose table and form regions cover 40% of the page are built from region reads only; other pages unchanged | [drop] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. FUNSD links +5.2 / +3.7, business key recall +16.6 / +3.5, but header/footer text -4.8 / -6.1, key precision held -15.8, printed totals -5 / -14, RD held -2.4, agreement with r-1 -3.7 / -6.6 | dev bus.tables -73.0; dev omnilay.iou50 +39.4; held bus.tables -57.0; held omnilay.iou50 +23.4 |
| 202 | 2026-11-13 | r2a-tables | read: zoom_regions=gate [batch] |
whole-page zoom strips as before, but none on pages where the layout shows neither a table nor a form region (57 pages re-prepared with the switch, the rest unchanged by construction) | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. no measure moves; all 9 verified digit corrections on business scans kept; one word correction on an olmOCR page lost (Panks -> Banks); USD per page -0.0004 / -0.0005 (olmOCR 0.0058 -> 0.0051, OmniDocBench 0.0061 -> 0.0052) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 203 | 2026-11-13 | r2a-tables | read: zoom_regions=tables_only (HTTP, port 8851) |
zoom strips cut to the layout table regions (k and k+1 strips per table), none on pages without a table region | [drop] | real HTTP run started 12:30 (not part of the batch). no devset measure moves and cost falls (business 0.0100 -> 0.0087, scanned with tables 0.0249 -> 0.0211) but only 7 of the 9 verified digit corrections are made (16,683 and 62588 lost) and 3 new unverified changes appear, one wrong (2,302 -> 2.302): other strips, other votes | dev cost.omni.s_page +52.3; dev omnilay.iou50 +37.6; held cost.omni.s_page +51.4; held bus.tables -38.0 |
| 204 | 2026-11-13 | r2a-tables | tables: reread=missed+broken+all;accept=content;missed_prompt=force;clean=1;formulas=matrix [batch] |
the kept table and formula switches together (replay) | [keep] | re-measured in one batch on one state of the working tree (baseline replayed first and last, identical); supersedes the earlier r2a-tables line of the same target. RD-TableBench 89.8 / 91.8 (+2.06 / +2.39; r-1 89.2 / 91.5), no_table 0 / 0, olmOCR table tests 93.7 / 90.0 (+6.3 / +6.7; r-1 87.3 / 88.3), olmOCR mean +1.3 / +1.3, formula edit dev 0.070 (r-1 0.073); no measure worse; USD per page +0.0009 / +0.0007 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 205 | 2026-11-13 | r2a-tables | tables: reread=missed;missed_prompt=force with layout thresholds lowered to 0.2 (stress test) |
the missed-table rules with unsure layout detections admitted as candidates (table and index score 0.2 instead of 0.5 / 0.6) | [drop] | 13 candidates instead of 8, 5 accepted instead of 3: the two extra ones (layout score 0.45 and 0.49) are two-column list-like text on a DocLayNet laws page that its labels do not call a table (held type recall -2.2, noise 2.2). The acceptance rules alone do not keep list-like text out; the layout score threshold is part of the rule and stays at 0.5 / 0.6 | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 206 | 2026-11-13 | r2b-forms | FINAL hfsplit: one-line running heads and feet cut into their word groups |
regions +hfsplit | [keep (recommended)] | both keys recall +3.6/+1.9 on the round-1 reads, +3.4/+2.4 on the live-run reads, +3.0/+2.1 on the second vanilla reads (noise of one pair 3.8/4.6; same sign six times); first key recall +3.9/+1.4; DocLayNet IoU +0.009/+0.012 and IoU>=0.5 +1.0/+1.7; OmniDocBench layout IoU +0.008/+0.008; held OmniDocBench text edit -0.007 (better). Lower inside noise, same sign on every set: first key precision held -0.3 to -0.6, DocLayNet type precision -0.2/-0.6 (a wrong Header block cut in two counts twice). No model call | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.7 |
| 207 | 2026-11-13 | r2b-forms | FINAL formread: form regions read from a crop as evidence only (4+ label lines; types, label/value joins, plain-line marks) |
regions +formread; one Haiku crop read per form region, 186 on 639 pages | [keep (recommended)] | FUNSD links +4.7/+1.0 on the round-1 reads and +3.3/+0.7 on the second reads (noise 2.1/1.4); both keys precision +6.4/+2.1, +0.6/+0.6, +1.4/+0.7 on the three read sets; recall within 0.4 except +2.5 held on the third set; agreement with r-1 has no stable sign (+0.5/+0.6, +0.2/-0.5, -0.6/-1.9); nothing moves on tables, olmOCR, OmniDocBench text. Cost +0.0003 USD/page over the devset (+0.001 on business pages) | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 208 | 2026-11-13 | r2b-forms | FINAL kv=page: on a page without any form region or checkbox the reader's Key Value lines are Text |
regions +kv=page | [keep (recommended, small)] | DocLayNet type precision +0.2/+0.1 and +0.2/+0.3, OmniDocBench layout precision 0/+0.5 and +0.2/+0.4, held recall +2.3 and +0.7 on the two read sets; business, FUNSD and everything else identical | dev bus.tables -37.0; dev dln.type_recall +14.5; held bus.tables -38.0; held dln.type_recall +15.9 |
| 209 | 2026-11-13 | r2b-forms | FINAL boxfill: blocks without a located box take the free layout region with their words |
regions +boxfill | [keep (recommended, small)] | both keys recall +0.4/+0.1, 0/+0.1, 0/+0.1 on the three read sets; DocLayNet held IoU +0.001; held DocLayNet type precision -0.1 (1 block); nothing else moves. Few blocks qualify: most blocks without a box are short numbers that occur twice on the page | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 210 | 2026-11-13 | r2b-forms | FINAL kvsplit: a Key Value block inside a form region is cut between layout regions where the next line starts a field |
regions +kvsplit | [option (lead's call)] | both keys precision +2.9/+1.1, +1.5/+0.5, +0.5/+0.8 and recall +2.0/-0.1, +1.8/+0.3, +1.4/+1.0 on the three read sets; costs with the same sign on every set, each inside noise: DocLayNet type precision held -0.6/-0.6, OmniDocBench layout precision -0.2, first key precision dev -0.4/-0.5/-1.0, agreement with r-1 held -0.6/-0.1/-0.8 (more Key Value blocks, which the strict layout maps always count as wrong) | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.9 |
| 211 | 2026-11-13 | r2b-forms | FINAL kv=form: field-like Text inside a form region that the page read shows fields in becomes Key Value |
regions +kv=form | [option (not recommended)] | FUNSD links +1.6/+2.0 and 0/+1.4 on two read sets and both keys precision +11.2/+0.4 (one blank T4 slip), but held key recall -1.6/-1.3/-0.6 and held first key precision -0.7/-0.7/-0.8 on all three read sets: short captions and notes inside form regions that the key calls Text. With formread on it adds only FUNSD held +1.0 | dev bus.tables -37.0; dev dln.type_recall +14.3; held bus.tables -38.0; held dln.type_recall +15.7 |
| 212 | 2026-11-13 | r2b-forms | FINAL figures: box-only Figure block for a picture detection no block covers (min 0.5% of the page) |
regions +figures | [option (not recommended as default)] | DocLayNet type recall +1.0/+0.8, IoU +0.009/+0.008; OmniDocBench layout recall +0.9/+0.7, IoU +0.013/+0.008; OmniDocBench layout type precision held -1.3 and -1.1 on two read sets (noise 0.5): ornaments its labels count as part of a title. Duplicates workstream C's shape option layout_figures: keep one | dev bus.tables -37.0; dev dln.type_recall +15.3; held bus.tables -38.0; held dln.type_recall +16.6 |
| 213 | 2026-11-13 | r2b-forms | FINAL recommended region set: hfsplit + kv=page + formread + boxfill |
regions +hfsplit,kv=page,formread,boxfill | [keep (recommended)] | three read sets: both keys recall +4.3/+1.8, +3.6/+2.1, +2.8/+4.7; precision +7.0/+1.9, +0.5/+0.4, +1.2/+0.8; FUNSD links +4.7/+1.0 and +3.3/+0.7; DocLayNet IoU +0.009/+0.013 and +0.014/+0.016; OmniDocBench layout precision +0.3/+0.6 and +0.5/+0.4. No measure lower beyond noise on the round-1 reads. No stable sign: agreement with r-1 (+1.1/+0.9, +0.3/-0.1, -1.1/-1.5) and first key precision held (+1.6, -0.1, -1.9) | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 214 | 2026-11-13 | r2b-forms | recommended set + kvsplit |
regions +hfsplit,kv=page,formread,boxfill,kvsplit | [option (lead's call)] | against the recommended set: both keys recall dev +2.0/+1.9/+1.5, held 0/+0.3/0; precision +1.9/+1.0, +1.3/+0.5, +0.7/+0.8; DocLayNet type precision held -0.5/-0.6; agreement with r-1 -0.1/-0.6, +0.1/-0.1, -0.2/-0.3 | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +16.0 |
| 215 | 2026-11-13 | r2b-forms | recommended set + kv=form |
regions +hfsplit,kv=form+page,formread,boxfill | [option (not recommended)] | against the recommended set: FUNSD held +1.0/+0.7, dev both keys precision +6.3/+0.6/+0.5; held both keys recall -0.9/-0.9/-1.0 and held precision -0.2/-0.9/-1.2 on the three read sets | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 216 | 2026-11-13 | r2b-forms | all six region switches |
regions +hfsplit,kvsplit,kv=form+page,formread,boxfill,figures | [option] | sum of the parts: best dev numbers (both keys recall +6.3, precision +14.7) but OmniDocBench layout precision held -0.8 (figures), held key recall +0.9 instead of +1.8 (kv=form) | dev bus.tables -37.0; dev dln.type_recall +15.7; held bus.tables -38.0; held dln.type_recall +16.8 |
| 217 | 2026-11-13 | r2b-forms | recommended set without round 1's replacing form crops (forms=notables off) |
regions types=guarded,boxes,split,hfsplit,kv=page,formread,boxfill | [drop] | against the recommended set: held first key recall -7.1, both keys recall -3.4, FUNSD -0.2/-0.3; forms=notables stays | dev bus.tables -37.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 218 | 2026-11-13 | r2b-forms | FINAL: recommended region set + handwriting print guard (= profile engine-final-r2b), by replay |
regions +hfsplit,kv=page,formread,boxfill; read.handwriting_illegible_sure_agreement=0.33, handwriting_max_ocr_agreement=0.33 | [keep (recommended)] | against engine-final: FUNSD links 86.0/84.1 (+4.5/+3.0; r-1 87.6/86.8; vanilla 80.6/82.8), both keys recall 73.3/75.0 (+3.8/+1.8), precision 79.9/83.9 (+6.7/+1.9), first key recall +4.4/+1.4, DocLayNet IoU +0.009/+0.013, OmniDocBench layout precision +0.3/+0.6; lower inside noise: DocLayNet type precision -0.1/-0.6, FUNSD word recall -0.1/-0.1 | dev bus.tables -36.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_recall +15.9 |
| 219 | 2026-11-13 | r2a-tables | tables: reread=missed+broken;missed_prompt=force;clean=1;formulas=matrix (the kept set without reread=all) |
the kept switches without the every-table re-read: for deployments where cost matters more than table structure | [option] | RD-TableBench 88.1 / 91.6 (+0.39 / +2.19; r-1 89.2 / 91.5), olmOCR table tests 87.3 / 90.0, formula edit dev 0.070; nothing worse; USD per page +0.00007 / +0.00002 (reread=all adds +1.7 on RD dev, +6.3 on olmOCR table tests dev, +0.2 on RD held and 0.0008 USD per page) | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 220 | 2026-11-13 | r2b-forms | FINAL through the real HTTP server: profile engine-final-r2b (recommended region set + handwriting print guard), whole devset, port 8852 |
profile engine-final-r2b = engine-final + regions hfsplit,kv=page,formread,boxfill + read.handwriting_illegible_sure_agreement=0.33, handwriting_max_ocr_agreement=0.33 | [keep (recommended)] | 636 of 639 responses identical to the replay r2b_finalF (the 3 others are the turned pages; DocLayNet held differs by 0.1). Against engine-final on the same reads: FUNSD links 86.0/84.1 (+4.5/+3.0; noise 2.1/1.4; r-1 87.6/86.8), both keys recall 73.3/75.0 (+3.8/+1.8), precision 79.9/83.9 (+6.7/+1.9), first key recall 71.8/75.9 (+4.4/+1.4), DocLayNet IoU +0.009/+0.013, OmniDocBench layout precision +0.3/+0.6; nothing lower beyond noise against engine-final or vanilla; +0.00013 USD/page | dev bus.tables -36.0; dev cost.funsd.s_page +28.6; held bus.tables -38.0; held cost.funsd.s_page +31.3 |
| 221 | 2026-11-13 | r2a-tables | profile engine-final-r2a through the real HTTP server (port 8851), 18:00 |
engine-final + ground.tables reread=missed+broken+all;accept=content;missed_prompt=force;clean=1;formulas=matrix;prefetch=1 + read.zoom_regions=gate; real requests, model reads replayed from the call cache | [keep] | RD-TableBench 89.8 / 91.8 (+2.06 / +2.39; r-1 89.2 / 91.5), no_table 0 / 0, olmOCR table tests 93.7 / 90.0 (+6.3 / +6.7; r-1 87.3 / 88.3), formula edit dev 0.070 (r-1 0.073); no measure worse beyond noise on dev or held; all 9 verified zoom digit corrections kept; USD per page 0.0058 / 0.0061 (engine-final 0.0052 / 0.0057). A first HTTP run at 13:25 missed the olmOCR table and one formula region because Ground had typed a block inside the region as Footer / List Item; the candidate rule now ignores furniture types there (fixed, re-run). All replay variants were re-run on documents prepared again on the final tree: same numbers as the [batch] lines | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.1 |
| 222 | 2026-11-13 | r2a-tables | the same set replayed in-process (documents prepared again on the final tree) |
ground.tables as above on the documents prepared with zoom_regions=gate for the 57 pages it touches | [keep] | every measure equal to the HTTP run; 613 of 639 responses identical to it, 17 differ in a box by under 0.01, 9 in how a text block is cut or typed (5 of them the pages on which replay and server have differed since another workstream's Shape commits); no table differs | dev omnilay.iou50 +37.6; dev bus.tables -37.0; held bus.tables -38.0; held omnilay.iou50 +23.3 |
| 223 | 2026-11-13 | combine-r2 | engine-final-2 = engine-final + A's table/formula/zoom set + B's form/structure set, first run (real HTTP server, port 8861, reads dev1) |
profile engine-final-2: ground.tables reread=missed+broken+all;accept=content;missed_prompt=force;clean=1;formulas=matrix;prefetch=1, read.zoom_regions=gate, regions +hfsplit,kv=page,formread,boxfill, handwriting print guard 0.33 | [check] | Additive: every measure equals the better of the two workstreams alone or is inside noise of it; 0 new model calls. Found: a form region is read twice with the same prompt (round-1 form crop and round-2 form evidence), 85 regions on 639 pages. | dev omnilay.iou50 +38.6; dev bus.tables -36.0; held bus.tables -38.0; held cost.omni.s_page +29.4 |
| 224 | 2026-11-13 | combine-r2 | engine-final-2 with one shared form crop read per region (regions/crops.py shared_reads) |
the round-1 guarded form crop and the round-2 form evidence read use ONE read of a form region (was two identical calls; in production two independent draws that could disagree) | [keep] | 639 of 639 responses identical to the first run (types, text, boxes); 85 fewer calls (1700 -> 1615), 0.0061 -> 0.0060 USD per page. This is the candidate final profile. | dev omnilay.iou50 +38.6; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +24.5 |
| 225 | 2026-11-13 | combine-r2 | engine-final-2 + kvsplit (reads dev1, whole devset) |
regions +kvsplit: a Key Value block inside a form region is cut between layout regions where the next line starts a field | [evidence] | On top of the combined profile, same reads: both keys (100 pages) precision +2.0 / +1.0, recall +2.0 / 0.0, first key recall +3.3 / 0.0 (noise 2.2 / 6.0); lower inside noise: first key precision dev -0.5, DocLayNet type precision held -0.6, OmniDocBench layout type precision -0.2 / -0.2, agreement with r-1 held -0.6. 0 model calls. | dev omnilay.iou50 +38.5; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +24.5 |
| 226 | 2026-11-13 | combine-r2 | engine-final-2 + C's shape option layout_figures as committed (reads dev1, whole devset) |
shape.layout_figures: a box-only Figure block for every layout picture detection that no block accounts for | [superseded] | OmniDocBench layout type precision -1.3 / -1.4 (noise 5.0 / 0.5); gains: DocLayNet type recall +1.7 / +1.0, IoU +0.015 / +0.007, OmniDocBench layout IoU +0.009 / +0.015, figure label recall 71 -> 83 / 63 -> 97. 13 of the 16 wrong figures on OmniDocBench dev are icons under 1% of the page on the line of a running head. | dev omnilay.iou50 +39.6; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +26.2 |
| 227 | 2026-11-13 | combine-r2 | engine-final-2 + B's regions switch figures (reads dev1, whole devset) |
regions +figures: box-only Figure block for a picture detection (score 0.6+, 0.5% of the page) that no block covers | [drop] | Same held loss as C's switch (OmniDocBench layout type precision -0.5 / -1.2) with smaller gains (DocLayNet type recall +1.0 / +0.8, OmniDocBench IoU +0.013 / +0.009) and no part in the product (no crops, no description, no box for a described figure). At most one figure switch is kept: C's. | dev omnilay.iou50 +40.3; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +25.7 |
| 228 | 2026-11-13 | combine-r2 | engine-final-2 + layout_figures with the running-head rule (reads dev1, whole devset) |
shape.layout_figures + layout_figures_running=0.5: a picture under 1% of the page with half its height on the line of a detected running head or foot is not made a Figure | [option] | Rule chosen on dev: OmniDocBench layout type precision dev -1.3 -> -0.5, no gain lost. Held still -1.3 (noise 0.5): the rule removes 2 of the 21 held figures the labels do not call a figure. Not a default; the one figure switch that is kept as an option. | dev cost.omni.s_page +51.4; dev omnilay.iou50 +39.5; held cost.omni.s_page +57.3; held bus.tables -38.0 |
| 229 | 2026-11-13 | combine-r2 | engine-final-2 on the round-1 live reads (sample live1), business pages |
profile engine-final-2, reads of the round-1 live run; base = cb_live (engine-final on the same reads) | [check] | Against engine-final on the same reads: both keys recall +3.6 / +2.1, first key recall +5.0 / +1.4, precision +0.5 / +0.4; grids +0.7 / 0; totals unchanged. 101 new table re-reads (0.31 USD). | dev bus.totals_found -26.0; dev bus.key70_precision +20.2; held bus.tables -28.0; held bus.grid_ok +12.9 |
| 230 | 2026-11-13 | combine-r2 | engine-final-2 + kvsplit on the live1 reads, business pages |
regions +kvsplit | [evidence] | Against engine-final-2 on the same reads: both keys precision +1.3 / +0.5, recall +1.8 / +0.3; first key precision dev -0.6 (noise 1.2). | dev bus.totals_found -26.0; dev bus.key70_precision +22.7; held bus.tables -28.0; held bus.grid_ok +12.9 |
| 231 | 2026-11-13 | combine-r2 | engine-final-2 + layout_figures (running-head rule) on the live1 reads, business pages |
shape.layout_figures | [option] | No business measure moves on this read set (the +0.6 first-key gain of the dev1 reads is not repeated). | dev bus.totals_found -26.0; dev bus.key70_precision +20.2; held bus.tables -28.0; held bus.grid_ok +12.9 |
| 232 | 2026-11-13 | combine-r2 | engine-final (round 1) on the second independent reads (sample dev2), whole devset |
profile engine-final, page reads / strips / verify / form crops of sample dev2; handwriting pages reuse the Sonnet read of sample dev1 | [check] | Base for the round-2 change on a second draw. olmOCR old_scans held 37.5 against vanilla's 40.3 on these reads (2 tests; noise 1 test): the handwriting route of round 1, not a round-2 piece. | dev omnilay.iou50 +39.5; dev bus.tables -39.0; held bus.tables -30.0; held omnilay.iou50 +25.2 |
| 233 | 2026-11-13 | combine-r2 | engine-final-2 without kvsplit on the dev2 reads, whole devset |
combined profile before the kvsplit decision and before the Key Value / missed-table fix | [check] | Against engine-final on the same reads: RD-TableBench +1.6 / +0.3 (the held menu table is NOT recovered: this read typed it Key Value), olmOCR table tests +6.3 / +6.7, FUNSD links +2.7 / +1.7, both keys recall +2.2 / +1.6. | dev omnilay.iou50 +39.9; dev bus.tables -39.0; held bus.tables -30.0; held omnilay.iou50 +26.6 |
| 234 | 2026-11-13 | combine-r2 | engine-final-2 + kvsplit on the dev2 reads, whole devset |
regions +kvsplit | [evidence] | Against the combined profile on the same reads: both keys precision +0.7 / +0.8, recall +1.4 / 0.0; DocLayNet type precision -0.7 / -0.6, first key precision dev -1.0 (inside noise). | dev omnilay.iou50 +39.7; dev bus.tables -39.0; held bus.tables -30.0; held omnilay.iou50 +26.6 |
| 235 | 2026-11-13 | combine-r2 | FINAL: profile engine-final-2 by name (A + B + kvsplit, shared form read, Key Value / missed-table fix), whole devset + extract, reads dev1 |
DOCFORGE_PROFILE=engine-final-2, nothing else set | [keep] | 639 of 639 responses identical to the env-switched kvsplit run. Against round 1 ahead beyond noise on both halves: RD-TableBench 89.8 / 91.8, olmOCR table tests 93.7 / 90.0, olmOCR mean, FUNSD links 86.6 / 84.1; nothing behind round 1 or vanilla beyond noise on both halves. Extract 100 / 100. | dev omnilay.iou50 +38.5; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +24.5 |
| 236 | 2026-11-13 | combine-r2 | FINAL profile on the dev2 reads, whole devset |
engine-final-2 with the Key Value / missed-table fix; handwriting pages reuse the Sonnet read of sample dev1 | [keep] | Against engine-final on the same reads: RD-TableBench +1.6 / +2.1 (89.9 / 91.2; the fix recovers the held menu table, +1.8), olmOCR table tests +6.3 / +6.7, FUNSD links +2.9 / +1.7, both keys precision +2.7 / +2.8 and recall +3.6 / +1.6; nothing lower beyond noise. | dev omnilay.iou50 +39.7; dev bus.tables -39.0; held bus.tables -30.0; held omnilay.iou50 +26.6 |
| 237 | 2026-11-13 | combine-r2 | FINAL profile on the live1 reads, business pages |
engine-final-2 (final code) | [keep] | Against engine-final on the same reads (cb_live): both keys recall +5.5 / +2.4, precision +1.8 / +0.9, first key recall +7.2 / +1.8. Identical scores to the env-switched kvsplit run. | dev bus.totals_found -26.0; dev bus.key70_precision +22.7; held bus.tables -28.0; held bus.grid_ok +12.9 |
| 238 | 2026-11-13 | combine-r2 | FINAL profile LIVE on fresh reads (sample live2), business pages, 4 requests at a time |
engine-final-2 through the HTTP server, nothing replayed | [check] | 147 of 147 responses, none empty, no crash. 0.0119 USD per page, wall 39.7 s mean / 29.3 s median (round-1 live run: 0.0093, 28.7 / 20.0). | dev bus.tables -30.0; dev bus.key70_precision +19.3; held bus.tables -31.0; held bus.matched -15.1 |
| 239 | 2026-11-13 | combine-r2 | vanilla-api on the fresh live reads (sample live2), business pages |
profile vanilla-api replaying the page reads of the live run | [check] | Vanilla on the same reads as the live run of the final profile. | dev bus.key70_precision +7.4; dev bus.key100_precision +6.4; held bus.key70_precision +9.7; held bus.tables +8.0 |
| 240 | 2026-11-13 | combine-r2 | engine-final (round 1) on the fresh live reads (sample live2), business pages |
profile engine-final replaying the reads of the live run | [check] | Against it the final profile on the same reads: both keys precision +10.4 / +1.9, recall +3.9 / +1.3, first key recall +6.0 / +1.4, grids +0.9 / 0; header/footer text, totals and everything else equal. | dev bus.tables -30.0; dev bus.grid_ok +9.8; held bus.tables -31.0; held bus.key70_precision +12.9 |
| 241 | 2026-11-13 | combine-r2 | server default: settings.model r-1 -> engine-final-2 when DOCFORGE_PROFILE is not set (tried in memory) |
MODEL_TO_PROFILE r-1 = engine-final-2 | [drop] | 5 of 120 existing API tests fail (they parse through the default profile cached-haiku without a key); the default is left; select the final engine with DOCFORGE_PROFILE=engine-final-2. Product tag final-round-2. | dev omnilay.iou50 +38.5; dev bus.tables -36.0; held bus.tables -38.0; held omnilay.iou50 +24.5 |
| 242 | 2026-11-13 | r3a-structure | hfdet alone |
regions switch hfdet: a block whose words lie in two or more page-header/page-footer layout detections standing side by side (gap >= 0.05 page widths) is cut into one block per detection; tokens grounding left out are looked up in every word source | [keep] | business both-keys recall +2.0 dev / +1.0 held, nothing lower beyond noise on business, OmniDocBench and DocLayNet layout, olmOCR unchanged; 0 tables changed; no model call | |
| 243 | 2026-11-13 | r3a-structure | pnsplit alone |
regions switch pnsplit: a page number at the start or end of a running head/foot in the top 12% / bottom 14% becomes its own block when the page words show it clear of the rest (gap 0.03 with the word page, 0.06 bare number on a one-line block of 2+ words), on another printed line, or in its own furniture detection | [keep] | business both-keys recall +1.4 dev / +1.3 held, OmniDocBench layout recall +0.4 / +0.2, nothing lower beyond noise; 0 tables changed; no model call | |
| 244 | 2026-11-13 | r3a-structure | hftable alone |
regions switch hftable: a table of at most 4 short cells in the top/bottom band whose located cells all lie in page-header/page-footer detections, with no table detection, becomes one block per cell | [option] | fires on 1 page of 639 (2 tables, dev): business both-keys recall +0.6 dev, 0 held; 0 tables changed on RD-TableBench, olmOCR, OmniDocBench, DocLayNet, FUNSD. Right on the page it fires on, too rare to confirm on held | |
| 245 | 2026-11-13 | r3a-structure | prose alone (one-column table is prose) |
regions switch prose: a one-column table (every row one cell) without checkbox or blank, 40+ words mostly in lines of 8+ words with a sentence, where the layout model sees no table, becomes one block per printed line; types from the markup and the layout | [keep] | fires on 2 business pages (redline dev, civil cover sheet held): first-key recall +9.9 dev, both-keys recall +3.6 dev, held 0; 0 tables changed on RD-TableBench, olmOCR, OmniDocBench, DocLayNet, FUNSD (those sets have no one-column table); r-1 writes no one-column table at all | |
| 246 | 2026-11-13 | r3a-structure | hfdet+pnsplit+hftable+prose+lab=paren together (business only) |
the five switches together | [keep] | business both-keys recall +6.7 dev / +1.9 held, precision +1.3 / +0.1, first key recall +13.3 / +1.1, same type as r-1 +2.1 / +0.5; dev missed key blocks 122 -> 89 of 494 | |
| 247 | 2026-11-13 | r3a-structure | lab=cap alone |
a bold-line Section Header matched to a caption detection becomes Text | [drop] | 8 blocks on 5 pages: DocLayNet type recall +0.5 dev (noise 4.4), OmniDocBench precision 0 / +0.2 (noise 0.5), agreement with r-1 -0.2 / -0.3: nothing clears the noise floor; the OmniDocBench captions typed Section Header are section_header for the layout model too | |
| 248 | 2026-11-13 | r3a-structure | lab=list alone |
a one-line List Item matched to a section-header detection becomes Section Header | [drop] | 6 blocks: every measure that moves goes down (DocLayNet type recall -0.2 dev, OmniDocBench -0.1 held): sub-figure captions (a) (b) are section_header for the layout model | |
| 249 | 2026-11-13 | r3a-structure | lab=paren alone |
a Header made from Text by the position rule alone whose whole text is bracketed or parenthesised (a unit line over a table) is Text again | [keep] | 8 blocks on 7 pages: agreement with r-1 +0.5 dev / +0.1 held, first key precision and recall +0.6 dev, OmniDocBench +0.1 dev; nothing lower anywhere; all inside the noise floor of two reads, kept as part of the furniture set because it only undoes a position rule | |
| 250 | 2026-11-14 | r3a-structure | lab=long alone |
a Section Header that is a paragraph (40+ words with a sentence) becomes Text | [drop] | fires on 1 block of 349 pages and that one lowers DocLayNet held by 0.1 | |
| 251 | 2026-11-14 | r3a-structure | lab=fig alone |
3+ short Text blocks inside one confident picture detection (labels read out of a diagram) are typed Figure | [option] | fires on 2 dev pages only (24 blocks): OmniDocBench type precision +0.9 dev, DocLayNet +2.4 dev, both inside dev noise (5.0 / 2.5); 0 held pages, so no confirmation; off | |
| 252 | 2026-11-14 | r3a-structure | recommended set (engine-final-3: hfdet,pnsplit,hftable,prose,lab=paren), whole devset, dev1 reads |
the five switches on top of engine-final-2, 639 pages | [keep] | nothing lower on any measure (official OmniDocBench text/table/order/formula measures, RD-TableBench, olmOCR, FUNSD unchanged; scores/r3a_final.json against scores/r3a_base.json); business both-keys recall +6.7 dev / +1.9 held, first key recall +13.3 / +1.1, same type as r-1 +2.1 / +0.5, OmniDocBench layout IoU +0.005 / +0.002; 0 tables changed outside business; 0 model calls | |
| 253 | 2026-11-14 | r3b-cost | tables all_if=header2 (table re-read only for tables whose page-read grid shows the sign) |
composed from r2a_combo (every table) and r2a_combo_noall (none) on tables, olm, business | [drop] | no gain over no re-read | |
| 254 | 2026-11-14 | r3b-cost | tables all_if=empty (table re-read only for tables whose page-read grid shows the sign) |
composed from r2a_combo (every table) and r2a_combo_noall (none) on tables, olm, business | [drop] | loses the olmOCR table-test gain and RD held -0.1 | |
| 255 | 2026-11-14 | r3b-cost | tables all_if=spans (table re-read only for tables whose page-read grid shows the sign) |
composed from r2a_combo (every table) and r2a_combo_noall (none) on tables, olm, business | [option] | half the re-reads; RD dev -1.3 against every table (noise 0.0), held level, olmOCR table tests kept; triggered pages wait about 10 s more (no prefetch) | |
| 256 | 2026-11-14 | r3b-cost | tables all_if=spans+header2 (table re-read only for tables whose page-read grid shows the sign) |
composed from r2a_combo (every table) and r2a_combo_noall (none) on tables, olm, business | [option] | same result as spans | |
| 257 | 2026-11-14 | r3b-cost | tables all_if=spans+header2+empty (table re-read only for tables whose page-read grid shows the sign) |
composed from r2a_combo (every table) and r2a_combo_noall (none) on tables, olm, business | [option] | 71% of the re-reads; RD dev -0.5 against every table, held level; triggered pages wait about 10 s more | |
| 258 | 2026-11-14 | r3b-cost | no zoom vote (read.zoom_vote=false) |
96 pages with strip calls requested | [option] | every devset measure of business and olmOCR equal to engine-final-2 (totals 235 / 241, grids 100.0 / 97.3, olmOCR table tests 93.7 / 90.0, old_scans 50.0 / 43.1); but 8 of the 9 hand-verified digit corrections are gone; saves 0.0075 USD per scanned-table page | |
| 259 | 2026-11-14 | r3b-cost | zoom strips at low effort (read.zoom_effort=low) |
30 business and olm pages with strips; 134 new strip reads | [drop] | 8 of 9 verified corrections (18 -> 16 lost) and one number changed against r-1 (37129 -> 37128); every devset measure of business and olmOCR equal to engine-final-2 (totals 235 / 241, grids 100.0 / 97.3, olmOCR table tests 93.7 / 90.0, old_scans 50.0 / 43.1) | |
| 260 | 2026-11-14 | r3b-cost | zoom strips at medium effort (read.zoom_effort=medium) |
30 business and olm pages with strips; 138 new strip reads | [drop] | same loss and same wrong change as low effort; every devset measure of business and olmOCR equal to engine-final-2 (totals 235 / 241, grids 100.0 / 97.3, olmOCR table tests 93.7 / 90.0, old_scans 50.0 / 43.1) | |
| 261 | 2026-11-14 | r3b-cost | second zoomed pass only over disputed strips (read.zoom_second=disputed) |
30 business and olm pages with strips; no new read | [keep] | 287 of 287 business and olmOCR responses identical to engine-final-2; 9 of 9 corrections; 19 strip calls fewer on the 33 scanned-table pages | |
| 262 | 2026-11-14 | r3b-cost | second zoomed pass only for digit disputes and only over those strips (read.zoom_second=digits+disputed) |
30 business and olm pages with strips; no new read | [option] | 9 of 9 corrections; 1 response differs (the wrong word change Preliminary -> Prelimary is no longer made); not chosen because word corrections on other components would also stop | |
| 263 | 2026-11-14 | r3b-cost | verify grid repair tries the prefetched table read first (verify.checks grid=norm+pre+draft) |
29 pages with verify calls | [option] | the table-only read passes the repair rule for 3 of 26 broken tables only; 2 responses differ with the same scores (RD-TableBench 89.8 / 91.8, grids 100.0 / 97.3, totals 235 / 241); 3 calls saved on 639 pages | |
| 264 | 2026-11-14 | r3b-cost | form region reads start with the page read (read.form_prefetch) |
40 form pages (24 business, 16 FUNSD), DOCFORGE_NO_LIVE=1 | [keep] | 40 of 40 responses identical; model seconds a page waits: business form pages median 38.9 -> 26.7 s, FUNSD 15.8 -> 8.9 s; 7 of the started reads were not used, hence form_prefetch_min_area=0.05 in the final profile | |
| 265 | 2026-11-14 | r3b-cost | per-page spending cap (read.page_cap_usd / settings.page_cap_usd) |
7 dense pages through the per-request settings field | [option] | cap 0.010: all 7 pages at or under 0.0096 USD (0.0064 to 0.0717 without); cap 0.005: 3 pages over only by their page read (recorded); skipped steps listed in usage.model_usage.spend_cap | |
| 266 | 2026-11-14 | r3b-cost | handwritten regions only to Sonnet |
analysis of the 45 routed pages, no model call (r3b/hw_regions.py) | [drop] | the layout model finds no region at all on 10 of the 15 routed business pages (handwritten ledgers) and 24 of 45 routed pages are 70% or more handwriting by region area: nothing to cut out | |
| 267 | 2026-11-14 | r3b-cost | Sonnet at lower effort on handwriting pages |
[not done] | stopped by the owner priorities before it ran; the devset has no handwriting truth beyond olmOCR old_scans (1 to 2 tests) | ||
| 268 | 2026-11-14 | r3a-structure | recommended set (engine-final-3: hfdet,pnsplit,hftable,prose,lab=paren), whole devset, dev2 reads |
the five switches on top of engine-final-2, 639 pages, second independent read set | [keep] | confirms dev1: business both-keys recall +6.3 dev / +1.3 held, first key recall +13.3 / +1.1, same type as r-1 +1.7 / +0.6, OmniDocBench layout type recall +1.5 / +0.4; no measure lower (bus.key70_precision held -0.1, noise 6.7); RD-TableBench, olmOCR, FUNSD and the official OmniDocBench measures identical; hftable turns 2 running-head tables of one OmniDocBench page into Header/Footer blocks (table measures unchanged); 0 model calls | |
| 269 | 2026-11-14 | r3a-structure | hftable: verdict after the dev2 pass |
no code change; supersedes the verdict "option" of the line "hftable alone" | [keep] | fires on 1 business page on dev1 and on 1 business page + 1 OmniDocBench page (2 running-head tables) on dev2; right on all 3, no table measure moves on any set | |
| 270 | 2026-11-14 | r3b-cost | engine-final-3-cost (engine-final-2 + zoom_second=disputed + form_prefetch + prefetch_score=0.6;prefetch_area=0.02), whole devset, dev1 reads |
second zoomed pass only over disputed strips; form region reads started with the page read (regions of 5%+ of the page, not on turned pages); table reads started early only for regions with score 0.6+ and 2%+ of the page | [keep] | 639 of 639 responses identical to engine-final-2 (r2_final), so every measure is equal; 9 of 9 verified digit corrections; USD per page 0.0060 -> 0.0059, business 0.0119 -> 0.0116; model seconds a business page waits, median 33.9 -> 23.9 (forms 39.1 -> 26.8, FUNSD 15.9 -> 9.1) | dev bus.tables -36.0; dev dln.type_recall +14.7; held bus.tables -38.0; held dln.type_precision +16.2 |
| 271 | 2026-11-14 | r3b-cost | engine-final-3-cost, whole devset, dev2 reads |
as above, second independent reads (Sonnet reads of dev1 reused as in round 2) | [keep] | 639 of 639 responses identical to engine-final-2 on the same reads (r2_d2); USD per page 0.0060 -> 0.0059, business 0.0119 -> 0.0116; business wait median 26.6 -> 18.4 s | dev bus.tables -39.0; dev dln.type_precision +12.7; held bus.tables -30.0; held omni.table_teds_page +20.1 |
| 272 | 2026-11-14 | r3b-cost | engine-cheapest (one page read, every no-call piece of engine-final-2), business + tables + FUNSD, dev1 reads |
no strips, no handwriting reader, no verify re-reads, no table or form reads | [option] | exactly 1.00 model calls per page, DOCFORGE_NO_LIVE=1; business 0.0033 USD and 16.9 s median; both-keys type recall 75.3 / 72.6 (vanilla 63.0 / 66.1, engine-final-2 75.3 / 75.0), grids 90.8 / 88.4 (100.0 / 97.3), RD-TableBench 87.8 / 89.9 (89.8 / 91.8), FUNSD links 81.4 / 82.8 (86.6 / 84.1), totals 235 / 239 (235 / 241) | dev bus.tables -36.0; dev bus.key_recall +8.8; held bus.tables -38.0; held bus.matched -10.8 |
Glossary¶
| Reducto r-1 | Reducto's own document model, $0.01 a page; r-1 mini is its cheaper tier |
|---|---|
| Plain Haiku / vanilla / model-only | one Claude Haiku 5.5 read per page at high effort, nothing added |
| Engine | our layer around the model: layout model, OCR word positions, checks, repairs, routing |
| RD-TableBench | Reducto's 1,000-table benchmark and grader |
| olmOCR-bench | Allen AI's 1,403-page benchmark of pass/fail tests |
| OmniDocBench | a 1,651-page benchmark of text, tables, formulas, reading order and layout |
| FUNSD | 199 scanned forms with labelled question–answer links |
| DocLayNet | pages with human-labelled block types and boxes |
| LongExtract | 50 long documents with structured-extraction answer keys |
| Dev / held | the two halves of the development set: tune on dev, check on held |
| Noise floor | how much two identical runs differ; a smaller gain is not evidence |
| Zoom vote | enlarged strips of a scan read twice; a token changes only when both agree against the page read |
| Effort | Anthropic's setting for how much the model thinks before answering |
Generated from the project's reports and ledger on 14 November 2026. Product code: reducto-test/product, tags final-round-1 to final-round-4-citations.