""" GA4 Analytics report for Portfolio Journal Property: 300195945 """ import os from datetime import date, timedelta from google.analytics.data_v1beta import BetaAnalyticsDataClient from google.analytics.data_v1beta.types import ( RunReportRequest, Dimension, Metric, DateRange, OrderBy, FilterExpression, Filter ) PROPERTY_ID = "519342587" import subprocess, json, tempfile def _fix_multiline_json(raw): result, in_string, escape_next = [], False, False for c in raw: if escape_next: result.append(c); escape_next = False elif c == '\\': result.append(c); escape_next = True elif c == '"': in_string = not in_string; result.append(c) elif in_string and c == '\n': result.append('\\n') elif in_string and c == '\r': pass else: result.append(c) return ''.join(result) key_raw = subprocess.check_output(["pass", "show", "google/sirius-service-account"]).decode() key_json = json.dumps(json.loads(_fix_multiline_json(key_raw))) _tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) _tmp.write(key_json); _tmp.flush() os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = _tmp.name client = BetaAnalyticsDataClient() TODAY = date.today().isoformat() D7 = (date.today() - timedelta(days=7)).isoformat() D28 = (date.today() - timedelta(days=28)).isoformat() D90 = (date.today() - timedelta(days=90)).isoformat() def run(dimensions, metrics, date_from, date_to=TODAY, order_by=None, limit=20): req = RunReportRequest( property=f"properties/{PROPERTY_ID}", dimensions=[Dimension(name=d) for d in dimensions], metrics=[Metric(name=m) for m in metrics], date_ranges=[DateRange(start_date=date_from, end_date=date_to)], limit=limit, ) if order_by: req.order_bys = order_by return client.run_report(req) def run_no_dim(metrics, date_from, date_to=TODAY): req = RunReportRequest( property=f"properties/{PROPERTY_ID}", metrics=[Metric(name=m) for m in metrics], date_ranges=[DateRange(start_date=date_from, end_date=date_to)], ) return client.run_report(req) def header(title): print(f"\n{'='*60}") print(f" {title}") print('='*60) def fmt_row(row, widths): parts = [str(v.value).ljust(w) for v, w in zip(list(row.dimension_values) + list(row.metric_values), widths)] print(" " + " ".join(parts)) # ── 1. Overview ──────────────────────────────────────────────── header("OVERVIEW — últimos 28 días vs 7 días") r28 = run_no_dim(["activeUsers", "sessions", "screenPageViews", "averageSessionDuration"], D28) r7 = run_no_dim(["activeUsers", "sessions", "screenPageViews", "averageSessionDuration"], D7) def ovw(r): row = r.rows[0] vals = [v.value for v in row.metric_values] return vals v28 = ovw(r28) v7 = ovw(r7) labels = ["Usuarios activos", "Sesiones", "Pantallas vistas", "Duración media (s)"] print(f"\n {'Métrica':<30} {'28d':>10} {'7d':>10}") print(" " + "-"*52) for l, a, b in zip(labels, v28, v7): print(f" {l:<30} {a:>10} {b:>10}") # ── 2. Pantallas más visitadas ────────────────────────────────── header("PANTALLAS MÁS VISITADAS — últimos 28d") r = run(["unifiedScreenName"], ["screenPageViews", "activeUsers"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="screenPageViews"), desc=True)]) print(f"\n {'Pantalla':<40} {'Vistas':>8} {'Usuarios':>10}") print(" " + "-"*60) for row in r.rows: fmt_row(row, [40, 8, 10]) # ── 3. Eventos clave ──────────────────────────────────────────── header("EVENTOS CLAVE — últimos 28d") r = run(["eventName"], ["eventCount", "totalUsers"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="eventCount"), desc=True)], limit=30) print(f"\n {'Evento':<45} {'Count':>8} {'Usuarios':>10}") print(" " + "-"*65) for row in r.rows: fmt_row(row, [45, 8, 10]) # ── 4. Funnel paywall ─────────────────────────────────────────── header("FUNNEL PAYWALL — últimos 90d") paywall_events = [ "paywall_shown", "purchase_attempt", "purchase_success", "purchase_failure", "restore_purchases" ] r = run(["eventName"], ["eventCount", "totalUsers"], D90, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="eventCount"), desc=True)], limit=50) print(f"\n {'Evento paywall':<40} {'Count':>8} {'Usuarios':>10}") print(" " + "-"*60) for row in r.rows: name = row.dimension_values[0].value if any(p in name for p in ["paywall", "purchase", "restore", "premium"]): fmt_row(row, [40, 8, 10]) # ── 5. Triggers del paywall ───────────────────────────────────── header("TRIGGERS DEL PAYWALL — últimos 90d (qué feature lo dispara)") try: r = run(["eventName", "customEvent:paywall_trigger"], ["eventCount"], D90, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="eventCount"), desc=True)], limit=30) rows = [row for row in r.rows if "paywall" in row.dimension_values[0].value] if rows: print(f"\n {'Evento':<30} {'Trigger':<25} {'Count':>8}") print(" " + "-"*65) for row in rows: fmt_row(row, [30, 25, 8]) else: print("\n (sin eventos paywall con trigger en este periodo — los datos aparecerán desde hoy)") except Exception as e: print(f"\n Error: {e}") # ── 6. Retención — nuevos vs recurrentes ─────────────────────── header("NUEVOS vs RECURRENTES — últimos 28d") r = run(["newVsReturning"], ["activeUsers", "sessions", "screenPageViews"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="activeUsers"), desc=True)]) print(f"\n {'Tipo':<20} {'Usuarios':>10} {'Sesiones':>10} {'Pantallas':>10}") print(" " + "-"*54) for row in r.rows: fmt_row(row, [20, 10, 10, 10]) # ── 7. Versiones de app ───────────────────────────────────────── header("VERSIONES EN USO — últimos 28d") r = run(["appVersion"], ["activeUsers", "sessions"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="activeUsers"), desc=True)], limit=10) print(f"\n {'Versión':<20} {'Usuarios':>10} {'Sesiones':>10}") print(" " + "-"*44) for row in r.rows: fmt_row(row, [20, 10, 10]) # ── 8. Dispositivos ───────────────────────────────────────────── header("DISPOSITIVOS — últimos 28d") r = run(["deviceModel"], ["activeUsers"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="activeUsers"), desc=True)], limit=10) print(f"\n {'Dispositivo':<30} {'Usuarios':>10}") print(" " + "-"*44) for row in r.rows: fmt_row(row, [30, 10]) # ── 9. País / región ──────────────────────────────────────────── header("PAÍSES — últimos 28d") r = run(["country"], ["activeUsers"], D28, order_by=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="activeUsers"), desc=True)], limit=10) print(f"\n {'País':<30} {'Usuarios':>10}") print(" " + "-"*44) for row in r.rows: fmt_row(row, [30, 10]) print("\n")