Skip to content

ID-145 — FORM-EXTRACTION / FILL Spike (empirical, measured)

Type: Empirical spike feeding {145.3} TECH. Every number below is MEASURED against the owner’s real forms on 12/07/2026 — not estimated. Does NOT edit the ledger and does NOT author TECH.

Environment. Throwaway venv, system Python 3.14.2, PIP_USER=0. Pinned reader deps (pdfplumber==0.11.9, openpyxl==3.1.5, python-docx>=1.1.2, pydantic==2.12.5) + the vision stack (commonforms==0.2.1, langextract==1.6.0, pypdf==6.14.2, torch==2.13.0, rfdetr==1.8.3, onnxruntime, transformers). System pandoc present at /opt/homebrew/bin/pandoc (Charnwood track-changes resolved via it). Deterministic readers recovered from 5494b527 (=c84be63b^) into a temp restore, FormMetadata stubbed to bypass the anthropic/cocoindex import chain in extraction.py (the stub is field-identical; it does not touch field extraction). Temp restore removed after the run — repo tree clean.


  1. The recovered id-52 deterministic readers reproduce the ACCEPTANCE.md numbers EXACTLY on the three fixtures that were in the id-52 corpus (Charnwood 58, EFA 19, CSP 45) — recovery is a zero-cost, zero-regression git show.
  2. But those numbers are archetype-coupled: on two previously-unseen real forms (British Council annex_2 docx response form, annex_3 rate-card xlsx) the readers emit ZERO fields, and the Charnwood “58” is 51 prose-regex placeholder hits + one table — 7 of 8 tables surface zero fields, CONFIRMED as a design limit, not a bounded bug.
  3. Every real UK procurement PDF measured is FLAT (0 AcroForm widgets on 5/5 PDFs) so AcroForm parsing is a dead end and the FFDetr render→detect→write path is mandatory — commonforms delivers it (Croydon SQ 23 pp → 120 fields in 3.8 s CPU), corroborating ARCH-REVIEW’s 198-field/57-pp run.
  4. langextract does semantic, char-offset-grounded entity extraction (text spans only — no coordinates, no widget types, no fill status); it is NOT a fillable-field detector and adds no value over the live Claude question path for v1.
  5. A /goal ML “commonforms-for-docx/xlsx” build is NOT warranted — docx/xlsx fillable structure is native to OOXML (readable by python-docx/openpyxl), so the right move is to resurrect-and-generalise the deterministic readers (bounded engineering, seeded by these examples), not to train a detector.

1. Deterministic id-52 readers on real .docx / .xlsx

Section titled “1. Deterministic id-52 readers on real .docx / .xlsx”

Command (per file, via a standalone harness that stubs FormMetadata and calls docx.extract / xlsx.extract directly):

Terminal window
# recover the 6 deleted files from git
for f in __init__ docx xlsx pdf shared orchestrator; do
git show 5494b527:scripts/cocoindex_pipeline/form_extractors/$f.py > <tmp>/$f.py
done
# venv (PIP_USER=0 to defeat a global --user pip config)
python3 -m venv id145venv
PIP_USER=0 id145venv/bin/pip install pdfplumber==0.11.9 openpyxl==3.1.5 \
'python-docx>=1.1.2' pydantic==2.12.5
# run: PATH includes /opt/homebrew/bin so docx_utils finds system pandoc
PATH=/opt/homebrew/bin:$PATH id145venv/bin/python run_det_readers.py

1.1 Measured field counts vs ACCEPTANCE.md

Section titled “1.1 Measured field counts vs ACCEPTANCE.md”
SetFormatMeasuredACCEPTANCEVerdict
Charnwood ITT Services.docxdocx5858✓ exact
British Council annex_2_supplier_response.docxdocx0(not in id-52 corpus)NEW form → ZERO
EFA evaluation-matrix-itt-vol8.xlsxxlsx1919✓ exact (dedup, not 2N)
British Council annex_3_pricing_approach.xlsxxlsx0(not in id-52 corpus)NEW form → ZERO
CSP Cloud Security Principles Checklist V5_3.xlsx (owner)xlsx4545✓ exact

