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:
53
COWORK.md
53
COWORK.md
@@ -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.
|
||||
|
||||
@@ -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`).
|
||||
|
||||
Reference in New Issue
Block a user