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"])
|
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"]
|
||||||
@ -139,7 +140,8 @@ def run():
|
|||||||
"spend": round(d["spend"], 2),
|
"spend": round(d["spend"], 2),
|
||||||
"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())
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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"]
|
||||||
@ -62,7 +63,8 @@ def main():
|
|||||||
"spend": round(d["spend"], 2),
|
"spend": round(d["spend"], 2),
|
||||||
"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())
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user