Files
puppy-tracker/checks/trend-curves.mjs
T
Alexander Heldt e4a5c3fe29 Leave a day that doesn't count off the trends entirely
The Sleep and Walk trends already kept a day marked "not counted" out of the
window average and out of the "yesterday" comparison. What they still drew was
that day's own curve, when it was the day you had selected — as the boldest
line on the panel. So the single day you had said not to trust was the one the
chart led with, against references that had carefully excluded it.

It is left off now, along with its legend chip and, for the sleep trend, the
projected tail that continued it. What remains is the average and yesterday,
which is what you would want to look at on a day like that.

This reverses part of an earlier fix. That one stopped the curve being drawn as
a flat zero, on the reasoning that marking a day means "don't let it drag the
average" rather than "pretend nothing happened". The flat zero was certainly
wrong, but so was the conclusion: a real curve for an untrusted day is still
the wrong thing to lead with. Absent is the honest third option.

The walk trend gets the same treatment. It is the same panel in different
units, and the two disagreeing about what a marked day means would be worse
than either answer.
2026-09-21 20:44:23 +00:00

78 lines
3.3 KiB
JavaScript

// The sleep and walk trends draw the selected day against yesterday and the
// window average. A day marked "not counted" has to be absent from all three,
// and the one that kept slipping through was the selected day itself — it is
// the boldest line on the panel, so it reads as the answer.
import { load } from "./extract.mjs";
import { suite, eq, ok, report } from "./assert.mjs";
const DAY = 86_400_000;
const SEL = new Date(2026, 8, 20); // the day under the cursor
const at = (day, hour) => new Date(2026, 8, day, hour).getTime();
let excluded = new Set();
let windowDays = 7;
const curves = load({
names: ["startOfDay", "pairWindows", "sleepWindows", "sleepTrendCurves"],
stubs: {
selectedDay: () => SEL,
ymd: (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`,
isExcluded: (d) => excluded.has(d.getDate()),
chartDays: () => windowDays,
},
});
// A night's sleep on each of several days, so every curve has something to draw.
const slept = (day, fromHour, toHour) => ([
{ id: `s${day}a`, type: "sleep-start", at: at(day, fromHour) },
{ id: `s${day}b`, type: "sleep-end", at: at(day, toHour) },
]);
const week = [16, 17, 18, 19, 20].flatMap(d => slept(d, 1, 5));
suite("the selected day's own curve");
{
excluded = new Set();
const c = curves.sleepTrendCurves(week);
ok(c.today && c.today.length > 1, "a normal day is drawn");
eq(c.dayExcluded, false, "…and not flagged as excluded");
excluded = new Set([20]); // the selected day
const m = curves.sleepTrendCurves(week);
eq(m.today, null, "a day marked 'not counted' is not drawn at all");
eq(m.dayExcluded, true, "…and says so, so the legend can drop its chip");
eq(m.projected, null, "…and nothing is projected from a curve that isn't there");
ok(m.avg && m.avg.length > 1, "the average it would have been read against survives");
ok(m.yesterday && m.yesterday.length > 1, "so does yesterday");
}
suite("the comparison day and the average");
{
excluded = new Set([19]); // yesterday, relative to the 20th
const c = curves.sleepTrendCurves(week);
eq(c.yesterday, null, "a marked yesterday is dropped rather than drawn flat");
ok(c.today && c.today.length > 1, "the selected day is unaffected by it");
// Every day but the selected one marked: nothing left to average over.
excluded = new Set([16, 17, 18, 19]);
const none = curves.sleepTrendCurves(week);
eq(none.avg, null, "an average with no days left to average is null, not zero");
ok(none.today && none.today.length > 1, "…and the selected day still draws");
}
suite("a marked day never contributes to the average");
{
// The 19th sleeps far longer than the rest. With it counted the average is
// dragged up; marked, it should leave no trace.
const lopsided = [...[16, 17, 18].flatMap(d => slept(d, 1, 3)), ...slept(19, 1, 23), ...slept(20, 1, 3)];
windowDays = 7;
excluded = new Set();
const withIt = curves.sleepTrendCurves(lopsided).avg[24].y;
excluded = new Set([19]);
const without = curves.sleepTrendCurves(lopsided).avg[24].y;
ok(withIt > without, "marking the outlier lowers the average it was inflating");
eq(Math.round(without), 2, "…back to the two hours the remaining days actually slept");
}
export default report("trend-curves");