"""P3 — Compliance-Advisor-Proof: obligation-basierte Antwort aus der Registry (NICHT RAG-Text, KEIN LLM). Demonstriert den besseren Antworttyp: PFLICHT (LEGAL_MINIMUM + Rechtsgrundlage + Applicability) / BEST PRACTICE (guidance_basis) / NACHWEISE (evidence_facets + member controls) + Beziehungen. Deterministisch + zitierfähig — das ist der Produktnutzen der Registry. python3 scripts/obligation_discovery/advisor_proof.py --registry obligations/cra.json \ --topic sbom --has-digital-elements """ from __future__ import annotations import argparse import json def applies(obl: dict, has_digital: bool) -> tuple[bool, str]: a = obl.get("applicability", "universal") if a == "universal": return True, "" if a.startswith("domain:products_with_digital_elements"): return (has_digital, "nur für Produkte mit digitalen Elementen (CRA Art. 3)") if a.startswith("domain:"): return True, a.split(":", 1)[1] if a.startswith("conditional:"): return True, f"bedingt: {a.split(':',1)[1]}" return True, "" def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--registry", required=True) ap.add_argument("--topic", default="sbom", help="Family/Keyword der Pflicht (z.B. sbom)") ap.add_argument("--has-digital-elements", action="store_true") ap.add_argument("--question", default="Muss ich als Maschinenbauer eine SBOM bereitstellen?") a = ap.parse_args() reg = json.load(open(a.registry, encoding="utf-8")) obls = [o for o in reg["obligations"] if a.topic in o.get("family", "") or a.topic in o["id"] or a.topic in o.get("name", "").lower()] ids = {o["id"] for o in obls} pflicht = [o for o in obls if o["tier"] == "LEGAL_MINIMUM"] best = [o for o in obls if o["tier"] in ("BEST_PRACTICE", "IMPLEMENTATION_GUIDANCE")] print(f"FRAGE: {a.question}") print(f"KONTEXT: Hersteller; digitale Elemente = {a.has_digital_elements} → CRA-Geltungsbereich") applicable_pflicht = [(o, applies(o, a.has_digital_elements)) for o in pflicht] any_pflicht = any(ok for _, (ok, _) in applicable_pflicht) n_pflicht = sum(1 for _, (ok, _) in applicable_pflicht if ok) verdict = "JA" if any_pflicht and a.has_digital_elements else "NUR WENN CRA-anwendbar" print(f"\nANTWORT: {verdict} — unter dem CRA bestehen {n_pflicht} gesetzliche " f"Mindestpflichten zum Thema {a.topic}.\n") print("── PFLICHT (LEGAL_MINIMUM, Wortlaut im CRA) ──") for o, (ok, note) in applicable_pflicht: if not ok: continue lb = "; ".join(f"{b.get('source','')} {b.get('anchor','')}".strip() for b in o.get("legal_basis", [])) print(f" • {o['id']} — {o.get('description','')[:84]}") print(f" Rechtsgrundlage: {lb or '—'}" + (f" | {note}" if note else "")) print("\n── BEST PRACTICE (anerkannte Umsetzung, KEINE CRA-Wortlautpflicht) ──") for o in best: gb = "; ".join(b.get("source", "") for b in o.get("guidance_basis", [])) print(f" • {o['id']} — {o.get('description','')[:74]} | Guidance: {gb or '—'}") print("\n── NACHWEISE (Evidence) ──") for o in pflicht + best: f = o.get("evidence_facets", {}) facets = "/".join(k for k in ("governance", "capability", "evidence") if f.get(k)) or "—" print(f" • {o['id']}: {o.get('member_count','?')} Controls · Facetten {facets}") print("\n── BEZIEHUNGEN (warum es zählt) ──") for r in reg.get("relationships", []): if r.get("from") in ids and r.get("to") not in ids: print(f" • {r['from']} --{r['type']}--> {r['to']}: {r.get('note','')[:70]}") if __name__ == "__main__": main()