How to fetch the battery capacity as defined in smartshunt-settings?

Version 1.3, very small change on line 93, increased testwindow: “Match = absDelta < 0.055555;”
Surely there are opportunities to improve on the rounding and selection mechanism (that discards ‘unfitting’ capacity candidates) but this works perfectly for us. The correct capacity is found within a handful of value updates and when I change the capacity in the BMV manually, it automatically re-aligns with the new values as well.

// Battery Capacity Estimator: Async SoC and ConsumedAh to Bound Intersection
// Receives msg.topic 'soc' (payload float 0.0-100.0 in 0.1 increments) or 'consumedah' (payload float <=0.0 in 0.1 increments)
// Computes capacity bounds for each pair, intersects to converge on actual integer capacity

var store = flow.get('storeahsoc') || {
    ahsoc: undefined,
    capacity: undefined,
    latestSoc: undefined,
    latestConsumed: undefined,
    minCapacity: 100,  // Initial wide range min
    maxCapacity: 3000, // Initial wide range max
    misMatches: 0,
    capacityStats: {},
    debugStats: {}
};

// debug values
var rawSoc = null;
var rawConsumed = null;
var rawminCapacity = null;
var rawmaxCapacity = null;
var rawexpectedSoc = null;
var rawMatch = null;
var rawDelta = null;
var rawpairMin = null;
var rawpairMax = null;
var rawahSoc = null;

// Optional reset on topic='reset'
if (msg.topic === 'reset') {
    store.minCapacity = 100;
    store.maxCapacity = 3000;
    store.misMatches = 0;
    flow.set('storeahsoc', store);
    node.status({fill: 'red', shape: 'dot', text: 'topic reset'});
    return null;
}

rawSoc = store.latestSoc;
rawConsumed = store.latestConsumed;
// Update based on topic
if (msg.topic === 'soc') {
    var newSoc = Math.round(1000 * msg.payload) / 1000;
    rawSoc = newSoc;
    if ( newSoc === store.latestSoc) {
        return null; // Ignore duplicate value
    }
    store.latestSoc = newSoc;
    flow.set('storeahsoc', store); // Persist immediately
} else if (msg.topic === 'consumedah') {
    var newConsumed =  Math.round(1000 * msg.payload) / 1000;
    rawConsumed = newConsumed;
    if ( newConsumed === store.latestConsumed) {
        return null; // Ignore duplicate value
    }
    store.latestConsumed = newConsumed;
    flow.set('storeahsoc', store); // Persist immediately
} else {
    node.status({fill: 'red', shape: 'dot', text: 'wait1'});
    return null; // Ignore invalid topic
}

// Compute only if both present
if (store.latestSoc === undefined || store.latestConsumed === undefined) {
    node.status({fill: 'red', shape: 'dot', text: 'wait2'});
    return null;
}
if (store.latestSoc <= 0 || store.latestSoc >= 100) {
    node.status({fill: 'red', shape: 'dot', text: 'wait3'});
    return null; // Skip div-zero or invalid
}

// Compute bounds for this pair
var denom = 1 - (store.latestSoc / 100);
if (denom <= 0) {
    node.status({fill: 'red', shape: 'dot', text: 'wait4'});
    return null; // Redundant div-zero guard
}

// Linear search for consistent capacities
rawminCapacity = store.minCapacity;
rawmaxCapacity = store.maxCapacity;
var Match = false;
var pairMin = store.maxCapacity;
var pairMax = store.minCapacity;
for ( var c = store.minCapacity; c <= store.maxCapacity; c++ ) {
    rawexpectedSoc = 100 * ( 1 + store.latestConsumed / c );
    rawexpectedSoc = Math.round(1000 * rawexpectedSoc ) / 1000;
    rawDelta = rawexpectedSoc - store.latestSoc;
    rawDelta =  Math.round(1000 * rawDelta) / 1000;
    var expectedSoC = Math.round(10 * rawexpectedSoc ) / 10;
    var absDelta = Math.abs( rawDelta );
    Match = absDelta < 0.055555;
    if ( Match ) {
        pairMin = Math.min(pairMin, c);
        pairMax = Math.max(pairMax, c);
    }
}
rawMatch = Match;
rawpairMin = pairMin;
rawpairMax = pairMax;

