Compare commits
2 Commits
dff6809560
...
7b2e8e74d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b2e8e74d9 | ||
|
|
c0704deec3 |
@@ -212,18 +212,22 @@ const modulesEnService = (() => {
|
||||
})();
|
||||
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 = {
|
||||
kneePct: 33,
|
||||
kwhPerPointAbove: 0.169 * echelleParc,
|
||||
kwhPerPointBelow: 0.097 * echelleParc,
|
||||
kneePct: 0,
|
||||
kwhPerPointAbove: 0.1615 * 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.
|
||||
function energyBetweenSoc(socLo, socHi) {
|
||||
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
|
||||
// devait un jour traverser le genou, il faut la COURBE (src/energy-balance/
|
||||
// BALANCE.js) et non cette constante.
|
||||
// 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 * (() => {
|
||||
// 20/09/2026 — RE-MESURE sur le parc assaini (13 modules, courbe PLATE a
|
||||
// 0,30 kWh/point, plus de genou) : 0,30/point / 13 modules x 100 points =
|
||||
// 2,308 kWh par module (etait 2,414 sur l'ancien parc a 7 modules abimes).
|
||||
// Le nombre de modules se LIT (meme helper que le bilan) — les deux decrivent
|
||||
// forcement le meme parc. Absent ou illisible => 7 (aucun changement tant que
|
||||
// personne ne l'ecrit). A remesurer apres toute evolution du parc.
|
||||
batteryUsableCapacityKwh: 2.308 * (() => {
|
||||
const n = Number(input.modules_en_service);
|
||||
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.
|
||||
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 %',
|
||||
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: {
|
||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||
consumption_p50: P50,
|
||||
@@ -60,9 +60,9 @@ export const balanceScenarios = [
|
||||
usable: true,
|
||||
crossingHour: 8,
|
||||
energyToCoverKwh: 0.82,
|
||||
requiredSocPct: 35.2,
|
||||
requiredSocPct: 30.9,
|
||||
gridNeeded: true,
|
||||
prudentRequiredSocPct: 51.7,
|
||||
prudentRequiredSocPct: 48.2,
|
||||
deficitTrustworthy: true,
|
||||
},
|
||||
},
|
||||
@@ -75,7 +75,7 @@ export const balanceScenarios = [
|
||||
// ensemble, ce que ce scenario verifie en exigeant les deux.
|
||||
node: 'BALANCE',
|
||||
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: {
|
||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||
consumption_p50: P50,
|
||||
@@ -91,7 +91,7 @@ export const balanceScenarios = [
|
||||
requiredSocPct: 85,
|
||||
gridNeeded: true,
|
||||
// 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
|
||||
// cible prudente monte aussi. Consequence assumee — pendant une charge
|
||||
// manuelle, P12 cesse de descendre la batterie pour la voiture.
|
||||
@@ -101,7 +101,7 @@ export const balanceScenarios = [
|
||||
{
|
||||
node: 'BALANCE',
|
||||
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: {
|
||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||
consumption_p50: P50,
|
||||
@@ -117,8 +117,8 @@ export const balanceScenarios = [
|
||||
// que RELEVER l'exigence. Un reglage manuel trop bas ne desarme pas le
|
||||
// bilan, il est simplement sans effet.
|
||||
usable: true,
|
||||
requiredSocPct: 30.5,
|
||||
prudentRequiredSocPct: 41,
|
||||
requiredSocPct: 27.1,
|
||||
prudentRequiredSocPct: 37,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -137,14 +137,14 @@ export const balanceScenarios = [
|
||||
},
|
||||
expect: {
|
||||
usable: true,
|
||||
requiredSocPct: 30.5,
|
||||
prudentRequiredSocPct: 41,
|
||||
requiredSocPct: 27.1,
|
||||
prudentRequiredSocPct: 37,
|
||||
},
|
||||
},
|
||||
{
|
||||
node: 'BALANCE',
|
||||
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: {
|
||||
nowMs: Date.parse('2026-08-24T06:43:00+02:00'),
|
||||
consumption_p50: P50,
|
||||
@@ -158,9 +158,9 @@ export const balanceScenarios = [
|
||||
usable: true,
|
||||
crossingHour: 8,
|
||||
energyToCoverKwh: 0.82,
|
||||
requiredSocPct: 30.5,
|
||||
requiredSocPct: 27.1,
|
||||
gridNeeded: true,
|
||||
prudentRequiredSocPct: 41,
|
||||
prudentRequiredSocPct: 37,
|
||||
deficitTrustworthy: true,
|
||||
},
|
||||
},
|
||||
@@ -180,7 +180,7 @@ export const balanceScenarios = [
|
||||
expect: {
|
||||
usable: true,
|
||||
gridNeeded: false,
|
||||
requiredSocPct: 30.5,
|
||||
requiredSocPct: 27.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -230,8 +230,8 @@ export const balanceScenarios = [
|
||||
nextDeficitEnergyKwh: 12.77,
|
||||
// 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.
|
||||
nextDeficitRequiredSocPct: 102.2,
|
||||
storageShortfallKwh: 0.38,
|
||||
nextDeficitRequiredSocPct: 101.1,
|
||||
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
|
||||
// sur le profil prudent, soit SOC 38,1 %.
|
||||
stormReserveKwh: 2.42,
|
||||
stormReserveSocPct: 41,
|
||||
stormReserveSocPct: 37,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -321,11 +321,11 @@ export const balanceScenarios = [
|
||||
stormLevel: 'orange',
|
||||
stormActive: true,
|
||||
stormReserveKwh: 2.42,
|
||||
stormReserveSocPct: 41,
|
||||
stormReserveSocPct: 37,
|
||||
stormCovered: false,
|
||||
// SOC 21 contre 38,1 requis : il manque 2,57 kWh pour tenir la matinée
|
||||
// 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
|
||||
// réseau. C'est le signal de dimensionnement, il ne doit pas être écrêté.
|
||||
stormReserveKwh: 12.77,
|
||||
stormReserveSocPct: 102.2,
|
||||
stormReserveSocPct: 101.1,
|
||||
stormCovered: false,
|
||||
stormShortfallKwh: 1.22,
|
||||
stormShortfallKwh: 0.98,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -182,10 +182,10 @@ export const scenarios = [
|
||||
},
|
||||
{
|
||||
id: 'acm-jour-topoff-52v4',
|
||||
label: "LE CORRECTIF DU 06/09 : SOC 93 dans la tranche 90-95, la tension monte a 52,4 V et le parc reprend de la charge",
|
||||
label: "LE CORRECTIF DU 06/09 : SOC 93 dans la tranche 90-95, la tension monte a 52,8 V (parc assaini, top-off releve 20/09) et le parc reprend de la charge",
|
||||
time: h(ETE, '15:00'),
|
||||
overrides: { ...parcNeuf, soc_percent: '93', pv_power_w: '4000', battery_voltage_v: '51.1' },
|
||||
expect: { mode: 'day_pv_topoff', profil: 'pv_topoff', courantA: 80, tensionV: 52.4 },
|
||||
expect: { mode: 'day_pv_topoff', profil: 'pv_topoff', courantA: 80, tensionV: 52.8 },
|
||||
},
|
||||
{
|
||||
id: 'acm-jour-hold-au-dessus-de-95',
|
||||
|
||||
18
tools/patch-20260920-topoff-52v8.mjs
Normal file
18
tools/patch-20260920-topoff-52v8.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Rec 2 (20/09/2026) : top-off ACM 52,4 -> 52,8 V, parc assaini.
|
||||
// Le 52,4 V avait ete pose le 06/09 pour menager la cellule 12 faible du pack
|
||||
// ANCIEN (§57). Ce pack est sorti ; les cellules du pack 2 sont a 3-6 mV, la
|
||||
// plus haute a 3356 mV, loin des 3550 mV ou le BMS reduit le courant. On releve
|
||||
// donc le top-off pour atteindre une charge plus pleine et RECALIBRER la jauge
|
||||
// SOC Pylontech (qui derive tant qu'on n'atteint jamais 100 %). 52,8 V =
|
||||
// 3,52 V/cellule, sous les 53,2 V que le BMS autorise.
|
||||
// ⚠️ Les cellules du pack 1 (US5000) ne sont pas encore sous surveillance
|
||||
// continue (2e cable en commande) — le proprietaire a autorise explicitement.
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
const f = 'workflows/victron-auto-charge-manager-v1.2.json';
|
||||
const s = readFileSync(f, 'utf8');
|
||||
const avant = "pv_topoff: { current_a: 80, voltage_v: 52.4 }";
|
||||
const apres = "pv_topoff: { current_a: 80, voltage_v: 52.8 }";
|
||||
if (!s.includes(avant)) { console.error('motif pv_topoff 52.4 introuvable'); process.exit(1); }
|
||||
if (s.split(avant).length - 1 !== 1) { console.error('motif non unique'); process.exit(1); }
|
||||
writeFileSync(f, s.replace(avant, apres));
|
||||
console.log('top-off 52.4 -> 52.8 V applique');
|
||||
File diff suppressed because one or more lines are too long
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