Coverage for src / quber / core / figures / dpt3 / models.py: 100%
49 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 dpt-3 track's digestion produces: one record per page, figure and table.
3A `PageDigest` is one scanned page read out of a dpt-3 response. It holds more
4than the graft takes: the graft receives each figure's text, id and rectangle,
5and everything else stays here for the consumers built after the track — the
6axis and legend checks, estimate classification, and crop-level disagreement
7checks.
9A `DigestedFigure` keeps the response's own separation of a figure into what
10kind of figure it is, what the model said about it, and the values it read as a
11small table. Each value cell carries a provenance marker separating a number
12printed on the page from one estimated off the drawing's geometry; digestion
13writes every cell `unclassified`, and classifying them is a later change. A
14resolved footnote attaches to the specific cell its marker's label matches, so
15an annotation qualifies the value it is printed on rather than the whole chart.
17A `DigestedTable` is an image table with the response's own cell grid: every
18cell arrives with its row, column and rectangle, which is what lets a
19disagreement check read a crop of one cell instead of the whole page.
20"""
22from __future__ import annotations
24from typing import List, Literal, Optional
26from pydantic import BaseModel, Field
28from quber.core.figures.models import Box
30#: Whether a figure value is printed on the page or estimated from the drawing.
31#: Digestion writes every value `unclassified`; classification is a later step.
32Provenance = Literal["printed", "estimate", "unclassified"]
35class PageDigest(BaseModel):
36 """One scanned page, digested from a dpt-3 response."""
38 page: int = Field(description="1-based source page the submission came from")
39 job_id: Optional[str] = Field(default=None, description="The scan job that produced the response")
40 model: Optional[str] = Field(default=None, description="The model requested for the scan")
41 version: Optional[str] = Field(default=None, description="The model version the response reported")
42 credits: Optional[float] = Field(default=None, description="Credits the scan was billed")
43 figures: List[DigestedFigure] = Field(default_factory=list)
44 tables: List[DigestedTable] = Field(default_factory=list)
45 context: List[DigestedText] = Field(
46 default_factory=list,
47 description="The page's other nodes — text and page furniture — kept as context",
48 )
51class DigestedFigure(BaseModel):
52 """One figure node, with the parts the response separates kept separate."""
54 page: int = Field(description="1-based source page the figure is printed on")
55 kind: str = Field(
56 default="",
57 description="What the response typed the figure as: chart, diagram, illustration",
58 )
59 description: str = Field(default="", description="The model's description blocks, in order")
60 box: Optional[Box] = Field(default=None, description="Normalized box around the figure")
61 values: List[FigureValueTable] = Field(
62 default_factory=list,
63 description="The value tables the model read off the figure, as returned",
64 )
65 footnotes: List[ResolvedNote] = Field(
66 default_factory=list,
67 description="The figure's footnotes once resolution has run; empty before it",
68 )
69 node_id: Optional[str] = Field(default=None, description="The response's own id for the node")
72class DigestedTable(BaseModel):
73 """One table node, as a dense grid with the response's box on every cell."""
75 page: int = Field(description="1-based source page the table is printed on")
76 cells: List[List[str]] = Field(description="The grid, rows of cell text")
77 cell_boxes: List[List[Optional[Box]]] = Field(
78 description="Normalized box per cell, shaped exactly like `cells`; absent where none was returned"
79 )
80 box: Optional[Box] = Field(default=None, description="Normalized box around the whole table")
81 node_id: Optional[str] = Field(default=None, description="The response's own id for the node")
84class DigestedText(BaseModel):
85 """A text or page-furniture node, kept as returned and attached to nothing."""
87 page: int = Field(description="1-based source page the text is printed on")
88 kind: str = Field(description="The node type as returned: text, marginalia, logo")
89 text: str = Field(description="The node's text, exactly as returned")
90 box: Optional[Box] = Field(default=None, description="Normalized box around the text")
91 node_id: Optional[str] = Field(default=None, description="The response's own id for the node")
94class FigureValueTable(BaseModel):
95 """One value table read off a figure. The rows are kept exactly as returned;
96 no shape is imposed, because two charts in one response lay their values out
97 differently and a fixed reading of one layout misreads the next."""
99 rows: List[List[FigureValueCell]] = Field(default_factory=list)
102class FigureValueCell(BaseModel):
103 """One cell of a figure's value table."""
105 text: str = Field(description="The cell's text, exactly as returned")
106 provenance: Provenance = Field(
107 default="unclassified",
108 description="Printed on the page, estimated from the drawing, or not yet classified",
109 )
110 footnotes: List[PlacedNote] = Field(
111 default_factory=list,
112 description="Resolved notes whose marker sits on this cell's label",
113 )
116class PlacedNote(BaseModel):
117 """One resolved footnote attached to the specific value its marker annotates."""
119 marker: str = Field(description="The marker as printed on the label")
120 text: str = Field(description="The resolved note text")
123class ResolvedNote(BaseModel):
124 """One footnote resolution outcome for a figure.
126 `sources` names where the text came from: `agent` for the correction
127 agent's read of the page image, `document` for the parse's own footnote
128 text on the page, `headings` for a section reference resolved to a heading
129 pointer. Two sources that agree corroborate each other; a disagreement is
130 carried as a flag by the run, never silently settled.
131 """
133 marker: str = Field(default="", description="The marker as printed; empty for an unmarked note")
134 text: str = Field(description="The note text a reader should see")
135 sources: List[str] = Field(default_factory=list)
136 corroborated: bool = Field(
137 default=False, description="True when the agent read and the document text agree"
138 )
139 placed: bool = Field(default=False, description="True when the note attached to a specific value cell")