#!/usr/bin/env python3 """ compare_snapshots.py Vergleicht zwei mit create_dataset.py erzeugte Snapshots (z. B. aus Version 6.8 und 7.0, jeweils mit derselben dataset.yaml erzeugt) und markiert: - status_changed: Datensatz ist in einer Version fehlgeschlagen, in der anderen nicht -> Kandidat für Format-/ Constraint-/Pflichtfeld-Änderung - value_changed: gespeicherter Wert unterscheidet sich zwischen den Versionen, obwohl derselbe Eingabewert verwendet wurde -> Kandidat für automatische Reformatierung - missing_record: Datensatz-Key nur in einem der beiden Snapshots vorhanden (z. B. weil er in dataset.yaml erst später ergänzt wurde) Nutzung: python3 compare_snapshots.py snapshot_68.json snapshot_70.json \\ --label-a 6.8 --label-b 7.0 Weiter geht's dann typischerweise so: Modellname aus einem Fund hier nehmen, den zugehörigen Tabellennamen bilden (Punkte durch Unterstriche ersetzen, wie es extract_updates.py auch tut) und in den AST-Analyse-Ergebnissen danach filtern: jq '.files[].operations[] | select(.tables[]? == "party_identifier")' \\ data/7.0/party.json """ import argparse import json def load(path): return json.load(open(path, encoding="utf-8")) def index_by_key(snapshot): return {r["key"]: r for r in snapshot["results"]} def compare(snap_a, snap_b, label_a, label_b): a = index_by_key(snap_a) b = index_by_key(snap_b) findings = [] for key in sorted(set(a) | set(b)): ra, rb = a.get(key), b.get(key) if ra is None or rb is None: findings.append({ "key": key, "kind": "missing_record", "detail": f"nur in {label_b if ra is None else label_a} vorhanden", }) continue if ra["status"] != rb["status"]: findings.append({ "key": key, "model": ra["model"], "kind": "status_changed", "detail": ( f"{label_a}: {ra['status']} ({ra.get('error')}) | " f"{label_b}: {rb['status']} ({rb.get('error')})" ), }) continue if ra["status"] == "error": continue # beide fehlgeschlagen - nichts auf Feldebene zu vergleichen fields_a, fields_b = ra["stored"], rb["stored"] for field in sorted(set(fields_a) | set(fields_b)): va = fields_a.get(field, "") vb = fields_b.get(field, "") if va != vb: findings.append({ "key": key, "model": ra["model"], "field": field, "kind": "value_changed", "detail": f"{label_a}={va!r} | {label_b}={vb!r}", }) return findings def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("snapshot_a") parser.add_argument("snapshot_b") parser.add_argument("--label-a", default="A") parser.add_argument("--label-b", default="B") args = parser.parse_args() findings = compare( load(args.snapshot_a), load(args.snapshot_b), args.label_a, args.label_b ) if not findings: print("Keine Unterschiede gefunden.") return by_model = {} for f in findings: by_model.setdefault(f.get("model", "(kein Modell)"), []).append(f) for model, items in sorted(by_model.items()): if model == "(kein Modell)": print(f"\n=== {model} - {len(items)} Fund(e) ===") else: table = model.replace(".", "_") print(f"\n=== {model} (Tabelle vermutlich: {table}) - {len(items)} Fund(e) ===") for item in items: field_suffix = f".{item['field']}" if "field" in item else "" print(f" [{item['kind']}] {item['key']}{field_suffix}: {item['detail']}") if __name__ == "__main__": main()