etm-powersync-energy-plugin.../docs/INTERFACE_energyratios.md
Patrick Schurig 444b13dbf8 feat(ratios): NymeaEnergy.GetEnergyRatios + EnergyRatiosChanged (voie c)
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>
2026-07-01 07:56:35 +02:00

99 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).