feat(registry-quality): scope-Achse — 2 out_of_scope + derived_obligation (User Option 2)

User-Entscheidung 2026-07-01 zum Scope-Audit: Adressat der Norm != Handlungspflicht des
Herstellers. Neue `scope`-Attribut-Achse (Enum, KEINE neue Objektklasse -> Freeze v1.0
unberuehrt): in_scope (default) / out_of_scope / derived_obligation.

- sanctions + market_surveillance_safeguard -> out_of_scope (reine Staats-/Durchsetzungs-
  bestimmungen; Praezedenz CSIRT/ENISA im CRA-Vuln-Cut). Aus join_keys gefiltert.
- notified_body_requirements -> derived_obligation (Norm adressiert primaer die notifizierte
  Stelle, erzeugt aber mittelbare Herstellerpflichten: NB einbeziehen + Unterlagen +
  Konformitaetsbewertung) + scope_split_candidate (spaetere Aufspaltung Normadressat <->
  abgeleitete Herstellerpflicht). BLEIBT im Set (Prinzip: Wissen nicht zu frueh verwerfen).
- export_join_keys.py filtert scope==out_of_scope + fuehrt scope je Eintrag -> join_keys
  126->124 (MaschVO 31->29; 123 in_scope + 1 derived_obligation).
- scope_audit.py jetzt 3-Wege-klassifikations-bewusst (0 unklassifizierte Reste) +
  apply_scope_classification.py (deterministisch). Fuer jeden kuenftigen Cut mitlaufen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-07-01 13:22:38 +02:00
parent e54d07f2d9
commit 6523286af6
6 changed files with 539 additions and 372 deletions
@@ -23,12 +23,15 @@ def main() -> None:
for path in a.registries:
reg = json.load(open(path, encoding="utf-8"))
for o in reg.get("obligations", []):
if o.get("scope") == "out_of_scope":
continue # Institutions-/Enforcement-Recht (Adressat != Hersteller) -> nicht im Join-Vertrag
citation_units = [b.get("anchor", "") for b in o.get("legal_basis", []) if b.get("anchor")]
keys.append({
"obligation_id": o["id"],
"regulation": reg.get("regulation", ""),
"family": o.get("family", ""),
"tier": o.get("tier", ""),
"scope": o.get("scope", "in_scope"),
"citation_units": citation_units,
"source_role": o.get("source_role", ""),
})