#!/usr/bin/env python3 """ model_diff.py Vergleicht zwei mit extract_model_tree.py erzeugte Modellbäume (z. B. model_tree_5.0.json gegen model_tree_8.0.json) auf Modell- und Feldebene und sortiert jedes Modell in eine von vier Kategorien: - entfernt: Modell gibt es in der ersten Version, aber nicht mehr in der zweiten. - neu: Modell gibt es erst in der zweiten Version. - geändert: Modell existiert in beiden, aber mind. ein Feld wurde entfernt oder hat sich (Typ/Relation/required) geändert. Das sind die Kandidaten, bei denen sich ein echter Testlauf gegen beide Versionen lohnt. - unverändert / nur ergänzt: Modell ist identisch geblieben oder hat nur zusätzliche Felder bekommen - hier ist über den reinen Code-Vergleich hinaus erstmal nichts Auffälliges zu erwarten (Laufzeitverhalten wie Constraints/Defaults kann natürlich trotzdem abweichen, das sieht man mit dieser rein statischen Analyse nicht). Nutzung: python3 model_diff.py --tree-a reports/model_tree_5.0.json --label-a 5.0 \\ --tree-b reports/model_tree_8.0.json --label-b 8.0 \\ --output reports/model_diff.json --report reports/model_diff.md """ import argparse import json from pathlib import Path def diff_field(fa, fb): changes = [] if fa["type"] != fb["type"]: changes.append(f"type: {fa['type']} -> {fb['type']}") if fa["relation"] != fb["relation"]: changes.append(f"relation: {fa['relation']} -> {fb['relation']}") if fa["required"] != fb["required"]: changes.append(f"required: {fa['required']} -> {fb['required']}") return changes def diff_trees(tree_a, tree_b): result = {} for model_name in sorted(set(tree_a) | set(tree_b)): if model_name not in tree_b: result[model_name] = {"category": "entfernt", "added": [], "removed": [], "changed": {}} continue if model_name not in tree_a: result[model_name] = {"category": "neu", "added": [], "removed": [], "changed": {}} continue fields_a = tree_a[model_name]["fields"] fields_b = tree_b[model_name]["fields"] added = sorted(set(fields_b) - set(fields_a)) removed = sorted(set(fields_a) - set(fields_b)) changed = {} for name in sorted(set(fields_a) & set(fields_b)): c = diff_field(fields_a[name], fields_b[name]) if c: changed[name] = c if removed or changed: category = "geändert" elif added: category = "nur ergänzt" else: category = "unverändert" result[model_name] = { "category": category, "added": added, "removed": removed, "changed": changed, } return result def write_report(diff, out_path, label_a, label_b): by_category = {} for model_name, info in diff.items(): by_category.setdefault(info["category"], []).append(model_name) lines = [f"# Modellbaum-Diff: {label_a} -> {label_b}\n"] for category, count_note in [ ("entfernt", "Modell existiert nicht mehr"), ("geändert", "Feld(er) entfernt oder geändert - Kandidat für einen echten Vergleichslauf"), ("neu", f"Modell gibt es erst ab {label_b}"), ("nur ergänzt", "nur neue Felder dazugekommen, Rest unverändert"), ("unverändert", "komplett identisch geblieben"), ]: models = sorted(by_category.get(category, [])) lines.append(f"\n## {category} ({len(models)}) - {count_note}\n") for model_name in models: info = diff[model_name] if category == "geändert": lines.append(f"\n### `{model_name}`") if info["removed"]: lines.append(f"- entfernte Felder: {', '.join(info['removed'])}") for field_name, changes in info["changed"].items(): lines.append(f"- **{field_name}**: {'; '.join(changes)}") if info["added"]: lines.append(f"- (zusätzlich neue Felder: {', '.join(info['added'])})") else: lines.append(f"- `{model_name}`") out_path.write_text("\n".join(lines) + "\n", encoding="utf-8") def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--tree-a", required=True) parser.add_argument("--tree-b", required=True) parser.add_argument("--label-a", default="A") parser.add_argument("--label-b", default="B") parser.add_argument("--output", default="reports/model_diff.json") parser.add_argument("--report", default="reports/model_diff.md") args = parser.parse_args() tree_a = json.loads(Path(args.tree_a).read_text(encoding="utf-8")) tree_b = json.loads(Path(args.tree_b).read_text(encoding="utf-8")) diff = diff_trees(tree_a, tree_b) Path(args.output).write_text(json.dumps(diff, indent=2, ensure_ascii=False, sort_keys=True), encoding="utf-8") write_report(diff, Path(args.report), args.label_a, args.label_b) counts = {} for info in diff.values(): counts[info["category"]] = counts.get(info["category"], 0) + 1 summary = ", ".join(f"{v} {k}" for k, v in counts.items()) print(f"{summary} -> {args.output}, {args.report}") if __name__ == "__main__": main()