Fuel by Effort, Not by Clock — How Smart Fueling Works in WattLog

Every cyclist knows the advice: eat every 45 minutes. Every cyclist who has done a hard interval session also knows it's wrong. A 60-minute threshold workout burns more carbohydrate than a 90-minute recovery spin, and a timer can't tell the two apart. The moment you notice you should have eaten is the moment it's too late: you're already deep in a set, hands on the bars, and the bonk arrives twenty minutes later.

I run WattLog, a solo-built training platform with live sensor ingestion, structured workouts that drive a smart trainer in ERG mode, and simulated GPX routes. It already had a "remind me to eat every X minutes" setting, and I kept forgetting to fuel anyway, because the reminder had nothing to do with how hard I was riding. This is how I replaced it with something that does.

The signal is already on the wire

A power meter or smart trainer reports watts several times a second. Power times time is mechanical work in kilojoules, and cycling has a convenient coincidence. Trained cyclists convert food energy into work at the pedals with an efficiency of roughly 18–26% [1], and one kcal is 4.184 kJ. At about 24% efficiency the two factors cancel, so 1 kJ of work at the pedals costs about 1 kcal of food energy. It's an approximation: efficiency differs between riders and rises with work rate [1, 2], so the true factor moves a little either way. I cover the maths in more detail in burning kilojoules on the trainer. It was already in my post-ride analytics. It just wasn't available during the ride.

So the core of the engine is an accumulator on the server-side session manager. Every power sample adds trapezoid-integrated work since the last one, with the gap between samples capped at 5 seconds so a Bluetooth dropout can't invent energy. When the total crosses a threshold (400 kJ by default, configurable per athlete), the rider gets told to eat. The state lives in the same Redis-backed session metadata as the rest of a live ride, so a reload or a reconnect to another API worker picks up where it left off instead of resetting the bar to zero.

That first version shipped quickly, and it was already better than the timer. It was also still wrong in an interesting way.

Not all kilojoules are equal

400 kJ at an easy endurance pace and 400 kJ at threshold are the same amount of work, but they don't cost the same fuel. Exercise physiology calls this the "crossover": at low intensity fat is the main fuel, and as intensity rises the body shifts towards carbohydrate, which becomes the dominant fuel at hard efforts [3]. Isotope-tracer studies in cyclists show the same picture in detail: muscle glycogen and blood glucose oxidation rise with every step in intensity, while fat oxidation peaks around moderate work rates and then falls [4, 5]. A bar that counts raw kilojoules under-warns on hard days and nags on easy ones.

The fix is a curve: carbohydrate share of energy as a function of intensity relative to FTP. I use a piecewise-linear curve with roughly 45% at 55% of FTP, 65% at 75%, 80% at 95%, and above 90% beyond FTP. To be clear about where these numbers come from: they are my own approximation, shaped to follow the direction and rough magnitude of the studies above, not values taken from a single paper. Those studies measure intensity against VO2max or maximal workload, not FTP, so the mapping is an engineering estimate. Each sample's kilojoules are then weighted by that share relative to a moderate reference effort:

weight = carb_share(power / FTP) / 0.65   (= 1.0 at a steady moderate effort)

fuel_bar += kJ_delta × weight

Two properties matter here. First, at a moderate effort the weight is exactly 1, so the threshold keeps its meaning and existing settings don't silently change behaviour. Second, without an FTP there's no way to judge intensity, so the bar falls back to unweighted kilojoules rather than guessing. In practice, riding at 96% of FTP now fills the bar more than 30% faster than riding the same amount of work at 56%. If you don't know your FTP yet, start with an FTP test.

The bar displays grams, not kilojoules. Riders think in "one gel" or "about 30 g", not in kJ, so the threshold is shown as a portion of carbohydrate (400 kJ is about 30 g with the same assumptions), and the in-ride readout reads "18 / 30 g".

The details that make it usable mid-interval

A correct number is not enough if the alert fires at the wrong moment. Most of the work went into when to speak:

The terrain lookahead produced the bug I'm most glad the tests caught. A waypoint crossed inside a blackout zone was skipped but left in the list, and since the trigger condition was "crossed between the previous and current position", it could never fire again: the prompt was silently lost. The fix fires a pending waypoint on the first tick after the blackout ends, and collapses several passed waypoints into one prompt so a stale one never fires late on its own.

Before the ride: what to pack

The other half of fuelling happens at home. Before any session longer than an hour, the setup page now shows a card with the carbohydrate and fluid to take along, without connecting a trainer or starting anything.

For a structured workout, the energy comes straight from the plan: each step's target watts times its duration gives exact kilojoules, normalized power gives the intensity, and the same carbohydrate curve is applied per step. A workout of hard surges and easy recoveries needs more carbohydrate than a steady ride at the same average power, and the estimate reflects that.

