File size: 3,260 Bytes
6b8ed55
 
 
 
 
 
 
 
 
bbbfba8
 
6b8ed55
 
 
 
 
 
 
 
 
 
 
 
bbbfba8
 
 
6b8ed55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bbbfba8
 
 
 
6b8ed55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""ViewerProjection builder — converts CanonicalDocument into viewer data.

The ViewerProjection is a lightweight structure that the front-end
consumes directly. It never parses XML. It derives from the canonical
document after validation and enrichment.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from src.app.domain.models.readiness import ExportEligibility
from src.app.domain.models.viewer_projection import InspectionData, OverlayItem, ViewerProjection
from src.app.viewer.overlays import (
    line_to_inspection,
    line_to_overlay,
    non_text_to_overlay,
    region_to_inspection,
    region_to_overlay,
    word_to_inspection,
    word_to_overlay,
)

if TYPE_CHECKING:
    from src.app.domain.models import CanonicalDocument


def build_projection(
    doc: CanonicalDocument,
    page_index: int = 0,
    *,
    export_status: ExportEligibility | None = None,
) -> ViewerProjection:
    """Build a ViewerProjection for a single page.

    Args:
        doc: The canonical document.
        page_index: Which page to project (default: first).
        export_status: Export eligibility to include in the projection.

    Returns:
        A ViewerProjection ready for the front-end.
    """
    if page_index >= len(doc.pages):
        raise ValueError(
            f"Page index {page_index} out of range"
            f" (document has {len(doc.pages)} pages)"
        )

    page = doc.pages[page_index]

    block_overlays: list[OverlayItem] = []
    line_overlays: list[OverlayItem] = []
    word_overlays: list[OverlayItem] = []
    non_text_overlays: list[OverlayItem] = []
    inspection_index: dict[str, InspectionData] = {}
    validation_flags: list[str] = list(page.warnings)

    for region in page.text_regions:
        block_overlays.append(region_to_overlay(region))
        inspection_index[region.id] = region_to_inspection(region)

        for line in region.lines:
            line_overlays.append(line_to_overlay(line))
            inspection_index[line.id] = line_to_inspection(line)

            for word in line.words:
                word_overlays.append(word_to_overlay(word))
                inspection_index[word.id] = word_to_inspection(word)

    for ntr in page.non_text_regions:
        non_text_overlays.append(non_text_to_overlay(ntr))

    image_ref = ""
    if doc.source.filename:
        image_ref = doc.source.filename
    if page.metadata and "image_ref" in page.metadata:
        image_ref = page.metadata["image_ref"]

    return ViewerProjection(
        image_ref=image_ref,
        image_width=int(page.width),
        image_height=int(page.height),
        block_overlays=block_overlays,
        line_overlays=line_overlays,
        word_overlays=word_overlays,
        non_text_overlays=non_text_overlays,
        inspection_index=inspection_index,
        validation_flags=validation_flags,
        export_status=export_status or ExportEligibility(),
    )


def build_all_projections(
    doc: CanonicalDocument,
    *,
    export_status: ExportEligibility | None = None,
) -> list[ViewerProjection]:
    """Build ViewerProjections for all pages."""
    return [
        build_projection(doc, i, export_status=export_status)
        for i in range(len(doc.pages))
    ]