vehicule_sur cesse de mentir : le drapeau dit desormais qu un humain a confirme

Les trois pieces soudees, parce qu aucune ne tient seule. Le defaut de la
colonne passe a faux ; plus rien dans le chemin d ecriture ordinaire ne peut le
mettre a vrai ; le libelle Grafana « selecteur change » devient « a confirmer »,
et le compteur « Etiquette douteuse » devient « A confirmer ».

L ancienne semantique — « le selecteur n a pas bouge pendant la session » — ne
prouvait rien : le selecteur est reste sur VW du 07/09 au 13/09 et les onze
sessions fausses portaient toutes true. La mesure d hier a acheve de la
disqualifier : sur douze branchements, le selecteur n a ete regle au bon moment
qu une seule fois.

Rattrapage des lignes anterieures a faux, sauf les six que le proprietaire a
arbitrees lui-meme le 13/09 — une vraie confirmation humaine, seulement donnee
de vive voix, qu il serait absurde de jeter. Une ligne de journal l explique.

Rien n est deploye : le service tourne encore l ancien code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-14 02:16:58 +02:00
parent 3f987f1b3d
commit d8ef69b584
3 changed files with 41 additions and 7 deletions

View File

@@ -77,7 +77,7 @@
"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 count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"Etiquette douteuse\"\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 count(*) AS \"Sessions\",\n count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"A confirmer\"\nFROM session_charge WHERE $__timeFilter(debut)"
}
],
"gridPos": {
@@ -267,7 +267,7 @@
"format": "table",
"rawQuery": true,
"editorMode": "code",
"rawSql": "SELECT to_char(debut, 'Dy DD/MM HH24:MI') AS \"Debut\",\n coalesce(vehicule, '?') AS \"Vehicule\",\n CASE WHEN vehicule_sur THEN '' ELSE 'selecteur change' END AS \"Doute\",\n round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n round(coalesce(energie_station_kwh, energie_kwh)::numeric, 2) AS \"kWh\",\n round(energie_kwh::numeric, 2) AS \"kWh integres\",\n round(energie_reseau_kwh::numeric, 2) AS \"dont reseau\",\n round(p_max_w::numeric) AS \"P max\",\n consigne_max_a AS \"Consigne max\",\n source_mode AS \"Source au depart\",\n to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\nFROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC"
"rawSql": "SELECT to_char(debut, 'Dy DD/MM HH24:MI') AS \"Debut\",\n coalesce(vehicule, '?') AS \"Vehicule\",\n CASE WHEN vehicule_sur THEN 'confirme' ELSE 'a confirmer' END AS \"Etiquette\",\n round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n round(coalesce(energie_station_kwh, energie_kwh)::numeric, 2) AS \"kWh\",\n round(energie_kwh::numeric, 2) AS \"kWh integres\",\n round(energie_reseau_kwh::numeric, 2) AS \"dont reseau\",\n round(p_max_w::numeric) AS \"P max\",\n consigne_max_a AS \"Consigne max\",\n source_mode AS \"Source au depart\",\n to_char(debranche_a, 'Dy HH24:MI') AS \"Debranche\"\nFROM session_charge WHERE $__timeFilter(debut) ORDER BY debut DESC"
}
],
"gridPos": {

View File

@@ -1029,7 +1029,7 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
"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"
" count(*) AS \"Sessions\",\n"
" count(*) FILTER (WHERE NOT vehicule_sur OR vehicule IS NULL) AS \"Etiquette douteuse\"\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 "
@@ -1056,7 +1056,7 @@ vehicules = dashboard("evcs-vehicules", "Les vehicules", (
sql("A",
"SELECT to_char(debut, 'Dy DD/MM HH24:MI') AS \"Debut\",\n"
" coalesce(vehicule, '?') AS \"Vehicule\",\n"
" CASE WHEN vehicule_sur THEN '' ELSE 'selecteur change' END AS \"Doute\",\n"
" CASE WHEN vehicule_sur THEN 'confirme' ELSE 'a confirmer' END AS \"Etiquette\",\n"
" round((extract(epoch FROM fin - debut) / 60)::numeric) AS \"Minutes\",\n"
" round(coalesce(energie_station_kwh, energie_kwh)::numeric, 2) AS \"kWh\",\n"
" round(energie_kwh::numeric, 2) AS \"kWh integres\",\n"

View File

@@ -434,7 +434,7 @@ async function migrer(client) {
fin timestamptz NOT NULL,
terminee boolean NOT NULL DEFAULT false,
vehicule text,
vehicule_sur boolean NOT NULL DEFAULT true,
vehicule_sur boolean NOT NULL DEFAULT false,
energie_kwh double precision NOT NULL DEFAULT 0,
p_max_w double precision,
courant_max_a double precision,
@@ -466,6 +466,35 @@ async function migrer(client) {
await client.query(
`ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS ${col} double precision`);
}
// ---------------------------------------------------------------------
// `vehicule_sur` CHANGE DE SENS (14/09/2026)
// ---------------------------------------------------------------------
// Avant : « le selecteur n'a pas change PENDANT la session ». Une propriete
// qui ne prouvait rien — le selecteur est reste sur VW du 07/09 au 13/09 et
// onze sessions fausses portaient toutes `true`. Mesure faite depuis : sur
// douze branchements, le selecteur n'a ete regle au bon moment QU'UNE FOIS.
// Desormais : « un humain a confirme », et seul le concierge le pose.
await client.query(
'ALTER TABLE session_charge ALTER COLUMN vehicule_sur SET DEFAULT false');
// Rattrapage des lignes anterieures, une seule fois. Rien n'ayant ete
// confirme par le canal tracable, tout retombe a faux — SAUF les six lignes
// que le proprietaire a lui-meme arbitrees le 13/09 (cf. la reparation
// ci-dessus) : c'est une vraie confirmation humaine, seulement donnee de
// vive voix. La perdre serait jeter l'information qu'il a fournie.
const { rowCount: bascules } = await client.query(
`UPDATE session_charge SET vehicule_sur = false
WHERE vehicule_sur AND debut < '2026-09-14'::date`);
if (bascules) {
await client.query(
`UPDATE session_charge SET vehicule_sur = true WHERE debut = ANY($1::timestamptz[])`,
[['2026-09-09 15:55:15.423+02', '2026-09-09 19:35:51.789+02',
'2026-09-10 01:25:24.359+02', '2026-09-11 16:54:58.101+02',
'2026-09-12 11:42:56.901+02', '2026-09-12 11:55:54.493+02']]);
await journaliser(client.query.bind(client), 'vehicule',
`Semantique de vehicule_sur inversee : ${bascules} lignes repassees a « non confirme »`,
{ origine: 'proprietaire', motif: 'le drapeau dit desormais « un humain a confirme »',
confirmees_de_vive_voix: 6, date_arbitrage: '2026-09-13' });
}
await client.query(
'CREATE INDEX IF NOT EXISTS session_charge_debut_idx ON session_charge (debut DESC)');
@@ -710,8 +739,13 @@ async function ecrireSession(sql, client, st) {
p_max_w = GREATEST(coalesce(p_max_w, 0), $3),
courant_max_a = GREATEST(coalesce(courant_max_a, 0), coalesce($5, 0)),
consigne_max_a = GREATEST(coalesce(consigne_max_a, 0), coalesce($6, 0)),
vehicule = coalesce($7, vehicule),
vehicule_sur = vehicule_sur AND ($7 IS NULL OR vehicule IS NULL OR vehicule = $7)
-- vehicule_sur n'est PLUS touche ici, et la virgule de la ligne
-- precedente a disparu avec lui. Le drapeau ne dit plus « le
-- selecteur n'a pas bouge » — une propriete qui ne prouvait rien,
-- un selecteur perime depuis six jours restant « sur » — mais
-- « UN HUMAIN A CONFIRME ». Seule une reponse du proprietaire,
-- via le concierge, peut le mettre a vrai.
vehicule = coalesce($7, vehicule)
WHERE debut = $1`,
[ouverte.debut, maintenant, p, dt,
num(g('sensor.victron_evcs_current')?.state),