Session reseau unique de la nuit : engagement sur le bilan, sortie sur la fenetre

§26 avait pose l objectif et designe le critere. Avant d ecrire quoi que ce
soit, j ai mesure les kWh reellement sortis de la batterie par point de SOC sur
cinq fenetres et trois nuits : 0,164 a 0,171 kWh/point au-dessus de 42 %, soit
environ 17 kWh, mais 0,121 entre 45 et 22 % et 0,082 entre 33 et 20 %. En bas
d echelle un point de SOC vaut moitie moins, alors que BALANCE.js convertit avec
une constante unique de 15 kWh. required_soc y est donc systematiquement trop
bas — c est le facteur 1,75 mesure la nuit du 02-03/09, ou le SOC descendait
bien plus vite que la ligne.

Cette mesure a dicte la conception : le relachement de la session ne peut pas
etre un seuil de SOC, puisqu il porterait sur une grandeur fausse et que tout
seuil que le SOC repasse ensuite rouvre l oscillateur du 27/08. Les deux
variantes evidentes — relacher sur required_soc, ou sur required_soc_prudent
avec la bande p50/p80 — ont ete ecartees pour cette raison.

Retenu : engagement sur le bilan (batterie sous la cible), relachement a la fin
de la fenetre. L oscillation devient structurellement impossible, la condition
de sortie etant monotone : une fenetre ne se ferme qu une fois. Un nouveau
helper input_boolean.victron_evcs_night_grid_session_committed porte
l engagement, cree par input_boolean.reload sans redemarrage HA et ecrit
seulement sur changement reel ; sans cette persistance la garde redeviendrait
une bande, donc un oscillateur.

Effet : l ACM recharge jusqu a la cible puis tient la batterie a 2 A, la maison
finit la nuit sur le reseau en heures creuses au lieu de vider la reserve, et la
batterie arrive a l aube pres de sa cible avec de la place pour le solaire. La
coupure immediate E2 de l hysteresis exige l absence d override et le helper
takeover reste ON : aucune modification du workflow d hysteresis.

Golden 75 -> 78. Le scenario epingle quelques heures plus tot comme limite
connue devient celui du correctif, attendu mis a jour dans le meme commit.
Quatre mutations ; la plus parlante retire la garde bilan et fait tomber les
trois scenarios anti-oscillateur du 27/08 — les tests de l ancien correctif
protegent le nouveau code.

Prerequis identifie et non traite : la conversion kWh vers points de SOC est une
constante la ou la realite est une courbe. Consequence, un sous-declenchement
possible en bas d echelle, jamais une oscillation. Prochain chantier du bilan.

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-03 23:14:49 +02:00
parent 05bdf019c2
commit 914ef021b2
11 changed files with 334 additions and 23 deletions

View File

@@ -1143,22 +1143,20 @@ export const scenarios = [
},
},
// ===== 03/09/2026 : SESSION RESEAU UNIQUE DE LA NUIT =====
// Engagement sur le bilan, relachement a la FIN DE LA FENETRE — jamais sur
// un seuil de SOC. Deux raisons, toutes deux mesurees le 03/09 : un seuil de
// relachement que le SOC repasse ensuite rouvre l'oscillateur du 27/08, et
// il porterait de toute facon sur une grandeur fausse (la conversion
// kWh -> points du bilan est lineaire a 15 kWh alors que la jauge vaut
// ~17 kWh au-dessus de 42 % et 8 a 12 kWh en dessous).
//
// Le premier scenario etait, quelques heures plus tot, epingle comme LIMITE
// CONNUE : le reseau restait relache alors que le SOC etait repasse sous la
// ligne. C'est exactement ce que ce correctif change.
{
// LIMITE CONNUE, FIGEE VOLONTAIREMENT (03/09/2026).
// La passe unique de recovery (27/08, anti-oscillateur) latche
// `nightRecoveryDone` : une fois la cible atteinte, le reseau est relache
// DEFINITIVEMENT pour la session, meme si le SOC repasse ensuite sous la
// ligne du bilan. La cible dynamique n'ameliore donc que la PREMIERE passe.
// Ce scenario epingle ce comportement pour qu'un changement futur soit
// deliberе et non accidentel. Filet restant : l'hysteresis a SOC <= 20 %,
// mais elle peut tomber hors heures creuses (c'est ce qui a coute 1,49 kWh
// au plein tarif le 03/09 entre 07:24 et 10:33).
// §26 designait deja le bon critere de re-engagement : « un budget
// d'energie restant a couvrir — il appartient au bilan energetique ».
// Le bilan est desormais cable ; la suite est un chantier a part, car elle
// touche la machine a etats de la nuit.
id: 'cible-bilan-limite-passe-unique',
label: "LIMITE FIGÉE : recovery déjà latchée, SOC 30 repasse sous le requis 37,1 % → le réseau reste relâché (passe unique, §26)",
id: 'session-unique-tient-sous-la-ligne',
label: "Nuit HC 05:30, recovery déjà latchée, SOC 30 sous le requis 37,1 % → la session réseau s'ENGAGE et tient",
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
@@ -1172,9 +1170,81 @@ export const scenarios = [
},
expect: {
profile: 'night_hc',
state: 'night_recovery_done_idle',
takeoverNeeded: true,
takeoverHelperService: 'turn_on',
sessionCommittedService: 'turn_on',
},
},
{
// LA PREUVE ANTI-OSCILLATEUR. Une fois engagee, la session ne se relache
// PAS parce que le SOC est repasse au-dessus de la ligne. Sans ce
// scenario, un relachement sur seuil passerait les tests et recreerait le
// cycle de 31 min corrige le 27/08.
id: 'session-unique-ne-relache-pas-sur-le-soc',
label: "Session déjà engagée, SOC 45 repassé AU-DESSUS du requis 37,1 % → le réseau reste tenu, et rien n'est réécrit",
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
night_grid_session_committed_prev: 'on',
soc_percent: '45',
balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3',
balance_status: 'ok', balance_last_run: '08/08 05:28',
},
expect: {
profile: 'night_hc',
takeoverNeeded: true,
takeoverHelperService: 'turn_on',
sessionCommittedService: null,
},
},
{
// LA SORTIE, et la seule : la fenetre se ferme. Condition monotone, donc
// aucune oscillation possible.
id: 'session-unique-fin-de-fenetre',
label: "08:00, hors fenêtre HC, session encore marquée engagée → réseau relâché et bit nettoyé",
time: '2026-08-08T08:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
night_grid_session_committed_prev: 'on',
soc_percent: '45',
balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3',
balance_status: 'ok', balance_last_run: '08/08 07:58',
},
expect: {
takeoverNeeded: false,
takeoverHelperService: 'turn_off',
sessionCommittedService: 'turn_off',
},
},
{
id: 'session-unique-bilan-indisponible',
label: "Même relevé qu'en tête, bilan périmé → aucun engagement, comportement historique (réseau relâché)",
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
grid_state: 'off', victron_acin1_available: '0',
battery_current_a: '-14', night_takeover_prev: 'off',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
soc_percent: '30',
balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3',
balance_status: 'ok', balance_last_run: '08/08 03:00',
},
expect: {
profile: 'night_hc',
takeoverNeeded: false,
takeoverHelperService: 'turn_off',
sessionCommittedService: null,
},
},