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