feat(reasoning): Regulatory Reasoning Engine MVP (scope/obligations/implementation/interpretation)
Deterministic reasoning layer ON TOP of the Legal Knowledge Graph (obligation
registry) and the Compliance Execution Graph (control mapping/evidence). Answers
which regulations apply to a concrete product, which obligations follow, whether
the customer's implementation covers them, and whether a customer interpretation
is too narrow/broad/plausible.
- ProductProfile with tri-state facts (Optional[bool]=None => uncertain, never
false security); safe predicate evaluator (no eval).
- 6 regulation triggers (CRA/MaschinenVO/RED/EMV/DataAct/NIS2) with missing-fact
prompts; 24 obligation scope rules.
- CRA obligation_ids RE-USED verbatim from the registry (93 ids) — never re-minted
(control_uuid trap); Machine/Data-Act flagged proposed=True.
- required_evidence constrained to the framework-agnostic shared evidence catalog;
capabilities echo the planned Obligation->Capability layer.
- Overlap groups (CRA<->MaschinenVO cyber-safety) + evidence-for-multiple (USP).
- 4 endpoints POST /reasoning/{scope,obligations,implementation-assessment,
interpretation-assessment}; thin handlers, registered in api/__init__.py.
- 22 tests (5 machine-builder scenarios + 10 acceptance questions). No DB
migration, no RAG, no new controls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""Customer implementation claim normaliser (spec §4.6).
|
||||
|
||||
Turns a free-text statement ("Wir haben einen Update-Prozess.") into structured
|
||||
capabilities + related topics + weakness qualifiers. Deterministic substring
|
||||
matching — the claim_id is a stable hash so the same statement always maps to
|
||||
the same id (no randomness, replay-safe).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from typing import List, Optional
|
||||
|
||||
from .schemas import CustomerImplementationClaim
|
||||
from .taxonomy_claims import match_capabilities, match_qualifiers, topics_for
|
||||
|
||||
|
||||
def _claim_id(raw_statement: str) -> str:
|
||||
digest = hashlib.sha1(raw_statement.strip().lower().encode("utf-8")).hexdigest()
|
||||
return "claim_%s" % digest[:10]
|
||||
|
||||
|
||||
def _normalized(capabilities: List[str], qualifiers: List[str]) -> str:
|
||||
if not capabilities:
|
||||
return "Keine bekannte Compliance-Fähigkeit aus der Aussage ableitbar."
|
||||
text = "Fähigkeiten: " + ", ".join(capabilities)
|
||||
if qualifiers:
|
||||
text += " | Einschränkungen: " + ", ".join(qualifiers)
|
||||
return text
|
||||
|
||||
|
||||
def normalize_claim(
|
||||
raw_statement: str, claim_id: Optional[str] = None, evidence_refs: Optional[List[str]] = None
|
||||
) -> CustomerImplementationClaim:
|
||||
capabilities = match_capabilities(raw_statement)
|
||||
qualifiers = match_qualifiers(raw_statement)
|
||||
return CustomerImplementationClaim(
|
||||
claim_id=claim_id or _claim_id(raw_statement),
|
||||
raw_statement=raw_statement,
|
||||
normalized_claim=_normalized(capabilities, qualifiers),
|
||||
claimed_capability=capabilities,
|
||||
related_topics=topics_for(capabilities),
|
||||
qualifiers=qualifiers,
|
||||
evidence_refs=evidence_refs or [],
|
||||
)
|
||||
Reference in New Issue
Block a user