"""Financial summary dashboard tab.""" from __future__ import annotations import streamlit as st from app.components import charts from app.pages._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.header("📊 Financial Summary") 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"${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"${bpv/1_000_000:,.1f}M") cols[4].metric("Costs PV", f"${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"): st.dataframe(yb, use_container_width=True, 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() ] st.dataframe(rows, use_container_width=True, 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", )