// Scénarios du nœud BALANCE. // Le scénario central est un REJEU de la matinée du 24/08/2026 : la production // horaire est celle réellement mesurée ce jour-là (heures 06-09 h non // censurées, donc fiables), le SOC est celui relevé à 06:43, et la capacité // est celle du parc réel (7 modules actifs, ~15 kWh). // C'est la situation qui a déclenché tout le chantier : le seuil fixe de 30 % // et le yoyo EDF de 2 à 4 minutes. const P50 = [0.70,0.70,0.70,0.70,0.60,0.70,0.70,0.80,0.90,1.40,1.40,2.00, 2.40,1.90,2.10,1.90,1.80,2.00,1.50,1.70,1.60,1.30,1.30,1.10]; const P80 = [1.00,0.70,0.70,0.70,0.70,0.80,1.30,1.80,1.70,2.00,2.50,3.10, 3.50,2.60,2.50,2.30,2.20,2.40,2.30,1.90,1.90,1.60,1.40,1.20]; // Production horaire réellement mesurée le 24/08/2026. const PROD_24_08 = { 6: 0.03, 7: 0.65, 8: 2.70, 9: 5.62, 10: 7.17, 11: 9.33, 12: 4.37, 13: 2.10, 14: 1.20, 15: 0.90, 16: 0.70, 17: 0.50, 18: 0.30, 19: 0.10, 20: 0.00, 21: 0.00, 22: 0.00, 23: 0.00, }; function horizonFrom(startIso, hours, production) { const h0 = Math.floor(Date.parse(startIso) / 3600000) * 3600000; return Array.from({ length: hours }, (_, i) => { const ms = h0 + i * 3600000; // parseInt et non Number : en fr-FR le format renvoie « 06 h ». const hour = Number.parseInt(new Intl.DateTimeFormat('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', hour12: false, }).format(new Date(ms)), 10); return { startMs: ms, hour, expectedKwh: production[hour] ?? 0, shapeQuality: hour >= 12 && hour <= 19 ? 'minoree' : 'fiable', }; }); } export const balanceScenarios = [ { 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 27,5 % au lieu du seuil fixe de 30 %', 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, }, expect: { usable: true, crossingHour: 8, energyToCoverKwh: 0.82, requiredSocPct: 27.5, gridNeeded: true, prudentRequiredSocPct: 38.1, deficitTrustworthy: true, }, }, { node: 'BALANCE', id: 'bilan-soc-suffisant-pas-de-reseau', label: 'Même matinée avec un SOC de 45 % : aucun réseau nécessaire', 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: 45, usable_capacity_kwh: 15, soc_floor_percent: 22, }, expect: { usable: true, gridNeeded: false, requiredSocPct: 27.5, }, }, { node: 'BALANCE', id: 'bilan-nuit-sans-croisement', label: 'Nuit sans production sur tout l\'horizon : le déficit complet est annoncé, pas masqué', snapshot: { nowMs: Date.parse('2026-08-24T22:00:00+02:00'), consumption_p50: P50, consumption_p80: P80, horizon: horizonFrom('2026-08-24T22:00:00+02:00', 6, {}), soc_percent: 60, usable_capacity_kwh: 15, soc_floor_percent: 22, }, expect: { usable: true, crossingFound: false, gridNeeded: false, }, }, { node: 'BALANCE', id: 'bilan-prochaine-fenetre-deficit', label: 'Projection à 15 h : le solaire couvre déjà, mais la nuit qui vient doit être annoncée', snapshot: { nowMs: Date.parse('2026-08-24T15:30:00+02:00'), consumption_p50: P50, consumption_p80: P80, horizon: horizonFrom('2026-08-24T15:30:00+02:00', 24, { ...PROD_24_08, 15: 3.00, 16: 2.80, 17: 2.60, 18: 2.40, 19: 1.20, 20: 0.20, // lendemain matin 7: 1.50, 8: 3.00, 9: 5.00, }), soc_percent: 95, usable_capacity_kwh: 15, soc_floor_percent: 22, }, expect: { usable: true, // fenêtre en cours : le solaire couvre, rien à franchir gridNeeded: false, // mais la nuit qui vient doit être chiffrée nextDeficitFound: true, nextDeficitStartHour: 19, 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: 107.1, storageShortfallKwh: 1.07, }, }, { node: 'BALANCE', id: 'bilan-entrees-manquantes', label: 'Capacité batterie absente : le bilan se déclare inutilisable, les contrôleurs gardent leurs règles', 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', 6, PROD_24_08), soc_percent: 21, }, expect: { usable: false, missing: 'usable_capacity_kwh', }, }, ];