Bilan : PREPARE_BALANCE ne transmettait ni les heures HC ni le plancher HC manuel
La regle « tenir jusqu'a la prochaine HC » (8de284c) et le plancher de charge HC manuelle (2654592, 18/09) etaient muets en production : la colle recopie le snapshot champ par champ et les avait oublies. Les tests appelaient BALANCE directement. Garde-fou : tout champ lu par BALANCE doit etre produit par PREPARE_BALANCE (mutation verifiee). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
@@ -54,6 +54,16 @@ return [{
|
|||||||
storm_alert_level: snapshot.storm_alert_level,
|
storm_alert_level: snapshot.storm_alert_level,
|
||||||
storm_alert_detail: snapshot.storm_alert_detail,
|
storm_alert_detail: snapshot.storm_alert_detail,
|
||||||
|
|
||||||
|
// Fenetres HC (28/09, « tenir jusqu'a la prochaine HC ») : horodatages
|
||||||
|
// calcules par le gabarit depuis les helpers victron_evcs_hc_*. Sans cette
|
||||||
|
// recopie, BALANCE.js ne les voit pas et la regle reste muette.
|
||||||
|
// Plancher de charge HC manuelle (18/09) : oublie ici jusqu'au 28/09, le
|
||||||
|
// bouton n'avait donc jamais atteint le bilan.
|
||||||
|
manual_hc_floor_percent: snapshot.manual_hc_floor_percent,
|
||||||
|
hc_window_end_ms: snapshot.hc_window_end_ms,
|
||||||
|
next_hc_start_ms: snapshot.next_hc_start_ms,
|
||||||
|
next_night_hc_start_ms: snapshot.next_night_hc_start_ms,
|
||||||
|
|
||||||
// Repris pour l'écriture des helpers, sans influencer le calcul.
|
// Repris pour l'écriture des helpers, sans influencer le calcul.
|
||||||
resourceUsable: resource.usable,
|
resourceUsable: resource.usable,
|
||||||
shapeConfidence: resource.source ? resource.source.shapeConfidence : null,
|
shapeConfidence: resource.source ? resource.source.shapeConfidence : null,
|
||||||
|
|||||||
@@ -178,6 +178,24 @@ for (const sc of all) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Garde-fou (28/09) : tout champ que BALANCE.js lit sur son entree doit etre
|
||||||
|
// produit par PREPARE_BALANCE.js. Les scenarios appellent BALANCE directement :
|
||||||
|
// un champ oublie dans la colle passait tous les tests et rendait une regle
|
||||||
|
// muette en production (heures HC, nuit du 28/09).
|
||||||
|
if (!PRINT && (!ONLY || 'prepare-balance-transmet-tout'.includes(ONLY))) {
|
||||||
|
run++;
|
||||||
|
const src = fs.readFileSync(path.join(ROOT, 'src/energy-balance/BALANCE.js'), 'utf8');
|
||||||
|
const lus = new Set([...src.matchAll(/(?<![$\w])input\.([A-Za-z_0-9]+)/g)].map(m => m[1]));
|
||||||
|
const prep = fs.readFileSync(path.join(ROOT, 'src/energy-balance/PREPARE_BALANCE.js'), 'utf8');
|
||||||
|
const manquants = [...lus].filter(k => !new RegExp(`^\\s*${k}\\s*:`, 'm').test(prep));
|
||||||
|
if (manquants.length) {
|
||||||
|
failed++;
|
||||||
|
console.log(`❌ prepare-balance-transmet-tout | champs lus par BALANCE absents de PREPARE_BALANCE : ${manquants.join(', ')}`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ prepare-balance-transmet-tout (${lus.size} champs)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PRINT) process.exit(0);
|
if (PRINT) process.exit(0);
|
||||||
console.log('');
|
console.log('');
|
||||||
if (failed) {
|
if (failed) {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
120
|
120
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// EVCS Energy Balance — PREPARE_BALANCE\n// =====================================================================\n// Colle de câblage n8n, et rien d'autre. Les nœuds de calcul (CENSORSHIP,\n// RESOURCE_ESTIMATE, BALANCE) sont volontairement écrits pour ne lire que\n// `$input`, afin de rester testables hors de n8n. Ce nœud rassemble donc ce\n// que BALANCE attend, en puisant dans les nœuds précédents.\n//\n// Il fait aussi la seule conversion nécessaire : les profils horaires vivent\n// dans des helpers `input_text` sous forme de CSV compact (24 valeurs, moins\n// de 255 caractères), il faut les redonner sous forme de tableaux.\n// =====================================================================\n\nfunction parseCsv24(raw) {\n if (typeof raw !== 'string') return null;\n const parts = raw.split(',').map(v => Number.parseFloat(v.trim()));\n if (parts.length !== 24 || parts.some(v => !Number.isFinite(v))) return null;\n return parts;\n}\n\n// Le nœud HTTP de n8n renvoie le corps du template dans `data`, sous forme de\n// chaîne. Hors n8n (tests), l'objet arrive déjà décodé — on accepte les deux.\nfunction unwrapSnapshot(raw) {\n if (raw && typeof raw.data === 'string') {\n try { return JSON.parse(raw.data); } catch { return raw; }\n }\n return (raw && raw.data) || raw;\n}\n\nconst snapshot = unwrapSnapshot($('HA Snapshot Balance').first().json);\nconst resource = $input.first().json;\n\nreturn [{\n json: {\n nowMs: Number.parseFloat(snapshot.nowMs),\n\n consumption_p50: parseCsv24(snapshot.consumption_p50_csv),\n consumption_p80: parseCsv24(snapshot.consumption_p80_csv),\n\n horizon: resource.horizon || [],\n\n soc_percent: snapshot.soc_percent,\n usable_capacity_kwh: snapshot.usable_capacity_kwh,\n modules_en_service: snapshot.modules_en_service,\n // Plancher dur : même valeur de repli que INPUT_PARSE.js\n // (evcs_night_battery_hard_floor_pct n'est aujourd'hui exposé par AUCUN\n // helper — la valeur « 22 % » documentée partout est en réalité un défaut\n // de code, cf. backlog du chantier).\n soc_floor_percent: 22,\n\n // Vigilance Météo-France, lue sans connaître la commune (l'entité\n // s'appelle sensor.<commune>_weather_alert). Absente tant que\n // l'intégration n'est pas installée : le bilan publie alors\n // 'indisponible' et ne change rien.\n storm_alert_level: snapshot.storm_alert_level,\n storm_alert_detail: snapshot.storm_alert_detail,\n\n // Repris pour l'écriture des helpers, sans influencer le calcul.\n resourceUsable: resource.usable,\n shapeConfidence: resource.source ? resource.source.shapeConfidence : null,\n },\n}];\n"
|
"jsCode": "// EVCS Energy Balance — PREPARE_BALANCE\n// =====================================================================\n// Colle de câblage n8n, et rien d'autre. Les nœuds de calcul (CENSORSHIP,\n// RESOURCE_ESTIMATE, BALANCE) sont volontairement écrits pour ne lire que\n// `$input`, afin de rester testables hors de n8n. Ce nœud rassemble donc ce\n// que BALANCE attend, en puisant dans les nœuds précédents.\n//\n// Il fait aussi la seule conversion nécessaire : les profils horaires vivent\n// dans des helpers `input_text` sous forme de CSV compact (24 valeurs, moins\n// de 255 caractères), il faut les redonner sous forme de tableaux.\n// =====================================================================\n\nfunction parseCsv24(raw) {\n if (typeof raw !== 'string') return null;\n const parts = raw.split(',').map(v => Number.parseFloat(v.trim()));\n if (parts.length !== 24 || parts.some(v => !Number.isFinite(v))) return null;\n return parts;\n}\n\n// Le nœud HTTP de n8n renvoie le corps du template dans `data`, sous forme de\n// chaîne. Hors n8n (tests), l'objet arrive déjà décodé — on accepte les deux.\nfunction unwrapSnapshot(raw) {\n if (raw && typeof raw.data === 'string') {\n try { return JSON.parse(raw.data); } catch { return raw; }\n }\n return (raw && raw.data) || raw;\n}\n\nconst snapshot = unwrapSnapshot($('HA Snapshot Balance').first().json);\nconst resource = $input.first().json;\n\nreturn [{\n json: {\n nowMs: Number.parseFloat(snapshot.nowMs),\n\n consumption_p50: parseCsv24(snapshot.consumption_p50_csv),\n consumption_p80: parseCsv24(snapshot.consumption_p80_csv),\n\n horizon: resource.horizon || [],\n\n soc_percent: snapshot.soc_percent,\n usable_capacity_kwh: snapshot.usable_capacity_kwh,\n modules_en_service: snapshot.modules_en_service,\n // Plancher dur : même valeur de repli que INPUT_PARSE.js\n // (evcs_night_battery_hard_floor_pct n'est aujourd'hui exposé par AUCUN\n // helper — la valeur « 22 % » documentée partout est en réalité un défaut\n // de code, cf. backlog du chantier).\n soc_floor_percent: 22,\n\n // Vigilance Météo-France, lue sans connaître la commune (l'entité\n // s'appelle sensor.<commune>_weather_alert). Absente tant que\n // l'intégration n'est pas installée : le bilan publie alors\n // 'indisponible' et ne change rien.\n storm_alert_level: snapshot.storm_alert_level,\n storm_alert_detail: snapshot.storm_alert_detail,\n\n // Fenetres HC (28/09, « tenir jusqu'a la prochaine HC ») : horodatages\n // calcules par le gabarit depuis les helpers victron_evcs_hc_*. Sans cette\n // recopie, BALANCE.js ne les voit pas et la regle reste muette.\n // Plancher de charge HC manuelle (18/09) : oublie ici jusqu'au 28/09, le\n // bouton n'avait donc jamais atteint le bilan.\n manual_hc_floor_percent: snapshot.manual_hc_floor_percent,\n hc_window_end_ms: snapshot.hc_window_end_ms,\n next_hc_start_ms: snapshot.next_hc_start_ms,\n next_night_hc_start_ms: snapshot.next_night_hc_start_ms,\n\n // Repris pour l'écriture des helpers, sans influencer le calcul.\n resourceUsable: resource.usable,\n shapeConfidence: resource.source ? resource.source.shapeConfidence : null,\n },\n}];\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user