a5687bbc65
RCI/Delta as a read-/reasoning layer ON TOP of the product-first pipeline. Answers "what changes relative to my existing Regulatory Map?" — NOT "what does the new law say in general". No UI, no ingestion (newsletter/mailbox), no RAG, no new regulations/controls, no legal evaluation outside the stored map. - 4 core objects (compliance/rci/schemas.py): ComplianceBaseline (snapshot of profile + map + registry obligations + required/present evidence), RegulatoryChange (simulated/provided INPUT), ObligationDelta (delta_type NEW|CHANGED|REMOVED| ALREADY_COVERED|NEEDS_REVIEW|NOT_APPLICABLE), ChangeImpactSummary. delta_type is a THIRD vocabulary, disjoint from ClaimCoverage (Welt 1) and ComplianceStatus (Welt 2). - create_baseline() snapshots the existing pipeline once; assess_change() computes deltas deterministically against the snapshot (no re-evaluation). - 12 tests = the 5 acceptance questions (affects product? new/changed? already covered by evidence? needs human review? not relevant?) + repeal/uncertain-reg/ missing-evidence/boundary. Existing pipeline tests stay green; mypy clean; LOC ok. - App/reasoning types only — no compliance-meta-model classes (freeze v1.0 untouched). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
93 lines
4.1 KiB
Python
93 lines
4.1 KiB
Python
"""Regulatory Change Intelligence (RCI) — domain objects.
|
|
|
|
RCI is a read-/reasoning layer ON TOP of the product-first pipeline. It answers
|
|
"what changes relative to my existing Regulatory Map?" — NOT "what does the new
|
|
law say in general". A RegulatoryChange is simulated/provided INPUT (no ingestion,
|
|
no newsletter/mailbox, no RAG); the delta is computed against a stored
|
|
ComplianceBaseline (snapshot of the map).
|
|
|
|
`delta_type` is a THIRD vocabulary — distinct from `ClaimCoverage` (Welt 1, what
|
|
the customer claims) and `ComplianceStatus` (Welt 2, verified evidence). The three
|
|
must never be conflated. These are application/reasoning types, NOT
|
|
compliance-meta-model classes (architecture freeze v1.0 untouched).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import Dict, List, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from compliance.profile.canonical import CanonicalProductRegulatoryProfile
|
|
from compliance.reasoning.enums import AuthorityLevel, Confidence
|
|
from compliance.regulatory_map.schemas import RegulatoryMap
|
|
|
|
|
|
class DeltaType(str, Enum):
|
|
NEW = "new" # obligation now applies that was not in the baseline
|
|
CHANGED = "changed" # existing obligation substantively modified
|
|
REMOVED = "removed" # obligation no longer applies (repeal)
|
|
ALREADY_COVERED = "already_covered" # existing obligation, evidence likely suffices
|
|
NEEDS_REVIEW = "needs_review" # a human must check
|
|
NOT_APPLICABLE = "not_applicable" # change does not touch this product's map
|
|
|
|
|
|
class ChangeType(str, Enum):
|
|
NEW_REGULATION = "new_regulation"
|
|
AMENDMENT = "amendment"
|
|
REPEAL = "repeal"
|
|
GUIDANCE_UPDATE = "guidance_update"
|
|
|
|
|
|
# ── stored snapshot ──────────────────────────────────────────────────────
|
|
class ComplianceBaseline(BaseModel):
|
|
baseline_id: str
|
|
product_profile_snapshot: CanonicalProductRegulatoryProfile
|
|
regulatory_map_snapshot: RegulatoryMap
|
|
applicable_obligations: List[str] = Field(default_factory=list) # registry-linked obligation_ids
|
|
# required evidence per obligation (derived) — to compute missing_evidence
|
|
obligation_evidence_required: Dict[str, List[str]] = Field(default_factory=dict)
|
|
# evidence the customer ALREADY has, per obligation (provided)
|
|
evidence_refs: Dict[str, List[str]] = Field(default_factory=dict)
|
|
created_at: Optional[str] = None
|
|
|
|
|
|
# ── simulated/provided change (INPUT — never ingested) ───────────────────
|
|
class RegulatoryChange(BaseModel):
|
|
change_id: str
|
|
source: str = "simulated"
|
|
affected_regulations: List[str] = Field(default_factory=list)
|
|
affected_obligations: List[str] = Field(default_factory=list)
|
|
change_type: ChangeType
|
|
effective_date: Optional[str] = None
|
|
authority_level: AuthorityLevel = AuthorityLevel.LEGAL_TEXT
|
|
summary: str = ""
|
|
|
|
|
|
# ── per-obligation delta ─────────────────────────────────────────────────
|
|
class ObligationDelta(BaseModel):
|
|
obligation_id: str
|
|
delta_type: DeltaType
|
|
reason: str
|
|
affected_evidence: List[str] = Field(default_factory=list) # evidence already present for it
|
|
missing_evidence: List[str] = Field(default_factory=list) # required but not yet present
|
|
confidence: Confidence
|
|
|
|
|
|
# ── management-level summary ──────────────────────────────────────────────
|
|
class ChangeImpactSummary(BaseModel):
|
|
what_changed: str = ""
|
|
what_matters_for_this_product: List[str] = Field(default_factory=list) # need action
|
|
already_covered: List[str] = Field(default_factory=list)
|
|
needs_review: List[str] = Field(default_factory=list)
|
|
not_relevant: List[str] = Field(default_factory=list)
|
|
unsupported_domains: List[str] = Field(default_factory=list)
|
|
|
|
|
|
class ChangeAssessment(BaseModel):
|
|
change_id: str
|
|
affects_product: bool
|
|
deltas: List[ObligationDelta] = Field(default_factory=list)
|
|
summary: ChangeImpactSummary
|