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:
@@ -362,6 +362,7 @@ const batteryMorningTargetPct =
|
||||
: params.evcsNightTargetWeakForecastPct;
|
||||
const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied;
|
||||
const balanceMorningTargetApplied = !!nightPrev.balanceMorningTargetApplied;
|
||||
const nightGridSessionCommitted = !!nightPrev.nightGridSessionCommitted;
|
||||
const maxBatteryDischargePoints =
|
||||
Number.isFinite(nightPrev.maxBatteryDischargePoints) ? nightPrev.maxBatteryDischargePoints : 0;
|
||||
const gridTakeoverTriggerPct =
|
||||
@@ -1082,6 +1083,7 @@ return [{
|
||||
batteryMorningTargetPct,
|
||||
advisorNightTargetApplied,
|
||||
balanceMorningTargetApplied,
|
||||
nightGridSessionCommitted,
|
||||
maxBatteryDischargePoints,
|
||||
gridTakeoverTriggerPct,
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ pushAction(
|
||||
!!decision.set_cloud_debt_started_at
|
||||
);
|
||||
|
||||
pushAction(
|
||||
'helper_night_grid_session_committed',
|
||||
calls.helper_night_grid_session_committed,
|
||||
!!calls.helper_night_grid_session_committed?.execute
|
||||
);
|
||||
|
||||
pushAction(
|
||||
'helper_battery_soft_breach_at',
|
||||
calls.helper_battery_soft_breach_at,
|
||||
|
||||
@@ -986,6 +986,19 @@ return [{
|
||||
}
|
||||
},
|
||||
|
||||
// SESSION RESEAU UNIQUE (03/09/2026) : ce bit est ce qui rend la session
|
||||
// UNIQUE. Sans persistance, l'engagement serait reevalue a chaque cycle
|
||||
// et le systeme retomberait dans une bande — donc dans un oscillateur.
|
||||
// Ecrit uniquement sur changement reel.
|
||||
helper_night_grid_session_committed: {
|
||||
execute: night.nightGridSessionCommitted !== history.nightGridSessionCommittedPrev,
|
||||
domain: 'input_boolean',
|
||||
service: night.nightGridSessionCommitted ? 'turn_on' : 'turn_off',
|
||||
data: {
|
||||
entity_id: 'input_boolean.victron_evcs_night_grid_session_committed'
|
||||
}
|
||||
},
|
||||
|
||||
helper_night_grid_takeover_needed: {
|
||||
execute: true,
|
||||
domain: 'input_boolean',
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
|
||||
"allow_grid_hc_night": states("input_boolean.victron_evcs_allow_grid_hc_night"),
|
||||
"night_takeover_prev": states("input_boolean.victron_evcs_night_grid_takeover_needed"),
|
||||
"night_grid_session_committed_prev": states("input_boolean.victron_evcs_night_grid_session_committed"),
|
||||
"prev_surplus_state": states("input_text.victron_evcs_surplus_state"),
|
||||
"evcs_night_session_start_soc": states("input_text.victron_evcs_night_session_start_soc"),
|
||||
"evcs_night_session_start_at": states("input_text.victron_evcs_night_session_start_at"),
|
||||
|
||||
@@ -568,6 +568,8 @@ const history = {
|
||||
// DEPASSEMENT SOUPLE BATTERIE (01/09/2026) : horodatage du debut du
|
||||
// depassement en cours, ecrit par HA_SERVICE_CALLS_OUTPUT. Vide/illisible
|
||||
// => null => aucun depassement en cours, comportement de repli sur.
|
||||
nightGridSessionCommittedPrev:
|
||||
(input.night_grid_session_committed_prev ?? 'off').toString().trim().toLowerCase() === 'on',
|
||||
batterySoftBreachAt: cleanText(input.battery_soft_breach_at),
|
||||
batterySoftBreachMsPrev: parseParisStampToMs(cleanText(input.battery_soft_breach_at)),
|
||||
evcsPriorityManualUntilMs: parseParisStampToMs(cleanText(input.evcs_priority_manual_until)),
|
||||
|
||||
@@ -561,9 +561,57 @@ const batteryAssistAllowed =
|
||||
!nightTakeoverLatched &&
|
||||
!batteryRecoveryUrgent;
|
||||
|
||||
// =====================================================
|
||||
// SESSION RESEAU UNIQUE DE LA NUIT (03/09/2026)
|
||||
// =====================================================
|
||||
// §26 avait pose l'objectif — « une session reseau unique et dimensionnee » —
|
||||
// et designe le bon critere : « un budget d'energie restant a couvrir, il
|
||||
// appartient au bilan energetique ». Le bilan est cable depuis ce soir.
|
||||
//
|
||||
// CE QUI A ETE MESURE AVANT D'ECRIRE CECI, et qui a dicte la conception :
|
||||
// la conversion kWh -> points de SOC du bilan est une constante unique
|
||||
// (15 kWh). La jauge reelle vaut ~17 kWh au-dessus de 42 % (0,164 a
|
||||
// 0,171 kWh/point sur trois fenetres independantes) mais s'effondre en
|
||||
// dessous : 12,1 kWh entre 45 et 22 %, 8,2 kWh entre 33 et 20 %. Un point de
|
||||
// SOC vaut donc MOITIE MOINS dans la bande ou la nuit se termine, et
|
||||
// `required_soc` y est systematiquement trop BAS — c'est pourquoi la batterie
|
||||
// est restee sous la ligne toute la nuit du 02-03/09, en descendant 1,75 fois
|
||||
// plus vite qu'elle.
|
||||
//
|
||||
// CONSEQUENCE DE CONCEPTION : le relachement ne doit PAS etre un seuil de SOC.
|
||||
// Il porterait sur une grandeur fausse, et tout seuil que le SOC repasse
|
||||
// ensuite rouvre l'oscillateur eteint le 27/08.
|
||||
//
|
||||
// Donc : ENGAGEMENT sur le bilan (batterie sous la cible), RELACHEMENT a la
|
||||
// FIN DE LA FENETRE. L'oscillation devient structurellement impossible : la
|
||||
// condition de sortie est monotone, une fenetre ne se ferme qu'une fois. Le
|
||||
// bit d'engagement est porte par
|
||||
// input_boolean.victron_evcs_night_grid_session_committed — sans lui le
|
||||
// systeme reevaluerait a chaque cycle et retomberait dans une bande.
|
||||
//
|
||||
// CE QUE CA FAIT CONCRETEMENT : l'ACM recharge jusqu'a la cible puis tient la
|
||||
// batterie a 2 A (evcs_takeover_hold) ; 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, donc avec de la place pour le solaire du matin —
|
||||
// au lieu d'etre a 20 % avec le reseau encore ouvert a 10:33 (03/09).
|
||||
//
|
||||
// PORTEE : uniquement quand le bilan est exploitable, et uniquement pendant
|
||||
// une session nuit (fenetre HC + vehicule branche). Sinon, comportement
|
||||
// historique inchange. Le bit se nettoie tout seul a la fin de la session.
|
||||
const nightGridSessionCommittedPrev =
|
||||
(input.night_grid_session_committed_prev ?? 'off').toString().trim().toLowerCase() === 'on';
|
||||
|
||||
const nightGridSessionUncovered =
|
||||
balanceMorningTargetApplied && batteryBelowMorningTarget;
|
||||
|
||||
const nightGridSessionCommitted =
|
||||
nightSessionActive &&
|
||||
(nightGridSessionUncovered || nightGridSessionCommittedPrev);
|
||||
|
||||
const nightGridTakeoverNeeded =
|
||||
nightSessionActive &&
|
||||
(
|
||||
nightGridSessionCommitted ||
|
||||
nightGridHcPreferred ||
|
||||
batteryBelowHardFloor ||
|
||||
(
|
||||
@@ -959,11 +1007,12 @@ return [{
|
||||
batteryMorningTargetPct,
|
||||
batteryMorningTargetBasePct,
|
||||
balanceMorningTargetApplied,
|
||||
nightGridSessionCommitted,
|
||||
nightGridSessionUncovered,
|
||||
balanceTargetAgeMin:
|
||||
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
|
||||
batteryMorningTargetRulePct,
|
||||
advisorNightTargetApplied,
|
||||
balanceMorningTargetApplied,
|
||||
maxBatteryDischargePoints,
|
||||
gridTakeoverTriggerPct,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user