Jeep identifiee aussi par sa fin de trajet juste avant le branchement (soir sans charge)

This commit is contained in:
Tallagrand Thierry
2026-10-05 23:42:14 +02:00
parent 8f526bdcd6
commit 5e88ce5720
4 changed files with 61 additions and 16 deletions

View File

@@ -29,12 +29,12 @@ try {
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:16:50.000Z'));
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:16 passe ZERO, confirmee', [s[1].vehicule, s[1].vehicule_sur], ['ZERO', true]);
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:16:50', [e.label, e.applique_depuis?.toISOString()], ['ZERO', '2026-10-04T15:16:50.000Z']);
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.

View File

@@ -40,14 +40,19 @@ eq('helper corrompu repart a vide', JSON.parse(ajouterReleve('pas du json', { ve
return { rowCount: 1 };
};
const lire = async (q, v) => {
if (q.includes('FROM releve_km') && q.includes("source = 'uconnect'")) {
const r = base.releves.filter(x => x.ts >= v[0] && x.ts <= v[1] && x.niveau !== null).sort((a, b) => b.ts - a.ts);
return { rows: r.length ? [{ ts: r[0].ts, niveau_pct: r[0].niveau }] : [] };
}
if (q.includes('FROM releve_km')) { const r = [...base.releves].sort((a, b) => b.ts - a.ts); return { rows: r.length ? [{ km: r[0].km, ts: r[0].ts }] : [] }; }
if (q.includes('FROM etiquette_branchement')) return { rows: base.etiquette ? [{ label: base.etiquette }] : [] };
if (q.includes('FROM evenement')) return { rows: [{ t: base.branche }] };
if (q.includes('FROM niveau_saisi')) return { rows: base.niveaux.filter(n => n.saisi_a >= v[0]) };
return { rows: [] };
};
const journ = async () => {};
const appels = [];
const etiq = async (b, n) => { appels.push([b.toISOString(), n]); return 1; };
const etiq = async (b, n, m) => { appels.push([b.toISOString(), n, m]); return 1; };
const etat = (km, niv, maj, branchee = 'on', borne = 'on') => new Map([
[JEEP.odometre, { state: String(km) }], [JEEP.niveau, { state: String(niv) }], [JEEP.majA, { state: maj }],
[JEEP.branchee, { state: branchee }], ['binary_sensor.victron_evcs_vehicle_connected', { state: borne }],
@@ -56,7 +61,7 @@ eq('helper corrompu repart a vide', JSON.parse(ajouterReleve('pas du json', { ve
eq('jeep : premier releve + niveau au branchement', [base.releves.length, base.niveaux.length, base.niveaux[0]?.niveau], [1, 1, 46]);
await ingererJeep(sql, lire, etat(112716, 60, '2026-10-04T11:58:39+00:00'), journ, etiq);
eq('jeep : compteur immobile et 2e maj du branchement -> rien', [base.releves.length, base.niveaux.length], [1, 1]);
eq('jeep : branchement identifie UNE fois, avec le niveau lu', appels, [['2026-10-04T11:28:00.000Z', 46]]);
eq('jeep : branchement identifie UNE fois, avec le niveau lu', appels, [['2026-10-04T11:28:00.000Z', 46, 'branchee']]);
base.branche = new Date('2026-10-05T16:00:00Z');
await ingererJeep(sql, lire, etat(112760, 30, '2026-10-05T17:00:00+00:00'), journ);
eq('jeep : trajet -> releve ; maj 1 h apres le branchement -> pas de niveau', [base.releves.length, base.releves[1]?.km, base.niveaux.length], [2, 112760, 1]);
@@ -66,6 +71,18 @@ eq('helper corrompu repart a vide', JSON.parse(ajouterReleve('pas du json', { ve
await ingererJeep(sql, lire, etat(112800, 25, '2026-10-06T16:05:00+00:00', 'on', 'off'), journ, etiq);
eq('jeep : borne debranchee -> pas d identification', appels.length, 1);
eq('jeep : borne debranchee -> releve oui, niveau au branchement non', [base.releves.length, base.niveaux.length], [3, 1]);
// Arrivee (05/10) : fin de trajet a 19:05, branchement a 19:06, Jeep non branchee pour elle.
base.branche = new Date('2026-10-07T17:06:15Z');
await ingererJeep(sql, lire, etat(112850, 36, '2026-10-07T17:05:13+00:00', 'off', 'on'), journ, etiq);
eq('jeep : arrivee 1 min avant le branchement -> identifiee, niveau d arrivee', appels.at(-1), ['2026-10-07T17:06:15.000Z', 36, 'arrivee']);
await ingererJeep(sql, lire, etat(112850, 36, '2026-10-07T17:05:13+00:00', 'off', 'on'), journ, etiq);
eq('jeep : arrivee -> une seule identification', appels.length, 2);
base.branche = new Date('2026-10-08T17:30:00Z');
await ingererJeep(sql, lire, etat(112900, 30, '2026-10-08T17:05:00+00:00', 'off', 'on'), journ, etiq);
eq('jeep : arrivee 25 min avant -> pas d identification', appels.length, 2);
base.branche = new Date('2026-10-09T17:06:00Z'); base.etiquette = 'VW';
await ingererJeep(sql, lire, etat(112950, 40, '2026-10-09T17:05:00+00:00', 'off', 'on'), journ, etiq);
eq('jeep : proprietaire a deja dit VW -> rien', [appels.length, base.niveaux.length], [2, 2]);
}
if (process.argv.includes('--vues')) {

View File

@@ -1434,15 +1434,15 @@ async function appliquerEtiquetteConcierge(sql, client, st) {
// 3. niveau « saisi » du vehicule = niveau lu, sauf saisie humaine depuis le
// branchement - 1 h (l'automatisation HA recale la reference du compteur).
// Le concierge ne doit alors plus poser sa question : il lit etiquette_branchement.
async function etiqueterJeepUconnect(sql, client, st, branche, niveau) {
async function etiqueterJeepUconnect(sql, client, st, branche, niveau, motif = 'branchee') {
if (DRY) return 0;
const { rowCount } = await client.query(
`INSERT INTO etiquette_branchement (branchement, label, source_airtria, confirmed_at)
VALUES ($1, 'JEEP', 'uconnect', $2) ON CONFLICT (branchement) DO NOTHING`,
[branche, new Date().toISOString()]);
if (!rowCount) return 0; // deja etiquete (le proprietaire a repondu) : on ne touche a rien
await journaliser(sql, 'vehicule', `Branchement identifie : JEEP (la voiture se declare branchee, Uconnect), niveau ${niveau} %`,
{ origine: 'uconnect', label: 'JEEP', branchement: branche.toISOString(), niveau });
await journaliser(sql, 'vehicule', `Branchement identifie : JEEP (${motif === 'arrivee' ? 'fin de trajet juste avant le branchement' : 'la voiture se declare branchee'}, Uconnect), niveau ${niveau} %`,
{ origine: 'uconnect', label: 'JEEP', branchement: branche.toISOString(), niveau, preuve: motif });
let n = 1 + await piloterSelecteurVehicule(sql, st, 'JEEP', 'uconnect');
const saisieA = Number(st.get('input_datetime.victron_evcs_vehicle_level_at')?.attributes?.timestamp) * 1000;
if (!(saisieA >= branche.getTime() - 3600000)) {
@@ -2212,7 +2212,7 @@ async function cycle(client) {
}) + await ingererNiveauSaisi(sql, st).catch((e) => {
console.error(`niveau saisi en echec : ${e.message}`); return 0;
}) + await ingererJeep(sql, (q, v) => client.query(q, v), st, journaliser,
(branche, niveau) => etiqueterJeepUconnect(sql, client, st, branche, niveau)).catch((e) => {
(branche, niveau, motif) => etiqueterJeepUconnect(sql, client, st, branche, niveau, motif)).catch((e) => {
console.error(`jeep uconnect en echec : ${e.message}`); return 0;
}),
pvDiagnostic: DRY ? 0 : await publierDiagnosticPv(st).catch((e) => {

View File

@@ -303,24 +303,52 @@ export async function ingererJeep(sql, lire, st, journaliser, etiqueter = null)
{ vehicule: 'JEEP', km, niveau, ts: ts.toISOString(), source: 'uconnect' });
}
}
if (niveau !== null && st.get(JEEP.branchee)?.state === 'on'
&& st.get('binary_sensor.victron_evcs_vehicle_connected')?.state === 'on') {
// Deux preuves que le branchement de la borne est la JEEP :
// 1. elle se DECLARE branchee dans [branchement -5 min, +30 min] (charge demarree) ;
// 2. (05/10, accord du proprietaire) elle a FINI UN TRAJET (releve uconnect,
// compteur change) dans les 10 min AVANT le branchement. Le soir en « sans
// EDF » la charge ne demarre pas et la Jeep ne republie rien : sans cet
// indice, le 05/10 a 19:06 le concierge a du poser sa question.
// Risque assume : une autre voiture branchee dans ces 10 min serait prise pour
// la JEEP — la reponse du proprietaire corrige (sa derniere reponse fait foi).
if (st.get('binary_sensor.victron_evcs_vehicle_connected')?.state === 'on') {
const { rows: b } = await lire(
`SELECT max(ts) AS t FROM evenement WHERE genre = 'vehicule'
AND detail->>'entite' = 'binary_sensor.victron_evcs_vehicle_connected' AND detail->>'apres' = 'on'`, []);
const branche = b[0]?.t;
if (branche && ts >= new Date(branche.getTime() - 5 * 60000) && ts <= new Date(branche.getTime() + 30 * 60000)) {
let preuve = null;
if (branche && niveau !== null && st.get(JEEP.branchee)?.state === 'on'
&& ts >= new Date(branche.getTime() - 5 * 60000) && ts <= new Date(branche.getTime() + 30 * 60000)) {
preuve = { ts, niveau, motif: 'branchee' };
} else if (branche) {
const { rows: arr } = await lire(
`SELECT ts, niveau_pct FROM releve_km WHERE vehicule = 'JEEP' AND source = 'uconnect'
AND ts >= $1 AND ts <= $2 AND niveau_pct IS NOT NULL ORDER BY ts DESC LIMIT 1`,
[new Date(branche.getTime() - 10 * 60000), branche]);
if (arr.length) preuve = { ts: arr[0].ts, niveau: Number(arr[0].niveau_pct), motif: 'arrivee' };
}
// Le proprietaire a deja nomme un AUTRE vehicule pour ce branchement : on n'y touche pas.
let autre = false;
if (preuve) {
const { rows: et } = await lire(
`SELECT label FROM etiquette_branchement WHERE branchement BETWEEN $1 AND $2 LIMIT 1`,
[new Date(branche.getTime() - 2000), new Date(branche.getTime() + 2000)]);
autre = et.length > 0 && et[0].label !== 'JEEP';
}
if (preuve && !autre) {
const ts = preuve.ts, niveau = preuve.niveau;
const { rows: deja } = await lire(
"SELECT 1 FROM niveau_saisi WHERE source = 'uconnect' AND saisi_a >= $1", [new Date(branche.getTime() - 5 * 60000)]);
"SELECT 1 FROM niveau_saisi WHERE source = 'uconnect' AND saisi_a >= $1", [new Date(branche.getTime() - 10 * 60000)]);
if (!deja.length) {
const r = await sql(
"INSERT INTO niveau_saisi (saisi_a, niveau_pct, ref_kwh, source) VALUES ($1,$2,NULL,'uconnect') ON CONFLICT DO NOTHING",
[ts, niveau]);
if (r?.rowCount) {
n++;
await journaliser(sql, 'vehicule', `Niveau JEEP au branchement : ${niveau} % (lu par Uconnect)`,
{ niveau, ts: ts.toISOString(), branchement: branche.toISOString() });
if (etiqueter) n += await etiqueter(branche, niveau);
await journaliser(sql, 'vehicule', `Niveau JEEP au branchement : ${niveau} % (lu par Uconnect`
+ (preuve.motif === 'arrivee' ? ', fin de trajet juste avant le branchement)' : ')'),
{ niveau, ts: ts.toISOString(), branchement: branche.toISOString(), preuve: preuve.motif });
if (etiqueter) n += await etiqueter(branche, niveau, preuve.motif);
}
}
}