From f755c727ee58d0fc87500c078390191632357ba6 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sun, 28 Jun 2026 12:05:46 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20contrat=20de=20donn=C3=A9es=20UI=20rev.?= =?UTF-8?q?5=20=E2=80=94=20r=C3=A9serve=20d=C3=A9cision-2=20lev=C3=A9e=20(?= =?UTF-8?q?cumuls=20s=C3=A9par=C3=A9s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intègre la vérif terrain (sonde .75) : GetPowerBalanceLogs sépare import/export en cumulé (totalAcquisition / totalReturn) → autoconso/autonomie dérivables des logs, la réserve de la décision-2 est levée. Aligne le doc tracké sur le code (seam EnergyRatiosInterim, commit 3821bf3). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/UI_data_contract.md | 101 ++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/docs/UI_data_contract.md b/docs/UI_data_contract.md index 84ef878..f8ad390 100644 --- a/docs/UI_data_contract.md +++ b/docs/UI_data_contract.md @@ -2,6 +2,10 @@ > Compagnon de `DASHBOARD_SPEC.md`. Tranche la question récurrente > « **qui calcule les valeurs affichées ?** » pour chaque écran. +> Rev. 5 — sonde `.75` : réserve décision-2 **levée** (cumuls séparés) ; ajout de +> 3 garde-fous : 2 conventions de signe (§7.1), buckets par Δ-de-cumuls (§7.3), +> gestion reset compteurs (§2.1/§8.1) ; RP calquées sur le template nymea, Influx +> v1.6.7 confirmée. > Rev. 4 — §8.3 corrigé : **réutiliser l'InfluxDB nymea** (pas SQLite), DB ETM > dédié + rollups natifs (retention policies) + checkpoint local pour le contrôle > vivant. Alerte : tag Docker `latest` → InfluxDB 3 Core (72 h de rétention). @@ -113,14 +117,16 @@ autonomie = (consommation − import) / consommation autoconsommation = (production − export) / production ``` -- **autonomie** : dérivable directement, `import` est dans les logs. ✅ -- **autoconsommation** : exige **l'export (return) comme cumul DISTINCT**. - ⚠️ **Point bloquant à vérifier sur la sonde** : `acquisition` est-il *net - signé* (import − export en un nombre) ou import/export **séparés** ? - - séparés → dérivable depuis logs ; - - net → **non récupérable** des logs ⇒ l'energymanager doit **intégrer - import et export lui-même** (il voit la P réseau signée en continu, + - connaissance Invariant 3). C'est ce qui rend #2 *architecturalement obligé*. +- **autonomie** : `import` = Δ `totalAcquisition`. ✅ +- **autoconsommation** : `export` = Δ `totalReturn`. ✅ + **RÉSERVE LEVÉE (sonde `.75`)** : le réseau *instantané* est net-signé (un seul + champ `acquisition`), MAIS les **cumuls `totalAcquisition` / `totalReturn` sont + séparés**. Donc les deux ratios sont dérivables des logs par **deltas de cumuls** + — pas besoin que le plugin intègre import/export pour l'historique. +- ⚠️ **Compteurs = monotones requis.** Δ d'un cumul n'est valide que si le compteur + ne reset jamais. Sur reset (restart nymead, re-add thing, rollover) → Δ < 0 : + détecter (`Δ < 0` ⇒ reset, reseed sur la nouvelle valeur, ne pas créer un bucket + négatif). À gérer côté plugin **et** dans les CQ de rollup. - Ces formules sont **correctes avec batterie** : import/export au compteur réseau nettent déjà les flux batterie ; PV→batterie→maison compte comme autoconsommé (pas exporté). Pas de correction round-trip. @@ -216,9 +222,19 @@ puissance que s'il est un *thing avec un state `currentPower`* (interfaces `smartmeter`/`energymeter`/consumer/producer, `logged: true`). → **La granularité d'optimisation Héos = la granularité de comptage.** -⚠️ **Convention de signe nymea** : `currentPower` est toujours du point de vue -**consommateur** → **négatif pour un producteur** (le PV a un `currentPower` < 0). -Signe canonique, à ne jamais « deviner » côté app (cause du bug autoconso / `.abs()`). +⚠️ **DEUX conventions de signe coexistent — ne pas les confondre** (piège « fix » +d'un faux bug) : +1. **Thing-level `currentPower`** : point de vue **consommateur** → **négatif pour + un producteur** (le PV a `currentPower` < 0). C'est la cause du bug autoconso / + `.abs()`. +2. **Power balance agrégé** (`production` / `consumption` / `acquisition`) : + `production` **positif**, `consumption` positif, `acquisition` **net signé** + (+ import / − export). (Sonde `.75` : `acquisition = -231`, et un + `currentPowerProduction` **positif** au niveau balance — normal, ce n'est PAS + le `currentPower` thing-level.) + +Le signe est **canonique des deux côtés** — l'app ne le devine jamais ; elle ne +dérive que le *sens* d'un flux à partir du signe (§1.1), rien d'autre. ### 7.2 Carte de comptage (état du parc) @@ -249,9 +265,16 @@ Signe canonique, à ne jamais « deviner » côté app (cause du bug autoconso / - **energymanager = série historique canonique** par consommateur/générateur, échantillonnée à la résolution du MPC (buckets 15 min), Invariant-3-correcte. - **logs nymea = filet / contrôle croisé**, pas l'entrée primaire de Héos. -- Justification : la Route A (logs nymea) impose le downsampling/rétention de - nymea sans contrôle du schéma, et ne sépare pas import/export. Seul le plugin - peut scinder import/export depuis la P réseau signée et figer la décomposition. +- **Construire les buckets par Δ de compteurs, pas par intégration du signal live** + (sonde `.75` : les cumuls sont séparés) : + - grid import/export du bucket = Δ `totalAcquisition` / Δ `totalReturn` ; + - conso par appareil = Δ `thing.totalEnergyConsumed` ; + - exact, pas de drift d'intégration. L'intégration du signal net-signé ne sert + qu'au temps réel sous le bucket (affichage), pas à la série stockée. + - ⚠️ même gestion de reset qu'en §2.1/§8.1 (Δ < 0 ⇒ reset). +- Justification Route B : la Route A (logs nymea bruts) impose le downsampling/ + rétention de nymea sans contrôle du schéma ; le plugin reste seul à figer la + décomposition Invariant-3 dans la série curée. --- @@ -263,6 +286,9 @@ Signe canonique, à ne jamais « deviner » côté app (cause du bug autoconso / - On stocke le **brut intégrable** par bucket : production, consommation, import, export (séparés !), + par charge pilotée, + charge/décharge batterie. + → obtenu par **Δ de cumuls** (`totalProduction`, `totalReturn`, + `totalConsumption`, `totalAcquisition`, `thing.totalEnergyConsumed`), pas par + intégration du signal live (§7.3). **Gérer le reset** (Δ < 0 ⇒ reseed). - Les **ratios ne sont JAMAIS stockés** comme donnée primaire — vues dérivées : ``` @@ -296,15 +322,18 @@ ses **retention policies + downsampling (CQ 1.x / tasks 2.x)** font les rollups **corrige au passage le « shard accumulation »** qui faisait crash-looper l'instance (les shards s'empilaient faute de purge). -- **DB / bucket ETM dédié** — pas dans les measurements nymea. Isole ta donnée des - upgrades/migrations nymea et te donne ta propre rétention. -- **Rollups par paliers** (retention policies dédiées) : - - 15 min → ~30 jours · horaire → ~2 ans · journalier → indéfini -- ⚠️ **Pinner la version Influx.** Le tag Docker `latest` pointe désormais vers - **InfluxDB 3 Core**, qui en OSS impose **72 h de rétention** + limite 5 DB → - destruction silencieuse de l'historique. Le diagnostic « shard accumulation » - = TSM = on est en **1.x / 2.x** (bonne version). Ne jamais laisser un upgrade - auto basculer en 3 Core. +- **DB / bucket ETM dédié** — pas dans la base `nymeatest` de nymea. Isole ta + donnée des upgrades/migrations nymea et te donne ta propre rétention. +- **Calquer les paliers sur le template RP nymea existant** (sonde `.75`) plutôt + que d'inventer : `live 24h · minutes 7j · hours 3 ans · days 20 ans` + (+ `discrete 1 an`). Cohérence opérationnelle, CQ alignées. +- ⚠️ **Ne pas écrire dans `autogen` (DEFAULT, rétention ∞).** C'est le vrai risque + de croissance (pas un shard runaway : `nymeatest` n'a que 2 shards aujourd'hui). + Router explicitement les writes ETM vers les RP tiers. +- ⚠️ **Version Influx** : `.75` est en **v1.6.7** ✓ (pas 3 Core, pas de piège + 72 h). Le tag Docker `latest` pointe désormais vers **InfluxDB 3 Core** + (rétention 72 h + limite 5 DB en OSS) → **pinner**, ne jamais laisser un upgrade + auto basculer. - **Checkpoint local minuscule** (fichier / petit SQLite) pour l'**état de contrôle vivant** (plan en cours d'exécution, dernier setpoint). Si Influx tombe, Héos ne doit **ni hard-fail ni perdre le plan exécuté** — dégradé propre, cohérent avec @@ -334,9 +363,9 @@ du « Plan de la journée »). (condition : animation respecte les sens figés §1.1, jamais de particule à contre-sens à travers Héos). ✅ tranché. 2. Source des ratios : **state du plugin energymanager** (ni app, ni Energy - experience). Court terme : dérivation depuis cumuls via §2.1, **sous réserve** - que l'export soit loggé séparément — sinon le plugin intègre import/export - lui-même. ✅ tranché. + experience). Dérivation depuis cumuls via §2.1. **Réserve levée** (sonde `.75` : + `totalAcquisition`/`totalReturn` séparés) — dérivable des logs, le plugin n'a + pas à intégrer pour l'historique. ✅ tranché. 3. Périmètre Things app : **hand-off nymea-app + MVP read-only client** ; **ModbusRTU master en premier** (§6, bloquant pour SDM/abbterra). Wizard ciblé seulement si vraiment nécessaire. ✅ tranché. @@ -349,10 +378,18 @@ du « Plan de la journée »). dédié, rollups via retention policies) + **checkpoint local** pour l'état de contrôle vivant. ⚠️ pinner Influx en **1.x/2.x** (pas 3 Core / 72 h). ✅ tranché. -### Reste ouvert (à lever avant code §2 / §8) +### Vérifié sur sonde (`.75`) -- `acquisition` dans `GetPowerBalanceLogs` = **net signé** ou **import/export - séparés** ? Détermine si l'autoconso est dérivable des logs ou doit être - intégrée par le plugin (§2.1 / §8.1). -- **Carte de comptage §7.2** : décider quels consommateurs non mesurés (PAC, - clim) justifient un compteur matériel pour une optim fine. +- ✅ **PowerBalance** : instantané net-signé (`acquisition`), cumuls + `totalAcquisition`/`totalReturn` **séparés** → ratios dérivables par Δ (§2.1). +- ✅ **Influx v1.6.7** (pas 3 Core). DB nymea = `nymeatest`, RP existantes + (`live/minutes/hours/days/discrete`), DEFAULT = `autogen` ∞ (le vrai risque). + +### Reste ouvert (vrai) + +- **Confirmer les signes sur box réelle** (`.120`) — `.75` est statique/factice + (totaux à 0). L'archi plugin=source rend l'app sign-agnostic, mais valider. +- **Carte de comptage §7.2** : décider produit par produit quels consommateurs + non mesurés (PAC, clim) justifient un compteur matériel pour une optim fine. +- **Gestion reset des compteurs** (§2.1/§7.3/§8.1) : implémenter la détection + Δ < 0 côté plugin **et** dans les CQ de rollup.