Per-facet (measured):

Settotalmandatoryword_limref_urlsplaceholderfull_coordssections
Charnwood580005873
EFA190000196
CSP450014424525

The owner CSP V5_3.xlsx returns the same 45 as the id-52 CSP fixture — the readers are faithful on in-corpus shapes.

1.2 Charnwood “7/8 tables zero fields” — CONFIRMED, and it is a DESIGN LIMIT

Section titled “1.2 Charnwood “7/8 tables zero fields” — CONFIRMED, and it is a DESIGN LIMIT”

Command: open via open_document_safe, classify each table’s header row with _classify_header and test _is_placeholder_grid.

Charnwood has 8 tables; only table[3] emits. full_coords=7 = those 7 grid cells; the other 51 of the “58” are _INLINE_PLACEHOLDER regex hits in paragraph prose (fields-per-table = {3: 7}).

tblheader (truncated)classifygrid?emittedwhat it really is
0[Name] / [Tel]None,Noneno0contact block — fillable, [Name]-style ≠ insert-pattern
1Stage / Date(s) and time(s)Noneno0tender timetable — fillable dates
2SCHEDULE HEADING / COMPLETED?Noneno0submission checklist — fillable Yes/No
3Insert question ti… / Insert %Noneyes7the one archetype it models
40-3 / Completely unsatis…Noneno0scoring rubric (reference — arguably correct)
5Service component / Costs (£)Noneno0pricing table — fillable £ cells
6I DECLARE THAT…Noneno0declaration (prose)
7SIGNED for and on…Noneno0signature block — fillable

The reader recognises exactly two shapes: (a) a table whose header cell _classify_headers to question/answer, and (b) a first-column-uniform insert-style placeholder grid. Real fillable areas that fit neither (contact/timetable/checklist/pricing/signature tables) are silently dropped. This is architectural — the engine is corpus-shape-coupled, not a generic fillable-field detector.

British Council annex_2 (7 tables — Social Value 10 %, Quality 20 %, Methodology, Commercial 40 %, Submission Checklist, Supplier details, Table of Information — a genuine supplier RESPONSE form) confirms it in the extreme: every table classifies None, none is an insert-grid, 0 fields. annex_3 (Rate Card & Resources: Role | Day Rate (£GBP) | Description, plus numbered Assumptions & Exclusions) matches neither the EFA scoring-matrix nor CSP letter-keyed archetype → NO_ARCHETYPE_REASON0 fields (graceful, logged, but empty).

Fidelity verdict: the readers are correct-but-overfit. Zero recovery cost and no regression on in-corpus shapes, but they generalise poorly — 0 fields on 2 of 4 unseen real forms and 7/8 Charnwood tables missed. Resurrecting them as-is is insufficient for the owner’s real corpus; they need generalisation (see §5).


Command:

Terminal window
python - <<'PY' # AcroForm status
from pypdf import PdfReader
for p in [...]:
r=PdfReader(p); f=r.get_fields() or {}
print(len(r.pages), len(f), 'ACROFORM' if f else 'FLAT')
PY
# FFDetr detect→write (weights auto-download to ~/.cache/huggingface on first run)
from commonforms import prepare_form
prepare_form(src, out, model_or_path='FFDetr', device='cpu', fast=True, confidence=0.4)
PDFpagesAcroForm fieldsclass
Croydon TMES CHS Salix MW SQ v1 (002).pdf230FLAT
Croydon …Bidders response document v1.pdf290FLAT
Lot Appendix A1.1 - TMES Company Details.pdf10FLAT
Lot Appendix A responses.pdf20FLAT
repo fixture standard-selection-questionnaire-ppn-03-24.pdf570FLAT

