La cible du matin devient une energie a couvrir, plus un palier

La regle classait le lendemain en trois paliers a partir de son TOTAL (>= 18 kWh
-> 35 %, >= 10 -> 55 %, sinon 85 %) et ignorait donc l heure d arrivee de
l energie. Le 03/09 demain annoncait 33,9 kWh — donc "fort", donc 35 % — avec un
matin creux (0,03 kWh a 7 h, 0,77 a 8 h) et un croisement a 9 h. Meme defaut que
le plancher du soir, a l autre bout de la nuit.

Releve de la nuit 02-03/09, requis contre reel : 00:30 62,4 / 53 ; 03:00
50,4 / 45 ; 04:30 41,7 / 37 ; 06:00 37,1 / 22 — cible fixe a 35 tout du long.
Le required_soc decroit proprement avec la nuit qui se consomme (6,1 -> 0 kWh a
couvrir), la batterie est restee sous la ligne toute la nuit, et le bilan le
savait des 00:30. A 03:00 EDF a ete relache cinq points sous la ligne ; le
reseau est revenu de 07:24 a 10:33 au plein tarif, 1,49 kWh.

La cible vaut desormais min(90, max(palier ou advisor, required_soc)). Sens
unique delibere : la direction corrigee est celle de l echec observe, et le
principe unificateur des deux cablages est que le bilan ne pousse jamais dans le
sens "couvre moins bien la nuit". Plafond 90 parce qu au-dela la batterie cesse
d etre un puits et evince la production du matin, et parce que le bilan publie
sans ecretage des valeurs superieures a 100 — sans ce plafond la cible serait
inatteignable et EDF resterait allume toute la nuit. C est deja la borne du
chemin advisor, aucune constante nouvelle.

Pas d oscillateur : required_soc decroit de facon monotone pendant la nuit et la
passe unique de recovery reste en place. Gardes et repli identiques au plancher
du soir, kill-switch existant.

Golden 70 -> 75. Quatre mutations font tomber chacune exactement un scenario
different. Un cinquieme scenario fige une LIMITE CONNUE, mesuree par sonde : la
passe unique de recovery latche le relachement pour toute la session, donc la
cible dynamique n ameliore que la premiere passe. §26 avait deja designe le bon
critere de re-engagement — un budget d energie restant a couvrir, qui appartient
au bilan ; la suite touche la machine a etats de la nuit et fera l objet d un
chantier propre.

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 22:51:09 +02:00
parent 6f3f98e8b6
commit 05bdf019c2
7 changed files with 344 additions and 9 deletions

View File

