Proteus-Zweig: dataset.yaml-Pipeline und 5.0/8.0-Modellbaumvergleich

- create_dataset.py, compare_snapshots.py, rst_to_dataset.py: Proteus-
  Testdatensatz anlegen/vergleichen bzw. aus *.rst-Szenarien extrahieren
- create_dataset.py: Feld-Alias-Fallback (FIELD_RENAMES) für bekannte
  Umbenennungen wie stock.move.uom -> unit, bidirektional
- merge_dedup_rsts.py: Extrahierte YAMLs pro Version zusammenführen,
  versionsinterne Duplikate entfernen, abhängigkeitskorrekt sortieren
- extract_model_tree.py: Modellbaum (Modelle/Felder) statisch aus dem
  Tryton-Quellcode ziehen, für 5.0 und 8.0
- model_diff.py, model_coverage.py: 5.0<->8.0-Modelländerungen finden und
  gegen die aktuelle Testdatenabdeckung abgleichen, priorisiert nach
  tatsächlicher Änderung
- reports/*_merged.yaml: Extraktionsergebnisse für die aktiven
  Module (rohe *.rst-Quellen bewusst nicht mitversioniert)
- Ordner doc ergänzt
This commit is contained in:
2026-08-22 10:52:13 +02:00
parent b549063ff6
commit c0dd24b6c5
38 changed files with 26136 additions and 20 deletions
+124
View File
@@ -0,0 +1,124 @@
#!/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, "<fehlt>")
vb = fields_b.get(field, "<fehlt>")
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()