meta-optimizer-formacion/setup_baserow.py
José Manuel Gómez 9239e2f67f Initial scaffold: Meta Optimizer for RoiFormacion campaigns
Ports meta-optimizer's Meta Ads execution/approval/creative-analysis layer
(agent.py, meta_ads_client.py, baserow_client.py, slack_notifier.py,
approval_server.py) and replaces the per-vertical CPL model with the
PPL + monthly-capping-per-course model already used by leads-optimizer,
via a new airtable_client.py that shares Cursos/Familias/CentroCurso/
CursoMes/Leads Lake with that project and adds Meta Ads Campaigns /
MetaCampaignMes alongside its Google Ads Campaigns / GACampaignMes.
2026-07-07 16:53:03 +02:00

175 lines
6.9 KiB
Python

"""
One-time script: creates the meta_optimizer_formacion database in Baserow
with all tables and fields. Run once, then paste the printed IDs into your
.env file.
A diferencia de meta-optimizer (Viviful), NO se crean tablas 'campaigns' ni
'verticals' — esa capa de negocio (PPL, capping, familia) vive en Airtable
(ver setup_airtable_meta_tables.py). daily_snapshots usa 'familia' en vez de
'vertical'.
Usage:
python setup_baserow.py
"""
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 your .env file.")
sys.exit(1)
# Authenticate to get a JWT token
_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: str, path: str, **kwargs) -> dict:
url = f"{BASE_URL}/api{path}"
resp = requests.request(method, url, 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()
def setup_table(db_id: int, table_name: str, fields: list) -> int:
"""Create table, rename the auto-created primary field, then add remaining fields."""
t = api("POST", f"/database/tables/database/{db_id}/", json={"name": table_name})
table_id = t["id"]
print(f"\n Table: {table_name} (id={table_id})")
existing = api("GET", f"/database/fields/table/{table_id}/")
primary_id = existing[0]["id"]
first = fields[0]
api("PATCH", f"/database/fields/{primary_id}/", json={"name": first["name"], "type": first["type"]})
print(f" ~ primary field renamed to: {first['name']}")
for field in fields[1:]:
api("POST", f"/database/fields/table/{table_id}/", json=field)
print(f" + {field['name']}")
return table_id
# ── 1. Workspace ──────────────────────────────────────────────────────────────
workspaces = api("GET", "/workspaces/")
if not workspaces:
print("No workspaces found. Create one in Baserow first.")
sys.exit(1)
workspace_id = workspaces[0]["id"]
print(f"Workspace: {workspaces[0]['name']} (id={workspace_id})")
# ── 2. Database ───────────────────────────────────────────────────────────────
db = api("POST", f"/applications/workspace/{workspace_id}/", json={
"type": "database",
"name": "meta_optimizer_formacion",
})
db_id = db["id"]
print(f"Database: meta_optimizer_formacion (id={db_id})")
# ── 3. Tables ─────────────────────────────────────────────────────────────────
tid_actions = setup_table(db_id, "proposed_actions", [
{"name": "campaign_id", "type": "text"},
{"name": "campaign_name", "type": "text"},
{
"name": "action_type",
"type": "single_select",
"select_options": [
{"value": "PAUSE", "color": "red"},
{"value": "REDUCE_BUDGET", "color": "orange"},
{"value": "INCREASE_BUDGET", "color": "green"},
{"value": "MAINTAIN", "color": "blue"},
],
},
{"name": "parameter", "type": "number", "number_decimal_places": 2},
{"name": "justification", "type": "long_text"},
{"name": "advice", "type": "long_text"},
{"name": "alert", "type": "long_text"},
{"name": "confidence", "type": "number", "number_decimal_places": 2},
{
"name": "status",
"type": "single_select",
"select_options": [
{"value": "pending", "color": "yellow"},
{"value": "approved", "color": "green"},
{"value": "rejected", "color": "red"},
{"value": "executed", "color": "blue"},
],
},
{"name": "proposed_at", "type": "date", "date_format": "ISO", "date_include_time": True},
{"name": "executed_at", "type": "date", "date_format": "ISO", "date_include_time": True},
{"name": "slack_message_ts", "type": "text"},
])
tid_creatives = setup_table(db_id, "creative_analyses", [
{"name": "ad_id", "type": "text"},
{"name": "ad_name", "type": "text"},
{"name": "campaign_id", "type": "text"},
{"name": "image_url", "type": "url"},
{"name": "analysis", "type": "long_text"},
{"name": "score", "type": "number", "number_decimal_places": 1},
{"name": "recommendations", "type": "long_text"},
{"name": "created_at", "type": "date", "date_format": "ISO", "date_include_time": True},
])
tid_logs = setup_table(db_id, "execution_logs", [
{"name": "executed_at", "type": "date", "date_format": "ISO", "date_include_time": True},
{"name": "mode", "type": "text"},
{"name": "campaigns_analyzed", "type": "number"},
{"name": "actions_proposed", "type": "number"},
{"name": "actions_executed", "type": "number"},
{"name": "errors", "type": "long_text"},
{"name": "summary", "type": "long_text"},
{"name": "duration_seconds", "type": "number", "number_decimal_places": 1},
])
tid_snapshots = setup_table(db_id, "daily_snapshots", [
{"name": "run_date", "type": "text"},
{"name": "campaign_id", "type": "text"},
{"name": "campaign_name", "type": "text"},
{"name": "familia", "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"},
])
# ── 4. Output env vars ────────────────────────────────────────────────────────
print(f"""
{'='*55}
Add these to your .env file:
{'='*55}
BASEROW_DB_ID={db_id}
BASEROW_TABLE_ACTIONS={tid_actions}
BASEROW_TABLE_CREATIVES={tid_creatives}
BASEROW_TABLE_LOGS={tid_logs}
BASEROW_TABLE_SNAPSHOTS={tid_snapshots}
{'='*55}
""")