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:
119
HANDOFF.md
119
HANDOFF.md
@@ -2923,3 +2923,122 @@ oscillateurs, et merite ses propres scenarios.
|
|||||||
|
|
||||||
---
|
---
|
||||||
*Document mis a jour le : 03/09/2026 (~22:55) — cible du matin issue du bilan energetique, golden 75/75, deploye*
|
*Document mis a jour le : 03/09/2026 (~22:55) — cible du matin issue du bilan energetique, golden 75/75, deploye*
|
||||||
|
|
||||||
|
## 40. Session du 03/09/2026 (suite) — Session reseau UNIQUE de la nuit
|
||||||
|
|
||||||
|
### La mesure qui a dicte la conception (a lire avant tout le reste)
|
||||||
|
Avant d'ecrire une ligne, j'ai mesure les kWh reellement sortis de la batterie
|
||||||
|
par point de SOC, sur cinq fenetres et trois nuits :
|
||||||
|
|
||||||
|
| fenetre | bande SOC | kWh/point | capacite implicite |
|
||||||
|
|---|---|---:|---:|
|
||||||
|
| 03/09 21:30-22:50 | 74 → 57 | 0,171 | **17,1 kWh** |
|
||||||
|
| 31/08 03:00-05:00 | 49 → 42 | 0,170 | **17,0 kWh** |
|
||||||
|
| 03/09 19:44-20:17 | 94 → 85 | 0,164 | **16,4 kWh** |
|
||||||
|
| 02-03/09 03:00-06:00 (zero reseau) | 45 → 22 | 0,121 | **12,1 kWh** |
|
||||||
|
| 01-02/09 03:00-06:00 | 33 → 20 | 0,082 | **8,2 kWh** |
|
||||||
|
|
||||||
|
Au-dessus de ~42 % la jauge est lineaire et coherente (~17 kWh, ce qui valide
|
||||||
|
le `batteryUsableCapacityKwh: 17.5` code en dur). **En dessous elle s'effondre :
|
||||||
|
un point de SOC y vaut moitie moins.** Or `BALANCE.js` convertit avec une
|
||||||
|
constante unique : `socNeeded = energyToCover / usable_capacity_kwh * 100`, avec
|
||||||
|
le helper a 15 kWh. `required_soc` est donc systematiquement TROP BAS dans la
|
||||||
|
bande ou la nuit se termine — c'est le facteur 1,75 mesure la nuit du 02-03/09,
|
||||||
|
ou le SOC descendait bien plus vite que la ligne.
|
||||||
|
|
||||||
|
**Conclusion de conception, et elle est structurante : le relachement de la
|
||||||
|
session reseau ne peut 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. C'est ce raisonnement qui a ecarte les deux variantes evidentes
|
||||||
|
(relacher sur `required_soc`, ou sur `required_soc_prudent` avec la bande p50/p80).
|
||||||
|
|
||||||
|
### Le correctif (deploye 03/09 23:13, golden 78/78)
|
||||||
|
**ENGAGEMENT sur le bilan, RELACHEMENT a la FIN DE LA FENETRE.**
|
||||||
|
|
||||||
|
```js
|
||||||
|
nightGridSessionUncovered = balanceMorningTargetApplied && batteryBelowMorningTarget;
|
||||||
|
nightGridSessionCommitted = nightSessionActive && (uncovered || committedPrev);
|
||||||
|
nightGridTakeoverNeeded = nightSessionActive && (nightGridSessionCommitted || ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
L'oscillation devient **structurellement impossible** : la condition de sortie
|
||||||
|
est monotone — une fenetre ne se ferme qu'une fois. Aucun seuil de
|
||||||
|
re-engagement n'existe, donc aucun cycle limite ne peut s'installer.
|
||||||
|
|
||||||
|
Nouveau helper **`input_boolean.victron_evcs_night_grid_session_committed`**
|
||||||
|
(paquet `victron_evcs_night_helpers.yaml`, sauvegarde dans `packages/SAVE/`,
|
||||||
|
cree par `input_boolean.reload` — **aucun redemarrage HA**, meme procede que le
|
||||||
|
01/09). Ecrit uniquement sur changement reel. **C'est ce bit 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.
|
||||||
|
|
||||||
|
### 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. A comparer au 03/09 :
|
||||||
|
batterie a 20 %, reseau encore ouvert a 10:33, et 1,49 kWh achetes au plein
|
||||||
|
tarif entre 07:24 et 10:33 (pendant lesquels le reseau evincait en plus la
|
||||||
|
production naissante).
|
||||||
|
|
||||||
|
### Composition avec l'hysteresis — verifiee
|
||||||
|
La coupure immediate E2 (`turn_off_recovery_done`) exige `!evcsGridOverride`, et
|
||||||
|
`evcsExplicitGridRequest` inclut `evcsNightGridTakeoverNeeded`. Tant que la
|
||||||
|
session est engagee, le helper takeover est ON, donc E2 ne s'applique pas et la
|
||||||
|
boucle generique maintient le reseau. **Aucune modification du workflow
|
||||||
|
d'hysteresis n'a ete necessaire.**
|
||||||
|
|
||||||
|
### Golden 75 → 78 (et un scenario promu)
|
||||||
|
`cible-bilan-limite-passe-unique`, epingle quelques heures plus tot comme LIMITE
|
||||||
|
CONNUE, est devenu `session-unique-tient-sous-la-ligne` : c'est exactement ce
|
||||||
|
que ce correctif change. Attendu mis a jour dans le meme commit, conformement a
|
||||||
|
la regle du depot.
|
||||||
|
|
||||||
|
| scenario | role |
|
||||||
|
|---|---|
|
||||||
|
| `session-unique-tient-sous-la-ligne` | SOC 30 sous le requis 37,1 → engagement |
|
||||||
|
| `session-unique-ne-relache-pas-sur-le-soc` | **preuve anti-oscillateur** : SOC 45 repasse AU-DESSUS, la session tient et rien n'est reecrit |
|
||||||
|
| `session-unique-fin-de-fenetre` | la seule sortie : 08:00, hors fenetre → relache + bit nettoye |
|
||||||
|
| `session-unique-bilan-indisponible` | bilan perime → aucun engagement, comportement historique |
|
||||||
|
|
||||||
|
Mutation, quatre directions. La plus instructive est la quatrieme : **retirer la
|
||||||
|
garde bilan fait tomber les TROIS scenarios anti-oscillateur du 27/08**
|
||||||
|
(`night-oscillateur-nuit-2627`, `night-recovery-done-pas-de-reengagement`,
|
||||||
|
`night-recovery-done-bande-collante`). Les tests de l'ancien correctif protegent
|
||||||
|
activement le nouveau code — c'est exactement ce qu'on attend d'une suite golden
|
||||||
|
apres cinq semaines d'accumulation.
|
||||||
|
|
||||||
|
### ⚠️ PREREQUIS IDENTIFIE, NON TRAITE — la conversion kWh ↔ SOC
|
||||||
|
Le tableau en tete de cette section est un resultat a part entiere, et il
|
||||||
|
depasse ce correctif : **`evcs_balance_usable_capacity_kwh` = 15 est une
|
||||||
|
constante la ou la realite est une courbe** (17 kWh au-dessus de 42 %, 8 a
|
||||||
|
12 kWh en dessous), et `INPUT_PARSE` code 17,5 en dur de son cote. Tant que
|
||||||
|
cette conversion est lineaire :
|
||||||
|
- `required_soc` sous-estime le SOC necessaire en bas d'echelle ;
|
||||||
|
- donc l'engagement de la session peut manquer des nuits ou il aurait fallu
|
||||||
|
s'engager (sous-declenchement — jamais d'oscillation, la conception y est
|
||||||
|
immune) ;
|
||||||
|
- et la reserve d'orage (§26) est optimiste dans la meme bande.
|
||||||
|
|
||||||
|
C'est le prochain chantier du bilan energetique. §28 avait deja signale
|
||||||
|
l'effondrement de la jauge en bas d'echelle et conclu « a verser au chantier du
|
||||||
|
bilan energetique ; ne pas corriger par un seuil de plus » — les mesures
|
||||||
|
ci-dessus lui donnent maintenant sa courbe.
|
||||||
|
|
||||||
|
### Limite de perimetre
|
||||||
|
La session ne s'engage que pendant une **session nuit** (fenetre HC ET vehicule
|
||||||
|
branche) : c'est la portee du contrôleur EVCS. Une nuit sans vehicule reste
|
||||||
|
geree par l'hysteresis seule, avec ses seuils fixes 20/30 (§11.8.A) — chantier
|
||||||
|
distinct.
|
||||||
|
|
||||||
|
### Etat du cablage du bilan
|
||||||
|
| consommateur | statut |
|
||||||
|
|---|---|
|
||||||
|
| plancher de demarrage du soir | ✅ §38 |
|
||||||
|
| cible du matin | ✅ §39 |
|
||||||
|
| session reseau unique de la nuit | ✅ §40 |
|
||||||
|
| conversion kWh ↔ SOC (prerequis) | ❌ constante 15 kWh vs courbe mesuree |
|
||||||
|
| hysteresis `grid_soc_on`/`off` (20/30) | ❌ niveaux fixes |
|
||||||
|
|
||||||
|
---
|
||||||
|
*Document mis a jour le : 03/09/2026 (~23:20) — session reseau unique de la nuit, golden 78/78, deploye*
|
||||||
|
|||||||
@@ -867,6 +867,51 @@ Le bon critere de re-engagement — « un budget d'energie restant a couvrir »
|
|||||||
etait deja designe par §26 ; le bilan est maintenant cable, mais la suite touche
|
etait deja designe par §26 ; le bilan est maintenant cable, mais la suite touche
|
||||||
la machine a etats de la nuit et fera l'objet d'un chantier propre.
|
la machine a etats de la nuit et fera l'objet d'un chantier propre.
|
||||||
|
|
||||||
|
#### G. Session reseau unique de la nuit (03/09/2026)
|
||||||
|
|
||||||
|
Le chantier annonce ci-dessus, fait le soir meme — mais pas comme prevu.
|
||||||
|
|
||||||
|
**Mesure prealable, qui a change la conception.** 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 ~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. Or le bilan convertit avec une constante unique de 15 kWh :
|
||||||
|
`required_soc` y est donc systematiquement trop bas — d'ou la batterie qui,
|
||||||
|
la nuit du 02-03/09, descendait 1,75 fois plus vite que la ligne.
|
||||||
|
|
||||||
|
**Consequence : le relachement ne peut pas etre un seuil de SOC.** Il porterait
|
||||||
|
sur une grandeur fausse, et 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.
|
||||||
|
|
||||||
|
**Conception retenue : engagement sur le bilan, 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 helper
|
||||||
|
`input_boolean.victron_evcs_night_grid_session_committed` porte l'engagement —
|
||||||
|
sans cette persistance, la garde serait reevaluee a chaque cycle et redeviendrait
|
||||||
|
une bande.
|
||||||
|
|
||||||
|
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 `!evcsGridOverride` et le helper
|
||||||
|
takeover reste ON : aucune modification du workflow d'hysteresis n'a ete
|
||||||
|
necessaire.
|
||||||
|
|
||||||
|
Golden 75 → 78. La mutation la plus parlante : retirer la garde bilan fait
|
||||||
|
tomber les trois scenarios anti-oscillateur du 27/08 — les tests de l'ancien
|
||||||
|
correctif protegent le nouveau code. Detail : HANDOFF §40.
|
||||||
|
|
||||||
|
⚠️ **Prerequis identifie, non traite** : `evcs_balance_usable_capacity_kwh` = 15
|
||||||
|
est une constante la ou la realite est une courbe, et `INPUT_PARSE` code 17,5 en
|
||||||
|
dur de son cote. Consequence : `required_soc` sous-estime le besoin en bas
|
||||||
|
d'echelle, donc la session peut manquer des nuits ou elle aurait du s'engager
|
||||||
|
(sous-declenchement — jamais d'oscillation, la conception y est immune), et la
|
||||||
|
reserve d'orage est optimiste dans la meme bande. §28 avait deja signale
|
||||||
|
l'effondrement de la jauge ; les mesures du 03/09 lui donnent sa courbe. C'est
|
||||||
|
le prochain chantier du bilan energetique.
|
||||||
|
|
||||||
⚠️ Point structurel mis au jour par cet incident : le bilan energetique publie
|
⚠️ Point structurel mis au jour par cet incident : le bilan energetique publie
|
||||||
l'heure de croisement, l'energie a couvrir, le SOC requis, `grid_needed` et
|
l'heure de croisement, l'energie a couvrir, le SOC requis, `grid_needed` et
|
||||||
`pv_censored` — et **aucun des deux workflows de decision n'en lit une seule
|
`pv_censored` — et **aucun des deux workflows de decision n'en lit une seule
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ const batteryMorningTargetPct =
|
|||||||
: params.evcsNightTargetWeakForecastPct;
|
: params.evcsNightTargetWeakForecastPct;
|
||||||
const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied;
|
const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied;
|
||||||
const balanceMorningTargetApplied = !!nightPrev.balanceMorningTargetApplied;
|
const balanceMorningTargetApplied = !!nightPrev.balanceMorningTargetApplied;
|
||||||
|
const nightGridSessionCommitted = !!nightPrev.nightGridSessionCommitted;
|
||||||
const maxBatteryDischargePoints =
|
const maxBatteryDischargePoints =
|
||||||
Number.isFinite(nightPrev.maxBatteryDischargePoints) ? nightPrev.maxBatteryDischargePoints : 0;
|
Number.isFinite(nightPrev.maxBatteryDischargePoints) ? nightPrev.maxBatteryDischargePoints : 0;
|
||||||
const gridTakeoverTriggerPct =
|
const gridTakeoverTriggerPct =
|
||||||
@@ -1082,6 +1083,7 @@ return [{
|
|||||||
batteryMorningTargetPct,
|
batteryMorningTargetPct,
|
||||||
advisorNightTargetApplied,
|
advisorNightTargetApplied,
|
||||||
balanceMorningTargetApplied,
|
balanceMorningTargetApplied,
|
||||||
|
nightGridSessionCommitted,
|
||||||
maxBatteryDischargePoints,
|
maxBatteryDischargePoints,
|
||||||
gridTakeoverTriggerPct,
|
gridTakeoverTriggerPct,
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ pushAction(
|
|||||||
!!decision.set_cloud_debt_started_at
|
!!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(
|
pushAction(
|
||||||
'helper_battery_soft_breach_at',
|
'helper_battery_soft_breach_at',
|
||||||
calls.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: {
|
helper_night_grid_takeover_needed: {
|
||||||
execute: true,
|
execute: true,
|
||||||
domain: 'input_boolean',
|
domain: 'input_boolean',
|
||||||
|
|||||||
@@ -132,6 +132,7 @@
|
|||||||
|
|
||||||
"allow_grid_hc_night": states("input_boolean.victron_evcs_allow_grid_hc_night"),
|
"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_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"),
|
"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_soc": states("input_text.victron_evcs_night_session_start_soc"),
|
||||||
"evcs_night_session_start_at": states("input_text.victron_evcs_night_session_start_at"),
|
"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 SOUPLE BATTERIE (01/09/2026) : horodatage du debut du
|
||||||
// depassement en cours, ecrit par HA_SERVICE_CALLS_OUTPUT. Vide/illisible
|
// depassement en cours, ecrit par HA_SERVICE_CALLS_OUTPUT. Vide/illisible
|
||||||
// => null => aucun depassement en cours, comportement de repli sur.
|
// => 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),
|
batterySoftBreachAt: cleanText(input.battery_soft_breach_at),
|
||||||
batterySoftBreachMsPrev: parseParisStampToMs(cleanText(input.battery_soft_breach_at)),
|
batterySoftBreachMsPrev: parseParisStampToMs(cleanText(input.battery_soft_breach_at)),
|
||||||
evcsPriorityManualUntilMs: parseParisStampToMs(cleanText(input.evcs_priority_manual_until)),
|
evcsPriorityManualUntilMs: parseParisStampToMs(cleanText(input.evcs_priority_manual_until)),
|
||||||
|
|||||||
@@ -561,9 +561,57 @@ const batteryAssistAllowed =
|
|||||||
!nightTakeoverLatched &&
|
!nightTakeoverLatched &&
|
||||||
!batteryRecoveryUrgent;
|
!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 =
|
const nightGridTakeoverNeeded =
|
||||||
nightSessionActive &&
|
nightSessionActive &&
|
||||||
(
|
(
|
||||||
|
nightGridSessionCommitted ||
|
||||||
nightGridHcPreferred ||
|
nightGridHcPreferred ||
|
||||||
batteryBelowHardFloor ||
|
batteryBelowHardFloor ||
|
||||||
(
|
(
|
||||||
@@ -959,11 +1007,12 @@ return [{
|
|||||||
batteryMorningTargetPct,
|
batteryMorningTargetPct,
|
||||||
batteryMorningTargetBasePct,
|
batteryMorningTargetBasePct,
|
||||||
balanceMorningTargetApplied,
|
balanceMorningTargetApplied,
|
||||||
|
nightGridSessionCommitted,
|
||||||
|
nightGridSessionUncovered,
|
||||||
balanceTargetAgeMin:
|
balanceTargetAgeMin:
|
||||||
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
|
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
|
||||||
batteryMorningTargetRulePct,
|
batteryMorningTargetRulePct,
|
||||||
advisorNightTargetApplied,
|
advisorNightTargetApplied,
|
||||||
balanceMorningTargetApplied,
|
|
||||||
maxBatteryDischargePoints,
|
maxBatteryDischargePoints,
|
||||||
gridTakeoverTriggerPct,
|
gridTakeoverTriggerPct,
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ function summarize(out) {
|
|||||||
takeoverNeeded: !!c.night_grid_takeover_needed,
|
takeoverNeeded: !!c.night_grid_takeover_needed,
|
||||||
edfOnAction: !!calls.edf_general_ok_on?.execute,
|
edfOnAction: !!calls.edf_general_ok_on?.execute,
|
||||||
takeoverHelperService: calls.helper_night_grid_takeover_needed?.service || null,
|
takeoverHelperService: calls.helper_night_grid_takeover_needed?.service || null,
|
||||||
|
// Session reseau unique (03/09/2026) : null = aucune ecriture ce cycle.
|
||||||
|
sessionCommittedService: calls.helper_night_grid_session_committed?.execute
|
||||||
|
? calls.helper_night_grid_session_committed.service
|
||||||
|
: null,
|
||||||
// Consigne de puissance reseau ecrite ce cycle (null = aucune ecriture).
|
// Consigne de puissance reseau ecrite ce cycle (null = aucune ecriture).
|
||||||
gridSetpointW: calls.grid_power_setpoint?.execute
|
gridSetpointW: calls.grid_power_setpoint?.execute
|
||||||
? calls.grid_power_setpoint.data.value
|
? calls.grid_power_setpoint.data.value
|
||||||
|
|||||||
@@ -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).
|
id: 'session-unique-tient-sous-la-ligne',
|
||||||
// La passe unique de recovery (27/08, anti-oscillateur) latche
|
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",
|
||||||
// `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)",
|
|
||||||
time: '2026-08-08T05:30:00+02:00',
|
time: '2026-08-08T05:30:00+02:00',
|
||||||
overrides: {
|
overrides: {
|
||||||
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
|
...VEHICLE_NIGHT, allow_grid_hc_night: 'on',
|
||||||
@@ -1172,9 +1170,81 @@ export const scenarios = [
|
|||||||
},
|
},
|
||||||
expect: {
|
expect: {
|
||||||
profile: 'night_hc',
|
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,
|
takeoverNeeded: false,
|
||||||
takeoverHelperService: 'turn_off',
|
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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user