5/5 FLAT. AcroForm parsing yields nothing on real UK procurement PDFs — the detection path is not optional.

2.2 FFDetr detection (fast=True / FFDNet-S, CPU, conf=0.4) — MEASURED

Section titled “2.2 FFDetr detection (fast=True / FFDNet-S, CPU, conf=0.4) — MEASURED”
PDFpagesfields written/Tx (text)/Btn (checkbox)time (CPU)
Croydon SQ v1 (002)2312097233.8 s
Lot A1.1 Company Details13301.3 s

Per-page detection is genuinely spatial (SQ page 15: 27 fields, page 11: 12, pages 21-22: 0). Times exclude the one-time weights download. Corroborates ARCH-REVIEW’s 198-field/57-pp/35.9-s run: FFDetr is deployable on the existing IONOS worker without a GPU.

Gap the run exposes (unchanged from ARCH-REVIEW): detection yields coordinates + widget type ONLY — no label text. Auto-map matches lexically on field.question_text, so the PDF lane MUST pair each detected box with nearby label words (pdfplumber words left/above the box; the recovered pdf.py already extracts positioned words) or PDF auto-map is a structural no-op.

Licence status unchanged: commonforms code carries NO licence (GitHub license: null); only the FFDetr weights are stated Apache. Treat as viable-pending-licence — either confirm upstream, or replicate the ~200-line render→detect→write wrapper against the Apache FFDetr weights (pypdf + onnxruntime) using commonforms only as a dev-side reference.


3. langextract for non-PDF questions/entities

Section titled “3. langextract for non-PDF questions/entities”

Command: import + introspect langextract.data.Extraction, the provider registry, and the extract factory (no ANTHROPIC_API_KEY/OPENAI_API_KEY in env, so a live extraction could not be run here — assessment is architectural + API-level).

  • Output shape (Extraction): extraction_class, extraction_text, char_interval{start_pos,end_pos}, alignment_status, attributes. That is text spans with character offsets — there is NO row/col/table coordinate, NO widget type, NO fill_status. langextract does not detect fillable-field structure; it is complementary to (not a substitute for) a field detector.
  • Backends (measured registry): ^gemini (Gemini), local Ollama family (llama/mistral/mixtral/phi/qwen/deepseek/gpt-oss/…), and OpenAI (^gpt-4/^gpt-5). Non-Gemini is real. No Anthropic builtin; a Claude backend requires the community langextract-anthropic plugin.
  • Text-only: PDFs/docx/xlsx must be textified first (the deterministic readers or python-docx/openpyxl can supply that text).
  • Value over live Claude extraction: its one differentiator is char-offset span grounding (extractions map to exact source spans → aligns with the citation span model, D-S330-1). But it still needs an LLM (no cost saving), adds a dependency + a community Anthropic plugin as a prod dep, and the live Claude path already produces sectioned questions with word limits/weights across PDF/DOCX. v1 verdict: not adopted; track as a second-generation upgrade when span-grounded question provenance is wanted (matches ARCH-REVIEW R-A).

4. Per-format recommendation {PDF, .docx, .xlsx, .html}

Section titled “4. Per-format recommendation {PDF, .docx, .xlsx, .html}”
FormatReal-world classExtraction (plane 2 structure)FillNotes
PDFFLAT (5/5 measured)FFDetr render→detect→write (commonforms approach; measured 120 / SQ, 3 / A1.1). AcroForm parse = dead end.pypdf AcroForm value writes into the detected widgetsMUST add label-pairing (pdfplumber words near boxes) or auto-map no-ops. Licence-gate the code dep.
.docxstructured OOXMLresurrect id-52 reader but generalise beyond the 2 archetypes (measured 0 on annex_2; 7/8 Charnwood tables missed)fill_template.py / docxtpl (formatting-preserving)structure is native — no ML detector needed; generic “labelled cell + empty/placeholder response cell”, content-controls, highlighted runs
.xlsxstructured SpreadsheetMLresurrect id-52 reader but generalise beyond EFA/CSP archetypes (measured 0 on annex_3 rate-card)openpyxl cell writes at recorded coordssame: detect empty answer cells adjacent to labelled cells generically
.htmlnone (no fixture)net-new lanedefer until a real sample exists

