"""CanonicalProductRegulatoryProfile -> gap.ProductProfile JSON shape. Emits exactly the keys the Go gap engine already consumes (gap/models.go json tags), so the gap engine runs UNCHANGED — the canonical is a superset and gap is its lossless projection. Canonical-only fields (role/radio/components/...) are intentionally not emitted here; they reach the reasoning side via to_reasoning. """ from __future__ import annotations from typing import Any, Dict from .canonical import CanonicalProductRegulatoryProfile def to_gap_profile(c: CanonicalProductRegulatoryProfile) -> Dict[str, Any]: return { "name": c.name, "description": c.description, "product_type": c.product_type.value if c.product_type else "", "technologies": list(c.technologies), "data_processing": list(c.data_processing), "markets": list(c.markets), "existing_certifications": list(c.existing_certifications), "applied_norms": list(c.applied_norms), "connected_to_internet": bool(c.connected_to_internet), "has_software_updates": bool(c.has_software_updates), "uses_ai": bool(c.uses_ai), "processes_personal_data": bool(c.processes_personal_data), "is_critical_infra_supplier": bool(c.is_critical_infra_supplier), "has_risk_assessment": bool(c.has_risk_assessment), "has_technical_file": bool(c.has_technical_file), "has_operating_manual": bool(c.has_operating_manual), "has_sbom": bool(c.has_sbom), "has_vuln_management": bool(c.has_vuln_management), "has_update_mechanism": bool(c.has_update_mechanism), "has_incident_response": bool(c.has_incident_response), "has_supply_chain_mgmt": bool(c.has_supply_chain_mgmt), "ce_marking_since": c.ce_marking_since if c.ce_marking_since is not None else "", "product_age": c.product_age if c.product_age is not None else "", }