R2: git = source de vérité — src/nodes/ + build/extract intégrés au push
- 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.
This commit is contained in:
191
archive/decoupesurplus/V2/HASERVICEOUTPUTP1.js
Normal file
191
archive/decoupesurplus/V2/HASERVICEOUTPUTP1.js
Normal file
@@ -0,0 +1,191 @@
|
||||
function round1(v) {
|
||||
return Math.round(v * 10) / 10;
|
||||
}
|
||||
function round2(v) {
|
||||
return Math.round(v * 100) / 100;
|
||||
}
|
||||
function clamp(v, min, max) {
|
||||
return Math.min(Math.max(v, min), max);
|
||||
}
|
||||
function to2(n) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
function formatParisStampFromMs(ms) {
|
||||
const fmt = new Intl.DateTimeFormat('fr-FR', {
|
||||
timeZone: 'Europe/Paris',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
const parts = Object.fromEntries(
|
||||
fmt.formatToParts(new Date(ms))
|
||||
.filter(p => p.type !== 'literal')
|
||||
.map(p => [p.type, p.value])
|
||||
);
|
||||
const hour = Number(parts.hour) === 24 ? 0 : Number(parts.hour);
|
||||
return `${parts.year}-${parts.month}-${parts.day} ${to2(hour)}:${parts.minute}:${parts.second}`;
|
||||
}
|
||||
|
||||
const prev = $input.first().json;
|
||||
const {
|
||||
input,
|
||||
now,
|
||||
nowMs,
|
||||
cfg,
|
||||
parsed,
|
||||
params,
|
||||
history,
|
||||
strategy,
|
||||
windows,
|
||||
context,
|
||||
derived,
|
||||
batteryTune,
|
||||
batteryPolicy,
|
||||
locks,
|
||||
cloud,
|
||||
modes,
|
||||
safeguards,
|
||||
conditions,
|
||||
decision,
|
||||
} = prev;
|
||||
|
||||
// =====================================================
|
||||
// FINAL ACTIONS
|
||||
// =====================================================
|
||||
let desiredCurrentA = decision.desiredCurrentA;
|
||||
|
||||
desiredCurrentA = decision.desiredCharging
|
||||
? clamp(
|
||||
desiredCurrentA,
|
||||
params.currentMinA,
|
||||
Math.max(params.currentMinA, derived.physicalCapA || params.currentMinA)
|
||||
)
|
||||
: params.currentMinA;
|
||||
|
||||
const chargingAction =
|
||||
decision.desiredCharging === parsed.evcsChargingSwitchOn
|
||||
? 'none'
|
||||
: decision.desiredCharging
|
||||
? 'turn_on'
|
||||
: 'turn_off';
|
||||
|
||||
const permissionAction =
|
||||
decision.desiredPermission === parsed.chargePermissionNow
|
||||
? 'none'
|
||||
: decision.desiredPermission
|
||||
? 'turn_on'
|
||||
: 'turn_off';
|
||||
|
||||
const latchAction =
|
||||
decision.desiredLatch === parsed.controlLatch
|
||||
? 'none'
|
||||
: decision.desiredLatch
|
||||
? 'turn_on'
|
||||
: 'turn_off';
|
||||
|
||||
const autoStartAction =
|
||||
parsed.controllerEnabled &&
|
||||
parsed.evcsVehicleConnected &&
|
||||
parsed.evcsAutoStartSwitchOn
|
||||
? 'turn_off'
|
||||
: 'none';
|
||||
|
||||
const currentAction =
|
||||
decision.desiredCharging &&
|
||||
(
|
||||
!Number.isFinite(derived.currentReferenceA) ||
|
||||
Math.abs(desiredCurrentA - derived.currentReferenceA) >= 0.5
|
||||
)
|
||||
? 'set'
|
||||
: 'none';
|
||||
|
||||
const setLastStartAt = chargingAction === 'turn_on';
|
||||
const setLastStopAt = chargingAction === 'turn_off';
|
||||
const setLastCurrentChangeAt = currentAction === 'set';
|
||||
|
||||
// =====================================================
|
||||
// CLEAN BLOCKING LOGIC
|
||||
// =====================================================
|
||||
const blockedUntilToWrite =
|
||||
decision.nextBlockReason === 'none'
|
||||
? now.stamp
|
||||
: decision.nextBlockedUntilStamp;
|
||||
|
||||
const needsBlockReasonUpdate =
|
||||
decision.nextBlockReason !== history.lastBlockReason;
|
||||
|
||||
// =====================================================
|
||||
// GLOBAL EVCS PRIORITY
|
||||
// AUTO + OVERRIDE MANUEL
|
||||
// =====================================================
|
||||
const forceWindowMinutes = Math.max(
|
||||
30,
|
||||
batteryTune?.global?.forceModeAutoRevertMinutes ?? 120
|
||||
);
|
||||
|
||||
const activeChargeIntent =
|
||||
decision.desiredCharging ||
|
||||
['start', 'hold', 'step_up', 'step_down', 'step_down_fast'].includes(decision.currentDecision);
|
||||
|
||||
// -------------------------
|
||||
// PRIORITÉ AUTO
|
||||
// -------------------------
|
||||
let autoEvcsPriorityActive = false;
|
||||
let autoEnergyPriorityMode = 'normal';
|
||||
let autoEnergyPriorityReason = 'Aucune priorité EVCS';
|
||||
let autoEvcsPriorityUntil = now.stamp;
|
||||
|
||||
if (parsed.evcsVehicleConnected && !strategy.isStopMode) {
|
||||
if (strategy.forceChargeRequested || strategy.forceGridRequested) {
|
||||
autoEvcsPriorityActive = true;
|
||||
autoEnergyPriorityMode = 'evcs_priority';
|
||||
autoEnergyPriorityReason =
|
||||
strategy.forceGridRequested
|
||||
? 'EVCS prioritaire : force_grid_now'
|
||||
: 'EVCS prioritaire : force_charge_now';
|
||||
autoEvcsPriorityUntil = formatParisStampFromMs(nowMs + forceWindowMinutes * 60000);
|
||||
|
||||
} else if (context.departurePriorityActive && Number.isFinite(prev.lifecycle.minutesUntilDeparture)) {
|
||||
autoEvcsPriorityActive = true;
|
||||
autoEnergyPriorityMode = 'evcs_priority';
|
||||
autoEnergyPriorityReason = `EVCS prioritaire : départ dans ${Math.round(prev.lifecycle.minutesUntilDeparture)} min`;
|
||||
autoEvcsPriorityUntil = formatParisStampFromMs(
|
||||
nowMs + Math.max(1, prev.lifecycle.minutesUntilDeparture) * 60000
|
||||
);
|
||||
|
||||
} else if (activeChargeIntent && context.profile === 'midday_hc_or_solar') {
|
||||
autoEvcsPriorityActive = true;
|
||||
autoEnergyPriorityMode = 'evcs_priority';
|
||||
autoEnergyPriorityReason = 'EVCS prioritaire : recharge active fenêtre midi';
|
||||
autoEvcsPriorityUntil = formatParisStampFromMs(nowMs + 90 * 60000);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// OVERRIDE MANUEL
|
||||
// -------------------------
|
||||
const manualPriorityOverrideActive =
|
||||
parsed.evcsPriorityManualOverride &&
|
||||
parsed.evcsPriorityManualMode === 'evcs_priority' &&
|
||||
history.evcsPriorityManualUntilMs !== null &&
|
||||
history.evcsPriorityManualUntilMs > nowMs + 1000;
|
||||
|
||||
let evcsPriorityActive = autoEvcsPriorityActive;
|
||||
let energyPriorityMode = autoEnergyPriorityMode;
|
||||
let energyPriorityReason = autoEnergyPriorityReason;
|
||||
let evcsPriorityUntil = autoEvcsPriorityUntil;
|
||||
let evcsPrioritySource = 'auto';
|
||||
|
||||
if (manualPriorityOverrideActive) {
|
||||
evcsPriorityActive = true;
|
||||
energyPriorityMode = 'evcs_priority';
|
||||
energyPriorityReason =
|
||||
parsed.evcsPriorityManualReason || 'EVCS prioritaire : override manuel';
|
||||
evcsPriorityUntil =
|
||||
history.evcsPriorityManualUntilAt || autoEvcsPriorityUntil;
|
||||
evcsPrioritySource = 'manual';
|
||||
}
|
||||
Reference in New Issue
Block a user