etm-powersync-app/docs/DASHBOARD_SPEC.md
Patrick Schurig 0ebbde49da chore(docs): range specs/contrats/refs dans docs/, track l'autorité
- specs -> docs/ (DASHBOARD_SPEC compagnon du contrat)
- ref introspection nymea-jsonRPC -> docs/reference/
- mockups HTML -> docs/mockups/ ; briefs consommés -> docs/archive/
- track AGENTS.md + INTERFACE_etmvariableload.md (étaient untracked)
- INTERFACE_*: marqué miroir, canonique = repo plugin
2026-06-28 12:49:57 +02:00

253 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Dashboard ETM-PowerSync — Brief d'implémentation Flutter
> Spec pour Claude Code. Objectif : recréer l'écran **Dashboard** de l'app `app.etm-powersync.fr` (Flutter), tel que validé en maquette. Les fichiers HTML de référence (`etm-powersync-dashboard-v4.html`, `etm-powersync-flux-drilldown.html`) sont la **source de vérité visuelle** — ce document en est la transcription technique.
---
## 1. Contexte
ETM-PowerSync est un HEMS (Home Energy Management System). Le cœur intelligent s'appelle **Héos** (optimiseur prédictif MILP, horizon 2448 h). Le Dashboard doit faire deux choses : montrer l'état énergétique **en direct**, et rendre **lisible et explicable** ce que Héos décide — c'est le différenciateur produit. Public : clients résidentiels (langage clair, zéro jargon) + installateurs.
---
## 2. Principes directeurs (ne pas dévier)
1. **Chaque information à un seul endroit.** Pas de doublon entre les blocs. L'autoconsommation est en KPI (haut) → elle n'apparaît nulle part ailleurs sur le Dashboard.
2. **Lisibilité avant densité.** Lecture rapide en haut, détail optionnel en bas. Le client pressé s'arrête à la décision Héos ; le curieux scrolle.
3. **Expliquer = créer la confiance.** Toute action automatique de Héos est accompagnée d'un *pourquoi* en langage courant.
4. **Cohérence des flux = physique réelle.** Héos est au centre. Le solaire et le réseau alimentent (→ vers le centre) ; le centre répartit vers batterie et maison. Jamais « maison → batterie ».
5. **Passé plein, futur voilé.** Sur tout graphe temporel : réalisé en couleur pleine, prévu en hachuré + fond grisé, ligne « maintenant » qui traverse.
6. **Fluide = rien ne saute.** Chaque valeur live s'anime (interpolation), jamais de flash.
7. **Quality floor non négociable.** Responsive, dark mode, focus clavier, `prefers-reduced-motion` respecté, contraste AA.
---
## 3. Design tokens (`etm_tokens.dart`)
Compléter le fichier existant avec ces valeurs. Typo : **IBM Plex Sans** (UI) + **IBM Plex Mono** (toutes les valeurs chiffrées / données).
### Couleurs de marque (identiques light/dark sauf note)
| Token | Hex | Usage |
|---|---|---|
| `brand` | `#31A3DD` | bleu ETM, accents, navigation active |
| `solar` | `#FEC113` (dark `#FFCB33`) | production solaire |
| `eco` | `#28A06A` (dark `#33B87A`) | Héos, éco, autonomie, batterie |
| `heat` | `#EF8B2A` | chauffage / PAC |
| `water` | `#31A3DD` | eau chaude (ECS) |
| `grid` | `#8A98A3` | réseau |
| `import` | `#E8806F` | import réseau |
### Thème clair
| Token | Valeur |
|---|---|
| `bg` | `#EEF2F5` |
| `bg2` | `#E4EAEE` |
| `surface` | `#FFFFFF` |
| `ink` | `#0D2B3B` |
| `muted` | `#5D6F7B` |
| `faint` | `#8B9AA4` |
| `line` | `rgba(13,43,59,.10)` |
| `lineStrong` | `rgba(13,43,59,.16)` |
### Thème sombre
| Token | Valeur |
|---|---|
| `bg` | `#06141D` |
| `bg2` | `#0A1F2B` |
| `surface` | `#0D2B3B` (la couleur de marque devient la surface) |
| `ink` | `#EAF3F7` |
| `muted` | `#9BB0BC` |
| `faint` | `#6C8493` |
| `line` | `rgba(255,255,255,.09)` |
| `lineStrong` | `rgba(255,255,255,.16)` |
### Couleurs des flux (graphe « plan de la journée »)
`solToHouse #B8C34D` · `solToEcs #31A3DD` · `solToVe #28A06A` · `solToBatt #7FCF72` · `solToGrid #FEC113` · `battToHouse #2F6FB0` · `gridToHouse #E8806F`
### Formes & profondeur
- Rayons : cartes **20**, contrôles internes **1314**, pills/badges **30** (stadium).
- **Parti pris assumé** : bordure hairline 1 px (`line`) + rayon large. Ombres quasi nulles en clair (`0 1px 2px rgba(13,43,59,.04)`), aucune en sombre. **Ne pas** revenir aux cartes blanches à ombre portée Material par défaut.
- Espacement entre cartes : 14. Padding interne carte : 1416.
---
## 4. Architecture de l'écran
`DashboardScreen` = `Scaffold` + `SingleChildScrollView` (colonne), bottom nav fixe à 5 onglets. Ordre vertical des blocs :
```
[ TopBar : wordmark + toggle thème clair/sombre ]
[ 1. EnergyFlowCard ] ← flux en croix, temps réel, drill-down
[ 2. KpiRow ] ← 4 tuiles : autoconso, autonomie, vers/depuis réseau
[ 3. HeosDecisionCard ] ← décision en cours + pourquoi
[ 4. DayPlanCard ] ← météo + barres horaires (réalisé/prévu)
[ BottomNav : Dashboard·Énergie·Things·A/C·Favoris ]
```
Onglets : `Dashboard` actif (couleur `brand`), les autres en `faint`.
---
## 5. Composants
### 5.1 EnergyFlowCard — flux en croix + drill-down
**Layout** : carte carrée (aspect 1:1, max 320). `Stack` :
- Couche basse : `CustomPaint` (lignes hairline entre hub↔nœuds + **particules animées**).
- Couche haute : nœuds en `Positioned` (Stack), `GestureDetector` sur les nœuds tappables.
**Vue d'ensemble (`overview`)** — positions cardinales :
| Position | Nœud | Valeur (ex.) | Direction flux | Tap |
|---|---|---|---|---|
| centre (hub) | **Héos** (icône HEMS, anneau eco pulsant) | — | — | — |
| haut | Solaire | `3.8 kW` | entrant (nœud→hub) | → `prod` |
| droite | Batterie | `87 %` | sortant (hub→nœud) | — |
| gauche | Réseau | `0.2 kW` | entrant | — |
| bas | Maison | `2.4 kW` | sortant | → `conso` |
**Drill-down `conso`** (depuis Maison) — hub = Maison, autour :
PAC (haut, `heat`), Eau chaude (droite, `water`), Voiture (bas, `eco`), **Autres (estimé)** (gauche, bordure **dashed**, `grid`).
> Le nœud « Autres (estimé) » est **obligatoire** : il absorbe le non-mesuré pour que le bilan boucle (conso totale = somme des postes). Sans lui, l'addition ne tombe pas juste et le client se méfie.
**Drill-down `prod`** (depuis Solaire) — hub = Solaire, autour :
Onduleur 1 (haut), Onduleur 2 (bas), chacun avec **point d'état** (vert OK / orange défaut). Sens : onduleur → hub.
**Particules** : `CustomPainter` + `AnimationController` en boucle. N points par segment circulant de la source vers la destination. **Vitesse et densité ∝ puissance** (flux fort = plus de points, plus rapides ; flux faible = 1 point lent). Couleur = couleur de la source.
**Transitions drill-down** : sortie = fade + scale 0.8 (~170 ms) ; entrée = « éclatement radial », chaque nœud apparaît en `scale .25→1` avec overshoot (`Curves.easeOutBack`) et **stagger** (~60 ms entre nœuds). Détail intégral du sens/positions dans `etm-powersync-flux-drilldown.html`.
**Affordance tap** : badge `+` discret sur les nœuds tappables (Héos n'est pas tappable ; Solaire et Maison le sont). Profondeur **max 2 niveaux** — au-delà, écran dédié.
**Icône HEMS** : utiliser `HemsIcon` (déjà livré, `hems_icon.dart`), couleur `eco`.
### 5.2 KpiRow
Grille 2×2.
- **Tuile autoconsommation** : anneau de progression (`CustomPaint`, couleur `solar`) + `%` en mono. S'anime au chargement (anneau qui se remplit + count-up).
- **Tuile autonomie** : idem, couleur `eco`.
- **Tuile « Vers réseau »** : icône flèche ←, valeur `kWh` du jour, badge fond `eco` léger.
- **Tuile « Depuis réseau »** : icône flèche →, valeur `kWh`, badge fond `import` léger.
> Ces deux métriques (autoconso/autonomie) sont les seules occurrences sur le Dashboard. Ne pas les répéter ailleurs.
### 5.3 HeosDecisionCard
Carte bordée `eco` (liseré subtil). Contenu :
- **En-tête** : icône étincelle (mark Héos) + titre « Héos pilote en ce moment » + sous-titre mono « optimise sur 24 h · maj HH:MM » + point eco pulsant.
- **Résumé** : 1 phrase en langage client, segment clé surligné `eco` (ex. « Le surplus part dans l'eau chaude et la voiture plutôt que d'être revendu à bas prix »).
- **Liste d'actions** (1 par asset piloté) : icône + titre court + **le pourquoi** (1 ligne, `muted`) + puissance (`kW`, mono, `eco`), séparées par hairline.
- **Pied** : lien « Voir le plan de la journée » → ouvre l'écran plein **Plan de la journée** (séparé, voir `etm-powersync-plan-journee.html` ; c'est là que vivent les pistes appareils détaillées + la ligne gains €).
### 5.4 DayPlanCard — météo + barres horaires
`CustomPaint` unique (préférer à fl_chart : contrôle fin du voile futur + bande météo intégrée). **Une seule colonne temporelle, axe horaire partagé**, de haut en bas :
1. **Bande météo** : icône (soleil / soleil-nuage / lune) + température, à intervalles (~toutes les 4 h), posée sur l'axe.
2. **Barres empilées horaires** (kWh) : segments positifs vers le haut (`solToHouse`, `solToEcs`, `solToVe`, `solToBatt`, `solToGrid`), négatifs vers le bas (`battToHouse`, `gridToHouse`). Axe Y `0/2/4`.
3. **Axe X** : `0h 6h 12h 18h 24h`.
**Traitement passé/futur** : tout ce qui est à droite de la ligne « maintenant » = **fond grisé + voile hachuré** (lignes diagonales couleur surface, opacité ~.5) ; ligne « maintenant » verticale + tab `HH:MM`.
**Légende** sous le graphe : pastilles maison/eau/voiture/batterie/réseau/batt.→maison.
> **NE PAS mettre sur le Dashboard** (retirés volontairement, ils vivent dans l'écran plein « Plan de la journée ») :
> - les **pistes appareils** (timelines ECS / batterie charge-décharge / VE) ;
> - la **ligne gains** (autoconso prévue / réseau ce soir / +€).
### 5.5 Toggle thème
Switch clair/sombre dans la TopBar, knob animé (overshoot). Persister le choix (provider de thème).
---
## 6. Animations & widgets
| Effet | Approche Flutter |
|---|---|
| Valeurs live (kW, %) | `TweenAnimationBuilder` (count-up, easeOutCubic ~900 ms) |
| Particules de flux | `CustomPainter` + `AnimationController` répété ; position = `(t + offset) % 1` mappée sur le segment |
| Anneaux KPI | `CustomPaint`, `Tween` sur l'angle balayé |
| Toggles / switch | `AnimatedAlign` ou `AnimatedPositioned`, courbe overshoot (`Curves.easeOutBack`) |
| Segmented (pill qui glisse) | `Stack` + `AnimatedPositioned` du « glider » |
| Cartes expandables (A/C) | `AnimatedSize` |
| Drill-down croix | `AnimationController` + `Interval` staggered (scale + opacity) |
| Anneau Héos (hub) | pulsation lente en boucle (opacité 0.25↔0.6) |
**Reduced motion** : si `MediaQuery.of(context).disableAnimations`, court-circuiter les animations (valeurs finales directes, pas de particules en boucle).
---
## 7. Modèles de données (esquisse)
```dart
class LiveFlow { // alimente EnergyFlowCard (overview)
final double solarKw, gridKw, houseKw, batteryKw; // batteryKw signé
final int batteryPct;
}
class FlowNode { // générique, réutilisé à chaque niveau
final String label, valueText, iconKey;
final FlowDirection direction; // inbound | outbound
final bool tappable, estimated; // estimated → bordure dashed
final NodeState? state; // ok | warn (onduleurs)
final String? drillTo; // 'conso' | 'prod'
}
class HeosDecision {
final String summary; // langage client
final List<HeosAction> actions;
final DateTime updatedAt;
}
class HeosAction { final String assetIconKey, title, why; final double powerKw; }
class Kpi { final int autoconsumptionPct, autonomyPct; final double toGridKwh, fromGridKwh; }
class DayPlan {
final List<HourlyFlow> hours; // 0..23
final List<WeatherPoint> weather;
final double nowHour; // ex. 14.5
}
class HourlyFlow { // tous en kWh, signés selon sens
final int hour;
final double solToHouse, solToEcs, solToVe, solToBatt, solToGrid;
final double battToHouse, gridToHouse;
}
class WeatherPoint { final int hour; final WeatherKind kind; final int tempC; }
```
Toutes les valeurs affichées proviennent de ces modèles (les chiffres des maquettes sont des exemples). Prévoir des états **loading** (skeleton) et **vide** (« en attente de données du compteur… », jamais d'écran blanc).
---
## 8. Assets fournis
- `hems_icon.dart``HemsIcon(size, color)`, `CustomPainter` natif sans dépendance. À utiliser pour le hub central.
- `hems_icon.svg` — équivalent SVG (`currentColor`) si passage par `flutter_svg`.
---
## 9. Arborescence suggérée
```
lib/features/dashboard/
dashboard_screen.dart
widgets/energy_flow_card.dart + painters/flow_painter.dart
widgets/kpi_row.dart + painters/ring_painter.dart
widgets/heos_decision_card.dart
widgets/day_plan_card.dart + painters/day_plan_painter.dart
models/dashboard_models.dart
lib/shared/widgets/hems_icon.dart
lib/theme/etm_tokens.dart (compléter)
```
## 10. Ordre de construction recommandé
1. Compléter `etm_tokens.dart` (couleurs light/dark, typo, radius).
2. `KpiRow` + `RingPainter` (le plus simple, valide les tokens).
3. `HeosDecisionCard` (statique, valide la typo/hairlines).
4. `EnergyFlowCard` overview + `FlowPainter` (lignes + particules), **puis** drill-down.
5. `DayPlanCard` + `DayPlanPainter` (barres + voile futur + bande météo).
6. Assemblage `DashboardScreen` + bottom nav + toggle thème.
7. Passe quality floor : reduced motion, focus, contraste AA en clair **et** sombre.
---
*Réfs visuelles : `etm-powersync-dashboard-v4.html` (écran complet) et `etm-powersync-flux-drilldown.html` (interaction croix). En cas de doute sur un espacement ou une couleur, ces fichiers font foi.*