diff --git a/RECAP.md b/RECAP.md index 19d726e..9b07af3 100644 --- a/RECAP.md +++ b/RECAP.md @@ -1,8 +1,104 @@ # RECAP — etm-powersync-app · reprise de session -> **Session 2026-08-26.** Branche **`feature/beta-add-config`**, **poussée** (`801ba15`). -> La journée du **26** est en tête ci-dessous ; la soirée du **25** (lot C) suit ; la section -> « État au 2026-06-30 » plus bas reste valable pour tout ce qu'elle décrit. +> **Session 2026-08-27.** Branche **`feature/beta-add-config`**, **39 commits d'avance, +> NON POUSSÉS** — la forge est à terre (`192.168.1.113` injoignable, front public en 503). +> Bundle de secours vérifié : +> `~/bundles/etm-powersync-app_feature-beta-add-config_2026-08-27.bundle`. +> La journée du **27** est en tête ; celle du 26 suit. + +--- + +## Journée 2026-08-27 — deux lots moteur, le premier passage écran, et trois défauts trouvés + +La box est passée de `+etm22` à `+etm24` dans la journée. Fil conducteur : **ce que l'app +affiche doit avoir été dit par la box** — et ce qu'aucun test unitaire ne peut voir, seul +l'appareil le montre. + +| Commit | Contenu | +|---|---| +| `a145529` | **3g-1** — `BATTERY_RESERVE` branché, `funding` lu, l'état d'une borne passe devant | +| `a92e9b2` | `SetChargingInfo` n'est PAS une écriture partielle — lire-patcher-réécrire | +| `a44d64d` | **3g-2** — les bornes sont des charges configurées, une seule liste, un seul rang | +| `24822ce` | **3g-2** — la mise en garde borne tombe, sans devenir une certitude trop large | +| `f7fd91e` | Le SOC véhicule inventé disparaît — la grandeur n'existe pas | +| `f33b8c2` | Le rapport de bug pré-rempli, repoussé depuis le premier jour | +| `da0c87e` | Le harnais d'appareil, et le cadrage des zones de clim | +| `01fdad7` | **Les cibles tactiles étaient à 26 px** — trouvé au premier passage sur appareil | +| `2701a9c` | L'arbitrage descend en bas, et les blocs se replient | + +### Les trois défauts que seul l'appareil a montrés + +1. **La poignée de glissement mesurait 26 × 27 px** (minimum Material : 48). Le `Container` + transparent posé la veille avait supprimé la *bande morte*, jamais agrandi la *cible* : + le geste accrochait quand on visait juste et ratait sinon. +2. **Les flèches de rang mesuraient 18 × 18 px** — la taille de l'icône, sans marge de + touche. +3. **La correction naïve était laide** : deux cibles de 44 px empilées font 88 px de haut + pour deux icônes de 18, et la colonne de rang devenait une colonne vide. Le test disait + « 44 px, c'est bon » ; c'est la **capture** qui a dit que c'était laid. Passées côte à + côte. + +Le harnais lui-même a demandé trois corrections avant de dire la vérité, et chacune se +reproduira : il regardait le haut de l'écran (dans un `ListView`, ce qui n'est pas visible +n'est pas construit) ; `find` trouve un widget **posé mais hors viewport** et `getCenter` +en rend une coordonnée qui n'existe pas (mesuré y = 1129 sur un écran de 825 px) ; la +course de glissement était devinée (~150) là où elle vaut **384 px mesurés**. + +### Ce que le moteur a changé, et ce que l'app en a fait + +- **`loads[] ⊆ GetLoadConfig` rétabli** : toute borne détectée reçoit une entrée + `LoadConfig` (`.75` passe de 2 à 4). Aller-retour verbatim vérifié **neutre** sur machine. +- **La mise en garde borne tombe** — mais *personne d'autre ne commande* n'est pas *la + borne obéit*. `allocationIsCommand` est **supprimé** plutôt que mis à `true` partout : le + garder inviterait à lire « commande » comme « réalité ». +- **`EV_GRID_START` aurait cassé la réconciliation en silence.** C'est le seul motif dont + l'allocation se PARTAGE entre les deux compteurs du budget ; filtrer sur + `funding == "surplus"` le comptait zéro. Trouvé en lisant le brief plutôt que son résumé. +- **Le rang par défaut d'une borne est un artefact** (trois charges à `priority = 1` sur le + banc). L'écran le nomme au lieu de le présenter comme un classement. + +### Ce qui a été RETIRÉ parce que c'était faux + +- **Le SOC véhicule à 62 % en dur.** Vérifié sur les 58 classes de `.75` : aucune classe + `evcharger` ne déclare d'état de charge. Le brief moteur a tranché ensuite, et c'est pire + qu'une mesure manquante — `carBatteryLevel` est une valeur que **le moteur écrit + lui-même**, publiée sous un nom de mesure. Décision LM-1009 : l'avancement s'affichera en + **énergie livrée**, jamais en pourcentage. +- **`BorneSection`** : les bornes ont désormais un rang et un domaine, la section les + affichait une seconde fois en affirmant le contraire. +- **Les deux flèches de domaine** : doublon de la poignée, désormais vérifiée au doigt. + +### 🔴 Aucun bilan JOURNALIER n'existe dans l'app + +Constaté en répondant à une question sur le tableau de bord. Les quatre tuiles sous le flux +énergétique (`features/dashboard/widgets/kpi_row.dart`) mêlent **deux natures de grandeur**, +et **aucune n'est journalière** : + +| Tuile | Ce qu'elle montre RÉELLEMENT | +|---|---| +| Autoconsommation | **% instantané** | +| Autonomie | **% instantané** | +| Vers réseau · cumulé | **kWh cumulés depuis l'origine** | +| Depuis réseau · cumulé | **kWh cumulés depuis l'origine** | + +Les deux dernières portaient « aujourd'hui », et c'était faux : `totalReturn` n'est pas remis +à zéro chaque nuit. Le libellé dit « · cumulé » depuis. **Le bilan du jour reste à faire** — +il passe par `GetPowerBalanceLogs` avec des bornes à minuit local, jamais par les totaux de +`GetPowerBalance`. Voir `TODO.md`. + +### ❓ Question ouverte au moteur — « ECS d'abord, batterie ensuite » + +Demandé par Patrick : que le surplus aille d'abord à l'eau chaude, et à la batterie +seulement ensuite. **Ce n'est pas exprimable aujourd'hui**, et le réglage existant fait +l'inverse : `batteryLevelConsideration` annule le budget *sous* le seuil, donc la batterie +est prioritaire jusque-là. Le mettre à 0 ne donne pas la priorité à l'ECS — il retire +seulement le blocage, et c'est l'onduleur qui arbitre. + +La cause est structurelle : **la batterie n'est pas dans le waterfall**. Elle n'a aucune +entrée `GetLoadConfig`, et `enableCharging` / `chargingRate` / `enableDischarging` +n'apparaissent nulle part dans le plugin ETM. Le levier existe pourtant — la classe +**SunSpec Storage** de `.75` déclare ces cinq actions. Question à porter au moteur : *le +stockage est-il une charge arbitrable, ou reste-t-il hors du budget ?* --- diff --git a/TODO.md b/TODO.md index f407c94..1b86822 100644 --- a/TODO.md +++ b/TODO.md @@ -1,19 +1,69 @@ # TODO — ETM PowerSync App +## Ouvert au 2026-08-27 + +- [ ] 🔴 **Bilan JOURNALIER — il n'en existe aucun dans l'app.** Les quatre tuiles du + tableau de bord (`features/dashboard/widgets/kpi_row.dart`) mêlent deux natures de + grandeur, et aucune n'est journalière : *Autoconsommation* et *Autonomie* sont des + **pourcentages instantanés**, *Vers réseau* et *Depuis réseau* des **kWh cumulés depuis + l'origine** — `totalReturn` / `totalAcquisition` ne sont pas remis à zéro chaque nuit. + Le libellé disait « aujourd'hui » ; il porte « · cumulé » depuis, mais **le bilan du jour + reste à écrire**. + - il passe par `Energy.GetPowerBalanceLogs` avec des bornes à **minuit local**, jamais par + les totaux de `GetPowerBalance` ; + - piège vérifié : les entrées de log portent `production` / `consumption` / `acquisition` + / `storage`, **sans** le préfixe `currentPower` de `GetPowerBalance`. Réutiliser les + mauvaises clés ne produit **aucune erreur** — tout tombe sur `0` ; + - `.75` publie des cumulés corrompus (9,1e+33 kWh exportés) : `EnergyData.isPlausibleKwh` + existe pour ça, ne pas le contourner. + +- [ ] ❓ **« ECS d'abord, batterie ensuite » — à porter au moteur.** Demandé le 2026-08-27, + et **pas exprimable aujourd'hui**. `batteryLevelConsideration` fait l'inverse : il annule + le budget *sous* le seuil, donc la batterie passe avant. La batterie n'est pas dans le + waterfall — aucune entrée `GetLoadConfig`, et `enableCharging` / `chargingRate` / + `enableDischarging` n'apparaissent nulle part dans le plugin ETM. Le levier existe + pourtant : la classe **SunSpec Storage** de `.75` déclare ces cinq actions. Question : + *le stockage est-il une charge arbitrable, ou reste-t-il hors du budget ?* Une règle + `Rules.*` serait un contournement — deux commandeurs sur la batterie, exactement le + défaut que 3g-2 vient de supprimer sur les bornes. + +- [ ] **Zones de climatisation — cadrage écrit, banc à préparer.** Voir + `docs/CADRAGE_zones_clim.md`. Le blocage n'est pas « aucune zone déclarée » : sur les 58 + classes de `.75`, **aucune ne porte `thermostat`, `closablesensor`, `temperaturesensor` + ni `notifications`** — aucun plugin installé ne sait en créer une. Deux paquets du dépôt + apt du banc suffisent (`nymea-plugin-generic-heatingcooling`, + `nymea-plugin-generic-sensors`). Six décisions à prendre avant de coder. + +- [ ] **`EV_GRID_START` / `PHASE_LIMIT` — éprouvés par test seulement.** Les clés ARB sont + prêtes, les motifs n'ont **jamais été vus sur machine**. Signaler la première occurrence + réelle. Ne rien bâtir autour d'ici là. + +- [ ] **`EVChargingCard` n'est montée par AUCUN écran** (constaté le 2026-08-27 — le + tableau de bord utilise `features/dashboard/widgets/`). Soit elle rejoint un écran, soit + elle part ; en l'état c'est du code mort qui a l'air vivant. + +- [ ] **Le passage sur appareil doit devenir une habitude, pas un rattrapage.** Il a glissé + deux fois, et au premier passage il a trouvé deux cibles tactiles hors norme (26 px, 18 px) + qu'aucun des 129 tests unitaires ne pouvait voir. `integration_test/ui_3g2_on_device_test.dart` + est le harnais. Rappel MIUI : la confirmation d'installation est redemandée à **chaque** + fois — faire un `adb install -r -t` préalable, puis lancer le test. + ## Charges pilotables — ce qui attend la box (2026-08-26) -- [ ] **`kCodesReserveBatterie`** — le `Set` est volontairement VIDE dans - `lib/services/telemetry_text.dart`. Y mettre le code du motif « budget annulé par la réserve - batterie » **quand le moteur le publiera**, pas avant : deviner son nom produirait un - affichage qui ne se déclenche jamais. +- [x] ~~**`kCodesReserveBatterie`**~~ — **fait le 2026-08-27.** Le motif est publié : + `BATTERY_RESERVE`, params `socPercent` / `reservePercent` / `withheldW`. Les clés que le + fichier cherchait (`batteryLevel`, `threshold`) n'avaient **jamais** existé côté plugin — + le motif sortait donc en repli anglais sur le seul cas qu'aucune mesure ne révèle. - [ ] **`minPowerW == null` sans véhicule branché** — couvert en test unitaire (`test/ev_runtime_test.dart`), **pas exercé sur appareil** : la borne n'entre pas dans `loads[]` avant `3g`. À reprendre quand l'arbitrage prendra la recharge en charge. - [ ] **Réserve batterie — portée élargie.** Le jour où le seuil gouverne tout le budget, reprendre le texte de `BatteryReserveCard` : il annonce aujourd'hui un futur, il devra décrire un présent. -- [ ] ~~Zones~~ — `ac_screen.dart` est de la donnée fictive et `.75` n'en déclare aucune. - **Décidé : à ne pas entreprendre.** +- [ ] ~~Zones — à ne pas entreprendre~~ — **décision revue le 2026-08-27** : le lot est + cadré (`docs/CADRAGE_zones_clim.md`), voir l'entrée en tête de ce fichier. `ac_screen.dart` + reste de la donnée fictive — quatre pièces, huit températures crédibles affichées comme + des mesures. ## En cours / priorité haute