Phase 3: coaching preferences + weekly check-in engine

Add coaching preferences (auto-derived from the profile, user-overridable) and
a periodic check-in engine that quotes the person's own words and asks whether
their direction still feels valid — mirror, not compass.

- Preferences are deterministic: a documented triad mapping (gut → direct/
  higher-friction, heart → warm/drift-sensitive, head → reflective/question-led)
  produces defaults for the six fields (coaching_frequency, coaching_style,
  misalignment_threshold, friction_tolerance, prefer_questions_over_directives,
  time_of_day_preference). PUT overrides; regenerate re-derives.
- CheckinCoach (app/services/coaching.py): Anthropic-backed; writes a check-in
  that quotes the person's goals back and asks if the direction still holds.
- Endpoints (app/routers/coaching.py): GET/PUT/regenerate preferences;
  GET/POST checkins; respond (records still_valid); admin POST /run is the
  weekly batch (due = cadence elapsed + locked profile), intended for a cron.
- Models + migration 005: coaching_preferences (per user) and coaching_checkin.
- Frontend: coaching.html (preferences form + check-in feed); linked from
  profile.html.

Tests: 68 passing (added deterministic-preference unit tests and coaching
endpoint/batch tests; run in-container). README updated for Phase 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joel Salmon
2026-06-16 21:10:54 -05:00
parent b4d8d17aed
commit 50453901b3
13 changed files with 1317 additions and 1 deletions
+30
View File
@@ -368,6 +368,36 @@ textarea.edit {
min-height: 90px;
}
.pref-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 8px;
}
.pref-select {
width: 100%;
padding: 11px 14px;
border: 1px solid rgba(13, 27, 42, 0.18);
border-radius: 10px;
background: #fff;
color: var(--navy);
font-family: "DM Sans", system-ui, sans-serif;
font-size: 1rem;
}
.pref-select:focus {
outline: none;
border-color: var(--gold);
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.25);
}
@media (max-width: 560px) {
.pref-grid {
grid-template-columns: 1fr;
}
}
.back-link {
margin-top: 8px;
}