La consigne reseau se dimensionne sur le deficit du bilan

Elle empruntait le plafond de la borne — `maison + physicalCapA x V` — qui n a
aucune relation physique avec ce dont la batterie a besoin. Qu il tombe juste
etait un accident : le 17/09 a 13:51, voiture partie depuis cinq minutes,
consigne a 7032 W pour une maison a 933 W.

Elle additionne desormais des besoins NOMMES : la maison toujours, la voiture
seulement si un vehicule est branche, la batterie seulement dans une fenetre
d heures creuses — recharger la batterie sur le reseau n a de sens economique
que la. Hors fenetre le terme vaut zero, donc la division par un temps qui tend
vers zero disparait par construction et non par garde.

MAX ET NON ADDITION. La consigne est une AUTORISATION, pas une prediction : le
fichier le dit deja — « la consigne dit ce que le reseau a le DROIT de servir,
DVCC dit ce que la batterie a le droit de prendre ». Additionner deux
autorisations independantes n est pas plus honnete, seulement plus permissif.
Mesure : avec l addition, `consigne-session-engagee-couvre-la-maison` passe de
5595 a 6341 W — une autorisation en plus sur le chemin dominant (85 nuits sur
88 ont un vehicule) pour corriger un chemin emprunte zero fois en treize jours.

LE TERME BATTERIE EST BORNE PAR DVCC, PAS PAR LE BMS NI PAR LE TEMPS. Le BMS
declare 250 A, soit 12,2 kW : au-dessus de GRID_SETPOINT_MAX_W, il ne bornerait
rien. Et un plancher d horizon ne borne que le diviseur : en fin de fenetre
(14 min, manque 5,672 kWh) la division rend 17 kW, ecretes a 10 000, soit PIRE
que les 5437 empruntes qu on remplace. Avec DVCC le terme vaut 1538 W et la
consigne 2380 W. Corollaire : le plancher d horizon devient sans objet, il ne
reste qu une garde d une minute contre la division par zero.

Le manque vient de `evcs_balance_grid_energy_needed_kwh` et non de
`energy_to_cover_kwh` : la premiere est le MANQUE, nulle des que la batterie
couvre et convertie par la courbe mesuree par morceaux ; la seconde est
l energie que la batterie devra FOURNIR et ne tombe jamais a zero tant qu il
reste de la nuit — 11,96 kWh annonces le 17/09 a 20:00 avec un SOC de 91 %.

Le plancher nuage suit la voiture et PAS la batterie : il couvre l exposition
creee par le courant du vehicule ; une recharge batterie qui ralentit quand le
solaire s effondre ne coupe personne.

INVARIANT TENU PAR LA FORME : `maison + max(...)` ne peut jamais descendre sous
la maison, quel que soit DVCC. La regle du 01/09 et du 04/09 est donc garantie
par construction. Toute lecture manquante retombe sur la formule d avant.

Golden 102 -> 104. `nuit-sans-vehicule-session-engage` passe de 5437 a 2380 et
recoit le manque explicitement, sans quoi il heritait du zero de la fixture et
decrivait un etat impossible (SOC 20 pour un requis de 59,1 avec batterie
couverte). Deux scenarios ajoutes : la fin de fenetre bornee par DVCC — celui
qui protege contre la regression de 10 kW — et le contrefactuel a vehicule
branche qui epingle le choix du max.

Mutations : plafond DVCC retire -> 2 echecs dont la fin de fenetre ; max
remplace par une addition -> le contrefactuel seul ; bilan ignore -> les deux
scenarios pilotes par le bilan. Les autres suites sont intactes
(hysteresis 24, ACM 20, advisor, bilan 48).

⚠️ RIEN N EST POUSSE VERS n8n : la regle du depot impose la validation du
proprietaire avant tout PUT.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-18 02:11:56 +02:00
parent 8c2735c181
commit 8709e436df
7 changed files with 235 additions and 8 deletions

View File

