fitness-web/app/routers/agent_api.py
Jacob Hinkle 1a2509ab34 Add Plan page with phase timeline and agent phase management API
- New /plan page shows all phases in order with current phase highlighted
- Add GET/POST/PUT /api/agent/phases endpoints for the AI coach
- Plan link added to navigation bar
- Agent config updated with phase management instructions
2026-06-29 10:51:28 -04:00

205 lines
5.7 KiB
Python

from fastapi import APIRouter, Header, HTTPException, Depends
from pydantic import BaseModel
from sqlalchemy import select
from app.config import AGENT_API_KEY
from app.models.base import async_session
from app.models.user import User
from app.models.workout import Phase, Workout, WorkoutSet
from app.models.checkin import Checkin
router = APIRouter(prefix="/api/agent", tags=["agent"])
async def verify_agent(x_api_key: str = Header("")):
if AGENT_API_KEY and x_api_key != AGENT_API_KEY:
raise HTTPException(status_code=403, detail="invalid api key")
class CreatePhaseRequest(BaseModel):
name: str
description: str = ""
start_date: str | None = None
end_date: str | None = None
notes: str = ""
class CreateWorkoutRequest(BaseModel):
username: str
name: str
date: str
phase_id: int | None = None
notes: str = ""
class CreateSetRequest(BaseModel):
exercise: str
set_number: int
reps: int | None = None
weight: float | None = None
rpe: float | None = None
notes: str = ""
class CreateCheckinRequest(BaseModel):
username: str
date: str
feeling: str | None = None
weight_lb: float | None = None
calories: int | None = None
steps: int | None = None
sleep_hours: float | None = None
notes: str = ""
@router.post("/workouts")
async def agent_create_workout(
body: CreateWorkoutRequest,
_=Depends(verify_agent),
):
async with async_session() as session:
result = await session.execute(
select(User).where(User.username == body.username)
)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="user not found")
workout = Workout(
user_id=user.id,
phase_id=body.phase_id,
name=body.name,
date=body.date,
notes=body.notes,
status="plan",
)
session.add(workout)
await session.commit()
await session.refresh(workout)
return {"id": workout.id, "name": workout.name, "date": workout.date, "status": workout.status}
@router.post("/workouts/{workout_id}/sets")
async def agent_add_set(
workout_id: int,
body: CreateSetRequest,
_=Depends(verify_agent),
):
async with async_session() as session:
ws = WorkoutSet(
workout_id=workout_id,
exercise=body.exercise,
set_number=body.set_number,
reps=body.reps,
weight=body.weight,
rpe=body.rpe,
notes=body.notes,
)
session.add(ws)
await session.commit()
await session.refresh(ws)
return {"id": ws.id, "exercise": ws.exercise, "set_number": ws.set_number}
@router.put("/workouts/{workout_id}/complete")
async def agent_complete_workout(
workout_id: int,
_=Depends(verify_agent),
):
async with async_session() as session:
result = await session.execute(
select(Workout).where(Workout.id == workout_id)
)
workout = result.scalar_one_or_none()
if not workout:
raise HTTPException(status_code=404, detail="workout not found")
workout.status = "complete"
await session.commit()
return {"id": workout.id, "status": "complete"}
@router.post("/checkins")
async def agent_create_checkin(
body: CreateCheckinRequest,
_=Depends(verify_agent),
):
async with async_session() as session:
result = await session.execute(
select(User).where(User.username == body.username)
)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="user not found")
checkin = Checkin(
user_id=user.id,
date=body.date,
feeling=body.feeling,
weight_lb=body.weight_lb,
calories=body.calories,
steps=body.steps,
sleep_hours=body.sleep_hours,
notes=body.notes,
)
session.add(checkin)
await session.commit()
await session.refresh(checkin)
return {"id": checkin.id, "date": checkin.date}
@router.get("/phases")
async def agent_list_phases(_=Depends(verify_agent)):
async with async_session() as session:
result = await session.execute(select(Phase).order_by(Phase.start_date.nulls_last()))
phases = result.scalars().all()
return [
{"id": p.id, "name": p.name, "description": p.description,
"start_date": p.start_date, "end_date": p.end_date, "notes": p.notes}
for p in phases
]
@router.post("/phases")
async def agent_create_phase(
body: CreatePhaseRequest,
_=Depends(verify_agent),
):
async with async_session() as session:
phase = Phase(
name=body.name,
description=body.description,
start_date=body.start_date,
end_date=body.end_date,
notes=body.notes,
)
session.add(phase)
await session.commit()
await session.refresh(phase)
return {"id": phase.id, "name": phase.name}
@router.put("/phases/{phase_id}")
async def agent_update_phase(
phase_id: int,
body: CreatePhaseRequest,
_=Depends(verify_agent),
):
async with async_session() as session:
result = await session.execute(select(Phase).where(Phase.id == phase_id))
phase = result.scalar_one_or_none()
if not phase:
raise HTTPException(status_code=404, detail="phase not found")
phase.name = body.name
phase.description = body.description
phase.start_date = body.start_date
phase.end_date = body.end_date
phase.notes = body.notes
await session.commit()
return {"id": phase.id, "name": phase.name}