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.
This commit is contained in:
117
app/pages/versions.py
Normal file
117
app/pages/versions.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user