50ae9e94d1
Thin adapter — it judges the customer's reading WITHIN the already-built
RegulatoryMap, it does not assess abstract legal questions and it is not RCI.
- Reuses the existing assess_interpretation (no new legal reasoning); the 6
verdicts (plausible/too_narrow/too_broad/partially_correct/unsupported/uncertain)
pass through unchanged.
- Restricts affected_regulations/affected_obligations to those present in the map
(intersection); links to the map's uncertain regulations.
- Touched unsupported domains (wastewater/chemicals/...) are reported as
future_corpus_domains (future_corpus_needed) — never pseudo-evaluated.
- Customer-readable explanation ("Ihre Interpretation ist wahrscheinlich zu eng. …
Betroffen in Ihrer Map: CRA.").
- POST /reasoning/interpretation-in-map (renders the map, then interprets).
- 7 tests; 63 green (existing reasoning MVP stays green), mypy clean, LOC ok.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
142 lines
5.4 KiB
Python
142 lines
5.4 KiB
Python
"""Tests for Interpretation-in-Map (step 5).
|
|
|
|
Acceptance: a customer interpretation is judged against the existing map, using
|
|
only assess_interpretation; affected regulations/obligations are referenced from
|
|
the map; unsupported domains (wastewater/chemicals) are flagged
|
|
future_corpus_needed, not pseudo-evaluated; output is customer-readable.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from compliance.interpretation_map import interpret_in_map
|
|
from compliance.profile.canonical import (
|
|
CanonicalLifecyclePhase,
|
|
CanonicalProductRegulatoryProfile,
|
|
CanonicalProductType,
|
|
EconomicOperatorRole,
|
|
EnvironmentalImpact,
|
|
)
|
|
from compliance.reasoning.enums import InterpretationVerdict
|
|
from compliance.reasoning.interpretation_engine import assess_interpretation
|
|
from compliance.regulatory_map import render_regulatory_map
|
|
|
|
|
|
def ready_profile(**ov) -> CanonicalProductRegulatoryProfile:
|
|
base = dict(
|
|
name="Industriespülmaschine",
|
|
product_type=CanonicalProductType.MACHINERY,
|
|
markets=["EU", "DE"],
|
|
economic_operator_role=EconomicOperatorRole.MANUFACTURER,
|
|
lifecycle_phase=CanonicalLifecyclePhase.PLACING_ON_MARKET,
|
|
is_machine=True,
|
|
is_component=False,
|
|
has_software_updates=True,
|
|
has_embedded_software=True,
|
|
has_remote_access=True,
|
|
technologies=["cloud", "ota_updates"],
|
|
)
|
|
base.update(ov)
|
|
return CanonicalProductRegulatoryProfile(**base)
|
|
|
|
|
|
def _map(**ov):
|
|
return render_regulatory_map(ready_profile(**ov))
|
|
|
|
|
|
# 1 + 2. evaluated against the map, using ONLY assess_interpretation.
|
|
def test_uses_assess_interpretation_verdict():
|
|
text = "Wir glauben, der CRA gilt nur für neue Produkte."
|
|
result = interpret_in_map(_map(), text)
|
|
assert result.assessment == assess_interpretation(text).assessment == InterpretationVerdict.TOO_NARROW
|
|
assert "CRA" in result.affected_regulations # CRA is in the map
|
|
assert result.in_scope_of_map is True
|
|
|
|
|
|
# 3. the six verdict values pass through unchanged.
|
|
def test_verdict_values():
|
|
m = _map()
|
|
assert interpret_in_map(m, "CRA gilt nur für neue Produkte.").assessment == InterpretationVerdict.TOO_NARROW
|
|
assert interpret_in_map(m, "Open Source ist ausgenommen, also betrifft uns der CRA nicht.").assessment == InterpretationVerdict.PARTIALLY_CORRECT
|
|
assert interpret_in_map(m, "Der Mond beeinflusst unsere Updatezyklen.").assessment == InterpretationVerdict.UNCERTAIN
|
|
|
|
|
|
# 4. affected regulations/obligations are referenced FROM the map.
|
|
def test_affected_refs_from_map():
|
|
m = _map()
|
|
result = interpret_in_map(m, "Eine SBOM reicht, dann sind wir fertig.")
|
|
map_ob_ids = {o.obligation_id for v in m.applicable_regulations for o in v.obligations}
|
|
map_reg_ids = {v.regulation_id for v in m.applicable_regulations} | {v.regulation_id for v in m.uncertain_regulations}
|
|
assert "sbom_creation" in result.affected_obligations
|
|
assert set(result.affected_obligations) <= map_ob_ids
|
|
assert set(result.affected_regulations) <= map_reg_ids
|
|
|
|
|
|
# 5. environmental aspects are NOT pseudo-evaluated.
|
|
def test_environmental_not_pseudo_evaluated():
|
|
m = _map(environmental=EnvironmentalImpact(discharges_to_wastewater=True))
|
|
result = interpret_in_map(m, "Beim Abwasser sind wir nicht betroffen, das spielt für uns keine Rolle.")
|
|
domains = {d.domain for d in result.future_corpus_domains}
|
|
assert "environment_water" in domains
|
|
assert "future_corpus_needed" in result.explanation
|
|
|
|
|
|
# 6. output is customer-readable.
|
|
def test_customer_readable():
|
|
result = interpret_in_map(_map(), "Der CRA gilt nur für neue Produkte.")
|
|
assert "zu eng" in result.explanation
|
|
assert result.explanation.startswith("Ihre Interpretation ist wahrscheinlich")
|
|
|
|
|
|
# affected refs never leave the map (no abstract legal questions).
|
|
def test_affected_regs_never_outside_map():
|
|
m = _map()
|
|
map_reg_ids = (
|
|
{v.regulation_id for v in m.applicable_regulations}
|
|
| {v.regulation_id for v in m.uncertain_regulations}
|
|
| {v.regulation_id for v in m.excluded_regulations}
|
|
)
|
|
for text in ["CRA gilt nur für neue Produkte.", "Ohne Funkmodul keine Cyber-Pflichten.", "SBOM reicht."]:
|
|
result = interpret_in_map(m, text)
|
|
assert set(result.affected_regulations) <= map_reg_ids
|
|
|
|
|
|
# endpoint smoke.
|
|
@pytest.fixture(scope="module")
|
|
def client():
|
|
from compliance.api.reasoning_routes import router
|
|
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
return TestClient(app)
|
|
|
|
|
|
def test_endpoint_interpretation_in_map(client):
|
|
r = client.post(
|
|
"/reasoning/interpretation-in-map",
|
|
json={
|
|
"product_profile": {
|
|
"name": "M",
|
|
"product_type": "machinery",
|
|
"markets": ["EU"],
|
|
"economic_operator_role": "manufacturer",
|
|
"lifecycle_phase": "placing_on_market",
|
|
"is_machine": True,
|
|
"is_component": False,
|
|
"has_software_updates": True,
|
|
"has_embedded_software": True,
|
|
"has_remote_access": True,
|
|
"technologies": ["cloud"],
|
|
},
|
|
"customer_interpretation": "Der CRA gilt nur für neue Produkte.",
|
|
},
|
|
)
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["assessment"] == "too_narrow"
|
|
assert "CRA" in body["affected_regulations"]
|
|
assert "zu eng" in body["explanation"]
|