---
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`
