Three sources pulled in parallel into one warehouse, written with idempotent upserts so re-running the same window never duplicates rows. A forecast model then flags days that look wrong.
Hourly tick (or manual /api/marketing/etl/run).
Meta Ads, GA4 Data API, Firestore - different APIs, different tables.
NDJSON load-job into a temp table, then MERGE on the key.
BigQuery ML ARIMA_PLUS over fresh data writes alerts.
MERGE is correct on every run.| Source | Grain | Note |
|---|---|---|
| Meta Ads | date x ad | Daily insights with time_increment=1, enriched with campaign status |
| GA4 | date x UTM x platform | Web sessions + a fixed allowlist of funnel events |
| Firestore | date x event x UTM | Source of truth for mobile conversions (GA4 under-fires Flutter events) |
180-day daily series per metric (sessions, submissions, spend).
CREATE OR REPLACE MODEL - idempotent, holiday-aware.
ML.DETECT_ANOMALIES at 0.98 over the last 14 days.
Direction, % deviation, severity into the alerts table.
../src/marketing.