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:
parent
ff3aafe82a
commit
e4840467c6
6
run.py
6
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())
|
||||
]
|
||||
|
||||
@ -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())
|
||||
]
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user