Coverage for src / quber / core / fusion / models.py: 100%
22 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
1"""What the overlap matcher decides about each table on a page.
3Fusion matches the Set-of-Mark/Camelot tables against docling's tables and
4pictures one page at a time, by bounding-box overlap. A `RegionMatch` records
5one outcome: which SoM tables and which docling items overlapped, and what that
6cardinality means. The graft and annotation passes act on these records; they
7make no geometric decisions of their own.
9The seven kinds correspond one-to-one to the fusion matrix:
11- `replace` — one SoM table over one docling table, both agree. The Camelot body
12 replaces the docling body; the SoM table is unchanged.
13- `som_merged` — more docling tables than SoM tables in the region: SoM fused
14 stacked tables. `split_table` splits the SoM table; the splits graft in.
15- `docling_undercount` — more SoM tables than docling tables: docling dropped or
16 merged a complex table. The docling object is split and amended up to Camelot.
17- `image_table` — a docling table with no text layer under it (read by OCR,
18 Camelot empty): a real table rendered as an image. Docling's OCR'd table is
19 kept; the SoM table is annotated as an image table.
20- `chart` — a SoM table over a docling picture and no docling table: SoM mistook
21 an image for a table. Nothing grafts; the SoM table is annotated as a chart.
22- `docling_miss` — a SoM table over nothing in docling: docling missed it. The
23 Camelot table is inserted into the document.
24- `som_miss` — a docling table no SoM table matched: SoM missed a table docling
25 found. Reported as an error; nothing is synthesized from docling.
26"""
28from __future__ import annotations
30from dataclasses import dataclass, field
31from typing import TYPE_CHECKING, List, Literal
33from pydantic import BaseModel, Field
35if TYPE_CHECKING:
36 from docling_core.types.doc.document import DoclingDocument
38 from quber.core.extractors.base import CellFlag, ExtractedTable
39 from quber.core.parsers import ParseResult
41MatchKind = Literal[
42 "replace",
43 "som_merged",
44 "docling_undercount",
45 "image_table",
46 "chart",
47 "docling_miss",
48 "som_miss",
49]
52class RegionMatch(BaseModel):
53 """One reconciliation outcome for a region on a page.
55 `som_indices` index into the Set-of-Mark `ExtractedTable` list handed to the
56 matcher. `docling_table_refs` / `docling_picture_refs` are docling
57 `self_ref` strings. The combination of which lists are populated and `kind`
58 drives the graft and annotation passes.
60 After grafting, `docling_table_refs` reference the *unified* document:
61 the graft deletes and inserts tables, which renumbers every later table's
62 `self_ref`, so it rewrites each match's refs to the grafted tables' final
63 positions. A consumer resolving these refs against the unified document
64 (the report always ships beside it) lands on the table the match is about.
65 """
67 page: int
68 kind: MatchKind
69 som_indices: List[int] = Field(default_factory=list)
70 docling_table_refs: List[str] = Field(default_factory=list)
71 docling_picture_refs: List[str] = Field(default_factory=list)
72 picture_classes: List[str] = Field(
73 default_factory=list,
74 description="Predicted classes of the matched docling pictures (for chart kind)",
75 )
76 overlap: float = Field(
77 default=0.0, description="Representative box-overlap coverage for the region (0..1)"
78 )
79 detail: str = Field(default="", description="Human-readable note on the match")
82@dataclass
83class FusionResult:
84 """The two corrected outputs of one fusion.
86 `document` is the unified `DoclingDocument`: docling's spine with the Camelot
87 table bodies grafted in. `tables` is the corrected Set-of-Mark/Camelot output:
88 merged regions split, charts and image tables annotated via `ExtractedTable.kind`.
89 `matches` is the final per-region classification (after any split), and
90 `errors` surfaces the regions that could not be fused (a table docling found
91 that SoM missed, or a merge the split pass declined).
92 """
94 document: "DoclingDocument"
95 tables: List["ExtractedTable"]
96 matches: List[RegionMatch] = field(default_factory=list)
97 errors: List[str] = field(default_factory=list)
98 # Headings the review demoted to page decoration, one flag per heading —
99 # written into the run's flags artifact alongside the cell flags.
100 heading_flags: List["CellFlag"] = field(default_factory=list)
101 # The document extraction the fusion consumed. Carried so a caller that ran
102 # the parse inside fuse can persist the parse-side artifacts — above all
103 # the positioned page cells, which the figure stage's value reconciliation
104 # requires beside the parse it is handed.
105 parse: "ParseResult | None" = None