File size: 2,011 Bytes
3213f50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bbbfba8
3213f50
 
 
bbbfba8
 
 
3213f50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Enricher subsystem — post-normalization enrichment pipeline.

Enrichers transform a CanonicalDocument into a new CanonicalDocument
with additional derived data. They produce new instances (models are frozen).

Every enricher MUST:
  - Set provenance.evidence_type to 'derived' or 'inferred'
  - Update geometry.status if geometry was modified
  - Add warnings when appropriate
  - Respect the active DocumentPolicy
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

from src.app.policies.document_policy import DocumentPolicy

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


class BaseEnricher(ABC):
    """Abstract base for enrichers."""

    @property
    @abstractmethod
    def name(self) -> str:
        """Enricher name used in audit trail."""
        ...

    @abstractmethod
    def enrich(
        self, doc: CanonicalDocument, policy: DocumentPolicy
    ) -> CanonicalDocument:
        """Apply enrichment, returning a new document."""
        ...


class EnricherPipeline:
    """Runs a configurable chain of enrichers."""

    def __init__(self, enrichers: list[BaseEnricher] | None = None) -> None:
        self._enrichers = enrichers or []

    def add(self, enricher: BaseEnricher) -> None:
        self._enrichers.append(enricher)

    def run(
        self, doc: CanonicalDocument, policy: DocumentPolicy | None = None
    ) -> CanonicalDocument:
        """Run all enrichers in order, threading the document through."""
        if policy is None:
            policy = DocumentPolicy()

        applied: list[str] = list(doc.audit.enrichers_applied)
        current = doc

        for enricher in self._enrichers:
            current = enricher.enrich(current, policy)
            applied.append(enricher.name)

        # Update audit with enrichers applied
        new_audit = doc.audit.model_copy(update={"enrichers_applied": applied})
        return current.model_copy(update={"audit": new_audit})