Une session de charge ne raconte plus celle d avant

Le journal a annonce deux fois 4,20 kWh : la vraie charge de nuit, puis onze
minutes de charge diurne. L energie de la borne etait echantillonnee a chaque
cycle et conservee par un GREATEST — un operateur monotone, qui ne peut que
surestimer et qui verrouille la premiere valeur vue. Au premier echantillon de
la session de midi, le capteur portait encore le total de la nuit.

Confrontation des cinq lignes a l integration relue dans InfluxDB sur les
periodes de charge reelles : ecarts de 1, 5, 14, 60 et 1135 %.

Le compteur cumulatif de la borne devient la mesure de reference : releve a
l ouverture, sa difference a la cloture donne l energie sans ambiguite de
semantique ni erreur d integration. last_session_energy est lu une seule fois,
a la cloture, et enregistre comme observation ; le journal ne l affiche que
s il s ecarte de plus de 10 %, ce qui rend visible le cas d une session
commencee avant le demarrage du service au lieu de le substituer en silence.
Les treize capteurs de la borne entrent enfin dans l historisation InfluxDB :
c est leur absence qui empechait d etablir cette semantique.

Second defaut de la meme fonction : l heure de debranchement etait posee en
bloc sur toutes les sessions non marquees des dix-huit dernieres heures. La
session de nuit de la JEEP a ainsi herite du debranchement de la VW — l inverse
du role de cette colonne. La duree de branchement la borne desormais.

Reparation idempotente des lignes existantes, limitee a ce qui a ete verifie.

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-06 14:03:17 +02:00
parent c7e665cd5c
commit c3ef58dd18
3 changed files with 237 additions and 18 deletions

View File

