tensr docs

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

TableOne 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:

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;

→ Hand this model to your agent and let it do the querying