fix: mostrar Google Ads desde ad_daily cuando kpi_weekly está vacío
kpi_weekly es una MV de solo lectura sin canales paid; canal/seguimiento ahora hacen fallback a ad_daily. Bump Google Ads API a v21 en sync. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
94
scripts/ingest_gads_json.py
Executable file
94
scripts/ingest_gads_json.py
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ingest .ops/gads/{customer_id}.json MCP dumps into ad_daily."""
|
||||
from __future__ import annotations
|
||||
import json, os, sys, urllib.request, urllib.error
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
ROOT = Path('/Users/4ventures/4ventures-client-portal')
|
||||
# load .env.local
|
||||
for line in (ROOT/'.env.local').read_text().splitlines():
|
||||
if '=' in line and not line.startswith('#') and line.split('=',1)[0] not in os.environ:
|
||||
k,v=line.split('=',1); os.environ[k]=v.strip().strip('"').strip("'")
|
||||
|
||||
url=os.environ['NEXT_PUBLIC_SUPABASE_URL'].rstrip('/')
|
||||
key=os.environ['SUPABASE_SERVICE_ROLE_KEY']
|
||||
h={'apikey':key,'Authorization':f'Bearer {key}','Content-Type':'application/json'}
|
||||
|
||||
MAP={
|
||||
'3019377678':'a7a6eb49-435c-411e-bdb0-a45b6a75ffab',
|
||||
'7658651782':'2f6587f1-5dab-4994-b99f-d62f4300dd92',
|
||||
'4975185233':'25d3cdda-a3fc-4af7-b733-b38304b489df',
|
||||
'3183140753':'506a6d00-dc5a-4d84-bedc-73a35e3014a6',
|
||||
'8746480744':'98d023ba-1c5c-4cab-8ab9-4e70c4367bb1',
|
||||
'7181044127':'6f5c19e4-bf8b-4ef0-8c7e-55c9d0c0a041',
|
||||
'8436023380':'97def34e-e1c4-41b4-a579-036b14cd5091',
|
||||
'2986275882':'229687b5-6861-4b6d-b321-e09197629731',
|
||||
'6481978772':'8e5761b7-6347-4fd1-acbf-4572730a783a',
|
||||
'9069365731':'73a1b415-2325-4cab-9769-823021a5e888',
|
||||
'8177742252':'730feaf2-f2a3-4a18-963b-12b853551d8d',
|
||||
'9627541456':'fd7d361b-c4d0-47ca-bc0e-75d21ac50da3',
|
||||
'8592129580':'7df5293e-b954-4059-94da-d18534a08767',
|
||||
'6871069003':'c538a248-4ef0-437b-b4a0-544cff597659',
|
||||
'2508925045':'a5a250b4-e79a-4cb3-bfa1-4f9a6d908a03',
|
||||
'4418211066':'48919ad2-6af0-4ef0-96a3-fd9ca0a46cd2',
|
||||
'1323363601':'0b3c5fc0-6901-4caf-b26a-b6a0201d8541',
|
||||
}
|
||||
|
||||
def call(method, path, body=None):
|
||||
headers=dict(h); headers['Prefer']='return=minimal'
|
||||
data=None if body is None else json.dumps(body).encode()
|
||||
req=urllib.request.Request(f'{url}{path}', data=data, headers=headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as r:
|
||||
return r.status, r.read().decode()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode()[:400]
|
||||
|
||||
since=(datetime.now(timezone.utc)-timedelta(days=30)).date().isoformat()
|
||||
gads=ROOT/'.ops'/'gads'
|
||||
total_rows=0; total_spend=0.0
|
||||
for path in sorted(gads.glob('*.json')):
|
||||
cid=path.stem
|
||||
tid=MAP.get(cid)
|
||||
if not tid:
|
||||
print('skip unknown', cid); continue
|
||||
rows_raw=json.loads(path.read_text())
|
||||
if not rows_raw:
|
||||
print(cid, 'EMPTY'); continue
|
||||
out=[]
|
||||
for r in rows_raw:
|
||||
date=r.get('segments.date') or (r.get('segments') or {}).get('date')
|
||||
if not date: continue
|
||||
camp_id=r.get('campaign.id') or (r.get('campaign') or {}).get('id')
|
||||
camp_name=r.get('campaign.name') or (r.get('campaign') or {}).get('name')
|
||||
imp=r.get('metrics.impressions') or (r.get('metrics') or {}).get('impressions') or 0
|
||||
clicks=r.get('metrics.clicks') or (r.get('metrics') or {}).get('clicks') or 0
|
||||
cost=r.get('metrics.cost_micros') or (r.get('metrics') or {}).get('costMicros') or (r.get('metrics') or {}).get('cost_micros') or 0
|
||||
conv=r.get('metrics.conversions') or (r.get('metrics') or {}).get('conversions') or 0
|
||||
cid_s = str(camp_id) if camp_id is not None else ''
|
||||
out.append({
|
||||
'tenant_id': tid,
|
||||
'date': date,
|
||||
'platform': 'google',
|
||||
'campaign_id': cid_s or None,
|
||||
'campaign_name': camp_name,
|
||||
# ad_daily.ad_id is NOT NULL — campaign-level rows reuse campaign id
|
||||
'ad_id': cid_s,
|
||||
'ad_name': '',
|
||||
'impressions': int(float(imp or 0)),
|
||||
'clicks': int(float(clicks or 0)),
|
||||
'spend': round(float(cost or 0)/1_000_000, 2),
|
||||
'platform_leads': int(round(float(conv or 0))),
|
||||
})
|
||||
call('DELETE', f'/rest/v1/ad_daily?tenant_id=eq.{tid}&platform=eq.google&date=gte.{since}')
|
||||
ok=True
|
||||
for i in range(0,len(out),400):
|
||||
st,res=call('POST','/rest/v1/ad_daily', out[i:i+400])
|
||||
if st not in (200,201):
|
||||
print('FAIL', cid, st, res); ok=False; break
|
||||
if ok:
|
||||
spend=sum(x['spend'] for x in out)
|
||||
total_rows+=len(out); total_spend+=spend
|
||||
print(f'OK {cid} rows={len(out)} spend={spend:.2f}')
|
||||
print('TOTAL', total_rows, round(total_spend,2))
|
||||
Reference in New Issue
Block a user