feat: add locale formatting config and update notebook outputs
Add configurable locale/display formatting environment variables (`PALLADIUM_CURRENCY_SYMBOL`, `PALLADIUM_THOUSANDS_SEP`, `PALLADIUM_DECIMAL_SEP`) to support regional number formatting in the Streamlit app. Update `.env.example` with documentation for these new variables. Also refresh `00_setup.ipynb` with current execution outputs reflecting a live Athena connection with report templates, a selected client (Global Guardian Insurance, ID=2), and resolved NameError in assumption override cells.
This commit is contained in:
@@ -5,12 +5,17 @@ from __future__ import annotations
|
||||
import streamlit as st
|
||||
|
||||
from app.components.tables import df_to_values, value_editor
|
||||
from app.pages._helpers import report_meta, safe
|
||||
from app.utils import icon
|
||||
|
||||
from app.views._helpers import report_meta, safe
|
||||
from core.tei_client import TEIClient
|
||||
|
||||
|
||||
def render(client: TEIClient, tool: dict) -> None:
|
||||
st.header("💰 Benefits")
|
||||
st.markdown(
|
||||
f"<h2>{icon('graph-up-arrow')} Benefits</h2>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
public_id = tool["id"]
|
||||
report = report_meta(client, tool)
|
||||
analysis_years = int(report.get("analysis_period_years") or 3)
|
||||
@@ -32,7 +37,8 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
|
||||
col1, col2 = st.columns([1, 4])
|
||||
with col1:
|
||||
if st.button("💾 Save benefits", use_container_width=True):
|
||||
if st.button("Save benefits", width="stretch"):
|
||||
|
||||
payload = df_to_values(edited, "benefits", analysis_years)
|
||||
result = safe(client.update_values, public_id, payload)
|
||||
if result is not None:
|
||||
|
||||
@@ -5,12 +5,17 @@ from __future__ import annotations
|
||||
import streamlit as st
|
||||
|
||||
from app.components.tables import df_to_values, value_editor
|
||||
from app.pages._helpers import report_meta, safe
|
||||
from app.utils import icon
|
||||
|
||||
from app.views._helpers import report_meta, safe
|
||||
from core.tei_client import TEIClient
|
||||
|
||||
|
||||
def render(client: TEIClient, tool: dict) -> None:
|
||||
st.header("💸 Costs")
|
||||
st.markdown(
|
||||
f"<h2>{icon('receipt')} Costs</h2>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
public_id = tool["id"]
|
||||
report = report_meta(client, tool)
|
||||
analysis_years = int(report.get("analysis_period_years") or 3)
|
||||
@@ -32,7 +37,8 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
|
||||
col1, col2 = st.columns([1, 4])
|
||||
with col1:
|
||||
if st.button("💾 Save costs", use_container_width=True):
|
||||
if st.button("Save costs", width="stretch"):
|
||||
|
||||
payload = df_to_values(edited, "costs", analysis_years)
|
||||
result = safe(client.update_values, public_id, payload)
|
||||
if result is not None:
|
||||
|
||||
@@ -5,13 +5,19 @@ from __future__ import annotations
|
||||
import streamlit as st
|
||||
|
||||
from app.components import charts
|
||||
from app.pages._helpers import report_meta, safe
|
||||
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.header("📊 Financial Summary")
|
||||
st.markdown(
|
||||
f"<h2>{icon('bar-chart-line')} Financial Summary</h2>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
public_id = tool["id"]
|
||||
report = report_meta(client, tool)
|
||||
|
||||
@@ -39,14 +45,14 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
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[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"${bpv/1_000_000:,.1f}M")
|
||||
cols[4].metric("Costs PV", f"${cpv/1_000_000:,.1f}M")
|
||||
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()
|
||||
|
||||
@@ -64,7 +70,18 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
if yb:
|
||||
charts.cashflow(yb, initial_cost=initial)
|
||||
with st.expander("Cash flow table"):
|
||||
st.dataframe(yb, use_container_width=True, hide_index=True)
|
||||
_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.")
|
||||
|
||||
@@ -94,11 +111,26 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
}
|
||||
for k, v in envelope["scenarios"].items()
|
||||
]
|
||||
st.dataframe(rows, use_container_width=True, hide_index=True)
|
||||
_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)"):
|
||||
if st.button("Build export envelope (JSON)"):
|
||||
envelope = safe(
|
||||
build_report_data,
|
||||
client,
|
||||
|
||||
@@ -4,7 +4,9 @@ from __future__ import annotations
|
||||
|
||||
import streamlit as st
|
||||
|
||||
from app.pages._helpers import safe
|
||||
from app.utils import icon
|
||||
|
||||
from app.views._helpers import safe
|
||||
from core.tei_client import TEIClient
|
||||
|
||||
|
||||
@@ -17,6 +19,7 @@ def _diff_rows(a: dict[str, dict], b: dict[str, dict]) -> list[dict]:
|
||||
"""Return one row per field with side-by-side year values."""
|
||||
keys = sorted(set(a.keys()) | set(b.keys()))
|
||||
rows: list[dict] = []
|
||||
|
||||
def _years_of(v: dict) -> dict:
|
||||
"""Accept both friendly (year_values) and wire (nested years) shapes."""
|
||||
if isinstance(v.get("year_values"), dict):
|
||||
@@ -54,7 +57,10 @@ def _diff_rows(a: dict[str, dict], b: dict[str, dict]) -> list[dict]:
|
||||
|
||||
|
||||
def render(client: TEIClient, tool: dict) -> None:
|
||||
st.header("🕒 Versions")
|
||||
st.markdown(
|
||||
f"<h2>{icon('clock-history')} Versions</h2>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
public_id = tool["id"]
|
||||
|
||||
versions = safe(client.list_versions, public_id) or []
|
||||
@@ -63,7 +69,7 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
)
|
||||
|
||||
# Save new version
|
||||
with st.expander("➕ Save current state as a new version", expanded=not versions):
|
||||
with st.expander("Save current state as a new version", expanded=not versions):
|
||||
note = st.text_area(
|
||||
"Version note",
|
||||
placeholder=(
|
||||
@@ -71,7 +77,7 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
"raised legacy license cost from $160 to $180/agent.'"
|
||||
),
|
||||
)
|
||||
if st.button("💾 Save version", disabled=not note.strip()):
|
||||
if st.button("Save version", disabled=not note.strip()):
|
||||
result = safe(client.save_version, public_id, note.strip())
|
||||
if result:
|
||||
st.success(
|
||||
@@ -102,7 +108,8 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
"Note": v.get("note", ""),
|
||||
}
|
||||
)
|
||||
st.dataframe(rows, use_container_width=True, hide_index=True)
|
||||
st.dataframe(rows, width="stretch", hide_index=True)
|
||||
|
||||
|
||||
# Compare two versions
|
||||
st.subheader("Compare")
|
||||
@@ -132,4 +139,5 @@ def render(client: TEIClient, tool: dict) -> None:
|
||||
if not diff:
|
||||
st.success("No value differences between these versions.")
|
||||
else:
|
||||
st.dataframe(diff, use_container_width=True, hide_index=True)
|
||||
st.dataframe(diff, width="stretch", hide_index=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user