@@ -359,8 +359,96 @@ const gridSessionActive =
night.nightGridSessionCommitted === true
);
// =====================================================
// DIMENSIONNEMENT SUR LE DEFICIT DU BILAN (18/09/2026)
// =====================================================
// Ce que la consigne remplace : `maison + plafond de la borne`, ou le plafond
// de la borne n'a AUCUNE relation physique avec ce dont la batterie a besoin.
// Qu'il tombe juste est un accident — releve du 17/09 a 13:51, voiture partie
// depuis cinq minutes, consigne a 7032 W pour une maison a 933 W.
//
// La consigne additionne desormais des besoins NOMMES :
// maison toujours ;
// voiture seulement si un vehicule est branche ;
// batterie seulement dans une fenetre d'heures creuses.
//
// ⚠️ MAX ET NON ADDITION, et ce n'est pas de la prudence. La consigne est une
// AUTORISATION, pas une prediction de consommation — le commentaire de la bande
// asymetrique le dit plus bas : « la consigne dit ce que le reseau a le DROIT
// de servir, DVCC dit ce que la batterie a le droit de prendre ». Additionner
// deux autorisations independantes n'est pas plus honnete, seulement plus
// permissif. Mesure a l'appui : avec l'addition,
// `consigne-session-engagee-couvre-la-maison` passe de 5595 a 6341 W, soit
// 746 W d'autorisation en plus sur une nuit engagee qui fonctionne deja — une
// regression sur le chemin DOMINANT pour corriger un chemin emprunte zero fois
// en treize jours.
//
// ⚠️ ET LE PLAFOND DU TERME BATTERIE EST DVCC, PAS LE BMS. Le BMS declare
// 250 A, soit 12,2 kW a 49 V : au-dessus de GRID_SETPOINT_MAX_W, donc il ne
// bornerait rien. Mesure : en fin de fenetre (14 min restantes, manque
// 5,672 kWh) la division rendait 17 kW, ecretes a 10 000 — PIRE que les 5437
// d'aujourd'hui. Avec DVCC le terme vaut 1538 W et la consigne 2380 W.
// Composer les deux leviers, ce n'est pas les confondre : le reseau n'est
// autorise a servir la batterie qu'a hauteur de ce que la batterie est
// autorisee a prendre.
//
// Corollaire : aucun plancher d'horizon n'est necessaire. Un horizon qui tend
// vers zero donne `min(enorme, DVCC) = DVCC`. Il ne reste qu'une garde d'une
// minute, qui est de l'arithmetique defensive et non une constante calibree.
//
// INVARIANT TENU PAR LA FORME : `maison + max(...)` ne peut jamais descendre
// sous la maison, quel que soit DVCC. La regle du 01/09 et du 04/09 — « tant
// que le contacteur est ferme A NOTRE DEMANDE, la consigne doit couvrir la
// maison » — est donc garantie par construction. A DVCC nul, le plafond vaut
// exactement la maison.
const SETPOINT_BALANCE_MAX_AGE_MIN = 30;
const setpointBalanceAgeMin = Number.isFinite(params.balanceLastRunMs)
? (nowMs - params.balanceLastRunMs) / 60000
: null;
// Le manque, pas l'energie a couvrir : voir le commentaire d'INPUT_PARSE.
const balanceUsableForSetpoint =
params.balanceStatusOk &&
Number.isFinite(params.balanceGridEnergyNeededKwh) &&
setpointBalanceAgeMin !== null &&
setpointBalanceAgeMin >= -5 &&
setpointBalanceAgeMin <= SETPOINT_BALANCE_MAX_AGE_MIN;
const dvccChargeLimitW =
Number.isFinite(params.dvccChargeLimitA) && params.dvccChargeLimitA >= 0
? params.dvccChargeLimitA * derived.batteryVoltageRef
: null;
// Toute lecture manquante retombe sur la formule d'avant : degradation vers
// l'existant, jamais pire.
const setpointFromBalance = balanceUsableForSetpoint && dvccChargeLimitW !== null;
const evcsSetpointShareW = parsed.evcsVehicleConnected
? derived.physicalCapA * derived.acVoltageRef
: 0;
// Recharger la batterie sur le reseau n'a de sens economique QUE dans une
// fenetre creuse. Hors fenetre le terme vaut zero — ce n'est pas un repli
// faute d'horizon, c'est le comportement voulu, et le probleme de la division
// par un temps qui tend vers zero disparait par construction.
const hcMinutesLeft = Number.isFinite(windows.minutesRemainingInHcWindow)
? windows.minutesRemainingInHcWindow
: null;
const batterySetpointShareW =
setpointFromBalance && hcMinutesLeft !== null && params.balanceGridEnergyNeededKwh > 0
? Math.min(
(params.balanceGridEnergyNeededKwh * 1000) / Math.max(hcMinutesLeft / 60, 1 / 60),
dvccChargeLimitW
)
: 0;
const gridSetpointCeilingW = clamp(
Math.round(derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef),
Math.round(
setpointFromBalance
? derived.houseNonEvcsW + Math.max(evcsSetpointShareW, batterySetpointShareW)
: derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef
),
params.gridSetpointIdleW,
GRID_SETPOINT_MAX_W
);
@@ -431,8 +519,16 @@ const batterySurgeBudgetW = Math.max(
0,
Math.abs(batteryPolicy.batteryDischargeHardDynamicA) * derived.batteryVoltageRef * 0.8
);
const gridSetpointLoadW =
derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef;
// ⚠️ LE PLANCHER NUAGE SUIT LA VOITURE, PAS LA BATTERIE. Il existe pour que la
// consigne ne descende pas au point ou la perte totale du solaire depasserait
// ce que la batterie sait encaisser — et le commentaire ci-dessus dit pourquoi :
// « c'est le courant de la voiture qui cree l'exposition ». Une batterie qu'on
// recharge depuis le reseau n'est PAS une exposition : si le solaire s'effondre,
// la recharge ralentit et personne ne se fait couper. L'y inclure remonterait le
// plancher sans aucune contrepartie de securite, et contre l'objectif.
const gridSetpointLoadW = setpointFromBalance
? derived.houseNonEvcsW + evcsSetpointShareW
: derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef;
const gridSetpointCloudFloorW = Math.round(
Math.max(0, gridSetpointLoadW - batterySurgeBudgetW)
);
@@ -736,6 +832,16 @@ return [{
in_midday_window: windows.inMiddayWindow,
in_evening_window: windows.inEveningWindow,
// D'ou vient la consigne reseau. Sans cette trace, un repli est
// SILENCIEUX : la consigne reprend la formule d'avant sans que rien ne le
// dise, et c'est precisement l'information qui manquera le jour ou elle se
// comportera mal. `bilan` = dimensionnee sur le manque, `heritee` = repli.
setpoint_source: setpointFromBalance ? 'bilan' : 'heritee',
setpoint_battery_share_w: Math.round(batterySetpointShareW),
setpoint_evcs_share_w: Math.round(evcsSetpointShareW),
dvcc_limit_w: dvccChargeLimitW === null ? null : Math.round(dvccChargeLimitW),
hc_minutes_left: hcMinutesLeft,
departure_priority_active: context.departurePriorityActive,
minutes_until_departure: lifecycle.minutesUntilDeparture,