workspace: niveau du vehicule par le concierge (option A), simulation par drapeau
- concierge-write.js : setVehicleLevel (entier 0-100, refus sans correction, relecture), ZERO dans SELECTOR_LABELS ; tests 13/13. - concierge-live.js answer : reconnait « NN % », ecrit le niveau, lignes « 🔋 Niveau noté » / « ⚠️ Niveau NON noté ». - Drapeau workspace/.simulation lu par concierge-live.js et airtria-ha-vehicule.js : aucune ecriture HA pendant un banc agent. - airtria-ha-vehicule.js : validation de tous les arguments avant ecriture. - AGENTS.md et reference-concierge.md : vehicule et niveau passent par le concierge. - Journaux de tri Orange et AIRTRIA, DREAMS.
This commit is contained in:
@@ -21,9 +21,18 @@ const { SELECTOR_ENTITY, SELECTOR_LABELS } = W;
|
||||
// CONCIERGE_SIMULATION='{"connected":"on|off","options":[..],"plugin":{"on":<epoch>,"on_text":"..","off":null}|null,
|
||||
// "pending":{..}|null,"pgDown":false}' → answer tourne SANS HA, SANS PostgreSQL et SANS AUCUNE écriture
|
||||
// (tests unitaires, 30/09/2026). Hors simulation : comportement réel inchangé.
|
||||
const SIM = process.env.CONCIERGE_SIMULATION ? JSON.parse(process.env.CONCIERGE_SIMULATION) : null;
|
||||
// 03/10/2026 : la simulation s'active AUSSI par un fichier drapeau `.simulation`
|
||||
// a cote du script (meme JSON, ou `{}`). Un banc qui passe par l'agent complet
|
||||
// ne controle pas l'environnement des commandes que l'agent lance : seul un
|
||||
// fichier pose avant le banc protege l'installation (incidents du 30/09 et du
|
||||
// 02/10, ecritures reelles pendant des tests).
|
||||
const SIM_FILE = path.join(__dirname, '.simulation');
|
||||
const SIM = process.env.CONCIERGE_SIMULATION ? JSON.parse(process.env.CONCIERGE_SIMULATION)
|
||||
: fs.existsSync(SIM_FILE) ? (() => { try { return JSON.parse(fs.readFileSync(SIM_FILE, 'utf8') || '{}'); } catch { return {}; } })()
|
||||
: null;
|
||||
const publishLabel = SIM ? async () => {} : W.publishLabel;
|
||||
const setVehicleSelector = SIM ? async () => {} : W.setVehicleSelector;
|
||||
const setVehicleLevel = SIM ? async () => {} : W.setVehicleLevel;
|
||||
const health = require('./concierge-health.js');
|
||||
|
||||
const STATE = path.join(__dirname, 'concierge-state.json');
|
||||
@@ -165,7 +174,30 @@ function parseLabel(text, options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Niveau ANNONCE dans le message (03/10/2026, decision du proprietaire) :
|
||||
// « JEEP reste 70 % », « elle est a 40% », « 55 pour cent ». Un nombre SANS « % »
|
||||
// ou « pour cent » n'est jamais pris pour un niveau (02/10 : « 92 170 »).
|
||||
// Renvoie { brut, valeur } — valeur = entier 0-100 ou null si invalide ; le
|
||||
// script REFUSE une valeur invalide, il ne la corrige jamais.
|
||||
function parseNiveau(text) {
|
||||
const m = /(\d{1,3}(?:[.,]\d+)?)\s*(?:%|pour\s*cent|pourcent)/i.exec(text || '');
|
||||
if (!m) return null;
|
||||
const v = Number(m[1].replace(',', '.'));
|
||||
return { brut: m[1], valeur: Number.isInteger(v) && v >= 0 && v <= 100 ? v : null };
|
||||
}
|
||||
|
||||
async function noterNiveau(niveau) {
|
||||
if (!niveau) return '';
|
||||
if (niveau.valeur === null) {
|
||||
return `⚠️ Niveau NON noté : « ${niveau.brut} % » n'est pas un niveau valide (entier de 0 à 100).`;
|
||||
}
|
||||
try { await setVehicleLevel(niveau.valeur, 'whatsapp'); }
|
||||
catch (e) { return `⚠️ Niveau NON noté (${e.message}).`; }
|
||||
return `🔋 Niveau noté : ${niveau.valeur} %.`;
|
||||
}
|
||||
|
||||
async function answer(text) {
|
||||
const niveau = parseNiveau(text);
|
||||
const pending = SIM ? (SIM.pending || null) : loadJSON(PENDING, null);
|
||||
let options = ['VW', 'JEEP'];
|
||||
try {
|
||||
@@ -173,7 +205,14 @@ async function answer(text) {
|
||||
if (Array.isArray(sel.attributes?.options)) options = sel.attributes.options;
|
||||
} catch { /* défaut VW/JEEP */ }
|
||||
const label = parseLabel(text, options);
|
||||
if (!label) return "(aucune action concierge : pas de véhicule nommé — ne relaie pas cette ligne, réponds à la question)"; // non reconnu : on ne publie rien
|
||||
if (!label && !niveau) return "(aucune action concierge : pas de véhicule nommé — ne relaie pas cette ligne, réponds à la question)"; // non reconnu : on ne publie rien
|
||||
// Niveau SEUL (« elle est a 40 % ») : il vaut pour la voiture branchee, s'il y en a une.
|
||||
if (!label) {
|
||||
let connected = false;
|
||||
try { connected = (await haState(CONNECTED)).state === 'on'; } catch { /* ignore */ }
|
||||
if (!connected) return "Rien n'est noté : aucune voiture n'est branchée.";
|
||||
return await noterNiveau(niveau);
|
||||
}
|
||||
|
||||
// Cible de l'étiquette = le branchement à confirmer :
|
||||
// - avec question en attente : celui du pending.
|
||||
@@ -214,7 +253,9 @@ async function answer(text) {
|
||||
}
|
||||
if (pending && !SIM) fs.rmSync(PENDING, { force: true });
|
||||
const when = new Date(on * 1000).toLocaleString('fr-FR', { timeZone: 'Europe/Paris' });
|
||||
return `✅ Noté : ${label} pour la voiture branchée du ${when}. Merci !${selectorNote}`;
|
||||
const ligneNiveau = await noterNiveau(niveau);
|
||||
return `✅ Noté : ${label} pour la voiture branchée du ${when}. Merci !${selectorNote}` +
|
||||
(ligneNiveau ? `\n${ligneNiveau}` : '');
|
||||
}
|
||||
|
||||
// Alerte "voiture branchée mais ne charge pas" (19/09/2026). EVCS détecte l'anomalie
|
||||
@@ -248,5 +289,6 @@ const cmd = process.argv[2];
|
||||
catch (e) { out = `Rien n'est noté : erreur du script (${e.message}).`; }
|
||||
}
|
||||
else { console.error('usage: concierge-live.js detect | answer "<texte>"'); process.exit(2); }
|
||||
if (out && SIM && cmd === 'answer') out += '\n[SIMULATION : rien n\'a été écrit dans Home Assistant]';
|
||||
if (out) console.log(out);
|
||||
})().catch(e => { console.error('[concierge-live]', e.message); process.exit(1); });
|
||||
|
||||
Reference in New Issue
Block a user