Revue : pistes ecartees et source (local/OpenRouter) gardees, historisation pour rejeu, carte verbeuse du conseiller

This commit is contained in:
Tallagrand Thierry
2026-10-08 05:43:54 +02:00
parent 78f434778b
commit b25770736f
7 changed files with 206 additions and 41 deletions

View File

@@ -779,6 +779,36 @@ async function migrer(client) {
// proprietaire. NULL pour les releves anterieurs dont le pack n'est pas sur.
await client.query('ALTER TABLE cellule ADD COLUMN IF NOT EXISTS pack smallint');
// REVUE DE LA JOURNEE (08/10/2026) : constats calcules et pistes du
// conseiller, gardes pour REJOUER le conseiller plus tard (regression) et
// juger s'il merite un role actif. Les pistes ecartees le sont avec leur
// motif ; `avis_proprietaire` recevra le verdict humain (utile / a cote).
await client.query(`
CREATE TABLE IF NOT EXISTS revue_constat (
jour date NOT NULL,
code text NOT NULL,
gravite text,
debut timestamptz,
fin timestamptz,
texte text,
contexte text,
chiffres jsonb,
PRIMARY KEY (jour, code, debut)
)`);
await client.query(`
CREATE TABLE IF NOT EXISTS revue_piste (
jour date NOT NULL,
code text NOT NULL,
piste text NOT NULL,
retenue boolean NOT NULL,
motif text,
modele text,
source text,
avis_proprietaire text,
ts timestamptz DEFAULT now(),
PRIMARY KEY (jour, code, piste)
)`);
// Ce que le CONSEILLER a observe. Ses helpers sont ecrases toutes les
// quinze minutes : sans cette table, un signalement grave emis a 3 h du
// matin a disparu au reveil. C'est precisement la trace que le proprietaire
@@ -1147,6 +1177,7 @@ async function dejaFait(client, genre, jour) {
}
async function revueDeLaJournee(sql, client, st) {
if (!client) return 0;
if (!revueHistoriquePubliee) await publierRevueHistorique(client);
const { min, jour } = partsParis();
if (min < REVUE_PUBLIEE || min > 10 * 60) return 0;
const hc = (id) => (st.get(id)?.state || '').slice(0, 5);
@@ -1166,9 +1197,36 @@ async function revueDeLaJournee(sql, client, st) {
constats: r.constats.map((c) => ({ code: c.code, gravite: c.gravite, texte: c.texte, contexte: c.contexte })),
texte: r.texte,
});
for (const c of r.constats) {
await sql(`INSERT INTO revue_constat (jour, code, gravite, debut, fin, texte, contexte, chiffres)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT DO NOTHING`,
[jour, c.code, c.gravite, new Date(c.debut), new Date(c.fin), c.texte, c.contexte, JSON.stringify(c.chiffres || {})]);
}
await journaliser(sql, 'revue', `Revue de la journee : ${aExaminer} point(s) a examiner`, { jour, texte: r.texte });
await publierRevueHistorique(client);
n++;
}
// Les pistes du conseiller (retenues ET ecartees) sont historisees des qu'il a
// repondu, independamment du courriel.
{
const avis = st.get('sensor.evcs_advisor_revue');
if (avis && avis.state === jour && !(await dejaFait(client, 'revue_pistes', jour))) {
const at = avis.attributes || {};
const lignes = [
...(at.pistes || []).map((p) => ({ ...p, retenue: true, motif: null })),
...(at.ecartees || []).map((p) => ({ ...p, retenue: false })),
];
for (const p of lignes) {
await sql(`INSERT INTO revue_piste (jour, code, piste, retenue, motif, modele, source)
VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT DO NOTHING`,
[jour, p.code || '', p.piste || '', p.retenue, p.motif, at.modele || null, at.source || null]);
}
await journaliser(sql, 'revue_pistes', `Revue : ${(at.pistes || []).length} piste(s) du conseiller, ${(at.ecartees || []).length} ecartee(s)`,
{ jour, source: at.source || null, modele: at.modele || null });
await publierRevueHistorique(client);
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');
@@ -1176,6 +1234,7 @@ async function revueDeLaJournee(sql, client, st) {
if (revue && revue.state === jour && (avisDuJour || min >= REVUE_COURRIEL_SANS_AVIS)) {
const a = revue.attributes || {};
const pistes = (avisDuJour?.pistes || []).map((p) => `- ${p.piste}`);
const quiRepond = avisDuJour ? (avisDuJour.source_texte || '') : '';
const texte = [
`Revue de la journee (24 h jusqu'a la fin des heures creuses de ce matin)`,
'',
@@ -1183,17 +1242,41 @@ async function revueDeLaJournee(sql, client, st) {
a.texte || '(aucun)',
'',
a.a_examiner > 0
? (pistes.length ? ['PISTES DU CONSEILLER IA (avis a verifier, pas des faits) :', ...pistes].join('\n')
? (pistes.length ? ['PISTES DU CONSEILLER IA (avis a verifier, pas des faits) :', ...pistes,
`(${quiRepond}${avisDuJour.rejetees ? ` ${avisDuJour.rejetees} piste(s) ecartee(s).` : ''})`].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 });
await publierRevueHistorique(client);
n++;
}
}
return n;
}
// Historique des revues (7 derniers jours) pour la carte du conseiller :
// combien de points par jour, et ce qu'il en a dit. Publie a chaque nouvelle
// revue ou reponse, et au demarrage.
let revueHistoriquePubliee = false;
async function publierRevueHistorique(client) {
const { rows: c } = await client.query(`SELECT to_char(jour,'YYYY-MM-DD') jour, code, gravite, texte
FROM revue_constat WHERE jour > current_date - 7 ORDER BY jour DESC, debut`);
const { rows: p } = await client.query(`SELECT to_char(jour,'YYYY-MM-DD') jour, code, piste, retenue, motif, source
FROM revue_piste WHERE jour > current_date - 7 ORDER BY jour DESC, retenue DESC`);
const jours = [...new Set(c.map((x) => x.jour))];
await publierCapteur('sensor.evcs_revue_historique', jours.length, {
friendly_name: 'Revues des 7 derniers jours',
jours: jours.map((j) => ({
jour: j,
constats: c.filter((x) => x.jour === j).map(({ code, gravite, texte }) => ({ code, gravite, texte })),
pistes: p.filter((x) => x.jour === j).map(({ code, piste, retenue, motif, source }) => ({ code, piste, retenue, motif, source })),
})),
});
revueHistoriquePubliee = true;
return 1;
}
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));