""" IPython display helpers — KPI cards, formatted summary blocks, alerts. Functions are notebook-safe: they fall back to plain ``print`` when running outside Jupyter / when IPython is not available. """ from __future__ import annotations from typing import Any try: # pragma: no cover – IPython is a soft dep from IPython.display import HTML, display _IPY = True except Exception: # pragma: no cover _IPY = False def _money(value: Any, default: str = "—") -> str: try: v = float(value) except (TypeError, ValueError): return default if abs(v) >= 1_000_000_000: return f"${v/1_000_000_000:,.1f}B" if abs(v) >= 1_000_000: return f"${v/1_000_000:,.1f}M" if abs(v) >= 1_000: return f"${v/1_000:,.1f}K" return f"${v:,.0f}" def _pct(value: Any, default: str = "—") -> str: try: v = float(value) except (TypeError, ValueError): return default return f"{v:,.0f}%" def _months(value: Any, default: str = "N/A") -> str: if value is None: return default try: v = float(value) except (TypeError, ValueError): return default if v < 6: return f"<6 months ({v:.1f})" return f"{v:.1f} months" def kpi_cards(summary: dict, *, title: str | None = None) -> Any: """ Render a row of KPI cards (NPV, ROI, Payback, Benefits PV). In notebooks, returns/displays inline HTML. Outside IPython, prints a plain text version. """ npv = _money(summary.get("npv")) roi = _pct(summary.get("roi") or summary.get("roi_pct")) payback = _months(summary.get("payback_months")) benefits_pv = _money(summary.get("total_benefits_pv")) costs_pv = _money(summary.get("total_costs_pv")) if not _IPY: # pragma: no cover print(title or "TEI Summary") print(f" NPV: {npv} ROI: {roi} Payback: {payback}") print(f" Benefits PV: {benefits_pv} Costs PV: {costs_pv}") return None title_html = ( f'