Files
breakpilot-compliance/backend-compliance/compliance/interpretation_map/adapter.py
T
Benjamin Admin 50ae9e94d1 feat(interpretation-in-map): judge a customer interpretation within the map (step 5)
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>
2026-06-26 10:58:00 +02:00

91 lines
4.0 KiB
Python

"""Interpretation-in-Map adapter (step 5).
Evaluates a customer interpretation WITHIN the already-built RegulatoryMap. It
reuses the existing `assess_interpretation` (no new legal engine), restricts the
affected regulations/obligations to those present in the map, and reports any
touched unsupported domain (wastewater/chemicals/...) as future_corpus_needed
rather than pseudo-evaluating it.
"""
from __future__ import annotations
from typing import Dict, List
from compliance.reasoning.enums import InterpretationVerdict
from compliance.reasoning.interpretation_engine import assess_interpretation
from compliance.regulatory_map.schemas import RegulatoryMap
from .schemas import InterpretationInMapResult
_LABEL: Dict[InterpretationVerdict, str] = {
InterpretationVerdict.PLAUSIBLE: "plausibel",
InterpretationVerdict.TOO_NARROW: "zu eng",
InterpretationVerdict.TOO_BROAD: "zu weit",
InterpretationVerdict.PARTIALLY_CORRECT: "teilweise korrekt",
InterpretationVerdict.UNSUPPORTED: "nicht belegt",
InterpretationVerdict.UNCERTAIN: "unsicher",
}
# domain -> keywords that signal the interpretation is ABOUT that (uncovered) domain.
_ENV_KEYWORDS: Dict[str, List[str]] = {
"environment_water": ["abwasser", "wastewater", "gewässer", "gewaesser", "einleitung", "abfluss"],
"chemicals": ["chemikalie", "reach", "clp", "reinigungsmittel", "biozid", "gefahrstoff", "detergenz", "lösemittel", "loesemittel"],
"environment_air": ["luft", "emission", "voc", "immission", "abluft", "verbrennung"],
"waste": ["abfall", "entsorgung", "weee", "recycling"],
"energy_resources": ["energie", "ökodesign", "oekodesign", "verbrauch"],
}
def _touches(text: str, domain: str) -> bool:
low = text.lower()
return any(kw in low for kw in _ENV_KEYWORDS.get(domain, []))
def _explain(label: str, detail: str, affected_regs: List[str], future_domains: List[str], in_scope: bool) -> str:
base = "Ihre Interpretation ist wahrscheinlich %s." % label
if detail:
base += " " + detail
if affected_regs:
base += " Betroffen in Ihrer Map: %s." % ", ".join(affected_regs)
if future_domains:
base += (
" Für %s liegt noch kein Regelkorpus vor — diese Aspekte werden nicht bewertet (future_corpus_needed)."
% ", ".join(future_domains)
)
if not in_scope and not future_domains:
base += " Diese Auslegung betrifft kein Regelwerk Ihrer aktuellen Produkt-Map."
return base
def interpret_in_map(reg_map: RegulatoryMap, interpretation: str) -> InterpretationInMapResult:
a = assess_interpretation(interpretation) # existing engine — no new reasoning
map_reg_ids = (
{v.regulation_id for v in reg_map.applicable_regulations}
| {v.regulation_id for v in reg_map.uncertain_regulations}
| {v.regulation_id for v in reg_map.excluded_regulations}
)
map_ob_ids = {o.obligation_id for v in reg_map.applicable_regulations for o in v.obligations}
uncertain_ids = {v.regulation_id for v in reg_map.uncertain_regulations}
affected_regs = [r for r in a.affected_regulations if r in map_reg_ids]
affected_obs = [o for o in a.affected_obligations if o in map_ob_ids]
related_unc = [r for r in a.affected_regulations if r in uncertain_ids]
future = [d for d in reg_map.unsupported_domains if _touches(interpretation, d.domain)]
in_scope = bool(affected_regs or affected_obs)
return InterpretationInMapResult(
raw_interpretation=interpretation,
assessment=a.assessment,
in_scope_of_map=in_scope,
affected_regulations=affected_regs,
affected_obligations=affected_obs,
related_uncertainties=related_unc,
future_corpus_domains=future,
corrected_interpretation=a.corrected_interpretation,
risks=a.risks,
legal_basis_refs=a.legal_basis_refs,
explanation=_explain(_LABEL[a.assessment], a.explanation, affected_regs, [d.domain for d in future], in_scope),
confidence=a.confidence,
)