#!/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""" GIG AI-Visibility — Weekly Report {date}
GIG Gulf · AI-Visibility · Weekly Report

Where GIG shows up when buyers ask AI

Week of {date} · {len(rows)} answers across ChatGPT, Claude & Google · {len(pr)} buyer prompts · UAE. {baseline_line}

{overall[2]:.0f}%
Overall AI citation share
({overall[0]}/{overall[1]} answers)
{cat_pct:.0f}%
Category (non-brand)
where new buyers start
{brandwin:.0f}%
Brand / Trust / Utility
where GIG already wins

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.

AXA legacy = owned demand, not a leak. AXA appears in nearly every category answer. That is GIG harvesting AXA-searcher intent — protect and feed it, don't scrub it. Note: an external GEO audit that counts only the literal string "GIG Gulf" will understate true visibility and report a category gap that the AXA equity is already filling.
""") # by phase H.append("

By funnel phase — where we win and lose

") for ph in PHASE_ORDER: if ph in phases: g,n,pct=phases[ph]; cls='lo' if pct<25 else ('mid' if pct<60 else '') H.append(f"") H.append("
PhaseGIG citedVisibilityWhat it is
{ph}{g}/{n} = {pct:.0f}%{PHASE_NOTE[ph]}
") # by engine H.append("

By engine

") for e,(g,n,pct) in sorted(engines.items()): H.append(f"") H.append("
EngineGIG citation share
{e}{g}/{n} = {pct:.0f}%
") # competitor leaderboard H.append("

Who takes the answer instead — competitor mentions

") for c,cnt in comp.most_common(8): H.append(f"") H.append("
BrandAppearances (of "+str(len(rows))+" answers)
{c}{cnt}
") # ACTIONS H.append("

Do these this week — non-brand prompts GIG is losing

") H.append("

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"
{q}
{lob} · {ph} · GIG cited by {g}/{n} engines · taking the answer instead: {', '.join(cs)}
") H.append(f"""

How to read this vs the agency pitch

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.

GIG Gulf · AI-Visibility tool (Articulate) · prompts & snapshots in AIVisibility/ · re-run weekly: python3 run.py && python3 report.py
""") open(os.path.join(ROOT,"weekly-report.html"),"w").write("".join(H)) print("wrote weekly-report.html") print(f"overall {overall[2]:.0f}% | Category {phases.get('Category',(0,0,0))[2]:.0f}% | actions {len(actions)}") for ph in PHASE_ORDER: if ph in phases: print(f" {ph}: {phases[ph][2]:.0f}%")