Cocoindex Doc Handling — KH Form Pipeline Mapping
Cocoindex Doc Handling — KH Form Pipeline Mapping
Section titled “Cocoindex Doc Handling — KH Form Pipeline Mapping”Date: 13/05/2026
Branch: content-items-investigation
Scope: Phase B prerequisite 2c per feedback-findings-review.md Theme G (doc skills) + Theme B (form extraction pipeline) + Finding 06 (CX.33 source-doc explorer).
Author: Claude (research-only sub-agent — no code edits).
Status: RESEARCH ONLY — informs Theme B 6-step flow tool selection + Finding 06 per-MIME viewer decisions.
Inputs read:
feedback-findings-review.md§1 Theme B + Theme G + §3.2 cocoindex input matrix10-feedback-investigation-findings/04-q35-q310-historical-extraction-coverage.md(1B-3)10-feedback-investigation-findings/06-cx33-source-doc-explorer.md(1B-C)10-feedback-investigation-findings/00-synthesis.md(N2 cataloguer skill)0.8.5-skill-seekers-evaluation.md(current KH choice for PDF scraping)0.8.4-pullmd-evaluation.md+0.9-spike-S4-pullmd-bakeoff.md(URL extraction).claude/skills/{docx,xlsx,pdf,markdown-converter,cocoindex}/SKILL.md- Cocoindex docs + GitHub
examples/pdf_to_markdown,examples/pdf_embedding,examples/paper_metadata,examples/patient_intake_extraction_baml(via WebFetch) - KH form fixtures
docs/client-documentation-base/forms/*(file metadata sample only) - KH existing extractors
scripts/extract_tender_questions.py,scripts/analyse_template.py,lib/quality/qa-detection.ts
Limitations / flags:
- Cocoindex extraction-quality claims could not be independently verified — Docling is the actual extractor and its claims are vendor-marketing (LF AI & Data Foundation hosting + OpenSSF certification do NOT independently validate per-format conversion fidelity). See §1.1.
- No empirical bake-off was run for this prereq — the closest data point is
0.9-spike-S4-pullmd-bakeoff.mdwhich exercised pullmd’s PDF path (failed badly) but not Docling’s. - The two form fixtures were not opened; only file headers + the structural details captured in Finding 04 §3.3 / Finding 06 §5.4 are available. Recommend an empirical Docling spike before committing.
§1 — Cocoindex doc handling capabilities
Section titled “§1 — Cocoindex doc handling capabilities”Cocoindex itself is a pipeline-orchestration framework, not a document parser. Every “doc-handling” capability cited here is actually delegated to a third-party library that the cocoindex example wires into the framework. This is the most important framing for the rest of this doc: when we say “cocoindex handles PDFs,” what we really mean is “cocoindex has an example that wires Docling into a memoised @coco.fn.” Replace Docling and the equation changes.
1.1 PDF→Markdown example
Section titled “1.1 PDF→Markdown example”What it does (verified via https://raw.githubusercontent.com/cocoindex-io/cocoindex/main/examples/pdf_to_markdown/main.py):
from docling.document_converter import DocumentConverterimport cocoindex as cocofrom cocoindex.connectors import localfsfrom cocoindex.resources.file import PatternFilePathMatcher
_converter = DocumentConverter()
@coco.fn(memo=True)def process_file(file: localfs.File, outdir: pathlib.Path) -> None: markdown = _converter.convert(file.file_path.resolve()).document.export_to_markdown() outname = file.file_path.path.stem + ".md" localfs.declare_file(outdir / outname, markdown, create_parent_dirs=True)The full pipeline is ~25 LOC. The real work is in DocumentConverter().convert(...).document.export_to_markdown() from Docling.
What Docling claims (per Docling docs — vendor-stated, not independently verified):
- Supports PDF, DOCX, PPTX, XLSX, HTML, WAV, MP3, WebVTT, images (PNG/TIFF/JPEG), LaTeX, plain text, USPTO patents, JATS articles, XBRL.
- “Advanced PDF understanding incl. page layout, reading order, table structure, code, formulas, image classification.”
- Exports to Markdown, HTML, WebVTT, DocTags, lossless JSON.
- “Extensive OCR support for scanned PDFs and images” + Visual Language Models (GraniteDocling, Heron layout model).
- LF AI & Data Foundation project + OpenSSF Best Practices certification (governance signal, NOT a quality signal).
KH-fit (vs SSQ PDF fixture — standard-selection-questionnaire-ppn-03-24.pdf, 8 pages, v1.4):
- The SSQ PDF was the source used by
scripts/catalogue-standard-sq.tsto hand-craft 66template_requirementsrows (Finding 04 §3.1). Today there is no automated path from this PDF totemplate_requirements. - Docling’s claimed table-structure preservation + heading hierarchy would, in principle, replace the manual transcription. But: the SSQ has a section-numbered + part-prefixed Q-numbering scheme (Part 1 §17 × 17 sub-q, Part 2 Exclusion Grounds, Part 3 Modern Slavery, Declaration, plus PPN-specific selection-question parts — Finding 04 §3.1). Whether Docling preserves THAT hierarchy is unverified.
- Spike-S4 PDF result (§3 below): pullmd hard-failed on PDFs. That’s not Docling, but it sets a prior that “PDF → useful markdown” is harder than vendor claims suggest. Recommend a 2-PDF Docling spike before commit (SSQ + KCSIE-2025 Part One, which is the 70-page bid-library fixture cited in 0.9-Spike-S4 §4 ground-truth table).
Strengths (claimed, partly verified):
- Single dependency for many formats (PDF/DOCX/XLSX/PPTX/HTML/images) — replaces a stack of format-specific libraries.
- Cocoindex memoisation means re-running over the same PDF skips re-conversion — important for the Theme D freshness cadence.
- OCR support included (none of KH’s current PDF tools —
unpdf,extract_pdf_text.py— do OCR). - Lossless JSON export means structured re-extraction post-hoc is possible without re-parsing the PDF.
Weaknesses (verified or strongly suspected):
- Docling is a heavy dependency. Vendor docs reference layout models (Heron) + Visual Language Models (GraniteDocling) — these are ML models with non-trivial container size and inference cost. Cocoindex’s PDF example marks
process_filewith@coco.fn(memo=True)but the underlying example uses a CPU path; thepdf_embeddingexample uses@coco.fn.as_async(runner=coco.GPU)for the conversion step, suggesting GPU is the production-quality path. This has Cloud Run cost implications similar to the pullmd Playwright sidecar (~3.7 GB image). - No independent fidelity benchmark exists that compares Docling to: (a)
unpdf, (b)pdf-parse, (c) Jina Reader, (d)markitdown(Microsoft’s, behind Anthropic’smarkdown-converterskill), (e)pdfplumber. We are taking the vendor’s word on “advanced PDF understanding.” - Whether table-structure preservation works on the SSQ-style numbered question lists vs blob narrative text is unverified. Real bake-off needed.
1.2 Structured Extraction
Section titled “1.2 Structured Extraction”What it is: Cocoindex provides the pipeline plumbing for structured extraction (memoisation, target declarations, source tracking) but does NOT ship a built-in structured-extraction operator. The two relevant examples both delegate to external schema-extraction libraries:
| Example | Library used | LLM | Schema syntax |
|---|---|---|---|
examples/paper_metadata | Raw openai.OpenAI + JSON-coerced Python @dataclass | GPT-4o | Python @dataclass; output validated via JSON schema in the system prompt |
examples/patient_intake_extraction_baml | BAML (Boundary AI Markup Language) + baml_client | Claude (BAML chooses model) | BAML’s .baml schema files compiled to a Python Patient type |
examples/patient_intake_extraction_dspy | DSPy | Configurable (DSPy abstraction) | DSPy Signature classes |
Pattern across all three: the structured-extraction call sits inside a @coco.fn(memo=True) wrapper, which gives the cocoindex benefits (skip re-extraction on unchanged input + lineage tracking) on top of whatever extraction library you pick. Failure mode is whatever the library does — paper_metadata’s GPT-4o JSON-mode call will raise on malformed JSON; BAML retries; DSPy has its own retry/repair logic.
KH-fit:
- Step 4 (classify-form-data) of Liam’s 6-step flow is the natural target. Once a form is converted to markdown (step 3), you want to extract
{section_ref, question_number, raw_text, requirement_type, primary_domain, primary_subtopic, matching_keywords, is_mandatory, sector_applicability}per question — exactly theTemplateRequirementshape inscripts/catalogue-standard-sq.tslines 30-200. - The paper_metadata example is the closest pattern:
@dataclassschema + GPT-4o + JSON mode. Direct port to KH would be@dataclass class TemplateRequirement+ Claude (KH’s existing LLM) + JSON mode viainstructor(cocoindex skill SKILL.md §Pattern 3 already shows this pattern with Pydantic + litellm). - BAML is more elegant but adds another tool to learn + a
.bamlcompile step. DSPy is research-grade — overkill. - The cocoindex framing adds value over a raw LLM call because: (i) memoisation skips re-extraction on identical markdown input, (ii) lineage tracks which extraction produced which
template_requirementsrow, (iii) failure-mode telemetry comes “for free” via the cocoindex op ledger.
Strengths:
- Pipeline-aware (memoised + lineage-tracked) extraction beats a one-shot LLM call by a lot for KH’s “AI-assisted cataloguing” workflow per Finding 04 Rec 5.
- Schema is just Python (
@dataclassorpydantic.BaseModel) — no DSL to learn, matches the cocoindex skill SKILL.md philosophy line 4: “no DSL — use dataclasses, Pydantic, NamedTuple.” - LLM backend is bring-your-own — KH can keep its existing Claude wiring (already in
lib/ai/extract-questions.ts).
Weaknesses:
- Not zero-friction — you still need to write the schema dataclass + the prompt + the JSON-mode call. The cocoindex layer is plumbing, not extraction.
- Failure mode is library-dependent. instructor + litellm + Claude has well-trodden failure paths but they’re not part of the cocoindex contract — you handle retries yourself.
- The paper_metadata example uses GPT-4o (not Claude); cost calculus may differ.
1.3 XLSX / DOCX / HTML support
Section titled “1.3 XLSX / DOCX / HTML support”Cocoindex examples cover NONE of these formats directly. The example list (verified via https://github.com/cocoindex-io/cocoindex/tree/main/examples) contains:
amazon_s3_embedding, audio_to_text, code_embedding, code_embedding_lancedb, conversation_to_knowledge, csv_to_kafka, entire_session_search, files_transform, gdrive_text_embedding, hn_trending_topics, image_search, image_search_colpali, kafka_to_lancedb, meeting_notes_graph_falkordb, meeting_notes_graph_neo4j, multi_codebase_summarization, oci_object_storage_embedding, paper_metadata, patient_intake_extraction_baml, patient_intake_extraction_dspy, pdf_embedding, pdf_to_markdown, postgres_source, text_embedding, text_embedding_lancedb, text_embedding_qdrant, text_embedding_turbopuffer.
Gaps explicit:
- No XLSX example. Docling claims XLSX support but no cocoindex example wires it. The Cloud Security Principles Checklist fixture would need: (a) Docling experimental XLSX path, or (b) a separate
exceljs/openpyxl@coco.fnwrapper. Finding 04 §3.4 already recommendedexceljsfor the deterministic question-text extraction layer. - No DOCX example. Docling claims DOCX support but no cocoindex example wires it. KH’s existing
mammoth + Turndownpipeline (lib/bid-library-ingest/docx-to-markdown.ts) is in-stack and verified. Docling would replace it; doing so without a fidelity bake-off is unjustified. - No HTML example. Docling claims HTML support but no cocoindex example wires it. KH has 2 HTML pipelines (Readability+JSDOM in
lib/extraction/html.tsfor P4; regex+Turndown fallback inlib/intelligence/content-extractor.tsTier 2 for P9). Spike-S4 already replaced both with pullmd.
Net: cocoindex’s “doc handling” headline reduces to (a) one PDF→Markdown example using Docling, (b) one PDF-embedding example using Docling, (c) three structured-extraction examples that do not address the conversion problem — they consume already-converted text. Anything beyond that is a bet on Docling itself.
§2 — Anthropic doc skills (per skill)
Section titled “§2 — Anthropic doc skills (per skill)”Anthropic ships four doc skills in .claude/skills/. Each is a different shape of tool, despite the surface similarity.
| Skill | Format(s) | Output | Fit for step 1 (evaluate) | Fit for step 3 (convert) | Fit for step 4 (classify) |
|---|---|---|---|---|---|
docx | DOCX read/edit/create | Markdown (via pandoc) + raw XML access | HIGH — pandoc + raw XML give the agent enough to propose a conversion approach | MEDIUM — pandoc with --track-changes=all works for simple DOCX; KH’s existing mammoth+Turndown is better for tables | LOW — not a classifier; just extracts |
xlsx | XLSX read/edit/create | pandas DataFrame OR openpyxl Workbook in Python | HIGH — pandas read + sheet introspection is exactly the right shape for evaluating “what’s in this XLSX” | HIGH — pandas to_markdown() is one line; openpyxl preserves formulas | MEDIUM — pandas is data, not classification; would feed step 4 not BE step 4 |
pdf | PDF read/extract/create/forms | Plain text OR structured tables (pdfplumber) OR OCR (pytesseract) | HIGH — pdfplumber tables + reference.md’s pdftotext -bbox-layout give the agent precise structural understanding | MEDIUM — pdfplumber extract_text+extract_tables works but loses the layout fidelity Docling claims | LOW — extraction only |
markdown-converter | PDF, DOCX, PPTX, XLSX, HTML, CSV, JSON, XML, images (EXIF+OCR), audio (transcript), ZIP, YouTube, EPub | Markdown only (via markitdown) | MEDIUM — one-shot conversion gives the agent the output of step 3 but no preview of structure-fidelity | HIGH — uvx markitdown <file> is one command; Azure Document Intelligence (-d) for PDF fallback | LOW — output only; no schema |
Per-skill prose
Section titled “Per-skill prose”docx skill (Anthropic, in .claude/skills/docx/): This is the most powerful Anthropic doc skill by a wide margin. It does NOT just convert — it provides a workflow for reading, editing, and creating DOCX, including a Redlining workflow with tracked-changes batches (3-10 changes per batch, RSID preservation, mandatory pre-pack pandoc verification). The two primary tools are pandoc --track-changes=all for extraction and the bespoke python ooxml/scripts/unpack.py + Document library + pack.py chain for editing. For step 1 (evaluate-form): this is the agent’s correct tool for DOCX. It can pandoc-extract, then read word/document.xml to understand table structure, embedded media, and whether there are tracked changes / comments that would confuse a downstream classifier. For step 3 (convert): pandoc is fine but KH’s existing mammoth+Turndown is in-stack and validated (CLAUDE.md gotcha: “mammoth convertToMarkdown() drops tables — use two-step convertToHtml() → Turndown with turndown-plugin-gfm”). The docx skill doesn’t actually do anything KH’s existing path doesn’t. For step 4 (classify): not its concern.
xlsx skill (Anthropic, in .claude/skills/xlsx/): Heavily oriented toward creation of financial models (industry-standard colour coding, formula error prevention, recalculation via recalc.py with LibreOffice). For our use case (read + evaluate Cloud Security Principles Checklist), the relevant patterns are pd.read_excel('file.xlsx', sheet_name=None) for multi-sheet introspection + openpyxl.load_workbook(data_only=True) for calculated values. For step 1 (evaluate-form): perfect. The agent can list sheets, list column headers per sheet, sample rows, identify whether the XLSX is questionnaire-shaped (column headers like Principle / Sub-Principle / Requirement / Response) vs free-form. For step 3 (convert): pd.DataFrame.to_markdown() is one line and preserves table structure; this is materially better than Docling’s XLSX path (unverified) for KH’s use case because we WANT table-as-table markdown, not “best effort prose.” For step 4 (classify): the data structure is already pandas-shaped — feeds a classification step but isn’t one.
pdf skill (Anthropic, in .claude/skills/pdf/ + reference.md): Broad toolkit — pypdf for merge/split/metadata/forms, pdfplumber for text+tables, reportlab for creation, pytesseract+pdf2image for OCR, plus CLI tools (pdftotext, qpdf, pdftk, pdfimages, pdftoppm). The reference.md adds pypdfium2 rendering, JS libs (pdf-lib, pdfjs-dist), and advanced pdfplumber settings for tables (vertical_strategy='lines', snap tolerances). For step 1 (evaluate-form): excellent for the SSQ fixture. The agent can pypdf to count pages (8), pdfplumber.extract_tables with custom settings to identify the Q-numbered structure, pdftotext -bbox-layout to get word coordinates for layout understanding. For step 3 (convert): adequate, not best-in-class. pdfplumber produces clean structured text but doesn’t claim the layout-model + VLM enrichment Docling does. The CLI tool pdftotext -layout preserves columns; markitdown (the markdown-converter skill) wraps Azure Document Intelligence as a Tier-2 fallback. For step 4 (classify): not its concern. Note: the pdf skill has a dedicated forms.md (mentioned but not read here) for PDF form-filling — likely relevant if the agent encounters AcroForm-shaped fillable PDFs, but the SSQ isn’t one.
markdown-converter skill (Anthropic, in .claude/skills/markdown-converter/): Thin wrapper around Microsoft’s markitdown — one command, all formats. Notable: the -d flag enables Azure Document Intelligence as the PDF backend (paid Azure service, Microsoft’s equivalent of Docling/AWS Textract). This is the one Anthropic skill that closes the gap with Docling on the “advanced PDF understanding” axis — at the cost of an Azure subscription. For step 1 (evaluate-form): too coarse. It produces markdown but doesn’t expose the structural information the agent needs to propose a conversion approach. For step 3 (convert): great default. uvx markitdown form.xlsx returns markdown in one shot; uvx markitdown form.pdf -d -e <azure-endpoint> adds Azure DI for PDFs. For step 4 (classify): not its concern.
Net Anthropic skills picture: the four skills are complementary, not substitutable. The right loadout for step 1 (evaluate-form) is format-specific (docx for .docx, xlsx for .xlsx, pdf for .pdf). The right loadout for step 3 (convert) is markdown-converter as default + format-specific override when fidelity matters. The right loadout for step 4 (classify) is none of them — it’s a cocoindex Structured Extraction job (per §1.2) or a bespoke KH skill.
§3 — Alternative tools
Section titled “§3 — Alternative tools”3.1 skill-seekers — current KH choice for PDF scraping (per 0.8.5-skill-seekers-evaluation.md)
Section titled “3.1 skill-seekers — current KH choice for PDF scraping (per 0.8.5-skill-seekers-evaluation.md)”What it is: yusufkaraaslan/Skill_Seekers — Python 3.10+, MIT, ~13.4k stars. 17 source adapters (web, github, pdf, word, epub, video, local, jupyter, html, openapi, asciidoc, pptx, rss, manpage, confluence, notion, chat, config). PDF path uses pdfplumber (NOT unpdf) and supports OCR (enable_ocr), encrypted PDFs (password), image extraction (min_image_size), chapter detection + page chunking + code-block-merging across pages.
KH-fit decision per 0.8.5 §2.1 (Q1 verdict, lines 75-89):
- “STRONG replacement candidate for PDF” — skill-seekers’
pdf_scraper.pyhandles features KH’sunpdfdoes not: chapters, OCR, password-protected PDFs, images. - “KH’s
unpdfis fast but minimal — text-only, no structure preservation.” - Decision is HYBRID adoption is the right answer, not full replacement — adopt skill-seekers as a P7 upgrade for PDF, keep Readability for single-URL article extraction, keep KH’s Q&A docx parser, keep KH’s P9 RSS pipeline.
Status: No production integration changes have been made. The evaluation is recommendation-only. The decision is therefore PENDING — KH has NOT yet swapped to skill-seekers for PDF; it’s still on unpdf + extract_pdf_text.py.
Comparison to Docling: Both are heavyweight PDF stacks claiming superior structure preservation. Differences:
- skill-seekers is one library among 17 adapters; Docling is a focused document-AI library + a model stack.
- skill-seekers has explicit OCR + encrypted PDF + image extraction; Docling has the same plus layout models + VLMs.
- skill-seekers is Python only; Docling is Python only.
- skill-seekers is single-shot; Docling integrates as a memoised
@coco.fnstep. - skill-seekers has no XLSX/DOCX/HTML overlap with KH’s existing stack (its
word_scraper.pyuses the same mammoth+python-docx toolchain KH uses); Docling claims native XLSX/DOCX/HTML.
3.2 pullmd (per 0.8.4-pullmd-evaluation.md + 0.9-spike-S4-pullmd-bakeoff.md)
Section titled “3.2 pullmd (per 0.8.4-pullmd-evaluation.md + 0.9-spike-S4-pullmd-bakeoff.md)”What it is: AeternaLabsHQ/pullmd — Node.js + Python self-hosted service. URL→markdown cascade: Cloudflare native short-circuit → Mozilla Readability + Trafilatura on static HTML → Playwright headless Chromium for JS-heavy pages → Reddit comment-tree adapter. AGPL v3. 80.1 MB Docker image (multi-arch).
KH-fit decision per Spike-S4 §1 (verdict, lines 13-29):
- CONDITIONAL PASS — adopt pullmd as Tier 2/Tier 2.5/Tier 3 replacement for HTML and Reddit URL paths. KEEP KH’s Jina Reader fallback for PDF URLs (pullmd does not parse PDFs).
- 45/50 URLs verified pullmd ≥ KH cascade (90%); 5/5 PDFs hard-failed (binary garbage at HTTP 200 with misleading X-Quality=0.5).
- Drop Firecrawl from the cascade entirely once pullmd is wired.
- AGPL v3 + production-scale SLA + 6-12mo stability remain Liam-decisions outside the spike’s scope.
Status: No production integration changes have been made. Phase A-D plan (~3-3.5 weeks total) is documented in 0.8.4 §10.3 but not started. Decision is PENDING Liam ratification of PM-Q1 (50-URL bake-off → done, conditional pass), PM-Q2 (AGPL v3), PM-Q9 (storage_path sentinels), and the Phase A-D start.
Comparison to Docling: pullmd and Docling are different shapes — pullmd is a URL→markdown service (HTML, JS-pages, Reddit), Docling is a binary→markdown library (PDF, DOCX, XLSX). They’re orthogonal, not competitive. The cleanest framing: pullmd owns Layer 3 URL inputs; Docling (if adopted) owns Layer 3 binary inputs. Spike-S4 §5.4 reinforces this: “P4/P9 swap must add a HEAD-request content-type sniff OR .pdf extension check BEFORE calling pullmd. If application/pdf, route to KH’s existing PDF path.”
3.3 KH existing extractors
Section titled “3.3 KH existing extractors”scripts/extract_tender_questions.py (verified via Read):
- Pure Python, uses
python-docx+docx_utils.open_document_safe. - Domain-specific header normalisation: 30+ variants of “question/requirement/response/answer/etc.” mapped to canonical names (
_QUESTION_HEADERS,_ANSWER_HEADERS,_SECTION_HEADERS,_NUMBER_HEADERS). - Output: JSON to stdout with
{sections, questions, word_limits, sequence}. - Purpose: tender-shape DOCX → intermediate JSON for
bid_questions. - Used for SSQ/Charnwood? No. Finding 04 §3.2 confirms this codepath produces
bid_questionsshape, nottemplate_requirementsshape. - Reusable for new form pipeline? Partially — the
_*_HEADERSnormalisation is exactly the canonical-name logic any new form extractor needs. Lift the constants; rewrite the table-walk againstexceljsfor XLSX + Docling/pdfplumber for PDF.
scripts/analyse_template.py (verified via Read):
- Detects fillable fields in DOCX bid-response templates (empty cells adjacent to populated question cells, placeholder text patterns, merged-cell handling).
- 13
PLACEHOLDER_PATTERNSregexes (\[insert ...\],\{\{var\}\},<<RESPONSE>>, etc.). - Used for SSQ/Charnwood? No. Finding 04 §3.2 confirms this codepath produces
template_fieldsshape (writeable-target detection for bid-response composition), nottemplate_requirementsshape. Empty in prod. - Reusable for new form pipeline? Yes for the placeholder-detection logic — this is exactly the L1 deterministic step Finding 04 §3.4 recommends. Lift the regex constants; rewrite the cell-walk for XLSX + PDF.
lib/quality/qa-detection.ts (verified via Read, first 80 lines):
- TS port of the Python parser. Detects Q&A pairs in mammoth HTML output using four strategies: tables, numbered lists, heading-paragraph, text-marker fallback.
- Deterministic (no AI calls); operates on HTML strings post-mammoth.
- Phase 1 of the Q&A Auto-Split spec.
- Used for SSQ/Charnwood? No. Same Q&A-pair-not-template-requirement distinction. Finding 04 §3.2.
- Reusable for new form pipeline? Some — the four-strategy detection is sound for Q&A-pair-shaped forms (Phew bid library), less sound for template-question-shaped forms (SSQ/Charnwood). Use as a fallback when LLM classification (cocoindex Structured Extraction) is uncertain.
Net existing-extractor picture: The existing scripts are not the form-extraction pipeline Theme B specifies. They solve adjacent problems (Q&A pair extraction for Phew library, bid-response template field detection) with overlapping primitives (header normalisation, placeholder regex, table walks). They’re harvestable for constants and regex patterns, but the pipeline structure needs to be rebuilt around the Theme B 6-step flow.
§4 — Fixture walkthrough
Section titled “§4 — Fixture walkthrough”Picked fixture: Cloud Security Principles Checklist V5_3 - PHEW.xlsx (34 KB, Microsoft Excel 2007+, in docs/client-documentation-base/forms/).
Why this one (not the SSQ PDF): XLSX is the more ambiguous case for tool selection. The SSQ PDF answer is clearer (Docling vs pdf-skill vs markdown-converter is well-trodden); the XLSX answer exposes the Anthropic-skill vs cocoindex-skill vs custom-skill tension more sharply. Also: Cloud Security Principles maps onto Phew’s actual UAT corpus (per Finding 06 §5.4) and is the example Liam cited in 09-decision-graph-feedback-actions.md §1 Q3.5.
Step 1 — evaluate-form skill (Anthropic xlsx skill)
Section titled “Step 1 — evaluate-form skill (Anthropic xlsx skill)”The agent invokes the xlsx Anthropic skill:
import pandas as pdfrom openpyxl import load_workbook
# Sheet discoveryall_sheets = pd.read_excel( 'docs/client-documentation-base/forms/Cloud Security Principles Checklist V5_3 - PHEW.xlsx', sheet_name=None)print(list(all_sheets.keys()))# Expected (per NCSC Cloud Security Principles structure):# ['Cover', 'Principle 1', 'Principle 2', ..., 'Principle 14'] OR# ['Checklist'] (single-sheet variant)
# Column-header inspection per sheetfor name, df in all_sheets.items(): print(f"--- {name} ---") print(df.columns.tolist()) print(df.head(3))Expected output (inferred, not opened): the agent identifies whether this is single-sheet (one row per principle/sub-principle) or multi-sheet (one sheet per principle, multiple rows per sheet for sub-questions). Header names likely follow NCSC’s 14 cloud security principles taxonomy: Principle | Sub-Principle | Requirement | Response | Evidence | Status.
Step 1 output (agent prose, fed to HITL step 2): “This XLSX is a checklist-shaped form with N sheets corresponding to N NCSC Cloud Security Principles. Each sheet has columns {Principle, Sub-Principle, Requirement, Response, Evidence, Status}. The Requirement column is question-shaped; the Response column is answer-shaped. Recommended conversion approach: per-sheet pandas DataFrame → markdown table → flatten to one Q&A pair per Requirement row. Sheets that are pure cover-page / instructions should be skipped at the L1 extraction step.”
Step 2 — HITL verification
Section titled “Step 2 — HITL verification”Liam reviews the agent’s evaluate-form output. Confirms sheet structure + Q&A row identification. May adjust:
- Which sheets to include / skip (cover sheets, instruction sheets).
- Which columns are question / answer / metadata.
- Whether Response column is empty (form-to-fill) or pre-populated (form-as-answered).
Step 3 — Markdown conversion (cocoindex pipeline OR Anthropic markdown-converter)
Section titled “Step 3 — Markdown conversion (cocoindex pipeline OR Anthropic markdown-converter)”Option A (cocoindex pipeline, recommended): A reusable cocoindex converter for the XLSX-form type:
import pandas as pdimport cocoindex as coco
@coco.fn(memo=True)def xlsx_form_to_markdown(file_path: pathlib.Path) -> str: sheets = pd.read_excel(file_path, sheet_name=None) out = [] for name, df in sheets.items(): if name.lower() in ("cover", "instructions", "guidance"): continue out.append(f"# {name}\n\n") out.append(df.to_markdown(index=False)) out.append("\n\n") return "".join(out)This sits inside a cocoindex flow that triggers on file changes (Theme D freshness) and writes the markdown sidecar to localfs (Finding 06 §6 markdown-canonical pattern).
Option B (Anthropic markdown-converter skill, fallback): uvx markitdown 'Cloud Security Principles Checklist V5_3 - PHEW.xlsx' -o cloud-security-checklist.md. Single command, no cocoindex plumbing. Loses memoisation + lineage. Useful for one-off conversions; not for the recurring pipeline.
Step 3 output: markdown sidecar .md with one heading per non-skipped sheet + GFM table per Requirements list. Lands at <source_documents.storage_path with .md suffix> per Finding 06 §6.
Step 4 — classify-form-data (cocoindex Structured Extraction)
Section titled “Step 4 — classify-form-data (cocoindex Structured Extraction)”The agent invokes a @coco.fn(memo=True) that wraps a @dataclass schema + Claude:
from dataclasses import dataclassfrom typing import Optionalimport cocoindex as cocoimport instructorfrom litellm import acompletion
_instructor_client = instructor.from_litellm(acompletion, mode=instructor.Mode.JSON)
@dataclassclass TemplateRequirement: section_ref: str # e.g. "P5" section_name: str # e.g. "Operational security" question_number: str # e.g. "5.3" requirement_text: str # the question text description: Optional[str] # any inline description / guidance requirement_type: str # one of: policy / statement / evidence / data / narrative / declaration / reference primary_domain: str # KH taxonomy primary_subtopic: str # KH taxonomy matching_keywords: list[str] # 5-8 keywords for tier-2 matching is_mandatory: bool sector_applicability: list[str] word_limit_guidance: Optional[str]
@coco.fn(memo=True)async def classify_form_question(question_md: str) -> TemplateRequirement: return await _instructor_client.chat.completions.create( model="claude-sonnet-4-5", response_model=TemplateRequirement, messages=[ {"role": "system", "content": "<KH taxonomy + classification rules + few-shot examples>"}, {"role": "user", "content": question_md}, ], )Schema mirrors the existing template_requirements table shape (per Finding 04 §3.1) — direct upsert target.
Step 5 — HITL verification of classification
Section titled “Step 5 — HITL verification of classification”Liam reviews the cataloguer skill output. Per Finding 04 Rec 5 / N2 in synthesis: output is a generated scripts/catalogue-<slug>.ts seed-script. Liam reads through, adjusts mis-classifications, commits.
Step 6 — cocoindex pipeline
Section titled “Step 6 — cocoindex pipeline”The cataloguer skill’s output flows into the cocoindex pipeline that owns template_requirements:
- Upsert with
onConflict: 'template_name,template_version,section_ref,question_number'(per Finding 04 §3.1, existing pattern). - Generate embeddings via
text-embedding-3-large@ 1024 dims. - Wire entity-graph relationships (per Charnwood/SSQ pattern in existing seed-scripts).
- Coverage matching recomputes against
content_items(Finding 04 §4.3 — existing).
§5 — Recommendation
Section titled “§5 — Recommendation”Toolset per step
Section titled “Toolset per step”| Step | Recommended tool | Why | Alternative |
|---|---|---|---|
| 1 — evaluate-form (per format) | Anthropic format-specific doc skills: docx for .docx, xlsx for .xlsx, pdf for .pdf | These skills give the agent the structural primitives (sheet lists, column headers, table extraction, raw XML access) needed to propose a conversion approach. They are NOT thin wrappers; each has bespoke tooling appropriate to its format. The agent’s evaluation output is the prose input to HITL step 2. | Custom KH evaluate-form skill (rejected for v1 — Anthropic skills cover the same surface). |
| 3 — markdown convert | Reusable cocoindex pipeline converter with format-specific @coco.fn per MIME (XLSX via pandas, DOCX via mammoth+Turndown, PDF via Docling, HTML via pullmd) | Memoisation + lineage + freshness integration (Theme D) come from the cocoindex layer. The actual conversion library is format-specific — there is NO “one converter for everything” answer. Reusability across form versions comes from the pipeline shape (input MIME → output markdown sidecar), not from a single conversion tool. | markdown-converter Anthropic skill (uvx markitdown) for one-off conversions or rare formats. Loses memoisation. |
| 4 — classify-form-data | Cocoindex Structured Extraction pattern: @coco.fn(memo=True) wrapping @dataclass TemplateRequirement + instructor + Claude (per paper_metadata example pattern, KH-port to Claude) | Memoised LLM classification with lineage tracking. Output schema is the template_requirements table shape — direct upsert. Generates the seed-script per Finding 04 Rec 5 / N2. | Bespoke KH template-cataloguer/SKILL.md plugin skill (Finding 04 Rec 5 wording) — equivalent surface, different host. Cocoindex framing wins on pipeline integration. |
Top-3 actions
Section titled “Top-3 actions”-
Spike Docling against the two production form fixtures before committing. Run Docling’s
DocumentConverter().convert()on bothCloud Security Principles Checklist V5_3 - PHEW.xlsxandstandard-selection-questionnaire-ppn-03-24.pdf. Score againstmarkdown-converter(uvx markitdown),pdfskill’spdfplumber.extract_tables, and KH’s existingmammoth+Turndown(for DOCX equivalent). Three-way bake-off in the same shape as0.9-spike-S4-pullmd-bakeoff.md. Without this, the recommendation for step 3’s PDF/XLSX branches is unverified. Confidence today on Docling fit: 55% — the vendor claims are plausible but the prior from pullmd’s PDF failure is “be sceptical of vendor marketing on PDF.” -
Build the cocoindex Structured Extraction wrapper as a thin port of
paper_metadata/main.py. Use Claude (not GPT-4o),@dataclass TemplateRequirement,instructorfor JSON mode, KH’s existing prompt patterns (lib/ai/extract-questions.tsalready builds taxonomy-aware prompts). This is the highest-leverage piece of cocoindex doc handling for KH — it directly converts Finding 04 Rec 5 “AI-assisted cataloguing skill” into a memoised + lineage-tracked + freshness-aware pipeline component. Effort: ~1 week (schema + prompt + wrapper + tests). -
Update Finding 06’s per-MIME viewer recommendation to reference cocoindex doc handling explicitly. Currently §3.1-3.8 only weigh in-stack JS primitives (Tiptap, mammoth, react-pdf, exceljs, docx-preview). The §4.1 primary recommendation does not name a markdown-conversion tool for the DOCX→Tiptap path. Recommended update: DOCX path uses cocoindex converter (server-side, Docling OR mammoth+Turndown pending §5 action 1 spike result) → markdown sidecar → Tiptap; PDF path uses cocoindex converter (Docling pending spike) → markdown sidecar → Tiptap edit OR
PdfReaderViewfor read-only fidelity; XLSX path uses cocoindex converter (pandas → GFM markdown) → markdown sidecar → Tiptap. The “zero new dependencies for v1” framing in Finding 06 §4.1 is partly obsoleted if Docling is adopted — Docling is a new dependency with non-trivial deployment cost.
§6 — Open decisions this resolves
Section titled “§6 — Open decisions this resolves”| Open decision | Routing source | Status after this prereq |
|---|---|---|
| Theme B step 1 — evaluate-form skill, one per format type? | feedback-findings-review §1 Theme B | RESOLVED-NEW-DIRECTION. Use Anthropic’s existing format-specific skills (docx, xlsx, pdf) rather than building KH-specific evaluate-form skills. Each Anthropic skill is more powerful than a thin wrapper would be, and they’re already installed in .claude/skills/. KH-specific skills only add value if Anthropic’s skills miss KH-particular shapes (e.g. SSQ section-numbered Q-numbering), which can be added as KH plugin skills on top of the Anthropic baseline rather than replacing it. |
| Theme B step 3 — Structured Outputs skill OR reusable cocoindex pipeline converter? | feedback-findings-review §1 Theme B step 3 | RESOLVED-CONFIRMED. Cocoindex pipeline converter wins. Memoisation + lineage + freshness integration are non-negotiable for the recurring form-extraction workflow. Structured Outputs is a feature of the step 4 classification, not the step 3 conversion. The two get confused because “Structured Outputs” sounds like it covers both — but in cocoindex idiom, structured extraction (paper_metadata pattern) is what consumes markdown, not what produces it. |
| Theme B step 4 — classify-form-data tool? | feedback-findings-review §1 Theme B step 4 + Finding 04 Rec 5 + 00-synthesis N2 | RESOLVED-NEW-DIRECTION. Cocoindex Structured Extraction pattern (paper_metadata-style, ported to Claude) is the right host. Finding 04 Rec 5 / N2 framed it as a “plugin skill” — cocoindex’s @coco.fn(memo=True) wrapping a @dataclass schema is a strictly better host than a Claude plugin skill because it inherits memoisation, lineage, and freshness from the framework. The plugin-skill framing in N2 is obsolete; cocoindex is the host. |
| Finding 06 DOCX viewer rewrite | feedback-findings-review §2.6 Finding 06 + 06-cx33-source-doc-explorer §3.5 + §5.2 | STILL-OPEN pending §5 action 1 spike. If Docling beats KH’s mammoth+Turndown on the SSQ + Cloud Security fixtures, swap to cocoindex+Docling converter. If it doesn’t, keep mammoth+Turndown and just wrap it in a @coco.fn. Finding 06’s “zero new dependencies for v1” claim is conditional on this spike outcome. |
| Finding 06 PDF viewer rewrite | feedback-findings-review §2.6 Finding 06 + 06-cx33-source-doc-explorer §3.3 + §5.3 | RESOLVED-NEW-DIRECTION (subject to §5 action 1 spike). PDF viewer becomes: cocoindex+Docling pipeline → markdown sidecar → Tiptap (read or edit). Removes the “PDF round-trip is out of scope” framing from Finding 06 §5.3 because the round-trip is now markdown↔markdown, not PDF↔PDF. The PDF binary stays as the “frozen original” per Finding 02 sidecar pattern. |
| Finding 06 XLSX viewer rewrite | feedback-findings-review §2.6 Finding 06 + 06-cx33-source-doc-explorer §3.4 + §5.4 | RESOLVED-NEW-DIRECTION. XLSX path no longer needs a “spreadsheet viewer” primitive. Cocoindex converter (pandas → GFM markdown) produces a markdown sidecar that Tiptap renders. The Finding 06 §5.4 framing — “the user needs the form-fill UI from the Wave 1B-3 forms / coverage stream, not a cell-grid” — still holds for editing, but for viewing the markdown sidecar covers it. Interim CTA per Finding 06 OQ-CX33-D stays for v1, replaced by markdown view post-cocoindex-converter. |
| Theme G doc-skills evaluation | feedback-findings-review §1 Theme G | RESOLVED-NEW-DIRECTION. The Anthropic doc skills (docx, xlsx, pdf, markdown-converter) are PART OF the form-extraction pipeline (step 1 evaluate-form) but DO NOT cover step 3 (markdown convert) or step 4 (classify) by themselves. The full pipeline composition is Anthropic-skills-for-step-1 + cocoindex-converter-for-step-3 + cocoindex-structured-extraction-for-step-4. None of these tools replaces another; they layer. |
| N2 cataloguer skill build-now-or-defer | 00-synthesis N2 + Finding 04 Rec 5 | STILL-OPEN — recommend build-now (per §5 action 2). Build the cocoindex Structured Extraction wrapper for TemplateRequirement as the form pipeline’s step 4. Effort ~1 week. Gates only on §5 action 1 spike for the upstream step 3 conversion path. |
| OQ-Q35-B cataloguer-skill output shape | Finding 04 §8 OQ-Q35-B | RESOLVED-CONFIRMED (per 00-synthesis §2.4 recommendation): generated scripts/catalogue-<slug>.ts seed-script-and-commit. Cocoindex Structured Extraction outputs the TemplateRequirement dataclass list; a small post-processor writes the seed-script. |
| PDF→Markdown example vs skill-seekers | feedback-findings-review §3.2 cocoindex input matrix | STILL-OPEN pending §5 action 1 spike. Both Docling (cocoindex) and skill-seekers’ pdf_scraper.py claim superior PDF structure preservation vs KH’s current unpdf. Neither has been bake-off-tested against the SSQ fixture. The pullmd spike (0.9-Spike-S4) tested only pullmd’s PDF path (failed). Recommend including skill-seekers as a third path in the §5 action 1 spike. |
| Structured Extraction capability | feedback-findings-review §3.2 cocoindex input matrix | RESOLVED-CONFIRMED. Cocoindex Structured Extraction is real and viable — three working examples (paper_metadata, patient_intake_extraction_baml, patient_intake_extraction_dspy). The framework provides memoisation + lineage; the actual LLM call uses a bring-your-own library (instructor recommended, BAML or DSPy as alternatives). Schema is plain Python @dataclass or Pydantic. Failure mode is library-dependent. |
| Entity Resolution capability | feedback-findings-review §3.2 cocoindex input matrix | OUT OF SCOPE FOR THIS PREREQ. No cocoindex example covers entity resolution directly. Theme A form-type generalisation (Q&A pairs being form-agnostic) is the only place this matters for the Theme B pipeline, and it’s resolved by the ontology prereq, not by cocoindex doc handling. |
| CocoInsight / Persistent Pipeline (Theme D freshness) | feedback-findings-review §3.2 cocoindex input matrix | OUT OF SCOPE FOR THIS PREREQ. Theme D freshness affects how often step 3 conversion + step 4 classification re-run, but doesn’t change which tools do the work. Covered by prereq 2b (operational cocoindex) per feedback-findings-review.md §1 Theme D routing. |
End of cocoindex doc handling prereq (2c). Three actions surfaced for parent session: (1) Docling+skill-seekers+mammoth bake-off on SSQ + Cloud Security fixtures, (2) build cocoindex Structured Extraction wrapper for TemplateRequirement (~1 week), (3) update Finding 06 per-MIME viewer recommendations to reference cocoindex converter explicitly. Twelve open decisions disposed: 7 RESOLVED, 4 STILL-OPEN-PENDING-SPIKE, 1 OUT-OF-SCOPE. Confidence on the recommendation: 75% — the framing (Anthropic skills for step 1 + cocoindex pipeline for step 3 + cocoindex Structured Extraction for step 4) is sound; the 25% drag is Docling fidelity on KH’s actual fixtures being unverified.