Files
EVCS/deploy/historisation/grafana/dashboards/nuit.json
Tallagrand Thierry 64aee4bfcc Les requetes s'executaient, les grandeurs etaient fausses
Trois defauts trouves en LISANT les tableaux, qu'aucune des 19 requetes ne
signalait puisqu'elles s'executaient toutes sans erreur.

1. `historise-evcs.mjs` horodate chaque mesure avec son propre last_updated —
   le vrai instant de la mesure, et c'est le bon choix. Mais deux grandeurs ne
   partagent alors presque jamais le meme ts : 2656 lignes pour 2656 instants
   distincts. Mes GROUP BY ts rendaient une ligne par grandeur avec une seule
   colonne remplie, donc la courbe "Excedent" — la grandeur meme du §36 —
   etait VIDE sur les 2650 lignes.

2. Le service n'ecrit que les changements, donc un regroupement a la minute
   laisse des trous : la difference n'existait que 73 minutes sur 434. Report
   de la derniere valeur connue, ce que fait aussi le controleur en temps
   reel. 430/435 apres.

3. L'energie horaire par sum(valeur)/60 suppose 60 echantillons par heure ; il
   y en a eu 26 a 47. Integration ponderee par la duree, plafonnee a 300 s
   pour qu'un arret du service n'extrapole pas une valeur perimee. ~9 %
   d'ecart. Le "25 a 58 %" que j'avais d'abord annonce etait faux : il
   comparait la somme a la moyenne, qui ignore la duree.

Plus deux corrections d'affichage : la colonne `jour` s'affichait
"2026-09-04 02:00:00" (date nue rendue comme instant UTC), et le decompte des
chargeurs MPPT est de six entites publiees, pas sept.

Lecon : verifier-dashboards.mjs prouve qu'une requete s'EXECUTE, pas qu'elle
rend la grandeur voulue — une colonne integralement nulle passe sans un mot.
Ce sont les courbes regardees a l'ecran qui ont attrape les trois.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
2026-09-04 08:48:42 +02:00

645 lines
18 KiB
JSON