// No match found on a single pair
if ( !Match && ( pairMin == pairMax ) ) {
    node.warn('No Match single pair, widen range');
    node.status({fill: 'red', shape: 'dot', text: 'nomatch single'});
    pairMin = pairMin - 1;
//    pairMax = pairMax + 1;
}

// Reset on inversion
if ( pairMin > pairMax ) {
    node.warn('pairMin > pairMax, Skip invalid pair');
    node.status({fill: 'red', shape: 'dot', text: 'skip invalid'});
    return null; // Skip invalid pair
}

// Intersect with global range
store.minCapacity = Math.max( store.minCapacity, pairMin );
store.maxCapacity = Math.min( store.maxCapacity, pairMax );

// Handle inversion as inconsistency (reset)
if ( store.minCapacity > store.maxCapacity ) {
    store.minCapacity = 100;
    store.maxCapacity = 3000;
    store.misMatches = 0;
    node.warn('Inversion detected, resetting bounds');
    node.status({fill: 'red', shape: 'dot', text: 'reset bounds'});
}

// Determine output: capacity if converged, else midpoint
if ( store.minCapacity === store.maxCapacity ) {
    msg.payload = store.minCapacity;
} else if ( store.uniques > 2 ) {
    msg.payload = Math.round( ( store.minCapacity + store.maxCapacity ) / 2 );
} else if ( store.maxCapacity - store.minCapacity > 1 ) {
    msg.payload = Math.round( ( store.minCapacity + store.maxCapacity ) / 2 );
} else {
    msg.payload = store.minCapacity;
}

// High precision ahsoc integration with div-zero guard
store.capacity = msg.payload;
if ( store.capacity !== 0 ) {
    rawahSoc = 100 * (1 + (store.latestConsumed / store.capacity) );
    rawahSoc = Math.round(1000 * rawahSoc) / 1000;
    store.ahsoc = rawahSoc;
} else {
    store.ahsoc = null;
}

// Compute and store stats KPI under store.capacityStats
var statsuniques = store.minCapacity <= store.maxCapacity ? store.maxCapacity - store.minCapacity + 1 : 0
var statsusingFallback = ( statsuniques > 1 );//store.minCapacity !== store.maxCapacity
store.capacityStats = {
    uniques: statsuniques,
    usingFallback: statsusingFallback 
};

// count mismatches
//var calculatedSoC = Math.round( 10 * store.ahsoc ) / 10;
//var misMatch = calculatedSoC !== store.latestSoc
//var misMatch = !Match;
//if ( misMatch && ( statsuniques <= 2 ) ) {
if ( !Match && statsuniques <= 2 ) {
    store.misMatches = store.misMatches + 1;
    if (store.misMatches >= 2) {
        store.minCapacity = Math.round( 0.9 * store.minCapacity );
        store.maxCapacity = Math.round( 1.1 * store.maxCapacity );
        store.misMatches = 0;
        node.warn('2 mismatches, reset');
        node.status({fill: 'red', shape: 'dot', text: 'mismatch reset'});
    }
}
if ( !Match ) {
//if ( true ) {
    store.debugStats = {
        rawSoc: rawSoc,
        rawConsumed: rawConsumed,
        rawminCapacity: rawminCapacity,
        rawmaxCapacity: rawmaxCapacity,
        rawexpectedSoc: rawexpectedSoc,
        rawMatch: rawMatch,
        rawDelta: rawDelta,
        rawpairMin: rawpairMin,
        rawpairMax: rawpairMax,
        rawahSoc: rawahSoc
    }
}

// Persist all state including stats KPI
flow.set('storeahsoc', store);
msg.ahsoc = store.ahsoc;
msg.capacity = store.capacity;
msg.accurate = !store.capacityStats.usingFallback;
msg.payload = msg.ahsoc;

// node status
let text = ( (msg.ahsoc !== undefined) ? ( msg.ahsoc + '% ' + (-rawDelta) +' SoC '+ statsuniques + ' @ '+ msg.capacity + 'Ah'  ) : 'wait')
let fill = ( ( (msg.accurate === true) && Match ) ? 'green' : 'red')
let shape = 'dot'
node.status({fill, shape, text});

return msg;