"""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",
)