trainlog/docs/database.md

313 lines
5.9 KiB
Markdown

# Desktop database
## 1. Status
```text
TRAINLOG_DATABASE_SCHEMA_VERSION=5
DATABASE_SCHEMA_V5=PASS
TRAINLOG_FORMAT_V1=FROZEN
```
The desktop SQLite database is the canonical long-term Trainlog history.
Its schema evolves independently from all JSON exchange-format versions.
## 2. Versioning
Schema version uses:
```sql
PRAGMA user_version;
```
Current value:
```text
5
```
Supported historical databases are migrated explicitly through the implemented
migration chain. A database newer than the running binary understands is
rejected.
A schema fixture must represent the real historical structure. Rewriting only
`user_version` is not an acceptable migration test.
## 3. Connection invariants
Every connection enables:
```sql
PRAGMA foreign_keys = ON;
```
A bounded SQLite busy timeout is configured by the core.
## 4. Tables
### `exercises`
Canonical desktop exercise catalog.
```text
id
exercise_id UNIQUE stable identity
name
normalized_name UNIQUE normalized display form
tracking_mode reps | duration
recording_mode sets | continuous
data_fields bounded bit mask
```
Rules include:
- continuous implies duration tracking;
- unknown supplemental field bits are rejected;
- normalized names remain unique.
### `sessions`
```text
id
session_id UNIQUE stable identity
started_at
ended_at nullable
session_type training | max_test
notes nullable
```
### `session_exercises`
Ordered exercise occurrence inside one session.
```text
session_row_id
exercise_row_id
recording_mode
data_fields
position
load_mode
rest_seconds
target_sets
target_reps
target_duration_seconds
target_weight_kg
notes
```
Current desktop history snapshots `recording_mode` and `data_fields` in the
session row. `tracking_mode` remains associated with the referenced exercise
catalog identity.
`SETS` rows support two target shapes in schema v5:
```text
explicit planned target
target_sets + exactly one target metric
actual-only mobile observation
target_sets = NULL
target_reps = NULL
target_duration_seconds = NULL
```
This v5 rule is what permits heterogeneous mobile performed sets without
inventing a fake uniform target.
`CONTINUOUS` rows are targetless and require:
```text
load_mode = none
rest_seconds = 0
target_weight_kg = NULL
```
### `performed_sets`
Ordered actual set records.
```text
session_exercise_row_id
position
reps nullable
duration_seconds nullable
weight_kg nullable
```
Exactly one primary actual metric is present:
```text
reps
or
duration_seconds
```
Actual repetitions may be zero.
Each row is independent; heterogeneous repetition sequences are first-class
data.
### `continuous_activity`
One-to-one actual record for a continuous session exercise.
```text
session_exercise_row_id UNIQUE
duration_seconds
speed_kmh nullable
distance_km nullable
```
Continuous activity never creates a fake performed set.
### `body_observations`
```text
observation_id UNIQUE
observed_at
session_row_id optional UNIQUE link
body_weight_kg
neck_cm
shoulders_cm
chest_cm
waist_cm
hips_cm
left_arm_cm
right_arm_cm
left_forearm_cm
right_forearm_cm
left_thigh_cm
right_thigh_cm
left_calf_cm
right_calf_cm
notes
```
At least one body metric must be present.
## 5. Identifier generation
Official desktop creator prefixes:
```text
ex_ exercise
se_ session
bo_ body observation
sy_ synchronization run
```
All use random UUIDv4 values.
Exchange parsers may accept other schema-valid opaque identities where their
contract explicitly permits it.
## 6. Transactions
Multi-row user operations are atomic.
Persisted session correction replaces session child rows transactionally while
preserving the parent:
```text
session_id
started_at
ended_at
session_type
session notes
linked body observation
```
Removing an exercise from a persisted session is therefore a transactional
replacement of the remaining child set.
A failed replacement rolls back to the previously persisted session.
Body-observation editing preserves its stable identity, timestamp, and optional
session link.
## 7. Mobile import semantics
`tools/import_mobile_export.py` validates the complete mobile snapshot before
committing database changes.
Properties:
```text
schema-v5 aware
transactional
idempotent by stable IDs
profile-aware catalog reconciliation
heterogeneous performed sets preserved
no fake target generated
continuous activity kept separate
```
## 8. Units
Canonical desktop persistence:
```text
weight/load kg
body circumference cm
duration/rest seconds
speed km/h
distance km
```
## 9. Android database
The Android SQLite database is independent.
Desktop and Android schema versions are not required to match.
Do not synchronize SQLite database files.
## 10. Validation
```bash
meson compile -C build
meson test -C build --print-errorlogs
```
Migration-specific regression coverage includes:
```text
schema_v5_migration
```
The current normal suite contains 19 tests.
## 11. Measured-max derivation
Measured maxima require no desktop schema v6.
The existing `sessions.session_type = max_test` classification plus actual
`performed_sets` are sufficient.
Exercise performance points carry the originating session type so the
measured-max layer can distinguish explicit tests from ordinary training.
Rules:
```text
training session
never becomes measured max implicitly
max_test + external
greatest successful actual load
tie -> greatest reps/duration
max_test + assistance
lowest successful assistance
tie -> greatest reps/duration
max_test + no load
greatest successful reps/duration
```
A zero-repetition failed attempt is not a successful measurement.
The current measured result is the newest successful max-test point. The record
is the best max-test point using the same load mode.
No extra maximum row is persisted; results are derived from canonical history.