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

1"""What the overlap matcher decides about each table on a page. 

2 

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. 

8 

9The seven kinds correspond one-to-one to the fusion matrix: 

10 

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""" 

27 

28from __future__ import annotations 

29 

30from dataclasses import dataclass, field 

31from typing import TYPE_CHECKING, List, Literal 

32 

33from pydantic import BaseModel, Field 

34 

35if TYPE_CHECKING: 

36 from docling_core.types.doc.document import DoclingDocument 

37 

38 from quber.core.extractors.base import CellFlag, ExtractedTable 

39 from quber.core.parsers import ParseResult 

40 

41MatchKind = Literal[ 

42 "replace", 

43 "som_merged", 

44 "docling_undercount", 

45 "image_table", 

46 "chart", 

47 "docling_miss", 

48 "som_miss", 

49] 

50 

51 

52class RegionMatch(BaseModel): 

53 """One reconciliation outcome for a region on a page. 

54 

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. 

59 

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 """ 

66 

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") 

80 

81 

82@dataclass 

83class FusionResult: 

84 """The two corrected outputs of one fusion. 

85 

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 """ 

93 

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