#!/usr/bin/env python3 """Diff two openfut_snapshot_*.json files from snapshot_lineup_tables.lua. Usage: diff_snapshots.py before.json after.json Reports, per table, which rows changed and which fields differed — this is how the real DB write behind FLE's "Freeze Lineup" GUI feature gets discovered (see docs/foundational-xi-injection-test.md). """ import json import sys def row_key(row): for candidate in ("playerid", "player_id", "id", "rowid"): if candidate in row: return row[candidate] return json.dumps(row, sort_keys=True) def diff_table(name, before, after): before_rows = {row_key(r): r for r in before.get("rows", [])} after_rows = {row_key(r): r for r in after.get("rows", [])} changes = [] for key, after_row in after_rows.items(): before_row = before_rows.get(key) if before_row is None: changes.append({"row": key, "type": "added", "row_data": after_row}) continue field_diffs = {} for field in set(before_row) | set(after_row): bval = before_row.get(field) aval = after_row.get(field) if bval != aval: field_diffs[field] = {"before": bval, "after": aval} if field_diffs: changes.append({"row": key, "type": "modified", "fields": field_diffs}) for key in before_rows: if key not in after_rows: changes.append({"row": key, "type": "removed"}) if changes: print(f"\n=== {name}: {len(changes)} changed row(s) ===") for c in changes: print(json.dumps(c, indent=2)) def main(): if len(sys.argv) != 3: print(__doc__) sys.exit(1) with open(sys.argv[1]) as f: before = json.load(f) with open(sys.argv[2]) as f: after = json.load(f) table_names = set(before.get("tables", {})) | set(after.get("tables", {})) any_changes = False for name in sorted(table_names): b = before.get("tables", {}).get(name, {"rows": []}) a = after.get("tables", {}).get(name, {"rows": []}) before_len = len(b.get("rows", [])) after_len = len(a.get("rows", [])) if before_len != after_len: print(f"{name}: row count changed {before_len} -> {after_len}") any_changes = True diff_table(name, b, a) if not any_changes: print("(row-count-only check found no differences; per-field diffs above are authoritative)") if __name__ == "__main__": main()