Expose autoconsommation/autonomie comme source canonique de l'energymanager,
pour remplacer le seam interim app-side (EnergyRatiosInterim.compute).
Voie (c) : méthode + notification sur le handler NymeaEnergy de NOTRE plugin
(déjà forké/buildé), calculées depuis EnergyManager::totalX() — interface
abstraite stable. PAS de champs sur Energy.PowerBalance → aucun fork du coeur
nymea (nymea-experience-plugin-energy) à rebaser perpétuellement.
- EnergyRatiosCalculator (GPL pur mesure) : Δ de cumuls, baseline minuit local,
reseed (1er appel / nouveau jour / non-monotone Δ<0), den<=0 -> n/a, clamp
[0,100]. Porté 1:1 du seam interim app.
- NymeaEnergy.GetEnergyRatios -> {o:selfConsumptionRate, o:autonomyRate} Double,
champ OMIS si n/a (jamais null).
- NymeaEnergy.EnergyRatiosChanged : meme forme, branchee sur powerBalanceChanged(),
emise uniquement quand une valeur (ou sa disponibilite) change.
- testEnergyRatiosAlignment : vecteurs joues 1:1 contre l'interim (seed, normal,
clamp-bas, den<=0->n/a, non-monotone, nouveau jour local).
- docs/INTERFACE_energyratios.md : contrat pour l'agent app (RPC a consommer).
Build prod 0/0. Suite simulation 25/0 (testLoadConfigRpc traverse le handler
modifie -> pas de regression).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
4.1 KiB
Markdown
99 lines
4.1 KiB
Markdown
# Contrat — Ratios énergétiques canoniques (`NymeaEnergy.GetEnergyRatios`)
|
||
|
||
> **Statut :** Phase 2, voie (c). Source canonique côté energymanager.
|
||
> Remplace le seam interim app-side `EnergyRatiosInterim.compute()`.
|
||
> Exposé par `powersync-energy-plugin-nymea` (experience-plugin, namespace
|
||
> JSON-RPC `NymeaEnergy`) — **pas** sur `Energy.PowerBalance` (voir §5).
|
||
|
||
## 1. Pourquoi (c) et pas des champs sur `PowerBalance`
|
||
|
||
`PowerBalance` (namespace `Energy`) appartient à `nymea-experience-plugin-energy`,
|
||
un plugin **cœur** nymea. Y ajouter des champs imposerait de forker et rebaser ce
|
||
plugin à chaque montée de version nymea — coût d'infra perpétuel. La voie (c)
|
||
expose les ratios sur le handler `NymeaEnergy` de **notre** plugin (déjà forké,
|
||
déjà buildé), calculés à partir des **mêmes cumuls canoniques** de l'energymanager.
|
||
Valeurs strictement identiques à ce que (a) aurait produit ; seul le canal diffère.
|
||
|
||
## 2. Sémantique
|
||
|
||
Ratios dérivés par **Δ de cumuls** sur la **journée locale courante** (depuis
|
||
minuit local), pas par intégration du signal live → déterministe, pas de drift.
|
||
|
||
```
|
||
autonomie = (Δ totalConsumption − Δ totalAcquisition) / Δ totalConsumption
|
||
autoconsommation = (Δ totalProduction − Δ totalReturn) / Δ totalProduction
|
||
```
|
||
|
||
- Unité : **pourcent**, borné `[0, 100]`.
|
||
- `Δ X = X_courant − X_baseline`, baseline = cumuls au début de la période.
|
||
- **Reseed de la baseline** si : 1er calcul, **nouveau jour local**, OU compteur
|
||
**non monotone** (`Δ<0` : restart nymead / re-add thing / rollover).
|
||
- **Dénominateur ≤ 0** (nuit sans prod, etc.) → ratio **n/a** : le champ est
|
||
**OMIS** de la réponse. **Jamais `null`, jamais `NaN`.**
|
||
|
||
C'est le portage 1:1 de `EnergyRatiosInterim.compute()` — l'app peut supprimer
|
||
ce calcul et lire la source.
|
||
|
||
## 3. Méthode : `NymeaEnergy.GetEnergyRatios`
|
||
|
||
**Params :** aucun.
|
||
|
||
**Returns :**
|
||
|
||
| Champ | Type | Présence |
|
||
|---|---|---|
|
||
| `selfConsumptionRate` | `Double` (%) | **optionnel** — omis si n/a |
|
||
| `autonomyRate` | `Double` (%) | **optionnel** — omis si n/a |
|
||
|
||
Exemple (les deux disponibles) :
|
||
```json
|
||
{"id": 1, "method": "NymeaEnergy.GetEnergyRatios", "params": {}}
|
||
```
|
||
```json
|
||
{"id": 1, "status": "success",
|
||
"params": {"selfConsumptionRate": 66.6667, "autonomyRate": 50.0}}
|
||
```
|
||
|
||
Exemple nuit (autoconso n/a, le champ disparaît) :
|
||
```json
|
||
{"id": 1, "status": "success", "params": {"autonomyRate": 50.0}}
|
||
```
|
||
|
||
> ⚠️ Champ absent = **n/a**. Ne pas confondre avec 0 %. Mapper l'absence sur
|
||
> ton `double?` (Dart `null`).
|
||
|
||
## 4. Notification : `NymeaEnergy.EnergyRatiosChanged`
|
||
|
||
Mêmes champs que `GetEnergyRatios`. Émise sur `powerBalanceChanged()` de
|
||
l'energymanager, **uniquement quand une valeur (ou sa disponibilité) change**.
|
||
|
||
```json
|
||
{"notification": "NymeaEnergy.EnergyRatiosChanged",
|
||
"params": {"selfConsumptionRate": 80.0, "autonomyRate": 50.0}}
|
||
```
|
||
|
||
S'abonner via `JSONRPC.SetNotificationStatus` avec le namespace `NymeaEnergy`
|
||
(le même que `LoadConfigChanged`, `ChargingSchedulesChanged`, etc. — donc même
|
||
session, pas de nouvelle connexion).
|
||
|
||
## 5. Migration côté app
|
||
|
||
1. Au boot : `NymeaEnergy.GetEnergyRatios` pour l'état initial.
|
||
2. S'abonner à `NymeaEnergy.EnergyRatiosChanged` pour les MAJ.
|
||
3. **Supprimer** `EnergyRatiosInterim.compute()` et son état de baseline
|
||
(`_baseProduction`/`_baseReturn`/…/`_baseDay`) : la baseline/reseed/minuit-local
|
||
vivent désormais côté plugin.
|
||
4. Mapper champ-absent → `null` (n/a). Conserver `EnergyRatios{autoconsommation,
|
||
autonomie}` côté app ; seul le **producteur** change.
|
||
|
||
> Si nymea expose un jour `selfConsumptionRate`/`autonomyRate` directement sur
|
||
> `PowerBalance` (feature request upstream ouverte séparément), la migration
|
||
> app sera triviale : lire les champs `PowerBalance` au lieu d'appeler
|
||
> `GetEnergyRatios`. Jamais bloqué, jamais de fork du cœur à porter.
|
||
|
||
## 6. Frontière GPL
|
||
|
||
Pur mesure (Δ de compteurs cumulés). Aucune logique d'optimisation / prévision /
|
||
pondération — celle-ci reste dans l'optimiseur propriétaire. Implémenté dans
|
||
`energyplugin/etm/ratios/energyratioscalculator.{h,cpp}` (GPL-3.0-or-later).
|