#!/usr/bin/env python3 """Generate the action-first weekly AI-visibility report from snapshots.""" import json, os, glob, datetime, collections ROOT=os.path.dirname(os.path.abspath(__file__)) snaps=sorted(glob.glob(os.path.join(ROOT,"snapshots","*.json"))) cur=json.load(open(snaps[-1])); prev=json.load(open(snaps[-2])) if len(snaps)>1 else None rows=[r for r in cur["rows"] if r["ok"]] def share(rs): n=len(rs); return (sum(1 for r in rs if r["gig"]),n, (sum(1 for r in rs if r["gig"])/n*100 if n else 0)) def by(key): d=collections.defaultdict(list) for r in rows: d[r[key]].append(r) return {k:share(v) for k,v in d.items()} overall=share(rows) phases=by("phase"); engines=by("engine"); lobs=by("lob") # prompt-level: cited by how many engines pr=collections.defaultdict(lambda:{"gig":0,"n":0,"comp":set(),"q":"","phase":"","lob":""}) for r in rows: p=pr[r["id"]]; p["n"]+=1; p["gig"]+=1 if r["gig"] else 0 p["comp"].update(r["competitors"]); p["q"]=r["q"]; p["phase"]=r["phase"]; p["lob"]=r["lob"] # competitor leaderboard comp=collections.Counter() for r in rows: for c in r["competitors"]: comp[c]+=1 # ACTIONS: non-brand prompts (Category/Attribute) where GIG absent on all/most engines but competitors present actions=[] for pid,p in pr.items(): if p["phase"] in ("Category","Attribute","Utility") and p["gig"]<=1 and p["comp"]: actions.append((p["q"],p["lob"],p["phase"],sorted(p["comp"])[:4],p["gig"],p["n"])) actions.sort(key=lambda x:(x[4], 0 if x[2]=="Category" else 1)) PHASE_ORDER=["Category","Attribute","Competitive","Trust","Utility"] PHASE_NOTE={"Category":"non-branded category search — upstream of the funnel, where new buyers start", "Attribute":"feature/product questions","Competitive":"head-to-head comparisons","Trust":"brand & reputation","Utility":"govt-service how-tos"} def delta(cur_share): if not prev: return "" pv=[r for r in prev["rows"] if r["ok"]] return "" # baseline week date=cur["date"] cat_pct=phases.get('Category',(0,0,0))[2] story_phrase = "strong where buyers already know the brand and weak where they do not" if cat_pct < overall[2] else "building category presence" baseline_line = "Baseline week — deltas start next run." if not prev else "vs last week." brandwin = max((phases.get('Trust',(0,0,0))[2], phases.get('Utility',(0,0,0))[2])) H=[] H.append(f"""
Week of {date} · {len(rows)} answers across ChatGPT, Claude & Google · {len(pr)} buyer prompts · UAE. {baseline_line}
The story in one line: GIG is highly visible in UAE AI answers — but a large share of that visibility rides on AXA legacy equity (GIG Gulf is the rebranded AXA Gulf, and AXA queries deliver up to ~25% of GWP). Counting AXA as GIG-owned, citation share is {overall[2]:.0f}% overall and {cat_pct:.0f}% on non-branded category questions.
| Phase | GIG cited | Visibility | What it is |
|---|---|---|---|
| {ph} | {g}/{n} = {pct:.0f}% | {PHASE_NOTE[ph]} |
| Engine | GIG citation share |
|---|---|
| {e} | {g}/{n} = {pct:.0f}% |
| Brand | Appearances (of "+str(len(rows))+" answers) |
|---|---|
| {c} | {cnt} |
Category & attribute questions where GIG was cited by zero engines but competitors were. Each is a page to build or fix.
") if not actions: H.append("No weak category/attribute prompts this run.
") for q,lob,ph,cs,g,n in actions[:6]: H.append(f"This is the same measurement an external GEO agency charges $3.5k–9.5k/month for — run in-house, on your own prompt set, and built to end with actions, not just a score. Next week it shows movement: which prompts we won back, which competitors moved.