Files
breakpilot-compliance/backend-compliance/compliance/reasoning/rules_regulations.py
T
Benjamin Admin 1607c89459 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>
2026-06-25 19:30:53 +02:00

161 lines
6.7 KiB
Python

"""Regulation-level applicability trigger rules (scope discovery, spec Modus 1).
Each rule is pure data consumed by `scope_engine`. Triggers reference
`ProductProfile` fields through the safe predicate evaluator. `required_facts`
that are unknown turn the verdict *uncertain* and surface `fact_prompts`.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from .enums import Confidence
from .predicates import Condition
# Positive, human-readable label per profile fact (for trigger_facts output).
FIELD_LABELS: Dict[str, str] = {
"has_software": "Produkt enthält Software / digitale Elemente",
"has_embedded_software": "Produkt enthält eingebettete Software",
"has_remote_access": "Produkt besitzt Fernzugriff / Fernwartung",
"has_cloud_connection": "Produkt ist mit einer Cloud verbunden",
"has_radio_module": "Produkt enthält ein Funkmodul",
"has_safety_function": "Produkt erfüllt eine Sicherheitsfunktion",
"generates_usage_data": "Vernetztes Produkt erzeugt nutzbare Produktdaten",
"is_machine": "Produkt ist eine Maschine",
"is_component": "Produkt ist ein (Sicherheits-)Bauteil",
"eu_market": "Produkt wird auf dem EU-Markt bereitgestellt",
"is_essential_or_important_entity": "Unternehmen ist wesentliche/wichtige Einrichtung",
"manufacturer_role": "Wirtschaftsakteur-Rolle (Hersteller/Importeur/Händler)",
}
@dataclass(frozen=True)
class RegulationRule:
regulation_id: str
name: str
trigger: Condition
required_facts: List[str]
fact_prompts: Dict[str, str]
legal_basis_refs: List[str]
summary: str
confidence_when_applicable: Confidence = Confidence.HIGH
exclusion: Optional[Condition] = None
# Status is downgraded to PARTIALLY_APPLICABLE / MEDIUM when the trigger
# fires only via inference rather than a directly stated fact.
inferred: bool = False
excludable_roles: List[str] = field(default_factory=list)
_ECONOMIC_ROLES = ["manufacturer", "importer", "distributor"]
REGULATION_RULES: List[RegulationRule] = [
RegulationRule(
regulation_id="CRA",
name="Cyber Resilience Act (EU) 2024/2847",
trigger={
"all": [
{"any": [("has_software", "eq", True), ("has_embedded_software", "eq", True)]},
("eu_market", "eq", True),
]
},
required_facts=["has_software", "eu_market", "manufacturer_role"],
fact_prompts={
"has_software": "Enthält das Produkt Software / digitale Elemente?",
"eu_market": "Wird das Produkt auf dem EU-Markt bereitgestellt oder in Verkehr gebracht?",
"manufacturer_role": "Welche Rolle nehmen Sie ein (Hersteller / Importeur / Händler)?",
},
legal_basis_refs=["CRA Art. 2(1)", "CRA Art. 3(1)"],
summary="Produkte mit digitalen Elementen, die auf dem EU-Markt bereitgestellt werden.",
confidence_when_applicable=Confidence.HIGH,
excludable_roles=["operator"],
),
RegulationRule(
regulation_id="MaschinenVO",
name="Maschinenverordnung (EU) 2023/1230",
trigger={
"any": [
("is_machine", "eq", True),
{"all": [("is_component", "eq", True), ("has_safety_function", "eq", True)]},
]
},
required_facts=["is_machine", "eu_market"],
fact_prompts={
"is_machine": "Ist das Produkt eine Maschine oder ein Sicherheitsbauteil?",
"has_safety_function": "Erfüllt das Bauteil eine Sicherheitsfunktion?",
},
legal_basis_refs=["MaschinenVO (EU) 2023/1230 Art. 2", "Anhang III"],
summary="Maschinen oder Sicherheitsbauteile, ggf. mit sicherheitsrelevanter Steuerung.",
confidence_when_applicable=Confidence.MEDIUM,
),
RegulationRule(
regulation_id="RED",
name="Radio Equipment Directive 2014/53/EU",
trigger=("has_radio_module", "eq", True),
required_facts=["has_radio_module"],
fact_prompts={
"has_radio_module": "Besitzt das Produkt ein Funkmodul (WLAN, Bluetooth, Mobilfunk)?",
},
legal_basis_refs=["RED 2014/53/EU Art. 1", "Art. 3(3)(d-f)"],
summary="Funkanlagen; Art. 3(3) deckt zusätzlich Cybersecurity-Anforderungen ab.",
confidence_when_applicable=Confidence.HIGH,
),
RegulationRule(
regulation_id="EMV",
name="EMV-Richtlinie 2014/30/EU",
trigger={
"any": [
("has_software", "eq", True),
("has_embedded_software", "eq", True),
("has_radio_module", "eq", True),
]
},
required_facts=[],
fact_prompts={
"is_electrical": "Ist das Produkt ein elektrisches / elektronisches Betriebsmittel?",
},
legal_basis_refs=["EMV-RL 2014/30/EU Art. 2"],
summary="Elektrische/elektronische Betriebsmittel (hier aus den digitalen Elementen abgeleitet).",
confidence_when_applicable=Confidence.MEDIUM,
inferred=True,
),
RegulationRule(
regulation_id="DataAct",
name="Data Act (EU) 2023/2854",
trigger={
"all": [
{"any": [("has_cloud_connection", "eq", True), ("has_remote_access", "eq", True)]},
("generates_usage_data", "eq", True),
]
},
required_facts=["generates_usage_data"],
fact_prompts={
"generates_usage_data": "Erzeugt das vernetzte Produkt nutzbare Produkt-/Nutzungsdaten?",
},
legal_basis_refs=["Data Act (EU) 2023/2854 Art. 2(5)", "Art. 3-5"],
summary="Vernetzte Produkte, die Nutzungsdaten erzeugen und zugänglich machen.",
confidence_when_applicable=Confidence.HIGH,
),
RegulationRule(
regulation_id="NIS2",
name="NIS2-Richtlinie (EU) 2022/2555",
trigger=("is_essential_or_important_entity", "eq", True),
required_facts=["company_size", "sector", "is_essential_or_important_entity"],
fact_prompts={
"company_size": "Unternehmensgröße (Mitarbeiterzahl / Umsatz)?",
"sector": "In welchem Sektor ist das Unternehmen tätig (Anhang I/II)?",
"is_essential_or_important_entity": "Fällt das Unternehmen als wesentliche/wichtige Einrichtung unter NIS2?",
},
legal_basis_refs=["NIS2-RL (EU) 2022/2555 Art. 2", "Art. 3"],
summary="Adressiert die ORGANISATION (Größe/Sektor/Rolle), nicht das Produkt.",
confidence_when_applicable=Confidence.MEDIUM,
),
]
def regulation_rule(regulation_id: str) -> Optional[RegulationRule]:
for rule in REGULATION_RULES:
if rule.regulation_id == regulation_id:
return rule
return None