Files
palladium/app/pages/versions.py
Robert Helewka a2420ed692 refactor: restructure repo into core/app modules with per-study folders
Reorganize Palladium codebase into a modular architecture with `core/`
shared logic and `app/` Streamlit UI, separating per-study assets into
`studies/YYYYMM_<Vendor>/` folders containing notebooks, seed data, and
configuration. Update README to reflect new structure, add `.gitignore`
entries for `.env` and study exports, and refresh component documentation.
2026-05-20 22:28:12 -04:00

118 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Version history tab — list, diff, save, restore."""
from __future__ import annotations
import streamlit as st
from app.pages._helpers import safe
from core.tei_client import TEIClient
def _flatten_values(values: list[dict]) -> dict[str, dict]:
"""Index a values list by field_key for easy diffing."""
return {v.get("field_key", ""): v for v in values}
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] = []
for k in keys:
av = a.get(k, {}) or {}
bv = b.get(k, {}) or {}
ay = av.get("year_values") or {}
by = bv.get("year_values") or {}
years = sorted(set(ay.keys()) | set(by.keys()), key=lambda x: int(x))
for y in years:
a_val = float(ay.get(y) or 0)
b_val = float(by.get(y) or 0)
if abs(a_val - b_val) < 1e-9:
continue
rows.append(
{
"field_key": k,
"year": y,
"left": a_val,
"right": b_val,
"delta": b_val - a_val,
}
)
return rows
def render(client: TEIClient, tool: dict) -> None:
st.header("🕒 Versions")
public_id = tool["id"]
versions = safe(client.list_versions, public_id) or []
versions = sorted(
versions, key=lambda v: int(v.get("version_number") or 0), reverse=True
)
# Save new version
with st.expander(" Save current state as a new version", expanded=not versions):
note = st.text_area(
"Version note",
placeholder=(
"What changed? E.g. 'CFO confirmed 1.8M contacts/month; "
"raised legacy license cost from $160 to $180/agent.'"
),
)
if st.button("💾 Save version", disabled=not note.strip()):
result = safe(client.save_version, public_id, note.strip())
if result:
st.success(
f"Saved version {result.get('version_number', '?')}."
)
st.rerun()
if not versions:
st.info("No versions saved yet.")
return
# Listing
st.subheader("History")
rows = []
for v in versions:
snap = v.get("summary_snapshot") or v.get("summary") or {}
rows.append(
{
"Version": v.get("version_number"),
"Date": v.get("created_at") or v.get("date"),
"NPV": float(snap.get("npv") or 0),
"ROI %": float(snap.get("roi") or snap.get("roi_pct") or 0),
"Note": v.get("note", ""),
}
)
st.dataframe(rows, use_container_width=True, hide_index=True)
# Compare two versions
st.subheader("Compare")
if len(versions) < 2:
st.caption("Save two or more versions to compare.")
return
labels = {f"v{v['version_number']}{v.get('note', '')[:40]}": v for v in versions}
keys = list(labels.keys())
c1, c2 = st.columns(2)
with c1:
left_label = st.selectbox("Left (older)", keys, index=min(1, len(keys) - 1))
with c2:
right_label = st.selectbox("Right (newer)", keys, index=0)
if left_label == right_label:
st.caption("Pick two different versions to see a diff.")
return
left = safe(client.get_version, public_id, labels[left_label]["version_number"])
right = safe(client.get_version, public_id, labels[right_label]["version_number"])
if not (left and right):
return
a_values = left.get("values_snapshot") or left.get("values") or []
b_values = right.get("values_snapshot") or right.get("values") or []
diff = _diff_rows(_flatten_values(a_values), _flatten_values(b_values))
if not diff:
st.success("No value differences between these versions.")
else:
st.dataframe(diff, use_container_width=True, hide_index=True)