diff --git a/docs/current_state.md b/docs/current_state.md index d6ecd71..2ce1345 100644 --- a/docs/current_state.md +++ b/docs/current_state.md @@ -81,7 +81,7 @@ The rollout packager now keeps the legacy request daemon and one-shot helper in the same relocatable, hashed bundle as the Web generation coordinator, avoiding mixed-checkout user-service execution during the controlled rollout. -Snapshot date: **2026-09-22**. +Snapshot date: **2026-09-23**. `TRAINLOG_CODE_READABILITY_V1` normalizes the recent generation-MTP, generation/ACK, Android-backup, Web Dashboard serialization, tests, doubles and @@ -112,8 +112,8 @@ parallel implementation of its rules. | Boundary | Current state | |---|---| | Frozen project exchange | `TRAINLOG_FORMAT_V1=PASS/FROZEN` | -| Desktop SQLite | schema v29; Sleep Diary V1 is additive in v29 | -| Android SQLite | schema v26; Sleep Diary V1 is additive in v26 | +| Desktop SQLite | schema v29; Sleep Diary V1 is additive in v29; Cardio v30 work is implementation-started but not yet closed | +| Android SQLite | schema v26; Sleep Diary V1 is additive in v26; Cardio v27 work is implementation-started but not yet closed | | Mobile snapshot | V3 active; V1/V2 readable legacy inputs; explicit V4 codec staged, not selected by transport | | Desktop terminal backend | Notcurses only | | Trainlog product version | `0.1.5` development, synchronized across Android and desktop; latest stable release: `v0.1.4` | @@ -147,7 +147,7 @@ parallel implementation of its rules. | Private full-generation rollout | `TRAINLOG_SYNC_FINALIZATION_AND_ROLLOUT_V1=PASS` (authenticated Bluetooth primary transport with direct MTP recovery on one authorized phone) | | Local Web | `TRAINLOG_WEB_V1=CONTRACT_FROZEN / IMPLEMENTATION_STARTED` | | Web Exercises V1 | `TRAINLOG_WEB_EXERCISES_V1=PASS` | -| Current operational cursor | `TRAINLOG_CARDIO_V1_NEXT_CONTRACT`: heart-rate sensor ingestion starts in 0.1.5 and remains distinct from the frozen Bluetooth synchronization transport | +| Current operational cursor | `TRAINLOG_CARDIO_V1=CONTRACT_FROZEN / IMPLEMENTATION_STARTED`: Android-only sensor ownership, training timeline, Sleep one-tap capture, cardio session/calibration and BPM-guidance contract frozen; persistence/sync implementation is the next closeout gate | | Complete synchronization gap contract | Frozen dependency contract; operational USB/Drive slices required by v0.1.2 are delivered | | Isolated synchronization test environment | `TRAINLOG_SYNC_TEST_ENV_V1=PASS/FROZEN` | | Synchronization characterization | `TRAINLOG_SYNC_CHARACTERIZATION_V1=PASS/FROZEN` | diff --git a/docs/design/cardio_v1.md b/docs/design/cardio_v1.md new file mode 100644 index 0000000..7c904cc --- /dev/null +++ b/docs/design/cardio_v1.md @@ -0,0 +1,412 @@ +# Cardio V1 + +Status: `TRAINLOG_CARDIO_V1=CONTRACT_FROZEN / IMPLEMENTATION_STARTED` + +This document is the implementation contract for Trainlog 0.1.5 cardio, +training-timeline correlation, and Android one-tap sleep capture. It does not +declare the implementation complete. + +## Ownership and hard boundaries + +Android is the only heart-rate sensor client. It owns BLE discovery, +connection, reconnection, Heart Rate Measurement parsing, local persistence, +capture lifecycle, and the live BPM presentation. + +Desktop, TUI and Web never scan, pair with, or connect directly to a +heart-rate sensor. They consume only measurements already recorded by Android +and synchronized through Trainlog. + +Heart-rate BLE/GATT and Trainlog Bluetooth synchronization are independent: + +```text +heart-rate sensor -- BLE/GATT --> Android +Android <-------- Trainlog full-generation --------> desktop + over + Bluetooth Classic RFCOMM or MTP recovery +``` + +The cardio implementation must not reuse the frozen Bluetooth Classic service +UUID, framing, peer identity, connection state, or transport protocol. + +## Standard sensor target + +Cardio V1 targets the Bluetooth LE Heart Rate Service: + +- Heart Rate Service: `0x180D`; +- Heart Rate Measurement: `0x2A37`. + +The parser must support the standard 8-bit and 16-bit BPM forms, optional +sensor-contact state, optional cumulative energy expended, and zero or more RR +intervals. RR intervals are preserved as raw integer units of 1/1024 second. + +Battery Service may be observed as optional device metadata but is not required +to retain a heart-rate sample. + +Malformed notifications are rejected. Trainlog never manufactures missing BPM +or RR values. + +## Global Android heart-rate indicator + +The Android application shell exposes one compact indicator at the top right: + +```text +♥ 82 BPM +``` + +Only the heart and BPM are shown. Sensor name, address, and technical state are +not shown in the global header. + +Heart colour describes connection freshness only: + +- grey: known sensor unavailable/disconnected; +- orange: connecting, reconnecting, or the last measurement is stale; +- green: connected and receiving fresh valid measurements. + +Colour never indicates a medical zone or whether the BPM is high or low. + +When no fresh measurement exists, the value is `-- BPM`. A stale previous +number must never remain visible as if it were current. + +With a fresh BPM the heart uses a restrained pulse animation derived from +`60 / bpm`. This is only a visual representation of the current measured +rate; it does not claim knowledge of the exact instant of each contraction. + +## Capture identities and contexts + +One capture has a stable `hrc_` identity and exactly one semantic +context: + +- `session`: a normal training or MAX session identified by stable + `se_`; +- `cardio`: a dedicated cardio session identified by stable + `se_`; +- `sleep`: one Sleep Diary entry identified by stable `sl_`. + +Context is never inferred from a title, clock date, exercise name, or display +position. + +A workout/cardio sample may additionally snapshot the stable active occurrence +`sxe_` at the instant the sample is received. + +## Raw measurement contract + +Each received measurement stores at minimum: + +- `capture_id`; +- monotonically increasing `sequence` within that capture; +- offset-bearing `observed_at` reception timestamp; +- integer `bpm`. + +When actually supplied by the sensor it may also store: + +- sensor-contact state; +- cumulative energy-expended value; +- zero or more RR intervals in 1/1024-second units. + +For workout/cardio captures it may store the active `entry_id` snapshot. + +Raw storage preserves signal gaps. It does not interpolate, smooth, estimate, +diagnose, infer sleep stages, infer a return to sleep, or synthesize missing +measurements. + +## Android durability + +Android SQLite owns authoritative field capture. An active capture remains +durable across application navigation and ordinary process/lifecycle +transitions supported by Android. Long-running acquisition uses a dedicated +foreground service distinct from synchronization. + +Cardio V1 permits one active heart-rate capture at a time. + +Sensor disconnect does not delete or close a capture. Reconnection resumes +future measurements and leaves the missing interval as a real gap. + +Explicit stop closes the capture with its actual `ended_at`. + +## Synchronization contract + +Recorded cardio is synchronized Android -> desktop as the separate optional +full-generation companion: + +```text +logical_name = heart-rate +format = trainlog-heart-rate +version = 1 +filename = heart-rate-v1.json +``` + +Desktop persists and presents these measurements but is not a sensor producer. + +Normal publication contains completed/stopped, not-yet-acknowledged captures. +An active capture is retained locally and is not emitted as a mutable +full-generation snapshot. + +Identity is idempotent: + +- capture key: `capture_id`; +- sample key: `capture_id + sequence`; +- RR key: `capture_id + sequence + rr_index`. + +Exact replay is a no-op. Reusing an identity with different measured content +is rejected. + +The exact captured `capture_id -> generation_id` relation is durable. Only a +correlated consumed ACK marks those captures acknowledged. A rejected +generation does not acknowledge them. Acknowledged captures remain in Android +history but are excluded from ordinary future publication. + +The companion does not alter `TRAINLOG_FORMAT_V1`, mobile-export V3/V4, +Sleep Diary V1, generation/ACK framing, or Bluetooth Classic sync V1. + +## Training timeline correlation + +A training session must expose real time boundaries, never reconstructed +timings. + +Session start/end use the existing stable session identity and canonical +`started_at` / `ended_at` lifecycle when those values are already the +authoritative timestamps. + +Each session occurrence has an explicit execution lifecycle: + +```text +not_started -> in_progress -> completed +``` + +Starting an exercise records its actual start timestamp against +`session_id + entry_id`. Ending it records its actual end timestamp. + +Only one occurrence may be active at a time. Starting another occurrence while +one remains active requires an explicit resolution rather than silently +inventing an end. + +Samples received while an occurrence is active snapshot that `entry_id`. +Samples between occurrences remain valid session samples with no fabricated +exercise ownership and represent rest/transition. + +Ending a session while an occurrence is active requires an explicit UI action +to close the occurrence at the validated finalization instant. + +The synchronized history must permit factual curve markers for: + +- session start; +- exercise start; +- exercise end; +- session end. + +Future set-level `set_start` / `set_end` events may extend this model, but +Cardio V1 must not infer them from reps, duration targets, or form edits. + +## Dedicated cardio session type + +Trainlog 0.1.5 adds a real third session type: + +```text +training +max_test +cardio +``` + +Every existing boundary that assumes only `training|max_test` must be audited. +A frozen artifact may not be silently widened. If an existing wire codec +cannot safely express `cardio`, add a separately versioned compatible +extension while retaining legacy readers. + +A cardio session consists of cardio exercises and optional phases. Initial +exercise families include treadmill, bicycle, elliptical, rower, stepper, +walking, running, and custom cardio. + +A phase may express a type, target, duration, and exit condition. Required +phase concepts include warm-up, work, recovery, and cool-down. + +The motor must be able to represent fixed duration, reaching a BPM zone, +recovering below a threshold, and bounded combinations of time and +heart-rate conditions. + +Machine speed, resistance, incline, and distance may be recorded, but BPM is +the primary guidance signal. + +## BPM-guidance motor + +Cardio V1 guidance outputs are: + +```text +ACCÉLÈRE +MAINTIENS +RALENTIS +``` + +Guidance is based only on fresh measurements and the phase target. + +Hysteresis and a minimum persistence interval prevent oscillation around a +boundary. One isolated sample must not cause rapid instruction changes. + +When the signal becomes stale, guidance is suspended. The global heart becomes +orange and BPM becomes unavailable rather than continuing from an old value. + +## Calibration cardio exercise + +The first dedicated cardio exercise is `Calibration cardio`. + +Its purpose is to establish a personal measured reference for later guided +exercise. Trainlog records an **observed peak BPM**, not an automatically +asserted physiological maximum. + +The protocol is progressive: + +```text +warm-up -> easy stage -> progressive stages -> user-chosen highest tolerated +effort for that day -> stop effort -> recovery observation +``` + +A manual stop is always immediately available. The motor never attempts to +beat the previous observed peak automatically. + +Each calibration is immutable/versioned and retains at minimum: + +- stable `calibration_id`; +- protocol version; +- performance timestamp; +- associated heart-rate capture; +- observed peak BPM; +- effort-end timestamp; +- recovery timeline. + +Recovery projections may expose +1, +2 and +3 minute facts while the complete +raw curve remains authoritative. + +A later calibration creates a new reference. It never rewrites an older +calibration or a prior guided session. + +Every guided cardio session snapshots the exact `calibration_id` and target +thresholds used at that time. + +Measured, user-configured, and calculated/estimated values remain explicitly +distinguishable. + +## Android Sleep entry point + +Android adds a first-class `Sommeil` menu entry backed by the existing Sleep +Diary domain. It must not create a parallel sleep database or incompatible +sleep truth. + +Night-time interaction is deliberately minimal. The primary screen exposes: + +```text +[ COUCHÉ ] + +[ médicament 1 ] [ médicament 2 ] +[ médicament 3 ] [ + ] + +[ RÉVEIL ] + +[ LEVÉ ] +``` + +Buttons are large, visually consistent with Trainlog, and usable while only +partly awake. + +### Couché + +One tap records the actual `bed_time` timestamp in the correct stable Sleep +Diary entry. If heart-rate acquisition is available, the nocturnal capture is +linked directly to that `sleep entry_id`. + +### Médicaments + +Active configured medications are one-tap actions. A tap stores the actual +intake timestamp plus stable medication identity and the effective +name/dose/unit snapshot. Full editing remains available later. + +### Réveil + +There is no `Rendormi` button. + +One tap records one punctual night wake event. Nothing else is required before +the user tries to return to sleep. + +Trainlog does not infer a certain return-to-sleep time from BPM or RR data. +An approximate interval may be completed later only if the user chooses to +provide it. + +### Levé + +One tap records the actual `final_get_up` timestamp, closes the night, and +stops the linked nocturnal heart-rate capture when one is active. + +### Immediate undo and later correction + +After Couché, medication, Réveil, or Levé, Android shows a short-lived action +such as: + +```text +Réveil enregistré à 02:17 [ ANNULER ] +``` + +Undo is scoped to that exact action and must not roll back unrelated newer +state. + +`Corriger la nuit` provides later editing of timestamps, medication +intakes, accidental wake points, forgotten events, and existing subjective +Sleep Diary fields. + +The night-time rule is: one tap, one timestamp, then no further required +interaction. + +## Sleep + cardio factual timeline + +A nocturnal capture is owned by stable `sleep entry_id`, not only a date. + +After synchronization, Analyse may superpose factual events and measurements: + +- Couché; +- medication intakes; +- wake points; +- Levé; +- BPM; +- RR when supplied; +- real sensor disconnect/reconnect gaps. + +Trainlog does not diagnose sleep, invent sleep stages, or claim a certain +return-to-sleep instant from heart rate alone. + +## Web/Analyse projection + +Web and desktop consume synchronized facts only. + +Training views may show the BPM curve, session bounds, exercise intervals, +rest/transition gaps, and factual min/max/mean projections. + +Cardio views may additionally show phase targets, chronological guidance, +calibration identity, and measured response versus target. + +Sleep views may overlay Sleep Diary events with BPM/RR measurements. + +Graphs are projections. Persistent measurements, identities, and timestamps +remain the source of truth. + +## Performance bounds + +One night can contain tens of thousands of samples. Storage and exchange must +use bounded artifacts, indexed temporal queries, transactional import, and +avoid per-sample N+1 behavior on analysis paths. + +Trainlog does not arbitrarily downsample the authoritative raw capture merely +for transport convenience. Presentation may derive bounded projections while +retaining the recorded source data. + +## Implementation order + +1. audit/freeze this contract; +2. Android + desktop persistence and strict heart-rate companion exchange; +3. standard Heart Rate Measurement parser and real-sensor diagnostics; +4. Android foreground acquisition service and global ♥ BPM indicator; +5. training start/end and exercise start/end timeline; +6. Android one-tap Sleep UI and sleep-linked capture; +7. true `cardio` session type; +8. versioned Calibration cardio exercise; +9. BPM-guided phase motor; +10. Web factual visualization; +11. complete regression, migration, hardware, replay, and restart validation. + +No stage permits Web/TUI sensor connection. diff --git a/docs/roadmap.md b/docs/roadmap.md index 9edee36..1b85ed4 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -59,14 +59,17 @@ mobile snapshot or `TRAINLOG_FORMAT_V1` contracts. Stable v0.1.4 closes the Analyse/Sleep Diary cycle and freezes the Bluetooth Classic synchronization transport independently from future sensor work. -The next development cycle is 0.1.5 cardio. Android will own heart-rate sensor -discovery/acquisition and timestamped local capture; synchronized desktop/Web -surfaces will consume recorded measurements and will not connect directly to -the sensor. Heart-rate ingestion must not reuse or overload the frozen Trainlog +The 0.1.5 cycle owns cardio end to end. Android alone discovers and reads the +heart-rate sensor, exposes the global ♥ BPM state, timestamps training +session/exercise boundaries, provides one-tap Sleep capture, and records the +new dedicated cardio session type. Cardio starts with a versioned Calibration +exercise and later drives phase guidance from fresh BPM. Desktop/Web consume +only synchronized recorded measurements and never connect directly to the +sensor. Heart-rate ingestion must not reuse or overload the frozen Trainlog Bluetooth synchronization transport contract. ```text -CURRENT_OPERATIONAL_CURSOR=TRAINLOG_CARDIO_V1_NEXT_CONTRACT +CURRENT_OPERATIONAL_CURSOR=TRAINLOG_CARDIO_PERSISTENCE_SYNC_V1 ``` The focused `TRAINLOG_SYNC_CAUSAL_DELETE_V1_CLOSEOUT` is complete before this