test(cardio): prove multi-occurrence program heart-rate ownership
This commit is contained in:
parent
21f56e3986
commit
c8bfb7a759
4 changed files with 72 additions and 54 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -75,11 +75,12 @@
|
||||||
|
|
||||||
- Added one end-to-end tomorrow-session readiness regression. A synchronized
|
- Added one end-to-end tomorrow-session readiness regression. A synchronized
|
||||||
Program session retains its stable session and Program identities through
|
Program session retains its stable session and Program identities through
|
||||||
edits, two explicit exercise intervals, pre-exercise/inter-exercise/active
|
edits, two ordered Program-derived occurrence intervals,
|
||||||
heart-rate ownership, finalization, full-generation publication, desktop
|
pre-exercise/inter-exercise/active heart-rate ownership, finalization,
|
||||||
import, replay and correlated ACK. Raw observation timestamps, BPM, RR,
|
full-generation publication, desktop import, replay and correlated ACK. Raw
|
||||||
nullable stable exercise ownership and capture/session bounds are compared
|
observation timestamps, BPM, RR, nullable stable exercise ownership and
|
||||||
on both platforms without inferring ownership from time overlap.
|
capture/session bounds are compared on both platforms without inferring
|
||||||
|
ownership from time overlap.
|
||||||
|
|
||||||
- Added factual synchronized heart-rate timelines to the local Web. One bounded C17 read model and loopback GET endpoint now project raw BPM/RR measurements with canonical workout exercise intervals or Sleep Diary/medication markers. Cardio history additionally presents resolved phase targets, instruction changes and calibration peak/recovery facts. History and Sleep reuse one responsive SVG component; full-sample min/max/mean statistics remain exact while only very large chart projections retain bounded bucket extrema. No sleep stage, HRV, readiness or medical inference is added.
|
- Added factual synchronized heart-rate timelines to the local Web. One bounded C17 read model and loopback GET endpoint now project raw BPM/RR measurements with canonical workout exercise intervals or Sleep Diary/medication markers. Cardio history additionally presents resolved phase targets, instruction changes and calibration peak/recovery facts. History and Sleep reuse one responsive SVG component; full-sample min/max/mean statistics remain exact while only very large chart projections retain bounded bucket extrema. No sleep stage, HRV, readiness or medical inference is added.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,7 @@ import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.labfytools.trainlog.model.HeartRateContextKind
|
import com.labfytools.trainlog.model.HeartRateContextKind
|
||||||
import com.labfytools.trainlog.model.NewExerciseProfile
|
|
||||||
import com.labfytools.trainlog.model.RecordingMode
|
|
||||||
import com.labfytools.trainlog.model.SessionExerciseDraft
|
|
||||||
import com.labfytools.trainlog.model.SessionSetDraft
|
import com.labfytools.trainlog.model.SessionSetDraft
|
||||||
import com.labfytools.trainlog.model.TrackingMode
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -67,17 +63,27 @@ class ProgramSessionCardioReadinessTest {
|
||||||
try {
|
try {
|
||||||
val programId = "pg_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
val programId = "pg_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
||||||
val programSessionId = "pgs_bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
|
val programSessionId = "pgs_bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
|
||||||
val programExerciseId = "ex_dddddddd-dddd-4ddd-8ddd-dddddddddddd"
|
val programExerciseAId = "ex_dddddddd-dddd-4ddd-8ddd-dddddddddddd"
|
||||||
|
val programExerciseBId = "ex_11111111-1111-4111-8111-111111111111"
|
||||||
val catalog =
|
val catalog =
|
||||||
JSONObject()
|
JSONObject()
|
||||||
.put("format", "trainlog-pc-catalog")
|
.put("format", "trainlog-pc-catalog")
|
||||||
.put("version", 1)
|
.put("version", 1)
|
||||||
.put(
|
.put(
|
||||||
"exercises",
|
"exercises",
|
||||||
JSONArray().put(
|
JSONArray()
|
||||||
|
.put(
|
||||||
JSONObject()
|
JSONObject()
|
||||||
.put("exercise_id", programExerciseId)
|
.put("exercise_id", programExerciseAId)
|
||||||
.put("name", "Program exercise fixture")
|
.put("name", "Program exercise fixture A")
|
||||||
|
.put("recording_mode", "sets")
|
||||||
|
.put("tracking_mode", "reps")
|
||||||
|
.put("data_fields", 0),
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
JSONObject()
|
||||||
|
.put("exercise_id", programExerciseBId)
|
||||||
|
.put("name", "Program exercise fixture B")
|
||||||
.put("recording_mode", "sets")
|
.put("recording_mode", "sets")
|
||||||
.put("tracking_mode", "reps")
|
.put("tracking_mode", "reps")
|
||||||
.put("data_fields", 0),
|
.put("data_fields", 0),
|
||||||
|
|
@ -110,13 +116,14 @@ class ProgramSessionCardioReadinessTest {
|
||||||
.put("note", JSONObject.NULL)
|
.put("note", JSONObject.NULL)
|
||||||
.put(
|
.put(
|
||||||
"occurrences",
|
"occurrences",
|
||||||
JSONArray().put(
|
JSONArray()
|
||||||
|
.put(
|
||||||
JSONObject()
|
JSONObject()
|
||||||
.put(
|
.put(
|
||||||
"entry_id",
|
"entry_id",
|
||||||
"pge_cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
"pge_cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
||||||
)
|
)
|
||||||
.put("exercise_id", programExerciseId)
|
.put("exercise_id", programExerciseAId)
|
||||||
.put("equipment_id", JSONObject.NULL)
|
.put("equipment_id", JSONObject.NULL)
|
||||||
.put("load_mode", "external")
|
.put("load_mode", "external")
|
||||||
.put("rest_seconds", 90)
|
.put("rest_seconds", 90)
|
||||||
|
|
@ -125,6 +132,22 @@ class ProgramSessionCardioReadinessTest {
|
||||||
.put("target_duration_seconds", JSONObject.NULL)
|
.put("target_duration_seconds", JSONObject.NULL)
|
||||||
.put("target_weight_kg", 40.0)
|
.put("target_weight_kg", 40.0)
|
||||||
.put("notes", JSONObject.NULL),
|
.put("notes", JSONObject.NULL),
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
JSONObject()
|
||||||
|
.put(
|
||||||
|
"entry_id",
|
||||||
|
"pge_22222222-2222-4222-8222-222222222222",
|
||||||
|
)
|
||||||
|
.put("exercise_id", programExerciseBId)
|
||||||
|
.put("equipment_id", JSONObject.NULL)
|
||||||
|
.put("load_mode", "external")
|
||||||
|
.put("rest_seconds", 60)
|
||||||
|
.put("target_sets", 1)
|
||||||
|
.put("target_reps", 10)
|
||||||
|
.put("target_duration_seconds", JSONObject.NULL)
|
||||||
|
.put("target_weight_kg", 25.0)
|
||||||
|
.put("notes", JSONObject.NULL),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -141,15 +164,6 @@ class ProgramSessionCardioReadinessTest {
|
||||||
ProgramsImportResult.Applied(1, 0, 0),
|
ProgramsImportResult.Applied(1, 0, 0),
|
||||||
android.applyProgramsV1Json(programs),
|
android.applyProgramsV1Json(programs),
|
||||||
)
|
)
|
||||||
val exerciseB =
|
|
||||||
(android.createExercise(
|
|
||||||
NewExerciseProfile(
|
|
||||||
"Readiness exercise B",
|
|
||||||
RecordingMode.SETS,
|
|
||||||
TrackingMode.REPS,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
) as CreateExerciseResult.Created).exercise
|
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
StartProgramSessionResult.Started,
|
StartProgramSessionResult.Started,
|
||||||
|
|
@ -160,12 +174,12 @@ class ProgramSessionCardioReadinessTest {
|
||||||
val sessionStartedAt = checkNotNull(opened.draft.startedAt)
|
val sessionStartedAt = checkNotNull(opened.draft.startedAt)
|
||||||
assertEquals(programId, opened.draft.sourceProgramId)
|
assertEquals(programId, opened.draft.sourceProgramId)
|
||||||
assertEquals(programSessionId, opened.draft.sourceProgramSessionId)
|
assertEquals(programSessionId, opened.draft.sourceProgramSessionId)
|
||||||
val exerciseA = opened.draft.exercises.single()
|
assertEquals(2, opened.draft.exercises.size)
|
||||||
val exerciseBEntry =
|
val exerciseA = opened.draft.exercises[0]
|
||||||
SessionExerciseDraft(
|
val exerciseBEntry = opened.draft.exercises[1]
|
||||||
exercise = exerciseB,
|
assertEquals(programExerciseAId, exerciseA.exercise.exerciseId)
|
||||||
sets = listOf(SessionSetDraft(reps = 10, weightKg = 25.0)),
|
assertEquals(programExerciseBId, exerciseBEntry.exercise.exerciseId)
|
||||||
)
|
assertTrue(exerciseA.entryId != exerciseBEntry.entryId)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ActiveDraftMutationResult.Saved,
|
ActiveDraftMutationResult.Saved,
|
||||||
android.saveActiveSessionDraft(
|
android.saveActiveSessionDraft(
|
||||||
|
|
@ -175,7 +189,9 @@ class ProgramSessionCardioReadinessTest {
|
||||||
exerciseA.copy(
|
exerciseA.copy(
|
||||||
sets = listOf(SessionSetDraft(reps = 8, weightKg = 40.0)),
|
sets = listOf(SessionSetDraft(reps = 8, weightKg = 40.0)),
|
||||||
),
|
),
|
||||||
exerciseBEntry,
|
exerciseBEntry.copy(
|
||||||
|
sets = listOf(SessionSetDraft(reps = 10, weightKg = 25.0)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ parallel implementation of its rules.
|
||||||
| Cardio BPM guidance | `TRAINLOG_CARDIO_BPM_GUIDANCE_V1=PASS`: absolute/calibration-derived immutable BPM targets, 2 BPM hysteresis, 3 s confirmation, stale-signal suspension, durable phase/condition history, Android live guidance UI and strict Android→desktop `trainlog-cardio-guidance` V1 sync |
|
| Cardio BPM guidance | `TRAINLOG_CARDIO_BPM_GUIDANCE_V1=PASS`: absolute/calibration-derived immutable BPM targets, 2 BPM hysteresis, 3 s confirmation, stale-signal suspension, durable phase/condition history, Android live guidance UI and strict Android→desktop `trainlog-cardio-guidance` V1 sync |
|
||||||
| Cardio Web visualization | `TRAINLOG_CARDIO_WEB_VISUALIZATION_V1=PASS`: one bounded factual heart-rate timeline read model/HTTP endpoint powers Web History and Sleep with raw BPM/RR, real session/exercise/Sleep markers, guided targets/instructions and calibration facts; no sleep-stage or medical inference |
|
| Cardio Web visualization | `TRAINLOG_CARDIO_WEB_VISUALIZATION_V1=PASS`: one bounded factual heart-rate timeline read model/HTTP endpoint powers Web History and Sleep with raw BPM/RR, real session/exercise/Sleep markers, guided targets/instructions and calibration facts; no sleep-stage or medical inference |
|
||||||
| 0.1.5 validation | `TRAINLOG_0_1_5_VALIDATION_V1=SOFTWARE_PASS / DEVICE_VISUAL_PASS`: native 111/111; Android 304 tests (299 pass + 5 historical skips) and debug APK build; Web 183/183 + typecheck/build baseline. Private vc46 preserves the live HR capture and samples across Training → Cardio, drives measured guidance, and renders Sommeil and Calibration cardio without mutation. No stable release is authorized by this status. |
|
| 0.1.5 validation | `TRAINLOG_0_1_5_VALIDATION_V1=SOFTWARE_PASS / DEVICE_VISUAL_PASS`: native 111/111; Android 304 tests (299 pass + 5 historical skips) and debug APK build; Web 183/183 + typecheck/build baseline. Private vc46 preserves the live HR capture and samples across Training → Cardio, drives measured guidance, and renders Sommeil and Calibration cardio without mutation. No stable release is authorized by this status. |
|
||||||
| Tomorrow session readiness | `TRAINLOG_TOMORROW_SESSION_CARDIO_READINESS_V1=PASS`: the private vc46 is clean on schema v31 with fresh CYCPLUS BPM and the next Program session is unambiguous; one combined regression proves Program provenance, factual two-exercise timeline/HR ownership, finalization, full generation, desktop import, replay and correlated ACK |
|
| Tomorrow session readiness | `TRAINLOG_TOMORROW_SESSION_CARDIO_READINESS_V1=PASS`: the private vc46 is clean on schema v31 with fresh CYCPLUS BPM and the next Program session is unambiguous; one combined regression proves two ordered occurrences loaded from the synchronized Program, Program provenance, factual timeline/HR ownership, finalization, full generation, desktop import, replay and correlated ACK |
|
||||||
| Current operational cursor | `TRAINLOG_TOMORROW_SESSION_CARDIO_READINESS_V1=PASS`: tomorrow's recording path is ready; any stable 0.1.5 tag or release remains a separate explicit decision |
|
| Current operational cursor | `TRAINLOG_TOMORROW_SESSION_CARDIO_READINESS_V1=PASS`: tomorrow's recording path is ready; any stable 0.1.5 tag or release remains a separate explicit decision |
|
||||||
| Complete synchronization gap contract | Frozen dependency contract; operational USB/Drive slices required by v0.1.2 are delivered |
|
| 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` |
|
| Isolated synchronization test environment | `TRAINLOG_SYNC_TEST_ENV_V1=PASS/FROZEN` |
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,10 @@ Tomorrow's next synchronized Program session is unambiguously
|
||||||
private phone remains on Android schema v31 with integrity/FK checks clean and
|
private phone remains on Android schema v31 with integrity/FK checks clean and
|
||||||
no active draft, timeline, HR capture, guidance phase or calibration. A single
|
no active draft, timeline, HR capture, guidance phase or calibration. A single
|
||||||
combined regression now proves Program start and provenance through two
|
combined regression now proves Program start and provenance through two
|
||||||
explicit exercise intervals, four HR ownership positions, finalization, full
|
ordered occurrences originating in the synchronized Program payload, four HR
|
||||||
generation, desktop import, idempotent replay and correlated ACK. This
|
ownership positions, finalization, full generation, desktop import, idempotent
|
||||||
readiness result does not authorize a stable 0.1.5 tag or release.
|
replay and correlated ACK. This readiness result does not authorize a stable
|
||||||
|
0.1.5 tag or release.
|
||||||
|
|
||||||
The focused `TRAINLOG_SYNC_CAUSAL_DELETE_V1_CLOSEOUT` is complete before this
|
The focused `TRAINLOG_SYNC_CAUSAL_DELETE_V1_CLOSEOUT` is complete before this
|
||||||
cursor: complete predecessor state, imported built-in authorization,
|
cursor: complete predecessor state, imported built-in authorization,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue