// Rejoue la correction humaine du 04/10 (JEEP -> ZERO a 17:16) sur la vraie // base, dans une transaction ANNULEE. A lancer dans le conteneur d'historisation. import fs from 'node:fs'; import pg from 'pg'; const src = fs.readFileSync(new URL('../../tools/historise-evcs.mjs', import.meta.url), 'utf8'); const debut = src.indexOf('async function appliquerEtiquetteConcierge(sql, client, st) {'); let i = src.indexOf('{', debut), prof = 0, fin = i; for (; fin < src.length; fin++) { if (src[fin] === '{') prof++; else if (src[fin] === '}' && --prof === 0) break; } const corps = src.slice(debut, fin + 1); const c = new pg.Client(); await c.connect(); let ok = 0, ko = 0; const eq = (n, a, b) => { if (JSON.stringify(a) === JSON.stringify(b)) ok++; else { ko++; console.log('ECHEC', n, JSON.stringify(a), '!=', JSON.stringify(b)); } }; try { await c.query('BEGIN'); await c.query('ALTER TABLE etiquette_branchement ADD COLUMN IF NOT EXISTS applique_depuis timestamptz'); const journal = []; const deps = { txt: (x) => (x ?? '').toString().trim(), LABELS: new Set(['VW', 'JEEP', 'MEGANE', 'INVITE', 'ZERO']), HOUSEHOLD: new Set(['VW', 'JEEP']), CANAUX_HUMAINS: new Set(['whatsapp', 'proprietaire']), DRY: false, journaliser: async (sql, g, r, d) => { journal.push(r); await sql(`INSERT INTO evenement (genre, source, resume, detail) VALUES ($1,'test',$2,$3)`, [g, r, JSON.stringify(d || {})]); }, piloterSelecteurVehicule: async () => 0, }; const fn = new Function(...Object.keys(deps), 'let dernierBlancVu = null; return (' + corps.replace('async function appliquerEtiquetteConcierge', 'async function') + ');')(...Object.values(deps)); const sql = (q, p) => c.query(q, p); const st = (label, conf) => new Map([ ['input_text.airtria_vehicle_label', { state: JSON.stringify({ branchement: '2026-10-04 16:55:45.120519+02', label, confirmed_at: conf, source: 'whatsapp' }) }], ['input_boolean.airtria_concierge_enabled', { state: 'on' }]]); // Une session terminee AVANT la correction, pour verifier qu'elle garde JEEP. await c.query(`INSERT INTO session_charge (debut, fin, terminee, vehicule, vehicule_sur, energie_kwh, energie_station_kwh) VALUES ('2026-10-04T14:56:00Z','2026-10-04T14:56:30Z',true,'JEEP',true,0.05,0.1)`); await c.query(`INSERT INTO evenement (genre, source, resume, detail) VALUES ('vehicule','test','avant',$1)`, [JSON.stringify({ origine: 'airtria', label: 'JEEP', sessions: ['2026-10-04T14:56:00.000Z', '2026-10-04T14:56:55.447Z'] })]); await fn(sql, c, st('ZERO', '2026-10-04T15:10:00.000Z')); // avant la coupe reelle de 17:16 (session JEEP 16:56-17:16) const s = (await c.query(`SELECT debut, vehicule, vehicule_sur FROM session_charge WHERE debut BETWEEN '2026-10-04T14:50Z' AND '2026-10-04T16:00Z' ORDER BY debut`)).rows; eq('session terminee avant la correction garde JEEP', s[0].vehicule, 'JEEP'); eq('session en cours a 17:10 passe ZERO, confirmee', [s[1].vehicule, s[1].vehicule_sur], ['ZERO', true]); const e = (await c.query(`SELECT label, applique_depuis FROM etiquette_branchement WHERE branchement = '2026-10-04T14:55:45.120Z'`)).rows[0]; eq('etiquette du branchement = ZERO depuis 17:10', [e.label, e.applique_depuis?.toISOString()], ['ZERO', '2026-10-04T15:10:00.000Z']); eq('journal de correction', journal.some(r => r.startsWith('Etiquette corrigee par le proprietaire : JEEP -> ZERO')), true); eq('pas de refus', journal.some(r => r.includes('REFUSEE')), false); // Une contradiction NON humaine reste refusee. journal.length = 0; const st2 = new Map([['input_text.airtria_vehicle_label', { state: JSON.stringify({ branchement: '2026-10-04 16:55:45.120519+02', label: 'MEGANE', confirmed_at: '2026-10-04T15:30:00.000Z', source: 'regle-puissance' }) }], ['input_boolean.airtria_concierge_enabled', { state: 'on' }]]); await fn(sql, c, st2); eq('contradiction par la regle de puissance : refusee', journal.some(r => r.includes('REFUSEE')), true); } finally { await c.query('ROLLBACK'); await c.end(); } console.log(`${ok}/${ok + ko}`); process.exit(ko ? 1 : 0);