# Cell status glossary

Every non-empty cell of a table's `corrected_grid` carries a `status` naming
why it does or does not have a measured box, when the table's structure
correction was accepted. A table whose correction was skipped or rejected
carries its raw grid as `corrected_grid`, and every cell's status there is
empty. Statuses are assigned in two steps. First, deterministically at
grounding time (`classify_cells` in `merge_grounding.py`): the deciding test
is whether the cell's text exists in the table's source — the Camelot grid
plus the region's text layer. That
assignment is a hypothesis from text evidence. Second, the status inspector
(`agents/status_inspector.py`, run by `inspection.py`) LOOKS at the table
image and verifies each gap cell's proposed condition; whatever it cannot
positively confirm downgrades to `unverified`. Proposed statuses stand
unconfirmed, with nothing downgraded, in two cases: with the inspector disabled
(`QUBER_STATUS_INSPECTOR_BACKEND=off`), and for a table `quber fuse` produces
by splitting a merged region (`split.py`), which is grounded and classified but
never inspected. The gate is deterministic and only
ever downgrades — no agent answer can move a cell to a better status.
A confirmed or refuted cell carries the inspector's one-line evidence in
`note`.

The statuses fall in three tiers. PASS: expected behavior, nothing to
review. REVIEW: shown to the user when the table is consumed, but not a
defect. DEFECT: the page positively contradicts the extraction. Catalog
case numbers refer to `docs/CELL_STATUS_CASES.pdf`, the before-and-after
walkthrough of each condition on real documents.

Every code names the observed condition, in the vocabulary of the case
catalog, so a reader can tell what happened on the page without decoding
project shorthand.

| Status | Catalog case | Tier | Observed condition |
| --- | --- | --- | --- |
| `reconciled` | — | Pass | The cell's text tied back to a printed source and carries that source's measured coordinates. Trust the geometry. |
| `header_printed_unlocated` | 1 | Pass | Header or band text is printed on the page but its coordinates could not be measured (mid-table period bands, interleaved side-by-side stacks). Text correct, geometry missing. |
| `label_printed_unlocated` | 2 and 3 | Pass | A row label is printed on the page, usually wrapped across printed lines (including repeated twins), but its coordinates could not be measured. |
| `single_character` | 7 | Pass | One-character text (a bare `$`) that no pass boxed. Most span-based passes never match text that short, by design: a bare symbol or digit would anchor inside any unrelated number. A one-character cell that whole-cell alignment or the header column-stack closure boxes reads `reconciled`. |
| `total_label_added` | 5 | Review — not a defect | The totals row is printed with no label; the extraction wrote `Total` (an authorized label, `AUTHORIZED_LABELS` in `base.py`). The author could have intended something else. |
| `header_label_added` | 6 | Review — not a defect | The table is printed with no header over this column; the extraction wrote a generic column name (`Item`/`Description`). The author could have intended something else. |
| `unverified` | — | Review — open question | The condition could not be confirmed: unverifiable text outside the authorized conventions, or an image the inspector could not settle. The catch-all: uncataloged failure modes land here by default. |
| `defect` | — | Defect | The inspector positively observed the page showing something OTHER than what the extraction recorded. The evidence names what the page shows. |
| `header_text_dropped` | 4 | Review — open question | Table-level, not a cell: text printed in the table's header area appears in no output cell (`ExtractedTable.dropped_text`). Usually surrounding page text the correction rightly excluded, but surfaced so an omission is never silent. |

## Flags that are not cell statuses

`CELL_STATUS_REFERENCE` in `base.py` is the full catalogue, and six of its
codes name something other than a cell of a `corrected_grid`: the table-level
`header_text_dropped`, listed above because it has a catalog case, and the
five below. They are listed here because `base.py` points at this file as the
complete glossary. Each
names the element it flags, so a consumer knows what the code is attached to.

| Code | Attached to | Tier | Observed condition |
| --- | --- | --- | --- |
| `footnote_unresolved` | A marker, not a cell | Review — open question | A footnote marker is printed on a table cell or a figure's label and no tier found the note it points at: not the pairs read off the image, not the reading-order scan, not the demand-driven lookup. The element states a figure the document qualifies somewhere and the qualification is missing, which read alone is indistinguishable from a clean reading. |
| `footnote_marker_unplaced` | A table, not a cell | Review — open question | The correction agent read a footnote reference marker off the table image but could not tie it to a carrying cell, or the position it reported failed validation. The marker is real and only its cell is unknown, so its footnote attaches at table level rather than to a row. |
| `footnote_unreferenced` | A note, not a cell | Catalogued, not surfaced | A note printed for a table or figure that no marker on it names. Judged within one element type today, so a note a table claims can still read as unreferenced by a figure on the same page, and the reverse. It becomes meaningful once every reader's claims are pooled and the leftovers reported once per document. |
| `value_misread` | A figure value | Defect | A chart value whose page fragment at that position states a different number than the reading. One of the two is wrong and the position pins where to look, so the value never passes silently. Carries the fragment's box. |
| `value_unreconciled` | A figure value | Review — open question | A chart value present in one reading with no counterpart in the other. The note states the direction: a scan-only value the local read did not corroborate, or a locally read value the scan omitted. The value stands unverified and is surfaced rather than passed. |

None of these five carries a catalog case number, because
`docs/CELL_STATUS_CASES.pdf` walks through the cell conditions only.

Empty cells carry no status — they have no printed mark and are not gaps.

A pass status is not an assumption when the inspector runs: cases 1–3 keep
their pass tier only because the inspector confirmed the condition on the
image; a proposal the image cannot settle is `unverified`, and one the image
contradicts is `defect`. With the inspector disabled, the classifier's pass
statuses stand unconfirmed. The inspector is never forced to pick the nearest
option when none fits. Case 4's dropped group label is not a cell that exists,
so no cell can carry it — it is recorded table-level in
`ExtractedTable.dropped_text` and flagged as `header_text_dropped`. The model
carries such group labels inconsistently, so this deterministic check is the
reliable net.

The review channel is the flags record: every status registered for
inspection, plus each table's dropped text, is emitted to
`<base>.flags.json` (`cell_flags` in `base.py`) and rendered in the
`<base>.flags.html` review document under `--review`. The run log WARNs
only for defects and for operational failures (inspection could not run);
everything expected or already flagged traces at DEBUG.

The status lives on `GroundedCell.status` (`core/extractors/base.py`) and
rides through the serialized table record, so any consumer of the output
JSON can read it without re-deriving anything.

For a front end, the machine-readable registry is `CELL_STATUS_REFERENCE`
in `core/extractors/base.py`: one entry per code with a display label, a
description, and an `inspect` flag marking the codes to surface to the
user. `GroundedCell` validates its `status` against this registry, so a
lookup table keyed on `code` can never meet an unknown value.
