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,216 @@
|
||||
"""Pydantic domain objects for the Regulatory Reasoning Engine.
|
||||
|
||||
Trigger facts that drive scope are tri-state (`Optional[bool] = None`): `None`
|
||||
means "fact unknown" and produces an *uncertain* verdict plus a concrete
|
||||
missing-fact prompt — never silent false security (spec §6.3).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .enums import (
|
||||
ApplicabilityStatus,
|
||||
AuthorityLevel,
|
||||
Confidence,
|
||||
CoverageStatus,
|
||||
InterpretationVerdict,
|
||||
ManufacturerRole,
|
||||
MarketModel,
|
||||
OverlapType,
|
||||
ProductLifecyclePhase,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Input
|
||||
# ---------------------------------------------------------------------------
|
||||
class ProductProfile(BaseModel):
|
||||
"""The customer's product / system. Tri-state booleans => unknown facts."""
|
||||
|
||||
product_name: str
|
||||
product_profile_id: Optional[str] = None
|
||||
manufacturer_role: Optional[ManufacturerRole] = None
|
||||
product_type: List[str] = Field(default_factory=list)
|
||||
|
||||
has_software: Optional[bool] = None
|
||||
has_embedded_software: Optional[bool] = None
|
||||
has_remote_access: Optional[bool] = None
|
||||
has_cloud_connection: Optional[bool] = None
|
||||
has_ai_functionality: Optional[bool] = None
|
||||
has_radio_module: Optional[bool] = None
|
||||
has_safety_function: Optional[bool] = None
|
||||
generates_usage_data: Optional[bool] = None
|
||||
|
||||
is_machine: Optional[bool] = None
|
||||
is_component: Optional[bool] = None
|
||||
is_spare_part: Optional[bool] = None
|
||||
|
||||
placed_on_market_after: Optional[date] = None
|
||||
intended_use: Optional[str] = None
|
||||
eu_market: Optional[bool] = None
|
||||
b2b_or_b2c: Optional[MarketModel] = None
|
||||
lifecycle_phase: Optional[ProductLifecyclePhase] = None
|
||||
|
||||
# Organisation context — only needed for NIS2 (not a product fact).
|
||||
company_size: Optional[str] = None
|
||||
sector: Optional[str] = None
|
||||
is_essential_or_important_entity: Optional[bool] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scope
|
||||
# ---------------------------------------------------------------------------
|
||||
class ApplicableRegulation(BaseModel):
|
||||
regulation_id: str
|
||||
name: str
|
||||
applicability_status: ApplicabilityStatus
|
||||
trigger_facts: List[str] = Field(default_factory=list)
|
||||
legal_basis_refs: List[str] = Field(default_factory=list)
|
||||
confidence: Confidence
|
||||
explanation: str
|
||||
|
||||
|
||||
class ExcludedRegulation(BaseModel):
|
||||
regulation_id: str
|
||||
name: str
|
||||
reason: str
|
||||
|
||||
|
||||
class UncertainRegulation(BaseModel):
|
||||
regulation_id: str
|
||||
name: str
|
||||
missing_facts: List[str] = Field(default_factory=list)
|
||||
explanation: str
|
||||
|
||||
|
||||
class RegulatoryScope(BaseModel):
|
||||
product_profile_id: Optional[str] = None
|
||||
applicable_regulations: List[ApplicableRegulation] = Field(default_factory=list)
|
||||
excluded_regulations: List[ExcludedRegulation] = Field(default_factory=list)
|
||||
uncertain_regulations: List[UncertainRegulation] = Field(default_factory=list)
|
||||
missing_facts: List[str] = Field(default_factory=list)
|
||||
confidence: Confidence = Confidence.MEDIUM
|
||||
reasoning_summary: str = ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Obligations
|
||||
# ---------------------------------------------------------------------------
|
||||
class ApplicableObligation(BaseModel):
|
||||
obligation_id: str
|
||||
title: str
|
||||
source_regulation: str
|
||||
legal_basis_refs: List[str] = Field(default_factory=list)
|
||||
obligation_text: str
|
||||
authority_level: AuthorityLevel
|
||||
applies_because: List[str] = Field(default_factory=list)
|
||||
applies_to_role: List[str] = Field(default_factory=list)
|
||||
lifecycle_phase: List[str] = Field(default_factory=list)
|
||||
overlap_group_id: Optional[str] = None
|
||||
required_evidence: List[str] = Field(default_factory=list)
|
||||
confidence: Confidence
|
||||
# True only when obligation_id is owned by the Legal-KG registry (CRA P1).
|
||||
registry_anchor: bool = False
|
||||
# Machine/Data-Act obligations the registry has not canonicalised yet.
|
||||
proposed: bool = False
|
||||
|
||||
|
||||
class ObligationOverlap(BaseModel):
|
||||
overlap_group_id: str
|
||||
obligations: List[str] = Field(default_factory=list)
|
||||
overlap_type: OverlapType
|
||||
canonical_obligation_id: str
|
||||
explanation: str
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Customer claims & assessments
|
||||
# ---------------------------------------------------------------------------
|
||||
class CustomerImplementationClaim(BaseModel):
|
||||
claim_id: str
|
||||
raw_statement: str
|
||||
normalized_claim: str = ""
|
||||
claimed_capability: List[str] = Field(default_factory=list)
|
||||
related_topics: List[str] = Field(default_factory=list)
|
||||
qualifiers: List[str] = Field(default_factory=list)
|
||||
evidence_refs: List[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ImplementationAssessment(BaseModel):
|
||||
claim_id: str
|
||||
obligation_id: str
|
||||
coverage_status: CoverageStatus
|
||||
missing_elements: List[str] = Field(default_factory=list)
|
||||
required_evidence: List[str] = Field(default_factory=list)
|
||||
explanation: str
|
||||
confidence: Confidence
|
||||
|
||||
|
||||
class InterpretationAssessment(BaseModel):
|
||||
interpretation_id: str
|
||||
raw_interpretation: str
|
||||
affected_regulations: List[str] = Field(default_factory=list)
|
||||
affected_obligations: List[str] = Field(default_factory=list)
|
||||
assessment: InterpretationVerdict
|
||||
risks: List[str] = Field(default_factory=list)
|
||||
corrected_interpretation: str = ""
|
||||
legal_basis_refs: List[str] = Field(default_factory=list)
|
||||
explanation: str
|
||||
confidence: Confidence
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# API request / response envelopes
|
||||
# ---------------------------------------------------------------------------
|
||||
class ScopeRequest(BaseModel):
|
||||
product_profile: ProductProfile
|
||||
|
||||
|
||||
class ScopeResponse(BaseModel):
|
||||
regulatory_scope: RegulatoryScope
|
||||
missing_facts: List[str] = Field(default_factory=list)
|
||||
confidence: Confidence
|
||||
|
||||
|
||||
class ObligationsRequest(BaseModel):
|
||||
product_profile: ProductProfile
|
||||
regulatory_scope: Optional[RegulatoryScope] = None
|
||||
|
||||
|
||||
class ObligationsResponse(BaseModel):
|
||||
applicable_obligations: List[ApplicableObligation] = Field(default_factory=list)
|
||||
overlaps: List[ObligationOverlap] = Field(default_factory=list)
|
||||
excluded_obligations: List[str] = Field(default_factory=list)
|
||||
evidence_for_multiple: Dict[str, List[str]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ImplementationRequest(BaseModel):
|
||||
product_profile: ProductProfile
|
||||
customer_claim: str
|
||||
|
||||
|
||||
class ImplementationResponse(BaseModel):
|
||||
claim: CustomerImplementationClaim
|
||||
assessments: List[ImplementationAssessment] = Field(default_factory=list)
|
||||
missing_evidence: List[str] = Field(default_factory=list)
|
||||
summary: str = ""
|
||||
|
||||
|
||||
class InterpretationRequest(BaseModel):
|
||||
product_profile: Optional[ProductProfile] = None
|
||||
customer_interpretation: str
|
||||
|
||||
|
||||
class InterpretationResponse(BaseModel):
|
||||
assessment: InterpretationVerdict
|
||||
affected_regulations: List[str] = Field(default_factory=list)
|
||||
affected_obligations: List[str] = Field(default_factory=list)
|
||||
corrected_interpretation: str = ""
|
||||
risks: List[str] = Field(default_factory=list)
|
||||
legal_basis_refs: List[str] = Field(default_factory=list)
|
||||
explanation: str = ""
|
||||
confidence: Confidence = Confidence.MEDIUM
|
||||
Reference in New Issue
Block a user