Recalage de la courbe batterie sur le parc assaini (rec 1)
Re-mesure du 20/09 sur le parc a 13 modules assaini (cellules 3-6 mV) : tools/mesure-courbe-batterie.mjs donne 0,30 kWh/point PLAT de 25 a 97 %, PLUS DE GENOU. Le genou a 33 % etait la signature de la cellule 12 morte du pack ancien (§50), il a disparu avec elle — exactement ce que predisait la note de prudence du 03/09. La reserve utile a quasiment double (12,4 -> ~23 kWh de 100 a 22 %). Le scaling modules/7 rattrapait par chance la partie HAUTE (0,169x13/7=0,31 ~ mesure) mais SOUS 33 % le bilan croyait 0,18 kWh/point contre 0,30 reels -> required_soc surestime en bas d'echelle. Corrige : - BALANCE.js : kneePct 33->0, pentes 0,169/0,097 -> 0,1615 plat (x13/7 = 0,30). - INPUT_PARSE : batteryUsableCapacityKwh 2,414 -> 2,308 kWh/module (0,30/13x100). - helper evcs_balance_usable_capacity_kwh 15 -> 23 (inerte mais coherent). Tests : bilan 50/50 apres re-figeage des 10 attendus (verifies a la main : ex rejeu 24/08, 0,82 kWh -> 22 + 0,82/0,1615 = 27,1 % contre 30,5 avant). Mutation deux sens : ancien genou -> 10 echecs, pente fausse -> 11. Golden controleur 116/116 inchange (capacite -4 %, aucun basculement discret). ⚠️ Courbe a REMESURER apres toute evolution du parc. 2e cable console en commande pour surveiller aussi les cellules du pack 1 (US5000), aujourd'hui hors surveillance continue (trames etendues Cerbo mortes, §69). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
@@ -212,18 +212,22 @@ const modulesEnService = (() => {
|
|||||||
})();
|
})();
|
||||||
const echelleParc = modulesEnService / MODULES_MESURE;
|
const echelleParc = modulesEnService / MODULES_MESURE;
|
||||||
|
|
||||||
|
// 20/09/2026 — RE-MESURE sur le parc ASSAINI (13 equivalents US2000 : 4 US5000
|
||||||
|
// + 5 US2000C, cellules a 3-6 mV). `tools/mesure-courbe-batterie.mjs 8` :
|
||||||
|
// 0,30 kWh/point PLAT de 25 a 97 %, PLUS DE GENOU. Le genou a 33 % etait bien
|
||||||
|
// la signature de la cellule 12 morte du pack ancien (§50) — il a disparu avec
|
||||||
|
// elle, exactement comme le predisait la note de prudence du 03/09. La reserve
|
||||||
|
// reelle a quasiment double (12,4 -> ~23 kWh utiles de 100 a 22 %).
|
||||||
|
// Base normalisee a 7 modules (MODULES_MESURE) : 0,30 x 7/13 = 0,1615 par point.
|
||||||
|
// x echelleParc(13/7) = 0,30 au parc actuel. Pente unique => plus de genou.
|
||||||
|
// A REMESURER apres toute evolution du parc (2e cable console en commande pour
|
||||||
|
// surveiller aussi les cellules du pack 1 US5000).
|
||||||
const BATTERY_CURVE = {
|
const BATTERY_CURVE = {
|
||||||
kneePct: 33,
|
kneePct: 0,
|
||||||
kwhPerPointAbove: 0.169 * echelleParc,
|
kwhPerPointAbove: 0.1615 * echelleParc,
|
||||||
kwhPerPointBelow: 0.097 * echelleParc,
|
kwhPerPointBelow: 0.1615 * 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;
|
||||||
|
|||||||
@@ -222,12 +222,13 @@ 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.
|
||||||
// 16,9 kWh mesures sur SEPT modules (§41), soit 2,414 kWh par module. Le parc
|
// 20/09/2026 — RE-MESURE sur le parc assaini (13 modules, courbe PLATE a
|
||||||
// ayant change deux fois en une semaine, le nombre de modules se LIT au lieu
|
// 0,30 kWh/point, plus de genou) : 0,30/point / 13 modules x 100 points =
|
||||||
// d'etre code : `input_number.evcs_balance_modules_en_service`, le meme helper
|
// 2,308 kWh par module (etait 2,414 sur l'ancien parc a 7 modules abimes).
|
||||||
// que le bilan energetique — les deux decrivent forcement le meme parc.
|
// Le nombre de modules se LIT (meme helper que le bilan) — les deux decrivent
|
||||||
// Absent ou illisible => 7, donc aucun changement tant que personne ne l'ecrit.
|
// forcement le meme parc. Absent ou illisible => 7 (aucun changement tant que
|
||||||
batteryUsableCapacityKwh: 2.414 * (() => {
|
// personne ne l'ecrit). A remesurer apres toute evolution du parc.
|
||||||
|
batteryUsableCapacityKwh: 2.308 * (() => {
|
||||||
const n = Number(input.modules_en_service);
|
const n = Number(input.modules_en_service);
|
||||||
return Number.isFinite(n) && n >= 1 && n <= 24 ? n : 7;
|
return Number.isFinite(n) && n >= 1 && n <= 24 ? n : 7;
|
||||||
})(),
|
})(),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const balanceScenarios = [
|
|||||||
// champ, prouve que l'absence du helper ne change rien.
|
// champ, prouve que l'absence du helper ne change rien.
|
||||||
node: 'BALANCE',
|
node: 'BALANCE',
|
||||||
id: 'bilan-parc-reduit-a-quatre-modules',
|
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 %',
|
label: 'Pack ancien isole : quatre modules au lieu de sept, meme matinee du 24/08 → le SOC exige passe de 27,1 a 30,9 %',
|
||||||
snapshot: {
|
snapshot: {
|
||||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
consumption_p50: P50,
|
consumption_p50: P50,
|
||||||
@@ -60,9 +60,9 @@ export const balanceScenarios = [
|
|||||||
usable: true,
|
usable: true,
|
||||||
crossingHour: 8,
|
crossingHour: 8,
|
||||||
energyToCoverKwh: 0.82,
|
energyToCoverKwh: 0.82,
|
||||||
requiredSocPct: 35.2,
|
requiredSocPct: 30.9,
|
||||||
gridNeeded: true,
|
gridNeeded: true,
|
||||||
prudentRequiredSocPct: 51.7,
|
prudentRequiredSocPct: 48.2,
|
||||||
deficitTrustworthy: true,
|
deficitTrustworthy: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -75,7 +75,7 @@ export const balanceScenarios = [
|
|||||||
// ensemble, ce que ce scenario verifie en exigeant les deux.
|
// ensemble, ce que ce scenario verifie en exigeant les deux.
|
||||||
node: 'BALANCE',
|
node: 'BALANCE',
|
||||||
id: 'bilan-plancher-manuel-releve-la-cible-et-le-manque',
|
id: 'bilan-plancher-manuel-releve-la-cible-et-le-manque',
|
||||||
label: 'Charge HC manuelle a 85 % : la cible passe de 30,5 a 85 % ET le manque suit (9,95 kWh au lieu de 0,82)',
|
label: 'Charge HC manuelle a 85 % : la cible passe de 27,1 a 85 % ET le manque suit (10,34 kWh au lieu de 0,82)',
|
||||||
snapshot: {
|
snapshot: {
|
||||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
consumption_p50: P50,
|
consumption_p50: P50,
|
||||||
@@ -91,7 +91,7 @@ export const balanceScenarios = [
|
|||||||
requiredSocPct: 85,
|
requiredSocPct: 85,
|
||||||
gridNeeded: true,
|
gridNeeded: true,
|
||||||
// 21 -> 85 traverse le genou de 33 % : 12 points a 0,097 puis 52 a 0,169.
|
// 21 -> 85 traverse le genou de 33 % : 12 points a 0,097 puis 52 a 0,169.
|
||||||
gridEnergyNeededKwh: 9.95,
|
gridEnergyNeededKwh: 10.34,
|
||||||
// Le plancher passe par `coverage()`, appelee pour les DEUX profils : la
|
// Le plancher passe par `coverage()`, appelee pour les DEUX profils : la
|
||||||
// cible prudente monte aussi. Consequence assumee — pendant une charge
|
// cible prudente monte aussi. Consequence assumee — pendant une charge
|
||||||
// manuelle, P12 cesse de descendre la batterie pour la voiture.
|
// manuelle, P12 cesse de descendre la batterie pour la voiture.
|
||||||
@@ -101,7 +101,7 @@ export const balanceScenarios = [
|
|||||||
{
|
{
|
||||||
node: 'BALANCE',
|
node: 'BALANCE',
|
||||||
id: 'bilan-plancher-manuel-ne-baisse-jamais-la-cible',
|
id: 'bilan-plancher-manuel-ne-baisse-jamais-la-cible',
|
||||||
label: 'CONTREFACTUEL — plancher manuel a 25 %, sous les 30,5 % calcules : le bilan garde SA cible',
|
label: 'CONTREFACTUEL — plancher manuel a 25 %, sous les 27,1 % calcules : le bilan garde SA cible',
|
||||||
snapshot: {
|
snapshot: {
|
||||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
consumption_p50: P50,
|
consumption_p50: P50,
|
||||||
@@ -117,8 +117,8 @@ export const balanceScenarios = [
|
|||||||
// que RELEVER l'exigence. Un reglage manuel trop bas ne desarme pas le
|
// que RELEVER l'exigence. Un reglage manuel trop bas ne desarme pas le
|
||||||
// bilan, il est simplement sans effet.
|
// bilan, il est simplement sans effet.
|
||||||
usable: true,
|
usable: true,
|
||||||
requiredSocPct: 30.5,
|
requiredSocPct: 27.1,
|
||||||
prudentRequiredSocPct: 41,
|
prudentRequiredSocPct: 37,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,14 +137,14 @@ export const balanceScenarios = [
|
|||||||
},
|
},
|
||||||
expect: {
|
expect: {
|
||||||
usable: true,
|
usable: true,
|
||||||
requiredSocPct: 30.5,
|
requiredSocPct: 27.1,
|
||||||
prudentRequiredSocPct: 41,
|
prudentRequiredSocPct: 37,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
node: 'BALANCE',
|
node: 'BALANCE',
|
||||||
id: 'bilan-rejeu-24-08-matin',
|
id: 'bilan-rejeu-24-08-matin',
|
||||||
label: 'Rejeu réel du 24/08 06:43 : croisement à 08 h, 0,82 kWh à couvrir → SOC requis 30,5 % (courbe mesurée). Le seuil fixe de 30 % tombait juste ce matin-là — par coïncidence, pas par construction',
|
label: 'Rejeu réel du 24/08 06:43 : croisement à 08 h, 0,82 kWh à couvrir → SOC requis 27,1 % (courbe plate remesuree 20/09). Le seuil fixe de 30 % tombait juste ce matin-là — par coïncidence, pas par construction',
|
||||||
snapshot: {
|
snapshot: {
|
||||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||||
consumption_p50: P50,
|
consumption_p50: P50,
|
||||||
@@ -158,9 +158,9 @@ export const balanceScenarios = [
|
|||||||
usable: true,
|
usable: true,
|
||||||
crossingHour: 8,
|
crossingHour: 8,
|
||||||
energyToCoverKwh: 0.82,
|
energyToCoverKwh: 0.82,
|
||||||
requiredSocPct: 30.5,
|
requiredSocPct: 27.1,
|
||||||
gridNeeded: true,
|
gridNeeded: true,
|
||||||
prudentRequiredSocPct: 41,
|
prudentRequiredSocPct: 37,
|
||||||
deficitTrustworthy: true,
|
deficitTrustworthy: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -180,7 +180,7 @@ export const balanceScenarios = [
|
|||||||
expect: {
|
expect: {
|
||||||
usable: true,
|
usable: true,
|
||||||
gridNeeded: false,
|
gridNeeded: false,
|
||||||
requiredSocPct: 30.5,
|
requiredSocPct: 27.1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -230,8 +230,8 @@ export const balanceScenarios = [
|
|||||||
nextDeficitEnergyKwh: 12.77,
|
nextDeficitEnergyKwh: 12.77,
|
||||||
// Au-dessus de 100 : même pleine, la batterie actuelle ne passe pas la
|
// Au-dessus de 100 : même pleine, la batterie actuelle ne passe pas la
|
||||||
// nuit sur le profil prudent. C'est le chiffre du dimensionnement.
|
// nuit sur le profil prudent. C'est le chiffre du dimensionnement.
|
||||||
nextDeficitRequiredSocPct: 102.2,
|
nextDeficitRequiredSocPct: 101.1,
|
||||||
storageShortfallKwh: 0.38,
|
storageShortfallKwh: 0.17,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -277,7 +277,7 @@ export const balanceScenarios = [
|
|||||||
// La réserve se calcule quand même : 2,42 kWh pour la matinée du 24/08
|
// La réserve se calcule quand même : 2,42 kWh pour la matinée du 24/08
|
||||||
// sur le profil prudent, soit SOC 38,1 %.
|
// sur le profil prudent, soit SOC 38,1 %.
|
||||||
stormReserveKwh: 2.42,
|
stormReserveKwh: 2.42,
|
||||||
stormReserveSocPct: 41,
|
stormReserveSocPct: 37,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -321,11 +321,11 @@ export const balanceScenarios = [
|
|||||||
stormLevel: 'orange',
|
stormLevel: 'orange',
|
||||||
stormActive: true,
|
stormActive: true,
|
||||||
stormReserveKwh: 2.42,
|
stormReserveKwh: 2.42,
|
||||||
stormReserveSocPct: 41,
|
stormReserveSocPct: 37,
|
||||||
stormCovered: false,
|
stormCovered: false,
|
||||||
// SOC 21 contre 38,1 requis : il manque 2,57 kWh pour tenir la matinée
|
// SOC 21 contre 38,1 requis : il manque 2,57 kWh pour tenir la matinée
|
||||||
// sans réseau.
|
// sans réseau.
|
||||||
stormShortfallKwh: 2.52,
|
stormShortfallKwh: 2.58,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -354,9 +354,9 @@ export const balanceScenarios = [
|
|||||||
// 107,1 % — même pleine, la batterie actuelle ne passe pas la nuit sans
|
// 107,1 % — même pleine, la batterie actuelle ne passe pas la nuit sans
|
||||||
// réseau. C'est le signal de dimensionnement, il ne doit pas être écrêté.
|
// réseau. C'est le signal de dimensionnement, il ne doit pas être écrêté.
|
||||||
stormReserveKwh: 12.77,
|
stormReserveKwh: 12.77,
|
||||||
stormReserveSocPct: 102.2,
|
stormReserveSocPct: 101.1,
|
||||||
stormCovered: false,
|
stormCovered: false,
|
||||||
stormShortfallKwh: 1.22,
|
stormShortfallKwh: 0.98,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
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