@@ -2821,3 +2821,105 @@ pas, et c'est tout l'interet.
--- ---
*Document mis a jour le : 03/09/2026 (~22:25) — plancher du soir issu du bilan energetique, golden 70/70, deploye* *Document mis a jour le : 03/09/2026 (~22:25) — plancher du soir issu du bilan energetique, golden 70/70, deploye*
## 39. Session du 03/09/2026 (suite) — La cible du matin devient une energie
### Le defaut, symetrique de la §38
La regle historique classe le lendemain en trois paliers a partir de son TOTAL :
`>= 18 kWh -> 35 %`, `>= 10 -> 55 %`, sinon 85 %. Elle est donc aveugle a
**l'heure d'arrivee** de l'energie. Le 03/09, demain annoncait 33,9 kWh — donc
« fort », donc 35 % — mais avec un matin creux (0,03 kWh a 7 h, 0,77 a 8 h,
1,81 a 9 h) et un croisement a 9 h. C'est le meme defaut que le plancher du soir
de la §38, a l'autre bout de la nuit.
### Le releve qui a motive le cablage (nuit 02-03/09)
| heure | requis (bilan) | SOC reel | cible fixe |
|---|---:|---:|---:|
| 00:30 | 62,4 | 53 | 35 |
| 03:00 | **50,4** | **45** | 35 |
| 04:30 | 41,7 | 37 | 35 |
| 06:00 | 37,1 | 22 | 35 |
`required_soc` decroit proprement avec la nuit qui se consomme (6,1 -> 0 kWh a
couvrir) : c'est une cible instantanee, pas une constante deguisee. **La
batterie est restee sous la ligne toute la nuit, et le bilan le savait des
00:30.** A 03:00 EDF a ete relache cinq points sous la ligne ; la batterie est
tombee a 20 % vers 06:30 et le reseau est revenu de 07:24 a 10:33 —
**1,49 kWh AU PLEIN TARIF**.
### Le correctif (deploye 03/09 22:50, golden 75/75)
Dans `WINDOWS_SOURCEV2.js` :
```js
batteryMorningTargetPct = balanceApplique
? Math.min(90, Math.max(batteryMorningTargetBasePct, params.balanceRequiredSocPct))
: batteryMorningTargetBasePct;
```
`batteryMorningTargetBasePct` = advisor si son toggle est actif, sinon les
paliers. L'ordre est donc : paliers ou advisor d'abord, puis le bilan ne peut
que RELEVER.
**SENS UNIQUE**, comme en §38 : la direction corrigee est celle de l'echec
observe — cible trop basse, batterie a plat avant le croisement, reseau au
plein tarif le matin. L'abaisser ouvrirait un risque neuf que rien n'a valide.
Principe unificateur des deux cablages : **le bilan ne pousse jamais dans le
sens « couvre moins bien la nuit »**.
**PLAFOND 90 %** : au-dela la batterie cesse d'etre un puits et la production du
matin se fait evincer (mesures du 31/08). Charger au reseau au-dela de 90
echangerait un kWh bon marche cette nuit contre plusieurs kWh censures demain.
C'est deja la borne du chemin advisor : aucune constante nouvelle. Le bilan
publie sans ecretage des valeurs > 100 (voulu, §26 : signal de dimensionnement),
donc ce plafond n'est pas cosmetique — sans lui la cible serait inatteignable
et EDF resterait allume toute la nuit.
**PAS D'OSCILLATEUR** : `required_soc` decroit de facon monotone pendant la nuit,
et la passe unique de recovery (§26) reste en place.
**Gardes et repli** identiques a la §38 (fraicheur 30 min, statut `ok`, valeur
finie ; kill-switch `evcs_balance_enabled` deja existant). Trace logbook :
`Nuit EVCS: target=X% (BILAN)`.
### Golden 70 → 75
| scenario | attendu |
|---|---|
| `cible-bilan-tient-le-reseau` (releve 03:00, SOC 45, requis 50,4) | reseau MAINTENU |
| `cible-bilan-perime-relache-comme-avant` (bilan vieux de 2 h) | relache — le comportement qui a coute 1,49 kWh |
| `cible-bilan-ne-baisse-pas-la-cible` (SOC 30, requis 25) | palier 35 maitre, reseau maintenu |
| `cible-bilan-plafonnee-a-90` (SOC 92, requis 118,9) | plafonnee, relache |
Mutation, quatre directions (bilan ignore / `Math.max` en affectation directe /
plafond 90 supprime / garde de fraicheur supprimee) : **chacune fait tomber
exactement UN scenario, different a chaque fois**.
### ⚠️ LIMITE CONNUE, MESUREE ET FIGEE — la passe unique
Scenario `cible-bilan-limite-passe-unique`, verifie par sonde : recovery deja
latchee, SOC 30 repassant sous un requis de 37,1 % => **le reseau reste
relache**, et la raison affiche meme « batterie a la cible » sept points sous
la ligne. La passe unique de recovery (§26, anti-oscillateur) latche
`nightRecoveryDone` pour toute la session : la cible dynamique n'ameliore donc
que la PREMIERE passe.
Filet restant : l'hysteresis a SOC ≤ 20 %, mais elle peut tomber HORS heures
creuses — c'est exactement le 1,49 kWh plein tarif du 03/09.
Ce comportement est epingle par un scenario pour qu'un changement futur soit
delibere. **§26 avait deja designe le bon critere de re-engagement** : « un
budget d'energie restant a couvrir — il appartient au bilan energetique, pas a
une constante de plus ici ». Le bilan est desormais cable ; la suite naturelle
est une **session reseau unique et dimensionnee** — ne pas relacher tant qu'on
est sous la ligne ET dans la fenetre HC. C'est un chantier a part : il touche la
machine a etats de la nuit (§18/§19/§26), qui a deja produit quatre
oscillateurs, et merite ses propres scenarios.
### Etat du cablage du bilan
| consommateur | statut |
|---|---|
| plancher de demarrage du soir | ✅ §38 |
| cible du matin | ✅ §39 |
| re-engagement du reseau la nuit | ❌ passe unique (ci-dessus) |
| hysteresis `grid_soc_on`/`grid_soc_off` (20/30) | ❌ toujours des niveaux fixes (§11.8.A) |
---
*Document mis a jour le : 03/09/2026 (~22:55) — cible du matin issue du bilan energetique, golden 75/75, deploye*

