Nuit : la voiture profite d'EDF ferme pour la batterie, la nuit prepare le soir ; conseiller IA relecteur de la journee (revue calculee + pistes)
This commit is contained in:
@@ -31,6 +31,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import pg from 'pg';
|
||||
import { creerSchemaKm, ingererReleves, ingererNiveauSaisi, ingererJeep } from './kilometrage.mjs';
|
||||
import { texteRapport, envoyer, donneesNuit, texteDepuis } from './rapport-nuit.mjs';
|
||||
import { donneesRevue, constatsRevue } from './revue-jour.mjs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, bilanNuit, etatReseau, importJour, minuitLocal, COMPTEURS_IMPORT, COMPTEUR_EDF, consoJour, COMPTEURS_CONSO, projectionSoir, previsionNuit, vrmVersCourbes, nuitMaisonPrevue } from './diagnostics-agent.mjs';
|
||||
import { transitionsDansSerie } from './allers-retours.mjs';
|
||||
import { energieSur24h, energieDepuis, COMPTEUR_CHAUFFE_EAU } from './chauffe-eau.mjs';
|
||||
@@ -1122,6 +1123,83 @@ async function envoyerRapportDuMatin(sql, client) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// REVUE DE LA JOURNEE (08/10/2026, tools/revue-jour.mjs) : a 07:30, les
|
||||
// constats (calcules, testes) sont publies dans sensor.evcs_revue_jour ; le
|
||||
// conseiller IA les relit vers 07:35 et publie ses pistes dans
|
||||
// sensor.evcs_advisor_revue ; a 07:45 le courriel « revue de la journee » part
|
||||
// avec les constats et, s'il a repondu (sinon a 08:15 sans lui), ses pistes —
|
||||
// presentees comme un AVIS, jamais comme un fait.
|
||||
const REVUE_PUBLIEE = 7 * 60 + 30;
|
||||
const REVUE_COURRIEL = 7 * 60 + 45;
|
||||
const REVUE_COURRIEL_SANS_AVIS = 8 * 60 + 15;
|
||||
function partsParis() {
|
||||
const p = Object.fromEntries(new Intl.DateTimeFormat('fr-FR', {
|
||||
timeZone: 'Europe/Paris', year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', hour12: false,
|
||||
}).formatToParts(new Date()).filter((x) => x.type !== 'literal').map((x) => [x.type, Number(x.value)]));
|
||||
return { min: p.hour * 60 + p.minute, jour: `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}` };
|
||||
}
|
||||
async function dejaFait(client, genre, jour) {
|
||||
const { rowCount } = await client.query(`SELECT 1 FROM evenement WHERE genre = $1
|
||||
AND ts >= $2::date AND ts < $2::date + interval '1 day' LIMIT 1`, [genre, jour]);
|
||||
return rowCount > 0;
|
||||
}
|
||||
async function revueDeLaJournee(sql, client, st) {
|
||||
if (!client) return 0;
|
||||
const { min, jour } = partsParis();
|
||||
if (min < REVUE_PUBLIEE || min > 10 * 60) return 0;
|
||||
const hc = (id) => (st.get(id)?.state || '').slice(0, 5);
|
||||
const fenetres = [
|
||||
{ nom: 'nuit', debut: hc('input_datetime.victron_evcs_hc_night_start'), fin: hc('input_datetime.victron_evcs_hc_night_stop') },
|
||||
{ nom: 'midi', debut: hc('input_datetime.victron_evcs_hc_midday_start'), fin: hc('input_datetime.victron_evcs_hc_midday_stop') },
|
||||
];
|
||||
let n = 0;
|
||||
if (!(await dejaFait(client, 'revue', jour))) {
|
||||
// Fin de la fenetre = fin des HC de nuit d'aujourd'hui + 1 min.
|
||||
const fin = Date.parse(`${jour}T${fenetres[0].fin || '07:24'}:00${decalageParis(jour)}`) + 60000;
|
||||
const r = constatsRevue(await donneesRevue(client, fenetres, fin));
|
||||
const aExaminer = r.constats.filter((c) => c.gravite === 'a_examiner').length;
|
||||
await publierCapteur('sensor.evcs_revue_jour', jour, {
|
||||
friendly_name: 'Revue de la journee',
|
||||
a_examiner: aExaminer,
|
||||
constats: r.constats.map((c) => ({ code: c.code, gravite: c.gravite, texte: c.texte, contexte: c.contexte })),
|
||||
texte: r.texte,
|
||||
});
|
||||
await journaliser(sql, 'revue', `Revue de la journee : ${aExaminer} point(s) a examiner`, { jour, texte: r.texte });
|
||||
n++;
|
||||
}
|
||||
if (min >= REVUE_COURRIEL && !(await dejaFait(client, 'revue_courriel', jour))) {
|
||||
const revue = st.get('sensor.evcs_revue_jour');
|
||||
const avis = st.get('sensor.evcs_advisor_revue');
|
||||
const avisDuJour = avis && avis.state === jour ? avis.attributes : null;
|
||||
if (revue && revue.state === jour && (avisDuJour || min >= REVUE_COURRIEL_SANS_AVIS)) {
|
||||
const a = revue.attributes || {};
|
||||
const pistes = (avisDuJour?.pistes || []).map((p) => `- ${p.piste}`);
|
||||
const texte = [
|
||||
`Revue de la journee (24 h jusqu'a la fin des heures creuses de ce matin)`,
|
||||
'',
|
||||
'CONSTATS (mesures) :',
|
||||
a.texte || '(aucun)',
|
||||
'',
|
||||
a.a_examiner > 0
|
||||
? (pistes.length ? ['PISTES DU CONSEILLER IA (avis a verifier, pas des faits) :', ...pistes].join('\n')
|
||||
: `Pistes du conseiller IA : ${avisDuJour ? 'aucune' : 'pas de reponse ce matin'}.`)
|
||||
: '',
|
||||
].join('\n').trim();
|
||||
await envoyer(texte, `EVCS - revue de la journee${a.a_examiner > 0 ? ` (${a.a_examiner} point(s))` : ''}`);
|
||||
await journaliser(sql, 'revue_courriel', 'Revue de la journee envoyee par courriel', { jour, avis: !!avisDuJour });
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function decalageParis(jour) {
|
||||
const d = new Date(`${jour}T12:00:00Z`);
|
||||
const h = Number(new Intl.DateTimeFormat('en-GB', { timeZone: 'Europe/Paris', hour: '2-digit', hour12: false }).format(d));
|
||||
return `+${String(h - 12).padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Verse dans la base les relevés de cellule deposes par le poste qui porte le
|
||||
// cable serie. Celui-ci n'ecrit qu'un CSV dans le depot partage : il n'a donc
|
||||
@@ -2355,6 +2433,9 @@ async function cycle(client) {
|
||||
rapport: DRY ? 0 : await envoyerRapportDuMatin(sql, client).catch((e) => {
|
||||
console.error('rapport de nuit :', e.message); return 0;
|
||||
}),
|
||||
revue: DRY ? 0 : await revueDeLaJournee(sql, client, st).catch((e) => {
|
||||
console.error('revue de la journee :', e.message); return 0;
|
||||
}),
|
||||
cellules: DRY ? 0 : await ingererCellules(client).catch((e) => {
|
||||
console.error(`ingestion cellules en echec : ${e.message}`);
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user