feat(profile): CanonicalProductRegulatoryProfile convergence layer (types + mappers + tests)
ONE canonical product profile so the Go gap engine and the Python reasoning
engine stop diverging ("SPS mit Remote Access" means the same everywhere).
gap.ProductProfile LEADS; the reasoning ProductProfile becomes an adapter/DTO.
Types + mappers only — no regulation logic, no Go changes, no UI, no new questions.
- CanonicalProductRegulatoryProfile mirrors gap.ProductProfile + the Navigator
gaps the audit found: economic-operator role, radio_module, generates_usage_data,
lifecycle_phase, structured BOM (ProductComponent), safety-vs-security split,
machine-vs-component + a forward-looking EnvironmentalImpact domain (wastewater/
air/chemicals triggers — fields only, no rules yet).
- Mappers: from_product_wizard (lossless), from_company_profile (prefill incl.
the machineBuilder block), to_gap_profile (emits the unchanged gap JSON shape),
to_reasoning_profile (projects into the reasoning ProductProfile; AI stays
delegated to ai-act/ucca). Only profile->reasoning is coupled; reasoning stays
hermetic.
- 10 tests = the 10 acceptance criteria incl. ProductWizard round-trip lossless,
markets no longer forced ['EU'], and canonical->reasoning->discover_scope
proving one semantic profile drives the engine. 33 tests green, mypy clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""company-profile -> CanonicalProductRegulatoryProfile (prefill, acceptance #2).
|
||||
|
||||
Pulls master data (industry, business model, size, markets) and the conditional
|
||||
`machine_builder` block (camelCase JSONB keys, defined frontend-side) so the user
|
||||
re-answers nothing. The machineBuilder block is the richest product/safety/
|
||||
connectivity source — note it is industry-gated in the UI, so a prefill may find
|
||||
it empty; that is fine (fields stay None = unknown).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .canonical import CanonicalProductRegulatoryProfile
|
||||
|
||||
_EU_MEMBER_HINTS = {"DE", "AT", "FR", "IT", "NL", "LU", "LI", "EU", "EWR", "EEA", "DACH"}
|
||||
|
||||
|
||||
def _markets(p: Dict[str, Any], mb: Dict[str, Any]) -> List[str]:
|
||||
out: List[str] = []
|
||||
for source in (p.get("target_markets"), mb.get("exportMarkets"), [p.get("primary_jurisdiction")], [p.get("headquarters_country")]):
|
||||
for m in source or []:
|
||||
if m and m not in out:
|
||||
out.append(m)
|
||||
return out
|
||||
|
||||
|
||||
def _is_machine(mb: Dict[str, Any]) -> Any:
|
||||
types = mb.get("productTypes")
|
||||
if types:
|
||||
return True
|
||||
return None
|
||||
|
||||
|
||||
def from_company_profile(profile: Dict[str, Any]) -> CanonicalProductRegulatoryProfile:
|
||||
p = profile
|
||||
mb = p.get("machine_builder") or {}
|
||||
contains_ai = mb.get("containsAI")
|
||||
uses_ai = contains_ai if contains_ai is not None else p.get("uses_ai")
|
||||
return CanonicalProductRegulatoryProfile(
|
||||
description=mb.get("productDescription") or "",
|
||||
sector_industry=p.get("industry") or None,
|
||||
b2b_or_b2c=p.get("business_model") or None,
|
||||
company_size=p.get("company_size") or None,
|
||||
primary_jurisdiction=p.get("primary_jurisdiction") or None,
|
||||
markets=_markets(p, mb),
|
||||
uses_ai=uses_ai,
|
||||
ai_integration_type=list(mb.get("aiIntegrationType") or []),
|
||||
human_oversight_level=mb.get("humanOversightLevel") or None,
|
||||
has_embedded_software=mb.get("containsFirmware"),
|
||||
has_safety_function=mb.get("hasSafetyFunction"),
|
||||
safety_function_description=mb.get("safetyFunctionDescription") or None,
|
||||
has_remote_access=mb.get("hasRemoteAccess"),
|
||||
connected_to_internet=mb.get("isNetworked"),
|
||||
has_software_updates=mb.get("hasOTAUpdates"),
|
||||
has_risk_assessment=mb.get("hasRiskAssessment"),
|
||||
is_machine=_is_machine(mb),
|
||||
is_critical_infra_supplier=mb.get("criticalSectorClients"),
|
||||
)
|
||||
Reference in New Issue
Block a user