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:
parent
0eb360bf6d
commit
4848253c49
2
agent.py
2
agent.py
@ -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",
|
||||
|
||||
@ -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')"
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}"),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user