Files
EVCS/DECISION/decision_engineP3.js

365 lines
12 KiB
JavaScript

// =================================================
// VEHICLE FULL / PAUSED - NUMERIC FILTER DECISION
// IMPORTANT:
// - uniquement en session active
// - uniquement si la source est réellement livrable
// - sans utiliser le PV comme critère
// =================================================
if (
vehicleDemandFilter.evcsVehicleNoDemandSemantic &&
vehicleDemandFilter.sourceDeliverableForNoDemand
) {
setOff({
stateName: 'vehicle_full_or_paused',
decisionName: 'stop_vehicle_full_or_paused',
reasonText: 'Véhicule branché mais ne demande plus réellement de puissance',
dynamic: false,
mustStopFlag: false,
blockReason: 'none',
blockedUntilStamp: now.stamp,
});
reason.push(`DeliveryRatioEma=${vehicleDemandFilter.deliveryRatioEma}`);
reason.push(`NoiseEma=${vehicleDemandFilter.deliveryNoiseEma}`);
reason.push(`NoDemandScore=${vehicleDemandFilter.noDemandScoreEma}`);
reason.push(`ScoreOn=${vehicleDemandFilter.noDemandScoreOn}`);
reason.push(`ScoreOff=${vehicleDemandFilter.noDemandScoreOff}`);
if (vehicleDemandFilter.evcsNoDemandSinceMinutes !== null) {
reason.push(`NoDemand depuis ${round1(vehicleDemandFilter.evcsNoDemandSinceMinutes)} min`);
}
}
else if (activeSourceMode === 'blocked') {
if (lifecycle.minRunElapsed) {
setOff({
stateName: 'stop_source_lost',
decisionName: 'stop',
reasonText: 'Source autorisée perdue',
dynamic: false,
mustStopFlag: true,
blockReason: context.profile === 'outside_window' ? 'outside_window' : 'source_unavailable',
blockedUntilStamp:
context.profile === 'outside_window' && Number.isFinite(locks.nextAllowedWindowStartMs)
? formatParisStampFromMs(locks.nextAllowedWindowStartMs)
: formatParisStampFromMs(nowMs + params.minStopMinutes * 60000),
});
} else {
setOn({
stateName: 'hold_min_run_source_lost',
decisionName: 'hold',
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
reasonText: "Source perdue mais maintien jusqu'au mini ON",
dynamic: false,
mustStopFlag: false,
});
}
}
else if (
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
!parsed.gridAvailable
) {
if (lifecycle.minRunElapsed) {
setOff({
stateName:
context.profile === 'night_hc'
? 'night_wait_grid_takeover_runtime'
: 'wait_grid_source_runtime',
decisionName: 'hold',
reasonText:
context.profile === 'night_hc'
? 'Relais réseau demandé pendant la session nuit EVCS'
: 'Source réseau demandée mais pas encore disponible',
dynamic: false,
mustStopFlag: false,
blockReason:
context.profile === 'night_hc' ? 'grid_hc_unavailable' : 'grid_unavailable',
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
});
} else {
setOn({
stateName: 'hold_min_run_wait_grid_takeover',
decisionName: 'hold',
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
reasonText: "Maintien jusqu'au mini ON en attente du réseau",
dynamic: false,
mustStopFlag: false,
});
}
}
else if (!holdConditionsOk) {
if (lifecycle.minRunElapsed) {
setOff({
stateName: 'stop_conditions_lost',
decisionName: 'stop',
reasonText: 'Conditions de maintien perdues',
dynamic: false,
mustStopFlag: true,
blockReason: 'hold_conditions_lost',
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
});
} else {
setOn({
stateName: 'hold_min_run',
decisionName: 'hold',
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
reasonText: "Maintien jusqu'au temps mini ON",
dynamic: true,
mustStopFlag: false,
});
}
}
else if ((derived.currentReferenceA > derived.physicalCapA) && lifecycle.currentCooldownElapsed) {
setOn({
stateName: 'charging_step_down_system_cap',
decisionName: 'step_down',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - Math.max(params.currentStepDownA, 2),
derived.physicalCapA
)
),
reasonText: 'Réduction pour respecter la capacité système',
dynamic: true,
mustStopFlag: false,
});
}
else if (conditions.shouldStepDownOverload) {
setOn({
stateName: 'charging_step_down_overload',
decisionName: 'step_down_fast',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - Math.max(batteryTune.global.overloadStepDownA, params.currentStepDownA),
derived.physicalCapA
)
),
reasonText: 'Baisse rapide: surcharge convertisseur',
dynamic: true,
mustStopFlag: false,
});
}
else if (conditions.shouldStepDownVebusPower) {
setOn({
stateName: 'charging_step_down_vebus_power',
decisionName: 'step_down_fast',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - Math.max(params.currentStepDownA + 1, 3),
derived.physicalCapA
)
),
reasonText: 'Baisse rapide: puissance VE.Bus élevée',
dynamic: true,
mustStopFlag: false,
});
}
else if (conditions.shouldStepDownVebusVoltage) {
setOn({
stateName: 'charging_step_down_dc_voltage',
decisionName: 'step_down_fast',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - Math.max(params.currentStepDownA + 1, 3),
derived.physicalCapA
)
),
reasonText: 'Baisse rapide: tension DC basse',
dynamic: true,
mustStopFlag: false,
});
}
else if (
activeSourceMode === 'battery_assist_night' &&
conditions.batteryAssistNightShouldStepDown
) {
setOn({
stateName: 'charging_step_down_battery_assist_night',
decisionName: 'step_down',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - params.currentStepDownA,
derived.physicalCapA
)
),
reasonText:
nightEvcs?.batteryRecoveryUrgent
? 'Réduction: approche départ, préparation takeover réseau'
: 'Réduction: assistance batterie nuit devient trop coûteuse',
dynamic: true,
mustStopFlag: false,
});
}
else if (
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
conditions.gridShouldStepDownBattery
) {
setOn({
stateName:
activeSourceMode === 'grid_forced'
? 'charging_step_down_grid_forced_guard'
: 'charging_step_down_grid_guard',
decisionName: 'step_down',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - params.currentStepDownA,
derived.physicalCapA
)
),
reasonText:
activeSourceMode === 'grid_forced'
? 'Réduction réseau forcé: la batterie commence à participer'
: 'Réduction réseau: la batterie commence à participer',
dynamic: true,
mustStopFlag: false,
});
}
else if (
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
conditions.solarShouldStepDownCloudDebt
) {
setOn({
stateName: 'charging_step_down_cloud_debt',
decisionName: 'step_down',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - Math.max(params.currentStepDownA, 2),
derived.physicalCapA
)
),
reasonText: 'Réduction: dette nuage trop élevée',
dynamic: true,
mustStopFlag: false,
});
}
else if (
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
conditions.solarShouldStepDownBattery
) {
setOn({
stateName: 'charging_step_down_battery',
decisionName: 'step_down',
currentA: Math.max(
params.currentMinA,
Math.min(
derived.currentReferenceA - params.currentStepDownA,
derived.physicalCapA
)
),
reasonText: 'Réduction: batterie trop sollicitée',
dynamic: true,
mustStopFlag: false,
});
}
else if (
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') &&
conditions.gridShouldStepUp
) {
setOn({
stateName:
activeSourceMode === 'grid_forced'
? (
nightEvcs?.batteryRecoveryUrgent
? 'night_departure_deadline_grid_forced'
: 'charging_step_up_grid_forced'
)
: 'charging_step_up_grid_hc',
decisionName: 'step_up',
currentA: clamp(
derived.currentReferenceA + params.currentStepUpA,
params.currentMinA,
derived.physicalCapA
),
reasonText:
activeSourceMode === 'grid_forced'
? (
nightEvcs?.batteryRecoveryUrgent
? 'Hausse prudente: priorité départ, réseau forcé'
: 'Hausse prudente du courant en charge réseau forcée'
)
: 'Hausse prudente du courant en HC réseau',
dynamic: true,
mustStopFlag: false,
});
}
else if (
activeSourceMode === 'battery_assist_night' &&
conditions.batteryAssistNightShouldStepUp
) {
setOn({
stateName: 'charging_step_up_battery_assist_night',
decisionName: 'step_up',
currentA: clamp(
derived.currentReferenceA + params.currentStepUpA,
params.currentMinA,
derived.physicalCapA
),
reasonText: 'Hausse prudente du courant en assistance batterie nuit',
dynamic: true,
mustStopFlag: false,
});
}
else if (
(activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential') &&
conditions.solarShouldStepUp
) {
setOn({
stateName:
conditions.probeSuccessNow
? 'probe_success_step_up'
: (modes.batteryPriorityMode
? 'charging_step_up_battery'
: 'charging_step_up_forecast'),
decisionName: 'step_up',
currentA: clamp(
derived.currentReferenceA + params.currentStepUpA,
params.currentMinA,
derived.physicalCapA
),
reasonText: 'Hausse prudente du courant',
dynamic: true,
mustStopFlag: false,
});
}
else {
setOn({
stateName:
conditions.probeSuccessNow ? 'probe_success_hold' :
activeSourceMode === 'battery_assist_night' ? 'charging_hold_battery_assist_night' :
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced') ? 'charging_hold_grid' :
cloud.cloudDebtActive ? 'charging_hold_cloud_tolerance' :
'charging_hold',
decisionName: 'hold',
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
reasonText:
activeSourceMode === 'battery_assist_night'
? 'Maintien du courant en assistance batterie nuit'
: 'Maintien du courant',
dynamic: true,
mustStopFlag: false,
});
}
}
}