Fix leadform double-attribution and Slack message truncation

- airtable_client: restrict leadform cursoid path (path 5) to PMX-only
  campaigns, preventing triple-attribution to search+pmx+leadform companion
- slack_reporter: derive margen from ingreso-coste instead of stale MetricasDiarias
  field; split monthly rankings from fields to separate section blocks; add
  _trunc() helper to clamp all text blocks under Slack's 3000-char limit
- agent: increase portfolio_daily_analysis max_tokens 500→800
- backfill: extend to June 11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Manuel 2026-06-11 23:04:40 +02:00
parent 0eb360bf6d
commit 4848253c49
4 changed files with 36 additions and 24 deletions

View File

@ -141,7 +141,7 @@ def portfolio_daily_analysis(collected: list) -> str:
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
max_tokens=800,
system=PORTFOLIO_SYSTEM,
messages=[{
"role": "user",

View File

@ -359,7 +359,10 @@ class AirtableClient:
m = re.search(r'fco_(?:search|pmx)_(\d+)', campaign_name, re.IGNORECASE)
if m:
course_num = m.group(1)
if "pmx" in campaign_name.lower() and "_leadform" not in campaign_name.lower():
is_pmx_non_leadform = (
"pmx" in campaign_name.lower() and "_leadform" not in campaign_name.lower()
)
if is_pmx_non_leadform:
utm_source = "pmx"
elif "search" in campaign_name.lower():
utm_source = "google"
@ -370,6 +373,8 @@ class AirtableClient:
f"AND({{attr_cursoid}}='{course_num}',"
f"{{attr_utm_source}}='{utm_source}')"
)
# Path 5: leadform leads en Airtable, solo para PMX (no search, no _leadform)
if is_pmx_non_leadform:
leadform_cursoid_clause = (
f"AND({{attr_cursoid}}='{course_num}',"
f"{{UserAgent del visitante}}='Google-Ads-Notifications')"

View File

@ -8,7 +8,7 @@ import re
from datetime import datetime
from airtable_client import AirtableClient
DAYS = ["2026-06-08", "2026-06-09", "2026-06-10"]
DAYS = ["2026-06-08", "2026-06-09", "2026-06-10", "2026-06-11"]
def _course_num(name: str) -> str | None:

View File

@ -46,14 +46,20 @@ def _last_n_days_combined(current_md: dict, prev_md: dict, now: datetime, n: int
entries.sort(key=lambda x: x[0])
last_n = entries[-n:]
total_coste = round(sum(v.get("coste", 0) for _, v in last_n), 2)
total_ingreso = round(sum(v.get("ingreso", 0) for _, v in last_n), 2)
return {
"coste": round(sum(v.get("coste", 0) for _, v in last_n), 2),
"ingreso": round(sum(v.get("ingreso", 0) for _, v in last_n), 2),
"margen": round(sum(v.get("margen", 0) for _, v in last_n), 2),
"coste": total_coste,
"ingreso": total_ingreso,
"margen": round(total_ingreso - total_coste, 2),
"n_days": len(last_n),
}
def _trunc(text: str, limit: int = 2950) -> str:
return text if len(text) <= limit else text[:limit - 3] + ""
def _fmt_eur(v: float) -> str:
sign = "+" if v > 0 else ""
return f"{sign}{v:,.0f}".replace(",", ".")
@ -195,7 +201,7 @@ def build_and_send(collected: list, dry_run: bool, prev_month_metricas: dict = N
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"📅 *MÁRGENES POR DÍA — {MESES_ES[now.month].upper()}*\n```\n" + "\n".join(rows) + "\n```",
"text": _trunc(f"📅 *MÁRGENES POR DÍA — {MESES_ES[now.month].upper()}*\n```\n" + "\n".join(rows) + "\n```"),
},
}
@ -289,14 +295,14 @@ def build_and_send(collected: list, dry_run: bool, prev_month_metricas: dict = N
"type": "section",
"text": {
"type": "mrkdwn",
"text": _ranking_last5_text(worst_last5, "📉 *PEOR — ÚLTIMOS 5 DÍAS*"),
"text": _trunc(_ranking_last5_text(worst_last5, "📉 *PEOR — ÚLTIMOS 5 DÍAS*")),
},
})
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": _ranking_last5_text(best_last5, "📈 *MEJOR — ÚLTIMOS 5 DÍAS*"),
"text": _trunc(_ranking_last5_text(best_last5, "📈 *MEJOR — ÚLTIMOS 5 DÍAS*")),
},
})
@ -304,16 +310,17 @@ def build_and_send(collected: list, dry_run: bool, prev_month_metricas: dict = N
blocks.append({"type": "divider"})
blocks.append({
"type": "section",
"fields": [
{
"text": {
"type": "mrkdwn",
"text": _ranking_month_text(worst_month, "📉 *PEOR — MES EN CURSO*"),
"text": _trunc(_ranking_month_text(worst_month, "📉 *PEOR — MES EN CURSO*")),
},
{
})
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": _ranking_month_text(best_month, "📈 *MEJOR — MES EN CURSO*"),
"text": _trunc(_ranking_month_text(best_month, "📈 *MEJOR — MES EN CURSO*")),
},
],
})
if portfolio_analysis_text:
@ -322,7 +329,7 @@ def build_and_send(collected: list, dry_run: bool, prev_month_metricas: dict = N
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"🤖 *DIAGNÓSTICO ESTRATÉGICO*\n{portfolio_analysis_text}",
"text": _trunc(f"🤖 *DIAGNÓSTICO ESTRATÉGICO*\n{portfolio_analysis_text}"),
},
})