PaceUp
AI-powered running coach (iOS) · personal project
The problem
I used to ask AI for running plans myself. Every time I had to explain my age, my weight, my current pace, how many days a week I was free — and what came back was a list. That was the problem: the plan stays inside the chat as text, and after a while you stop going back to it and drift off. In PaceUp you fill in your profile once. The chatbot already knows you, so from then on you just state the goal — "I want to run a half marathon in three months". The moment you confirm it, the programme lands in the app's calendar day by day. You tick the workouts off there, and the app reminds you with a notification the day before.
Technical details
One save, four independent effects
Finishing a workout creates one WorkoutResult, and Django signals handle the rest: the workout is marked complete, the programme counter increments, user stats and streaks recalculate, and an achievement check fires — sending a notification if one is earned. Each effect is independent and lives in its own Django app. Deleting the result runs the same chain in reverse, so counters and streaks stay consistent.
Counters that survive a race
I increment programme and user counters with F() expressions. The arithmetic happens inside the database rather than as a read-modify-write in Python. Two workouts finishing in the same second can't overwrite each other's count.
Subscriptions that survive a race too
RevenueCat verification has to be idempotent, because the webhook and the app itself can both try to confirm the same purchase at once. I match on the RevenueCat app user id and the store's immutable original transaction id — the same id used to trace a refund or dispute later. I also keep the timestamp of the last verification, for debugging.
An LLM that can't claim work it didn't do
I split the agent's tools in two. UI tools open a typed form in the app instead of emitting free text. Exactly one backend tool actually writes to the database: create_workout_plan. The model can write "I've prepared your programme" and it means nothing on its own — a plan exists only once that backend tool has really run.
Two services, one auth system
The FastAPI service verifies the JWTs Django issues, using the same signing key over HS256, and reads the same PostgreSQL database. So there is no second login screen, no token exchange, and no second user table to keep in sync. LangGraph's checkpointer picks conversations up where they left off, even across a restart or a redeploy.