{
"uid": "evcs-nuit",
"title": "La nuit",
"description": "Ce que le bilan exigeait, contre ce que la batterie a reellement fait. C'est la question qui a demande une nuit d'archeologie le 03/09 et qui tient maintenant en un coup d'oeil.",
"tags": [
"evcs"
],
"timezone": "Europe/Paris",
"editable": true,
"schemaVersion": 39,
"version": 1,
"refresh": "1m",
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"30s",
"1m",
"5m",
"15m",
"1h"
]
},
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"name": "Annotations & Alerts",
"iconColor": "rgba(0, 211, 255, 1)",
"type": "dashboard"
},
{
"name": "Evenements",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"enable": true,
"iconColor": "semi-dark-orange",
"hide": false,
"target": {
"format": "table",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time, resume AS text, genre || ',' || coalesce(source,'?') AS tags FROM evenement WHERE $__timeFilter(ts) ORDER BY 1"
}
}
]
},
"panels": [
{
"type": "timeseries",
"title": "Couverture de la nuit",
"description": "SOC requis = ce qu'il faut MAINTENANT pour tenir jusqu'au croisement PV/conso. Il decroit avec la nuit qui se consomme. Quand la courbe reelle passe dessous, la batterie ne passera pas la nuit sans reseau.",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "time_series",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time,\n soc_pct AS \"SOC reel\",\n soc_requis_pct AS \"SOC requis (bilan)\"\nFROM bilan\nWHERE $__timeFilter(ts)\nORDER BY 1"
},
{
"refId": "B",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "time_series",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time, valeur AS \"Cible du matin\"\nFROM mesure\nWHERE cle = 'input_number.victron_evcs_night_target_soc'\n AND $__timeFilter(ts)\nORDER BY 1"
}
],
"gridPos": {
"x": 0,
"y": 0,
"w": 16,
"h": 9
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"showPoints": "never",
"spanNulls": true,
"axisBorderShow": false,
"gradientMode": "opacity"
},
"min": 0,
"max": 100
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "SOC reel"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "custom.lineWidth",
"value": 3
}
]
},
{
"matcher": {
"id": "byName",
"options": "SOC requis (bilan)"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "orange"
}
},
{
"id": "custom.fillOpacity",
"value": 0
}
]
},
{
"matcher": {
"id": "byName",
"options": "Cible du matin"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "purple"
}
},
{
"id": "custom.fillOpacity",
"value": 0
},
{
"id": "custom.lineStyle",
"value": {
"fill": "dash",
"dash": [
8,
6
]
}
}
]
}
]
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true,
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "timeseries",
"title": "Marge (reel moins requis)",
"description": "Sous zero, il manque de l'energie pour finir la nuit. La nuit du 02-03/09 est restee negative de bout en bout, et personne ne le voyait.",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "time_series",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time, soc_pct - soc_requis_pct AS \"Marge\"\nFROM bilan\nWHERE $__timeFilter(ts) AND soc_requis_pct IS NOT NULL\nORDER BY 1"
}
],
"gridPos": {
"x": 16,
"y": 0,
"w": 8,
"h": 9
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"color": {
"mode": "thresholds"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 25,
"showPoints": "never",
"gradientMode": "scheme",
"spanNulls": true,
"thresholdsStyle": {
"mode": "line"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"options": {
"legend": {
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
}
},
{
"type": "state-timeline",
"title": "Ce que le controleur a decide",
"description": "Une ligne par CHANGEMENT de decision, pas une toutes les 30 s. La session reseau de nuit doit s'engager UNE fois et se relacher a la fermeture de la fenetre : plusieurs bascules = un oscillateur.",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "table",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time,\n source_mode AS \"Source\",\n etat AS \"Etat\",\n CASE WHEN session_engagee THEN 'engagee' ELSE '-' END AS \"Session reseau\",\n CASE WHEN takeover_reseau THEN 'demande' ELSE '-' END AS \"Reprise reseau\"\nFROM decision\nWHERE $__timeFilter(ts)\nORDER BY 1"
}
],
"gridPos": {
"x": 0,
"y": 9,
"w": 24,
"h": 7
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"lineWidth": 0,
"fillOpacity": 80,
"insertNulls": false,
"spanNulls": true
},
"mappings": [
{
"type": "value",
"options": {
"-": {
"color": "dark-blue",
"index": 0
},
"engagee": {
"color": "orange",
"index": 1
},
"demande": {
"color": "semi-dark-orange",
"index": 2
}
}
}
]
},
"overrides": []
},
"options": {
"showValue": "auto",
"mergeValues": true,
"alignValue": "left",
"rowHeight": 0.85,
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single"
}
}
},
{
"type": "timeseries",
"title": "Les puissances de la nuit",
"description": "Batterie positive = elle se charge. Contacteur ferme avec un import qui ne couvre pas la maison = la batterie paie quand meme : c'est le defaut du 04/09 (consigne retombee a 50 W en pleine session).",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "time_series",
"rawQuery": true,
"editorMode": "code",
"rawSql": "WITH par_minute AS (\n SELECT date_trunc('minute', ts) AS t,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_grid_power_l1') AS Reseau,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_battery_power') AS Batterie,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS Maison,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_evcs_ac_power') AS Voiture\n FROM mesure WHERE $__timeFilter(ts) GROUP BY 1\n),\npaliers AS (\n SELECT t, Reseau, Batterie, Maison, Voiture,\n count(Reseau) OVER (ORDER BY t) AS g_Reseau,\n count(Batterie) OVER (ORDER BY t) AS g_Batterie,\n count(Maison) OVER (ORDER BY t) AS g_Maison,\n count(Voiture) OVER (ORDER BY t) AS g_Voiture\n FROM par_minute\n),\nreport AS (\n SELECT t,\n first_value(Reseau) OVER (PARTITION BY g_Reseau ORDER BY t) AS Reseau,\n first_value(Batterie) OVER (PARTITION BY g_Batterie ORDER BY t) AS Batterie,\n first_value(Maison) OVER (PARTITION BY g_Maison ORDER BY t) AS Maison,\n first_value(Voiture) OVER (PARTITION BY g_Voiture ORDER BY t) AS Voiture\n FROM paliers\n)\nSELECT t AS time,\n Reseau AS \"Reseau\",\n Batterie AS \"Batterie\",\n Maison AS \"Maison\",\n Voiture AS \"Voiture\"\nFROM report ORDER BY t"
}
],
"gridPos": {
"x": 0,
"y": 16,
"w": 12,
"h": 8
},
"fieldConfig": {
"defaults": {
"unit": "watt",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 8,
"showPoints": "never",
"spanNulls": true,
"axisBorderShow": false,
"gradientMode": "opacity"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Reseau"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "red"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Batterie"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Maison"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "text"
}
},
{
"id": "custom.fillOpacity",
"value": 0
}
]
},
{
"matcher": {
"id": "byName",
"options": "Voiture"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
}
]
}
]
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true,
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "timeseries",
"title": "Ce qu'il reste a couvrir",
"description": "\"Manque de stockage\" non nul = meme pleine, la batterie actuelle ne passe pas. C'est le signal de dimensionnement pour l'extension prevue cet hiver, pas une erreur.",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "time_series",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS time,\n energie_a_couvrir_kwh AS \"A couvrir d'ici le croisement\",\n reseau_energie_kwh AS \"Dont reseau necessaire\",\n manque_stockage_kwh AS \"Manque de stockage\"\nFROM bilan\nWHERE $__timeFilter(ts)\nORDER BY 1"
}
],
"gridPos": {
"x": 12,
"y": 16,
"w": 12,
"h": 8
},
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 2,
"fillOpacity": 12,
"showPoints": "never",
"spanNulls": true,
"gradientMode": "opacity"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true,
"calcs": []
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"type": "table",
"title": "Journal des evenements",
"description": "Une courbe sans ses evenements ne s'interprete pas. Ces lignes apparaissent aussi en marqueurs verticaux sur tous les graphiques.",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "evcs-pg"
},
"format": "table",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT ts AS \"Quand\", genre AS \"Genre\", source AS \"Par\",\n resume AS \"Ce qui s'est passe\"\nFROM evenement\nWHERE $__timeFilter(ts)\nORDER BY ts DESC"
}
],
"gridPos": {
"x": 0,
"y": 24,
"w": 24,
"h": 8
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "left",
"cellOptions": {
"type": "auto"
},
"filterable": true
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Quand"
},
"properties": [
{
"id": "custom.width",
"value": 190
}
]
},
{
"matcher": {
"id": "byName",
"options": "Genre"
},
"properties": [
{
"id": "custom.width",
"value": 140
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-text"
}
},
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"deploiement": {
"color": "blue",
"index": 0
},
"incident": {
"color": "red",
"index": 1
},
"intervention": {
"color": "orange",
"index": 2
},
"systeme": {
"color": "purple",
"index": 3
},
"reseau": {
"color": "yellow",
"index": 4
},
"vehicule": {
"color": "green",
"index": 5
}
}
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "Par"
},
"properties": [
{
"id": "custom.width",
"value": 130
}
]
}
]
},
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false
}
}
}
]
}