feat(rci): Regulatory Change Intelligence foundation (delta over the stored map)

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>
This commit is contained in:
Benjamin Admin
2026-06-26 13:45:23 +02:00
parent da466b3821
commit a5687bbc65
5 changed files with 432 additions and 0 deletions
@@ -0,0 +1,44 @@
"""Snapshot the current product-first pipeline into a ComplianceBaseline.
This is the ONLY place RCI runs the pipeline — to freeze a point-in-time map +
registry-linked obligations + their required evidence. Everything downstream
(delta computation) works purely against this snapshot, never re-evaluating.
"""
from __future__ import annotations
from typing import Dict, List, Optional
from compliance.profile.canonical import CanonicalProductRegulatoryProfile
from compliance.profile.to_reasoning import to_reasoning_profile
from compliance.reasoning.obligation_engine import derive_obligations
from compliance.regulatory_map.renderer import render_regulatory_map
from .schemas import ComplianceBaseline
def create_baseline(
profile: CanonicalProductRegulatoryProfile,
evidence_refs: Optional[Dict[str, List[str]]] = None,
baseline_id: str = "baseline",
created_at: Optional[str] = None,
) -> ComplianceBaseline:
reg_map = render_regulatory_map(profile)
obligations = derive_obligations(to_reasoning_profile(profile)).applicable_obligations
applicable: List[str] = []
required: Dict[str, List[str]] = {}
for ob in obligations:
if ob.registry_anchor: # only registry-linked obligations enter the baseline
applicable.append(ob.obligation_id)
required[ob.obligation_id] = list(ob.required_evidence)
return ComplianceBaseline(
baseline_id=baseline_id,
product_profile_snapshot=profile,
regulatory_map_snapshot=reg_map,
applicable_obligations=applicable,
obligation_evidence_required=required,
evidence_refs=dict(evidence_refs or {}),
created_at=created_at,
)