meta-optimizer/create_snapshots_table.py
José Manuel Gómez 8fb0b69896 Add Baserow persistence, Slack interactive reports, Streamlit dashboard, and full analysis pipeline
- Migrate from Airtable to Baserow: BaserowClient with snapshots, actions, creatives, logs
- Claude agents (Haiku for decisions/units, Sonnet for creatives) with cost_cap_eur vs CPL comparison
- Slack bot with colored action emojis, effect text before approval buttons, 500-char justifications
- Streamlit dashboard with date-range navigation, campaign drill-down (adsets/ads), Histórico tab
- Approval server (FastAPI + ngrok) for Slack button callbacks
- backfill.py for historical snapshot regeneration with Claude re-analysis
- Margin fix: 0-lead campaigns contribute -spend (not 0) to margin
- CTR fix: Meta returns CTR as percentage already, removed *100
- Parameter fix: pass decision parameter to Slack action for correct budget effect display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 09:30:13 +02:00

86 lines
2.8 KiB
Python

"""One-time: add daily_snapshots table to existing meta_optimizer database in Baserow."""
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
BASE_URL = os.environ.get("BASEROW_URL", "").rstrip("/")
EMAIL = os.environ.get("BASEROW_EMAIL", "")
PASSWORD = os.environ.get("BASEROW_PASSWORD", "")
if not BASE_URL or not EMAIL or not PASSWORD:
print("Error: BASEROW_URL, BASEROW_EMAIL and BASEROW_PASSWORD must be set in .env")
sys.exit(1)
auth = requests.post(f"{BASE_URL}/api/user/token-auth/",
json={"email": EMAIL, "password": PASSWORD}, timeout=10)
if not auth.ok:
print(f"Auth error: {auth.text}")
sys.exit(1)
JWT = auth.json()["access_token"]
HEADERS = {"Authorization": f"JWT {JWT}", "Content-Type": "application/json"}
def api(method, path, **kwargs):
resp = requests.request(method, f"{BASE_URL}/api{path}", headers=HEADERS, **kwargs)
if not resp.ok:
print(f" API error {resp.status_code} {method} {path}: {resp.text[:300]}")
resp.raise_for_status()
return resp.json()
# Find meta_optimizer database
db_id = None
for ws in api("GET", "/workspaces/"):
for app in api("GET", f"/applications/workspace/{ws['id']}/"):
if app.get("name") == "meta_optimizer":
db_id = app["id"]
break
if db_id:
break
if not db_id:
db_id_env = os.environ.get("BASEROW_DB_ID")
if db_id_env:
db_id = int(db_id_env)
else:
print("Error: could not find meta_optimizer database. Set BASEROW_DB_ID in .env.")
sys.exit(1)
print(f"Database: meta_optimizer (id={db_id})")
# Create table
t = api("POST", f"/database/tables/database/{db_id}/", json={"name": "daily_snapshots"})
table_id = t["id"]
print(f"Table: daily_snapshots (id={table_id})")
# Rename primary field
primary_id = api("GET", f"/database/fields/table/{table_id}/")[0]["id"]
api("PATCH", f"/database/fields/{primary_id}/", json={"name": "run_date", "type": "text"})
print(" ~ primary field: run_date")
for f in [
{"name": "campaign_id", "type": "text"},
{"name": "campaign_name", "type": "text"},
{"name": "vertical", "type": "text"},
{"name": "spend", "type": "number", "number_decimal_places": 2},
{"name": "leads", "type": "number"},
{"name": "cpl", "type": "number", "number_decimal_places": 2},
{"name": "margin", "type": "number", "number_decimal_places": 2, "number_negative": True},
{"name": "action_type", "type": "text"},
{"name": "justification", "type": "long_text"},
{"name": "adsets_json", "type": "long_text"},
{"name": "ads_json", "type": "long_text"},
]:
api("POST", f"/database/fields/table/{table_id}/", json=f)
print(f" + {f['name']}")
print(f"""
{'='*50}
Añade esto a tu .env:
BASEROW_TABLE_SNAPSHOTS={table_id}
{'='*50}
""")