[object Object]

← back to Domain Landings

TK-10631: stage expanded GA4 create script for all 150 remaining domain-landings domains (Steve-approved scope expansion)

f5789c10fbbaa14948addbe94c3c30e2d93083a2 · 2026-09-03 13:54:33 -0700 · Steve Abrams

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4T2ZoC42e62XV7YkkT1Qg

Files touched

Diff

commit f5789c10fbbaa14948addbe94c3c30e2d93083a2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 3 13:54:33 2026 -0700

    TK-10631: stage expanded GA4 create script for all 150 remaining domain-landings domains (Steve-approved scope expansion)
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01E4T2ZoC42e62XV7YkkT1Qg
---
 ops/tk10631/create_all_ga4.py | 96 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)

diff --git a/ops/tk10631/create_all_ga4.py b/ops/tk10631/create_all_ga4.py
new file mode 100644
index 0000000..35698b0
--- /dev/null
+++ b/ops/tk10631/create_all_ga4.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""TK-10631 (expanded scope, 2026-09-03): create GA4 properties for ALL
+domain-landings domains lacking GA4 — not just the original 12, now all
+150 remaining of the 162 total (Steve: "any website we have live even
+domain for sale sites"). GATED: Google-account write — run only with
+approval (Steve approved this run verbatim 2026-09-03).
+
+Idempotent by DOMAIN (not display_name — two titles collide: "Cypres
+Awards", "Petition Your"), matched against every existing web data
+stream's default_uri host in the APPS account, so a re-run never
+double-creates. Read-back verifies each domain->measurement_id mapping.
+
+This script ONLY creates GA4 properties. It does NOT touch
+data/domains.json and does NOT deploy anything live — that remains a
+separate gated customer-facing-deploy step per standing rule.
+"""
+import json, time, sys
+from urllib.parse import urlparse
+from google.oauth2 import service_account
+from google.analytics.admin import AnalyticsAdminServiceClient
+from google.analytics.admin_v1alpha.types import Property, DataStream
+
+KEY = "/Users/macstudio3/.config/ga-analytics-agent/service-account.json"
+APPS_ACCOUNT = "accounts/371997142"  # Steve Abrams APPS
+SCOPES = ["https://www.googleapis.com/auth/analytics.edit",
+          "https://www.googleapis.com/auth/analytics.readonly"]
+DOMAINS_JSON = "data/domains.json"
+OUT = "/tmp/tk10631_results_full.json"
+
+creds = service_account.Credentials.from_service_account_file(KEY, scopes=SCOPES)
+client = AnalyticsAdminServiceClient(credentials=creds)
+
+cfg = json.load(open(DOMAINS_JSON))
+targets = {d: v for d, v in cfg.items() if not v.get("ga4")}
+print(f"{len(targets)} domains lack ga4 out of {len(cfg)} total")
+
+# Build existing domain -> (property, measurement_id) map from EVERY
+# property already in the APPS account, keyed by data-stream default_uri
+# host (NOT display_name — display names are not guaranteed unique).
+print("Indexing existing properties/streams in APPS account...")
+existing_by_domain = {}
+n_props = 0
+for s in client.list_account_summaries():
+    if s.account != APPS_ACCOUNT:
+        continue
+    for p in s.property_summaries:
+        n_props += 1
+        try:
+            for st in client.list_data_streams(parent=p.property):
+                if st.web_stream_data and st.web_stream_data.default_uri:
+                    host = urlparse(st.web_stream_data.default_uri).netloc.lower()
+                    host = host[4:] if host.startswith("www.") else host
+                    mid = st.web_stream_data.measurement_id
+                    if host and mid:
+                        existing_by_domain[host] = {"property": p.property, "measurement_id": mid}
+        except Exception as e:
+            print(f"  WARN: couldn't list streams for {p.property}: {e}", file=sys.stderr)
+print(f"Indexed {n_props} properties, {len(existing_by_domain)} domain->stream mappings")
+
+results = {}
+created_count = 0
+for i, (domain, v) in enumerate(sorted(targets.items()), 1):
+    name = v.get("title") or domain
+    if domain in existing_by_domain:
+        r = existing_by_domain[domain]
+        print(f"[{i}/{len(targets)}] EXISTS  {domain:32} {r['property']} mid={r['measurement_id']}")
+        results[domain] = {**r, "created": False}
+        continue
+    try:
+        display_name = f"{name} ({domain})"[:100]
+        prop = Property(parent=APPS_ACCOUNT, display_name=display_name,
+                         time_zone="America/Los_Angeles", currency_code="USD",
+                         industry_category="SHOPPING")
+        created = client.create_property(property=prop)
+        prop_name = created.name
+        stream = DataStream(type_=DataStream.DataStreamType.WEB_DATA_STREAM,
+                             display_name=f"{name} Web"[:100],
+                             web_stream_data=DataStream.WebStreamData(default_uri=f"https://{domain}"))
+        st = client.create_data_stream(parent=prop_name, data_stream=stream)
+        mid = st.web_stream_data.measurement_id
+        ok = False
+        for s2 in client.list_data_streams(parent=prop_name):
+            if s2.web_stream_data and s2.web_stream_data.measurement_id == mid:
+                ok = (domain in s2.web_stream_data.default_uri) and mid.startswith("G-")
+        print(f"[{i}/{len(targets)}] CREATED {domain:32} {prop_name} mid={mid} readback_ok={ok}")
+        results[domain] = {"property": prop_name, "measurement_id": mid, "created": True, "readback_ok": ok}
+        created_count += 1
+        time.sleep(0.6)
+    except Exception as e:
+        print(f"[{i}/{len(targets)}] FAILED  {domain:32} {e}", file=sys.stderr)
+        results[domain] = {"error": str(e), "created": False}
+        time.sleep(0.6)
+
+json.dump(results, open(OUT, "w"), indent=2)
+ok_count = sum(1 for r in results.values() if r.get("measurement_id"))
+print(f"\nSUMMARY: {ok_count} of {len(targets)} have a measurement_id ({created_count} newly created). Written to {OUT}")

← c630ac0 TK-10631: stage gated 12-property GA4 create script (approva  ·  back to Domain Landings  ·  TK-10631: backfill 84 existing GA4 measurement IDs into doma c4037fe →