#!/usr/bin/env python3 """Locked process v1.1: temporal delta — compare two raw captures, emit DELTA.md. Usage: audit_diff.py BEFORE_RUN_DIR AFTER_RUN_DIR Compares the LATEST capture in each run dir across the fields the audit scores (name, phone, address, hours per day, rating, reviews, website, category, description, schema presence) and writes DELTA.md into the AFTER dir. This is the proof-of-fix artifact: after the client applies the recommendations, re-run the pipeline and the delta is the invoice evidence. Exit 0 always (a diff is a result, not an error); DELTA.md lists every change and confirms unchanged fields. """ import json, os, sys, glob sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import report_generate as rg # latest_capture + normalization DAYS = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] def load(d): if os.path.isfile(d): f = d else: f = rg.latest_capture(d) raw = json.load(open(f)) src = raw.get("sources", {}) g = src.get("google_business_profile") or {} a = src.get("apple_maps") or {} w = src.get("website") if isinstance(src.get("website"), dict) else {} return raw, g, a, w def row(label, before, after, fmt=str): b, n = fmt(before), fmt(after) if b == n: return None, (b, b) return (label, b, n), (b, n) def schema_present(w): return bool(w.get("schema")) if isinstance(w, dict) else False def compare(before_dir, after_dir): raw_b, g_b, a_b, w_b = load(before_dir) raw_a, g_a, a_a, w_a = load(after_dir) changes, same = [], [] def add(label, b, n, fmt=str): r, vals = row(label, b, n, fmt) (changes if r else same).append(r or (label, vals[0])) add("Name (GBP)", g_b.get("name"), g_a.get("name")) add("Phone (GBP)", rg.phone_core(g_b.get("phone")), rg.phone_core(g_a.get("phone"))) add("Phone (Apple)", rg.phone_core(a_b.get("phone")), rg.phone_core(a_a.get("phone"))) add("Address (GBP)", rg.addr_core(g_b.get("address")), rg.addr_core(g_a.get("address"))) add("Address (Apple)", rg.addr_core(a_b.get("address")), rg.addr_core(a_a.get("address"))) add("Website (GBP)", g_b.get("website"), g_a.get("website")) add("Rating (GBP)", g_b.get("rating"), g_a.get("rating")) add("Reviews (GBP)", g_b.get("reviews"), g_a.get("reviews")) add("Category (GBP)", g_b.get("category"), g_a.get("category")) add("Description set (GBP)", bool(g_b.get("description")), bool(g_a.get("description"))) add("Schema (website)", schema_present(w_b), schema_present(w_a)) for day in DAYS: gh_b, gh_a = (g_b.get("hours") or {}).get(day), (g_a.get("hours") or {}).get(day) if gh_b != gh_a: changes.append((f"Hours {day} (GBP)", gh_b or "—", gh_a or "—")) ah_b, ah_a = (a_b.get("hours") or {}).get(day), (a_a.get("hours") or {}).get(day) if ah_b != ah_a: changes.append((f"Hours {day} (Apple)", ah_b or "—", ah_a or "—")) return changes, same, raw_b, raw_a def main(): if len(sys.argv) != 3: sys.exit("usage: audit_diff.py BEFORE_RUN_DIR AFTER_RUN_DIR") before_dir, after_dir = sys.argv[1], sys.argv[2] changes, same, raw_b, raw_a = compare(before_dir, after_dir) L = [f"# Delta: {raw_a.get('name', '?')}", "", f"**Before:** {rg.run_date(raw_b)} (`{os.path.basename(before_dir)}`) ", f"**After:** {rg.run_date(raw_a)} (`{os.path.basename(after_dir)}`) ", "", f"**{len(changes)} field(s) changed** across the audited surfaces.", ""] if changes: L += ["## Changed", "", "| Field | Before | After |", "|-------|--------|-------|"] for label, b, n in changes: L.append(f"| {label} | {b} | {n} |") L.append("") else: L += ["No changes detected between the two captures.", ""] L += ["## Unchanged", ""] L += [f"- {label}: {v}" for label, v in same] L += ["", "*Generated by audit_diff.py (locked process v1.1). Compare only — no judgment.*", ""] out = os.path.join(after_dir, "DELTA.md") if os.path.isdir(after_dir) else \ os.path.join(os.path.dirname(os.path.abspath(after_dir)), "DELTA.md") open(out, "w").write("\n".join(L)) print(f"{os.path.basename(after_dir)}: {len(changes)} change(s) -> {out}") if __name__ == "__main__": main()