From e4840467c6171517af2e63c3ba36ec576033973b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Sat, 13 Jun 2026 23:20:50 +0200 Subject: [PATCH] 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 --- run.py | 6 +++-- send_slack_report.py | 6 +++-- slack_notifier.py | 60 ++++++++++++++++++++++++++------------------ 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/run.py b/run.py index abce877..40ed713 100644 --- a/run.py +++ b/run.py @@ -125,10 +125,11 @@ def run(): v = _extract_vertical(row["campaign_name"]) target = vertical_cpls.get(v, config.META_TARGET_CPL) margin = round((target - row["spend"] / row["leads"]) * row["leads"], 2) if row["leads"] > 0 else round(-row["spend"], 2) - d = _daily.setdefault(row["date"], {"spend": 0.0, "leads": 0, "margin": 0.0}) + d = _daily.setdefault(row["date"], {"spend": 0.0, "leads": 0, "margin": 0.0, "v_margins": {}}) d["spend"] += row["spend"] d["leads"] += row["leads"] d["margin"] += margin + d["v_margins"][v] = d["v_margins"].get(v, 0.0) + margin mv = monthly_verticals.setdefault(v, {"spend": 0.0, "leads": 0, "margin": 0.0, "target_cpl": target}) mv["spend"] += row["spend"] mv["leads"] += row["leads"] @@ -139,7 +140,8 @@ def run(): "spend": round(d["spend"], 2), "leads": int(d["leads"]), "cpl": round(d["spend"] / d["leads"], 2) if d["leads"] > 0 else 0.0, - "margin": round(d["margin"], 2), + "margin": round(d["margin"], 2), + "v_margins": {v: round(m, 0) for v, m in d["v_margins"].items()}, } for date, d in sorted(_daily.items()) ] diff --git a/send_slack_report.py b/send_slack_report.py index 618d8d2..d49d407 100644 --- a/send_slack_report.py +++ b/send_slack_report.py @@ -48,10 +48,11 @@ def main(): round((target - row["spend"] / row["leads"]) * row["leads"], 2) if row["leads"] > 0 else round(-row["spend"], 2) ) - d = _daily.setdefault(row["date"], {"spend": 0.0, "leads": 0, "margin": 0.0}) + d = _daily.setdefault(row["date"], {"spend": 0.0, "leads": 0, "margin": 0.0, "v_margins": {}}) d["spend"] += row["spend"] d["leads"] += row["leads"] d["margin"] += margin + d["v_margins"][v] = d["v_margins"].get(v, 0.0) + margin mv = monthly_verticals.setdefault(v, {"spend": 0.0, "leads": 0, "margin": 0.0, "target_cpl": target}) mv["spend"] += row["spend"] mv["leads"] += row["leads"] @@ -62,7 +63,8 @@ def main(): "spend": round(d["spend"], 2), "leads": int(d["leads"]), "cpl": round(d["spend"] / d["leads"], 2) if d["leads"] > 0 else 0.0, - "margin": round(d["margin"], 2), + "margin": round(d["margin"], 2), + "v_margins": {v: round(m, 0) for v, m in d["v_margins"].items()}, } for date, d in sorted(_daily.items()) ] diff --git a/slack_notifier.py b/slack_notifier.py index 46b6850..f57059e 100644 --- a/slack_notifier.py +++ b/slack_notifier.py @@ -171,40 +171,50 @@ def send_daily_report( }, ] - # ── Rentabilidad mensual con margen ─────────────────────────────────────── + # ── Rentabilidad mensual con margen por vertical ───────────────────────── if daily_totals: - lines = [f"{'Día':<5} {'Gasto':>7} {'Leads':>5} {'CPL':>7} {'Margen':>8} Est"] - lines.append("─" * 42) + # Orden de verticales: por margen mensual acumulado (mejor primero) + v_order = ( + sorted(monthly_verticals.keys(), key=lambda v: -monthly_verticals[v]["margin"]) + if monthly_verticals else [] + ) + cw = 7 # ancho de cada columna de vertical + + # Cabecera + header = f"{'Día':<5} {'Gasto':>6} {'Leads':>5} {'CPL':>7}" + for v in v_order: + header += f" {v[:6]:>{cw}}" + header += " Est" + sep = "─" * len(header) + + lines = [header, sep] total_spend = total_leads = total_margin = 0.0 + total_v = {v: 0.0 for v in v_order} + for d in daily_totals: day = d["date"][8:10] + "/" + d["date"][5:7] margin = d.get("margin", 0.0) total_spend += d["spend"] total_leads += d["leads"] total_margin += margin + v_day = d.get("v_margins", {}) icon = "✅" if d["leads"] > 0 else ("❌" if d["spend"] > 0 else "—") - m_sign = f"+{margin:.0f}€" if margin >= 0 else f"{margin:.0f}€" - lines.append( - f"{day:<5} {d['spend']:>6.0f}€ {d['leads']:>5} {d['cpl']:>6.2f}€ {m_sign:>8} {icon}" - ) - lines.append("─" * 42) - total_cpl = round(total_spend / total_leads, 2) if total_leads > 0 else 0.0 - m_tot_sign = f"+{total_margin:.0f}€" if total_margin >= 0 else f"{total_margin:.0f}€" - lines.append( - f"{'TOTAL':<5} {total_spend:>6.0f}€ {int(total_leads):>5} {total_cpl:>6.2f}€ {m_tot_sign:>8}" - ) - # Margen mensual por vertical - if monthly_verticals: - mv_lines = ["", f"{'Vertical':<16} {'Gasto':>7} {'Leads':>5} {'CPL':>7} {'Obj':>5} {'Margen':>8}"] - mv_lines.append("─" * 54) - for v, mv in sorted(monthly_verticals.items(), key=lambda x: -x[1]["margin"]): - v_cpl = round(mv["spend"] / mv["leads"], 2) if mv["leads"] > 0 else 0.0 - m_sign = f"+{mv['margin']:.0f}€" if mv["margin"] >= 0 else f"{mv['margin']:.0f}€" - obj = mv.get("target_cpl", 0) - mv_lines.append( - f"{v:<16} {mv['spend']:>6.0f}€ {mv['leads']:>5} {v_cpl:>6.2f}€ {obj:>4.1f}€ {m_sign:>8}" - ) - lines += mv_lines + row = f"{day:<5} {d['spend']:>5.0f}€ {d['leads']:>5} {d['cpl']:>6.2f}€" + for v in v_order: + vm = v_day.get(v, 0.0) + total_v[v] += vm + vm_s = (f"+{vm:.0f}€" if vm >= 0 else f"{vm:.0f}€") if round(vm) != 0 else " —" + row += f" {vm_s:>{cw}}" + row += f" {icon}" + lines.append(row) + + lines.append(sep) + total_row = f"{'TOTAL':<5} {total_spend:>5.0f}€ {int(total_leads):>5} {'':>7}" + for v in v_order: + tv = total_v[v] + tv_s = f"+{tv:.0f}€" if tv >= 0 else f"{tv:.0f}€" + total_row += f" {tv_s:>{cw}}" + lines.append(total_row) blocks.append({ "type": "section",