The data model
Your export is a SQLite file — the real database the app reads and writes, not a cache. (NDJSON / CSV / Parquet exports carry the same fields, flatter.) Every table below is yours to query directly.
Schemas evolve. Discover before you assume — start from
sqlite_master, then query what actually exists in your export.
-- What tables exist? (skip the sync-internal _ tables)
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE '\_%' ESCAPE '\';
-- What columns does a table have?
SELECT sql FROM sqlite_master WHERE type='table' AND name='session_segment'; Core tables
| Table | One row is… | Key columns |
|---|---|---|
session | one workout | started_at, ended_at, duration_sec, total_impulse_kgs, total_volume_kg, total_rep_count, routine_title |
session_segment | one movement within a session | movement_key, impulse_kgs, rep_count, volume_kg, max_rep_peak_kg, side, started_at/ended_at |
session_channel | an ambient channel (e.g. heart rate) | channel_kind ('hr'), stats_json (bpm min/avg/max + recovery) |
session_recording | the raw force curve | encoded — an encoded blob, not plain SQL; the per-segment summaries are computed from it |
canonical_movement / canonical_alias | the open movement catalog | slug, name, muscles. Join session_segment.movement_key → slug for names |
custom_movement | a movement you named yourself | id, name, aliases, primary_muscles |
goal | an active goal | metric, dimension, target, window |
routine / routine_version | a saved program (versioned) | title, ast_json (the routine tree) |
Tables prefixed _ (e.g. _changes, _sync_state)
are sync/internal — ignore them for analysis.
The metric vocabulary
tensr records force over time, so its numbers say more than sets × reps:
- Impulse (kg·s) — force integrated over time; the headline volume metric (
impulse_kgs). - Peak force (kg) — the hardest instant of a set (
max_rep_peak_kg); the intensity proxy. - Effective reps — reps within ~80% of the set's hardest rep: the ones that drive hypertrophy. Derived from the force recording.
- RFD — rate of force development — how fast you produce force ("how fast you get tight"); a strength/explosiveness lever.
- Time under tension and tempo — seconds loaded, eccentric speed; hypertrophy levers.
- HR recovery — beats dropped 60 s after a set's peak, from
session_channel.stats_json; a cardio-fitness marker.
Starter query
-- Per-movement history, name-resolved, newest first
SELECT date(s.started_at) AS day,
COALESCE(cm.name, seg.movement_key) AS movement,
seg.rep_count AS reps,
round(seg.impulse_kgs) AS impulse_kgs,
round(seg.max_rep_peak_kg, 1) AS peak_kg
FROM session_segment seg
JOIN session s ON s.id = seg.session_id
LEFT JOIN canonical_movement cm ON cm.slug = seg.movement_key
ORDER BY s.started_at DESC
LIMIT 100;