FitAI: The Fitness App That Turns Workout Logs Into a Controlled AI Decision System
It does more than track sets and streaks. It builds a performance brief, protects workout plans from model drift, and uses cache-aware AI to answer training questions with structure instead of guesswork.
- FitAI is less a workout logger than a routed decision system that decides when to answer from the database, when to synthesize context, and when to keep the model advisory only.
- Its performance analysis is built from structured signals like volume trends, weak muscle groups, and PR recency instead of raw chat over logs.
- The streak logic reflects real training behavior by respecting rest days that were never planned as workout days.
- The product's value comes from combining domain rules with AI, not from letting AI do everything.
Why FitAI feels more like a fitness analyst than a tracker
Most fitness apps optimize for logging. FitAI optimizes for judgment. It takes the last 30 days of workout history, turns them into a structured performance brief, and only then asks an LLM to help interpret the result.
That sounds subtle, but it changes the product. The model is not the system. It is a constrained component inside a workflow that already knows how to classify intent, synthesize context, and protect workout plans from direct mutation.
The result is a fitness app that behaves less like a chat window with dumbbells and more like an analytics engine with an AI front end.
The router at the front door
The first important move is classification. Before anything reaches Gemini, FitAI asks what kind of request this is: a direct database lookup, a performance analysis request, or general knowledge.
function classifyIntent(message) {
const text = message.toLowerCase();
if (/streak|workout log|last workout|today.*workout/.test(text)) return 'direct';
if (/plateau|progress|balance|volume|why am i/.test(text)) return 'performance';
return 'knowledge';
}
async function handleAIRequest(message, userId) {
const intent = classifyIntent(message);
if (intent === 'direct') return getDirectData(userId, message);
if (intent === 'performance') return analyzePerformance(userId);
return askGemini(message);
}
That router matters because it prevents unnecessary model calls. A streak question does not need a narrative answer. A plateau question does. A generic training concept does not need to be wrapped in workout history at all.
This is the first sign that FitAI is organized around workflow, not novelty.
How FitAI turns logs into a performance brief
The core of the repo lives in the context aggregator. It does the boring work that makes the AI useful: it gathers recent sessions, calculates volume trends, identifies weak muscle groups, and tracks how recently the user hit a personal record.
The clever part is not that it summarizes data. It is how it summarizes data. FitAI compares recent training blocks, weighs muscle groups differently, and surfaces the kinds of signals a coach would actually use.
Primary muscles get full credit. Secondary muscles get partial credit. That makes the weak-group analysis more faithful to real training than a naive set counter ever could.
It also explains why the output feels specific. The model is not hallucinating structure. The structure already exists before the prompt is assembled.
const PRIMARY_WEIGHT = 1.0;
const SECONDARY_WEIGHT = 0.5;
function weightedSets(session) {
return session.exercises.reduce((total, exercise) => {
const weight = exercise.muscleRole === 'primary'
? PRIMARY_WEIGHT
: SECONDARY_WEIGHT;
return total + (exercise.sets * weight);
}, 0);
}
function flagWeakMuscles(muscleTotals) {
const average = Object.values(muscleTotals).reduce((a, b) => a + b, 0) / Object.keys(muscleTotals).length;
return Object.entries(muscleTotals)
.filter(([, total]) => total < average * 0.6)
.map(([muscle]) => muscle);
}
The analysis cache is part of the product, not just the backend
FitAI caches performance analysis for 24 hours unless new data arrives. That keeps the expensive reasoning path fast and keeps API usage under control without sacrificing freshness when the user actually trains again.
This is a practical design choice, but it is also an editorial one. It tells you what the app believes is worth recomputing. A new workout matters. Reasking the same question does not.
Why the streak logic understands rest days
A lot of fitness apps turn streaks into a blunt instrument. Miss a day and the count breaks. FitAI is smarter. It checks whether the missed day was even supposed to be a workout day before deciding the streak is over.
| Situation | Basic tracker | FitAI |
|---|---|---|
| User skips Tuesday | Streak breaks | Streak can remain intact if Tuesday was not a planned workout day |
| User trains on schedule | Count increases | Count increases |
| User misses a planned session | Streak breaks | Streak breaks |
| User asks about progress | Raw logs only | Contextualized against recent volume and weak-group signals |
That matters because it aligns the app with how real training works. Rest is not failure. In many programs, rest is part of the plan.
A streak system that understands that distinction feels less gamified and more coached.
The guardrails that keep the AI from becoming dangerous
FitAI is careful about what the model can do. The system prompt keeps the assistant advisory only, and the plan builder remains outside the LLM's direct control. That separation is the difference between a helpful assistant and an unstable one.
The backend also does unglamorous but important work: timeout handling, manual JSON extraction from model output, and explicit error paths when the response is malformed. Those details matter because LLMs are useful until they are not.
By treating the model as a fallible component, the repo avoids a common trap. It does not pretend the model is a database, a validator, or a source of truth.
const SYSTEM_PROMPT = `You are advisory only and cannot directly modify workout plans.\nUse performance context when available.\nIf the user needs plan changes, direct them to the Plan Builder.`;
async function analyzePerformance(userId) {
const cached = await getCachedAnalysis(userId);
if (cached && !hasNewWorkoutData(userId)) return cached;
const context = await buildPerformanceContext(userId);
const raw = await callGemini({ system: SYSTEM_PROMPT, context });
const json = extractJSON(raw);
await saveCachedAnalysis(userId, json, 24 * 60 * 60);
return json;
}
The JSON extraction step is especially telling. It is a small admission that the model may return messy output. FitAI still ships because the code expects that mess and cleans it up.
Why the stack matters
The stack is modern, but the point is not fashion. The backend uses Express, Mongoose, JWT cookies, Joi validation, and Docker. The frontend uses React, Vite, Tailwind, Framer Motion, and Recharts. Together, they support a product that needs routing, state, visualization, and controlled AI calls.
| Layer | What it does | Why it fits FitAI |
|---|---|---|
| React and Vite | Fast client UI | Supports quick interaction around analysis and logs |
| Express and Mongoose | API and data access | Keeps routing and aggregation close to the data |
| Gemini integration | Narrative analysis | Used only after context is structured |
| Docker and CI | Repeatable deployment | Useful for a system with backend, frontend, and MongoDB |
This is a monorepo with a clear division of labor. That structure matches the product's thesis: gather data, shape it, then ask the model to explain it.
What FitAI does better than a normal tracker
FitAI is not trying to out-chat a chatbot, and it is not trying to out-log a tracker. It wins by combining domain logic with AI in the right order.
| Capability | Basic tracker | Generic AI assistant | FitAI |
|---|---|---|---|
| Understands workout context | Limited | No | Yes |
| Protects workout plans from direct model mutation | No | No | Yes |
| Caches expensive analysis | Sometimes | Rarely | Yes |
| Handles rest days intelligently | Rarely | No | Yes |
| Computes muscle balance | No | No | Yes |
| Gives direct database answers when appropriate | No | No | Yes |
That is the real differentiator. FitAI uses AI where reasoning helps, and it uses deterministic code where correctness matters. The model stays useful because it stays inside a system that already knows what good fitness data looks like.
For anyone building AI products, that is the lesson worth stealing.