// ===================================================== // START BRANCH // ===================================================== else { const startConditionsOk = (context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_forced') ? conditions.gridStartOk : (context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') ? conditions.solarStartOk : context.sourceMode === 'hybrid_probe' ? conditions.probeStartOk : context.sourceMode === 'battery_assist_night' ? conditions.batteryAssistNightStartOk : false; if (!derived.canRunAtMinCurrent) { setOff({ stateName: 'blocked_by_capacity_min_current', decisionName: 'hold', reasonText: 'Capacité insuffisante pour courant mini', dynamic: false, mustStopFlag: false, blockReason: 'system_capacity_stop', blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000), }); } else if (context.sourceMode === 'blocked') { setOff({ stateName: context.profile === 'outside_window' ? 'blocked_outside_window' : (context.profile === 'night_hc' ? 'night_no_grid_hc_lock' : 'idle_wait_source'), decisionName: 'hold', reasonText: context.profile === 'outside_window' ? 'Hors fenêtre préférée sans source suffisante' : context.profile === 'night_hc' ? 'Nuit sans autorisation HC réseau' : 'Source non autorisée ou insuffisante', dynamic: false, mustStopFlag: false, blockReason: context.profile === 'outside_window' ? 'outside_window' : context.profile === 'night_hc' ? 'grid_hc_unavailable' : 'source_unavailable', blockedUntilStamp: Number.isFinite(locks.nextAllowedWindowStartMs) ? formatParisStampFromMs(locks.nextAllowedWindowStartMs) : formatParisStampFromMs(nowMs + 15 * 60000), }); } else if ( (context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_forced') && !parsed.gridAvailable ) { setOff({ stateName: context.profile === 'night_hc' ? 'night_wait_grid_takeover' : 'wait_grid_source', decisionName: 'hold', reasonText: context.profile === 'night_hc' ? ( nightEvcs?.batteryRecoveryUrgent ? 'Réseau demandé: urgence recharge batterie avant départ' : 'Prise de relais réseau demandée pour 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 if (locks.startBlockedByReason) { setOff({ stateName: 'wait_reason_lock', decisionName: 'hold', reasonText: `Blocage anti-cliquetis: ${history.lastBlockReason}`, dynamic: false, mustStopFlag: false, blockReason: history.lastBlockReason, blockedUntilStamp: history.blockedUntilAt || now.stamp, }); } else if (!startConditionsOk) { setOff({ stateName: 'idle_wait_conditions', decisionName: 'hold', reasonText: context.sourceMode === 'battery_assist_night' ? 'Conditions de démarrage nuit batterie non réunies' : 'Conditions de démarrage non réunies', dynamic: false, mustStopFlag: false, blockReason: 'none', blockedUntilStamp: now.stamp, }); } else if (!lifecycle.minStopElapsed) { setOff({ stateName: 'wait_min_stop', decisionName: 'hold', reasonText: 'Temps mini OFF non écoulé', dynamic: false, mustStopFlag: false, blockReason: 'none', blockedUntilStamp: now.stamp, }); } else { const startCurrentA = context.sourceMode === 'hybrid_probe' ? clamp(params.probeCurrentA, params.currentMinA, derived.physicalCapA) : params.currentMinA; setOn({ stateName: context.sourceMode === 'grid_forced' ? ( nightEvcs?.batteryRecoveryUrgent ? 'night_departure_deadline_grid_forced' : 'start_grid_forced_min_current' ) : context.sourceMode === 'grid_hc_allowed' ? 'start_grid_hc_min_current' : context.sourceMode === 'battery_assist_night' ? 'start_battery_assist_night_min_current' : context.sourceMode === 'hybrid_probe' ? 'probe_start_6a' : context.sourceMode === 'solar_potential' ? 'start_forecast_solar_potential' : modes.batteryFullHarvestMode ? 'start_min_current_battery_full_harvest' : modes.forecastCommitMode ? 'start_min_current_forecast_commit' : 'start_min_current', decisionName: 'start', currentA: startCurrentA, reasonText: context.sourceMode === 'grid_forced' ? ( nightEvcs?.batteryRecoveryUrgent ? 'Démarrage réseau forcé: priorité départ / recharge batterie' : 'Démarrage en charge réseau forcée' ) : context.sourceMode === 'grid_hc_allowed' ? 'Démarrage en fenêtre HC réseau' : context.sourceMode === 'battery_assist_night' ? 'Démarrage nuit en assistance batterie contrôlée' : context.sourceMode === 'hybrid_probe' ? 'Démarrage en mode sonde' : context.sourceMode === 'solar_potential' ? 'Démarrage par potentiel solaire confirmé' : modes.batteryFullHarvestMode ? 'Démarrage harvest solaire' : modes.forecastCommitMode ? 'Démarrage forecast commit' : 'Démarrage prudent à courant mini', dynamic: true, mustStopFlag: false, }); nextBlockReason = 'none'; nextBlockedUntilStamp = now.stamp; setProbeStartedAt = context.sourceMode === 'hybrid_probe'; } } // ===================================================== // CONTEXT REASONS // ===================================================== if (nightEvcs?.nightSessionActive) { reason.push( `Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}% | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%` ); if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) { reason.push( `SOC départ session nuit=${round1(nightEvcs.nightSessionStartSocPct)}% | décharge autorisée=${round1(nightEvcs.maxBatteryDischargePoints)} pts` ); } if (nightEvcs.batteryRecoveryUrgent) { reason.push("Urgence départ: il faut anticiper la reprise réseau"); } } reason.push(`VehicleDemandFilter delivery=${vehicleDemandFilter.deliveryRatioEma}`); reason.push(`VehicleDemandFilter noise=${vehicleDemandFilter.deliveryNoiseEma}`); reason.push(`VehicleDemandFilter score=${vehicleDemandFilter.noDemandScoreEma}`); reason.push(`VehicleDemandFilter noDemand=${vehicleDemandFilter.evcsVehicleNoDemandSemantic ? 'on' : 'off'}`); // ===================================================== // OUTPUT // ===================================================== return [{ json: { ...prev, vehicleDemandFilter, decision: { desiredCharging, desiredPermission, desiredLatch, desiredCurrentA, state, currentDecision, dynamicOk, mustStop, reason, nextBlockReason, nextBlockedUntilStamp, setCloudDebtStartedAt, setProbeStartedAt, setNightSessionStart, clearNightSessionStart, notifyWanted, notifyTitle, notifyMessage, } } }];