@@ -4596,6 +4596,81 @@ session de `tools/historise-evcs.mjs` semble rejouer la session de la nuit au
lieu d'en ouvrir une nouvelle. A corriger, sans quoi la table `session_charge` lieu d'en ouvrir une nouvelle. A corriger, sans quoi la table `session_charge`
double-compte. double-compte.
### Double comptage des sessions de charge — corrige
`Session de charge terminee : JEEP, 4.20 kWh` a ete emis a 02:52 (vraie charge
HC) puis a 13:42 (onze minutes de charge diurne), avec la MEME valeur au
centieme.
Cause : `energie_station_kwh` etait echantillonne a chaque cycle depuis
`sensor.evcs_hq2239dnt7p_last_session_energy` et conserve par un **`GREATEST`**
— un operateur monotone, qui ne peut donc que SURESTIMER et qui verrouille la
premiere valeur vue. Au premier echantillon de la session de midi, le capteur
portait encore le total de la nuit ; le maximum l'a fige pour toujours.
Confrontation de chaque ligne a l'integration de `victron_evcs_ac_power` relue
dans InfluxDB sur la periode de charge REELLE (periodes > 300 W separees de plus
de dix minutes) :
| periode reelle | energie reelle | valeur en base | ecart |
|---|---:|---:|---:|
| 05 01:26-03:55 | 8,27 kWh | 9,400 | 14 % |
| 05 10:00-10:54 | 2,32 kWh | 2,200 | 5 % |
| 05 11:11-12:38 | 3,37 kWh | 5,400 | **60 %** |
| 06 01:25-02:44 | 4,24 kWh | 4,200 | 1 % |
| 06 13:32-13:42 | 0,34 kWh | 4,200 | **1135 %** |
⚠️ **La semantique de `last_session_energy` n'etait pas etablie** — le capteur
n'etait pas historise, donc aucune serie ne permettait de trancher entre
« derniere session terminee » et « session en cours avec reinitialisation
tardive ». Deux indices concordants recueillis apres coup : au moment de la
reparation il lisait 0,3 kWh et `..._last_session_time` 11 min, soit exactement
la session de midi. Il decrit donc la derniere session TERMINEE, et le lire
PENDANT une session donne la precedente.
Correctif (`tools/historise-evcs.mjs`) :
1. **Le compteur CUMULATIF de la borne** (`..._total_energy`, ~3 780 kWh)
devient la mesure de reference : releve a l'ouverture
(`compteur_debut_kwh`), sa difference a la cloture donne l'energie sans
ambiguite de semantique NI erreur d'integration. C'est desormais le chiffre
annonce.
2. `last_session_energy` et `last_session_time` sont lus **une seule fois, a la
cloture**, et enregistres comme des OBSERVATIONS
(`energie_station_kwh`, `duree_borne_min`). Le journal ne les affiche que
s'ils s'ecartent de plus de 10 % — ce qui rend visible le cas qui avait
motive la colonne (le 05/09, 8 kWh reels pour 0,75 integres, session commencee
avant le demarrage du service) au lieu de les substituer en silence.
3. Plus aucun `GREATEST` sur une grandeur d'energie.
4. **Les treize capteurs `sensor.evcs_*` de la borne entrent dans
l'historisation InfluxDB** (`configuration.yaml`, sauvegarde dans
`SAVE_CONFIG/`) : c'est leur absence qui a empeche d'etablir la semantique.
⚠️ Prend effet au prochain redemarrage de Home Assistant (les filtres
`include`/`exclude` d'InfluxDB restent en YAML et n'ont pas de rechargement).
### Meme fonction, second defaut : l'heure de debranchement posee en bloc
`debranche_a` etait ecrit par un UPDATE **sans limite** sur toutes les sessions
non marquees des dix-huit dernieres heures. Le 05/09 a 15:02:16, les trois
sessions ouvertes ont recu la meme heure — dont **la session de nuit de la JEEP,
qui a herite du debranchement de la VW**. Or cette colonne existe precisement
pour departager les deux vehicules apres coup : elle affirmait l'inverse de son
role.
Correctif : la fenetre est bornee par
`sensor.victron_evcs_charging_time`, qui repart de zero a chaque connexion
(signal du reset P11) et se fige a la valeur du debranchement — il donne donc la
duree exacte du branchement qui s'acheve. Duree illisible ou nulle => on ne
marque que la session la plus recente, jamais celles d'avant.
### Reparation des lignes existantes (migration idempotente)
Appliquee au prochain demarrage du service. On ne corrige QUE ce qui a ete
verifie : la ligne du 06 13:31 passe a 0,3 kWh (valeur du capteur au moment de
la reparation, corroboree par la duree de 11 min), celle du 05 11:11 est
EFFACEE (60 % d'ecart, aucun substitut fiable), les trois lignes coherentes ne
sont pas touchees, et le `debranche_a` errone de la session JEEP du 05 02:56
repasse a NULL — on efface plutot que d'inventer.
⚠️ `debut` porte les millisecondes : les reparations ciblent une fenetre d'une
seconde et non une egalite, et la condition sur la valeur attendue les rend
idempotentes. Verifie : chaque fenetre designe exactement une ligne.
### Reste ouvert ### Reste ouvert
- Detecteur de gel dans le service d'historisation : une grandeur qui ne bouge - Detecteur de gel dans le service d'historisation : une grandeur qui ne bouge
pas alors qu'une autre du meme bilan bouge = alerte. Non fait. pas alors qu'une autre du meme bilan bouge = alerte. Non fait.

View File

@@ -404,3 +404,11 @@ heure_reelle,etat,courant_mA,c1_mV,c2_mV,c3_mV,c4_mV,c5_mV,c6_mV,c7_mV,c8_mV,c9_
2026-09-06T11:41:05.944Z,Y230202C30371425,,3350,3350,3350,3350,3350,3349,3350,3349,3349,3350,3350,3350,3350,3350,3350,1 2026-09-06T11:41:05.944Z,Y230202C30371425,,3350,3350,3350,3350,3350,3349,3350,3349,3349,3350,3350,3350,3350,3350,3350,1
2026-09-06T11:41:06.039Z,Y230202C30371431,,3349,3348,3348,3348,3348,3349,3347,3348,3347,3347,3349,3348,3348,3348,3347,2 2026-09-06T11:41:06.039Z,Y230202C30371431,,3349,3348,3348,3348,3348,3349,3347,3348,3347,3347,3349,3348,3348,3348,3347,2
2026-09-06T11:41:06.226Z,Y230202C30371427,,3342,3342,3343,3344,3343,3342,3343,3343,3343,3343,3342,3343,3343,3344,3344,2 2026-09-06T11:41:06.226Z,Y230202C30371427,,3342,3342,3343,3344,3343,3342,3343,3343,3343,3343,3342,3343,3343,3344,3344,2
2026-09-06T11:51:06.330Z,Y230202C30371426,,3342,3343,3343,3344,3344,3342,3343,3343,3342,3343,3342,3343,3343,3343,3344,2
2026-09-06T11:51:06.426Z,Y230202C30371425,,3406,3405,3405,3407,3405,3403,3406,3404,3406,3404,3406,3407,3408,3405,3406,5
2026-09-06T11:51:06.552Z,Y230202C30371431,,3405,3404,3404,3402,3409,3403,3401,3402,3402,3402,3407,3402,3403,3402,3403,8
2026-09-06T11:51:06.678Z,Y230202C30371427,,3404,3403,3404,3404,3406,3405,3404,3404,3404,3404,3404,3404,3406,3407,3405,4
2026-09-06T12:01:06.782Z,Y230202C30371426,,3403,3402,3404,3406,3406,3406,3403,3404,3399,3404,3404,3405,3404,3403,3405,7
2026-09-06T12:01:06.911Z,Y230202C30371425,,3412,3411,3409,3414,3409,3408,3411,3409,3411,3409,3411,3412,3410,3411,3408,6
2026-09-06T12:01:07.007Z,Y230202C30371431,,3414,3413,3413,3411,3424,3411,3408,3410,3410,3404,3415,3409,3412,3410,3412,20
2026-09-06T12:01:07.135Z,Y230202C30371427,,3413,3409,3411,3411,3406,3413,3410,3408,3412,3411,3411,3411,3415,3416,3407,10
1 heure_reelle etat courant_mA c1_mV c2_mV c3_mV c4_mV c5_mV c6_mV c7_mV c8_mV c9_mV c10_mV c11_mV c12_mV c13_mV c14_mV c15_mV ecart_mV
404 2026-09-06T11:41:05.944Z Y230202C30371425 3350 3350 3350 3350 3350 3349 3350 3349 3349 3350 3350 3350 3350 3350 3350 1
405 2026-09-06T11:41:06.039Z Y230202C30371431 3349 3348 3348 3348 3348 3349 3347 3348 3347 3347 3349 3348 3348 3348 3347 2
406 2026-09-06T11:41:06.226Z Y230202C30371427 3342 3342 3343 3344 3343 3342 3343 3343 3343 3343 3342 3343 3343 3344 3344 2
407 2026-09-06T11:51:06.330Z Y230202C30371426 3342 3343 3343 3344 3344 3342 3343 3343 3342 3343 3342 3343 3343 3343 3344 2
408 2026-09-06T11:51:06.426Z Y230202C30371425 3406 3405 3405 3407 3405 3403 3406 3404 3406 3404 3406 3407 3408 3405 3406 5
409 2026-09-06T11:51:06.552Z Y230202C30371431 3405 3404 3404 3402 3409 3403 3401 3402 3402 3402 3407 3402 3403 3402 3403 8
410 2026-09-06T11:51:06.678Z Y230202C30371427 3404 3403 3404 3404 3406 3405 3404 3404 3404 3404 3404 3404 3406 3407 3405 4
411 2026-09-06T12:01:06.782Z Y230202C30371426 3403 3402 3404 3406 3406 3406 3403 3404 3399 3404 3404 3405 3404 3403 3405 7
412 2026-09-06T12:01:06.911Z Y230202C30371425 3412 3411 3409 3414 3409 3408 3411 3409 3411 3409 3411 3412 3410 3411 3408 6
413 2026-09-06T12:01:07.007Z Y230202C30371431 3414 3413 3413 3411 3424 3411 3408 3410 3410 3404 3415 3409 3412 3410 3412 20
414 2026-09-06T12:01:07.135Z Y230202C30371427 3413 3409 3411 3411 3406 3413 3410 3408 3412 3411 3411 3411 3415 3416 3407 10

View File

@@ -374,9 +374,70 @@ async function migrer(client) {
// 03:16 — 0,75 kWh au lieu de 8. La borne, elle, compte depuis le debut. // 03:16 — 0,75 kWh au lieu de 8. La borne, elle, compte depuis le debut.
await client.query( await client.query(
'ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS energie_station_kwh double precision'); 'ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS energie_station_kwh double precision');
// Le COMPTEUR CUMULATIF de la borne (`..._total_energy`, ~3 780 kWh au
// 06/09). Sa difference entre l'ouverture et la cloture donne l'energie de
// la session sans aucune ambiguite de semantique et sans erreur
// d'integration — contrairement a `..._last_session_energy`, dont on ne
// savait meme pas s'il decrivait la session en cours ou la precedente.
// Il ne couvre que ce que le service a vu ; `duree_borne_min`
// (`..._last_session_time`) dit si la borne, elle, en a vu davantage.
for (const col of ['compteur_debut_kwh', 'energie_compteur_kwh', 'duree_borne_min']) {
await client.query(
`ALTER TABLE session_charge ADD COLUMN IF NOT EXISTS ${col} double precision`);
}
await client.query( await client.query(
'CREATE INDEX IF NOT EXISTS session_charge_debut_idx ON session_charge (debut DESC)'); 'CREATE INDEX IF NOT EXISTS session_charge_debut_idx ON session_charge (debut DESC)');
// -------------------------------------------------------------------
// REPARATION UNIQUE (06/09/2026) — sessions faussees par le GREATEST
// -------------------------------------------------------------------
// Chaque ligne est confrontee a l'integration de `victron_evcs_ac_power`
// relue dans InfluxDB sur la periode de charge REELLE (detection des
// periodes > 300 W separees par plus de dix minutes) :
//
// periode reelle energie reelle valeur en base ecart
// 05 01:26-03:55 8,27 kWh 9,400 14 % -> gardee
// 05 10:00-10:54 2,32 kWh 2,200 5 % -> gardee
// 05 11:11-12:38 3,37 kWh 5,400 60 % -> effacee
// 06 01:25-02:44 4,24 kWh 4,200 1 % -> gardee
// 06 13:32-13:42 0,34 kWh 4,200 1135 % -> corrigee
//
// La derniere est celle qui a produit le double comptage dans le journal :
// le total de la nuit, capture au premier echantillon et verrouille par le
// GREATEST. Le capteur de la borne lisait 0,3 kWh au moment de la reparation,
// et `last_session_time` 11 min — la duree exacte de cette session.
// On ne corrige QUE ce qu'on a verifie ; les trois lignes coherentes ne sont
// pas touchees, et celle a 60 % d'ecart est effacee plutot que devinee.
//
// ⚠️ `debut` porte les millisecondes : on cible une FENETRE D'UNE SECONDE,
// pas une egalite. Deux sessions ne peuvent pas s'ouvrir dans la meme
// seconde, et la condition sur la valeur attendue rend l'operation
// idempotente — rejouee, elle ne trouve plus rien.
const reparations = [
['2026-09-06 13:31:18+02', 0.3, 4.2],
['2026-09-05 11:11:22+02', null, 5.4],
];
for (const [debut, valeur, attendu] of reparations) {
await client.query(
`UPDATE session_charge SET energie_station_kwh = $2
WHERE debut >= $1::timestamptz AND debut < $1::timestamptz + interval '1 second'
AND energie_station_kwh = $3::float8`,
[debut, valeur, attendu]);
}
// Meme jour, meme fonction : l'heure de debranchement etait posee en bloc sur
// toutes les sessions non marquees des dix-huit dernieres heures. Le 05/09 a
// 15:02:16, la session de nuit de la JEEP a donc herite du debranchement de
// la VW. On ne sait pas quand la JEEP a ete debranchee : on efface plutot que
// d'inventer.
// Les deux sessions VW de la journee (10:00 et 11:11) appartiennent bien au
// meme branchement que ce debranchement de 15:02 : elles sont conservees.
await client.query(
`UPDATE session_charge SET debranche_a = NULL
WHERE debut >= '2026-09-05 02:56:13+02'::timestamptz
AND debut < '2026-09-05 02:56:14+02'::timestamptz
AND debranche_a >= '2026-09-05 15:02:16+02'::timestamptz
AND debranche_a < '2026-09-05 15:02:17+02'::timestamptz`);
// Tensions cellule par cellule des modules Pylontech. La source est la // Tensions cellule par cellule des modules Pylontech. La source est la
// console serie du BMS, pas Home Assistant : aucune de ces valeurs n'est // console serie du BMS, pas Home Assistant : aucune de ces valeurs n'est
// publiee cote Victron, qui n'expose que l'agregat du parc. // publiee cote Victron, qui n'expose que l'agregat du parc.
@@ -424,6 +485,20 @@ async function migrer(client) {
const SEUIL_CHARGE_W = 300; // au-dela, la voiture prend vraiment du courant const SEUIL_CHARGE_W = 300; // au-dela, la voiture prend vraiment du courant
const FIN_APRES_MIN = 10; // sous le seuil pendant ce temps : session close const FIN_APRES_MIN = 10; // sous le seuil pendant ce temps : session close
// "1h 49m 53s" / "8m 27s" / "0s" -> secondes. Meme lecture que INPUT_PARSE.
// `sensor.victron_evcs_charging_time` repart de zero a CHAQUE nouvelle
// connexion physique (c'est le signal du reset P11) et se fige a la valeur du
// debranchement : il donne donc la duree exacte du branchement qui s'acheve.
function dureeBranchementSec(s) {
const t = (s ?? '').toString().toLowerCase();
if (!t) return null;
let sec = 0, vu = false;
const h = t.match(/(\d+)\s*h/); if (h) { sec += Number(h[1]) * 3600; vu = true; }
const m = t.match(/(\d+)\s*m/); if (m) { sec += Number(m[1]) * 60; vu = true; }
const q = t.match(/(\d+)\s*s/); if (q) { sec += Number(q[1]); vu = true; }
return vu ? sec : null;
}
async function ecrireSession(sql, client, st) { async function ecrireSession(sql, client, st) {
// En blanc la base n'est pas connectee : on lit du vide plutot que d'echouer. // En blanc la base n'est pas connectee : on lit du vide plutot que d'echouer.
// La logique se deroule alors comme si aucune session n'etait ouverte — ce // La logique se deroule alors comme si aucune session n'etait ouverte — ce
@@ -445,14 +520,14 @@ async function ecrireSession(sql, client, st) {
await sql( await sql(
`INSERT INTO session_charge (debut, fin, vehicule, energie_kwh, p_max_w, `INSERT INTO session_charge (debut, fin, vehicule, energie_kwh, p_max_w,
courant_max_a, consigne_max_a, source_mode, brut, courant_max_a, consigne_max_a, source_mode, brut,
energie_station_kwh) compteur_debut_kwh)
VALUES ($1,$1,$2,0,$3,$4,$5,$6,$7,$8) ON CONFLICT (debut) DO NOTHING`, VALUES ($1,$1,$2,0,$3,$4,$5,$6,$7,$8) ON CONFLICT (debut) DO NOTHING`,
[maintenant, vehicule, p, [maintenant, vehicule, p,
num(g('sensor.victron_evcs_current')?.state), num(g('sensor.victron_evcs_current')?.state),
num(g('sensor.victron_evcs_set_current')?.state), num(g('sensor.victron_evcs_set_current')?.state),
txt(g('input_text.victron_evcs_source_mode')?.state) || null, txt(g('input_text.victron_evcs_source_mode')?.state) || null,
JSON.stringify({ profil: txt(g('input_text.victron_evcs_operating_mode')?.state) }), JSON.stringify({ profil: txt(g('input_text.victron_evcs_operating_mode')?.state) }),
num(g('sensor.evcs_hq2239dnt7p_last_session_energy')?.state)]); num(g('sensor.evcs_hq2239dnt7p_total_energy')?.state)]);
n++; n++;
} else { } else {
// Le pas de temps est plafonne : un arret du service ne doit pas // Le pas de temps est plafonne : un arret du service ne doit pas
@@ -475,39 +550,100 @@ async function ecrireSession(sql, client, st) {
p_max_w = GREATEST(coalesce(p_max_w, 0), $3), p_max_w = GREATEST(coalesce(p_max_w, 0), $3),
courant_max_a = GREATEST(coalesce(courant_max_a, 0), coalesce($5, 0)), courant_max_a = GREATEST(coalesce(courant_max_a, 0), coalesce($5, 0)),
consigne_max_a = GREATEST(coalesce(consigne_max_a, 0), coalesce($6, 0)), consigne_max_a = GREATEST(coalesce(consigne_max_a, 0), coalesce($6, 0)),
energie_station_kwh = GREATEST(coalesce(energie_station_kwh, 0), coalesce($9::float8, 0)),
vehicule = coalesce($7, vehicule), vehicule = coalesce($7, vehicule),
vehicule_sur = vehicule_sur AND ($7 IS NULL OR vehicule IS NULL OR vehicule = $7) vehicule_sur = vehicule_sur AND ($7 IS NULL OR vehicule IS NULL OR vehicule = $7)
WHERE debut = $1`, WHERE debut = $1`,
[ouverte.debut, maintenant, p, dt, [ouverte.debut, maintenant, p, dt,
num(g('sensor.victron_evcs_current')?.state), num(g('sensor.victron_evcs_current')?.state),
num(g('sensor.victron_evcs_set_current')?.state), vehicule, num(g('sensor.victron_evcs_set_current')?.state), vehicule,
txt(g('input_text.victron_evcs_source_mode')?.state).startsWith('Réseau') ? 1 : 0, txt(g('input_text.victron_evcs_source_mode')?.state).startsWith('Réseau') ? 1 : 0]);
num(g('sensor.evcs_hq2239dnt7p_last_session_energy')?.state)]);
n++; n++;
} }
} else if (ouverte) { } else if (ouverte) {
const silence = (maintenant - new Date(ouverte.fin)) / 60000; const silence = (maintenant - new Date(ouverte.fin)) / 60000;
if (silence >= FIN_APRES_MIN || !connecte) { if (silence >= FIN_APRES_MIN || !connecte) {
await sql('UPDATE session_charge SET terminee = true WHERE debut = $1', [ouverte.debut]); // Le compteur de la borne se lit UNE FOIS, A LA CLOTURE.
//
// Il etait auparavant echantillonne a chaque cycle et conserve par un
// GREATEST : un operateur monotone, qui ne peut donc que SUREVALUER, et
// qui verrouillait la premiere valeur vue. Le 06/09 la session de midi
// (0,34 kWh reels, 0,324 integres) s'est refermee sur 4,20 kWh — le
// total de la nuit precedente, capture au premier echantillon et jamais
// relache. D'ou le meme chiffre annonce deux fois dans le journal.
//
// ⚠️ La semantique exacte de ce capteur n'est PAS etablie : il n'etait
// pas historise, donc on n'a aucune serie pour trancher entre « energie
// de la derniere session terminee » et « compteur de la session en cours
// qui se reinitialise avec du retard ». Confrontation aux integrations
// reelles des cinq sessions connues : ecarts de 1, 5, 14, 60 et 1135 %.
// Il est donc enregistre comme une OBSERVATION, jamais substitue en
// silence a notre integration. Le capteur vient d'etre ajoute a
// l'historisation InfluxDB pour pouvoir enfin l'etablir.
const station = num(g('sensor.evcs_hq2239dnt7p_last_session_energy')?.state);
const dureeBorne = num(g('sensor.evcs_hq2239dnt7p_last_session_time')?.state);
const compteur = num(g('sensor.evcs_hq2239dnt7p_total_energy')?.state);
const parCompteur = compteur !== null && ouverte.compteur_debut_kwh !== null
&& compteur >= ouverte.compteur_debut_kwh
? compteur - Number(ouverte.compteur_debut_kwh) : null;
await sql(
`UPDATE session_charge SET terminee = true, energie_station_kwh = $2,
energie_compteur_kwh = $3, duree_borne_min = $4
WHERE debut = $1`,
[ouverte.debut, station, parCompteur, dureeBorne]);
// Le chiffre annonce est le NOTRE, celui qu'on sait defendre. Celui de la
// borne n'apparait que s'il s'en ecarte notablement — soit qu'elle ait vu
// un debut que le service a manque (le cas qui a motive cette colonne :
// 8 kWh reels pour 0,75 integres le 05/09), soit qu'elle raconte autre
// chose, et dans les deux cas ca se voit.
// Le chiffre annonce est, par ordre de preference : la difference du
// compteur cumulatif de la borne (exacte, sans integration), sinon notre
// integration de la puissance. Celui de `last_session_energy` n'apparait
// que s'il s'en ecarte notablement — soit que la borne ait vu un debut
// que le service a manque (le cas qui a motive cette colonne : 8 kWh
// reels pour 0,75 integres le 05/09), soit qu'elle raconte autre chose,
// et dans les deux cas ca se voit au lieu de se substituer en silence.
const retenu = parCompteur !== null ? parCompteur : Number(ouverte.energie_kwh);
const ecart = station !== null && retenu > 0
? Math.abs(station - retenu) / retenu : 0;
await journaliser(sql, 'vehicule', await journaliser(sql, 'vehicule',
`Session de charge terminee : ${ouverte.vehicule ?? 'vehicule inconnu'}, ` + `Session de charge terminee : ${ouverte.vehicule ?? 'vehicule inconnu'}, `
`${Number(ouverte.energie_station_kwh ?? ouverte.energie_kwh).toFixed(2)} kWh`, + `${retenu.toFixed(2)} kWh`
{ debut: ouverte.debut, vehicule: ouverte.vehicule, kwh: ouverte.energie_kwh }); + (ecart > 0.10 ? ` (borne : ${station.toFixed(2)} kWh sur ${dureeBorne ?? '?'} min)` : ''),
{ debut: ouverte.debut, vehicule: ouverte.vehicule, kwh: retenu,
kwh_integre: Number(ouverte.energie_kwh), kwh_compteur: parCompteur,
kwh_borne: station, duree_borne_min: dureeBorne });
n += 2; n += 2;
} }
} }
// L'heure de debranchement, notee sur la derniere session. Elle confirme le // L'heure de debranchement. Elle confirme le vehicule apres coup : les deux
// vehicule apres coup : les deux partent a des heures differentes. // partent a des heures differentes.
//
// ⚠️ Ne marquer QUE les sessions du branchement qui vient de s'achever. La
// version d'origine marquait en bloc toutes les sessions non marquees des
// dix-huit dernieres heures : le 05/09 a 15:02, la session de nuit de la
// JEEP a herite de l'heure de debranchement de la VW — soit exactement
// l'inverse de ce que cette colonne est censee etablir. La duree du
// branchement borne la fenetre (5 min de marge pour le pas de mesure) ;
// duree illisible ou nulle => on ne marque que la session la plus recente,
// jamais celles d'avant.
if (!connecte) { if (!connecte) {
const { rowCount } = await lire( const dureeSec = dureeBranchementSec(txt(g('sensor.victron_evcs_charging_time')?.state));
`SELECT 1 FROM session_charge const depuis = dureeSec !== null && dureeSec > 0
WHERE debranche_a IS NULL AND fin > now() - interval '18 hours' LIMIT 1`); ? new Date(maintenant.getTime() - dureeSec * 1000 - 5 * 60000)
if (rowCount) { : null;
await sql( const { rows: aMarquer } = await lire(
`UPDATE session_charge SET debranche_a = $1 depuis
WHERE debranche_a IS NULL AND fin > now() - interval '18 hours'`, [maintenant]); ? `SELECT debut FROM session_charge
WHERE debranche_a IS NULL AND fin > now() - interval '18 hours' AND debut >= $1`
: `SELECT debut FROM session_charge
WHERE debranche_a IS NULL AND fin > now() - interval '18 hours'
ORDER BY debut DESC LIMIT 1`,
depuis ? [depuis] : []);
for (const r of aMarquer) {
await sql('UPDATE session_charge SET debranche_a = $2 WHERE debut = $1',
[r.debut, maintenant]);
n++; n++;
} }
} }