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:
2026-06-10 11:54:28 -04:00
parent 253ff38118
commit ecd164ee6d
13 changed files with 839 additions and 111 deletions

87
app/locale.py Normal file
View File

@@ -0,0 +1,87 @@
"""
Locale / formatting settings for the Palladium Streamlit app.
All settings are read from environment variables (via .env) so the same
codebase can be deployed for different regions without code changes.
Environment variables
---------------------
PALLADIUM_CURRENCY_SYMBOL Default: "$"
Prefix shown before monetary values (e.g. "$", "", "£", "CAD ").
PALLADIUM_THOUSANDS_SEP Default: ","
Thousands separator used in number display (e.g. "," for Americas,
"." for continental Europe, " " for some locales).
PALLADIUM_DECIMAL_SEP Default: "."
Decimal separator (e.g. "." for Americas/UK, "," for continental Europe).
Note: Streamlit's NumberColumn ``format`` uses printf-style strings.
The ``%,`` flag (thousands separator) is supported in Streamlit ≥ 1.31.
For non-standard separators (e.g. European "." thousands / "," decimal)
the values are pre-formatted as strings and displayed in TextColumns.
"""
from __future__ import annotations
import os
def _env(key: str, default: str) -> str:
return os.environ.get(key, default).strip()
# ---------------------------------------------------------------------------
# Resolved settings (read once at import time; restart app to pick up changes)
# ---------------------------------------------------------------------------
CURRENCY_SYMBOL: str = _env("PALLADIUM_CURRENCY_SYMBOL", "$")
THOUSANDS_SEP: str = _env("PALLADIUM_THOUSANDS_SEP", ",")
DECIMAL_SEP: str = _env("PALLADIUM_DECIMAL_SEP", ".")
# True when the locale uses standard printf-compatible separators
# (i.e. "," thousands + "." decimal — the C/POSIX default).
# When False, we pre-format values as strings instead of relying on printf.
_STANDARD_LOCALE: bool = THOUSANDS_SEP == "," and DECIMAL_SEP == "."
def currency_fmt() -> str:
"""Return a Streamlit NumberColumn ``format`` string for currency.
For standard locales returns e.g. ``"$%,.0f"`` (thousands-separated,
no decimal places). For non-standard locales returns ``"%s"`` and
callers should use :func:`fmt_currency` to pre-format the value.
"""
if _STANDARD_LOCALE:
return f"{CURRENCY_SYMBOL}%,.0f"
return "%s"
def pct_fmt() -> str:
"""Return a Streamlit NumberColumn ``format`` string for percentages.
Stores the value as a fraction (01) and displays as e.g. ``"20.00%"``.
Streamlit's ``%%`` in format strings renders a literal ``%``.
"""
if _STANDARD_LOCALE:
return "%.2f%%"
return "%s"
def fmt_currency(value: float) -> str:
"""Format *value* as a currency string using the configured locale."""
if _STANDARD_LOCALE:
return f"{CURRENCY_SYMBOL}{value:,.0f}"
# Non-standard: build manually
integer_part = f"{int(abs(value)):,}".replace(",", THOUSANDS_SEP)
sign = "-" if value < 0 else ""
return f"{sign}{CURRENCY_SYMBOL}{integer_part}"
def fmt_pct(value: float) -> str:
"""Format *value* (01 fraction) as a percentage string."""
pct = value * 100
if _STANDARD_LOCALE:
return f"{pct:.2f}%"
integer_part = f"{int(pct)}"
decimal_part = f"{abs(pct) % 1:.2f}"[1:] # ".xx"
return f"{integer_part}{DECIMAL_SEP}{decimal_part[1:]}%"