Agent IA planificateur EVCS en MODE OMBRE (Advisor Shadow V1)

- Workflow n8n tdfInZns7cqo8fU2 actif (15 min): snapshot HA compact -> prompt FR
  -> LLM local Spark (modèle dynamique via /v1/models) avec fallback OpenRouter
  claude-haiku-4.5 (credential chiffré, clé hors git) -> validation stricte
  (enums, bornes, rejet total si invalide) -> helpers advisor_* + divergence
  + compteurs accord/divergence + logbook.
- GARANTIE OMBRE structurelle: écritures limitées à advisor_*/logbook,
  vérifiée par tests/advisor/run.mjs (16 tests, bloquants au push).
- Kill-switch input_boolean.evcs_advisor_enabled.
- Extension R2 multi-workflows (lib-node-map WORKFLOWS[], build/extract/push
  par fichier, n8n-create.mjs, gen-advisor-workflow.mjs).
- Helpers HA via package NFS + carte dashboard EVCS Master (backup fait).
- 1re reco réelle: auto_solar_plus, cible 35%, accord avec le réel.
- Fix sécurité n8n-push: plus de fallback d'id implicite vers le workflow EVCS.
This commit is contained in:
Tallagrand Thierry
2026-08-07 22:42:06 +02:00
parent 1ae8129658
commit 63d56bf284
13 changed files with 1282 additions and 69 deletions

View File

@@ -1,21 +1,27 @@
#!/usr/bin/env node
// R2 — EXTRACT : workflow JSON → src/nodes/*.js (+ template snapshot).
// À utiliser UNIQUEMENT pour (re)synchroniser src/ après une modification faite
// directement dans l'UI n8n (à éviter). En temps normal : éditer src/nodes/,
// puis tools/n8n-push.mjs reconstruit le JSON automatiquement.
// R2 — EXTRACT : workflows JSON → src/ (resynchronisation après édition UI n8n — à éviter).
// Usage : node tools/n8n-extract.mjs [fichierWorkflow]
import fs from 'fs';
import path from 'path';
import { WORKFLOW_FILE, SRC_DIR, NODE_MAP, getCode } from './lib-node-map.mjs';
import { WORKFLOWS, getCode, findWorkflowByFile } from './lib-node-map.mjs';
const wf = JSON.parse(fs.readFileSync(WORKFLOW_FILE, 'utf8'));
fs.mkdirSync(SRC_DIR, { recursive: true });
const fileArg = process.argv.slice(2).find(a => !a.startsWith('--'));
const targets = fileArg ? [findWorkflowByFile(fileArg)].filter(Boolean) : WORKFLOWS;
for (const { file, node, kind } of NODE_MAP) {
const n = wf.nodes.find(x => x.name === node);
if (!n) throw new Error(`Node introuvable: ${node}`);
const code = getCode(n, kind);
fs.writeFileSync(path.join(SRC_DIR, file), code);
console.log(`${file} ← "${node}" (${code.length} chars)`);
for (const w of targets) {
if (!fs.existsSync(w.workflowFile)) {
console.log(`(skip ${w.id}: ${w.workflowFile} absent)`);
continue;
}
const wf = JSON.parse(fs.readFileSync(w.workflowFile, 'utf8'));
fs.mkdirSync(w.srcDir, { recursive: true });
for (const { file, node, kind } of w.nodes) {
const n = wf.nodes.find(x => x.name === node);
if (!n) throw new Error(`Node introuvable: ${node} (${w.workflowFile})`);
const code = getCode(n, kind);
fs.writeFileSync(path.join(w.srcDir, file), code);
console.log(`${w.srcDir}/${file} ← "${node}" (${code.length} chars)`);
}
}
console.log(`\nExtraction OK dans ${SRC_DIR}/`);
console.log('\nExtraction OK');