Move bbox.py from domain/ to infra/ and unify coordinate logic
domain/ must be pure with no external dependencies. bbox.py imports docling_core and belongs in infra/. Also refactor ServeConverter to use the canonical to_topleft_list via BoundingBox instead of duplicated manual coordinate conversion. Move docling-core to base requirements since it is now needed in both modes.
This commit is contained in:
parent
6a791cec48
commit
ab6d42aecd
10 changed files with 25 additions and 22 deletions
|
|
@ -48,8 +48,8 @@ document-parser/
|
||||||
├── main.py # FastAPI app, CORS, lifespan
|
├── main.py # FastAPI app, CORS, lifespan
|
||||||
├── domain/ # Pure domain — no HTTP, no DB
|
├── domain/ # Pure domain — no HTTP, no DB
|
||||||
│ ├── models.py # Document, AnalysisJob dataclasses
|
│ ├── models.py # Document, AnalysisJob dataclasses
|
||||||
│ ├── parsing.py # Docling conversion & page extraction
|
│ ├── ports.py # Abstract protocols (converter, chunker)
|
||||||
│ └── bbox.py # Bounding box coordinate normalization
|
│ └── value_objects.py # ConversionResult, PageDetail, ChunkResult
|
||||||
├── api/ # HTTP layer (FastAPI routers)
|
├── api/ # HTTP layer (FastAPI routers)
|
||||||
│ ├── schemas.py # Pydantic DTOs (camelCase serialization)
|
│ ├── schemas.py # Pydantic DTOs (camelCase serialization)
|
||||||
│ ├── documents.py # /api/documents endpoints
|
│ ├── documents.py # /api/documents endpoints
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ document-parser/
|
||||||
|
|
||||||
| Layer | Role | Depends on |
|
| Layer | Role | Depends on |
|
||||||
|-------|------|------------|
|
|-------|------|------------|
|
||||||
| **domain** | Dataclasses, bbox math, Docling conversion | Nothing (pure Python) |
|
| **domain** | Dataclasses, value objects, ports | Nothing (pure Python) |
|
||||||
| **persistence** | SQLite CRUD, aiosqlite | domain (models) |
|
| **persistence** | SQLite CRUD, aiosqlite | domain (models) |
|
||||||
| **services** | Orchestrate use cases, call Docling | domain + persistence |
|
| **services** | Orchestrate use cases, call Docling | domain + persistence |
|
||||||
| **api** | HTTP endpoints, Pydantic DTOs, error handling | services |
|
| **api** | HTTP endpoints, Pydantic DTOs, error handling | services |
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ The frontend converts PDF points to CSS pixels, then the canvas renders at `devi
|
||||||
|
|
||||||
## Transformation 1 — `to_topleft_list()`
|
## Transformation 1 — `to_topleft_list()`
|
||||||
|
|
||||||
**File:** `document-parser/domain/bbox.py`
|
**File:** `document-parser/infra/bbox.py`
|
||||||
|
|
||||||
Normalizes any Docling bbox to `[left, top, right, bottom]` in TOPLEFT coordinates.
|
Normalizes any Docling bbox to `[left, top, right, bottom]` in TOPLEFT coordinates.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from docling_core.transforms.chunker import HierarchicalChunker
|
||||||
from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
|
from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
|
||||||
from docling_core.types.doc.document import DoclingDocument
|
from docling_core.types.doc.document import DoclingDocument
|
||||||
|
|
||||||
from domain.bbox import EMPTY_BBOX, to_topleft_list
|
from infra.bbox import EMPTY_BBOX, to_topleft_list
|
||||||
from domain.value_objects import ChunkBbox, ChunkingOptions, ChunkResult
|
from domain.value_objects import ChunkBbox, ChunkingOptions, ChunkResult
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ from docling_core.types.doc import (
|
||||||
TitleItem,
|
TitleItem,
|
||||||
)
|
)
|
||||||
|
|
||||||
from domain.bbox import to_topleft_list
|
from infra.bbox import to_topleft_list
|
||||||
from domain.value_objects import (
|
from domain.value_objects import (
|
||||||
ConversionOptions,
|
ConversionOptions,
|
||||||
ConversionResult,
|
ConversionResult,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import mimetypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from docling_core.types.doc.base import BoundingBox, CoordOrigin
|
||||||
|
|
||||||
from domain.value_objects import (
|
from domain.value_objects import (
|
||||||
ConversionOptions,
|
ConversionOptions,
|
||||||
|
|
@ -25,6 +26,7 @@ from domain.value_objects import (
|
||||||
PageDetail,
|
PageDetail,
|
||||||
PageElement,
|
PageElement,
|
||||||
)
|
)
|
||||||
|
from infra.bbox import to_topleft_list
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -222,21 +224,22 @@ def _add_element(item: dict, pages: dict[int, PageDetail]) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _extract_bbox(bbox_data: dict, page_height: float) -> list[float]:
|
def _extract_bbox(bbox_data: dict, page_height: float) -> list[float]:
|
||||||
"""Extract and normalize bbox to TOPLEFT [l, t, r, b] format."""
|
"""Extract and normalize bbox to TOPLEFT [l, t, r, b] format.
|
||||||
|
|
||||||
|
Delegates to the canonical to_topleft_list function via a docling-core
|
||||||
|
BoundingBox, ensuring consistent coordinate handling across all converters.
|
||||||
|
"""
|
||||||
if not isinstance(bbox_data, dict):
|
if not isinstance(bbox_data, dict):
|
||||||
return [0.0, 0.0, 0.0, 0.0]
|
return [0.0, 0.0, 0.0, 0.0]
|
||||||
|
|
||||||
left = bbox_data.get("l", 0.0)
|
origin_str = bbox_data.get("coord_origin", "TOPLEFT")
|
||||||
top = bbox_data.get("t", 0.0)
|
origin = CoordOrigin.BOTTOMLEFT if origin_str == "BOTTOMLEFT" else CoordOrigin.TOPLEFT
|
||||||
right = bbox_data.get("r", 0.0)
|
|
||||||
bottom = bbox_data.get("b", 0.0)
|
|
||||||
coord_origin = bbox_data.get("coord_origin", "TOPLEFT")
|
|
||||||
|
|
||||||
if coord_origin == "BOTTOMLEFT":
|
bbox = BoundingBox(
|
||||||
# In BOTTOMLEFT: top has higher y, bottom has lower y
|
l=bbox_data.get("l", 0.0),
|
||||||
# In TOPLEFT: flip both — new_top = page_height - old_top
|
t=bbox_data.get("t", 0.0),
|
||||||
new_top = page_height - top
|
r=bbox_data.get("r", 0.0),
|
||||||
new_bottom = page_height - bottom
|
b=bbox_data.get("b", 0.0),
|
||||||
top, bottom = new_top, new_bottom
|
coord_origin=origin,
|
||||||
|
)
|
||||||
return [left, top, right, bottom]
|
return to_topleft_list(bbox, page_height)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
docling>=2.80.0,<3.0.0
|
docling>=2.80.0,<3.0.0
|
||||||
docling-core>=2.0.0,<3.0.0
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
docling-core>=2.0.0,<3.0.0
|
||||||
fastapi>=0.115.0,<1.0.0
|
fastapi>=0.115.0,<1.0.0
|
||||||
uvicorn[standard]>=0.32.0,<1.0.0
|
uvicorn[standard]>=0.32.0,<1.0.0
|
||||||
python-multipart>=0.0.12
|
python-multipart>=0.0.12
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ misaligned overlays in the UI.
|
||||||
import pytest
|
import pytest
|
||||||
from docling_core.types.doc.base import BoundingBox, CoordOrigin
|
from docling_core.types.doc.base import BoundingBox, CoordOrigin
|
||||||
|
|
||||||
from domain.bbox import EMPTY_BBOX, to_topleft_list
|
from infra.bbox import EMPTY_BBOX, to_topleft_list
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Standard conversions
|
# Standard conversions
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue