File size: 3,257 Bytes
e2ec8a2
 
 
 
 
 
 
 
 
bbbfba8
e2ec8a2
 
 
 
bbbfba8
e2ec8a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3213f50
e2ec8a2
3213f50
e2ec8a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Document policy — centralised business rules for the pipeline.

This layer prevents critical decisions from being scattered across
adapters, validators, and serializers.  A policy is a named configuration
that controls what the system may or may not do.
"""

from __future__ import annotations

from enum import StrEnum

from pydantic import BaseModel, ConfigDict


class PolicyMode(StrEnum):
    """Named policy presets."""

    STRICT = "strict"
    STANDARD = "standard"
    PERMISSIVE = "permissive"


class DocumentPolicy(BaseModel):
    """Concrete policy controlling pipeline behaviour."""

    model_config = ConfigDict(frozen=True)

    mode: PolicyMode = PolicyMode.STANDARD

    # -- Text rules -----------------------------------------------------------
    allow_text_invention: bool = False
    """Never invent text that wasn't in the provider output."""

    # -- Geometry rules -------------------------------------------------------
    allow_polygon_to_bbox: bool = True
    """Allow deriving bbox from polygon (enricher)."""

    allow_bbox_inference: bool = True
    """Allow inferring bbox from context (e.g. line bbox from word bboxes)."""

    allow_bbox_invention: bool = False
    """Never invent bbox without any geometric basis."""

    # -- Language rules -------------------------------------------------------
    allow_lang_propagation: bool = True
    """Allow propagating language from parent to child nodes."""

    # -- Export rules ---------------------------------------------------------
    require_lines_for_alto: bool = True
    """ALTO export requires at least line-level geometry."""

    require_words_for_alto: bool = True
    """ALTO export requires word-level text and geometry."""

    allow_partial_alto: bool = True
    """Allow ALTO export with partial readiness."""

    allow_partial_page: bool = True
    """Allow PAGE export with partial readiness."""

    # -- Enricher rules -------------------------------------------------------
    allow_reading_order_inference: bool = True
    """Allow inferring reading order from spatial position."""

    allow_hyphenation_detection: bool = True
    """Allow detecting word hyphenation at line boundaries."""

    # -- Tolerance ------------------------------------------------------------
    bbox_containment_tolerance: float = 5.0
    """Pixels of allowed overflow for bbox containment checks."""

    @property
    def strict_mode(self) -> bool:
        return self.mode == PolicyMode.STRICT


def strict_policy() -> DocumentPolicy:
    """A strict policy: no inference, no partial exports."""
    return DocumentPolicy(
        mode=PolicyMode.STRICT,
        allow_polygon_to_bbox=False,
        allow_bbox_inference=False,
        allow_lang_propagation=False,
        allow_partial_alto=False,
        allow_partial_page=False,
        allow_reading_order_inference=False,
        allow_hyphenation_detection=False,
    )


def permissive_policy() -> DocumentPolicy:
    """A permissive policy: allow inference and partial exports."""
    return DocumentPolicy(
        mode=PolicyMode.PERMISSIVE,
        allow_bbox_inference=True,
        allow_partial_alto=True,
        allow_partial_page=True,
        bbox_containment_tolerance=10.0,
    )