Legacy .doc/.xls are still in the owner corpus (British Council .doc RFPs; Charnwood ITT Evaluation Matrix.xls) — convert-on-upload (LibreOffice headless on the worker) or reject-with-guidance is a TECH decision.

Partial-fill-as-success (BI-22) is the default contract for every format: a completion with gaps is completed + fields_skipped>0 (never fill_failed, reserved for I/O/engine errors); gaps stay skipped/pending and re-fill passes process only gaps.


5. /goal build verdict — NOT warranted (generalise the deterministic readers instead)

Section titled “5. /goal build verdict — NOT warranted (generalise the deterministic readers instead)”

The owner idea is a one-time Claude-Code-/goal side-project to BUILD a .docx/.xlsx commonforms-equivalent field detector, seeded with these examples.

Verdict: a /goal ML detector build is NOT warranted for docx/xlsx — it is a category error. commonforms needs vision because a PDF is a flat raster with no structural record of its fields; a .docx/.xlsx is the opposite — the fillable structure (tables, cells, merged cells, content controls, highlighting) is native to OOXML/SpreadsheetML and directly readable by python-docx/openpyxl. The deterministic readers already prove this: they reproduce the acceptance counts exactly at zero dependency cost. Their only defect is narrow heuristics (two hardcoded shapes), not a missing detector.

Recommendation: resurrect-and-generalise the deterministic readers. Rough plan (bounded, days not weeks; do NOT build now):

  1. Replace archetype-matching with generic structural detection. DOCX: any table row where a labelled/prose cell is followed by an empty-or-placeholder cell → a field (drop the _classify_header gate as the only path); also emit Word content-controls (w:sdt) and highlighted runs. XLSX: any labelled cell with an adjacent empty answer cell → a field (drop the EFA/CSP-only gate); keep dedup.
  2. Keep the two archetypes as high-precision fast-paths layered over the generic detector (so EFA 19 / CSP 45 / Charnwood grid stay stable).
  3. Regression-gate on this exact corpus: Charnwood ≥ its 8 real fillable tables (not 1), annex_2 > 0, annex_3 rate-card rows detected, EFA 19, CSP 45.
  4. Use /goal only as an authoring accelerator for that generalised reader against the eval harness — the deliverable is deterministic code + a fixture suite, NOT a trained model.

This reuses the id-52 investment (readers, tests, fixtures, the analyse_form queue-worker shape) and directly closes the measured gap, at a fraction of the cost and risk of a from-scratch ML build.


  1. PDF = FFDetr detection, never AcroForm parse. 5/5 real PDFs are FLAT; detection is mandatory and measured-deployable on CPU (120 fields / 3.8 s). Gate the code dependency on licence (replicate the ~200-line wrapper on Apache FFDetr weights if unresolved), and MUST pair detected boxes with pdfplumber label words or PDF auto-map is a no-op.
  2. .docx/.xlsx = generalise the recovered deterministic readers, do NOT /goal an ML detector. The measured design limit (0 fields on annex_2/annex_3; 7/8 Charnwood tables missed) is narrow heuristics over a native-structured format — fix with generic cell/placeholder/content-control detection, regression-gated on this corpus.
  3. langextract deferred; live Claude path stays plane-1. langextract emits text spans only (no field structure), needs an LLM anyway, and adds a community Anthropic plugin as a prod dep — track it as the span-grounded-provenance upgrade, not a v1 dependency. Legacy .doc/.xls need a convert-on-upload decision; .html defers (no fixture).