etm-powersync-app/tools/rpc/ratios_check.dart
Patrick Schurig 763df68a2a feat(bilan): deux grandeurs, deux phrases — les ratios de la box à côté de la journée
Les tuiles disent la JOURNÉE, par différence de compteurs sur la fenêtre publiée.
La nouvelle ligne dit ce que la BOX publie, qui part de la baseline — et elle ne
s'affiche QUE lorsque cette origine diffère du début de journée.

Le cas est arrivé de lui-même : `nymead` redémarré à 09:23, sonde à 09:26, et la
box publiait 100 % d'autoconsommation. Vrai sur trois minutes ; absurde intitulé
« aujourd'hui ». La ligne le rend lisible — « Instantané depuis 09:23 » — au lieu
de le laisser passer pour la journée.

Vérifié avec le MODÈLE de l'app et non à l'œil sur le JSON : `ratios_check.dart`
importe `EnergyRatiosSnapshot` (idiome de `measurement_check.dart`) et rend
`baselineRepriseEnJournee: true`, ratios couvrant 3 minutes. Le banc est passé en
Europe/Paris entre-temps, et `periodStartedAt` suit sans que le code bouge —
c'est l'intérêt d'une frontière publiée.

Quand les deux coïncident, rien n'est ajouté : une phrase permanente cesse d'être
lue, comme un avertissement permanent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
2026-09-05 09:31:44 +02:00

50 lines
2.1 KiB
Dart

// Ce que l'APP conclut des deux bornes de `GetEnergyRatios` — pas ce que le JSON montre.
//
// ⚠️ `dart run` : cette sonde **importe le modèle de l'app** (`EnergyRatiosSnapshot`) plutôt
// que de relire la charge utile à sa façon. Une lecture parallèle ne prouverait rien sur ce
// que l'écran dira — c'est la leçon de `measurement_check.dart`.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:etm_powersync_app/models/energy_ratios_snapshot.dart';
Future<void> main(List<String> args) async {
final host = args.isNotEmpty ? args[0] : '192.168.1.75';
final ws = await WebSocket.connect('ws://$host:4444')
.timeout(const Duration(seconds: 8));
var id = 0;
final pending = <int, Completer<Map<String, dynamic>>>{};
ws.listen((raw) {
final m = jsonDecode(raw as String) as Map<String, dynamic>;
final rid = m['id'];
if (rid is int && pending.containsKey(rid)) pending.remove(rid)!.complete(m);
});
Future<Map<String, dynamic>> call(String m) {
final myId = ++id;
final c = Completer<Map<String, dynamic>>();
pending[myId] = c;
ws.add(jsonEncode({'id': myId, 'method': m, 'params': const {}}));
return c.future.timeout(const Duration(seconds: 15));
}
await call('JSONRPC.Hello');
final r = await call('NymeaEnergy.GetEnergyRatios');
final p = Map<String, dynamic>.from(r['params'] as Map);
print('brut : ${jsonEncode(p)}');
final s = EnergyRatiosSnapshot.fromJson(p);
String q(DateTime? d) => d == null ? '—' : d.toLocal().toString();
print('périodeDébut : ${q(s.periodStartedAt)} (frontière de journée → BILAN)');
print('mesuréDepuis : ${q(s.measuredSince)} (baseline → RATIOS)');
print('fuseau : ${s.timeZone}');
print('datePeriode : ${s.datePeriode}');
print('baselineRepriseEnJournee : ${s.baselineRepriseEnJournee}');
if (s.baselineRepriseEnJournee) {
final couvert = DateTime.now().difference(s.measuredSince!);
print(' -> les ratios ne couvrent que ${couvert.inMinutes} min, '
'pas la journée. C\'est CE que le drapeau rend lisible.');
}
await ws.close();
}