// ===================================================== // GRID RELAX IN force_charge_now // ===================================================== // Assouplissement uniquement pour la garde réseau, // sans toucher aux sécurités dures globales. const gridForceChargeRelaxSoftA = strategy.forceChargeRequested && isGridSource ? ( parsed.soc >= 90 ? 28 : parsed.soc >= 80 ? 16 : parsed.soc >= 60 ? 8 : parsed.soc >= 40 ? 4 : 0 ) : 0; const gridForceChargeRelaxHardA = strategy.forceChargeRequested && isGridSource ? ( parsed.soc >= 90 ? 39 : parsed.soc >= 80 ? 29 : parsed.soc >= 60 ? 18 : parsed.soc >= 40 ? 8 : 0 ) : 0; const gridBatterySoftDynamicABase = Math.max(params.gridBatterySoftA, batteryDischargeSoftDynamicA); const gridBatteryHardDynamicABase = Math.max(params.gridBatteryHardA, batteryDischargeHardDynamicA); let gridBatterySoftDynamicA = gridBatterySoftDynamicABase - gridForceChargeRelaxSoftA; let gridBatteryHardDynamicA = gridBatteryHardDynamicABase - gridForceChargeRelaxHardA; // Garantit que le hard reste plus permissif que le soft d'au moins 3 A if (gridBatteryHardDynamicA > (gridBatterySoftDynamicA - 3)) { gridBatteryHardDynamicA = gridBatterySoftDynamicA - 3; } // ===================================================== // BASE SAFETY // ===================================================== const commonSafetyStartOk = derived.canRunAtMinCurrent && !derived.anyVebusOverload && !derived.anyVebusLowBattery && !derived.vebusDcVoltageSoftLow && !derived.vebusAcOutSoftReached && parsed.evcsDeviceConnected && parsed.evcsVehicleConnected && !parsed.evcsModeIsAuto; const commonSafetyHoldOk = derived.canRunAtMinCurrent && !derived.anyVebusLowBattery && !derived.vebusDcVoltageHardLow && parsed.evcsDeviceConnected && parsed.evcsVehicleConnected && !parsed.evcsModeIsAuto; // ===================================================== // START / HOLD CONDITIONS // ===================================================== const solarStartOk = (context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') && commonSafetyStartOk && parsed.soc >= profileSocStartFloor && parsed.batteryCurrentA >= profileBatteryDischargeFloorA && ( context.sourceMode === 'solar_potential' || parsed.pvPowerW >= context.effectivePvStartMinW ); const solarHoldOk = (context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') && commonSafetyHoldOk && parsed.soc >= profileSocHoldFloor && parsed.batteryCurrentA >= profileBatteryDischargeFloorA && ( parsed.pvPowerW >= params.pvHoldMinW || cloudToleranceOk ); const gridStartOk = isGridSource && commonSafetyStartOk && parsed.gridAvailable && parsed.batteryCurrentA >= gridBatterySoftDynamicA; const gridHoldOk = isGridSource && commonSafetyHoldOk && parsed.gridAvailable && parsed.batteryCurrentA >= gridBatteryHardDynamicA; const batteryAssistNightStartOk = context.sourceMode === 'battery_assist_night' && commonSafetyStartOk && nightSessionActive && batteryAssistAllowed && parsed.soc > batteryHardFloorPct && parsed.soc > gridTakeoverTriggerPct && parsed.batteryCurrentA >= profileBatteryDischargeFloorA; const batteryAssistNightHoldOk = context.sourceMode === 'battery_assist_night' && commonSafetyHoldOk && nightSessionActive && batteryAssistAllowed && parsed.soc > batteryHardFloorPct && parsed.soc > gridTakeoverTriggerPct && parsed.batteryCurrentA >= batteryDischargeHardDynamicA; // ===================================================== // PROBE LOGIC — STRICTEMENT LIMITÉE AU MODE PROBE // ===================================================== const probeModeActive = context.sourceMode === 'hybrid_probe'; const probeStartOk = probeModeActive && commonSafetyStartOk && (context.morningProbeEligible || context.middayProbeEligible); const probeHoldGuardOk = probeModeActive && commonSafetyHoldOk && parsed.batteryCurrentA >= profileBatteryDischargeFloorA; const probeWindowActive = probeModeActive && sessionObservedActive && history.probeStartedMs !== null && lifecycle.minutesSinceProbeStart !== null && lifecycle.minutesSinceProbeStart < params.probeMinutes; const probeElapsedReached = probeModeActive && sessionObservedActive && history.probeStartedMs !== null && lifecycle.minutesSinceProbeStart !== null && lifecycle.minutesSinceProbeStart >= params.probeMinutes; const probeSuccessNow = probeModeActive && sessionObservedActive && history.probeStartedMs !== null && ( parsed.pvPowerW >= params.pvHoldMinW && parsed.batteryCurrentA >= Math.min(0, batteryDischargeSoftDynamicA) ); const probeFailedNow = probeModeActive && probeElapsedReached && !probeSuccessNow; // ===================================================== // STEP-UP LOGIC // ===================================================== const stepUpByBatteryReserve = batteryPriorityMode && parsed.batteryCurrentA >= batteryChargeReserveDynamicA; const stepUpByForecastMode = !batteryPriorityMode && ( parsed.pvPowerW >= params.pvHoldMinW || parsed.pvPowerAvg10mW >= params.pvHoldMinW ) && derived.forecastBufferKwh >= -1 && derived.projectedEndSocAfterCharge >= (derived.endDayGuardSoc - 5) && cloudToleranceOk; const solarShouldStepUp = lifecycle.currentCooldownElapsed && derived.currentReferenceA < derived.physicalCapA && derived.physicalCapA >= params.currentMinA && !derived.vebusAcOutSoftReached && !derived.vebusDcVoltageSoftLow && !derived.houseLoadHighVsHeadroom && ( stepUpByBatteryReserve || stepUpByForecastMode || parsed.pvPowerW >= (context.effectivePvStartMinW + 400) ); const gridShouldStepUp = lifecycle.currentCooldownElapsed && derived.currentReferenceA < derived.physicalCapA && derived.physicalCapA >= params.currentMinA && !derived.vebusAcOutSoftReached && !derived.vebusDcVoltageSoftLow && parsed.batteryCurrentA >= gridBatterySoftDynamicA && !derived.houseLoadHighVsHeadroom; const batteryAssistNightShouldStepUp = context.sourceMode === 'battery_assist_night' && lifecycle.currentCooldownElapsed && derived.currentReferenceA < derived.physicalCapA && derived.physicalCapA >= params.currentMinA && !derived.vebusAcOutSoftReached && !derived.vebusDcVoltageSoftLow && !derived.houseLoadHighVsHeadroom && parsed.batteryCurrentA >= batteryChargeReserveDynamicA && batteryAssistAllowed; // ===================================================== // STEP-DOWN LOGIC // ===================================================== const solarShouldStepDownBattery = parsed.batteryCurrentA <= batteryDischargeSoftDynamicA && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const solarShouldStepDownCloudDebt = !cloudToleranceOk && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const gridShouldStepDownBattery = parsed.batteryCurrentA < gridBatterySoftDynamicA && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const batteryAssistNightShouldStepDown = context.sourceMode === 'battery_assist_night' && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA && ( parsed.batteryCurrentA <= batteryDischargeSoftDynamicA || parsed.soc <= gridTakeoverTriggerPct || batteryRecoveryUrgent ); const shouldStepDownVebusPower = derived.vebusAcOutSoftReached && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const shouldStepDownVebusVoltage = derived.vebusDcVoltageSoftLow && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const shouldStepDownOverload = derived.anyVebusOverload && lifecycle.currentCooldownElapsed && derived.currentReferenceA > params.currentMinA; const shouldStopOverloadAtMin = derived.anyVebusOverload && derived.currentReferenceA <= params.currentMinA && sessionObservedActive; // ===================================================== // HARD STOPS / PROTECTIONS // ===================================================== const batteryCurrentHardExceeded = parsed.batteryCurrentA <= batteryDischargeHardDynamicA; // overload volontairement exclu du hard stop immédiat const mustStopHard = derived.anyVebusLowBattery || derived.vebusDcVoltageHardLow || derived.vebusAcOutHardReached || batteryCurrentHardExceeded; const gridBatteryProtectionStop = sessionObservedActive && isGridSource && parsed.batteryCurrentA < gridBatteryHardDynamicA; // ===================================================== // OUTPUT // ===================================================== return [{ json: { ...prev, batteryPolicy: { socBand, batteryDischargeSoftDynamicA, batteryDischargeHardDynamicA, batteryChargeReserveDynamicA, batterySoftToleranceMinutes, batteryHardToleranceSeconds, batteryRecoveryMinutes, gridBatterySoftDynamicA, gridBatteryHardDynamicA, gridForceChargeRelaxSoftA, gridForceChargeRelaxHardA, houseSpikeDetected, solarPotentialContext, }, locks: { nextAllowedWindowStartMs, blockActive, blockStillRelevant, startBlockedByReason, }, cloud: { sessionObservedActive, batteryNetPowerW, cloudDebtNextKwh, cloudDebtMeaningful, cloudEventDischargingNow, continuingCloudEvent, enteringCloudDebt, cloudDebtActive, cloudDebtStartedMs, cloudDebtDurationMinutes, effectiveCloudToleranceMinutes, cloudToleranceOk, }, modes: { batteryFullHarvestMode, forecastCommitMode, batteryPriorityMode, }, nightEvcs: { nightSessionActive, nightSessionNeedsCapture, nightSessionNeedsClear, nightSessionStartSocPct, nightSessionStartAt, nightSessionStartMs, batteryHardFloorPct, batteryMorningTargetPct, maxBatteryDischargePoints, gridTakeoverTriggerPct, batteryBelowHardFloor, batteryBelowMorningTarget, batteryAtOrBelowGridTakeoverTrigger, batteryKwhMissingToTarget, batteryRecoveryMinutesNeeded, batteryRecoveryUrgent, batteryRecoveryAllowedNow, batteryTargetReached, batteryAssistAllowed, nightGridTakeoverNeeded, nightPreferredSourceMode, }, safeguards: { isGridSource, commonSafetyStartOk, commonSafetyHoldOk, profileSocStartFloor, profileSocHoldFloor, profileBatteryDischargeFloorA, batteryCurrentHardExceeded, mustStopHard, gridBatteryProtectionStop, }, conditions: { solarStartOk, solarHoldOk, gridStartOk, gridHoldOk, batteryAssistNightStartOk, batteryAssistNightHoldOk, probeStartOk, probeHoldGuardOk, probeWindowActive, probeElapsedReached, probeSuccessNow, probeFailedNow, solarShouldStepUp, gridShouldStepUp, batteryAssistNightShouldStepUp, solarShouldStepDownBattery, solarShouldStepDownCloudDebt, gridShouldStepDownBattery, batteryAssistNightShouldStepDown, shouldStepDownVebusPower, shouldStepDownVebusVoltage, shouldStepDownOverload, shouldStopOverloadAtMin, } } }];