refactor: stabilize cumulative mileage metrics

This commit is contained in:
kkfluous
2026-08-07 15:56:39 +08:00
parent 8c6a3ad831
commit 686fc4e2e2
6 changed files with 50 additions and 6 deletions
+3 -3
View File
@@ -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'));
+3 -2
View File
@@ -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);
});
+13
View File
@@ -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);
}