"""Financial summary dashboard tab.""" from __future__ import annotations import streamlit as st from app.components import charts from app.locale import CURRENCY_SYMBOL, currency_fmt, fmt_currency from app.utils import icon from app.views._helpers import report_meta, safe from core.export import build_report_data from core.tei_client import AthenaAPIError, TEIClient def render(client: TEIClient, tool: dict) -> None: st.markdown( f"

{icon('bar-chart-line')} Financial Summary

", unsafe_allow_html=True, ) public_id = tool["id"] report = report_meta(client, tool) try: summary = client.get_summary(public_id) except AthenaAPIError as e: if e.status_code == 404: st.info( "No summary yet — click **Recalculate** in the sidebar after " "filling in benefits and costs." ) return st.error(f"Athena API error: {e.detail}") return npv = float(summary.get("net_present_value") or summary.get("npv") or 0) roi = float( summary.get("roi_percentage") or summary.get("roi") or summary.get("roi_pct") or 0 ) payback = summary.get("payback_period_months", summary.get("payback_months")) bpv = float(summary.get("total_benefits_pv") or 0) cpv = float(summary.get("total_costs_pv") or 0) cols = st.columns(5) cols[0].metric("NPV", f"{CURRENCY_SYMBOL}{npv/1_000_000:,.1f}M") cols[1].metric("ROI", f"{roi:,.0f}%") cols[2].metric( "Payback", f"{float(payback):.1f} months" if payback is not None else "N/A", ) cols[3].metric("Benefits PV", f"{CURRENCY_SYMBOL}{bpv/1_000_000:,.1f}M") cols[4].metric("Costs PV", f"{CURRENCY_SYMBOL}{cpv/1_000_000:,.1f}M") st.divider() # Build the yearly breakdown from the documented per-year summary keys # (benefits_year_N / costs_year_N) when no pre-built breakdown exists. yb = summary.get("yearly_breakdown") or [] if not yb: n = 1 while f"benefits_year_{n}" in summary or f"costs_year_{n}" in summary: b = float(summary.get(f"benefits_year_{n}") or 0) c = float(summary.get(f"costs_year_{n}") or 0) yb.append({"year": n, "benefits": b, "costs": c, "net": b - c}) n += 1 initial = float(summary.get("initial_costs") or 0) if yb: charts.cashflow(yb, initial_cost=initial) with st.expander("Cash flow table"): _cur = currency_fmt() st.dataframe( yb, column_config={ "year": st.column_config.NumberColumn("Year", format="%d"), "benefits": st.column_config.NumberColumn("Benefits", format=_cur), "costs": st.column_config.NumberColumn("Costs", format=_cur), "net": st.column_config.NumberColumn("Net", format=_cur), }, width="stretch", hide_index=True, ) else: st.caption("No yearly breakdown in this summary.") # Scenario comparison — computed locally from current values with st.expander("Scenario analysis (conservative / moderate / aggressive)"): envelope = safe( build_report_data, client, public_id, include_scenarios=True, study_slug=report.get("name", ""), ) if envelope and envelope.get("scenarios"): charts.scenario_bars(envelope["scenarios"]) rows = [ { "Scenario": k, "Benefits PV": float(v.get("total_benefits_pv") or 0), "Costs PV": float(v.get("total_costs_pv") or 0), "NPV": float(v.get("npv") or 0), "ROI %": float(v.get("roi_pct") or 0), "Payback (months)": ( round(float(v.get("payback_months") or 0), 1) if v.get("payback_months") is not None else None ), } for k, v in envelope["scenarios"].items() ] _cur = currency_fmt() st.dataframe( rows, column_config={ "Scenario": st.column_config.TextColumn("Scenario"), "Benefits PV": st.column_config.NumberColumn("Benefits PV", format=_cur), "Costs PV": st.column_config.NumberColumn("Costs PV", format=_cur), "NPV": st.column_config.NumberColumn("NPV", format=_cur), "ROI %": st.column_config.NumberColumn("ROI %", format="%.1f%%"), "Payback (months)": st.column_config.NumberColumn( "Payback (months)", format="%.1f" ), }, width="stretch", hide_index=True, ) # Export button st.divider() if st.button("Build export envelope (JSON)"): envelope = safe( build_report_data, client, public_id, include_scenarios=True, study_slug=report.get("name", ""), ) if envelope: import json data = json.dumps(envelope, indent=2, default=str) st.download_button( "Download export.json", data=data, file_name=f"{public_id}_export.json", mime="application/json", )