"""Schemas for the Journey Matcher — the Delta -> Journey function of the Capability Delta Engine. Derived views (computed-not-stored): nothing here is persisted; every match is recomputed from the input delta + injected journey signatures each call. No new corpus, no graph (freeze v1.0). Python 3.9 compatible (no `|` unions). """ from __future__ import annotations from typing import List, Optional from pydantic import BaseModel, Field class JourneySignature(BaseModel): """A known journey described ONLY by its capability pattern (Input cluster -> Output cluster). Deliberately certificate-/regulation-agnostic: the match uses `capability_pattern` alone. `label` and the context fields exist for the human-auditable explanation, NEVER for the score. (Today the signatures are derived from the transition patterns; the IDs like "ISO27001->CRA" are just one way to describe the clusters — the matcher never reads them.) """ journey_id: str label: str capability_pattern: List[str] = Field(default_factory=list) # OUTPUT cluster: the delta this journey is about assumed_capabilities: List[str] = Field(default_factory=list) # INPUT cluster: typically already present industry: Optional[str] = None product_type: Optional[str] = None target_type: Optional[str] = None # context only: regulation / certification / contract / environmental class MatchContext(BaseModel): """Optional corroborating context — surfaced as documented reasons, never part of the score.""" industry: Optional[str] = None product_type: Optional[str] = None target_type: Optional[str] = None class JourneyMatchReason(BaseModel): """The auditable WHY behind one match — everything a reviewer needs, no opaque score.""" matched_capabilities: List[str] = Field(default_factory=list) # delta INTERSECT pattern (what it explains) unexplained_delta: List[str] = Field(default_factory=list) # delta - pattern (what it does NOT explain) journey_only: List[str] = Field(default_factory=list) # pattern - delta (journey covers, not needed here) context_signals: List[str] = Field(default_factory=list) # "gleiche Zielart", "gleiche Branche", ... class JourneyMatch(BaseModel): """One known journey, ranked by how much of the delta it EXPLAINS (not how well it 'fits').""" journey_id: str label: str score: float = 0.0 # |delta INTERSECT pattern| / |delta|, 0..1: share of the delta explained explains: str = "" # "8 von 10 fehlenden Capabilities" reason: JourneyMatchReason class JourneyMatchResult(BaseModel): """Ranked known journeys that EXPLAIN a Capability Delta. Journey = explanation, not cause.""" delta_size: int = 0 matches: List[JourneyMatch] = Field(default_factory=list) # ranked desc by score best: Optional[JourneyMatch] = None headline: str = ""