Files
EVCS/decoupesurplus/V2/batcloudP2.js

374 lines
11 KiB
JavaScript

// =====================================================
// LEGACY / FORECAST MODES
// =====================================================
const batteryFullHarvestMode =
parsed.forecastModeEnabled &&
context.profile !== 'night_hc' &&
context.profile !== 'outside_window' &&
parsed.soc >= params.batteryFullSoc &&
parsed.todayRemainingKwh >= params.batteryFullForecastMinKwh &&
derived.projectedEndSocAfterCharge >= (derived.endDayGuardSoc - 2);
const forecastCommitMode =
parsed.forecastModeEnabled &&
context.profile !== 'night_hc' &&
context.profile !== 'outside_window' &&
parsed.soc >= params.forecastCommitSocFloor &&
parsed.todayRemainingKwh >= params.forecastCommitMinKwh &&
derived.projectedEndSocAfterCharge >= derived.endDayGuardSoc;
const batteryPriorityMode =
!batteryFullHarvestMode && !forecastCommitMode;
// =====================================================
// PROFILE-BASED THRESHOLDS
// =====================================================
const profileSocStartFloor =
context.profile === 'morning_solar' ? params.morningSocMin :
context.profile === 'midday_hc_or_solar' ? params.middaySocMin :
context.profile === 'evening_high_soc' ? params.eveningSocStartMin :
params.socStartMin;
const profileSocHoldFloor =
context.profile === 'morning_solar' ? Math.max(params.socStopMin, params.morningSocMin - 10) :
context.profile === 'midday_hc_or_solar' ? Math.max(params.socStopMin, params.middaySocMin - 15) :
context.profile === 'evening_high_soc' ? params.eveningSocStopMin :
params.socStopMin;
const profileSpecificDischargeFloorA =
context.profile === 'morning_solar' ? params.morningBatteryMaxDischargeA :
context.profile === 'midday_hc_or_solar' ? params.middayBatteryMaxDischargeA :
context.profile === 'evening_high_soc' ? params.eveningBatteryMaxDischargeA :
params.batteryDischargeSoftA;
const profileBatteryDischargeFloorA =
Math.max(profileSpecificDischargeFloorA, batteryDischargeSoftDynamicA);
// =====================================================
// 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;
// =====================================================
// 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;
// =====================================================
// 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 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,
},
safeguards: {
isGridSource,
commonSafetyStartOk,
commonSafetyHoldOk,
profileSocStartFloor,
profileSocHoldFloor,
profileBatteryDischargeFloorA,
batteryCurrentHardExceeded,
mustStopHard,
gridBatteryProtectionStop,
},
conditions: {
solarStartOk,
solarHoldOk,
gridStartOk,
gridHoldOk,
probeStartOk,
probeHoldGuardOk,
probeWindowActive,
probeElapsedReached,
probeSuccessNow,
probeFailedNow,
solarShouldStepUp,
gridShouldStepUp,
solarShouldStepDownBattery,
solarShouldStepDownCloudDebt,
gridShouldStepDownBattery,
shouldStepDownVebusPower,
shouldStepDownVebusVoltage,
shouldStepDownOverload,
shouldStopOverloadAtMin,
}
}
}];