--- issueRef: "#172" version: 2 updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"} updatedAt: 1785272880017 --- ## #172 — Cadrage Main (2026-07-28) ### Décision produit Direction validée : - la montre reste un **capteur + télécommande** ; - le téléphone reste **source de vérité métier** ; - on optimise le trafic et la chauffe par **batching côté montre**, pas par déplacement de logique métier. ### 1. Télémétrie stats montre Deux modes à cadrer : - **écran montre allumé** : cadence plus fréquente pour un live crédible ; - **écran montre éteint / ambient** : flush agrégé toutes les ~30s, inspiré du comportement observé sur Heavy. La montre ne calcule pas les agrégats métier finaux. Elle bufferise seulement des échantillons ou micro-agrégats transport : - FC : min/max/moy locale de fenêtre + latest utile à l'affichage ; - distance/calories : latest cumulée de fenêtre, jamais recalcul métier final ; - timestamps de capture et contexte d'exécution. Le téléphone : - persiste ; - fusionne ; - construit les agrégats de séance / étape / série / exercice / historique ; - résout les conflits et les trous. ### 2. Score manuel montre Décision : **oui au debounce 500ms, non au "score final sans contexte"**. Le flux cible minimal : - la montre met à jour le score **optimistement** à chaque tap ; - chaque tap reset un timer de 500ms ; - au silence de 500ms, la montre envoie au téléphone une intention de type `setManualScore` contextualisée ; - le téléphone valide, persiste, reprojette ; - la montre se recale silencieusement sur la projection confirmée. ### 3. Garde-fous architecture - Pas de logique métier durable sur la montre. - Pas de calcul d'historique ni de règle d'invariants sur la montre. - Chaque message montre -> téléphone doit embarquer le contexte minimal : `sessionId`, indices d'exécution, `baseRevision`, horodatage. - Rejet/conflit côté téléphone : la projection confirmée gagne toujours. - Crash montre avant flush : perte limitée à la fenêtre non flushée, acceptable pour le score seulement si la montre ne prétend jamais avoir persisté côté téléphone. ### 4. Contrat à cadrer ensuite Ticket attendu après celui-ci : - extension ou adaptation du contrat watch bridge pour batches télémétrie ; - ajout d'une commande contextualisée `setManualScore` ou équivalent ; - stratégie de flush explicite selon état écran / foreground / ambient ; - QA device sur chauffe, batterie, cohérence de resync. ## Cadrage Architect (2026-07-28) Statut : **cadrage exploitable, prêt pour découpage dev**. ### Architecture cible minimale - Garder `phone = source de vérité`, `watch = capteur + télécommande`. - Conserver `WatchCompanionCommandHandler` comme point d'entrée unique des intentions montre. - Ajouter un flux applicatif téléphone dédié pour la télémétrie batchée : `WatchTelemetryBatchIngress` -> validation -> appel de `WorkoutTelemetryUseCases.recordTelemetrySample(...)` pour chaque sample -> `WorkoutHistoryUseCases.updateHeartRateSummary(...)` au flush final. - La montre ne calcule ni score final ni agrégats métier ; elle ne bufferise que du transport. ### Télémétrie batchée watch -> phone - Recommandation : nouveau DTO `WatchTelemetryBatch`, `schemaVersion = 5`, nouveau path message `/gametime/watch/telemetry-batch`. - Le téléphone déduplique au niveau **sample** (`sampleId` stable), pas au niveau batch. - `WatchSensorSummary` est conservé pour le résumé final de séance, pas remplacé. ### Stratégie interactive vs ambient/screenOff - `interactive` : flush toutes les `5s` max. - `ambient/screenOff` : flush toutes les `30s` max. - Flush immédiat aussi sur : fin de séance, pause explicite si la collecte s'arrête, reconnexion téléphone, changement d'état écran, buffer plein. - Cap de sécurité : couper les batches trop gros (~60 samples). - Une petite outbox locale de télémétrie côté montre est acceptable pour survivre à un crash : c'est du transport, pas du métier. ### Score manuel debounce - Décision Architect : **ne pas faire de `setManualScore(value)` comme contrat principal**. - Recommandation : un batch d'intentions delta, type `applyManualScoreDeltaBatch`. - La montre accumule les taps `+/-` pendant `500 ms`, puis envoie un seul delta net avec `sessionId`, position courante, `baseRevision`, horodatage. - Le téléphone applique le delta seulement si la cible métier est toujours la même ; sinon rejet et resync. ### Invariants et risques - La montre ne calcule jamais la valeur finale du score. - Les écritures d'historique et agrégats durables restent côté téléphone. - Une intention score batchée n'est applicable que si `sessionId + scope + position courante` correspondent encore. - Les retries de télémétrie ne doivent jamais dupliquer les samples : `sampleId` stable obligatoire. - En cas de crash montre : - score pending perdu puis recalage sur projection téléphone ; - télémétrie non flushée reprise depuis l'outbox local. - En reconnexion : - resync projection immédiat ; - flush immédiat de l'outbox télémétrie ; - aucun replay d'anciennes intentions score. ### Lots recommandés 1. `B1 [DevBackend] Contrat bridge v5 et ingress télémétrie batchée` 2. `B2 [DevBackend] Application téléphone score debounce + validation de cible` 3. `B3 [DevBackend] Déduplication, retries, flush final` 4. `F1 [DevFrontend] Buffer montre télémétrie et stratégie interactive vs ambient` 5. `F2 [DevFrontend] Score optimiste debounce 500 ms` 6. `QA1 [QA] Matrice de cohérence et robustesse`