From 935cec269f4033a54e4e0964b57db88b22413e33 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Fri, 28 Aug 2026 12:18:00 +0200 Subject: [PATCH] =?UTF-8?q?test(config):=20les=20deux=20notes=20sont=20MON?= =?UTF-8?q?T=C3=89ES,=20pas=20seulement=20calcul=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vérification demandée après le relevé V2C du banc : « ta note de rangs ex æquo doit s'afficher — vérifie qu'elle le fait ». Elle s'affiche. Mais rien ne le prouvait, et c'est exactement le défaut qui a coûté isNotGrouped : la contiguïté était calculée depuis le premier jour, testée en fonction, exposée par le provider — et affichée nulle part. Un test de fonction ne peut pas voir ça. Quatre tests de widgets sur la configuration réelle du 2026-08-28 (loadconfig_hems75_v2c.json), montés dans le harnais existant : · la note de rangs ex æquo est à l'écran ET nomme les charges (chauffe-eau, V2C Trydan) — « des rangs sont à égalité » sans dire lesquelles laisse chercher dans cinq cartes ; · la note d'ordre entrelacé est à l'écran ET annonce ce que l'enregistrement fera (REGROUPERA) — l'annoncer après serait un constat, pas un avertissement ; · contrôle négatif : sur un ordre groupé et classé, aucune des deux n'apparaît. Sans lui, deux notes toujours affichées deviennent du décor ; · THING_MISSING se lit « l'appareil n'existe plus dans nymea » + « réinstallez-le, ou retirez cette charge », et n'offre RIEN à lever — ni le bouton, ni l'aide qui l'accompagne. ⚠️ La télémétrie du quatrième test est COMPOSÉE, pas capturée : le banc publiait WRITE_FAILED sur cette charge au dernier relevé (l'adaptateur épuise son échelle d'écriture avant que THING_MISSING ne sorte). Le test éprouve le rendu du code sur le loadId réel de la Terra AC supprimée, pas la présence du code sur la box. Et deux traces de la campagne V2C, qui ne se câblent pas : · tools/rpc/evcharger_watch.dart entre au dépôt, documenté. C'est la sonde qui a produit les deux constats bloquants — une borne hors arbitrage n'apparaît pas dans telemetry_watch, et c'est pendant ces transitions qu'il faut la regarder. · ev_charging_card.dart affirmait que « les deux bornes du banc publient sessionEnergy ». Faux depuis l'arrivée de la V2C : elle publie chargeEnergy, remis à zéro à chaque interruption de charge. La note renvoie désormais à LoadMechanism et redit C1 de LM-1009 — « pas mesurable », jamais zéro. TODO : entrée d'attente pour sessionEnergy et le renommage currentL1/L2/L3. 146 tests passent, flutter analyze sans remarque nouvelle. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f --- TODO.md | 11 +++ lib/widgets/ev_charging_card.dart | 13 ++- test/merged_config_screen_test.dart | 95 +++++++++++++++++++ tools/rpc/README.md | 19 ++++ tools/rpc/evcharger_watch.dart | 136 ++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 tools/rpc/evcharger_watch.dart diff --git a/TODO.md b/TODO.md index 5844e35..d79f721 100644 --- a/TODO.md +++ b/TODO.md @@ -35,6 +35,17 @@ 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à. +- [ ] ⏳ **Deux grandeurs de la V2C attendent le plugin — ne rien câbler d'ici là.** + Mesuré sur `.75` le 2026-08-28, sonde `tools/rpc/evcharger_watch.dart`. + - `chargeEnergy` est **exact pendant la charge** et **remis à zéro à chaque + interruption** — donc à chaque nuage sur du pilotage par surplus. Le plugin V2C + publiera un `sessionEnergy` construit ; attendre celui-là, et **n'accumuler rien côté + app**. D'ici là, la condition C1 de LM-1009 s'applique en vrai sur cette borne : + « pas mesurable » et « rien livré » sont deux états différents, et zéro dit le second. + - `powerL1/L2/L3` sont des **ampères mal nommés** ; le renommage `currentL1/L2/L3` + (unité Ampere) est en cours côté plugin. Ne pas les afficher avant son déploiement, et + **n'écrire aucune conversion côté app** — elle survivrait au correctif. + - [ ] **`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. diff --git a/lib/widgets/ev_charging_card.dart b/lib/widgets/ev_charging_card.dart index 9230256..d2ccd50 100644 --- a/lib/widgets/ev_charging_card.dart +++ b/lib/widgets/ev_charging_card.dart @@ -472,9 +472,16 @@ class _ModeButton extends StatelessWidget { /// /// **aucun SOC de véhicule n'existe dans ce contrat.** Vérifié sur `.75` le 2026-08-27, /// sur les 58 classes de la box : aucune classe portant l'interface `evcharger` ne -/// déclare d'état de charge, aucune classe ne porte l'interface `car`, et les deux bornes -/// du banc publient `pluggedIn`, `charging`, `maxChargingCurrent`, `phaseCount`, -/// `sessionEnergy` — jamais un pourcentage. +/// déclare d'état de charge, aucune classe ne porte l'interface `car`, et les bornes du +/// banc publient `pluggedIn`, `charging`, `maxChargingCurrent` — jamais un pourcentage. +/// +/// **Et l'énergie livrée n'est pas là non plus, sur la borne du banc.** La V2C Trydan +/// arrivée le 2026-08-28 ne publie ni `sessionEnergy` ni `phaseCount` : elle publie +/// `chargeEnergy`, exact pendant la charge et **remis à zéro à chaque interruption** — +/// donc à chaque nuage sur du pilotage par surplus. Le détail et la mesure sont dans +/// `LoadMechanism` (`models/load_telemetry.dart`). Tant que le plugin ne publie pas le +/// `sessionEnergy` construit qu'il prépare, l'avancement de LM-1009 se dit **« pas +/// mesurable »** — et surtout pas zéro, qui se lirait « rien livré ». /// /// Le seul `batteryLevel` de l'installation est celui de la **batterie de la maison** /// (Fronius Storage, 50 %), rendu visible par le correctif `energystorage` du plugin diff --git a/test/merged_config_screen_test.dart b/test/merged_config_screen_test.dart index 0d62174..4df16b2 100644 --- a/test/merged_config_screen_test.dart +++ b/test/merged_config_screen_test.dart @@ -227,6 +227,101 @@ void main() { FieldSupport.unknown); }); }); + + // ─────────────────────────────────────────────────────────────────────────── + // Les deux notes de la configuration réelle du 2026-08-28 — MONTÉES, pas + // seulement calculées. + // + // `tiedByPriority` et `GroupedOrder.isContiguous` ont leurs tests de fonction dans + // `evcharger_config_test.dart`, et ils passaient déjà le 2026-08-27 : la contiguïté + // était calculée, exposée par le provider, et **affichée nulle part**. Un test de + // fonction ne pouvait pas le voir. Ceux-ci montent l'écran et cherchent le texte. + group('terrain 2026-08-28 — les notes s\'affichent VRAIMENT', () { + List> v2cConfig() => + (_fixture('loadconfig_hems75_v2c.json')['loadConfigs'] as List) + .cast>(); + + testWidgets('la note de rangs ex æquo est à l\'écran, et elle nomme les charges', + (tester) async { + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + final note = find.textContaining('Rangs à égalité'); + expect(note, findsOneWidget); + // Nommer les charges est le point : « des rangs sont à égalité » sans dire + // lesquelles laisse l'installateur chercher dans cinq cartes. + final texte = tester.widget(note).data!; + expect(texte, contains('chauffe-eau')); + expect(texte, contains('V2C Trydan')); + }); + + testWidgets('la note d\'ordre entrelacé est à l\'écran, et annonce l\'effet de l\'enregistrement', + (tester) async { + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + final note = find.textContaining('n\'est pas groupé par domaine'); + expect(note, findsOneWidget); + // Ce que l'enregistrement fera doit être dit AVANT, pas constaté après. + expect(tester.widget(note).data!, contains('REGROUPERA')); + }); + + testWidgets('un ordre déjà groupé et classé ne montre AUCUNE des deux notes', + (tester) async { + // Le contrôle négatif : les deux notes doivent disparaître d'elles-mêmes au + // premier vrai réglage, sinon elles deviennent du décor qu'on n'ouvre plus. + final config = LoadConfigProvider()..adoptForTest(_benchConfig()); + final telemetry = LoadTelemetryProvider() + ..adoptForTest({'degradedMode': false, 'loads': const []}); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + expect(find.textContaining('Rangs à égalité'), findsNothing); + expect(find.textContaining('n\'est pas groupé par domaine'), findsNothing); + }); + + testWidgets('THING_MISSING se lit « appareil absent », et n\'offre RIEN à lever', + (tester) async { + // ⚠️ Télémétrie COMPOSÉE ici, pas capturée : le banc publiait `WRITE_FAILED` sur + // cette charge au dernier relevé (l'adaptateur épuise son échelle d'écriture avant + // que `THING_MISSING` ne sorte). Ce test éprouve donc le RENDU du code, sur le + // `loadId` réel de la Terra AC supprimée — pas la présence du code sur la box. + const terra = '{88160e45-7df2-48cc-9639-f0d15ae6e382}'; + final config = LoadConfigProvider()..adoptForTest(v2cConfig()); + final telemetry = LoadTelemetryProvider()..adoptForTest({ + 'degradedMode': false, + 'loads': [ + { + 'loadId': terra, + 'allocatedW': 0, + 'available': false, + 'faultCode': 'THING_MISSING', + 'decision': {'code': 'NO_BUDGET', 'params': const {}}, + }, + ], + }); + + await tester.pumpWidget(_harness(config: config, telemetry: telemetry)); + await tester.pump(); + + // Le bouton ne peut pas tenir sa promesse — il n'existe pas. + expect(find.text('Lever le défaut'), findsNothing); + expect(find.textContaining('La levée est une action délibérée'), findsNothing); + // À la place : les deux gestes qui, eux, résolvent le cas. + expect(find.textContaining('Réinstallez-le dans nymea'), findsOneWidget); + // Et le libellé ne doit pas se lire « votre configuration est cassée ». + expect(find.textContaining('n\'existe plus dans nymea'), findsOneWidget); + }); + }); } extension on RpcSchema { diff --git a/tools/rpc/README.md b/tools/rpc/README.md index 331d093..1378cdd 100644 --- a/tools/rpc/README.md +++ b/tools/rpc/README.md @@ -23,6 +23,7 @@ dart tools/rpc/