Etiquettes : la derniere reponse humaine fait foi (correction de permutation), rejeu du 04/10

This commit is contained in:
Tallagrand Thierry
2026-10-05 14:01:43 +02:00
parent 13698c4d82
commit 8f526bdcd6
3 changed files with 110 additions and 10 deletions

View File

@@ -599,6 +599,9 @@ async function migrer(client) {
pose_a timestamptz NOT NULL DEFAULT now(),
sessions_vues integer NOT NULL DEFAULT 0
)`);
// Correction humaine (05/10) : l'etiquette ne vaut que pour les sessions non
// terminees a cet instant (NULL = tout le branchement).
await client.query('ALTER TABLE etiquette_branchement ADD COLUMN IF NOT EXISTS applique_depuis timestamptz');
await client.query(
'CREATE INDEX IF NOT EXISTS session_charge_debut_idx ON session_charge (debut DESC)');
@@ -1323,10 +1326,34 @@ async function appliquerEtiquetteConcierge(sql, client, st) {
WHERE x.value = ANY($1::text[]))`,
[[...cibles]]);
const contradiction = deja.map((r) => r.label).filter((l) => l && l !== label);
// CORRECTION HUMAINE (05/10/2026, decision du proprietaire) : sa DERNIERE
// reponse fait foi. Cas reel du 04/10 : JEEP notee a 16:59, puis « Non
// maintenant c'est la zero » a 17:16 — permutation rapide que la borne n'a
// pas vue. L'ancien refus laissait JEEP en base pendant que le concierge
// repondait « Noté : ZERO ». Desormais : les sessions TERMINEES avant la
// correction gardent leur etiquette (c'etait le bon vehicule a ce moment-la),
// la session en cours et les suivantes prennent la nouvelle. Une
// contradiction NON humaine (regle de puissance) reste refusee.
let correctionDepuis = null;
if (contradiction.length) {
return refuser(
`le branchement porte deja l etiquette ${contradiction.join('/')}, incompatible avec ${label}`,
{ sessions: [...cibles], deja: contradiction });
if (!CANAUX_HUMAINS.has(source)) {
return refuser(
`le branchement porte deja l etiquette ${contradiction.join('/')}, incompatible avec ${label}`,
{ sessions: [...cibles], deja: contradiction });
}
correctionDepuis = Number.isFinite(Date.parse(confirmedAt)) ? new Date(confirmedAt) : new Date();
const { rows: encore } = await lire(
`SELECT debut FROM session_charge WHERE debut = ANY($1::timestamptz[])
AND (fin IS NULL OR fin > $2 OR NOT terminee)`, [[...cibles], correctionDepuis]);
const gardees = cibles.size - encore.length;
cibles.clear();
for (const r of encore) cibles.add(enIso(r.debut));
await journaliser(sql, 'vehicule',
`Etiquette corrigee par le proprietaire : ${contradiction.join('/')} -> ${label} a partir de `
+ `${correctionDepuis.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`
+ (gardees ? ` (${gardees} session${gardees > 1 ? 's' : ''} terminee${gardees > 1 ? 's' : ''} avant : inchangee${gardees > 1 ? 's' : ''})` : ''),
{ origine: 'correction', source_airtria: source, label, avant: contradiction,
confirmed_at: confirmedAt, depuis: correctionDepuis.toISOString(), gardees });
}
const sur = CANAUX_HUMAINS.has(source);
@@ -1361,12 +1388,13 @@ async function appliquerEtiquetteConcierge(sql, client, st) {
const branchement = bornes[0]?.t || null;
if (branchement) {
await sql(
`INSERT INTO etiquette_branchement (branchement, label, source_airtria, confirmed_at)
VALUES ($1,$2,$3,$4)
`INSERT INTO etiquette_branchement (branchement, label, source_airtria, confirmed_at, applique_depuis)
VALUES ($1,$2,$3,$4,$5)
ON CONFLICT (branchement) DO UPDATE
SET label = EXCLUDED.label, source_airtria = EXCLUDED.source_airtria,
confirmed_at = EXCLUDED.confirmed_at, pose_a = now()`,
[branchement, label, source, confirmedAt || null]);
confirmed_at = EXCLUDED.confirmed_at, pose_a = now(),
applique_depuis = EXCLUDED.applique_depuis`,
[branchement, label, source, confirmedAt || null, correctionDepuis]);
}
// Le controleur lit le selecteur : la reponse doit y arriver, pas seulement
@@ -1439,7 +1467,7 @@ async function etiqueterJeepUconnect(sql, client, st, branche, niveau) {
async function appliquerEtiquettesEnAttente(sql, client) {
if (DRY) return 0;
const { rows } = await client.query(
`SELECT branchement, label, source_airtria FROM etiquette_branchement
`SELECT branchement, label, source_airtria, applique_depuis FROM etiquette_branchement
WHERE branchement > now() - interval '48 hours' ORDER BY branchement`);
let n = 0;
for (const e of rows) {
@@ -1452,8 +1480,9 @@ async function appliquerEtiquettesEnAttente(sql, client) {
WHERE genre = 'vehicule'
AND detail->>'entite' = 'binary_sensor.victron_evcs_vehicle_connected'
AND detail->>'apres' = 'off' AND ts > $1::timestamptz), 'infinity'::timestamptz)
AND ($4::timestamptz IS NULL OR s.fin IS NULL OR s.fin > $4::timestamptz OR NOT s.terminee)
AND (s.vehicule IS DISTINCT FROM $2 OR s.vehicule_sur IS DISTINCT FROM $3)`,
[e.branchement, e.label, sur]);
[e.branchement, e.label, sur, e.applique_depuis]);
if (rowCount) {
await journaliser(sql, 'vehicule',
`Etiquette ${e.label} posee sur ${rowCount} session(s) ouverte(s) depuis le branchement`,