IntervalEngine : le commentaire sur les pauses disait l'inverse d'Apple

L'en-tête affirmait que `HKLiveWorkoutBuilder.elapsedTime` « exclut déjà les
pauses », et en tirait que le moteur n'avait rien à savoir de la pause ni de
l'auto-pause.

La doc Apple dit le contraire, vérifié à la source le 2026-08-20 :
« The elapsed time for the workout based on the builder's current contents,
including pauses. »

Conséquence si on câblait le moteur dessus telle quelle : une pause de 5 min
ferait avancer le déroulé de 5 min d'effort — un fractionné mis en pause pour
traverser une route se déroulerait à l'arrêt.

La propriété qui exclut réellement les pauses est
`HKWorkoutBuilder.elapsedTime(at:)` : « The duration of a workout doesn't
include intervals between pause and resume events. » Les deux textes d'Apple
se contredisent frontalement ; le choix de la source de temps reste à faire
avant le câblage.

Le moteur lui-même reste correct : il n'intègre que ce qu'on lui pousse.
Aucun comportement modifié, 57 tests Swift toujours verts.

COWORK gagne l'analyse complète du câblage (3 couches, le serveur sait déjà
produire les étapes via _blocks_from_session) pour reprise à froid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-08-20 16:52:21 +00:00
parent 19fbfd116e
commit 891a8bddce
2 changed files with 79 additions and 3 deletions

View File

@@ -129,6 +129,59 @@ armer `allowsBackgroundLocationUpdates` sans elle termine l'app. Un garde-fou
la vérifie au démarrage, mais si elle disparaissait du build, le suivi
retomberait silencieusement au premier plan seul.
## 🔜 Câbler IntervalEngine — analyse du 2026-08-20 (rien de codé)
`IntervalEngine` **compile sur device** (build validé le 20/08, l'app tourne),
mais **aucune vue ne l'appelle** : le moteur est un guide sans itinéraire.
### Le chaînon manquant
Le moteur attend un `CoachSessionPlan` (liste d'étapes : durée, zone, bornes
bpm). **Rien ne le lui fournit**`ConnectivityManager` reçoit les zones, le
`CoachWidgetSnapshot` et la routine, mais aucun plan structuré. Vérifié par
lecture du fichier, pas supposé.
En revanche **le serveur sait déjà produire ces étapes** :
`coach_sportif/web/fitness_export.py::_blocks_from_session` déplie une séance
en blocs chronométrés, répétitions comprises (un bug de prod du 12/08 avait
fait disparaître un 7×(1'C/1'M) : les `repeat` sont désormais dépliés), et
`_zone_bpm(zone, hr_zones)` convertit une zone en bornes bpm. Ces briques
servent déjà à l'export TCX Workout.
Indice que le chemin était prévu : les `CodingKeys` de `CoachPlanStep` sont en
snake_case (`duration_sec`, `hr_zone`, `hr_bpm_min`…) — le type a été écrit
pour décoder du JSON venant du serveur.
### Les trois couches, dans l'ordre
1. **Serveur (Python, testable sans Mac)** — une route rendant la séance du
jour au format `CoachSessionPlan`, en réutilisant `_blocks_from_session` et
`app.current_zones()` (règle 9 du CLAUDE.md : jamais un % de FCmax
générique). ✅ Commencer par là : le JSON se juge dans un navigateur avant
qu'une ligne de Swift n'en dépende.
2. **iPhone** — pousser ce plan vers la montre par `WCSession`, à côté de ce
qui part déjà.
3. **Montre**`ConnectivityManager` publie un `sessionPlan` ; une vue appelle
`engine.update(elapsed:distance:)` à chaque tick et joue une haptique sur
`events.transitions`.
### ⚠️ Le piège à trancher AVANT de câbler
L'en-tête d'`IntervalEngine.swift` affirmait que
`HKLiveWorkoutBuilder.elapsedTime` « exclut déjà les pauses ». **La doc Apple
dit l'inverse**, vérifié à la source le 20/08 : *« The elapsed time for the
workout based on the builder's current contents, including pauses. »*
Câbler le moteur dessus telle quelle ferait avancer le déroulé **pendant les
pauses** — un fractionné mis en pause pour traverser une route se déroulerait
à l'arrêt. La propriété qui exclut réellement les pauses est
`HKWorkoutBuilder.elapsedTime(at:)`. Les deux textes d'Apple se contredisent
frontalement ; le commentaire du fichier a été corrigé, **le choix de la source
de temps reste à faire**.
⚠️ Et jamais un `Timer` de vue SwiftUI : il ne tourne pas écran éteint, donc
pendant l'essentiel d'une séance (même famille de piège que `VmaTestView`).
## 👉 À reprendre
-**Phase 4 login — FAIT & DÉPLOYÉ** (révocation Apple à la suppression de compte) : code complet côté backend `coach_sportif` (commit `5ae6e2d`) + clé `.p8` déployée sur le VPS prod (vérifié 2026-06-26). Rien à coder. Détails : `coach_sportif/COWORK.md`.
- **iOS — bloqué Mac** : builder + uploader TestFlight le natif accumulé (login Apple+Google natif, watchOS live, HeartRateRangeAlert). Sur le Mac : `cd ~/coach-ios && git pull && npm install && npx cap sync ios && open ios/App/App.xcodeproj` → Clean Build Folder → Archive → Upload.

View File

@@ -15,9 +15,32 @@ import Foundation
*
* - il se teste intégralement sur Linux (cf. `tests-linux/`), là où
* `WorkoutManager` ne le peut pas ;
* - le temps de référence est `HKLiveWorkoutBuilder.elapsedTime`, qui
* **exclut déjà les pauses** : le moteur n'a donc rien à savoir de la
* pause, ni de l'auto-pause de la montre ;
* - le temps de référence est poussé par l'appelant ;
*
* **CE COMMENTAIRE AFFIRMAIT LE CONTRAIRE DE SA SOURCE** (relevé le
* 2026-08-20, avant tout câblage). Il disait que
* `HKLiveWorkoutBuilder.elapsedTime` « exclut déjà les pauses », donc que
* le moteur n'avait rien à savoir de la pause. La doc Apple dit
* l'inverse, mot pour mot : « The elapsed time for the workout based on
* the builder's current contents, **including pauses**. »
* (developer.apple.com/documentation/healthkit/hkliveworkoutbuilder/elapsedtime)
*
* Conséquence si on câble le moteur sur cette propriété telle quelle :
* une pause de 5 min ferait avancer le déroulé de 5 min d'effort. Un
* fractionné mis en pause pour traverser une route se déroulerait tout
* seul, à l'arrêt.
*
* La propriété qui exclut réellement les pauses est
* `HKWorkoutBuilder.elapsedTime(at:)` « The duration of a workout
* doesn't include intervals between pause and resume events. » Ce n'est
* PAS la même API, et les deux textes d'Apple se contredisent
* frontalement sur ce point : à trancher avant le câblage.
*
* Le moteur, lui, reste correct : il ne fait qu'intégrer ce qu'on lui
* pousse. C'est l'appelant qui devra fournir un temps réellement actif ;
*
* - il ignore donc l'auto-pause de la montre, à condition que la source de
* temps ci-dessus soit correcte ;
* - il est rejouable : réinjecter la même suite de ticks redonne le même
* déroulé, ce qui rend la reprise après crash triviale
* (`handleActiveWorkoutRecovery`).