← back to Domain Landings

ops/tk10631/backfill_existing_ga4.py

71 lines

#!/usr/bin/env python3
"""TK-10631: backfill data/domains.json with GA4 measurement IDs for domains
that ALREADY have a purpose-built GA4 property (stream default_uri host ==
the domain). No Google write, no deploy — local, reversible via git.

Only fills domains that are (a) in the verified live-target list, (b) currently
have an empty `ga4`, and (c) have exactly one existing stream keyed to that host.
"""
import argparse, json
from urllib.parse import urlparse
from google.oauth2 import service_account
from google.analytics.admin import AnalyticsAdminServiceClient

KEY = "/Users/macstudio3/.config/ga-analytics-agent/service-account.json"
ap = argparse.ArgumentParser()
ap.add_argument("--targets", required=True)
ap.add_argument("--apply", action="store_true", help="write the file (default: preview)")
a = ap.parse_args()

creds = service_account.Credentials.from_service_account_file(
    KEY, scopes=["https://www.googleapis.com/auth/analytics.readonly"])
c = AnalyticsAdminServiceClient(credentials=creds)

idx = {}
for s in c.list_account_summaries():
    for p in s.property_summaries:
        try:
            for st in c.list_data_streams(parent=p.property):
                w = st.web_stream_data
                if w and w.default_uri and w.measurement_id:
                    h = urlparse(w.default_uri).netloc.lower()
                    h = h[4:] if h.startswith("www.") else h
                    if h:
                        idx.setdefault(h, (s.account, p.property, w.measurement_id))
        except Exception:
            pass

cfg = json.load(open("data/domains.json"))
targets = [d.strip() for d in open(a.targets) if d.strip()]
fills, missing = {}, []
for d in targets:
    if cfg.get(d, {}).get("ga4"):
        continue
    if d in idx:
        fills[d] = idx[d]
    else:
        missing.append(d)

for d, (acct, prop, mid) in sorted(fills.items()):
    print(f"  {d:34s} <- {mid:15s} {prop} ({acct})")
print(f"\n{len(fills)} to fill, {len(missing)} have no existing property "
      f"(need creation): {missing}")

if not a.apply:
    print("\nPREVIEW ONLY — pass --apply to write data/domains.json")
    raise SystemExit(0)

for d, (_, _, mid) in fills.items():
    cfg[d]["ga4"] = mid
json.dump(cfg, open("data/domains.json", "w"), indent=2)
open("data/domains.json", "a").write("\n")

# read-back verify
rb = json.load(open("data/domains.json"))
bad = [d for d, (_, _, mid) in fills.items() if rb[d].get("ga4") != mid]
assert not bad, f"read-back MISMATCH: {bad}"
assert len(rb) == len(cfg), "domain count changed!"
print(f"\nOK: wrote + read-back-verified {len(fills)} measurement IDs. "
      f"{sum(1 for v in rb.values() if v.get('ga4'))}/{len(rb)} domains now tagged.")
print("NOT deployed — customer-facing deploy remains gated.")