Le nombre de modules se lit, il ne se code plus
La courbe energie/SOC a ete mesuree sur sept modules. Retirer un module divise la capacite sans changer la forme de la courbe : les pentes sont donc mises a l'echelle du nombre reellement en service, lu dans input_number.evcs_balance_modules_en_service. Le controleur EVCS lit le meme helper pour sa capacite utile - les deux decrivent forcement le meme parc. Helper absent, illisible ou hors bornes => sept, la valeur de la mesure. Les 44 scenarios du bilan et les 80 golden passent donc sans qu'un seul attendu ait ete touche : la bascule ne change rien tant que personne ne l'ecrit. Deux scenarios couvrent la bascule elle-meme, l'un avec quatre modules (SOC exige 30,5 -> 35,2 %), l'autre avec une valeur aberrante qui doit retomber sur sept. Preuve par mutation : ignorer l'echelle fait tomber le premier et lui seul. Le genou a 33 % est conserve par PRUDENCE et non par mesure : l'hypothese est qu'il vient des cellules faibles du pack retire, donc qu'il disparait avec lui. Le garder revient a sous-estimer la reserve, c'est-a-dire a se tromper du bon cote. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -161,12 +161,34 @@ function buildSlots(consumption) {
|
|||||||
// ⚠️ A RE-MESURER apres toute evolution du parc (extension prevue cet hiver) :
|
// ⚠️ A RE-MESURER apres toute evolution du parc (extension prevue cet hiver) :
|
||||||
// `node tools/mesure-courbe-batterie.mjs 8`, puis mettre a jour ces trois
|
// `node tools/mesure-courbe-batterie.mjs 8`, puis mettre a jour ces trois
|
||||||
// nombres. Ce ne sont pas des reglages, ce sont des resultats de mesure.
|
// nombres. Ce ne sont pas des reglages, ce sont des resultats de mesure.
|
||||||
|
//
|
||||||
|
// ⚠️ CES PENTES ONT ETE MESUREES SUR SEPT MODULES. Retirer ou ajouter un module
|
||||||
|
// divise ou multiplie la capacite sans changer la FORME de la courbe : on les
|
||||||
|
// exprime donc par module, et on les multiplie par le nombre reellement en
|
||||||
|
// service. Ce nombre se lit dans un helper — `input_number
|
||||||
|
// .evcs_balance_modules_en_service` — et non dans le code : le parc a change
|
||||||
|
// deux fois en une semaine et changera encore cet hiver. Helper absent ou
|
||||||
|
// illisible => 7, la valeur sur laquelle la mesure a ete faite, donc aucun
|
||||||
|
// changement de comportement tant que personne ne l'ecrit.
|
||||||
|
const MODULES_MESURE = 7;
|
||||||
|
const modulesEnService = (() => {
|
||||||
|
const n = num(input.modules_en_service, MODULES_MESURE);
|
||||||
|
return Number.isFinite(n) && n >= 1 && n <= 16 ? n : MODULES_MESURE;
|
||||||
|
})();
|
||||||
|
const echelleParc = modulesEnService / MODULES_MESURE;
|
||||||
|
|
||||||
const BATTERY_CURVE = {
|
const BATTERY_CURVE = {
|
||||||
kneePct: 33,
|
kneePct: 33,
|
||||||
kwhPerPointAbove: 0.169,
|
kwhPerPointAbove: 0.169 * echelleParc,
|
||||||
kwhPerPointBelow: 0.097,
|
kwhPerPointBelow: 0.097 * echelleParc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ⚠️ LE GENOU EST CONSERVE PAR PRUDENCE, PAS PAR MESURE. L'hypothese la mieux
|
||||||
|
// etayee est qu'il vient des cellules faibles du pack ancien, donc qu'il
|
||||||
|
// disparait avec lui — auquel cas la pente reste plate et la reserve reelle est
|
||||||
|
// MEILLEURE qu'annoncee ici. Le garder revient a se tromper du bon cote.
|
||||||
|
// A trancher en remesurant : `node tools/mesure-courbe-batterie.mjs 8`.
|
||||||
|
|
||||||
// Energie que la batterie peut delivrer en descendant de socHi a socLo.
|
// Energie que la batterie peut delivrer en descendant de socHi a socLo.
|
||||||
function energyBetweenSoc(socLo, socHi) {
|
function energyBetweenSoc(socLo, socHi) {
|
||||||
if (!Number.isFinite(socLo) || !Number.isFinite(socHi) || socHi <= socLo) return 0;
|
if (!Number.isFinite(socLo) || !Number.isFinite(socHi) || socHi <= socLo) return 0;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
"balance_enabled": states("input_boolean.evcs_balance_enabled"),
|
"balance_enabled": states("input_boolean.evcs_balance_enabled"),
|
||||||
"usable_capacity_kwh": states("input_number.evcs_balance_usable_capacity_kwh"),
|
"usable_capacity_kwh": states("input_number.evcs_balance_usable_capacity_kwh"),
|
||||||
|
"modules_en_service": states("input_number.evcs_balance_modules_en_service"),
|
||||||
|
|
||||||
"consumption_p50_csv": states("input_text.evcs_balance_consumption_p50"),
|
"consumption_p50_csv": states("input_text.evcs_balance_consumption_p50"),
|
||||||
"consumption_p80_csv": states("input_text.evcs_balance_consumption_p80"),
|
"consumption_p80_csv": states("input_text.evcs_balance_consumption_p80"),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ return [{
|
|||||||
|
|
||||||
soc_percent: snapshot.soc_percent,
|
soc_percent: snapshot.soc_percent,
|
||||||
usable_capacity_kwh: snapshot.usable_capacity_kwh,
|
usable_capacity_kwh: snapshot.usable_capacity_kwh,
|
||||||
|
modules_en_service: snapshot.modules_en_service,
|
||||||
// Plancher dur : même valeur de repli que INPUT_PARSE.js
|
// Plancher dur : même valeur de repli que INPUT_PARSE.js
|
||||||
// (evcs_night_battery_hard_floor_pct n'est aujourd'hui exposé par AUCUN
|
// (evcs_night_battery_hard_floor_pct n'est aujourd'hui exposé par AUCUN
|
||||||
// helper — la valeur « 22 % » documentée partout est en réalité un défaut
|
// helper — la valeur « 22 % » documentée partout est en réalité un défaut
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"battery_reserve_up_a": states("input_number.victron_evcs_battery_reserve_up_a"),
|
"battery_reserve_up_a": states("input_number.victron_evcs_battery_reserve_up_a"),
|
||||||
"battery_neutral_band_a": states("input_number.victron_evcs_battery_neutral_band_a"),
|
"battery_neutral_band_a": states("input_number.victron_evcs_battery_neutral_band_a"),
|
||||||
"soc_start_min": states("input_number.victron_evcs_soc_start_min"),
|
"soc_start_min": states("input_number.victron_evcs_soc_start_min"),
|
||||||
|
"modules_en_service": states("input_number.evcs_balance_modules_en_service"),
|
||||||
"soc_stop_min": states("input_number.victron_evcs_soc_stop_min"),
|
"soc_stop_min": states("input_number.victron_evcs_soc_stop_min"),
|
||||||
"current_min_a": states("input_number.victron_evcs_current_min_a"),
|
"current_min_a": states("input_number.victron_evcs_current_min_a"),
|
||||||
"current_max_auto_a": states("input_number.victron_evcs_current_max_auto_a"),
|
"current_max_auto_a": states("input_number.victron_evcs_current_max_auto_a"),
|
||||||
|
|||||||
@@ -220,7 +220,15 @@ const CFG = {
|
|||||||
// ici qu'a des ecarts SOC -> cible, tous deux au-dessus du genou. Si un calcul
|
// ici qu'a des ecarts SOC -> cible, tous deux au-dessus du genou. Si un calcul
|
||||||
// devait un jour traverser le genou, il faut la COURBE (src/energy-balance/
|
// devait un jour traverser le genou, il faut la COURBE (src/energy-balance/
|
||||||
// BALANCE.js) et non cette constante.
|
// BALANCE.js) et non cette constante.
|
||||||
batteryUsableCapacityKwh: 16.9,
|
// 16,9 kWh mesures sur SEPT modules (§41), soit 2,414 kWh par module. Le parc
|
||||||
|
// ayant change deux fois en une semaine, le nombre de modules se LIT au lieu
|
||||||
|
// d'etre code : `input_number.evcs_balance_modules_en_service`, le meme helper
|
||||||
|
// que le bilan energetique — les deux decrivent forcement le meme parc.
|
||||||
|
// Absent ou illisible => 7, donc aucun changement tant que personne ne l'ecrit.
|
||||||
|
batteryUsableCapacityKwh: 2.414 * (() => {
|
||||||
|
const n = Number(input.modules_en_service);
|
||||||
|
return Number.isFinite(n) && n >= 1 && n <= 16 ? n : 7;
|
||||||
|
})(),
|
||||||
nominalAcVoltageV: 230,
|
nominalAcVoltageV: 230,
|
||||||
evcsDedicatedMaxW: 4900,
|
evcsDedicatedMaxW: 4900,
|
||||||
systemReserveW: 300,
|
systemReserveW: 300,
|
||||||
|
|||||||
@@ -36,6 +36,56 @@ function horizonFrom(startIso, hours, production) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const balanceScenarios = [
|
export const balanceScenarios = [
|
||||||
|
{
|
||||||
|
// Le parc a change deux fois en une semaine et rechangera cet hiver : le
|
||||||
|
// nombre de modules se lit dans un helper au lieu d'etre code. Ce scenario
|
||||||
|
// rejoue exactement la matinee du 24/08 avec quatre modules au lieu de
|
||||||
|
// sept. Meme energie a couvrir, meme creneau — seule la capacite change,
|
||||||
|
// donc le SOC exige monte de 30,5 a 35,2 %. Son jumeau ci-dessous, sans le
|
||||||
|
// champ, prouve que l'absence du helper ne change rien.
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-parc-reduit-a-quatre-modules',
|
||||||
|
label: 'Pack ancien isole : quatre modules au lieu de sept, meme matinee du 24/08 → le SOC exige passe de 30,5 a 35,2 %',
|
||||||
|
snapshot: {
|
||||||
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
|
consumption_p50: P50,
|
||||||
|
consumption_p80: P80,
|
||||||
|
horizon: horizonFrom('2026-08-24T06:43:00+02:00', 18, PROD_24_08),
|
||||||
|
soc_percent: 21,
|
||||||
|
usable_capacity_kwh: 15,
|
||||||
|
soc_floor_percent: 22,
|
||||||
|
modules_en_service: 4,
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
usable: true,
|
||||||
|
crossingHour: 8,
|
||||||
|
energyToCoverKwh: 0.82,
|
||||||
|
requiredSocPct: 35.2,
|
||||||
|
gridNeeded: true,
|
||||||
|
prudentRequiredSocPct: 51.7,
|
||||||
|
deficitTrustworthy: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-modules-valeur-aberrante-retombe-sur-sept',
|
||||||
|
label: 'Helper illisible (0 module) : on retombe sur les sept de la mesure plutot que de diviser par zero',
|
||||||
|
snapshot: {
|
||||||
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
|
consumption_p50: P50,
|
||||||
|
consumption_p80: P80,
|
||||||
|
horizon: horizonFrom('2026-08-24T06:43:00+02:00', 18, PROD_24_08),
|
||||||
|
soc_percent: 21,
|
||||||
|
usable_capacity_kwh: 15,
|
||||||
|
soc_floor_percent: 22,
|
||||||
|
modules_en_service: 0,
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
usable: true,
|
||||||
|
requiredSocPct: 30.5,
|
||||||
|
prudentRequiredSocPct: 41,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
node: 'BALANCE',
|
node: 'BALANCE',
|
||||||
id: 'bilan-rejeu-24-08-matin',
|
id: 'bilan-rejeu-24-08-matin',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user