"""Phase 5: derive how a person's goals evolved from the profile_revision snapshots already captured since Phase 2. Pure functions over revision rows — no DB access, fully testable. """ from typing import Any, Dict, List # The goal fields we track an evolution timeline for. GOAL_FIELDS = ("short_term_goals", "long_term_goals") def goal_history(revisions: List[Dict[str, Any]]) -> Dict[str, List[dict]]: """Build a per-goal timeline of distinct values over time. Args: revisions: each ``{"fields": {: value, ...}, "source": str, "created_at": datetime}``, in ascending chronological order. Returns: ``{"short_term_goals": [{"value", "source", "at"}...], "long_term_goals": [...]}`` — one entry per *change*; consecutive identical values are collapsed so the timeline shows only when a goal actually moved. """ timelines: Dict[str, List[dict]] = {f: [] for f in GOAL_FIELDS} last: Dict[str, Any] = {f: _SENTINEL for f in GOAL_FIELDS} for rev in revisions: fields = rev.get("fields") or {} for field in GOAL_FIELDS: value = fields.get(field) if value == last[field]: continue last[field] = value timelines[field].append( { "value": value, "source": rev.get("source"), "at": rev.get("created_at"), } ) return timelines class _Sentinel: pass # Distinct from None so a first snapshot whose goal is None still emits once. _SENTINEL = _Sentinel()