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>
91 lines
4.0 KiB
Python
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,
|
|
)
|