354 lines
11 KiB
Markdown
354 lines
11 KiB
Markdown
# Exercise data model
|
|
|
|
## Status
|
|
|
|
```text
|
|
EXERCISE_DATA_MODEL_V1=PASS
|
|
PROFILE_AWARE_DESKTOP=PASS
|
|
PROFILE_AWARE_ANDROID=PASS
|
|
CONTINUOUS_ACTIVITY=PASS
|
|
VARIABLE_REPETITION_SETS=PASS
|
|
TRAINLOG_FORMAT_V1=FROZEN
|
|
BODY_ZONES_V1=PASS
|
|
```
|
|
|
|
## 1. Metadata axes
|
|
|
|
An exercise is described by independent metadata:
|
|
|
|
```text
|
|
recording_mode = SETS | CONTINUOUS
|
|
tracking_mode = REPS | DURATION
|
|
data_fields = supplemental field bit mask
|
|
```
|
|
|
|
Known supplemental fields:
|
|
|
|
```text
|
|
SPEED_KMH bit 0, mask value 1
|
|
DISTANCE_KM bit 1, mask value 2
|
|
```
|
|
|
|
Consequently `data_fields = 1` means speed only and `data_fields = 3`
|
|
means speed plus distance. These meanings come from the shared C/Kotlin model
|
|
constants and serializers; the masks are not ordinal profile numbers.
|
|
|
|
Valid model-v1 combinations:
|
|
|
|
```text
|
|
SETS + REPS
|
|
SETS + DURATION
|
|
CONTINUOUS + DURATION
|
|
```
|
|
|
|
Invalid:
|
|
|
|
```text
|
|
CONTINUOUS + REPS
|
|
```
|
|
|
|
UI behavior must never be inferred from an exercise display name.
|
|
|
|
## 2. Examples
|
|
|
|
```text
|
|
Presse à cuisses
|
|
SETS + REPS
|
|
|
|
Gainage
|
|
SETS + DURATION
|
|
|
|
Marche
|
|
CONTINUOUS + DURATION + SPEED_KMH
|
|
|
|
Course
|
|
CONTINUOUS + DURATION + SPEED_KMH
|
|
|
|
Vélo
|
|
CONTINUOUS + DURATION + SPEED_KMH + DISTANCE_KM
|
|
|
|
Rameur
|
|
CONTINUOUS + DURATION + DISTANCE_KM
|
|
```
|
|
|
|
## 3. Body-zone metadata
|
|
|
|
Exercise behavior and body-zone classification are independent. The sole V1
|
|
taxonomy is `catalog/body-zones-v1.json`:
|
|
|
|
```text
|
|
full_body Corps entier
|
|
upper_body Membres supérieurs (group)
|
|
chest Pectoraux
|
|
back Dos
|
|
shoulders Épaules
|
|
arms Bras
|
|
core Abdominaux / tronc
|
|
lower_body Membres inférieurs (group)
|
|
glutes Fessiers
|
|
thighs Cuisses
|
|
calves Mollets
|
|
```
|
|
|
|
An exercise stores at most one direct `primary` relation and any number of
|
|
distinct `secondary` relations. Secondary relations require that primary; an
|
|
unclassified exercise has no relation. The same zone cannot have both roles.
|
|
Group nodes are not assignable: a direct `chest` relation is sufficient for an
|
|
`upper_body` descendant query. `full_body` is not a synonym for all zones, and
|
|
`core` is not implicitly upper or lower body. Cardio is an activity profile,
|
|
not a body zone.
|
|
|
|
New interactive `SETS` creation asks for a primary zone. Historical or
|
|
objectively ambiguous exercises may remain without relations and are displayed
|
|
and filterable as **Non renseignés**. Initial migration decisions use exact
|
|
stable `exercise_id` values and recorded equipment/catalog evidence, never a
|
|
general name rule.
|
|
|
|
The read surface supports zone lookup, children, ancestors, direct relations,
|
|
primary/secondary selection and exercises for a zone with optional descendants
|
|
or primary-only participation. Those exercise IDs compose with existing
|
|
performance/MAX history readers, so the separate session generator needs no new
|
|
duplicated MAX or history storage. The generator's proposals remain optional
|
|
planning metadata and do not alter historical actual work.
|
|
|
|
## 4. Load semantics
|
|
|
|
Load mode is session-specific:
|
|
|
|
```text
|
|
none
|
|
external
|
|
assistance
|
|
```
|
|
|
|
`external` is added resistance.
|
|
|
|
`assistance` is help. Lower assistance represents less help and is therefore
|
|
better when comparing otherwise equivalent performance.
|
|
|
|
Machine-displayed kilograms are stored faithfully without claiming mechanical
|
|
equivalence across different machines.
|
|
|
|
## 5. Set-based work
|
|
|
|
`SETS + REPS` stores one performed-set row per actual set.
|
|
|
|
Each ordered performed set independently owns its repetitions and an optional
|
|
`weight_kg`. Actual repetitions and actual loads can therefore differ from one
|
|
set to the next. A missing load is not a zero load and is not filled from a
|
|
planned target. When supplied, an actual load is finite and `>= 0`; an explicit
|
|
zero is preserved as an observed value.
|
|
|
|
Each performed row is the source of truth for actual work.
|
|
|
|
New normal desktop set work is created only as explicit actual rows. Compact
|
|
performed-repetition expressions are not an active desktop entry form; Android
|
|
legacy-draft decoding is a separate compatibility behavior documented in
|
|
`docs/android.md`.
|
|
|
|
Existing historical rows retain their stored repetitions, optional loads and
|
|
order unchanged. Editing or exchanging a session never normalizes heterogeneous
|
|
actual values into a uniform prescription.
|
|
|
|
This per-set capture contract does not introduce volume/tonnage, estimated 1RM,
|
|
or progression calculations.
|
|
|
|
`SETS + DURATION` likewise stores one actual duration per performed set.
|
|
|
|
## 6. Planned versus actual
|
|
|
|
Desktop-created set sessions may carry explicit planned targets.
|
|
|
|
Android mobile snapshots can represent actual-only heterogeneous work.
|
|
|
|
The implemented session generator adds optional target sets/repetitions or
|
|
duration, rest and an optional strictly positive target load to ordinary
|
|
Android occurrences. These are planning metadata, never actual sets. A target
|
|
with no weight uses plan mode `none`; a target with weight records external or
|
|
assistance mode. A targetless occurrence has mode `none` and zero rest.
|
|
Continuous and explicit-MAX occurrences remain targetless.
|
|
|
|
Desktop schema v5 therefore allows an imported set session to have no synthetic
|
|
uniform target:
|
|
|
|
```text
|
|
target_sets = NULL
|
|
target_reps = NULL
|
|
target_duration_seconds = NULL
|
|
```
|
|
|
|
Do not derive a fake target from heterogeneous actual sets.
|
|
|
|
## 7. Continuous work
|
|
|
|
Continuous activity does not ask for:
|
|
|
|
```text
|
|
set count
|
|
repetitions
|
|
per-set rest
|
|
load
|
|
```
|
|
|
|
Example:
|
|
|
|
```text
|
|
Marche
|
|
|
|
Durée 45 min
|
|
Vitesse 5.8 km/h
|
|
```
|
|
|
|
Actual persistence uses one `continuous_activity` record containing duration
|
|
and configured supplemental values.
|
|
|
|
No fake performed set is created.
|
|
|
|
## 8. Historical interpretation
|
|
|
|
Desktop `session_exercises` snapshot:
|
|
|
|
```text
|
|
recording_mode
|
|
data_fields
|
|
```
|
|
|
|
This prevents later catalog changes to those fields from rewriting the meaning
|
|
of historical rows.
|
|
|
|
Current desktop tracking mode remains tied to the referenced exercise identity.
|
|
Any future change to that historical contract requires an explicit schema
|
|
decision rather than an implicit name-based migration.
|
|
|
|
Catalog enrichment does not rewrite an occurrence snapshot. For example, a
|
|
historic `data_fields = 1` walk remains a speed-only occurrence after its
|
|
catalog definition becomes `data_fields = 3`. Its absent distance stays
|
|
absent/`NULL`; synchronization does not infer it from duration and speed.
|
|
|
|
## 9. Android/TUI parity
|
|
|
|
Both interfaces use the same metadata axes.
|
|
|
|
The PC -> Android catalog synchronization artifact carries:
|
|
|
|
```text
|
|
exercise_id
|
|
name
|
|
recording_mode
|
|
tracking_mode
|
|
data_fields
|
|
```
|
|
|
|
Creating an exercise inline on Android or desktop follows the same model rules.
|
|
Body zones use their separate direction-neutral V1 companion rather than
|
|
changing this catalog artifact or frozen session V1.
|
|
|
|
### Safe V2 identity reconciliation
|
|
|
|
A normalized-name collision between distinct `exercise_id` values is eligible
|
|
for automatic V2/catalog reconciliation only when:
|
|
|
|
- the normalized name is identical under the receiving store's current rule;
|
|
- `recording_mode` and `tracking_mode` are identical;
|
|
- all other represented business invariants, including overlapping equipment
|
|
load semantics on Android, are compatible;
|
|
- the `data_fields` masks are equal, or one is a bitwise subset of the other;
|
|
- moving references loses no occurrence, actual value or metadata.
|
|
|
|
The existing desktop identity is canonical. Android therefore adopts the PC
|
|
identity when applying a PC catalog. The catalog stores the bitwise union (the
|
|
compatible richer profile), while sessions retain their own `entry_id`, order,
|
|
profile snapshot, sets, loads, continuous values and equipment. The operation
|
|
is transactional and replay-safe. Different modes, incomparable masks, or any
|
|
other incompatible invariant produce an explicit conflict. Equal names alone
|
|
never establish identity.
|
|
|
|
This is a synchronization-V2 policy. It does not relax or redefine the frozen
|
|
Trainlog JSON v1 document rules.
|
|
|
|
## 10. Exchange boundaries
|
|
|
|
Frozen Trainlog session JSON v1 remains unchanged.
|
|
|
|
Synchronization data that exceeds frozen v1 uses separate versioned artifacts.
|
|
|
|
Continuous activity must never be:
|
|
|
|
- hidden in notes;
|
|
- converted into a fake set;
|
|
- silently discarded.
|
|
|
|
## 11. Measured max semantics
|
|
|
|
`session_type = max_test` is an explicit semantic boundary.
|
|
|
|
An explicit weight maximum is an occurrence-owned result:
|
|
|
|
```text
|
|
exercise_id
|
|
entry_id
|
|
equipment_id optional
|
|
max_weight_kg > 0
|
|
position
|
|
```
|
|
|
|
It is mutually exclusive with `performed_sets` and `continuous_activity` for
|
|
that occurrence. No `sets = 1` or `reps = 1` value is stored. This result mode
|
|
does not change the frozen catalogue combinations: a catalogue exercise keeps
|
|
its existing recording/tracking profile, while the containing `max_test`
|
|
session selects the explicit MAX capture form.
|
|
|
|
`exercise_id` owns the movement result. `equipment_id` identifies only the
|
|
physical context, so two movements performed on the same combined machine have
|
|
independent maxima.
|
|
|
|
Measured max v1 never equates an ordinary best set with a measured maximum.
|
|
Legacy `max_test` sets that cannot be migrated unambiguously remain available
|
|
under their historical semantics.
|
|
|
|
For those retained legacy set-based exercises:
|
|
|
|
```text
|
|
external
|
|
max measured load = greatest successful actual load in a max_test
|
|
|
|
assistance
|
|
best measured assistance = lowest successful assistance in a max_test
|
|
|
|
none
|
|
measured max = greatest successful reps/duration in a max_test
|
|
```
|
|
|
|
Ties use greater repetitions/duration.
|
|
|
|
The newest successful explicit test is the current measured result. A separate
|
|
same-mode historical record may be older.
|
|
|
|
No estimated 1RM is mixed into this contract.
|
|
|
|
## 12. Read-only training knowledge context
|
|
|
|
Training Knowledge V1 adds no exercise profile field and no persisted training
|
|
prescription. It can associate a real stable `exercise_id` with source-linked
|
|
scientific knowledge, compatible equipment and a separate scientific BODY ZONE
|
|
projection. That projection is not the persisted primary/secondary BODY ZONE
|
|
classification and cannot rewrite it.
|
|
|
|
The composed runtime view contains the actual exercise profile, persisted
|
|
zones, compatible equipment, latest explicit MAX and bounded chronological
|
|
occurrences with raw set values. Unknown custom and future runtime IDs remain
|
|
valid and may have no knowledge record. A name, legacy slug or generic machine
|
|
capability is never made into a phantom UUID association. Raw kilogram labels,
|
|
ROM, setup and actual force retain their recorded/unknown context; they do not
|
|
become interchangeable performance measurements.
|
|
|
|
See [Training knowledge system V1](domain/knowledge_system.md) for catalog
|
|
ownership, confidence and the documented-only future planning boundary.
|
|
The current implementation lifecycle is `TRAINING_KNOWLEDGE_V1=PASS`. Occurrence pagination and latest
|
|
explicit MAX use the settled shared temporal policy: the admitted source text
|
|
is preserved, instants and arbitrary dot fractions compare exactly, and equal
|
|
instants use bytewise session and entry ID ties. Cursors are exclusive and use
|
|
the same comparison; malformed cursors or matching stored timestamps report
|
|
explicit errors. The initial audit's stale temporal documentation, Android
|
|
loader, Meson input, and C role-only query findings were repaired and
|
|
independently verified; final validation passed.
|