Phase 4: task-to-goal integration (Vision side)

Build the boundary the ImpactFlow core time-tracker plugs into. A task maps to
a foundation — one of the six stable profile elements (love, strength, mission,
vocation, short_term, long_term) — so the tracker can ask "which goal does this
build toward?" and post the answer back to Vision.

- Models + migration 006: task_mapping (one row per logged time entry).
- app/services/foundations.py: the six foundations + a pure, testable
  work-pattern aggregator (rollup) and a plain-language summary.
- app/routers/integration.py (user-scoped; tracker calls as the user or via
  X-API-Key): GET /foundations, POST/GET /task-mappings,
  GET /work-patterns?days=N (per-foundation minutes/share/neglected).
- Reminder engine now pulls from real work patterns: CheckinCoach takes an
  optional work-pattern summary (last 14 days) and reflects where time has gone
  against the person's own words — an observation, never a verdict.
- Frontend: dashboard.html (time per foundation + neglected); linked from
  profile.html.

Documented the core-tracker integration contract in the README. Phase 4
completes the Vision module's roadmap on the Discovery side; the core tracker
integrates by calling these endpoints.

Tests: 86 passing (added pure-aggregator, integration-endpoint, and
work-pattern-into-check-in tests; run in-container).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joel Salmon
2026-06-16 21:24:58 -05:00
parent 50453901b3
commit c4fc1cccd7
16 changed files with 857 additions and 5 deletions
+63 -1
View File
@@ -46,6 +46,10 @@ If you need to explain this app in detail, use this mental model:
coaching preferences (auto-derived from their profile) and receive periodic
check-ins that quote their own words and ask if their direction still holds.
A weekly cron calls `POST /discovery/coaching/run` to generate due check-ins.
14. (Phase 4) The ImpactFlow core time-tracker maps each logged task to a
profile foundation via `POST /discovery/integration/task-mappings`. The
rolled-up work patterns drive `/static/dashboard.html` and are fed into the
coaching check-ins so they can reflect where time has actually gone.
Machine-to-machine callers (e.g. the MCP server) skip the OAuth dance and
authenticate with `X-API-Key: $IMPACTFLOW_API_KEY` instead. That header
@@ -147,21 +151,27 @@ Important files:
| `app/static/profile.html` | Browser-based profile display, edit, and confirm actions; links to reflection |
| `app/static/reflect.html` | Phase 2 AI-coach reflection chat (mirror loop, applies revisions, affirm) |
| `app/static/coaching.html` | Phase 3 coaching preferences form + check-in feed |
| `app/static/dashboard.html` | Phase 4 goal dashboard: time per foundation + neglected ones |
| `app/static/auth.js` | Shared `authedFetch` helper: sends session cookies, silently refreshes on `401`, redirects to login |
| `app/static/style.css` | Shared UI styling |
| `app/services/reflector.py` | `ReflectionCoach`: Anthropic-backed mirror loop, JSON parsing, revision filtering |
| `app/services/coaching.py` | Deterministic preference generator + `CheckinCoach` (Anthropic check-in text) |
| `app/services/foundations.py` | The six foundations + pure work-pattern aggregator (Phase 4) |
| `app/routers/coaching.py` | Phase 3 coaching routes: preferences, check-ins, weekly batch `/run` |
| `app/routers/integration.py` | Phase 4 task-to-goal integration: foundations, task-mappings, work-patterns |
| `alembic/versions/001_initial.py` | Initial database schema migration |
| `alembic/versions/002_add_auth.py` | Adds `users`, `refresh_tokens`, and `activity_log` tables |
| `alembic/versions/003_add_goals.py` | Adds the goal columns to `discovery_conversation` and `discovery_profile` |
| `alembic/versions/004_add_reflection.py` | Adds `reflection_message` and `profile_revision` tables (Phase 2) |
| `alembic/versions/005_add_coaching.py` | Adds `coaching_preferences` and `coaching_checkin` tables (Phase 3) |
| `alembic/versions/006_add_task_mapping.py` | Adds the `task_mapping` table (Phase 4) |
| `tests/conftest.py` | Shared `app_client` fixture (isolated app + temp DB) |
| `tests/test_extractor.py` | Unit tests for extraction plumbing, goals, and retry behavior |
| `tests/test_reflector.py` | Unit tests for `ReflectionCoach` (mirror, revision filtering, retry) |
| `tests/test_coaching_prefs.py` | Unit tests for the deterministic coaching-preference generator |
| `tests/test_coaching.py` | Tests for coaching endpoints (preferences, check-ins, due-logic batch) |
| `tests/test_coaching.py` | Tests for coaching endpoints (preferences, check-ins, due-logic batch, work-pattern wiring) |
| `tests/test_foundations.py` | Unit tests for the pure work-pattern aggregator |
| `tests/test_integration.py` | Tests for the task-to-goal integration endpoints |
| `tests/test_auth.py` | Tests for the dual-auth dependency (JWT + cookie + API key), token refresh/logout, admin enforcement, and domain allow-list |
| `tests/test_profile_edit.py` | Tests for `PATCH /discovery/profile/me` (edit, partial update, lock/`409`) |
| `tests/test_reflection.py` | Tests for the reflection endpoints (turns, applied revisions, lock/`409`, history) |
@@ -427,6 +437,30 @@ prescribes. The person's answer is recorded in `still_valid`.
cadence is due. Eligible = coaching cadence not `off` and an affirmed
(locked) profile; due = no prior check-in or the cadence interval has elapsed.
### 10. Task-to-Goal Integration (Phase 4)
This is the boundary the ImpactFlow **core time-tracker** plugs into. A task
maps to a **foundation** — one of the six stable profile elements: `love`,
`strength`, `mission`, `vocation`, `short_term`, `long_term`.
**Core-tracker contract:** when a user logs time, the tracker (1) fetches the
options from `GET /discovery/integration/foundations`, (2) asks the person
"which goal does this build toward?", and (3) posts the answer to
`POST /discovery/integration/task-mappings` with `{external_task_id,
foundation, minutes, task_label?, occurred_at?}`. It calls these endpoints as
the user (forwarded session/JWT) or service-to-service with `X-API-Key`.
- `GET /discovery/integration/foundations` — the six foundations with the
person's own text (what the tracker shows). `404` if no profile.
- `POST /discovery/integration/task-mappings` — record one logged unit of work.
- `GET /discovery/integration/task-mappings?days=N` — the user's mappings.
- `GET /discovery/integration/work-patterns?days=N` — per-foundation rollup
(minutes, share, task count, last activity) plus `neglected` foundations.
Powers the goal dashboard and feeds the coaching reminder engine: a check-in
is given a plain-language summary of the last 14 days so it can reflect where
time has and hasn't gone — as an observation to check against the person's
own words, never a verdict (mirror, not compass).
## API Reference
All `/discovery/*`, `/api/me*`, `/api/activity*`, and `/api/admin/*` routes
@@ -469,6 +503,10 @@ clients.) `/api/auth/login`, `/api/auth/callback`, `/health`, `/`, and
| `POST` | `/discovery/coaching/checkins` | yes | Generate a check-in now |
| `PUT` | `/discovery/coaching/checkins/{id}/respond` | yes | Record "is your direction still valid?" |
| `POST` | `/discovery/coaching/run` | admin | Weekly batch: generate due check-ins for eligible users |
| `GET` | `/discovery/integration/foundations` | yes | The six mappable foundations with the person's own text |
| `POST` | `/discovery/integration/task-mappings` | yes | Record a logged unit of work mapped to a foundation |
| `GET` | `/discovery/integration/task-mappings` | yes | List the user's task mappings in a window |
| `GET` | `/discovery/integration/work-patterns` | yes | Per-foundation work-pattern rollup over a window |
## Data Model
@@ -622,6 +660,22 @@ A periodic coaching check-in and the person's response (migration `005`).
| `response_note` | text nullable | Optional note with their response |
| `acknowledged_at` | datetime nullable | When they responded |
### `task_mapping`
One logged unit of work from the core tracker, mapped to a foundation
(migration `006`).
| Column | Type | Notes |
| --- | --- | --- |
| `id` | string | UUID primary key |
| `user_id` | string | FK to `users.id`, indexed |
| `external_task_id` | string | Opaque task id from the core tracker (not an FK) |
| `task_label` | string nullable | Human label of the task |
| `foundation` | string | `love`/`strength`/`mission`/`vocation`/`short_term`/`long_term`, indexed |
| `minutes` | integer | Time logged toward it |
| `occurred_at` | datetime | When the work happened, indexed |
| `created_at` | datetime | UTC |
## Extraction Details
`DiscoveryExtractor` is intentionally responsible for plumbing, not business
@@ -718,6 +772,14 @@ callback), so the pages hold no tokens of their own.
`.../respond`
- linked from `profile.html` ("Coaching preferences & check-ins")
`dashboard.html` (Phase 4 goal dashboard):
- reads `GET /discovery/integration/work-patterns?days=N` and renders minutes
and share per foundation as bars, with a selectable window
- surfaces foundations with no logged time and asks whether that matches where
the person wants their energy — it observes, it does not prescribe
- linked from `profile.html` ("Where your time goes")
## Configuration
Populate `.env` with at minimum the Anthropic key and the auth-related