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:
@@ -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()
|
||||
Reference in New Issue
Block a user