For a GPX route there is no plan, so I use the physics model the trainer simulation already relies on: for every 50 m segment, the power needed to hold the assumed speed against gravity, rolling resistance and air drag, using the rider's weight, bike mass, CdA, Crr and air density. This kind of model is well validated: in road tests it predicted measured power with R² = 0.97 and a standard error of 2.7 W [6]. Descents that need no pedalling count as zero work. A hilly route correctly comes out more expensive than a flat one of the same length.

The numbers then become a carry list: about half of the carbohydrate burned, capped at what the gut can absorb, and shown as a range because a single number would suggest false precision. The caps follow current guidance on carbohydrate intake during exercise: a single carbohydrate source can be oxidised at up to about 60 g/h, and only longer efforts justify about 90 g/h, using multiple transportable carbohydrates such as glucose plus fructose [7]. The same guidance says intake should be adjusted down when the absolute intensity is low, which is what the intensity curve does. The "half of what you burn" factor is my own choice, not a figure from the literature.

Fluids reuse the sweat-rate table from the post-ride debrief, capped at 0.8 L/h. Here the honest source is a caveat: sweat rates vary considerably between people, and the position stand on fluid replacement recommends working out your own by weighing yourself before and after a ride, with the goal of keeping fluid loss under about 2% of body weight [8]. The table gives a starting point, not your number. More on that in hydration, electrolytes and salt.

That reuse was deliberate. My first version of the pre-ride card used a flat 500 ml per hour, while the post-ride debrief assumed 1.4 L/h of sweat for a moderate indoor session. The rider would have been told two contradictory things about the same ride. Now there is one carbohydrate curve and one set of sweat rates, shared by the in-ride bar, the pre-ride plan and the post-ride debrief.

A bug the feature found elsewhere

While testing the route card I got a suspicious result: 2.7 L of fluid for a route the page said would take 1 h 41 min. That's 1.6 L/h, double the cap. The fluid maths was right. The duration was wrong, and not in my new code: four route screens had been estimating time as segments × 5 seconds, but segments are 50 m long, so that formula only holds at 36 km/h. At the default 18 km/h every route time in the app had been understated by half. The backend now returns the time with the fuel plan, and all route screens share one distance-over-speed helper.

What it doesn't do

This is guidance, not measurement, and the article would be dishonest without the caveats:

The whole engine can be switched off in Settings, and while it's on it replaces the old fixed-interval eat reminder instead of competing with it.

What I'd take from it

The feature that looked like "show an alert at 400 kJ" turned into three models that have to agree: a live accumulator, a pre-ride plan and a post-ride analysis. Most of the real work was not the formula but making those three tell the rider the same story, and choosing the right moment to speak. The best decision was refusing to let each screen keep its own constants. The most useful habit was treating a number that looked off, like 2.7 L in under two hours, as a bug report rather than a rounding issue.

References

  1. Coyle EF, Sidossis LS, Horowitz JF, Beltz JD. Cycling efficiency is related to the percentage of type I muscle fibers. Med Sci Sports Exerc. 1992;24(7):782–788. PubMed 1501563
  2. Ettema G, Lorås HW. Efficiency in cycling: a review. Eur J Appl Physiol. 2009;106(1):1–14. doi:10.1007/s00421-009-1008-7
  3. Brooks GA, Mercier J. Balance of carbohydrate and lipid utilization during exercise: the "crossover" concept. J Appl Physiol. 1994;76(6):2253–2261. doi:10.1152/jappl.1994.76.6.2253
  4. Romijn JA, Coyle EF, Sidossis LS, et al. Regulation of endogenous fat and carbohydrate metabolism in relation to exercise intensity and duration. Am J Physiol. 1993;265(3):E380–E391. doi:10.1152/ajpendo.1993.265.3.E380
  5. van Loon LJ, Greenhaff PL, Constantin-Teodosiu D, Saris WH, Wagenmakers AJ. The effects of increasing exercise intensity on muscle fuel utilisation in humans. J Physiol. 2001;536(1):295–304. doi:10.1111/j.1469-7793.2001.00295.x
  6. Martin JC, Milliken DL, Cobb JE, McFadden KL, Coggan AR. Validation of a mathematical model for road cycling power. J Appl Biomech. 1998;14(3):276–291. doi:10.1123/jab.14.3.276
  7. Jeukendrup A. A step towards personalized sports nutrition: carbohydrate intake during exercise. Sports Med. 2014;44(Suppl 1):S25–S33. doi:10.1007/s40279-014-0148-z (open access: PMC4008807)
  8. American College of Sports Medicine; Sawka MN, Burke LM, Eichner ER, et al. Exercise and fluid replacement. Med Sci Sports Exerc. 2007;39(2):377–390. doi:10.1249/mss.0b013e31802ca597
  9. Venables MC, Achten J, Jeukendrup AE. Determinants of fat oxidation during exercise in healthy men and women: a cross-sectional study. J Appl Physiol. 2005;98(1):160–167. doi:10.1152/japplphysiol.00662.2003

Train smarter with WattLog.pro

WattLog.pro collects data from your trainer and shows what's really happening with your fitness.

Try WattLog.pro for free →

← All blog posts