refactor: stabilize cumulative mileage metrics
This commit is contained in:
@@ -7,7 +7,7 @@ test('returns the published mileage metric contract', async () => {
|
||||
const payload = await response.json();
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(payload.catalogVersion, 5);
|
||||
assert.equal(payload.catalogVersion, 6);
|
||||
assert.deepEqual(payload.domains, ['mileage']);
|
||||
assert.ok(payload.metrics.some((metric: { id: string }) => metric.id === 'mileage.assessment_completion_rate'));
|
||||
});
|
||||
@@ -27,7 +27,7 @@ test('returns the published electric metric contract', async () => {
|
||||
const payload = await response.json();
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(payload.catalogVersion, 5);
|
||||
assert.equal(payload.catalogVersion, 6);
|
||||
assert.deepEqual(payload.domains, ['electric']);
|
||||
assert.ok(payload.metrics.some((metric: { id: string }) => metric.id === 'electric.charge_total_fee'));
|
||||
assert.ok(payload.metrics.some((metric: { id: string }) => metric.id === 'electric.blended_cost_intensity'));
|
||||
@@ -39,7 +39,7 @@ test('returns the published ETC metric contract', async () => {
|
||||
const payload = await response.json();
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(payload.catalogVersion, 5);
|
||||
assert.equal(payload.catalogVersion, 6);
|
||||
assert.deepEqual(payload.domains, ['etc']);
|
||||
assert.ok(payload.metrics.some((metric: { id: string }) => metric.id === 'etc.total_amount'));
|
||||
assert.ok(payload.metrics.some((metric: { id: string }) => metric.id === 'etc.bill_receivable'));
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getManualMonitoringSnapshot,
|
||||
storeManualMonitoringSnapshot,
|
||||
} from './manual-snapshot.js';
|
||||
import { sumMileageKm } from './precision.js';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
@@ -245,9 +246,9 @@ app.get('/', async (c) => {
|
||||
|
||||
const stats = {
|
||||
totalToday: Math.round(filtered.reduce((sum, v) => sum + v.dailyKm, 0)),
|
||||
totalAll: filtered.reduce((sum, v) => sum + (v.totalKm || 0), 0),
|
||||
totalAll: sumMileageKm(filtered.map(v => v.totalKm || 0)),
|
||||
vehicleCount: filtered.length,
|
||||
yesterdayTotal: filtered.reduce((sum, v) => sum + v.yesterdayKm, 0),
|
||||
yesterdayTotal: sumMileageKm(filtered.map(v => v.yesterdayKm)),
|
||||
};
|
||||
|
||||
const sorted = [...filtered].sort((a, b) => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { roundMileageKm, sumMileageKm } from './precision.js';
|
||||
|
||||
test('removes binary floating point tails at the published precision', () => {
|
||||
assert.equal(sumMileageKm([0.1, 0.2]), 0.3);
|
||||
assert.equal(roundMileageKm(44_488_200.300000004), 44_488_200.3);
|
||||
});
|
||||
|
||||
test('rounds after aggregation and ignores invalid values', () => {
|
||||
assert.equal(sumMileageKm([0.04, 0.04]), 0.1);
|
||||
assert.equal(sumMileageKm([1, Number.NaN, Number.POSITIVE_INFINITY]), 1);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
export function roundMileageKm(value: number, decimals = 1): number {
|
||||
if (!Number.isFinite(value)) return 0;
|
||||
const scale = 10 ** decimals;
|
||||
return Math.round((value + Number.EPSILON) * scale) / scale;
|
||||
}
|
||||
|
||||
export function sumMileageKm(values: Iterable<number>, decimals = 1): number {
|
||||
let total = 0;
|
||||
for (const value of values) {
|
||||
if (Number.isFinite(value)) total += value;
|
||||
}
|
||||
return roundMileageKm(total, decimals);
|
||||
}
|
||||
Reference in New Issue
Block a user