Files
Joel Salmon c4fc1cccd7 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>
2026-06-16 21:24:58 -05:00

86 lines
2.7 KiB
Python

"""FastAPI entrypoint for the ImpactFlow self-discovery module."""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from app import models # noqa: F401 - register ORM models with Base.metadata
from app.auth import ensure_api_key_admin
from app.database import AsyncSessionLocal, Base, engine
from app.routers import (
activity,
auth as auth_router,
coaching,
discovery,
integration,
)
from app.routers.activity import prune_old_activity
from app.tracking import ActivityTrackingMiddleware
# Path to the static directory, resolved relative to this file so it works
# regardless of the current working directory.
STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
@asynccontextmanager
async def lifespan(app: FastAPI):
# Alembic owns schema migrations in Docker; this create_all is an
# idempotent safety net so the app also runs cleanly in local/dev where
# migrations may not have been applied.
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with AsyncSessionLocal() as db:
await ensure_api_key_admin(db)
await prune_old_activity(db)
yield
app = FastAPI(title="ImpactFlow Self-Discovery", lifespan=lifespan)
# add_middleware wraps the previous app, so the LAST call becomes the
# OUTERMOST middleware. Register inner-to-outer:
# tracking (innermost — sees request.state.user set by deps) ->
# session (needs to wrap routes for the OAuth state cookie) ->
# CORS (outermost — preflights must short-circuit before anything else).
app.add_middleware(ActivityTrackingMiddleware)
_session_secret = os.getenv("JWT_SECRET") or "dev-session-secret-change-me"
app.add_middleware(SessionMiddleware, secret_key=_session_secret)
_cors_origins = [
o.strip()
for o in os.getenv(
"CORS_ALLOWED_ORIGINS", "http://localhost:8011"
).split(",")
if o.strip()
]
app.add_middleware(
CORSMiddleware,
allow_origins=_cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth_router.router)
app.include_router(activity.router)
app.include_router(discovery.router)
app.include_router(coaching.router)
app.include_router(integration.router)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/")
async def root():
return RedirectResponse(url="/static/discovery.html")