feat(pure): summary with today/week counts and per-day rate
This commit is contained in:
+38
@@ -65,3 +65,41 @@ export function pruneHistory(entries, now = Date.now()) {
|
|||||||
const cutoff = now - THIRTY_DAYS_MS;
|
const cutoff = now - THIRTY_DAYS_MS;
|
||||||
return entries.filter((e) => e.ts > cutoff);
|
return entries.filter((e) => e.ts > cutoff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarise training history.
|
||||||
|
*
|
||||||
|
* @param {{ts:number, delta:number}[]} entries
|
||||||
|
* @param {number} [now] Reference time in ms (defaults to Date.now()).
|
||||||
|
* @returns {{trainsToday:number, sevenDayAvgPerDay:number, perDay:number}}
|
||||||
|
*/
|
||||||
|
export function summary(entries, now = Date.now()) {
|
||||||
|
if (entries.length === 0) {
|
||||||
|
return { trainsToday: 0, sevenDayAvgPerDay: 0, perDay: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ONE_DAY = MS_PER_DAY;
|
||||||
|
const SEVEN_DAYS = 7 * MS_PER_DAY;
|
||||||
|
|
||||||
|
const todayCutoff = now - ONE_DAY;
|
||||||
|
const weekCutoff = now - SEVEN_DAYS;
|
||||||
|
|
||||||
|
let trainsToday = 0;
|
||||||
|
let trainsWeek = 0;
|
||||||
|
let latestDelta = 0;
|
||||||
|
let latestTs = -Infinity;
|
||||||
|
|
||||||
|
for (const e of entries) {
|
||||||
|
if (e.ts >= todayCutoff) trainsToday++;
|
||||||
|
if (e.ts >= weekCutoff) trainsWeek++;
|
||||||
|
if (e.ts > latestTs) {
|
||||||
|
latestTs = e.ts;
|
||||||
|
latestDelta = e.delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sevenDayAvgPerDay = trainsWeek / 7;
|
||||||
|
const perDay = latestDelta > 0 ? Math.floor(sevenDayAvgPerDay * latestDelta) : 0;
|
||||||
|
|
||||||
|
return { trainsToday, sevenDayAvgPerDay, perDay };
|
||||||
|
}
|
||||||
|
|||||||
+46
-1
@@ -1,6 +1,6 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { parseTarget, computeEstimate, pruneHistory, MS_PER_DAY } from '../src/pure.js';
|
import { parseTarget, computeEstimate, pruneHistory, summary, MS_PER_DAY } from '../src/pure.js';
|
||||||
|
|
||||||
const DAY = MS_PER_DAY;
|
const DAY = MS_PER_DAY;
|
||||||
const NOW = 1_700_000_000_000; // fixed reference
|
const NOW = 1_700_000_000_000; // fixed reference
|
||||||
@@ -94,3 +94,48 @@ test('pruneHistory: does not mutate input', () => {
|
|||||||
test('pruneHistory: empty input', () => {
|
test('pruneHistory: empty input', () => {
|
||||||
assert.deepEqual(pruneHistory([], NOW), []);
|
assert.deepEqual(pruneHistory([], NOW), []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('summary: empty history returns zeros', () => {
|
||||||
|
const s = summary([], NOW);
|
||||||
|
assert.equal(s.trainsToday, 0);
|
||||||
|
assert.equal(s.sevenDayAvgPerDay, 0);
|
||||||
|
assert.equal(s.perDay, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summary: counts trains within last 24h', () => {
|
||||||
|
const entries = [
|
||||||
|
{ ts: NOW - 1 * 60_000, delta: 100 }, // 1 min ago
|
||||||
|
{ ts: NOW - 12 * 3_600_000, delta: 100 }, // 12h ago
|
||||||
|
{ ts: NOW - 23 * 3_600_000, delta: 100 }, // 23h ago
|
||||||
|
{ ts: NOW - 25 * 3_600_000, delta: 100 }, // 25h ago (not today)
|
||||||
|
];
|
||||||
|
const s = summary(entries, NOW);
|
||||||
|
assert.equal(s.trainsToday, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summary: 7-day average is total/7', () => {
|
||||||
|
// 14 entries in last 7 days (spaced 12h apart, all within window)
|
||||||
|
const entries = [];
|
||||||
|
for (let i = 0; i < 14; i++) {
|
||||||
|
entries.push({ ts: NOW - (i / 2) * DAY, delta: 100 });
|
||||||
|
}
|
||||||
|
const s = summary(entries, NOW);
|
||||||
|
assert.equal(s.sevenDayAvgPerDay, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summary: perDay uses most recent delta when available', () => {
|
||||||
|
const entries = [
|
||||||
|
{ ts: NOW - 1 * 60_000, delta: 247 },
|
||||||
|
{ ts: NOW - 12 * 3_600_000, delta: 300 },
|
||||||
|
];
|
||||||
|
const s = summary(entries, NOW);
|
||||||
|
// 2 trains in last 7 days → 2/7 avg
|
||||||
|
// perDay = avg * most recent delta = (2/7) * 247 = ~70.57
|
||||||
|
assert.equal(s.perDay, Math.floor((2 / 7) * 247));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summary: perDay is 0 when no recent delta', () => {
|
||||||
|
const entries = [{ ts: NOW - 31 * DAY, delta: 100 }];
|
||||||
|
const s = summary(entries, NOW);
|
||||||
|
assert.equal(s.perDay, 0);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user