"""Product Regulatory Navigator — question catalog. The Navigator is a THIN missing-facts layer over CanonicalProductRegulatoryProfile. It does NOT decide what applies — `regulatory_domains_unblocked` is static metadata (which domains a fact would help the Scope Engine decide later), never an evaluation. No regulation logic, no UI, no Go, no RAG. `NavigatorQuestion` is an interaction type, NOT a compliance-meta-model class (architecture freeze v1.0 untouched). """ from __future__ import annotations from enum import Enum from typing import List from pydantic import BaseModel, Field from compliance.profile.canonical import CanonicalLifecyclePhase, EconomicOperatorRole class AnswerType(str, Enum): BOOL = "bool" ENUM = "enum" MULTISELECT = "multiselect" TEXT = "text" COUNTRY_LIST = "country_list" COMPONENT_LIST = "component_list" class QuestionPriority(str, Enum): P0 = "P0" # blocks scope: EU-vs-not, role, lifecycle, machine/component P1 = "P1" # unblocks a specific domain: RED, Data Act, environment, security P2 = "P2" # refinement: structured BOM _PRIORITY_ORDER = {QuestionPriority.P0: 0, QuestionPriority.P1: 1, QuestionPriority.P2: 2} class NavigatorQuestion(BaseModel): question_id: str target_field: str # dotted path into the canonical profile label: str why_needed: str regulatory_domains_unblocked: List[str] = Field(default_factory=list) answer_type: AnswerType options: List[str] = Field(default_factory=list) priority: QuestionPriority def order(self) -> int: return _PRIORITY_ORDER[self.priority] _ROLE_OPTIONS = [e.value for e in EconomicOperatorRole] _PHASE_OPTIONS = [e.value for e in CanonicalLifecyclePhase] QUESTION_CATALOG: List[NavigatorQuestion] = [ # ── P0: block the scope decision itself ─────────────────────────── NavigatorQuestion( question_id="markets", target_field="markets", label="In welche Märkte / Länder liefern Sie das Produkt?", why_needed="Bestimmt EU- vs. Nicht-EU-Anwendbarkeit und nationale Pflichten.", regulatory_domains_unblocked=["cyber", "machine_safety", "data", "radio", "emv", "environment"], answer_type=AnswerType.COUNTRY_LIST, priority=QuestionPriority.P0, ), NavigatorQuestion( question_id="economic_operator_role", target_field="economic_operator_role", label="Welche Rolle nehmen Sie ein?", why_needed="Pflichten hängen von der Rolle ab (Hersteller/Importeur/Händler/Betreiber/Service).", regulatory_domains_unblocked=["cyber", "machine_safety", "data"], answer_type=AnswerType.ENUM, options=_ROLE_OPTIONS, priority=QuestionPriority.P0, ), NavigatorQuestion( question_id="lifecycle_phase", target_field="lifecycle_phase", label="In welcher Lebenszyklusphase betrachten Sie das Produkt?", why_needed="Manche Pflichten greifen nur beim Inverkehrbringen oder in der Wartung.", regulatory_domains_unblocked=["cyber", "machine_safety"], answer_type=AnswerType.ENUM, options=_PHASE_OPTIONS, priority=QuestionPriority.P0, ), NavigatorQuestion( question_id="is_machine", target_field="is_machine", label="Ist das Produkt eine (vollständige) Maschine?", why_needed="Entscheidet die Anwendbarkeit der Maschinenverordnung.", regulatory_domains_unblocked=["machine_safety"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P0, ), NavigatorQuestion( question_id="is_component", target_field="is_component", label="Ist das Produkt ein Bauteil / eine unvollständige Maschine?", why_needed="Sicherheitsbauteil vs. vollständige Maschine ändert die Pflichten.", regulatory_domains_unblocked=["machine_safety"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P0, ), # ── P1: unblock one specific domain ─────────────────────────────── NavigatorQuestion( question_id="has_radio_module", target_field="has_radio_module", label="Enthält das Produkt ein Funkmodul (WLAN/Bluetooth/Mobilfunk)?", why_needed="Ein Funkmodul löst die Funkanlagen-Richtlinie (RED) aus.", regulatory_domains_unblocked=["radio"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), NavigatorQuestion( question_id="generates_usage_data", target_field="generates_usage_data", label="Erzeugt das vernetzte Produkt nutzbare Produkt-/Nutzungsdaten?", why_needed="Erzeugte Nutzungsdaten entscheiden über Data-Act-Pflichten.", regulatory_domains_unblocked=["data"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), NavigatorQuestion( question_id="has_security_function", target_field="has_security_function", label="Hat das Produkt eine dedizierte Security-Funktion (gegen böswillige Akteure)?", why_needed="Trennt Security- von Safety-Funktion (CRA vs. MaschinenVO).", regulatory_domains_unblocked=["cyber", "machine_safety"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), NavigatorQuestion( question_id="env_wastewater", target_field="environmental.discharges_to_wastewater", label="Gibt das Produkt Stoffe an Wasser / Abwasser ab?", why_needed="Abwassereinleitung löst Abwasser-/Gewässerrecht aus.", regulatory_domains_unblocked=["environment_water"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), NavigatorQuestion( question_id="env_air", target_field="environmental.emits_to_air", label="Entstehen Luftemissionen (VOC, Staub, Verbrennung, Aerosole)?", why_needed="Luftemissionen lösen Immissionsschutzrecht aus.", regulatory_domains_unblocked=["environment_air"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), NavigatorQuestion( question_id="env_chemicals", target_field="environmental.uses_cleaning_chemicals", label="Werden Reinigungs-, Desinfektions- oder Biozidmittel verwendet/mitgeliefert?", why_needed="Chemikalien lösen REACH/CLP/Detergenzien-/Biozidrecht aus.", regulatory_domains_unblocked=["chemicals"], answer_type=AnswerType.BOOL, priority=QuestionPriority.P1, ), # ── P2: refinement ──────────────────────────────────────────────── NavigatorQuestion( question_id="components", target_field="components", label="Aus welchen wesentlichen Komponenten besteht das Produkt?", why_needed="Eine strukturierte Stückliste verfeinert komponenten-abgeleitete Pflichten.", regulatory_domains_unblocked=["radio", "emv", "environment_water", "chemicals"], answer_type=AnswerType.COMPONENT_LIST, priority=QuestionPriority.P2, ), ]