5c — BALANCE ne repondait qu'a "combien jusqu'a ce que le solaire prenne le relais". A 15 h la reponse est zero : exacte mais inutile, le deficit qui approche etant celui de la nuit. Ajout de la prochaine traversee deficitaire (heure de debut, energie, SOC a atteindre avant). Ce SOC n'est PAS ecrete a 100 % : au-dela, meme une batterie pleine ne passe pas, et le manque est publie comme storage_shortfall_kwh — c'est le chiffre du dimensionnement. 5b — Workflow "EVCS Energy Profiles Daily V1" (N6UaB4ZozsbRPrFb), actif, 03:30 : lit /api/history/period sur 30 jours glissants et republie les quatre CSV (conso p50 et p80, enveloppe de production, taux de censure). CONSUMPTION_PROFILE devient DAILY_PROFILES et sait desormais decoder la reponse brute de l'API en plus des series deja pretes. Piege trouve : sans end_time, /api/history/period ne renvoie qu'UNE journee, silencieusement. Mes premieres mesures "sur 3 jours" n'en couvraient qu'une. Validation croisee : profils calcules depuis l'API REST (etats bruts, 11 j) contre profils calcules depuis les statistiques long terme (28 j) — 0,20 kWh/h d'ecart absolu moyen, par deux chemins de donnees independants. BUILD_HA_CALLS_PROFILES refuse d'ecraser les profils en place si l'historique ne couvre pas les 24 heures. Meme garantie de perimetre que le bilan. Suite a 27 scenarios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
116 lines
4.0 KiB
JavaScript
116 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
// Génère le squelette du workflow "EVCS Energy Profiles Daily V1" : recalcul
|
|
// quotidien des profils horaires (consommation, enveloppe de production, taux
|
|
// de censure) à partir de l'historique Home Assistant.
|
|
// Le CODE des nodes est injecté depuis src/energy-balance/ par n8n-build.mjs.
|
|
|
|
import fs from 'node:fs';
|
|
|
|
const HA_CRED = { id: 'uv3sknYYigxBkaVN', name: 'Bearer Auth account 2' };
|
|
const HA_URL = 'http://10.10.10.17:8123';
|
|
|
|
const ENTITIES = [
|
|
'sensor.airtria_mpii_v1_energy_from_inverter_to_ac_out',
|
|
'sensor.airtria_mpii_v1_energy_from_ac_in_1_to_ac_out',
|
|
'sensor.evcs_hq2239dnt7p_total_energy',
|
|
'sensor.gx_device_pv_energy',
|
|
'sensor.victron_battery_soc',
|
|
].join(',');
|
|
|
|
// Fenêtre demandée : 30 jours. L'API ne renvoie que ce que la rétention
|
|
// conserve (10 jours au 24/08, 90 une fois la nouvelle rétention accumulée),
|
|
// donc la fenêtre s'élargira d'elle-même sans rien changer ici.
|
|
// ⚠️ `end_time` est OBLIGATOIRE : sans lui, /api/history/period ne renvoie
|
|
// qu'UNE SEULE journée, silencieusement.
|
|
const HISTORY_URL =
|
|
"={{ '" + HA_URL + "/api/history/period/' " +
|
|
'+ new Date(Date.now() - 30 * 86400000).toISOString() ' +
|
|
"+ '?end_time=' + encodeURIComponent(new Date().toISOString()) " +
|
|
"+ '&filter_entity_id=" + ENTITIES + "' " +
|
|
"+ '&minimal_response&significant_changes_only=0' }}";
|
|
|
|
const nodes = [
|
|
{
|
|
name: 'Schedule Daily',
|
|
type: 'n8n-nodes-base.scheduleTrigger',
|
|
typeVersion: 1.2,
|
|
position: [0, 0],
|
|
parameters: { rule: { interval: [{ field: 'days', triggerAtHour: 3, triggerAtMinute: 30 }] } },
|
|
},
|
|
{
|
|
name: 'HA History',
|
|
type: 'n8n-nodes-base.httpRequest',
|
|
typeVersion: 4.2,
|
|
position: [220, 0],
|
|
parameters: {
|
|
method: 'GET',
|
|
url: HISTORY_URL,
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpBearerAuth',
|
|
options: {
|
|
timeout: 120000,
|
|
response: { response: { responseFormat: 'text' } },
|
|
},
|
|
},
|
|
credentials: { httpBearerAuth: HA_CRED },
|
|
},
|
|
{
|
|
name: 'DAILY_PROFILES',
|
|
type: 'n8n-nodes-base.code',
|
|
typeVersion: 2,
|
|
position: [440, 0],
|
|
parameters: { jsCode: '// injecté depuis src/energy-balance/DAILY_PROFILES.js' },
|
|
},
|
|
{
|
|
name: 'BUILD_HA_CALLS_PROFILES',
|
|
type: 'n8n-nodes-base.code',
|
|
typeVersion: 2,
|
|
position: [660, 0],
|
|
parameters: { jsCode: '// injecté depuis src/energy-balance/BUILD_HA_CALLS_PROFILES.js' },
|
|
},
|
|
{
|
|
name: 'Execute HA Service Profiles',
|
|
type: 'n8n-nodes-base.httpRequest',
|
|
typeVersion: 4.2,
|
|
position: [880, 0],
|
|
parameters: {
|
|
method: 'POST',
|
|
url: `={{ '${HA_URL}/api/services/' + $json.domain + '/' + $json.service }}`,
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpBearerAuth',
|
|
sendHeaders: true,
|
|
headerParameters: {
|
|
parameters: [
|
|
{ name: 'Content-Type', value: 'application/json' },
|
|
{ name: 'accept', value: 'application/json' },
|
|
],
|
|
},
|
|
sendBody: true,
|
|
specifyBody: 'json',
|
|
jsonBody: '={{ $json.data }}',
|
|
options: { timeout: 15000 },
|
|
},
|
|
credentials: { httpBearerAuth: HA_CRED },
|
|
onError: 'continueRegularOutput',
|
|
},
|
|
];
|
|
|
|
const connections = {
|
|
'Schedule Daily': { main: [[{ node: 'HA History', type: 'main', index: 0 }]] },
|
|
'HA History': { main: [[{ node: 'DAILY_PROFILES', type: 'main', index: 0 }]] },
|
|
DAILY_PROFILES: { main: [[{ node: 'BUILD_HA_CALLS_PROFILES', type: 'main', index: 0 }]] },
|
|
BUILD_HA_CALLS_PROFILES: { main: [[{ node: 'Execute HA Service Profiles', type: 'main', index: 0 }]] },
|
|
};
|
|
|
|
const workflow = {
|
|
id: 'N6UaB4ZozsbRPrFb',
|
|
name: 'EVCS Energy Profiles Daily V1',
|
|
nodes,
|
|
connections,
|
|
settings: { executionOrder: 'v1', callerPolicy: 'workflowsFromSameOwner' },
|
|
staticData: null,
|
|
};
|
|
|
|
fs.writeFileSync('workflows/evcs-energy-profiles-daily-v1.json', JSON.stringify(workflow, null, 2) + '\n');
|
|
console.log(`Squelette écrit : workflows/evcs-energy-profiles-daily-v1.json (${nodes.length} nodes)`);
|