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>
This commit is contained in:
Jose Manuel 2026-06-13 23:20:50 +02:00
parent ff3aafe82a
commit e4840467c6
3 changed files with 43 additions and 29 deletions

4
run.py
View File

@ -125,10 +125,11 @@ def run():
v = _extract_vertical(row["campaign_name"]) v = _extract_vertical(row["campaign_name"])
target = vertical_cpls.get(v, config.META_TARGET_CPL) 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) 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["spend"] += row["spend"]
d["leads"] += row["leads"] d["leads"] += row["leads"]
d["margin"] += margin 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 = monthly_verticals.setdefault(v, {"spend": 0.0, "leads": 0, "margin": 0.0, "target_cpl": target})
mv["spend"] += row["spend"] mv["spend"] += row["spend"]
mv["leads"] += row["leads"] mv["leads"] += row["leads"]
@ -140,6 +141,7 @@ def run():
"leads": int(d["leads"]), "leads": int(d["leads"]),
"cpl": round(d["spend"] / d["leads"], 2) if d["leads"] > 0 else 0.0, "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()) for date, d in sorted(_daily.items())
] ]

View File

@ -48,10 +48,11 @@ def main():
round((target - row["spend"] / row["leads"]) * row["leads"], 2) round((target - row["spend"] / row["leads"]) * row["leads"], 2)
if row["leads"] > 0 else round(-row["spend"], 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["spend"] += row["spend"]
d["leads"] += row["leads"] d["leads"] += row["leads"]
d["margin"] += margin 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 = monthly_verticals.setdefault(v, {"spend": 0.0, "leads": 0, "margin": 0.0, "target_cpl": target})
mv["spend"] += row["spend"] mv["spend"] += row["spend"]
mv["leads"] += row["leads"] mv["leads"] += row["leads"]
@ -63,6 +64,7 @@ def main():
"leads": int(d["leads"]), "leads": int(d["leads"]),
"cpl": round(d["spend"] / d["leads"], 2) if d["leads"] > 0 else 0.0, "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()) for date, d in sorted(_daily.items())
] ]

View File

@ -171,40 +171,50 @@ def send_daily_report(
}, },
] ]
# ── Rentabilidad mensual con margen ─────────────────────────────────────── # ── Rentabilidad mensual con margen por vertical ─────────────────────────
if daily_totals: if daily_totals:
lines = [f"{'Día':<5} {'Gasto':>7} {'Leads':>5} {'CPL':>7} {'Margen':>8} Est"] # Orden de verticales: por margen mensual acumulado (mejor primero)
lines.append("" * 42) 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_spend = total_leads = total_margin = 0.0
total_v = {v: 0.0 for v in v_order}
for d in daily_totals: for d in daily_totals:
day = d["date"][8:10] + "/" + d["date"][5:7] day = d["date"][8:10] + "/" + d["date"][5:7]
margin = d.get("margin", 0.0) margin = d.get("margin", 0.0)
total_spend += d["spend"] total_spend += d["spend"]
total_leads += d["leads"] total_leads += d["leads"]
total_margin += margin total_margin += margin
v_day = d.get("v_margins", {})
icon = "" if d["leads"] > 0 else ("" if d["spend"] > 0 else "") icon = "" if d["leads"] > 0 else ("" if d["spend"] > 0 else "")
m_sign = f"+{margin:.0f}" if margin >= 0 else f"{margin:.0f}" row = f"{day:<5} {d['spend']:>5.0f}{d['leads']:>5} {d['cpl']:>6.2f}"
lines.append( for v in v_order:
f"{day:<5} {d['spend']:>6.0f}{d['leads']:>5} {d['cpl']:>6.2f}{m_sign:>8} {icon}" vm = v_day.get(v, 0.0)
) total_v[v] += vm
lines.append("" * 42) vm_s = (f"+{vm:.0f}" if vm >= 0 else f"{vm:.0f}") if round(vm) != 0 else ""
total_cpl = round(total_spend / total_leads, 2) if total_leads > 0 else 0.0 row += f" {vm_s:>{cw}}"
m_tot_sign = f"+{total_margin:.0f}" if total_margin >= 0 else f"{total_margin:.0f}" row += f" {icon}"
lines.append( lines.append(row)
f"{'TOTAL':<5} {total_spend:>6.0f}{int(total_leads):>5} {total_cpl:>6.2f}{m_tot_sign:>8}"
) lines.append(sep)
# Margen mensual por vertical total_row = f"{'TOTAL':<5} {total_spend:>5.0f}{int(total_leads):>5} {'':>7}"
if monthly_verticals: for v in v_order:
mv_lines = ["", f"{'Vertical':<16} {'Gasto':>7} {'Leads':>5} {'CPL':>7} {'Obj':>5} {'Margen':>8}"] tv = total_v[v]
mv_lines.append("" * 54) tv_s = f"+{tv:.0f}" if tv >= 0 else f"{tv:.0f}"
for v, mv in sorted(monthly_verticals.items(), key=lambda x: -x[1]["margin"]): total_row += f" {tv_s:>{cw}}"
v_cpl = round(mv["spend"] / mv["leads"], 2) if mv["leads"] > 0 else 0.0 lines.append(total_row)
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
blocks.append({ blocks.append({
"type": "section", "type": "section",