From 9b283292bc4d1e6ada7cd54049edca003a8df2ec Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Sat, 3 Oct 2026 23:57:18 +0200 Subject: [PATCH] Grafana vehicules : resume en tableau, couleurs fixes ; HANDOFF concierge vehicule non confirme --- HANDOFF.md | 27 ++- .../grafana/dashboards/vehicules.json | 217 ++++++++++++++---- tools/gen-grafana-dashboards.py | 46 ++-- 3 files changed, 227 insertions(+), 63 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 904ca98..923764b 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1492,6 +1492,31 @@ du 03-04/10 = test du budget. vehicule_sur=false`). Session close : le service ne la reecrit pas. La session de 13:10 (JEEP, 4,6 kWh, a confirmer) est bien la JEEP. - JEEP rebranchee a 20:14, concierge 20:20 : la nuit du 03-04/10 reste le test du budget. +- **Suite (03/10 ~23:55, accord du proprietaire)** : + - Grafana « Sur la periode » : panneau `stat` (chiffres empiles, illisible) remplace par + un TABLEAU pleine largeur, une colonne par vehicule (VW, JEEP, ZERO, Invite, Non + etiquete, Sessions, A confirmer), couleurs fixes partagees avec l'histogramme + (`COULEURS_VEHICULES` : VW bleu, JEEP vert, ZERO violet, Invite jaune, Non etiquete + orange) — l'histogramme prenait la palette par defaut (JEEP en jaune). Verifie par + l'API : 7 jours = JEEP 62,5 / ZERO 4,7. + - `session_charge` 13:10 (JEEP) passee a **confirmee** en base. + - **Releve SPARK des reponses reelles du 03/10** : ZERO 48 % (17:11) et JEEP 54 % + (20:22) conformes. Mais a 14:13 « JEEP » a ete ANNULE par OpenClaw (« Skipped due to + queued user message », le « 53% » suivant etait deja en file) : seul le niveau a ete + note, le branchement de 13:06 est reste sans etiquette. Et « C'etait 53% a 13h10 » -> + l'agent a repondu « Noté : 53 % à 13:10 » (heure inventee, le script n'en enregistre + pas). + - **`concierge-live.js` (fait par moi, SPARK prevenu avant/apres)** : (1) un niveau + seul alors que le branchement en cours n'a pas d'etiquette concierge (ou historique + illisible) -> la ligne finit par « ⚠️ Véhicule non confirmé pour ce branchement : + lequel est-ce ? » ; le vehicule du selecteur n'est JAMAIS publie ; (2) la ligne du + niveau porte l'heure de saisie : « 🔋 Niveau noté : 53 % (saisi à 14:15). ». + 7 cas verifies en simulation (niveau seul etiquete / non etiquete / base muette, + vehicule + niveau, 150 % refuse, voiture debranchee, vehicule seul). Sauvegarde + `concierge-live.js.bak-nonconfirme-20261003-235613`. + - Horodatage « [Sat 2026-10-03 14:14 GMT+2 » dans un WhatsApp : ecrit par le MODELE + (il a recopie l'enveloppe des messages entrants apres l'appel annule), pas par le + gateway. Une occurrence : SPARK surveille, pas de regle de fiche. --- -*Document mis a jour le : 03/10/2026 (soir) — Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* +*Document mis a jour le : 03/10/2026 (~23:55) — concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* diff --git a/deploy/historisation/grafana/dashboards/vehicules.json b/deploy/historisation/grafana/dashboards/vehicules.json index 4beaa84..41bbfab 100644 --- a/deploy/historisation/grafana/dashboards/vehicules.json +++ b/deploy/historisation/grafana/dashboards/vehicules.json @@ -60,9 +60,9 @@ }, "panels": [ { - "type": "stat", + "type": "table", "title": "Sur la periode", - "description": "« Etiquette douteuse » compte les sessions ou le selecteur a change en cours de route, ou n'etait pas renseigne. C'est la mesure honnete de l'incertitude : elle ne peut pas etre levee autrement qu'en positionnant le selecteur au branchement.", + "description": "kWh charges par vehicule sur la plage choisie (compteur de la borne). « Invite » = tout vehicule hors foyer (Megane de service, amis). « A confirmer » compte les sessions dont l'etiquette vient du selecteur sans confirmation : la seule mesure honnete de l'incertitude.", "datasource": { "type": "grafana-postgresql-datasource", "uid": "evcs-pg" @@ -77,30 +77,24 @@ "format": "table", "rawQuery": true, "editorMode": "code", - "rawSql": "SELECT round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'ZERO'), 0)::numeric, 1) AS \"ZERO\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule NOT IN ('VW', 'JEEP', 'ZERO')), 0)::numeric, 1) AS \"Invite\",\n count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"A confirmer\"\nFROM session_charge WHERE $__timeFilter(debut)" + "rawSql": "SELECT round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'ZERO'), 0)::numeric, 1) AS \"ZERO\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule NOT IN ('VW', 'JEEP', 'ZERO')), 0)::numeric, 1) AS \"Invite\",\n round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule IS NULL), 0)::numeric, 1) AS \"Non etiquete\",\n count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"A confirmer\"\nFROM session_charge WHERE $__timeFilter(debut)" } ], "gridPos": { "x": 0, "y": 0, "w": 24, - "h": 5 + "h": 4 }, "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "text", - "value": null - } - ] - }, - "mappings": [] + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": true + } }, "overrides": [ { @@ -113,6 +107,16 @@ "id": "unit", "value": "kwatth" }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, { "id": "color", "value": { @@ -132,6 +136,16 @@ "id": "unit", "value": "kwatth" }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, { "id": "color", "value": { @@ -151,6 +165,16 @@ "id": "unit", "value": "kwatth" }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, { "id": "color", "value": { @@ -170,6 +194,16 @@ "id": "unit", "value": "kwatth" }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, { "id": "color", "value": { @@ -182,9 +216,44 @@ { "matcher": { "id": "byName", - "options": "Etiquette douteuse" + "options": "Non etiquete" }, "properties": [ + { + "id": "unit", + "value": "kwatth" + }, + { + "id": "decimals", + "value": 1 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "orange" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "A confirmer" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, { "id": "thresholds", "value": { @@ -200,31 +269,23 @@ } ] } + }, + { + "id": "color", + "value": { + "mode": "thresholds" + } } ] } ] }, "options": { - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "orientation": "horizontal", - "textMode": "auto", - "colorMode": "value", - "graphMode": "none", - "justifyMode": "center", - "percentChangeColorMode": "standard", - "noValue": "—", - "text": { - "valueSize": 34, - "titleSize": 12 - }, - "wideLayout": true + "showHeader": true, + "cellHeight": "sm", + "footer": { + "show": false + } } }, { @@ -250,7 +311,7 @@ ], "gridPos": { "x": 0, - "y": 5, + "y": 4, "w": 24, "h": 9 }, @@ -267,7 +328,83 @@ "axisBorderShow": false } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "VW" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "blue" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "JEEP" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "green" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ZERO" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "purple" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Invite" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "yellow" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Non etiquete" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed", + "fixedColor": "orange" + } + } + ] + } + ] }, "options": { "orientation": "auto", @@ -310,7 +447,7 @@ ], "gridPos": { "x": 0, - "y": 14, + "y": 13, "w": 24, "h": 11 }, diff --git a/tools/gen-grafana-dashboards.py b/tools/gen-grafana-dashboards.py index f9b3ef8..82c7845 100644 --- a/tools/gen-grafana-dashboards.py +++ b/tools/gen-grafana-dashboards.py @@ -1347,6 +1347,9 @@ WHERE $__timeFilter(debut) GROUP BY date_trunc(pas.unite, debut), pas.forme ORDER BY date_trunc(pas.unite, debut)""" +COULEURS_VEHICULES = {"VW": "blue", "JEEP": "green", "ZERO": "purple", + "Invite": "yellow", "Non etiquete": "orange"} + vehicules = dashboard("evcs-vehicules", "Les vehicules", ( "Ce que chaque voiture encaisse, session par session et sur la periode " "choisie. L'etiquette vient d'un selecteur manuel : elle peut etre fausse, " @@ -1356,39 +1359,38 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", ( "service, amis)."), frm="now-7d", refresh="5m", panels=[ - stat("Sur la periode", [ + table("Sur la periode", [ sql("A", "SELECT round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'VW'), 0)::numeric, 1) AS \"VW\",\n" " round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'JEEP'), 0)::numeric, 1) AS \"JEEP\",\n" " round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule = 'ZERO'), 0)::numeric, 1) AS \"ZERO\",\n" " round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule NOT IN ('VW', 'JEEP', 'ZERO')), 0)::numeric, 1) AS \"Invite\",\n" + " round(coalesce(sum(coalesce(energie_station_kwh, energie_kwh)) FILTER (WHERE vehicule IS NULL), 0)::numeric, 1) AS \"Non etiquete\",\n" " count(*) AS \"Sessions\",\n" " count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"A confirmer\"\n" "FROM session_charge WHERE $__timeFilter(debut)", fmt="table")], - PG, 0, 0, 24, 5, - "« Etiquette douteuse » compte les sessions ou le selecteur a change en " - "cours de route, ou n'etait pas renseigne. C'est la mesure honnete de " - "l'incertitude : elle ne peut pas etre levee autrement qu'en positionnant " - "le selecteur au branchement.", - taille={"valueSize": 34, "titleSize": 12}, - overrides=[ - sur("VW", [{"id": "unit", "value": "kwatth"}, - {"id": "color", "value": {"mode": "fixed", "fixedColor": "blue"}}]), - sur("JEEP", [{"id": "unit", "value": "kwatth"}, - {"id": "color", "value": {"mode": "fixed", "fixedColor": "green"}}]), - sur("ZERO", [{"id": "unit", "value": "kwatth"}, - {"id": "color", "value": {"mode": "fixed", "fixedColor": "purple"}}]), - sur("Invite", [{"id": "unit", "value": "kwatth"}, - {"id": "color", "value": {"mode": "fixed", "fixedColor": "yellow"}}]), - sur("Etiquette douteuse", [{"id": "thresholds", "value": seuils( - ("green", None), ("orange", 1))}])]), + PG, 0, 0, 24, 4, + "kWh charges par vehicule sur la plage choisie (compteur de la borne). " + "« Invite » = tout vehicule hors foyer (Megane de service, amis). " + "« A confirmer » compte les sessions dont l'etiquette vient du selecteur " + "sans confirmation : la seule mesure honnete de l'incertitude.", + overrides=[sur(n, [{"id": "unit", "value": "kwatth"}, + {"id": "decimals", "value": 1}, + {"id": "custom.cellOptions", "value": {"type": "color-text"}}, + {"id": "color", "value": {"mode": "fixed", "fixedColor": c}}]) + for n, c in COULEURS_VEHICULES.items()] + [ + sur("A confirmer", [{"id": "custom.cellOptions", "value": {"type": "color-text"}}, + {"id": "thresholds", "value": seuils(("green", None), ("orange", 1))}, + {"id": "color", "value": {"mode": "thresholds"}}])]), barchart("Energie par periode et par vehicule", [sql("A", SESSIONS_PERIODE, fmt="table")], - PG, 0, 5, 24, 9, "kwatth", + PG, 0, 4, 24, 9, "kwatth", "Le pas suit la plage choisie : l'heure jusqu'a 4 jours, le jour jusqu'a " "70, le mois au-dela. « Non etiquete » est ce qui a ete charge sans que le " - "selecteur soit renseigne — a interpreter, pas a ignorer."), + "selecteur soit renseigne — a interpreter, pas a ignorer.", + overrides=[sur(n, [{"id": "color", "value": {"mode": "fixed", "fixedColor": c}}]) + for n, c in COULEURS_VEHICULES.items()]), table("Les sessions", [ sql("A", @@ -1404,7 +1406,7 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", ( " source_mode AS \"Source au depart\",\n" " to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\n" "FROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC", fmt="table")], - PG, 0, 14, 24, 11, + PG, 0, 13, 24, 11, "« kWh » vient du compteur de la BORNE, qui compte depuis le vrai debut " "de la session. « kWh integres » est mon propre calcul, qui ne couvre que " "ce que le service a vu — un ecart entre les deux signale un demarrage ou " @@ -1789,7 +1791,7 @@ journee = dashboard( # found ». Ici on veut les quatre colonnes, tout le temps. barchart("Energie heure par heure", [sql("A", energie_sql(prevision=True), fmt="table")], - PG, 0, 5, 24, 9, "kwatth", + PG, 0, 4, 24, 9, "kwatth", "La forme de la journee : quand le solaire a couvert la maison, quand il " "a fallu acheter — et, a la suite des barres mesurees, ce que la derniere " "prevision annonce pour les heures qui restent. L'heure EN COURS n'a pas "