Skip to content
Back to projects
Project

fuelivo

Live

Fueling for endurance athletes - concrete strategies from a few inputs.

fuelivo.de
Preview of fuelivo

fuelivo is a rule-based calculator for sports nutrition. Endurance athletes and coaches enter session parameters - weight, duration, intensity, sport, conditions - and get concrete carbohydrate, sodium and fluid targets in g, ml and mg, split across before, during and after the session. The recommendations are deliberately deterministic: no black box, every output carries its reasoning and warnings. A backend, a web app and a native iOS app share the same logic.

Problem

Endurance athletes have to take in carbohydrates, fluid and electrolytes deliberately in training and competition - too little causes a performance drop, too much or the wrong thing causes GI trouble. The right amount depends on many factors at once: duration, intensity, sport, heat, gut tolerance. Blanket rules of thumb like '60 g of carbs per hour' ignore that, generic nutrition apps track daily balances instead of sessions, and AI coaches hand out recommendations you can't reason about.

Role

Everything - the idea, domain research, the calculation logic, design, backend, web frontend and the native iOS app. AI was a development tool here, not an autopilot.

The solution

fuelivo models published sports-nutrition heuristics as a deterministic rule engine. For each session it computes per-hour targets, scales them to the full duration and translates them into concrete products - drink, gel, bar, banana. Every calculation step produces a plain-language reason, and edge cases raise warnings. It positions itself deliberately as a sports-science calculator, not an AI coach.

  • Carbohydrates per hour: a base value from the duration plus additive modifiers for intensity, sport and heat, capped by a tolerance matrix of gut sensitivity and gut training.
  • Fluid and sodium per hour, depending on intensity and sport, optionally overridden by a measured sweat rate.
  • Three phases: carb-loading beforehand, timed intake during, recovery with protein afterwards - plus a race schedule with aid-station timing.
  • The total amount is translated into ready-to-buy products: depending on aid-station access, a plain drink, drink plus gel or a real-food mix.
  • Separate engines for gym training and race day, because their calculation models are fundamentally different - kept apart for independent maintainability.
Interactive proof

Deterministic, not a black box

Set a session and watch the fueling targets recompute instantly - every number carries its reasoning. The same inputs always produce the same output.

Intensity
Sport
Heat
per hour
Carbohydrates85 g/h

Base 60 · +15 Intensity · +10 Sport

Fluid800 ml/h

Base 700 · +100 Heat

Sodium700 mg/h

Base 600 · +100 Heat

A simplified model of the real engine - it shows the shape of the reasoning, not the exact grammage.

Open the real calculator
Features
  • Public calculator, no login requireddone
  • Transparent reasoning and warningsdone
  • Three-phase recommendation (before, during, after)done
  • Concrete product options per phaseProdone
  • Race plan with aid-station timingProdone
  • Gym and strength calculatordone
  • Athlete dashboard with historyProdone
  • Coach portal with roster and templatesCoachdone
  • Auth, Google OAuth and Sign in with Appledone
  • Stripe and Apple StoreKit subscriptionsdone
  • i18n German/Englishdone
  • Native iOS appdone
Architecture

FastAPI serves a Jinja2 shell that loads the built React SPA and injects auth and entitlement state straight into the HTML - no extra round trip. The iOS app talks to the same domain logic over a dedicated, versioned mobile API. Inside, it is Route -> Service -> Repository throughout.

  • Route -> Service -> Repository: thin routes, business logic testable in isolation, reused for the second client.
  • Calculation rows are append-only - an immutable audit trail instead of mutated records.
  • State-mutating POSTs are protected with a double-submit CSRF token.
  • Sentry, PostHog and the database are fail-open: missing config never blocks a local start.
  • A contract check in CI stops the backend and the iOS app from drifting apart.
Challenges

Migrating the core formula without distorting results

Problem

The original multiplicative model was to be replaced by a more transparent additive matrix model - without recommendations shifting unnoticed.

Solution

Both modes stay in the code, switchable by an env flag, with a shadow-compare mode for side-by-side comparison. The switch was low-risk and the old logic stays reproducible.

One entitlement model across two payment worlds

Problem

Pro access can exist via Stripe on the web or via Apple StoreKit on iOS. Routes must never check payment status directly.

Solution

A central entitlement service resolves Free, Pro and Coach across both providers - a single source of truth for feature flags.

Keeping the backend and the native iOS app in sync

Problem

Two clients on one API: contract breaks otherwise surface late, and on the user.

Solution

A dedicated, versioned mobile API with an OpenAPI spec and its own contract-check workflow. Drift is caught in the pull request already.

The swimming special case

Problem

A gel or bar during the session is impractical in the pool - generic fueling makes no sense.

Solution

A dedicated pool-bottle logic with carbohydrate powder and an electrolyte tablet, including sip timing at the wall, plus an electrolyte warning.

Timeline
  1. March 2026

    Foundation

    In a few weeks the calculation engine, web SPA, auth, billing and coach portal take shape - 257 of the 269 commits fall in this month.

  2. Late March

    Maturity

    CI workflows, the OpenAPI contract check, documentation and the new matrix calculation model alongside the legacy formula.

  3. April - May

    iOS & billing

    Native SwiftUI app, i18n and accessibility, Apple StoreKit subscriptions and the unified entitlement resolution across Stripe and Apple.

  4. June

    Polish

    Adjusting the fueling duration limits and final polish; the last commit lands on 17 June 2026.

What I learned
  • For the first time I combined a backend, a web frontend and a native iOS app in one project. The clean layering paid off with the second client: the logic was reusable, only the transport layer was new.
  • Payments across two worlds - Stripe on the web and Apple StoreKit on iOS - behind a single entitlement service: one source of truth for feature flags.
  • A versioned mobile API with OpenAPI and a contract check in CI is the point where 'backend and app drift apart' stops being a problem.
  • The first project that grew beyond a single Python script with a README - including its own domain, DNS, CI/CD and deployment.
  • Next time: one clear deployment path instead of Docker and Vercel in parallel, and shared types between backend and iOS earlier.