← back to Ga Allsites

scripts/delete_dup_properties.py

80 lines

#!/usr/bin/env python3
"""
Delete the 5 REDUNDANT duplicate GA4 properties created 2026-08-18.

Each of these 5 domains already had a live GA4 property (kept); this session minted a
duplicate. We delete only the DUPLICATE measurement id's property. GA4 delete_property is
a SOFT delete (moves to Trash, recoverable ~7 days), so this is reversible.

Resolves measurement id -> property by listing properties under account 15714274 and each
property's web data streams. Only deletes a property whose stream carries one of the DUP ids.
"""
from __future__ import annotations

import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).parent
SKILLS_DIR = Path.home() / ".claude" / "skills" / "analytics" / "scripts"
sys.path.insert(0, str(SKILLS_DIR))

from _auth import ensure_credentials
from google.analytics.admin import AnalyticsAdminServiceClient

ACCOUNT = "accounts/15714274"

# domain -> the DUPLICATE (redundant) id to delete; the EXISTING id (kept) is in the comment.
DUPS = {
    "commercialdesignreps.com": "G-1C6HDZJT1T",   # keep G-PRJ51PTW5G
    "designerrepresentatives.com": "G-X4WY2B9Z27", # keep G-MVZFTCQY3X
    "designtradelive.com": "G-LCM67ZD8PS",         # keep G-E8R8V9RDXN
    "wallsandfabrics.com": "G-7CTCV6VS2D",         # keep G-429EVNMHVY
    "iwascute.com": "G-3084642QM4",                # keep G-2DHDBP8R78
}
TARGET_IDS = set(DUPS.values())


def main() -> None:
    apply = "--apply" in sys.argv
    ensure_credentials()
    client = AnalyticsAdminServiceClient()

    # measurement_id -> (property_name, property_display_name)
    mid_map: dict[str, tuple] = {}
    for prop in client.list_properties(request={"filter": f"parent:{ACCOUNT}", "show_deleted": False}):
        for ds in client.list_data_streams(request={"parent": prop.name}):
            wsd = getattr(ds, "web_stream_data", None)
            mid = getattr(wsd, "measurement_id", "") if wsd else ""
            if mid:
                mid_map[mid] = (prop.name, prop.display_name)

    print(f"Account {ACCOUNT}: mapped {len(mid_map)} measurement ids to properties\n")
    to_delete = []
    for domain, dup in DUPS.items():
        hit = mid_map.get(dup)
        if hit:
            print(f"  {domain:32} dup {dup} -> {hit[0]} (display: {hit[1]})")
            to_delete.append((domain, dup, hit[0]))
        else:
            print(f"  {domain:32} dup {dup} -> NOT FOUND under this account (skip)")

    if not to_delete:
        print("\nNothing to delete.")
        return

    if not apply:
        print(f"\nDRY RUN — {len(to_delete)} properties would be trashed. Re-run with --apply.")
        return

    print(f"\nDeleting (soft/trash) {len(to_delete)} duplicate properties …")
    for domain, dup, name in to_delete:
        try:
            client.delete_property(request={"name": name})
            print(f"  ✓ trashed {name}  ({domain} dup {dup})")
        except Exception as exc:
            print(f"  ✗ FAILED {name}: {exc}")


if __name__ == "__main__":
    main()