"""Pydantic domain objects for the Regulatory Reasoning Engine. Trigger facts that drive scope are tri-state (`Optional[bool] = None`): `None` means "fact unknown" and produces an *uncertain* verdict plus a concrete missing-fact prompt — never silent false security (spec §6.3). """ from __future__ import annotations from datetime import date from typing import Dict, List, Optional from pydantic import BaseModel, Field from .enums import ( ApplicabilityStatus, AuthorityLevel, ClaimCoverage, Confidence, InterpretationVerdict, ManufacturerRole, MarketModel, OverlapType, ProductLifecyclePhase, ) # --------------------------------------------------------------------------- # Input # --------------------------------------------------------------------------- class ProductProfile(BaseModel): """The customer's product / system. Tri-state booleans => unknown facts.""" product_name: str product_profile_id: Optional[str] = None manufacturer_role: Optional[ManufacturerRole] = None product_type: List[str] = Field(default_factory=list) has_software: Optional[bool] = None has_embedded_software: Optional[bool] = None has_remote_access: Optional[bool] = None has_cloud_connection: Optional[bool] = None has_ai_functionality: Optional[bool] = None has_radio_module: Optional[bool] = None has_safety_function: Optional[bool] = None generates_usage_data: Optional[bool] = None is_machine: Optional[bool] = None is_component: Optional[bool] = None is_spare_part: Optional[bool] = None placed_on_market_after: Optional[date] = None intended_use: Optional[str] = None eu_market: Optional[bool] = None b2b_or_b2c: Optional[MarketModel] = None lifecycle_phase: Optional[ProductLifecyclePhase] = None # Organisation context — only needed for NIS2 (not a product fact). company_size: Optional[str] = None sector: Optional[str] = None is_essential_or_important_entity: Optional[bool] = None # --------------------------------------------------------------------------- # Scope # --------------------------------------------------------------------------- class ApplicableRegulation(BaseModel): regulation_id: str name: str applicability_status: ApplicabilityStatus trigger_facts: List[str] = Field(default_factory=list) legal_basis_refs: List[str] = Field(default_factory=list) confidence: Confidence explanation: str class ExcludedRegulation(BaseModel): regulation_id: str name: str reason: str class UncertainRegulation(BaseModel): regulation_id: str name: str missing_facts: List[str] = Field(default_factory=list) explanation: str class RegulatoryScope(BaseModel): product_profile_id: Optional[str] = None applicable_regulations: List[ApplicableRegulation] = Field(default_factory=list) excluded_regulations: List[ExcludedRegulation] = Field(default_factory=list) uncertain_regulations: List[UncertainRegulation] = Field(default_factory=list) missing_facts: List[str] = Field(default_factory=list) confidence: Confidence = Confidence.MEDIUM reasoning_summary: str = "" # --------------------------------------------------------------------------- # Obligations # --------------------------------------------------------------------------- class ApplicableObligation(BaseModel): obligation_id: str title: str source_regulation: str legal_basis_refs: List[str] = Field(default_factory=list) obligation_text: str authority_level: AuthorityLevel applies_because: List[str] = Field(default_factory=list) applies_to_role: List[str] = Field(default_factory=list) lifecycle_phase: List[str] = Field(default_factory=list) overlap_group_id: Optional[str] = None required_evidence: List[str] = Field(default_factory=list) confidence: Confidence # True only when obligation_id is owned by the Legal-KG registry (CRA P1). registry_anchor: bool = False # Machine/Data-Act obligations the registry has not canonicalised yet. proposed: bool = False class ObligationOverlap(BaseModel): overlap_group_id: str obligations: List[str] = Field(default_factory=list) overlap_type: OverlapType canonical_obligation_id: str explanation: str # --------------------------------------------------------------------------- # Customer claims & assessments # --------------------------------------------------------------------------- class CustomerImplementationClaim(BaseModel): claim_id: str raw_statement: str normalized_claim: str = "" claimed_capability: List[str] = Field(default_factory=list) related_topics: List[str] = Field(default_factory=list) qualifiers: List[str] = Field(default_factory=list) evidence_refs: List[str] = Field(default_factory=list) class ClaimObligationMapping(BaseModel): """One row of Welt-1 reasoning: how a customer claim relates to an obligation. Layers (spec / architect): claim -> interpretation (on the claim object) -> *potential* obligation coverage (`claim_coverage`) -> evidence required. Carries NO compliance verdict. """ claim_id: str obligation_id: str claim_coverage: ClaimCoverage missing_elements: List[str] = Field(default_factory=list) required_evidence: List[str] = Field(default_factory=list) explanation: str confidence: Confidence class InterpretationAssessment(BaseModel): interpretation_id: str raw_interpretation: str affected_regulations: List[str] = Field(default_factory=list) affected_obligations: List[str] = Field(default_factory=list) assessment: InterpretationVerdict risks: List[str] = Field(default_factory=list) corrected_interpretation: str = "" legal_basis_refs: List[str] = Field(default_factory=list) explanation: str confidence: Confidence # --------------------------------------------------------------------------- # API request / response envelopes # --------------------------------------------------------------------------- class ScopeRequest(BaseModel): product_profile: ProductProfile class ScopeResponse(BaseModel): regulatory_scope: RegulatoryScope missing_facts: List[str] = Field(default_factory=list) confidence: Confidence class ObligationsRequest(BaseModel): product_profile: ProductProfile regulatory_scope: Optional[RegulatoryScope] = None class ObligationsResponse(BaseModel): applicable_obligations: List[ApplicableObligation] = Field(default_factory=list) overlaps: List[ObligationOverlap] = Field(default_factory=list) excluded_obligations: List[str] = Field(default_factory=list) evidence_for_multiple: Dict[str, List[str]] = Field(default_factory=dict) class ImplementationReasoningRequest(BaseModel): product_profile: ProductProfile customer_claim: str class ImplementationReasoningResponse(BaseModel): claim: CustomerImplementationClaim mappings: List[ClaimObligationMapping] = Field(default_factory=list) missing_evidence: List[str] = Field(default_factory=list) summary: str = "" # Makes the Welt-1 boundary explicit: this is advisory claim-mapping, not a # conformity verdict (that is ComplianceStatus in the Execution Graph). disclaimer: str = "" class InterpretationRequest(BaseModel): product_profile: Optional[ProductProfile] = None customer_interpretation: str class InterpretationResponse(BaseModel): assessment: InterpretationVerdict affected_regulations: List[str] = Field(default_factory=list) affected_obligations: List[str] = Field(default_factory=list) corrected_interpretation: str = "" risks: List[str] = Field(default_factory=list) legal_basis_refs: List[str] = Field(default_factory=list) explanation: str = "" confidence: Confidence = Confidence.MEDIUM