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:
|
try:
|
||||||
response = client.messages.create(
|
response = client.messages.create(
|
||||||
model="claude-sonnet-4-6",
|
model="claude-sonnet-4-6",
|
||||||
max_tokens=500,
|
max_tokens=800,
|
||||||
system=PORTFOLIO_SYSTEM,
|
system=PORTFOLIO_SYSTEM,
|
||||||
messages=[{
|
messages=[{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|||||||
@ -359,7 +359,10 @@ class AirtableClient:
|
|||||||
m = re.search(r'fco_(?:search|pmx)_(\d+)', campaign_name, re.IGNORECASE)
|
m = re.search(r'fco_(?:search|pmx)_(\d+)', campaign_name, re.IGNORECASE)
|
||||||
if m:
|
if m:
|
||||||
course_num = m.group(1)
|
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"
|
utm_source = "pmx"
|
||||||
elif "search" in campaign_name.lower():
|
elif "search" in campaign_name.lower():
|
||||||
utm_source = "google"
|
utm_source = "google"
|
||||||
@ -370,10 +373,12 @@ class AirtableClient:
|
|||||||
f"AND({{attr_cursoid}}='{course_num}',"
|
f"AND({{attr_cursoid}}='{course_num}',"
|
||||||
f"{{attr_utm_source}}='{utm_source}')"
|
f"{{attr_utm_source}}='{utm_source}')"
|
||||||
)
|
)
|
||||||
leadform_cursoid_clause = (
|
# Path 5: leadform leads en Airtable, solo para PMX (no search, no _leadform)
|
||||||
f"AND({{attr_cursoid}}='{course_num}',"
|
if is_pmx_non_leadform:
|
||||||
f"{{UserAgent del visitante}}='Google-Ads-Notifications')"
|
leadform_cursoid_clause = (
|
||||||
)
|
f"AND({{attr_cursoid}}='{course_num}',"
|
||||||
|
f"{{UserAgent del visitante}}='Google-Ads-Notifications')"
|
||||||
|
)
|
||||||
|
|
||||||
formula = (
|
formula = (
|
||||||
f"AND("
|
f"AND("
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import re
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from airtable_client import AirtableClient
|
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:
|
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])
|
entries.sort(key=lambda x: x[0])
|
||||||
last_n = entries[-n:]
|
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 {
|
return {
|
||||||
"coste": round(sum(v.get("coste", 0) for _, v in last_n), 2),
|
"coste": total_coste,
|
||||||
"ingreso": round(sum(v.get("ingreso", 0) for _, v in last_n), 2),
|
"ingreso": total_ingreso,
|
||||||
"margen": round(sum(v.get("margen", 0) for _, v in last_n), 2),
|
"margen": round(total_ingreso - total_coste, 2),
|
||||||
"n_days": len(last_n),
|
"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:
|
def _fmt_eur(v: float) -> str:
|
||||||
sign = "+" if v > 0 else ""
|
sign = "+" if v > 0 else ""
|
||||||
return f"{sign}{v:,.0f}€".replace(",", ".")
|
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",
|
"type": "section",
|
||||||
"text": {
|
"text": {
|
||||||
"type": "mrkdwn",
|
"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",
|
"type": "section",
|
||||||
"text": {
|
"text": {
|
||||||
"type": "mrkdwn",
|
"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({
|
blocks.append({
|
||||||
"type": "section",
|
"type": "section",
|
||||||
"text": {
|
"text": {
|
||||||
"type": "mrkdwn",
|
"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": "divider"})
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"type": "section",
|
"type": "section",
|
||||||
"fields": [
|
"text": {
|
||||||
{
|
"type": "mrkdwn",
|
||||||
"type": "mrkdwn",
|
"text": _trunc(_ranking_month_text(worst_month, "📉 *PEOR — MES EN CURSO*")),
|
||||||
"text": _ranking_month_text(worst_month, "📉 *PEOR — MES EN CURSO*"),
|
},
|
||||||
},
|
})
|
||||||
{
|
blocks.append({
|
||||||
"type": "mrkdwn",
|
"type": "section",
|
||||||
"text": _ranking_month_text(best_month, "📈 *MEJOR — MES EN CURSO*"),
|
"text": {
|
||||||
},
|
"type": "mrkdwn",
|
||||||
],
|
"text": _trunc(_ranking_month_text(best_month, "📈 *MEJOR — MES EN CURSO*")),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if portfolio_analysis_text:
|
if portfolio_analysis_text:
|
||||||
@ -322,7 +329,7 @@ def build_and_send(collected: list, dry_run: bool, prev_month_metricas: dict = N
|
|||||||
"type": "section",
|
"type": "section",
|
||||||
"text": {
|
"text": {
|
||||||
"type": "mrkdwn",
|
"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