- src/nodes/*.js (6 nodes + template snapshot) = source de vérité unique. - n8n-push.mjs reconstruit le JSON depuis src/ avant syntaxe+golden+PUT (src gagne toujours; n8n-extract.mjs pour resynchroniser si édition UI). - n8n-build.mjs --check pour vérifier la synchro. - Copies historiques déplacées dans archive/ (mortes, dérivées de la prod). - .gitignore: backups locaux de rollback. - AGENTS.md: règle R2 pour toutes les futures sessions (dont Qwen local). - Validé: idempotence, mutation/restauration, pipeline push complet en réel.
242 lines
7.9 KiB
JavaScript
242 lines
7.9 KiB
JavaScript
// =====================================================
|
|
// ACTIVE SESSION BRANCH
|
|
// =====================================================
|
|
else if (cloud.sessionObservedActive) {
|
|
if (cloud.enteringCloudDebt) {
|
|
setCloudDebtStartedAt = true;
|
|
}
|
|
|
|
if (conditions.shouldStopOverloadAtMin) {
|
|
setOff({
|
|
stateName: 'vebus_overload_stop_min_current',
|
|
decisionName: 'stop',
|
|
reasonText: 'Arrêt: surcharge convertisseur persistante au courant mini',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'vebus_overload_persistent',
|
|
blockedUntilStamp: formatParisStampFromMs(
|
|
nowMs + batteryTune.global.overloadStopAfterSeconds * 1000
|
|
),
|
|
});
|
|
}
|
|
|
|
else if (safeguards.gridBatteryProtectionStop) {
|
|
setOff({
|
|
stateName: 'stop_grid_battery_guard',
|
|
decisionName: 'stop',
|
|
reasonText: `Arrêt charge réseau: batterie trop sollicitée (${round1(parsed.batteryCurrentA)}A)`,
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'grid_battery_guard',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 20 * 60000),
|
|
});
|
|
}
|
|
|
|
else if (conditions.probeFailedNow) {
|
|
setOff({
|
|
stateName: 'probe_failed_lock',
|
|
decisionName: 'stop',
|
|
reasonText: 'Mode sonde échoué',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'probe_failed',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + params.probeRetryMinutes * 60000),
|
|
});
|
|
}
|
|
|
|
else if (derived.physicalCapA < params.currentMinA) {
|
|
if (lifecycle.minRunElapsed) {
|
|
setOff({
|
|
stateName: 'stop_power_cap',
|
|
decisionName: 'stop_power_cap',
|
|
reasonText: 'Capacité système insuffisante',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'system_capacity_stop',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 10 * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'hold_min_run_power_cap',
|
|
decisionName: 'hold',
|
|
currentA: params.currentMinA,
|
|
reasonText: "Maintien jusqu'au temps mini ON",
|
|
dynamic: false,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else if (conditions.probeWindowActive && !conditions.probeSuccessNow) {
|
|
if (!conditions.probeHoldGuardOk) {
|
|
setOff({
|
|
stateName: 'probe_stop_guard',
|
|
decisionName: 'stop',
|
|
reasonText: 'Sonde interrompue: garde-fous non respectés',
|
|
dynamic: false,
|
|
mustStopFlag: true,
|
|
blockReason: 'probe_failed',
|
|
blockedUntilStamp: formatParisStampFromMs(nowMs + params.probeRetryMinutes * 60000),
|
|
});
|
|
} else {
|
|
setOn({
|
|
stateName: 'probe_hold',
|
|
decisionName: 'hold',
|
|
currentA: Math.min(params.probeCurrentA, derived.physicalCapA),
|
|
reasonText: `Mode sonde actif à ${round1(Math.min(params.probeCurrentA, derived.physicalCapA))}A`,
|
|
dynamic: true,
|
|
mustStopFlag: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
else {
|
|
const activeSourceMode =
|
|
conditions.probeSuccessNow
|
|
? 'solar_only'
|
|
: context.sourceMode;
|
|
|
|
const holdConditionsOk =
|
|
(activeSourceMode === 'grid_hc_allowed' || activeSourceMode === 'grid_forced')
|
|
? conditions.gridHoldOk
|
|
: (activeSourceMode === 'solar_only' || activeSourceMode === 'solar_potential')
|
|
? conditions.solarHoldOk
|
|
: activeSourceMode === 'hybrid_probe'
|
|
? conditions.probeHoldGuardOk
|
|
: activeSourceMode === 'battery_assist_night'
|
|
? conditions.batteryAssistNightHoldOk
|
|
: false;
|
|
|
|
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,
|
|
});
|
|
}
|