docling-studio/document-parser/domain/value_objects.py
Pier-Jean Malandrino 8694353d8b feat(reasoning): bidirectional PDF ↔ graph focus + DocumentView mode
Propagate Docling `self_ref` through PageElement so bboxes and graph nodes
share a stable identity. Add a Document/Graph mode switch to the reasoning
workspace; selecting a node highlights its bbox (numbered badge, focus ring,
optional dim of non-visited) and clicking a bbox re-centers the graph.
2026-04-29 14:00:00 +02:00

98 lines
2.7 KiB
Python

"""Domain value objects — pure data structures for document conversion.
These types define the contract between the domain and infrastructure layers.
They have ZERO external dependencies (no docling, no HTTP, no DB).
"""
from __future__ import annotations
from dataclasses import dataclass, field
# US Letter page dimensions (points) — fallback when page size is unknown
DEFAULT_PAGE_WIDTH: float = 612.0
DEFAULT_PAGE_HEIGHT: float = 792.0
@dataclass(frozen=True)
class PageElement:
type: str
bbox: list[float]
content: str
level: int = 0
# Docling `self_ref` ("#/texts/12", "#/tables/3", …). Empty for items
# that don't have one (rare — defensive default). Lets callers correlate
# a rendered bbox with the corresponding node in the graph without
# resorting to fuzzy bbox matching.
self_ref: str = ""
@dataclass(frozen=True)
class PageDetail:
page_number: int
width: float
height: float
elements: list[PageElement] = field(default_factory=list)
@dataclass(frozen=True)
class ConversionOptions:
do_ocr: bool = True
do_table_structure: bool = True
table_mode: str = "accurate"
do_code_enrichment: bool = False
do_formula_enrichment: bool = False
do_picture_classification: bool = False
do_picture_description: bool = False
generate_picture_images: bool = False
generate_page_images: bool = False
images_scale: float = 1.0
def is_default(self) -> bool:
"""Return True if all options match their defaults."""
return self == ConversionOptions()
@dataclass(frozen=True)
class ConversionResult:
page_count: int
content_markdown: str
content_html: str
pages: list[PageDetail]
skipped_items: int = 0
document_json: str | None = None
@dataclass(frozen=True)
class ChunkingOptions:
chunker_type: str = "hybrid" # "hybrid", "hierarchical", "page"
max_tokens: int = 512
merge_peers: bool = True
repeat_table_header: bool = True
def is_default(self) -> bool:
"""Return True if all options match their defaults."""
return self == ChunkingOptions()
@dataclass(frozen=True)
class ChunkBbox:
page: int
bbox: list[float] # [left, top, right, bottom] in TOPLEFT origin
@dataclass(frozen=True)
class ChunkDocItem:
"""Source element referenced by a chunk. Enables Neo4j DERIVED_FROM edges."""
self_ref: str
label: str
@dataclass(frozen=True)
class ChunkResult:
text: str
headings: list[str] = field(default_factory=list)
source_page: int | None = None
token_count: int = 0
bboxes: list[ChunkBbox] = field(default_factory=list)
doc_items: list[ChunkDocItem] = field(default_factory=list)