Grafana : les deux bases se consultent, et les requetes sont verifiees

Trois tableaux de bord provisionnes depuis git, sur le modele de la regle R2
(le depot est la source de verite, l'interface ne peut pas les ecraser) :

  La nuit      SOC reel contre SOC exige par le bilan, et la marge entre les
               deux. C'est la question qui avait demande une nuit d'archeologie
               le 03/09.
  Le solaire   l'excedent (§36), la censure, et le mode declare par chacun des
               sept chargeurs (§37) — le signal le plus direct dont on dispose.
  Prevision    l'ecart heure par heure, avec la bande de censure dessous : une
               production sous la prevision peut vouloir dire mauvaise
               prevision OU absence de preneur.

Les evenements de la table `evenement` deviennent des marqueurs verticaux sur
tous les graphiques — une courbe sans ses evenements ne s'interprete pas.

tools/verifier-dashboards.mjs rejoue CHAQUE requete pour de vrai (SQL contre
PostgreSQL, Flux contre InfluxDB, macros Grafana substituees), annotations
comprises puisqu'elles s'executent sur tous les panneaux. Sans lui une requete
cassee ne se voit qu'a l'ecran, panneau par panneau. 19/19 au deploiement.

Port 3001 et non 3000 : sonde avant d'ecrire la ligne, quelque chose ecoute
deja sur 3000 — la lecon du 5432 deja pris par le PostgreSQL de DSM.

Connexion PostgreSQL par le role evcs_lecture : un tableau de bord ne doit pas
pouvoir ecrire dans la memoire. Aucun secret duplique, aucun secret commite :
le provisionnement les reference par $VARIABLE depuis les deux .env.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
Tallagrand Thierry
2026-09-04 08:06:25 +02:00
parent 931e07eca9
commit fe029cfdff
11 changed files with 2264 additions and 0 deletions

View File

@@ -0,0 +1,238 @@
{
"uid": "evcs-prevision",
"title": "Prevision contre reel",
"description": "Le total de la prevision tombe juste ; sa FORME non. Sur les trois jours verifies le 04/09, la production arrive plus tot que prevu et l'apres-midi est sur-promis d'un facteur 2 a 3 apres 16 h. Ce tableau existe pour instruire cette question sur des semaines, pas sur trois jours.",
"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": "barchart",
"title": "Heure par heure : promis contre livre",
"description": "ATTENTION a l'interpretation : \"produit\" est une mesure CENSUREE. Moins que prevu peut vouloir dire mauvaise prevision OU absence de preneur. Lire la bande de censure juste dessous avant de conclure.",
"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": "WITH du_matin AS (\n SELECT DISTINCT ON (jour) jour, publie_a, horaire\n FROM prevision\n WHERE publie_a::date = jour\n AND jour BETWEEN $__timeFrom()::date AND $__timeTo()::date\n ORDER BY jour, publie_a ASC\n),\nprevu AS (\n SELECT (jour + (h - 1) * interval '1 hour') AS heure,\n horaire[h] AS kwh\n FROM du_matin, generate_subscripts(horaire, 1) AS h\n),\nproduit AS (\n -- Un echantillon toutes les 60 s : chaque point vaut 1/60 h.\n SELECT date_trunc('hour', ts) AS heure,\n sum(valeur) / 60.0 / 1000.0 AS kwh\n FROM mesure\n WHERE cle = 'sensor.victron_pv_power_system' AND $__timeFilter(ts)\n GROUP BY 1\n)\nSELECT coalesce(prevu.heure, produit.heure) AS time,\n round(prevu.kwh::numeric, 2) AS \"Prevu le matin meme\",\n round(produit.kwh::numeric, 2) AS \"Reellement produit\"\nFROM prevu FULL OUTER JOIN produit USING (heure)\nWHERE coalesce(prevu.heure, produit.heure) BETWEEN $__timeFrom() AND $__timeTo()\nORDER BY 1"
}
],
"gridPos": {
"x": 0,
"y": 0,
"w": 24,
"h": 10
},
"fieldConfig": {
"defaults": {
"unit": "kwatth",
"color": {
"mode": "palette-classic"
},
"custom": {
"lineWidth": 1,
"fillOpacity": 80,
"gradientMode": "none",
"axisBorderShow": false
}
},
"overrides": []
},
"options": {
"orientation": "auto",
"xTickLabelRotation": -45,
"showValue": "never",
"stacking": "none",
"groupWidth": 0.7,
"barWidth": 0.9,
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
}
},
{
"type": "state-timeline",
"title": "Y avait-il un preneur ?",
"description": "Seules les heures 'libre' permettent de juger la prevision. Les heures bridees ne disent rien de la ressource disponible.",
"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 CASE WHEN pv_censure THEN 'bridee (mesure inexploitable)'\n ELSE 'libre (mesure valide)' END AS \"Production\"\nFROM bilan WHERE $__timeFilter(ts) ORDER BY 1"
}
],
"gridPos": {
"x": 0,
"y": 10,
"w": 24,
"h": 5
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"lineWidth": 0,
"fillOpacity": 80,
"insertNulls": false,
"spanNulls": true
},
"mappings": [
{
"type": "value",
"options": {
"libre (mesure valide)": {
"color": "green",
"index": 0
},
"bridee (mesure inexploitable)": {
"color": "orange",
"index": 1
}
}
}
]
},
"overrides": []
},
"options": {
"showValue": "auto",
"mergeValues": true,
"alignValue": "left",
"rowHeight": 0.85,
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single"
}
}
},
{
"type": "table",
"title": "Les previsions telles qu'elles ont ete publiees",
"description": "Sans l'horodatage de PUBLICATION, on ne peut pas mesurer le biais : comparer une prevision au reel n'a de sens que si l'on sait quand elle a ete faite. C'est la raison d'etre de cette table.",
"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 publie_a AS \"Publiee le\", jour AS \"Pour le jour\",\n round(total_kwh::numeric, 1) AS \"Total annonce (kWh)\",\n round((horaire[8] + horaire[9] + horaire[10] + horaire[11])::numeric, 2) AS \"07-11 h\",\n round((horaire[12] + horaire[13] + horaire[14] + horaire[15])::numeric, 2) AS \"11-15 h\",\n round((horaire[16] + horaire[17] + horaire[18] + horaire[19])::numeric, 2) AS \"15-19 h\"\nFROM prevision\nWHERE $__timeFilter(publie_a)\nORDER BY publie_a DESC, jour"
}
],
"gridPos": {
"x": 0,
"y": 15,
"w": 24,
"h": 9
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "left",
"cellOptions": {
"type": "auto"
},
"filterable": true
}
},
"overrides": []
},
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false
}
}
}
]
}