fix(écran): trois défauts trouvés sur appareil, dont un de forme et un de moi
Passage sur le Redmi contre `.75`. Aucun des trois n'était visible en test. 1. LA PRODUCTION S'AFFICHAIT NÉGATIVE — « -8.9 kW · Solaire » sous 9 kW de soleil. `currentPowerProduction` est publié négatif par nymea et le service garde les signes bruts par politique ; la carte imprimait la valeur telle quelle. Ce qui rend le défaut net plutôt qu'opinable : la même carte normalisait DÉJÀ le réseau et la batterie par `.abs()` — la branche solaire était la seule oubliée, donc un oubli et non une convention. 2. LE LIBELLÉ SE COUPAIT AU MILIEU DU MOT — « Autoconsommat / ion », alors que le commentaire du code disait vouloir l'éviter. La cause n'est pas la longueur mais l'ÉCHELLE du texte : police système au-dessus de 100 %, et `maxLines: 2` ne protège de rien puisque Flutter coupe un mot long sans espace, sans même afficher l'ellipse. `FittedBox(scaleDown)` garde le mot entier et ne fait rien quand la place suffit. 3. MA LIGNE DES RATIOS RÉPÉTAIT LES TUILES — « Instantané depuis 09:23 — autoconsommation 52 %, autonomie 54 % » sous des tuiles affichant 52 % et 54 %. Les deux chemins étaient corrects et les valeurs coïncidaient vraiment (bilan 51,8/54,1 sur 00:00→16:45, box 51,9/53,7 depuis 09:23). Rien de faux, et illisible quand même : je déclenchais sur l'ORIGINE, alors qu'un lecteur compare des NOMBRES. La ligne sort désormais quand l'origine diffère ET que l'écart dépasse 2 points ; le relevé du jour est devenu un test. Vérifié à l'écran après correction : le libellé tient sur une ligne, la ligne des ratios se tait. Le signe, lui, N'A PAS pu être revu — la production du banc était retombée à 0 W à 17:03, et `abs(0) == 0`. Une correction qu'on ne peut pas voir demande une garde qui se voit : `flux_signe_test.dart` la pose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
This commit is contained in:
parent
763df68a2a
commit
b3000ca2d5
@ -346,7 +346,13 @@ class _NodeLayerState extends State<_NodeLayer>
|
||||
icon: Icons.wb_sunny_rounded,
|
||||
color: EtmTokens.solarOf(context),
|
||||
label: 'Solaire',
|
||||
value: '${(d.pvPower / 1000).toStringAsFixed(1)} kW',
|
||||
// ⚠️ `currentPowerProduction` est publié NÉGATIF par nymea (-8 980 W =
|
||||
// 8,98 kW produits), et le service garde les signes bruts par politique.
|
||||
// L'AFFICHAGE, lui, doit dire ce qu'un client comprend : une production est
|
||||
// une quantité positive. Vu sur appareil le 2026-09-05, « -8.9 kW Solaire »
|
||||
// sous un soleil de 9 kW — la carte normalisait déjà le réseau et la
|
||||
// batterie par `.abs()`, la branche solaire était la seule oubliée.
|
||||
value: '${(d.pvPower.abs() / 1000).toStringAsFixed(1)} kW',
|
||||
tappable: true,
|
||||
),
|
||||
),
|
||||
|
||||
@ -25,6 +25,34 @@ import '../painters/ring_painter.dart';
|
||||
/// corrompus, ou compteur en recul. Les tuiles affichent alors « — » et le disent. Elles
|
||||
/// n'affichent **jamais zéro** : « rien produit » et « on ne sait pas » sont deux états
|
||||
/// différents, et à 3 h du matin le premier est vrai sans que le second le soit.
|
||||
/// **La ligne ne s'affiche que si elle DIT quelque chose.**
|
||||
///
|
||||
/// Première version : elle sortait dès que l'origine différait du début de journée. Vu sur
|
||||
/// appareil le 2026-09-05, ça donnait ceci — tuiles **52 % / 54 %**, et juste dessous
|
||||
/// « Instantané depuis 09:23 — autoconsommation **52 %**, autonomie **54 %** ». Les mêmes
|
||||
/// deux nombres, écrits deux fois.
|
||||
///
|
||||
/// Les valeurs coïncidaient pour de bon : bilan sur 00:00→16:45 = 51,8 / 54,1, box depuis
|
||||
/// 09:23 = 51,9 / 53,7. Rien n'était faux — et la ligne était quand même illisible, parce
|
||||
/// qu'**un lecteur compare des nombres, pas des origines**. Devant deux fois « 52 % », il
|
||||
/// doit raisonner pour découvrir que les fenêtres diffèrent : exactement ce que la ligne
|
||||
/// existait pour lui épargner.
|
||||
///
|
||||
/// Elle sort donc quand l'origine diffère **et** que l'écart se voit. Sous le seuil, les
|
||||
/// tuiles suffisent — et une ligne permanente cesse d'être lue, comme un avertissement
|
||||
/// permanent.
|
||||
///
|
||||
/// Sans bilan, elle sort quand même : c'est alors le seul chiffre disponible.
|
||||
bool _ligneUtile(EnergyRatiosSnapshot? r, DailyBalance? b) {
|
||||
if (r == null || !r.baselineRepriseEnJournee) return false;
|
||||
if (b == null) return true;
|
||||
double ecart(double? x, double? y) =>
|
||||
(x == null || y == null) ? double.infinity : (x - y).abs();
|
||||
const seuil = 2.0; // points de pourcentage — sous ça, l'arrondi affiché est identique
|
||||
return ecart(r.selfConsumptionRate, b.selfConsumptionRate) >= seuil ||
|
||||
ecart(r.autonomyRate, b.autonomyRate) >= seuil;
|
||||
}
|
||||
|
||||
/// La phrase des ratios de la box — **avec leur origine, puisqu'elle diffère**.
|
||||
///
|
||||
/// Appelée seulement quand la baseline a été reposée après le début de journée : sans ça,
|
||||
@ -116,7 +144,7 @@ class KpiRow extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (ratios?.baselineRepriseEnJournee ?? false) ...[
|
||||
if (_ligneUtile(ratios, b)) ...[
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 2),
|
||||
@ -291,7 +319,20 @@ class _RingTileState extends State<_RingTile>
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
// ⚠️ **La coupure se fait AU MILIEU du mot quand le texte est agrandi.**
|
||||
// Vu sur appareil le 2026-09-05, réglage de police système au-dessus de
|
||||
// 100 % : « Autoconsommat / ion ». `maxLines: 2` ne protège de rien —
|
||||
// Flutter coupe un mot long sans espace plutôt que de déborder, et
|
||||
// l'ellipse ne s'affiche même pas. Le défaut n'est donc pas la longueur du
|
||||
// libellé mais la mise à l'échelle, et il reviendra sur n'importe quel mot
|
||||
// à la première langue plus verbeuse.
|
||||
//
|
||||
// `scaleDown` garde le mot ENTIER en réduisant ce qu'il faut, et ne touche
|
||||
// à rien quand la place suffit — donc aucun effet à échelle normale.
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.value == null
|
||||
? '${widget.label} · pas mesurable'
|
||||
: widget.label,
|
||||
@ -299,10 +340,9 @@ class _RingTileState extends State<_RingTile>
|
||||
size: 11,
|
||||
color: EtmTokens.mutedOf(context),
|
||||
),
|
||||
// Deux lignes plutôt qu'un mot coupé : « Autoconsomm… » ne se lit pas,
|
||||
// et la tuile a la hauteur pour le porter entier.
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
48
test/flux_signe_test.dart
Normal file
48
test/flux_signe_test.dart
Normal file
@ -0,0 +1,48 @@
|
||||
/// **Une production ne s'affiche pas en négatif.**
|
||||
///
|
||||
/// Vu sur appareil le 2026-09-05 à 16:52 : « **-8.9 kW · Solaire** » sous un soleil de
|
||||
/// 9 kW. `Energy.GetPowerBalance` publiait `currentPowerProduction: -8980.4` — nymea donne
|
||||
/// la production en négatif, et `NymeaService` garde les signes bruts par politique (§7.1).
|
||||
/// La carte imprimait donc la valeur telle quelle.
|
||||
///
|
||||
/// Ce qui rend le défaut net plutôt qu'opinable : **la même carte normalisait déjà** le
|
||||
/// réseau et la batterie par `.abs()`. La branche solaire était la seule oubliée — donc ce
|
||||
/// n'était pas une convention d'affichage assumée, c'était un oubli.
|
||||
///
|
||||
/// Ce test existe parce que l'écran ne pouvait plus le montrer : à 17:03 la production du
|
||||
/// banc était retombée à 0 W, et `abs(0) == 0`. Une correction qu'on ne peut pas voir
|
||||
/// demande une garde qui, elle, se voit.
|
||||
library;
|
||||
|
||||
import 'package:etm_powersync_app/features/dashboard/widgets/energy_flow_card.dart';
|
||||
import 'package:etm_powersync_app/models/energy_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Future<void> _poser(WidgetTester tester, EnergyData d) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1000, 1600));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(home: Scaffold(body: EnergyFlowCard(data: d))));
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('la production publiée NÉGATIVE s\'affiche positive', (tester) async {
|
||||
// La charge utile réelle du 2026-09-05 16:52.
|
||||
await _poser(tester, const EnergyData(pvPower: -8980.4, homePower: 3249.4));
|
||||
|
||||
// -8 980,4 W -> 8,98 kW -> « 9.0 kW » à une décimale. (Sur appareil la valeur de
|
||||
// l'instant donnait « -8.9 kW » ; c'est le SIGNE qui est en cause, pas l'arrondi.)
|
||||
expect(find.text('9.0 kW'), findsOneWidget);
|
||||
expect(find.text('-9.0 kW'), findsNothing,
|
||||
reason: 'un client lirait « mes panneaux consomment 9 kW »');
|
||||
});
|
||||
|
||||
testWidgets('zéro reste zéro — et ne prouve rien', (tester) async {
|
||||
// L'état du banc à 17:03, celui qui a rendu la correction invisible à l'écran.
|
||||
await _poser(tester,
|
||||
const EnergyData(pvPower: 0, homePower: 3250, gridPower: 3250));
|
||||
expect(find.text('0.0 kW'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@ -82,6 +82,47 @@ void main() {
|
||||
expect(find.textContaining('Instantané depuis'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('les deux coïncident : la ligne se TAIT — vu sur appareil', (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1000, 2000));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
// Relevé du 2026-09-05 16:52 : bilan 51,8 / 54,1 sur 00:00→16:45, box 51,9 / 53,7
|
||||
// depuis 09:23. L'origine diffère bien, mais les nombres non — et la ligne écrivait
|
||||
// « 52 %, 54 % » juste sous des tuiles qui affichaient déjà 52 % et 54 %.
|
||||
final coincide = EnergyRatiosSnapshot.fromJson({
|
||||
'autonomyRate': 53.67,
|
||||
'selfConsumptionRate': 51.92,
|
||||
'periodStartedAt': 1788559200,
|
||||
'measuredSince': 1788592995,
|
||||
'timeZone': 'Europe/Paris',
|
||||
});
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: KpiRow(
|
||||
data: const EnergyData(),
|
||||
// 8 kWh produits / 1 exporté -> 87,5 % ; 12 consommés / 5 importés -> 58,3 %.
|
||||
// On force ici un bilan proche des valeurs de la box pour éprouver le seuil.
|
||||
balance: DailyBalance(
|
||||
from: DateTime(2026, 9, 5, 0, 15),
|
||||
to: DateTime(2026, 9, 5, 16, 45),
|
||||
periodStart: DateTime(2026, 9, 5),
|
||||
sampleCount: 72,
|
||||
productionKwh: 59.89,
|
||||
consumptionKwh: 57.31,
|
||||
importKwh: 26.31,
|
||||
exportKwh: 28.89,
|
||||
),
|
||||
ratios: coincide,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Instantané depuis'), findsNothing,
|
||||
reason: 'répéter les mêmes nombres oblige le lecteur à raisonner pour '
|
||||
'découvrir que les fenêtres diffèrent');
|
||||
});
|
||||
|
||||
testWidgets('box antérieure : aucune ligne, aucune supposition', (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1000, 2000));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user