etm-powersync-app/test/energy_ratios_test.dart
Patrick Schurig 3821bf3fdb refactor(energy): retire le calcul de ratios app-side, isole l'interim derrière EnergyRatiosInterim
- supprime selfRate/autoRate de _parsePowerBalance, le getter autoconsommationW, les .abs() interim
- ratios dérivés par Δ-de-cumuls (§2.1/§8.1) dans lib/services/energy_ratios.dart
- reseed jour-roulant & Δ non-monotone ; gardes : dén≤0→null, pas de NaN, clamp [0,100]
- selfConsumptionPower net-signé sans .abs() (§2.2) ; signes nymea bruts préservés (§7.1)
- seam swappable vers un state plugin en Phase 2 (une seule fonction)

Interim (Phase 1). Gestion du signe d'affichage renvoyée à la Phase 4 (§1.1).
Réf. UI_DATA_CONTRACT.md rev.5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 11:51:50 +02:00

91 lines
3.4 KiB
Dart

import 'package:etm_powersync_app/services/energy_ratios.dart';
import 'package:flutter_test/flutter_test.dart';
/// Couvre l'interim ratios (§2.1) : dérivation par Δ cumuls, reseed (jour /
/// non-monotone), garde-fous (n/a, pas de NaN, clamp), formule autoconso band.
void main() {
final d1 = DateTime(2026, 6, 28, 12);
final d1bis = DateTime(2026, 6, 28, 13);
final d2 = DateTime(2026, 6, 29, 9);
test('1er échantillon : Δ=0 → n/a (null), jamais NaN', () {
final s = EnergyRatiosInterim();
final r = s.compute(
totalProduction: 10,
totalReturn: 2,
totalConsumption: 8,
totalAcquisition: 3,
now: d1);
expect(r.autoconsommation, isNull);
expect(r.autonomie, isNull);
});
test('Δ cumuls même jour → ratios déterministes, bornés [0,100]', () {
final s = EnergyRatiosInterim();
s.compute(
totalProduction: 10,
totalReturn: 2,
totalConsumption: 8,
totalAcquisition: 3,
now: d1); // baseline
final r = s.compute(
totalProduction: 20, // Δprod=10
totalReturn: 4, // Δret=2 → autoconso=(10-2)/10=80
totalConsumption: 18, // Δcons=10
totalAcquisition: 8, // Δacq=5 → autonomie=(10-5)/10=50
now: d1bis);
expect(r.autoconsommation, closeTo(80, 0.001));
expect(r.autonomie, closeTo(50, 0.001));
});
test('nouveau jour local → reseed baseline (Δ repart de 0)', () {
final s = EnergyRatiosInterim();
s.compute(
totalProduction: 10, totalReturn: 2, totalConsumption: 8,
totalAcquisition: 3, now: d1);
final r = s.compute(
totalProduction: 99, totalReturn: 9, totalConsumption: 99,
totalAcquisition: 9, now: d2); // jour différent → reseed → Δ=0
expect(r.autoconsommation, isNull);
expect(r.autonomie, isNull);
});
test('compteur non monotone (Δ<0) → reseed, jamais de ratio négatif', () {
final s = EnergyRatiosInterim();
s.compute(
totalProduction: 100, totalReturn: 10, totalConsumption: 80,
totalAcquisition: 5, now: d1); // baseline haute
// restart nymead : cumuls repartent bas → non monotone → reseed
final r = s.compute(
totalProduction: 5, totalReturn: 1, totalConsumption: 4,
totalAcquisition: 1, now: d1bis);
expect(r.autoconsommation, anyOf(isNull, greaterThanOrEqualTo(0)));
expect(r.autonomie, anyOf(isNull, greaterThanOrEqualTo(0)));
});
test('dénominateur nul (nuit sans prod) → n/a, pas de NaN', () {
final s = EnergyRatiosInterim();
s.compute(
totalProduction: 10, totalReturn: 2, totalConsumption: 8,
totalAcquisition: 3, now: d1);
final r = s.compute(
totalProduction: 10, // Δprod=0 → autoconso n/a
totalReturn: 2,
totalConsumption: 8, // Δcons=0 → autonomie n/a
totalAcquisition: 3,
now: d1bis);
expect(r.autoconsommation, isNull);
expect(r.autonomie, isNull);
});
test('selfConsumptionPower (§2.2) : net-signé, sans abs', () {
// export (acq<0) retranché de la production
expect(EnergyRatiosInterim.selfConsumptionPower(4869, -231),
closeTo(4638, 0.001));
// import (acq>0) → tout est autoconsommé
expect(EnergyRatiosInterim.selfConsumptionPower(2000, 500), 2000);
// export > production → borné à 0 (pas de négatif)
expect(EnergyRatiosInterim.selfConsumptionPower(100, -300), 0);
});
}