"""CanonicalProductRegulatoryProfile — the single semantic product profile. Convergence layer (spec 2026-06-26): instead of letting the Go `gap.ProductProfile` and the Python reasoning `ProductProfile` drift, ONE canonical type is the source of truth. The Go gap engine LEADS (it carries real engine logic), so the canonical mirrors gap's field names and adds the Navigator gaps the audit found missing (economic-operator role, radio module, generates_usage_data, lifecycle phase, structured BOM, safety-vs-security split, machine-vs-component) plus a forward-looking Environmental-Impact domain. No regulation logic lives here — types only. Mappers live in sibling modules. 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 CanonicalProductType(str, Enum): # mirrors gap.ProductType SOFTWARE = "software" HARDWARE = "hardware" IOT = "iot" SAAS = "saas" EXCHANGE = "exchange" MEDICAL_DEVICE = "medical_device" MACHINERY = "machinery" OTHER = "other" class EconomicOperatorRole(str, Enum): # CE/CRA role — gap.ProductProfile has none MANUFACTURER = "manufacturer" IMPORTER = "importer" DISTRIBUTOR = "distributor" INTEGRATOR = "integrator" OPERATOR = "operator" SERVICE_PROVIDER = "service_provider" class CanonicalLifecyclePhase(str, Enum): DEVELOPMENT = "development" PLACING_ON_MARKET = "placing_on_market" OPERATION = "operation" MAINTENANCE = "maintenance" UPDATE = "update" END_OF_LIFE = "end_of_life" class ComponentKind(str, Enum): MOTOR = "motor" PUMP = "pump" HEATING = "heating" COOLING = "cooling" CONTROLLER = "controller" PLC = "plc" HMI = "hmi" SENSOR = "sensor" ACTUATOR = "actuator" CAMERA = "camera" NETWORK_INTERFACE = "network_interface" RADIO_MODULE = "radio_module" CHEMICAL_DOSING = "chemical_dosing" WATER_INLET = "water_inlet" WASTEWATER_OUTLET = "wastewater_outlet" BATTERY = "battery" OTHER = "other" class ProductComponent(BaseModel): """One structured BOM node — these nodes are what later trigger domains.""" name: str kind: ComponentKind = ComponentKind.OTHER notes: Optional[str] = None class EnvironmentalImpact(BaseModel): """Forward-looking Umweltmedien-Trigger (own Navigator domain). No regulation logic consumes these yet — profile fields only, so the model is not blind to wastewater/air/chemicals/waste questions when that domain is wired later (AbwV/WRRL/REACH/CLP/IED/BImSchG ...). """ discharges_to_wastewater: Optional[bool] = None uses_cleaning_chemicals: Optional[bool] = None supplies_chemicals: Optional[bool] = None emits_to_air: Optional[bool] = None uses_solvents: Optional[bool] = None creates_waste: Optional[bool] = None contains_restricted_substances: Optional[bool] = None consumes_energy_or_water: Optional[bool] = None has_cooling_or_spraying_water: Optional[bool] = None class CanonicalProductRegulatoryProfile(BaseModel): # --- identity --- name: str = "" description: str = "" product_type: Optional[CanonicalProductType] = None product_profile_id: Optional[str] = None tenant_id: Optional[str] = None iace_project_id: Optional[str] = None # --- gap-native lists --- technologies: List[str] = Field(default_factory=list) data_processing: List[str] = Field(default_factory=list) markets: List[str] = Field(default_factory=list) # real list — never hardcoded ['EU'] existing_certifications: List[str] = Field(default_factory=list) applied_norms: List[str] = Field(default_factory=list) # --- gap-native product / IST-state booleans (tri-state: None = unknown) --- connected_to_internet: Optional[bool] = None has_software_updates: Optional[bool] = None uses_ai: Optional[bool] = None processes_personal_data: Optional[bool] = None is_critical_infra_supplier: Optional[bool] = None has_risk_assessment: Optional[bool] = None has_technical_file: Optional[bool] = None has_operating_manual: Optional[bool] = None has_sbom: Optional[bool] = None has_vuln_management: Optional[bool] = None has_update_mechanism: Optional[bool] = None has_incident_response: Optional[bool] = None has_supply_chain_mgmt: Optional[bool] = None ce_marking_since: Optional[str] = None product_age: Optional[str] = None # --- NEW Navigator-gap fields (audit 2026-06-26) --- economic_operator_role: Optional[EconomicOperatorRole] = None has_radio_module: Optional[bool] = None generates_usage_data: Optional[bool] = None lifecycle_phase: Optional[CanonicalLifecyclePhase] = None components: List[ProductComponent] = Field(default_factory=list) has_safety_function: Optional[bool] = None safety_function_description: Optional[str] = None has_security_function: Optional[bool] = None # safety vs security split has_remote_access: Optional[bool] = None has_embedded_software: Optional[bool] = None is_machine: Optional[bool] = None is_component: Optional[bool] = None is_spare_part: Optional[bool] = None # --- company / market context (NIS2 + scope; from company-profile) --- b2b_or_b2c: Optional[str] = None sector_industry: Optional[str] = None company_size: Optional[str] = None primary_jurisdiction: Optional[str] = None # --- AI context (classification stays delegated to ai-act/ucca) --- ai_integration_type: List[str] = Field(default_factory=list) human_oversight_level: Optional[str] = None # --- forward-looking environmental domain --- environmental: EnvironmentalImpact = Field(default_factory=EnvironmentalImpact)