"""
Email-Renderer fuer den Vendor-Redundanz + EU-Alternativen + Cost-/Savings-Block.
Wird im Email-Body unter dem VVT eingebaut.
"""
from __future__ import annotations
def _fmt_eur(low: int, high: int) -> str:
if not low and not high:
return "im Listpreis bundled"
if low == high:
return f"~{low:,} €".replace(",", ".")
return f"{low:,}–{high:,} €".replace(",", ".")
def build_redundancy_html(report: dict | None) -> str:
if not report:
return ""
s = report.get("summary") or {}
redundancies = report.get("redundancies") or []
eu_alts = report.get("eu_alternatives") or []
multi = report.get("multi_function_tools") or []
cur = s.get("estimated_current_year_eur") or [0, 0]
sav = s.get("estimated_saving_year_eur") or [0, 0]
pct = s.get("estimated_saving_pct") or "n/a"
parts = [
'
',
'
'
'Optimierungspotenzial: Redundanzen + EU-Alternativen
',
f'
'
f'{s.get("redundancy_count", 0)} Kategorien mit '
f'mehreren Anbietern · {s.get("consolidation_potential", 0)} '
f'Anbieter konsolidierbar · '
f'{s.get("eu_alternative_count", 0)} EU-Alternativen verfuegbar
',
'
',
'
'
'Diese Schaetzung umfasst NUR die als redundant erkannten Tools — '
'nicht den Gesamt-Stack der Website
',
f'
'
f'Listpreis-Schaetzung der redundanten Tools '
f'(Mehrfach-Anbieter in derselben Funktions-Kategorie):'
f' {_fmt_eur(*cur)}/Jahr
',
f'
'
f'Sparpotenzial durch Konsolidierung auf je 1 EU-Tool pro Kategorie:'
f' {_fmt_eur(*sav)}/Jahr ({pct})
',
'
'
'Wichtige Einschraenkungen:
'
'• Konzern-Konditionen liegen ueblicherweise 30–50% unter Listpreis — '
'realistisches Saving entsprechend €X·0,5 bis €X·0,7.
'
'• Eintraege "Eigene Marke — Tool" (z.B. "BMW AG — Adobe Analytics") '
'gehoeren oft zu einem einzigen Master-Vertrag, nicht zu mehreren Lizenzen.
'
'• Media-Spend (Google Ads, Meta Ads) ist NICHT enthalten — nur Tooling-Lizenzen.
'
'• Quelle: Gartner/Forrester 2025 + oeffentliche Listpreise.'
'
',
]
if redundancies:
parts.append(
'
'
''
'| Kategorie | '
'# | '
'Anbieter | '
'EU-Empfehlung | '
'Saving / Jahr | '
'
'
)
for r in redundancies[:12]:
vendors_str = ", ".join(r.get("vendors", [])[:6])
if len(r.get("vendors", [])) > 6:
vendors_str += f" (+{len(r['vendors']) - 6} weitere)"
sav_r = r.get("estimated_saving_year_eur") or [0, 0]
parts.append(
f''
f'| {r["category_label"]} | '
f'{r["count"]} | '
f'{vendors_str} | '
f'{r.get("suggested_eu_tool") or "–"} | '
f''
f'{_fmt_eur(*sav_r)} |
'
)
hint = r.get("consolidation_hint")
if hint:
parts.append(
f'| '
f'Hinweis: {hint} |
'
)
caveats = r.get("caveats") or []
if caveats:
parts.append(
f'| '
f'Moegliche Gruende fuer Mehrfach-Einsatz: '
+ "; ".join(caveats) + ' |
'
)
parts.append('
')
if multi:
parts.append(
'
'
'Multi-Funktions-Tools (1 Tool ersetzt mehrere Kategorien):'
'
'
)
for t in multi[:4]:
cats = ", ".join(t.get("replaces_categories", []))
parts.append(
f'- {t["name"]}'
f' ({t["country"]}) — ersetzt {cats}'
f' ({t.get("potential_replacements", 0)} Anbieter heute)
'
)
parts.append('
')
if eu_alts:
parts.append(
'
EU-Alternativen pro Anbieter (Details)
'
''
)
for e in eu_alts[:20]:
first_alt = (e.get("alternatives") or [{}])[0]
parts.append(
f'- {e["current_vendor"]}'
f' → {first_alt.get("name", "")} ({first_alt.get("country", "")})'
f' — {first_alt.get("notes", "")}
'
)
parts.append('
')
parts.append('
')
return "".join(parts)