"""Transition Reasoning v0 (RS-005) — domain objects. The **Transition Planning Engine**: it answers „Was muss ich noch wissen, um vom Ausgangszustand in den regulatorischen Zielzustand zu kommen?" — NOT „wie frage ich das?". It therefore owns the **information gaps** (`TransitionQuestionRequest`), never the rendered question text. Rendering (intent + subject -> sentence) is a separate, swappable layer (RS-005.1 Question Generator) and is NOT part of this engine. v0 consumes the Company Capability Profile (Phase 2A) as the „have" state and an INJECTED list of `TargetRequirement` as the Execution-owned „required" side (no required-capability data in product code — same discipline as 2A's EMPTY_MAPPING). Welt-1-Grenze: a probable coverage (from a certification) is a hint, never „erfüllt"; it produces a confirmation request, not a verdict. Application/reasoning types, NOT meta-model classes (freeze v1.0 untouched). Python 3.9 compatible (no `|` unions). """ from __future__ import annotations from enum import Enum from typing import List, Optional from pydantic import BaseModel, Field class TargetType(str, Enum): REGULATION = "regulation" CERTIFICATION = "certification" FRAMEWORK = "framework" class CoverageStatus(str, Enum): ALREADY_COVERED = "already_covered" # confirmed in the company profile PROBABLY_COVERED = "probably_covered" # inferred (e.g. from a certification) NEEDS_CONFIRMATION = "needs_confirmation" # only declared / weak signal MISSING = "missing" # no signal NOT_APPLICABLE = "not_applicable" UNSUPPORTED = "unsupported" # domain not yet in the corpus (future_corpus_needed) class RequestPriority(str, Enum): HIGH = "high" MEDIUM = "medium" LOW = "low" class InformationGain(str, Enum): HIGH = "high" MEDIUM = "medium" LOW = "low" class TransitionGoal(BaseModel): target_id: str # e.g. "CRA", "TISAX" target_type: TargetType = TargetType.REGULATION label: str = "" class TransitionContext(BaseModel): company_id: str = "" known_certifications: List[str] = Field(default_factory=list) known_regulations: List[str] = Field(default_factory=list) target: TransitionGoal # ── INJECTED (Execution-owned): what the target requires ─────────────────── class TargetRequirement(BaseModel): """One required capability for the target. In v0 injected; later resolved from `Obligation -> Control -> Required Capability` + `Control -> question_intent`.""" capability_id: str # MCAP-... question_intent: str = "verify_existence" # passed through to the request, not rendered expected_evidence: List[str] = Field(default_factory=list) source_control_id: Optional[str] = None supports_obligations: List[str] = Field(default_factory=list) unsupported: bool = False # domain not yet in the corpus # ── the OWNED output: an information gap, NOT a question ─────────────────── class TransitionQuestionRequest(BaseModel): capability_id: str control_id: Optional[str] = None reason: str question_intent: str # verify_existence / determine_duration / ... (rendered later) expected_evidence: List[str] = Field(default_factory=list) priority: RequestPriority information_gain: InformationGain class CapabilityCoverage(BaseModel): capability_id: str status: CoverageStatus have_status: Optional[str] = None # the 2A VerificationStatus that drove it class TransitionSummary(BaseModel): headline: str = "" # counts, NO percentage what_to_clarify: List[str] = Field(default_factory=list) # capability_ids with a request already_covered: List[str] = Field(default_factory=list) probably_covered: List[str] = Field(default_factory=list) missing: List[str] = Field(default_factory=list) not_applicable: List[str] = Field(default_factory=list) unsupported: List[str] = Field(default_factory=list) class TransitionAssessment(BaseModel): target_id: str coverage: List[CapabilityCoverage] = Field(default_factory=list) question_requests: List[TransitionQuestionRequest] = Field(default_factory=list) # ranked summary: TransitionSummary