Skip to content

Commit 3c9af4d

Browse files
committed
fix(eu-data-act): mileage stuck at lower of two reported values
Real-world dataset (VW_connect.json) showed VW emitting two mileage.value slots in the same /list payload: idx=11 uuid=69d437d3... value=82 (current odometer) idx=60 uuid=30cc36fd... value=71 (lagging snapshot from older report) The previous 'smallest UUID wins' tie-break inherited from the original HA reference picked 30cc36fd → state stayed at 71 even after a fresh download. Tibber and the in-car display showed 82; the adapter would not update until the lagging UUID disappeared from the portal (didn't happen on its own, even across Adapter Stop/Start). Fix mirrors what mikrohard/HA_VAG-EU-Data-Act does in its fork: - Generic case: pick the LAST occurrence in the ZIP array. The portal bundles minute-level snapshots in array order, so the last duplicate is the best proxy for the freshest value. - Monotonic fields (mileage.value, mileage): pick the LARGEST numeric value. Lagging snapshots can never read higher than the true current odometer, so the max is correct by construction. Sequence number ties break equal values. Verified against the user's real dataset: before fix: mileage.value = 71 after fix: mileage.value = 82 (correct) No timestampUtc per item to read (verified: 0 / 113 items in the sample carry one), so we don't use the timestamp-based ranking the HA fork adds — array position is the only signal available.
1 parent b51826d commit 3c9af4d

1 file changed

Lines changed: 43 additions & 6 deletions

File tree

lib/euDataAct.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,31 +315,68 @@ function extractVins(payload) {
315315

316316
// --- raw data points -> structured object (for json2iob) -------------------
317317

318+
// Field names that are monotonic non-decreasing — for these the largest
319+
// numeric value across duplicates is the freshest reading. Mirrors the
320+
// HA_VAG-EU-Data-Act fork's `monotonic` curated flag. Lagging report
321+
// snapshots in the same dataset can otherwise make the odometer read low.
322+
const MONOTONIC_FIELDS = new Set([
323+
"mileage.value",
324+
"mileage",
325+
]);
326+
318327
/**
319328
* Convert the flat `Data: [{key, dataFieldName, value}]` array into a nested
320329
* object keyed by the dotted dataFieldName, with values typed via the
321-
* dictionary. When the same field name appears multiple times (the portal
322-
* merges several report snapshots into one array), we keep the entry with the
323-
* smallest UUID — same arbitrary-but-stable choice as the HA reference, so a
324-
* sensor consistently tracks the same point across refreshes.
330+
* dictionary.
331+
*
332+
* Tie-break across duplicates of the same dataFieldName:
333+
* - monotonic fields (e.g. mileage.value): pick the LARGEST numeric value.
334+
* The portal mixes several report snapshots into one array; older
335+
* snapshots lag the current odometer by a few km, so the max is the
336+
* truest live reading.
337+
* - everything else: pick the LAST occurrence in the ZIP (array order).
338+
* Per the HA_VAG-EU-Data-Act analysis the portal bundles minute-level
339+
* snapshots in array order, so the last duplicate is the best proxy for
340+
* the freshest value. The earlier "smallest UUID" tie-break was stable
341+
* but arbitrary and consistently picked stale values for mileage.
325342
*/
326343
function normalizeDataset(payload) {
327344
const dictionary = loadDictionary();
328345
const points = {};
346+
let seq = 0;
329347
for (const item of payload.Data || []) {
330348
if (!item || !item.key) continue;
331349
const meta = dictionary[item.key] || {};
332350
const fieldName = item.dataFieldName || meta.name || item.key;
333351
if (!fieldName) continue;
352+
const sequence = seq++;
334353
if (!points[fieldName]) {
335-
points[fieldName] = { keys: [item.key], item, meta };
354+
points[fieldName] = { keys: [item.key], item, meta, sequence };
336355
continue;
337356
}
338357
const cur = points[fieldName];
339358
cur.keys.push(item.key);
340-
if (item.key < cur.item.key) {
359+
if (MONOTONIC_FIELDS.has(fieldName)) {
360+
// Largest numeric value wins; sequence is only a tie-breaker for
361+
// equal values.
362+
const a = parseFloat(item.value);
363+
const b = parseFloat(cur.item.value);
364+
const aNum = Number.isFinite(a);
365+
const bNum = Number.isFinite(b);
366+
let take = false;
367+
if (aNum && !bNum) take = true;
368+
else if (aNum && bNum && a > b) take = true;
369+
else if (aNum && bNum && a === b && sequence > cur.sequence) take = true;
370+
if (take) {
371+
cur.item = item;
372+
cur.meta = meta;
373+
cur.sequence = sequence;
374+
}
375+
} else if (sequence > cur.sequence) {
376+
// Last-occurrence wins.
341377
cur.item = item;
342378
cur.meta = meta;
379+
cur.sequence = sequence;
343380
}
344381
}
345382

0 commit comments

Comments
 (0)