Phase 5: iteration & polish (visualizations, goal history, smart tagging)

Final roadmap phase. No DB migration — it reads data already captured.

- Goal-evolution history: GET /discovery/profile/me/goal-history derives a
  per-goal timeline from the profile_revision snapshots (pure aggregator in
  app/services/profile_history.py).
- Smart tagging: POST /discovery/integration/suggest-foundation suggests which
  foundation a task builds toward + rationale/confidence (FoundationTagger,
  app/services/tagging.py). Suggestion only; the person confirms by posting the
  task mapping.
- Deeper goal-refinement: the reflect loop accepts an optional focus ("goals")
  that steers the coach toward sharpening goals — still mirror, not compass.
- Visualizations: visuals.html renders an Ikigai Venn and an Enneagram diagram
  (plain-language callouts, not the raw type number) plus the goal-evolution
  timeline; linked from profile.html.

Tests: 99 passing (added pure goal-history tests, goal-history + suggest
endpoint tests, reflect-focus passthrough; run in-container). README updated.

This completes the ImpactFlow Vision roadmap (Phases 1-5) on the Discovery side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joel Salmon
2026-06-16 21:37:33 -05:00
parent c4fc1cccd7
commit b9d7b0e22b
14 changed files with 798 additions and 9 deletions
+24 -5
View File
@@ -124,20 +124,36 @@ class ReflectionCoach:
return messages
async def _call_model(
self, profile: Dict[str, Any], messages: List[Dict[str, str]]
self,
profile: Dict[str, Any],
messages: List[Dict[str, str]],
system_suffix: str = "",
) -> str:
response = await self.client.messages.create(
model=self.model,
max_tokens=MAX_TOKENS,
system=SYSTEM_PROMPT.format(
profile=self.profile_context(profile)
),
)
+ system_suffix,
messages=messages,
)
return response.content[0].text
# Optional focus steers (Phase 5). Appended to the system prompt.
FOCUS_STEERS = {
"goals": (
"\n\nFOCUS: Concentrate this turn on helping the person sharpen "
"their near-term and long-term goals — make them concrete and "
"theirs. Still a mirror: clarify what THEY said, never assign goals."
)
}
async def reflect(
self, profile: Dict[str, Any], history: List[Dict[str, str]]
self,
profile: Dict[str, Any],
history: List[Dict[str, str]],
focus: str = "",
) -> Dict[str, Any]:
"""Produce one coach turn.
@@ -156,9 +172,10 @@ class ReflectionCoach:
ReflectionError on API failure or repeated parse failure.
"""
messages = self._build_messages(history)
steer = self.FOCUS_STEERS.get(focus, "")
try:
raw = await self._call_model(profile, messages)
raw = await self._call_model(profile, messages, system_suffix=steer)
except Exception as exc: # noqa: BLE001 - surface any SDK/transport error
raise ReflectionError(f"Anthropic API call failed: {exc}") from exc
@@ -170,7 +187,9 @@ class ReflectionCoach:
{"role": "user", "content": RETRY_REMINDER},
]
try:
raw_retry = await self._call_model(profile, retry)
raw_retry = await self._call_model(
profile, retry, system_suffix=steer
)
except Exception as exc: # noqa: BLE001
raise ReflectionError(
f"Anthropic API call failed on retry: {exc}"