class EnergyData { final double pvPower; // Watts - production solaire final double homePower; // Watts - consommation maison final double batteryPower; // Watts - puissance batterie (+ charge, - décharge) final double gridPower; // Watts - réseau (+ injection, - soutif) final double batterySOC; // % - état de charge batterie final double temperature; // °C final String weatherIcon; // Journée final double dayProductionWh; final double daySelfConsumptionWh; final double dayGridInjectionWh; /// Compteurs CUMULÉS de la box (kWh), tels qu'elle les publie — pas des totaux du jour. /// /// `Energy.GetPowerBalance` rend `totalReturn` (exporté) et `totalAcquisition` /// (importé). Ils sont **cumulés depuis toujours**, pas remis à zéro chaque nuit : les /// afficher sous un libellé « aujourd'hui » serait faux. /// /// `NaN` signifie **valeur non exploitable**, et c'est un état à part entière : le banc /// `.75` publie des compteurs manifestement corrompus (7,5e+33 kWh importés) alors que /// `totalProduction` reste sain. L'app ne les corrige pas — elle refuse de les /// présenter comme une mesure (cf. [gridExportKwhOrNull]). final double gridExportKwh; final double gridImportKwh; final double selfConsumptionRate; // % final double autonomyRate; // % final double dayGains; // € // Borne de recharge final ChargingMode chargingMode; final double chargingPower; // kW final double solarSourcePercent; // % final bool gridLimitOk; const EnergyData({ this.pvPower = 0, this.homePower = 0, this.batteryPower = 0, this.gridPower = 0, this.batterySOC = 0, this.temperature = 0, this.weatherIcon = 'cloud_snow', this.dayProductionWh = 0, this.daySelfConsumptionWh = 0, this.dayGridInjectionWh = 0, this.gridExportKwh = 0, this.gridImportKwh = 0, this.selfConsumptionRate = 0, this.autonomyRate = 0, this.dayGains = 0, this.chargingMode = ChargingMode.pv, this.chargingPower = 3.6, this.solarSourcePercent = 82, this.gridLimitOk = true, }); EnergyData copyWith({ double? pvPower, double? homePower, double? batteryPower, double? gridPower, double? batterySOC, double? temperature, String? weatherIcon, double? dayProductionWh, double? daySelfConsumptionWh, double? dayGridInjectionWh, double? gridExportKwh, double? gridImportKwh, double? selfConsumptionRate, double? autonomyRate, double? dayGains, ChargingMode? chargingMode, double? chargingPower, double? solarSourcePercent, bool? gridLimitOk, }) { return EnergyData( pvPower: pvPower ?? this.pvPower, homePower: homePower ?? this.homePower, batteryPower: batteryPower ?? this.batteryPower, gridPower: gridPower ?? this.gridPower, batterySOC: batterySOC ?? this.batterySOC, temperature: temperature ?? this.temperature, weatherIcon: weatherIcon ?? this.weatherIcon, dayProductionWh: dayProductionWh ?? this.dayProductionWh, daySelfConsumptionWh: daySelfConsumptionWh ?? this.daySelfConsumptionWh, dayGridInjectionWh: dayGridInjectionWh ?? this.dayGridInjectionWh, gridExportKwh: gridExportKwh ?? this.gridExportKwh, gridImportKwh: gridImportKwh ?? this.gridImportKwh, selfConsumptionRate: selfConsumptionRate ?? this.selfConsumptionRate, autonomyRate: autonomyRate ?? this.autonomyRate, dayGains: dayGains ?? this.dayGains, chargingMode: chargingMode ?? this.chargingMode, chargingPower: chargingPower ?? this.chargingPower, solarSourcePercent: solarSourcePercent ?? this.solarSourcePercent, gridLimitOk: gridLimitOk ?? this.gridLimitOk, ); } /// Plafond de plausibilité pour un compteur d'énergie domestique cumulé, en kWh. /// /// 10 GWh, soit environ **trois mille ans** de consommation d'une maison. Aucune /// installation résidentielle ne l'atteindra ; tout ce qui le dépasse est un compteur /// cassé, pas un record. Le seuil est volontairement absurde-haut : il ne s'agit pas de /// juger une valeur, seulement d'attraper les corrompues. static const double kPlausibleMaxKwh = 1e7; /// Vrai si [v] peut être présenté comme une mesure. static bool isPlausibleKwh(double v) => v.isFinite && v >= 0 && v <= kPlausibleMaxKwh; /// Export cumulé, ou `null` si la box publie une valeur inexploitable. double? get gridExportKwhOrNull => isPlausibleKwh(gridExportKwh) ? gridExportKwh : null; /// Import cumulé, ou `null` si la box publie une valeur inexploitable. double? get gridImportKwhOrNull => isPlausibleKwh(gridImportKwh) ? gridImportKwh : null; } enum ChargingMode { pv, minPv, boost } class HistoryPoint { final DateTime time; final double pvWh; final double homeWh; final double gridWh; final double batteryWh; const HistoryPoint({ required this.time, this.pvWh = 0, this.homeWh = 0, this.gridWh = 0, this.batteryWh = 0, }); } // ── Entrée Energy.GetPowerBalanceLogs ───────────────────────────────────────── class PowerBalanceEntry { final DateTime timestamp; final double productionW; // puissance PV (valeur absolue, W) final double consumptionW; // consommation maison (W) final double acquisitionW; // import réseau (W, positif) final double storageW; // batterie : +charge / -décharge (W) final double totalProductionWh; // cumulé depuis l'origine (Wh) final double totalConsumptionWh; final double totalReturnWh; // injection réseau cumulée (Wh) final double totalAcquisitionWh; const PowerBalanceEntry({ required this.timestamp, this.productionW = 0, this.consumptionW = 0, this.acquisitionW = 0, this.storageW = 0, this.totalProductionWh = 0, this.totalConsumptionWh = 0, this.totalReturnWh = 0, this.totalAcquisitionWh = 0, }); }