Six helpers que plus personne n ecrivait depuis la specialisation du 12/09 restaient affiches sur la carte du tableau EVCS Master comme des recommandations courantes : strategie, charge du soir, divergence, cible SOC matin figee a 55 %, plafond courant, et le toggle qui appliquait la cible. C est exactement ce fantome qui a rendu le cablage dangereux — la garde de fraicheur datait le run, pas la valeur. Retires du paquet et de la carte, sauvegardes dans SAVE/. La carte est reecrite pour ce que le conseiller fait desormais : gravite, ce qu il voit, ce qui cloche, la piste et sa confiance. Le conseiller relisait aussi SA PROPRE strategie precedente — figee depuis le 12/09, et parsee sans jamais servir dans le prompt. Retiree de son snapshot et de BUILD_PROMPT. Apres rechargement des trois domaines : dix helpers restants, exactement ceux que l observateur ecrit. Les compteurs ont survecu (8 cycles calmes, 0 signalement), ce qui valide la suppression des `initial:` du matin meme. Gabarit du conseiller re-rendu contre Home Assistant : 48 champs, JSON valide, aucun null. Pousse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
85 lines
3.0 KiB
JavaScript
85 lines
3.0 KiB
JavaScript
#!/usr/bin/env node
|
|
// Usage: node tools/ha-history.mjs <entity_id>[,<entity_id>...] [debut] [fin]
|
|
// node tools/ha-history.mjs sensor.victron_battery_soc 2026-08-25T00:00 2026-08-25T10:00
|
|
// node tools/ha-history.mjs input_boolean.evcs_advisor_enabled -48h
|
|
//
|
|
// Lit l'historique d'états via l'API REST de Home Assistant (jeton dans .env,
|
|
// même mécanique que tools/ha-service.mjs).
|
|
//
|
|
// ⚠️ PIÈGE VÉRIFIÉ : /api/history/period ne renvoie QU'UNE SEULE JOURNÉE si
|
|
// end_time est omis, sans le signaler. Ce script impose donc toujours les deux
|
|
// bornes.
|
|
//
|
|
// Les MCP du projet ne donnent que l'état courant : pour toute analyse
|
|
// a posteriori (qui a changé quoi, et quand), il faut l'historique.
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
function loadToken() {
|
|
if (process.env.HA_TOKEN) return process.env.HA_TOKEN;
|
|
const envPath = path.join(ROOT, '.env');
|
|
if (!fs.existsSync(envPath)) {
|
|
console.error('Jeton introuvable : ni $HA_TOKEN, ni fichier .env.');
|
|
process.exit(1);
|
|
}
|
|
for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
|
|
const m = line.match(/^\s*HA_TOKEN\s*=\s*(.+?)\s*$/);
|
|
if (m) return m[1].replace(/^["']|["']$/g, '');
|
|
}
|
|
console.error('HA_TOKEN absent du fichier .env');
|
|
process.exit(1);
|
|
}
|
|
|
|
const HA_URL = process.env.HA_URL || 'http://10.10.10.17:8123';
|
|
const entities = (process.argv[2] || '').split(',').filter(Boolean);
|
|
if (!entities.length) {
|
|
console.error('Usage: node tools/ha-history.mjs <entity_id>[,...] [debut|-Nh] [fin]');
|
|
process.exit(1);
|
|
}
|
|
|
|
function parseBound(arg, fallback) {
|
|
if (!arg) return fallback;
|
|
const rel = arg.match(/^-(\d+)h$/);
|
|
if (rel) return new Date(Date.now() - Number(rel[1]) * 3600e3);
|
|
return new Date(arg);
|
|
}
|
|
|
|
const end = parseBound(process.argv[4], new Date());
|
|
const start = parseBound(process.argv[3], new Date(end.getTime() - 24 * 3600e3));
|
|
|
|
const url =
|
|
`${HA_URL}/api/history/period/${start.toISOString()}` +
|
|
`?end_time=${encodeURIComponent(end.toISOString())}` +
|
|
`&filter_entity_id=${encodeURIComponent(entities.join(','))}` +
|
|
`&minimal_response&significant_changes_only`;
|
|
|
|
const res = await fetch(url, { headers: { Authorization: `Bearer ${loadToken()}` } });
|
|
if (!res.ok) {
|
|
console.error(`HTTP ${res.status} — ${await res.text()}`);
|
|
process.exit(1);
|
|
}
|
|
const series = await res.json();
|
|
|
|
const fmt = (iso) =>
|
|
new Date(iso).toLocaleString('fr-FR', {
|
|
timeZone: 'Europe/Paris',
|
|
day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
});
|
|
|
|
console.log(`Fenêtre : ${fmt(start.toISOString())} → ${fmt(end.toISOString())} (heure de Paris)\n`);
|
|
for (const rows of series) {
|
|
if (!rows.length) continue;
|
|
console.log(`=== ${rows[0].entity_id} (${rows.length} points) ===`);
|
|
let prev = null;
|
|
for (const r of rows) {
|
|
if (r.state === prev) continue;
|
|
console.log(` ${fmt(r.last_changed || r.last_updated)} ${r.state}`);
|
|
prev = r.state;
|
|
}
|
|
console.log('');
|
|
}
|