The analytics index¶
Workout pages are good for reading one session at a time. They’re slow for questions across years, like how your weekly volume has trended, how much time you spend in each zone, or how your resting heart rate moved through a training block. Adding those up page by page is also where an LLM is most likely to get a number wrong.
The analytics index answers those questions. It’s a local DuckDB database built from your workout pages, archived files, benchmarks, training blocks, and wellness series. You ask it questions in SQL with fitdocs query, or have your agent ask them.
Build it once¶
After the first build, sync, pull --sync, regen, and load refresh it automatically once they’ve written your pages, updating only the pages that changed. A refresh never makes one of those commands fail.
The index is a disposable cache. It lives outside your data root, in your user’s cache folder (~/.cache/fitdocs/index/ by default, or wherever FITDOCS_INDEX_DIR points), with one index for each data root. You can delete it any time, and fitdocs index builds it again. fitdocs index prints where the file is.
When an upgrade changes the index’s layout, fitdocs tells you, and fitdocs index rebuilds it. fitdocs index --rebuild builds a fresh one whenever you want.
Ask a question¶
Pass one SQL statement:
Weekly distance by sport:
fitdocs query "
SELECT date_trunc('week', start_utc)::DATE AS week, sport,
round(sum(distance_m) / 1000, 1) AS km
FROM activities
GROUP BY ALL
ORDER BY week DESC
LIMIT 12"
Your resting heart rate over the last 90 days:
fitdocs query "
SELECT day, value AS resting_hr_bpm, source
FROM wellness_daily
WHERE metric = 'resting_hr_bpm' AND canonical AND day >= current_date - 90
ORDER BY day"
For longer queries, keep the SQL in a file and pass --file weekly.sql, or pipe it in with fitdocs query -.
On a terminal, results print as a table. Piped into another program, they come out as CSV. --format json gives you JSON. A query returns at most 1,000 rows unless you raise --max-rows, and stops after 30 seconds unless you raise --timeout. If the index is behind your pages, the query tells you to run fitdocs index.
What’s in it¶
fitdocs query --schema lists every table and column, with units and a description of each. The main tables:
activities: one row per workout, with its summary numbers (time, distance, pace, heart rate, power, cadence, elevation, TRIMP, TSS)pages: each workout page’s frontmatter, including its load and race tagslaps,records, andstrength_sets: every lap, every recorded sample, and every logged setzone_times: time in each heart-rate, power, and pace zoneloadsandquality_flags: every channel’s load and every data-quality checkmean_max: the best power, speed, and heart rate each workout held for each durationdaily_loadandweekly_load: load totals, with fitness, fatigue, and form computed the same way as the history pagebenchmarksandbenchmark_periods: every threshold inathlete.toml, and when each one appliedblocks,mesocycles, andplanned_workouts: your training blocks, matched against what you loggedwellness_daily: every wellness value from every source, with the one to use markedcanonical
A missing value is NULL, never zero. Running cadence columns hold strides per minute, the FIT convention, so double them for steps per minute.
With an LLM¶
Install the packaged fitdocs-analytics skill (see Inside your wiki or vault). It teaches your agent to check the schema first, use the index for counts, totals, averages, and trends instead of reading pages one by one, show you the SQL it ran, and tell an empty result from a measured zero. Then ask in plain words: “How has my weekly running volume changed since January?”
To turn a query into a chart that stays up to date, see Dashboards.
The complete schema, column by column, is in the analytics reference.