"""Version history tab — list, diff, save, restore.""" from __future__ import annotations import streamlit as st from app.utils import icon from app.views._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] = [] def _years_of(v: dict) -> dict: """Accept both friendly (year_values) and wire (nested years) shapes.""" if isinstance(v.get("year_values"), dict): return {str(k): val for k, val in v["year_values"].items()} if isinstance(v.get("years"), dict): return { str(k): (cell or {}).get("value") for k, cell in v["years"].items() } if v.get("value") is not None: return {"1": v["value"]} return {} for k in keys: av = a.get(k, {}) or {} bv = b.get(k, {}) or {} ay = _years_of(av) by = _years_of(bv) 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.markdown( f"

{icon('clock-history')} Versions

", unsafe_allow_html=True, ) 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("net_present_value") or snap.get("npv") or 0), "ROI %": float( snap.get("roi_percentage") or snap.get("roi") or snap.get("roi_pct") or 0 ), "Note": v.get("note", ""), } ) st.dataframe(rows, width="stretch", 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, width="stretch", hide_index=True)