View File

@@ -830,10 +830,42 @@ Portee : demarrage. Le plancher de maintien reste a 80 %, donc aucune charge en
cours n'est coupee. Golden 66 → 70, quatre mutations faisant tomber chacune cours n'est coupee. Golden 66 → 70, quatre mutations faisant tomber chacune
exactement un scenario different. Detail : HANDOFF §38. exactement un scenario different. Detail : HANDOFF §38.
⚠️ Le meme defaut subsiste sur l'autre bout de la nuit : `night_target_soc` est #### F. La cible du matin etait un palier, pas une energie (03/09/2026)
toujours deduit du TOTAL de la prevision du lendemain (paliers 35/55/85), aveugle
a l'heure d'arrivee de l'energie — 35 % cote controleur contre 99,7 % cote bilan Symetrique du precedent, a l'autre bout de la nuit. La regle classe le lendemain
le 03/09. en trois paliers a partir de son TOTAL (≥ 18 kWh → 35 %, ≥ 10 → 55 %, sinon
85 %) et ignore donc **l'heure d'arrivee** de l'energie. Le 03/09, demain
annoncait 33,9 kWh — « fort », donc 35 % — avec un matin creux et un croisement
a 9 h.
Releve de la nuit 02-03/09, qui montre le probleme et sa solution dans le meme
tableau :
| heure | requis (bilan) | SOC reel | cible fixe |
|---|---:|---:|---:|
| 00:30 | 62,4 | 53 | 35 |
| 03:00 | **50,4** | **45** | 35 |
| 06:00 | 37,1 | 22 | 35 |
Le `required_soc` decroit avec la nuit qui se consomme. La batterie est restee
sous la ligne toute la nuit ; a 03:00 EDF a ete relache cinq points en dessous,
la batterie est tombee a 20 % vers 06:30 et le reseau est revenu **au plein
tarif** de 07:24 a 10:33 (1,49 kWh).
La cible vaut desormais `min(90, max(palier ou advisor, required_soc))`. Sens
unique delibere, comme le plancher du soir : le bilan ne pousse jamais dans le
sens « couvre moins bien la nuit ». Plafond 90 % parce qu'au-dela la batterie
cesse d'etre un puits et evince la production du matin — et parce que le bilan
publie sans ecretage des valeurs > 100 (§26). Golden 70 → 75, quatre mutations
faisant tomber chacune un scenario different. Detail : HANDOFF §39.
⚠️ **Limite connue et figee par un scenario** : la passe unique de recovery
(§26) latche le relachement pour toute la session, donc la cible dynamique
n'ameliore que la PREMIERE passe. Si le SOC repasse sous la ligne ensuite, le
reseau ne revient qu'via l'hysteresis a 20 %, possiblement hors heures creuses.
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
la machine a etats de la nuit et fera l'objet d'un chantier propre.
⚠️ 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

