diff --git a/HANDOFF.md b/HANDOFF.md index 5400ddf..3decd4c 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -3527,3 +3527,43 @@ Les evenements apparaissent en marqueurs verticaux sur **tous** les graphiques. --- *Document mis a jour le : 04/09/2026 (~08:30) — Grafana provisionne par git, 19/19 requetes verifiees* + +### Correction du meme jour — les requetes marchaient, les grandeurs non +Trois defauts trouves en LISANT les tableaux, qu'aucune des 19 requetes ne +signalait puisqu'elles s'executaient toutes sans erreur : + +1. **`ts` n'est pas un instant de cycle.** `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**. Un `GROUP BY ts` + rend donc une ligne par grandeur avec une seule colonne remplie, et toute + colonne CALCULEE est nulle. **La courbe « Excedent » — la grandeur meme du + §36 — etait vide sur les 2650 lignes**, sans que rien ne le signale. +2. **Le service n'ecrit que les changements** (`ON CONFLICT DO NOTHING` sur + `(cle, ts)`). Un simple regroupement a la minute laisse donc des trous : la + difference n'existait que les 73 minutes sur 434 ou les deux grandeurs + avaient bouge ensemble. Correction : report de la derniere valeur connue + (`first_value` sur les paliers numerotes par `count(x) OVER (ORDER BY t)`), + qui est aussi ce que fait 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 le 04/09. Remplace par une integration + ponderee par la duree (`valeur × intervalle jusqu'au point suivant`, + plafonne a 300 s pour qu'un arret du service n'extrapole pas une valeur + perimee). Ecart mesure : ~9 %. + ⚠️ Le chiffre de « 25 a 58 % » que j'avais d'abord annonce etait faux : il + comparait la somme a la MOYENNE, qui ignore la duree et n'est donc pas la + reference. + +Plus deux corrections d'affichage : la colonne `jour` (type `date`) s'affichait +`2026-09-04 02:00:00`, rendue comme un instant UTC converti en heure de Paris +(`to_char` desormais) ; et le decompte des chargeurs MPPT est de **six +entites** publiees, pas sept comme l'affirmait le §28. + +**Lecon**, dans la lignee des trois oscillateurs : `verifier-dashboards.mjs` +prouve qu'une requete s'EXECUTE, pas qu'elle rend la grandeur voulue. Une +colonne integralement nulle passe le controle sans un mot. Le seul test qui a +attrape ces trois defauts est d'avoir regarde les courbes et compare a ce qu'on +savait de la nuit. + +--- +*Document mis a jour le : 04/09/2026 (~09:00) — trois defauts de grandeur corriges dans les tableaux, 19/19* diff --git a/deploy/historisation/grafana/dashboards/nuit.json b/deploy/historisation/grafana/dashboards/nuit.json index cbad955..4924dc2 100644 --- a/deploy/historisation/grafana/dashboards/nuit.json +++ b/deploy/historisation/grafana/dashboards/nuit.json @@ -356,7 +356,7 @@ "format": "time_series", "rawQuery": true, "editorMode": "code", - "rawSql": "SELECT ts AS time,\n max(valeur) FILTER (WHERE cle = 'sensor.victron_grid_power_l1') AS \"Reseau\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_power') AS \"Batterie\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Maison\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_evcs_ac_power') AS \"Voiture\"\nFROM mesure\nWHERE $__timeFilter(ts)\nGROUP BY ts\nORDER BY ts" + "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": { diff --git a/deploy/historisation/grafana/dashboards/prevision.json b/deploy/historisation/grafana/dashboards/prevision.json index 57e6f52..4178a26 100644 --- a/deploy/historisation/grafana/dashboards/prevision.json +++ b/deploy/historisation/grafana/dashboards/prevision.json @@ -74,7 +74,7 @@ "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" + "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),\n-- INTEGRATION PONDEREE PAR LA DUREE, et non somme/60.\n-- Le service n'ecrit une mesure que lorsqu'elle change : compter les\n-- echantillons revient a supposer qu'il y en a 60 par heure alors qu'il y\n-- en a eu 26 a 47 le 04/09 : l'energie etait sous-estimee d'environ 9 %.\n-- Chaque point vaut donc son intervalle jusqu'au suivant, plafonne a 300 s\n-- pour qu'un arret du service n'extrapole pas une valeur perimee.\npas AS (\n SELECT ts, valeur,\n LEAST(EXTRACT(epoch FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) AS dt\n FROM mesure\n WHERE cle = 'sensor.victron_pv_power_system' AND $__timeFilter(ts)\n),\nproduit AS (\n SELECT date_trunc('hour', ts) AS heure,\n sum(valeur * coalesce(dt, 0)) / 3600.0 / 1000.0 AS kwh\n FROM pas GROUP BY 1\n)\nSELECT to_char(coalesce(prevu.heure, produit.heure), 'DD/MM HH24') || ' h' AS \"Heure\",\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 coalesce(prevu.heure, produit.heure)" } ], "gridPos": { @@ -205,7 +205,7 @@ "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" + "rawSql": "SELECT publie_a AS \"Publiee le\",\n to_char(jour, 'DD/MM') 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": { diff --git a/deploy/historisation/grafana/dashboards/solaire.json b/deploy/historisation/grafana/dashboards/solaire.json index e87c16f..62269e0 100644 --- a/deploy/historisation/grafana/dashboards/solaire.json +++ b/deploy/historisation/grafana/dashboards/solaire.json @@ -74,7 +74,7 @@ "format": "time_series", "rawQuery": true, "editorMode": "code", - "rawSql": "SELECT ts AS time,\n max(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system') AS \"Production\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Maison\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_evcs_ac_power') AS \"Voiture\",\n max(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system')\n - max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Excedent\"\nFROM mesure\nWHERE $__timeFilter(ts)\nGROUP BY ts\nORDER BY ts" + "rawSql": "WITH par_minute AS (\n SELECT date_trunc('minute', ts) AS t,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system') AS Production,\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, Production, Maison, Voiture,\n count(Production) OVER (ORDER BY t) AS g_Production,\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(Production) OVER (PARTITION BY g_Production ORDER BY t) AS Production,\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 Production AS \"Production\",\n Maison AS \"Maison\",\n Voiture AS \"Voiture\",\n Production - Maison AS \"Excedent\"\nFROM report ORDER BY t" } ], "gridPos": { @@ -270,7 +270,7 @@ { "type": "state-timeline", "title": "Ce que les chargeurs declarent eux-memes", - "description": "mppt_active = ce chargeur donne tout ce qu'il peut. voltage_current_limited ou off = il se retient, faute de preneur. Le 03/09 ces sept capteurs ont bascule 24 minutes avant le drapeau du bilan — c'est le signal le plus direct dont on dispose.", + "description": "mppt_active = ce chargeur donne tout ce qu'il peut. voltage_current_limited ou off = il se retient, faute de preneur. Le 03/09 ces capteurs ont bascule 24 minutes avant le drapeau du bilan — c'est le signal le plus direct dont on dispose.", "datasource": { "type": "influxdb", "uid": "evcs-influx" @@ -357,7 +357,7 @@ "format": "time_series", "rawQuery": true, "editorMode": "code", - "rawSql": "SELECT ts AS time,\n max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_soc') AS \"SOC\"\nFROM mesure WHERE $__timeFilter(ts) GROUP BY ts ORDER BY ts" + "rawSql": "SELECT date_trunc('minute', ts) AS time, avg(valeur) AS \"SOC\"\nFROM mesure WHERE cle = 'sensor.victron_battery_soc'\n AND $__timeFilter(ts) GROUP BY 1 ORDER BY 1" }, { "refId": "B", @@ -368,7 +368,7 @@ "format": "time_series", "rawQuery": true, "editorMode": "code", - "rawSql": "SELECT ts AS time,\n max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_current') AS \"Courant\"\nFROM mesure WHERE $__timeFilter(ts) GROUP BY ts ORDER BY ts" + "rawSql": "SELECT date_trunc('minute', ts) AS time, avg(valeur) AS \"Courant\"\nFROM mesure WHERE cle = 'sensor.victron_battery_current'\n AND $__timeFilter(ts) GROUP BY 1 ORDER BY 1" } ], "gridPos": { diff --git a/tools/gen-grafana-dashboards.py b/tools/gen-grafana-dashboards.py index 05c7d12..e7131a1 100644 --- a/tools/gen-grafana-dashboards.py +++ b/tools/gen-grafana-dashboards.py @@ -13,6 +13,43 @@ def sql(refid, q, fmt="time_series"): def flux(refid, q): return {"refId": refid, "datasource": IN, "query": q} +def sql_grandeurs(refid, colonnes, calculees=None): + """Rapproche plusieurs grandeurs de la table `mesure` sur un axe commun. + + Deux difficultes, toutes deux resolues ici : + + 1. Le service horodate chaque mesure avec son propre `last_updated` — le + vrai instant de la mesure, ce qui est le bon choix. Mais deux grandeurs + ne partagent alors presque jamais le meme `ts` : un `GROUP BY ts` rend + une ligne par grandeur, avec une seule colonne remplie. + 2. Le service n'ecrit une mesure que si elle a change. Un simple + regroupement a la minute laisse donc des trous, et toute colonne + CALCULEE (une difference, par exemple) n'existe que les minutes ou + toutes ses entrees ont bouge — 73 minutes sur 434 le 04/09. + + D'ou le report de la derniere valeur connue, qui est aussi ce que fait le + controleur en temps reel : il decide sur la derniere valeur qu'il a lue. + L'astuce `count(x) OVER (ORDER BY t)` numerote les paliers entre deux + valeurs non nulles ; `first_value` reprend celle du debut du palier. + """ + noms = [n for _, n in colonnes] + par_minute = ",\n".join( + f" avg(valeur) FILTER (WHERE cle = '{c}') AS {n}" for c, n in colonnes) + groupes = ",\n".join(f" count({n}) OVER (ORDER BY t) AS g_{n}" for n in noms) + remplis = ",\n".join( + f" first_value({n}) OVER (PARTITION BY g_{n} ORDER BY t) AS {n}" for n in noms) + sorties = ",\n".join(f' {n} AS "{n.replace("_", " ")}"' for n in noms) + if calculees: + sorties += ",\n" + ",\n".join(f' {e} AS "{lib}"' for e, lib in calculees) + q = (f"WITH par_minute AS (\n" + f" SELECT date_trunc('minute', ts) AS t,\n{par_minute}\n" + f" FROM mesure WHERE $__timeFilter(ts) GROUP BY 1\n" + f"),\npaliers AS (\n SELECT t, {', '.join(noms)},\n{groupes}\n FROM par_minute\n" + f"),\nreport AS (\n SELECT t,\n{remplis}\n FROM paliers\n)\n" + f"SELECT t AS time,\n{sorties}\nFROM report ORDER BY t") + return sql(refid, q) + + def ts(title, targets, ds, x, y, w, h, unit="", desc="", extra=None, legend="bottom"): p = {"type": "timeseries", "title": title, "description": desc, "datasource": ds, "targets": targets, @@ -171,15 +208,11 @@ nuit = dashboard("evcs-nuit", "La nuit", ( "demande": {"color": "semi-dark-orange", "index": 2}}}]), ts("Les puissances de la nuit", [ - sql("A", "SELECT ts AS time,\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_grid_power_l1') AS \"Reseau\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_power') AS \"Batterie\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Maison\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_evcs_ac_power') AS \"Voiture\"\n" - "FROM mesure\n" - "WHERE $__timeFilter(ts)\n" - "GROUP BY ts\n" - "ORDER BY ts")], + sql_grandeurs("A", [ + ('sensor.victron_grid_power_l1', 'Reseau'), + ('sensor.victron_battery_power', 'Batterie'), + ('sensor.victron_consumption_power_l1', 'Maison'), + ('sensor.victron_evcs_ac_power', 'Voiture')])], PG, 0, 16, 12, 8, "watt", "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 " @@ -246,16 +279,11 @@ solaire = dashboard("evcs-solaire", "Le solaire", ( "Ces panneaux montrent l'ecart entre les deux."), [ ts("Production, maison, et ce qui reste", [ - sql("A", "SELECT ts AS time,\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system') AS \"Production\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Maison\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_evcs_ac_power') AS \"Voiture\",\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system')\n" - " - max(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS \"Excedent\"\n" - "FROM mesure\n" - "WHERE $__timeFilter(ts)\n" - "GROUP BY ts\n" - "ORDER BY ts")], + sql_grandeurs("A", [ + ('sensor.victron_pv_power_system', 'Production'), + ('sensor.victron_consumption_power_l1', 'Maison'), + ('sensor.victron_evcs_ac_power', 'Voiture')], + calculees=[('Production - Maison', 'Excedent')])], PG, 0, 0, 24, 9, "watt", "L'excedent (production moins maison hors voiture) est la grandeur qui " "decide d'un demarrage depuis le 03/09. Le 03/09 a 19:44, la production " @@ -308,7 +336,7 @@ solaire = dashboard("evcs-solaire", "Le solaire", ( IN, 12, 9, 12, 6, "mppt_active = ce chargeur donne tout ce qu'il peut. " "voltage_current_limited ou off = il se retient, faute de preneur. " - "Le 03/09 ces sept capteurs ont bascule 24 minutes avant le drapeau du " + "Le 03/09 ces capteurs ont bascule 24 minutes avant le drapeau du " "bilan — c'est le signal le plus direct dont on dispose.", mappings=[{"type": "value", "options": { "mppt_active": {"color": "green", "index": 0}, @@ -316,12 +344,12 @@ solaire = dashboard("evcs-solaire", "Le solaire", ( "off": {"color": "dark-blue", "index": 2}}}]), ts("Batterie", [ - sql("A", "SELECT ts AS time,\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_soc') AS \"SOC\"\n" - "FROM mesure WHERE $__timeFilter(ts) GROUP BY ts ORDER BY ts"), - sql("B", "SELECT ts AS time,\n" - " max(valeur) FILTER (WHERE cle = 'sensor.victron_battery_current') AS \"Courant\"\n" - "FROM mesure WHERE $__timeFilter(ts) GROUP BY ts ORDER BY ts")], + sql("A", "SELECT date_trunc('minute', ts) AS time, avg(valeur) AS \"SOC\"\n" + "FROM mesure WHERE cle = 'sensor.victron_battery_soc'\n" + " AND $__timeFilter(ts) GROUP BY 1 ORDER BY 1"), + sql("B", "SELECT date_trunc('minute', ts) AS time, avg(valeur) AS \"Courant\"\n" + "FROM mesure WHERE cle = 'sensor.victron_battery_current'\n" + " AND $__timeFilter(ts) GROUP BY 1 ORDER BY 1")], PG, 0, 15, 12, 8, "percent", "Le SOC n'est pas lineaire : 0,169 kWh par point au-dessus de 33 %, " "0,097 en dessous (576 paliers mesures). Un point en bas d'echelle vaut " @@ -372,20 +400,29 @@ prevu AS ( horaire[h] AS kwh FROM du_matin, generate_subscripts(horaire, 1) AS h ), -produit AS ( - -- Un echantillon toutes les 60 s : chaque point vaut 1/60 h. - SELECT date_trunc('hour', ts) AS heure, - sum(valeur) / 60.0 / 1000.0 AS kwh +-- INTEGRATION PONDEREE PAR LA DUREE, et non somme/60. +-- Le service n'ecrit une mesure que lorsqu'elle change : compter les +-- echantillons revient a supposer qu'il y en a 60 par heure alors qu'il y +-- en a eu 26 a 47 le 04/09 : l'energie etait sous-estimee d'environ 9 %. +-- Chaque point vaut donc son intervalle jusqu'au suivant, plafonne a 300 s +-- pour qu'un arret du service n'extrapole pas une valeur perimee. +pas AS ( + SELECT ts, valeur, + LEAST(EXTRACT(epoch FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) AS dt FROM mesure WHERE cle = 'sensor.victron_pv_power_system' AND $__timeFilter(ts) - GROUP BY 1 +), +produit AS ( + SELECT date_trunc('hour', ts) AS heure, + sum(valeur * coalesce(dt, 0)) / 3600.0 / 1000.0 AS kwh + FROM pas GROUP BY 1 ) -SELECT coalesce(prevu.heure, produit.heure) AS time, - round(prevu.kwh::numeric, 2) AS "Prevu le matin meme", - round(produit.kwh::numeric, 2) AS "Reellement produit" +SELECT to_char(coalesce(prevu.heure, produit.heure), 'DD/MM HH24') || ' h' AS "Heure", + round(prevu.kwh::numeric, 2) AS "Prevu le matin meme", + round(produit.kwh::numeric, 2) AS "Reellement produit" FROM prevu FULL OUTER JOIN produit USING (heure) WHERE coalesce(prevu.heure, produit.heure) BETWEEN $__timeFrom() AND $__timeTo() -ORDER BY 1""" +ORDER BY coalesce(prevu.heure, produit.heure)""" prevision = dashboard("evcs-prevision", "Prevision contre reel", ( "Le total de la prevision tombe juste ; sa FORME non. Sur les trois jours " @@ -414,7 +451,8 @@ prevision = dashboard("evcs-prevision", "Prevision contre reel", ( "bridee (mesure inexploitable)": {"color": "orange", "index": 1}}}]), table("Les previsions telles qu'elles ont ete publiees", [ - sql("A", "SELECT publie_a AS \"Publiee le\", jour AS \"Pour le jour\",\n" + sql("A", "SELECT publie_a AS \"Publiee le\",\n" + " to_char(jour, 'DD/MM') 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"