Agentic-Service-Data-Eyond-Catalog / docs /knowledge /TABLE_BRANCH_PROPOSAL.md
Rifqi Hafizuddin
Claude Opus 5
[NOTICKET] fix: add a fault-code label, measured; re-diagnose the Komatsu tables
366697c
|
Raw History Blame
11 kB
# Table branch β€” design proposal (Step 8)
<!-- doc-status -->
> πŸ’‘ **PROPOSAL β€” nothing is built from this.** Written 2026-09-11 against the open queue in
> `knowledge_pipeline_runs/SESSION_HANDOFF.md` Β§4.6. Needs Rifqi's sign-off before any code.
> Roll-up: [DEV_PLAN.md](../../DEV_PLAN.md) Β§0.8 Β· built state: [REPO_STATUS.md](../../REPO_STATUS.md) Β§14.
**Owner:** Rifqi Β· **Size:** weeks Β· **Depends on:** parsing 0.4.0 (shipped), genre classification
(shipped 2026-09-11, advisory) Β· **Blocks:** any useful result on diagnostic documents.
---
## 1 Β· The problem, stated as a number
The Komatsu HD785-7 troubleshooting manual is **74% tabular** β€” 59 tables across 80 chunks β€” and the
pipeline extracts **zero** knowledge entries from it. Two paid runs, v1 and v2, both returned **0
definitions** from 75 and 93 clusters respectively. That is not a tuning failure and no prompt change
reaches it: a shop manual does not define its terms, it **asserts relationships between them**, and
it does so in a matrix.
The genre classifier (`src/knowledge_extraction/genre.py`) now names this document `diagnostic` and
reports `well_served = False` before anything is spent. This proposal is what would make that answer
change.
## 2 Β· What the document actually contains
A troubleshooting table is a **symptom Γ— cause matrix**. Each marked cell is one assertion:
| Symptom \ Cause | Fuel filter clogged | Injector worn | Air cleaner blocked |
|---|---|---|---|
| Engine does not start | ● | ● | |
| Black exhaust smoke | | ● | ● |
| Output drops | ● | | ● |
Three assertions come out of the first row alone, and **none of them is a definition**:
> *"Engine does not start" may be caused by "Fuel filter clogged".*
The unit of knowledge here is the **(row header, column header, cell mark) triple**, and it is
recoverable **deterministically** β€” the mark is in the parsed HTML, the headers are in the parsed
HTML, nothing needs to be inferred.
## 3 Β· The shape of the branch
**Deterministic extraction, LLM only to phrase.** This is the inversion of every existing branch, and
it is the whole point: the assertion's *existence* is read off the table, so the model cannot
fabricate a relationship that is not in the document β€” it can only phrase one that is.
```
table chunk ──► parse table_html ──► (row header, col header, cell mark) triples
β”‚
β”œβ”€β”€ deterministic: the assertion EXISTS
β”‚
β–Ό
one LLM call per TABLE (not per cell)
β”‚
β–Ό
natural-language phrasing + typing
β”‚
β–Ό
span check against the cell's own headers
```
**Provenance points at the cell**, which is stronger than anything the pipeline produces today: the
span is the row header plus the column header, both verbatim document text, both locatable by the
existing `span_present`.
### 3.1 Β· New entity
```python
class RelationEntry(BaseModel):
relation_id: str # sha256 over (doc_id, table asset_id, row, col) β€” positional
# within a table, but the table's asset_id is content-addressed,
# so a re-parse of identical bytes re-mints identical ids.
kind: Literal["symptom_cause", "component_action", "condition_effect"]
subject: str # verbatim row header
object: str # verbatim column header
statement: str | None # model-phrased; span-checked against subject + object + caption
table_asset_id: str
provenance: Provenance # span = the two headers; page from the chunk
term_ids: list[str] # links into the glossary where headers match known terms
```
### 3.2 Β· Why one call per table, not per cell
Komatsu's 59 tables carry on the order of 10Β³ marked cells. One call per cell is a different cost
class entirely and would blow the pre-flight ceiling by itself. One call per table hands the model
the linearised table plus the extracted triples and asks it to phrase each one β€” 59 calls, which is
**less than the 93 the glossary branch currently spends to abstain 93 times.**
## 4 Β· What has to be decided before coding
| # | Question | Why it blocks |
|---|---|---|
| T1 | Is a relation a first-class **entry kind** (a fifth `kind` in `knowledge_entries`) or a new table? | Persistence contract + Harry's migration. A fifth kind is additive; a table is a DDL handoff |
| T2 | What does a reviewer **do** with 1,000 relations? | The review queue is frequency-sorted and finishable by construction. 1,000 relations is not finishable. Likely answer: review per TABLE, not per relation β€” one approve covers the matrix |
| T3 | Does a mark type carry meaning (`●` vs `β—‹` vs `β–²`)? | Komatsu uses more than one glyph. If they mean "primary/secondary cause" the triple is really a quad, and getting this wrong silently inverts assertions |
| T4 | Which `table_html` shapes does the parser actually emit β€” merged cells, multi-row headers? | Determines whether header resolution is 20 lines or a small parser. **Measure before designing** |
| T5 | Does this ship behind the genre router (`diagnostic` only) or for every document? | BUMA is 25% tabular and its tables are *data*, not relation matrices. Running this on them produces noise |
**T4 is the one to do first** and it is free: inspect the 59 Komatsu tables and count the shapes.
## 5 Β· Kill lines, agreed before the first paid run
Copying the discipline that made the clustering defect findable β€” a metric that moves the *wrong* way
is worse than no metric (E2), so these are stated as recall against a **human-read sample**:
| Metric | What it measures | Kill line |
|---|---|---|
| **T-E1** | Triples extracted Γ· triples a human reads off the same 5 tables | β‰₯ 0.90 β€” it is deterministic; anything less is a parser bug |
| **T-E2** | Phrasings whose span check passes | β‰₯ 0.95 |
| **T-E3** | Phrasings a human judges to *invert or distort* the relation | 0 β€” this is the one that matters, and it is the reason the mark glyph question (T3) is not cosmetic |
| **T-cost** | Calls per document | ≀ 1 per table |
**T-E3 has no automated proxy.** It needs Mas Beta or an equivalent reader on a sample, which makes
the R4 conversation (does "found but undefined" help an expert?) worth having in the same sitting.
## 6 Β· What this does NOT solve
A relation matrix is not a glossary. After this branch ships, the Komatsu manual still yields **zero
definitions** β€” correctly, because it contains none. If the product's promise is "a glossary from
your documents", a diagnostic document will always disappoint it, and the honest place to say so is
the ingest response, which the genre classifier now makes possible.
---
## 7 Β· Groundwork already landed (2026-09-11)
Two pieces of this branch's plumbing exist before the branch does, and both are measured:
**The pointer is populated and durable.** 0.4.0 gives every table chunk an `Asset` whose id comes
from `synthetic_asset_id(doc_id, chunk_id, item_index)` β€” stable document coordinates, so it survives
a re-parse β€” plus `referenced_by` naming the prose chunk that introduces it, and the reverse pointer
on that prose chunk's `assets`.
**One linked partner now joins the evidence window** (`EVIDENCE_LINK_PARTNERS = 1`, in `rank/evidence.py`).
The measurement that motivated it: across 603 clusters on the three test documents, **zero** had both
sides of a linked pair in their top-5, so a table reached the model without the prose naming it, and
vice versa. 31/83, 55/93 and 70/427 clusters now gain a partner, at +1.6%/+4.1%/+4.2% of prompt spend.
**What this does *not* do, and the branch must:** `table_html` crosses the seam into
`Chunk.table_html` and **nothing reads it** β€” the prompt shows the linearised `render_table` text
from `Chunk.text` instead. That is deliberate: the span check validates against `heading + text`
only, so putting HTML in front of the model would let it quote markup that is then nulled as
unlocatable. A branch that wants the real cell structure has to extend the span-checked text too, or
carry provenance that points at a cell rather than a character span (Β§3.1's `cell_ref` does the
latter, which is the cleaner of the two).
---
## 8 Β· Correction and re-diagnosis of the Komatsu tables (2026-09-11)
> **Correction.** An earlier reading of this corpus (same day, recorded in the session
> notes) said the troubleshooting charts had **lost their column headers**, on the
> evidence that only 4 of 59 tabular chunks contain a `<th>`. That count is accurate
> and the conclusion drawn from it was **wrong**. The headers are present; they are
> emitted as ordinary `<td>` cells in a **separate chunk**.
### What Mistral actually produces
Each chart is split into three or four sibling tables, and the first cell of each names
the fragment. Across the 59 tabular chunks:
| first cell | count | what it holds |
|---|---|---|
| `Causes` | 17 | **the column headers** β€” the cause list |
| `Troubleshooting` | 16 | check rows |
| `Questions` | 15 | interview/symptom rows |
| `Remedy` | 10 | the action row |
So the (row header, column header, cell mark) triple is recoverable, but its three parts
live in **different chunks**. The branch's first job is re-assembly, not OCR recovery β€”
the fragments are adjacent, same-page, and regularly labelled.
### The defect that is real
**Marks survive in only 20 of 59 tabular chunks.** Doc-wide there are 278 marks
(18 β—Ž, 137 β—‹, 25 β–³, 98 ●), and they cluster on the pages where the matrix fragment
kept them; on pages 9-10 the `Questions` and `Troubleshooting` fragments come through
with row labels and **zero marks**. Column structure and `rowspan`/`colspan` are
preserved where the marks are.
A chart whose marks were dropped carries no symptom-to-cause link at all, so roughly
two thirds of the document's assertions are currently unrecoverable from the artifact.
**That, not the headers, is what gates this branch**, and it has to be fixed at the
parsing layer.
### Free evidence that the content is reachable
Adding a `fault code` label to the span filter (see `config/labels.yaml`) surfaced
**66 cause phrases** as clusters on this document β€” *"Clogged fuel filter and/or
strainer"*, *"Improper injection timing"*, *"Defective alternator (regulator section)"* β€”
plus 17 service codes. Those are the column headers, read out of the `Causes`
fragments. The raw material for the branch is already in the artifact and already
findable; what is missing is the mark matrix that connects it to the symptoms.