JEEP branchee a midi : reseau en HC seulement si le solaire ne suffit pas avant son depart
Regle du proprietaire (28/09). Le bilan compare le besoin saisi (niveau x capacite mesuree - deja delivre) au solaire disponible avant le depart de 15:30 (mar/mer/ ven/sam) sans descendre la batterie sous le plancher « tenir jusqu'a la prochaine HC ». Le controleur relaie le verdict sous les gardes du bilan ; session de midi engagee -> voiture sur le reseau avec la batterie. Bilan 61/61, golden 121/121. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
@@ -301,14 +301,14 @@ const hcNightReachActive = Number.isFinite(hcWindowEndMs) && Number.isFinite(nex
|
||||
// Energie a avoir en reserve a la fin de la fenetre : parcours A REBOURS des
|
||||
// tranches comprises entre les deux instants (au prorata de leur recouvrement).
|
||||
// Un surplus ne rembourse qu'un deficit POSTERIEUR — d'ou le sens du parcours.
|
||||
function hcReachFloorPct(slots, untilMs = nextHcStartMs) {
|
||||
if (!Number.isFinite(hcWindowEndMs) || !Number.isFinite(untilMs) || untilMs <= hcWindowEndMs) return 0;
|
||||
function hcReachFloorPct(slots, untilMs = nextHcStartMs, fromMs = hcWindowEndMs) {
|
||||
if (!Number.isFinite(fromMs) || !Number.isFinite(untilMs) || untilMs <= fromMs) return 0;
|
||||
let needKwh = 0;
|
||||
for (let i = slots.length - 1; i >= 0; i--) {
|
||||
const s = slots[i];
|
||||
const debut = num(s.startMs, NaN);
|
||||
if (!Number.isFinite(debut)) continue;
|
||||
const a = Math.max(debut, hcWindowEndMs);
|
||||
const a = Math.max(debut, fromMs);
|
||||
const b = Math.min(debut + 3600000, untilMs);
|
||||
if (b <= a) continue;
|
||||
needKwh = Math.max(0, needKwh - s.netKwh * (b - a) / 3600000);
|
||||
@@ -348,6 +348,77 @@ const hcFloorPrudentPct = Math.max(hcReachFloorPct(slotsP80, nextHcStartMs), hcF
|
||||
const typical = coverage(slotsP50, hcFloorTypicalPct);
|
||||
const prudent = coverage(slotsP80, hcFloorPrudentPct);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// VOITURE BRANCHEE EN HC AVEC UN DEPART AVANT LA PROCHAINE HC (28/09/2026)
|
||||
//
|
||||
// Regle du proprietaire : « JEEP branchee engage le reseau en HC midi si
|
||||
// l'analyse de production le justifie ». La JEEP repart a 15:30 certains jours
|
||||
// (calendrier dans HA) : ce qu'elle n'a pas pris avant, elle ne le prendra pas
|
||||
// en HC la nuit. Ailleurs, elle reste branchee et la HC de 01:24 la finit au
|
||||
// meme tarif — fermer le contacteur a midi n'y gagnerait rien et etoufferait le
|
||||
// solaire (installation sans injection).
|
||||
//
|
||||
// Ce que le solaire peut donner a la voiture avant son depart SANS toucher a
|
||||
// la reserve de la nuit : l'energie de la batterie au-dessus du plancher
|
||||
// « tenir jusqu'a la prochaine HC » evalue AU DEPART, plus le bilan net prevu
|
||||
// (production - maison) d'ici le depart. Profil p50, comme required_soc.
|
||||
// Le reseau est justifie quand le besoin de la voiture, borne par ce que son
|
||||
// chargeur peut absorber avant le depart, depasse ce solaire.
|
||||
//
|
||||
// Besoin = (100 - niveau saisi) % x capacite MESUREE au compteur de la borne,
|
||||
// moins ce que la borne a deja delivre depuis la saisie. Aucun capteur ne donne
|
||||
// le niveau de la voiture : sans saisie valable, la regle se tait.
|
||||
//
|
||||
// Deux constantes, justifiees :
|
||||
// - 3,7 kW : plateau MESURE des deux vehicules du foyer (3 655 et 3 680 W a
|
||||
// consigne pleine, MANUEL §0) ; au-dela la voiture n'absorbe rien de plus.
|
||||
// - 0,5 kWh : seuil de manque en dessous duquel une session ne se justifie
|
||||
// pas — une fermeture du contacteur coute 50 a 150 Wh de transitoire (§57),
|
||||
// on exige un gain d'au moins trois fois ce cout.
|
||||
const VEHICLE_CHARGER_KW = 3.7;
|
||||
const VEHICLE_GRID_MIN_SHORTFALL_KWH = 0.5;
|
||||
const nowMsBalance = num(input.nowMs, Date.now());
|
||||
const vehicleConnected = input.vehicle_connected === true || input.vehicle_connected === 'true';
|
||||
const vehicleDepartureMs = num(input.vehicle_departure_ms, NaN);
|
||||
const vehicleLevelPct = num(input.vehicle_level_pct, NaN);
|
||||
const vehicleLevelValid = input.vehicle_level_valid === true || input.vehicle_level_valid === 'true';
|
||||
const vehicleCapacityKwh = num(input.vehicle_capacity_kwh, NaN);
|
||||
const vehicleDeliveredKwh = Math.max(0, num(input.vehicle_delivered_since_level_kwh, 0));
|
||||
const vehicleNeedKwh =
|
||||
vehicleLevelValid && Number.isFinite(vehicleLevelPct) && vehicleLevelPct >= 0 && vehicleLevelPct <= 100
|
||||
&& Number.isFinite(vehicleCapacityKwh) && vehicleCapacityKwh > 0
|
||||
? Math.max(0, (100 - vehicleLevelPct) / 100 * vehicleCapacityKwh - vehicleDeliveredKwh)
|
||||
: NaN;
|
||||
const vehicleRuleActive =
|
||||
hcReachActive && vehicleConnected && Number.isFinite(vehicleNeedKwh)
|
||||
&& Number.isFinite(vehicleDepartureMs) && vehicleDepartureMs > nowMsBalance
|
||||
&& vehicleDepartureMs < nextHcStartMs;
|
||||
let vehicleSolarKwh = null;
|
||||
let vehicleReachableKwh = null;
|
||||
let vehicleShortfallKwh = null;
|
||||
let vehicleFloorAtDeparturePct = null;
|
||||
if (vehicleRuleActive) {
|
||||
vehicleFloorAtDeparturePct = hcReachFloorPct(slotsP50, nextHcStartMs, vehicleDepartureMs);
|
||||
let netKwh = 0;
|
||||
for (const sl of slotsP50) {
|
||||
const debut = num(sl.startMs, NaN);
|
||||
if (!Number.isFinite(debut)) continue;
|
||||
const a = Math.max(debut, nowMsBalance);
|
||||
const b = Math.min(debut + 3600000, vehicleDepartureMs);
|
||||
if (b > a) netKwh += sl.netKwh * (b - a) / 3600000;
|
||||
}
|
||||
// Difference SIGNEE : une batterie SOUS son plancher doit d'abord etre
|
||||
// remontee par ce meme surplus avant que la voiture n'en voie un kWh.
|
||||
const plancherDepart = Math.max(socFloorPct, vehicleFloorAtDeparturePct);
|
||||
const batterieAuDessusKwh = socPct >= plancherDepart
|
||||
? energyBetweenSoc(plancherDepart, socPct)
|
||||
: -energyBetweenSoc(socPct, plancherDepart);
|
||||
vehicleSolarKwh = Math.max(0, batterieAuDessusKwh + netKwh);
|
||||
vehicleReachableKwh = Math.min(vehicleNeedKwh, VEHICLE_CHARGER_KW * (vehicleDepartureMs - nowMsBalance) / 3600000);
|
||||
vehicleShortfallKwh = Math.max(0, vehicleReachableKwh - vehicleSolarKwh);
|
||||
}
|
||||
const vehicleGridNeeded = vehicleRuleActive && vehicleShortfallKwh > VEHICLE_GRID_MIN_SHORTFALL_KWH;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// PROCHAINE FENÊTRE DE DÉFICIT (ajout du 24/08/2026)
|
||||
//
|
||||
@@ -537,6 +608,18 @@ return [{
|
||||
surplusAvailableKwh: round2(surplusAfterCrossingKwh),
|
||||
},
|
||||
|
||||
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
||||
vehicleMidday: {
|
||||
active: vehicleRuleActive,
|
||||
needKwh: Number.isFinite(vehicleNeedKwh) ? round2(vehicleNeedKwh) : null,
|
||||
departureMs: Number.isFinite(vehicleDepartureMs) ? vehicleDepartureMs : null,
|
||||
solarKwh: vehicleSolarKwh === null ? null : round2(vehicleSolarKwh),
|
||||
reachableKwh: vehicleReachableKwh === null ? null : round2(vehicleReachableKwh),
|
||||
shortfallKwh: vehicleShortfallKwh === null ? null : round2(vehicleShortfallKwh),
|
||||
floorAtDeparturePct: vehicleFloorAtDeparturePct === null ? null : round1(vehicleFloorAtDeparturePct),
|
||||
gridNeeded: vehicleGridNeeded,
|
||||
},
|
||||
|
||||
// Tenir jusqu'a la prochaine fenetre HC (28/09) : SOC a avoir en fin de
|
||||
// fenetre HC en cours. 0 hors fenetre ou si la production suffit.
|
||||
hcReach: {
|
||||
|
||||
Reference in New Issue
Block a user