aa99111a87
Phase A½. The move from feature to product development: for every assessment, answer "how sure are we that this answer is COMPLETE?" — different from confidence. The product never claims full coverage; it makes its own knowledge state transparent and auditable. Shows what we do NOT know and why. - compliance/completeness/: assess_completeness(identified, corpus_status, uncertain, assumptions, assessed_obligations) -> CompletenessReport. Separates IDENTIFIED from ASSESSED (validated corpus AND determined applicability) and justifies every gap. Two kinds of open: corpus gap (future_corpus) and applicability uncertainty (query_required + deciding question, e.g. Data Act / generates_usage_data). - The metric is COUNTS, never a single percentage: "Identifiziert N · bewertet M · offen K · Unsicherheiten U · Begründung ja" + an honest audit statement. - ADR-007: auditable honesty; phase order A factory -> A½ Completeness -> B new domains; the transparency selling point. Deterministic, no LLM; corpus status + obligation count injected. - reference suite: "Regulatory Completeness" section runs an industrial-dishwasher assessment (assessed CRA/MaschinenVO; open EMV/Environmental=future_corpus, Data Act=query_required) and notes Environmental flips open->validated automatically once the corpus lands. 11 completeness tests (54 with adjacent modules), mypy --strict clean (15 files), check-loc 0. Product code with no app caller + ADR/reference = non-runtime -> no deploy (ADR-001). Freeze-safe. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
4.6 KiB
Python
90 lines
4.6 KiB
Python
"""Regulatory Completeness Engine — measure auditable knowledge coverage for an assessment.
|
|
|
|
Separates what we IDENTIFIED (triggered regulations) from what we ASSESSED (validated corpus AND
|
|
determined applicability), and justifies every gap. Two kinds of „open":
|
|
- corpus gap — no validated corpus yet (e.g. Environmental) -> future_corpus
|
|
- applicability open — corpus exists but applicability is uncertain (Data Act) -> query_required
|
|
The metric is COUNTS, never a single percentage. The audit statement says plainly „wir bewerteten M
|
|
von N Domänen; K sind nicht im validierten Korpus und wurden bewusst nicht bewertet".
|
|
|
|
Deterministic, computed-not-stored, no LLM, no new corpus/meta-model class (freeze v1.0). Python 3.9.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from .schemas import (
|
|
Assumption, CompletenessReport, CorpusStatus, DomainCoverage, Exclusion,
|
|
)
|
|
|
|
_VALID = {s.value for s in CorpusStatus}
|
|
|
|
|
|
def _status(corpus_status: Dict[str, str], reg: str) -> CorpusStatus:
|
|
raw = corpus_status.get(reg, "unknown")
|
|
return CorpusStatus(raw) if raw in _VALID else CorpusStatus.UNKNOWN
|
|
|
|
|
|
def assess_completeness(
|
|
identified_regulations: List[str],
|
|
corpus_status: Dict[str, str],
|
|
uncertain: Optional[List[Dict[str, Any]]] = None,
|
|
assumptions: Optional[List[Dict[str, Any]]] = None,
|
|
assessed_obligations: int = 0,
|
|
) -> CompletenessReport:
|
|
"""Build the auditable coverage report.
|
|
|
|
`identified_regulations`: triggered/identified for this product. `corpus_status`: regulation ->
|
|
one of validated/draft/unsupported/unknown (curated/injected corpus registry). `uncertain`:
|
|
applicability-uncertain regulations [{regulation, deciding_question, reason}]. `assumptions`:
|
|
[{key, value, note}]. `assessed_obligations`: count from Execution (injected, default 0).
|
|
"""
|
|
ids = sorted(set(identified_regulations))
|
|
unc = uncertain or []
|
|
unc_subjects = {str(u.get("regulation") or u.get("subject")) for u in unc if (u.get("regulation") or u.get("subject"))}
|
|
|
|
coverage = [DomainCoverage(regulation=r, status=_status(corpus_status, r)) for r in ids]
|
|
assessed = [r for r in ids if _status(corpus_status, r) == CorpusStatus.VALIDATED and r not in unc_subjects]
|
|
open_regs = [r for r in ids if r not in assessed]
|
|
open_corpora = [r for r in ids if _status(corpus_status, r) in (CorpusStatus.UNSUPPORTED, CorpusStatus.UNKNOWN)]
|
|
|
|
exclusions: List[Exclusion] = []
|
|
for u in unc:
|
|
subj = str(u.get("regulation") or u.get("subject") or "")
|
|
if not subj:
|
|
continue
|
|
exclusions.append(Exclusion(
|
|
subject=subj, reason=str(u.get("reason", "Anwendbarkeit unsicher")),
|
|
deciding_question=str(u.get("deciding_question", "")), resolution="query_required"))
|
|
for r in open_regs:
|
|
if r in unc_subjects:
|
|
continue
|
|
st = _status(corpus_status, r)
|
|
if st == CorpusStatus.DRAFT:
|
|
exclusions.append(Exclusion(subject=r, reason="Korpus in Bearbeitung (draft)", resolution="in_review"))
|
|
else:
|
|
exclusions.append(Exclusion(subject=r, reason="nicht im validierten Korpus", resolution="future_corpus"))
|
|
|
|
covered_subjects = {e.subject for e in exclusions}
|
|
justification = (not open_regs) or set(open_regs) <= covered_subjects
|
|
assumptions_m = [Assumption(key=str(a.get("key", "")), value=str(a.get("value", "")), note=str(a.get("note", ""))) for a in (assumptions or [])]
|
|
|
|
summary = "Identifiziert %d · bewertet %d · offen %d · Unsicherheiten %d · Begründung %s" % (
|
|
len(ids), len(assessed), len(open_regs), len(unc), "ja" if justification else "nein")
|
|
if open_regs:
|
|
audit = (
|
|
"Für dieses Produkt konnten wir %d von %d identifizierten regulatorischen Domänen vollständig "
|
|
"bewerten. %d weitere %s noch nicht Bestandteil des validierten Korpus bzw. anwendungsunsicher "
|
|
"und wurden deshalb bewusst nicht bewertet." % (
|
|
len(assessed), len(ids), len(open_regs), "ist" if len(open_regs) == 1 else "sind"))
|
|
else:
|
|
audit = "Für dieses Produkt konnten wir alle %d identifizierten regulatorischen Domänen vollständig bewerten." % len(ids)
|
|
|
|
return CompletenessReport(
|
|
identified_regulations=ids, assessed_regulations=assessed, open_regulations=open_regs,
|
|
open_corpora=open_corpora, coverage=coverage, assumptions=assumptions_m, exclusions=exclusions,
|
|
uncertainties_count=len(unc), assessed_obligations=assessed_obligations,
|
|
justification_present=justification, completeness_summary=summary, audit_statement=audit,
|
|
)
|