View File

@@ -361,6 +361,7 @@ const batteryMorningTargetPct =
? nightPrev.batteryMorningTargetPct ? nightPrev.batteryMorningTargetPct
: params.evcsNightTargetWeakForecastPct; : params.evcsNightTargetWeakForecastPct;
const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied; const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied;
const balanceMorningTargetApplied = !!nightPrev.balanceMorningTargetApplied;
const maxBatteryDischargePoints = const maxBatteryDischargePoints =
Number.isFinite(nightPrev.maxBatteryDischargePoints) ? nightPrev.maxBatteryDischargePoints : 0; Number.isFinite(nightPrev.maxBatteryDischargePoints) ? nightPrev.maxBatteryDischargePoints : 0;
const gridTakeoverTriggerPct = const gridTakeoverTriggerPct =
@@ -1080,6 +1081,7 @@ return [{
batteryHardFloorPct, batteryHardFloorPct,
batteryMorningTargetPct, batteryMorningTargetPct,
advisorNightTargetApplied, advisorNightTargetApplied,
balanceMorningTargetApplied,
maxBatteryDischargePoints, maxBatteryDischargePoints,
gridTakeoverTriggerPct, gridTakeoverTriggerPct,

View File

@@ -1319,7 +1319,7 @@ else {
// ===================================================== // =====================================================
if (nightEvcs?.nightSessionActive) { if (nightEvcs?.nightSessionActive) {
reason.push( reason.push(
`Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}%${nightEvcs.advisorNightTargetApplied ? ' (ADVISOR)' : ''} | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%` `Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}%${nightEvcs.advisorNightTargetApplied ? ' (ADVISOR)' : ''}${nightEvcs.balanceMorningTargetApplied ? ' (BILAN)' : ''} | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%`
); );
if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) { if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) {

View File

@@ -285,7 +285,7 @@ const advisorNightTargetApplied =
advisorTargetAgeMin !== null && advisorTargetAgeMin !== null &&
advisorTargetAgeMin <= ADVISOR_TARGET_MAX_AGE_MIN; advisorTargetAgeMin <= ADVISOR_TARGET_MAX_AGE_MIN;
const batteryMorningTargetPct = const batteryMorningTargetBasePct =
advisorNightTargetApplied advisorNightTargetApplied
? clamp( ? clamp(
params.advisorNightTargetSocPct, params.advisorNightTargetSocPct,
@@ -294,6 +294,65 @@ const batteryMorningTargetPct =
) )
: batteryMorningTargetRulePct; : batteryMorningTargetRulePct;
// =====================================================
// CIBLE DU MATIN : UNE ENERGIE A COUVRIR, PAS UN PALIER (03/09/2026)
// =====================================================
// La regle historique classe la journee du lendemain en trois paliers
// (>= 18 kWh -> 35 %, >= 10 -> 55 %, sinon 85 %) a partir de son TOTAL. Elle
// est donc aveugle a l'heure d'arrivee de l'energie : le 03/09, demain
// annoncait 33,9 kWh — « fort », donc 35 % — mais avec un matin creux
// (0,03 kWh a 7 h, 0,77 a 8 h, 1,81 a 9 h) et un croisement a 9 h.
//
// C'est le meme defaut que le plancher du soir (cf. le bloc BILAN de
// BATTERY_CLOUD_LOCKS), a l'autre bout de la nuit : un palier fige contre une
// energie mesurable. Le bilan publie la seconde, instant par instant.
//
// RELEVE DE LA NUIT 02-03/09, qui a motive ce cablage :
//
// heure requis SOC reel cible fixe
// 00:30 62,4 53 35
// 03:00 50,4 45 35 <- EDF relache ici, 5 points sous la ligne
// 04:30 41,7 37 35
// 06:00 37,1 22 35 <- hysteresis, puis reseau PLEIN TARIF
// de 07:24 a 10:33 (1,49 kWh)
//
// La batterie est restee sous la ligne toute la nuit et le bilan le savait des
// 00:30. Le `required_soc` decroit proprement avec la nuit qui se consomme
// (6,1 -> 0 kWh a couvrir) : c'est bien une cible instantanee, pas une
// constante deguisee.
//
// SENS UNIQUE, comme pour le plancher du soir : le bilan ne peut que RELEVER
// la cible, jamais l'abaisser. La direction corrigee est celle de l'echec
// observe (cible trop basse -> batterie a plat avant le croisement -> reseau
// au plein tarif le matin). L'abaisser — la regle dit 85 % l'hiver quand le
// bilan dira parfois moins — ouvrirait un risque neuf que rien n'a encore
// valide. Le principe unificateur : le bilan ne pousse JAMAIS dans le sens
// « couvre moins bien la nuit ».
//
// PLAFOND 90 % : au-dela, la batterie cesse d'etre un puits et la production
// du matin se fait evincer (cf. mesures du 31/08). Charger au reseau au-dela
// de 90 echangerait un kWh bon marche cette nuit contre plusieurs kWh censures
// demain. C'est deja la borne du chemin advisor : aucune constante nouvelle.
//
// PAS D'OSCILLATEUR : `required_soc` decroit de facon monotone pendant la nuit
// (l'energie restant a couvrir ne fait que diminuer), et la passe unique de
// recovery (`nightRecoveryDonePrev`, 27/08) reste en place.
//
// REPLI identique au plancher du soir : fraicheur, statut, valeur finie —
// sinon regle historique. Kill-switch existant : `evcs_balance_enabled` OFF
// fige `balance_last_run` et la cible retombe seule sur les paliers.
const BALANCE_TARGET_MAX_AGE_MIN = 30;
const balanceTargetAgeMin = minutesSince(params.balanceLastRunMs, nowMs);
const balanceMorningTargetApplied =
params.balanceStatusOk &&
Number.isFinite(params.balanceRequiredSocPct) &&
balanceTargetAgeMin !== null &&
balanceTargetAgeMin <= BALANCE_TARGET_MAX_AGE_MIN;
const batteryMorningTargetPct = balanceMorningTargetApplied
? Math.min(90, Math.max(batteryMorningTargetBasePct, params.balanceRequiredSocPct))
: batteryMorningTargetBasePct;
const maxBatteryDischargePoints = const maxBatteryDischargePoints =
nightSessionActive && Number.isFinite(nightSessionStartSocPct) nightSessionActive && Number.isFinite(nightSessionStartSocPct)
? Math.max(0, nightSessionStartSocPct - batteryMorningTargetPct) ? Math.max(0, nightSessionStartSocPct - batteryMorningTargetPct)
@@ -898,8 +957,13 @@ return [{
batteryHardFloorPct, batteryHardFloorPct,
batteryMorningTargetPct, batteryMorningTargetPct,
batteryMorningTargetBasePct,
balanceMorningTargetApplied,
balanceTargetAgeMin:
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
batteryMorningTargetRulePct, batteryMorningTargetRulePct,
advisorNightTargetApplied, advisorNightTargetApplied,
balanceMorningTargetApplied,
maxBatteryDischargePoints, maxBatteryDischargePoints,
gridTakeoverTriggerPct, gridTakeoverTriggerPct,

View File

@@ -1043,6 +1043,141 @@ export const scenarios = [
}, },
}, },
// ===== 03/09/2026 : la cible du matin est une energie, pas un palier =====
// Releve de la nuit 02-03/09 a 03:00 : le bilan demandait 50,4 % pour tenir
// jusqu'au croisement de 9 h, la regle des paliers disait 35 %, le SOC etait
// a 45 %. EDF a ete relache 5 points sous la ligne, la batterie s'est videe
// a 20 % vers 06:30, et le reseau est revenu de 07:24 a 10:33 AU PLEIN TARIF.
{
id: 'cible-bilan-tient-le-reseau',
label: "Nuit HC 03:00 (relevé du 03/09), VW pleine, SOC 45 : le bilan demande 50,4 % → cible relevée → le réseau est MAINTENU au lieu d'être relâché",
time: '2026-08-08T03: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',
prev_surplus_state: 'night_session_done_idle',
soc_percent: '45',
balance_required_soc: '50.4', balance_energy_to_cover_kwh: '4.3',
balance_status: 'ok', balance_last_run: '08/08 02:58',
},
expect: {
profile: 'night_hc',
state: 'night_session_done_idle',
takeoverNeeded: true,
takeoverHelperService: 'turn_on',
},
},
{
// CONTREFACTUEL : exactement la nuit du 03/09. Bilan indisponible => regle
// des paliers (35 %) => SOC 45 >= 35 => relache. C'est ce qui s'est passe.
id: 'cible-bilan-perime-relache-comme-avant',
label: "Même relevé, bilan vieux de 2 h → repli sur le palier 35 % → EDF relâché (le comportement qui a coûté 1,49 kWh au plein tarif)",
time: '2026-08-08T03: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',
prev_surplus_state: 'night_session_done_idle',
soc_percent: '45',
balance_required_soc: '50.4', balance_energy_to_cover_kwh: '4.3',
balance_status: 'ok', balance_last_run: '08/08 01:00',
},
expect: {
profile: 'night_hc',
state: 'night_recovery_done_idle',
takeoverNeeded: false,
takeoverHelperService: 'turn_off',
},
},
{
// SENS UNIQUE : le bilan ne doit jamais ABAISSER la cible.
id: 'cible-bilan-ne-baisse-pas-la-cible',
label: "Nuit HC, SOC 30, le bilan ne demande que 25 % → le palier 35 % reste maître → le réseau est maintenu",
time: '2026-08-08T03: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',
prev_surplus_state: 'night_session_done_idle',
soc_percent: '30',
balance_required_soc: '25', balance_energy_to_cover_kwh: '0.5',
balance_status: 'ok', balance_last_run: '08/08 02:58',
},
expect: {
profile: 'night_hc',
state: 'night_session_done_idle',
takeoverNeeded: true,
takeoverHelperService: 'turn_on',
},
},
{
// PLAFOND 90 % : le bilan publie sans ecretage des valeurs > 100 (c'est
// voulu, §26 : signal de dimensionnement). Sans plafond ici, la cible
// deviendrait inatteignable et EDF resterait allume toute la nuit a
// charger la batterie — en evincant au passage la production du matin.
id: 'cible-bilan-plafonnee-a-90',
label: "Nuit HC, SOC 92, le bilan demande 118,9 % (stockage insuffisant) → cible plafonnée à 90 % → recovery terminée, EDF relâché",
time: '2026-08-08T03: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',
prev_surplus_state: 'night_session_done_idle',
soc_percent: '92',
balance_required_soc: '118.9', balance_energy_to_cover_kwh: '14.54',
balance_status: 'ok', balance_last_run: '08/08 02:58',
},
expect: {
profile: 'night_hc',
state: 'night_recovery_done_idle',
takeoverNeeded: false,
takeoverHelperService: 'turn_off',
},
},
{
// 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)",
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 05:28',
},
expect: {
profile: 'night_hc',
state: 'night_recovery_done_idle',
takeoverNeeded: false,
takeoverHelperService: 'turn_off',
},
},
// ===== 03/09/2026 : le plancher du soir vient de l'energie a couvrir ===== // ===== 03/09/2026 : le plancher du soir vient de l'energie a couvrir =====
// Tous ces scenarios ont un VRAI excedent (PV 2000 W, maison 1000 W) et des // Tous ces scenarios ont un VRAI excedent (PV 2000 W, maison 1000 W) et des
// chargeurs qui se retiennent : les gardes de §36 et §37 sont donc passantes, // chargeurs qui se retiennent : les gardes de §36 et §37 sont donc passantes,

File diff suppressed because one or more lines are too long