9 Commits

Author SHA1 Message Date
deed1db80e Add deep creative analysis: standalone script, dashboard tab, compact Slack scorecard
- analyze_creatives.py: nuevo script independiente que analiza visualmente todos los
  anuncios activos, detecta fatiga creativa (CTR 3d vs 7d) y compara creatividades
  dentro del mismo adset usando Claude Sonnet con visión
- agent.py: analyze_creative_deep() con métricas de rendimiento + detección de fatiga,
  compare_adset_creatives() para comparativa multi-imagen, fallback de descarga de
  imágenes por lista de URLs, prompts en español
- meta_ads_client.py: get_ads_with_creatives() incluye adset_id, image_url separado de
  thumbnail_url, y video_thumbnail_url via AdVideo.picture para vídeos
- baserow_client.py: get_all_creative_analyses() y get_creative_history_by_ad()
- dashboard.py: nueva pestaña Creatividades con tabla seleccionable, panel lateral con
  thumbnail + análisis + recomendaciones + gráfico de evolución del score
- slack_notifier.py: scorecard compacto (una línea por anuncio con acción breve),
  fix del límite de 50 bloques via flush proactivo antes de cada adset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 12:03:10 +02:00
8480e530a0 Improve Slack vertical clarity: header blocks, grouped ok summary, dividers
- Vertical detail messages use Slack header block for prominent title
- 'Todo en orden' groups campaigns by vertical with CPL/obj summary per vertical
- Divider between each campaign within a vertical detail message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:56:39 +02:00
3afc1c25c1 Simplify Slack report: vertical-first hierarchical structure
Replace one-message-per-campaign with:
- Message 1: monthly table + vertical scorecard + actions + todo-en-orden
- One message per vertical that has non-MAINTAIN actions or ad PAUSEs
- Verticals with no issues are summarised in a single line in message 1
- Remove top-10 best/worst lists (noise)
- Adset detail (top 3) only shown for non-MAINTAIN campaigns
- Recommendations and approve/reject buttons are now front and centre

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:38:36 +02:00
c46baad502 Fix ad analysis: exclude paused ads, dual 3d/7d window, clearer Slack labels
- meta_ads_client: filter currently-paused ads from analysis (they have
  historic spend in the window but shouldn't get PAUSE recommendations)
- run.py: fetch both 3d and 7d ad metrics; merge cpl_3d/cpl_7d into each ad
- agent.py AD_SYSTEM: base PAUSE on 7d window (more stable for low-volume ads);
  treat high cpl_3d with acceptable cpl_7d as statistical noise
- slack_notifier: campaign header shows yesterday's spend/leads explicitly;
  ad table shows CPL(3d) and CPL(7d) side by side; labels include time period

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 16:32:54 +02:00
e4840467c6 Replace aggregate Margen column with per-vertical columns in monthly table
- Track v_margins dict per day in daily_totals (one entry per vertical)
- Monthly table now shows one margin column per vertical instead of aggregate Margen
- Verticals ordered by monthly margin (best first)
- Remove separate vertical breakdown section (now redundant)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:20:50 +02:00
ff3aafe82a Add monthly margin breakdown by vertical to Slack summary table
- Compute monthly_verticals in run.py and send_slack_report.py using per-vertical CPL targets
- Display vertical breakdown (spend, leads, CPL, target, margin) inside the monthly table block
- Add alarmas vertical (CPL target 8€) to Baserow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:14:32 +02:00
ac7a0a9738 Fix Slack block limit: send one message per campaign + error logging
- Refactor send_daily_report: summary in one message, each campaign in its own
  message — eliminates the 50-block Slack limit that was silently dropping campaigns
- _post now raises RuntimeError on Slack API errors (was silently returning None)
- send_slack_report.py accepts optional date argument (defaults to yesterday)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 23:04:51 +02:00
e5c0c9f6a6 Add ad-level pause actions via Baserow and Slack
- AD_SYSTEM prompt for per-ad PAUSE/MAINTAIN decisions (>5€ spend with 0 leads)
- pause_ad() in MetaAdsClient for individual ad pausing via Meta API
- Ad PAUSE actions saved to Baserow with "ad:{id}" campaign_id prefix
- _ad_action_blocks() in Slack notifier renders pause buttons per ad
- _table_name() strips diacritics (NFKD) for monospace column alignment
- Wider name column (45 chars) in adset/ad tables to reduce truncation
- Guard in _effect_text to suppress "+0% budget adjustment" when no change
- _execute_action handles "ad:" prefix to route pause to correct API method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 21:48:16 +02:00
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