@pond-ts/fit
Read your own activity files, look at any part of them, and work out what
they mean for the athlete who did them. @pond-ts/fit is the fitness layer
on pond-ts: FIT, GPX and TCX files in; distances, times, paces, heart rate,
power, laps, splits, best efforts and training load out, with every number in
a type that knows its unit.
An activity records what happened. Sections let you explore any part of it. Analysis explains what it means for the athlete.
npm install pond-ts @pond-ts/fit
A first look
Every example on these pages reads one of three made-up activities. Download
them to follow along: hill-ride.fit
(a 46 km ride with a power meter),
interval-run.fit (six 800 m
repeats) and pool-swim.fit (a set in a
25 m pool). They are modelled, not anyone's real recording;
the guides' introduction says how they were made.
import { readFileSync } from 'node:fs';
import { km, minutes } from '@pond-ts/fit';
import { readActivity } from '@pond-ts/fit/files';
const ride = readActivity(readFileSync('hill-ride.fit'), {
name: 'Two climbs loop',
});
const stats = ride.stats();
console.log(ride.name, '·', ride.sport, '·', ride.startedAt.toISOString());
console.log(stats.distance?.format('km', 1), stats.time.elapsed?.format());
console.log('moving', stats.time.moving?.format());
console.log('heart rate', stats.heartRate.average?.format());
console.log(
'power',
stats.power.average?.format(),
'normalized',
stats.power.normalized?.format(),
);
console.log('climbing', stats.elevation.gain?.format('m'));
Two climbs loop · ride · 2026-06-14T07:30:00.000Z
45.0 km 1:39:54
moving 1:33:11
heart rate 133 bpm
power 190 W normalized 228 W
climbing 597 m
The same questions work on any stretch of the ride: a lap, a split, or a window you pick by time or by distance.
const climb = ride.section({ distance: { from: km(28), to: km(32) } });
console.log('second climb', climb.stats().power.average?.format());
const second5k = ride.splits(km(5)).at(1)!;
console.log(second5k.label, second5k.stats().time.elapsed?.format());
const lap = ride.laps.at(2)!;
console.log(lap.label, lap.stats().distance?.format('km', 1));
second climb 303 W
Split 2 11:50
Lap 3 10.0 km
Anything that depends on the athlete (their FTP, their heart-rate zones) goes
through analyze, and anything the settings can't answer comes back null
rather than as a made-up number.
const athlete = { ftpWatts: [{ at: '2026-01-01', value: 250 }] };
const analysis = ride.analyze({ athlete });
console.log('IF', analysis.power.intensityFactor?.toFixed(2));
console.log('TSS', analysis.load?.value.toFixed(0));
console.log('best 20 min', ride.power.bestEffort(minutes(20))?.power.format());
IF 0.91
TSS 139
best 20 min 269 W
The four objects
| Object | What it is |
|---|---|
Activity | The whole recording, with its name, sport, start and device |
Section | One continuous stretch of an activity |
Sections | A collection of stretches: the laps, the splits, a lap group |
Analysis | Results that need the athlete's settings (FTP, zones, weight) |
An Activity is itself a Section (the stretch from start to finish), so
everything a section answers, the whole activity answers too. Properties say
what the recording holds (name, sport, laps); methods select, calculate
or transform (stats(), section(), splits(), analyze()).
Rules that hold everywhere
- Missing is
null; a real zero stays zero. A ride with no power meter hasstats.power.average === null, not0 W. A second spent coasting is a real 0 W. - Time is never ambiguous. Elapsed, timer, moving and swimming time are
separate clocks, and every
stats()result says which one it used. See Clocks. - Numbers carry units. A
Distanceformats as km or miles, aPaceper km or per mile, and nothing takes a bare number of metres where it could be misread. See Quantities. - Gaps stay gaps. A dropout in the heart-rate strap or the power meter is missing data, not something to draw a line through.
Where to go next
- Using the API: Reading activities · Quantities · Stats · Clocks · Sections · Analysis · Best efforts · Pool swims · Under the hood · Charting an activity
- Analysis guides, each technique built three ways (pond core in TypeScript, pond in Python, then fit) and charted: Moving time · Splits and pace · Normalized power and training stress · Power curve and best efforts · Time in zones
- API reference: every export, generated from the source.