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.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// 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");
|
||||
+28
-9
@@ -1997,9 +1997,12 @@
|
||||
};
|
||||
const totalOf = (pts) => pts[pts.length - 1].y;
|
||||
|
||||
const today = curveFor(dayStartTs(0), isToday ? Date.now() : null);
|
||||
// Same as the sleep trend: a day that doesn't count is dropped as a
|
||||
// comparison rather than drawn flat at zero.
|
||||
// Left off entirely when the day is marked "not counted" — same reasoning
|
||||
// as the sleep trend: it is already out of the average and out of
|
||||
// "yesterday", so drawing it as the boldest line would contradict that.
|
||||
const dayExcluded = isExcluded(day);
|
||||
const today = dayExcluded ? null : curveFor(dayStartTs(0), isToday ? Date.now() : null);
|
||||
// Same rule for the comparison day: dropped rather than drawn flat at zero.
|
||||
const prev = curveFor(dayStartTs(1));
|
||||
const yesterday = (!isExcluded(dayAgo(1)) && totalOf(prev) > 0) ? prev : null;
|
||||
|
||||
@@ -2023,7 +2026,7 @@
|
||||
const fmtDay = (daysAgo) =>
|
||||
new Date(dayStartTs(daysAgo)).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
||||
return {
|
||||
today, yesterday, avg, avgDays,
|
||||
today, yesterday, avg, avgDays, dayExcluded,
|
||||
dayLabel: isToday ? "Today" : fmtDay(0),
|
||||
prevDayLabel: isToday ? "Yesterday" : fmtDay(1),
|
||||
};
|
||||
@@ -2086,7 +2089,11 @@
|
||||
|
||||
const chip = (id) => document.getElementById(id);
|
||||
const mins = (pts) => `${Math.round(pts[pts.length - 1].y)} min`;
|
||||
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}`;
|
||||
// No line for a day that doesn't count, so no chip for it either.
|
||||
chip("legend-wtrend-today").hidden = !curves.today;
|
||||
if (curves.today) {
|
||||
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}`;
|
||||
}
|
||||
|
||||
const yLegend = chip("legend-wtrend-yesterday");
|
||||
yLegend.hidden = !curves.yesterday;
|
||||
@@ -2239,7 +2246,14 @@
|
||||
};
|
||||
|
||||
// A past day is complete, so its curve runs the full 24h uncapped.
|
||||
const today = curveFor(dayStartTs(0), isToday ? Date.now() : null);
|
||||
//
|
||||
// Unless the day is marked "not counted", in which case it is left off the
|
||||
// chart entirely. It is already out of the average and out of "yesterday",
|
||||
// and drawing it as the headline curve would put the one day you have said
|
||||
// not to trust in the boldest line on the panel. What remains is the
|
||||
// references — which is what you would want to see on a day like that.
|
||||
const dayExcluded = isExcluded(day);
|
||||
const today = dayExcluded ? null : curveFor(dayStartTs(0), isToday ? Date.now() : null);
|
||||
|
||||
// A day that doesn't count is no comparison at all, so it is dropped
|
||||
// outright rather than drawn as a flat line at zero. Checked explicitly
|
||||
@@ -2275,7 +2289,7 @@
|
||||
// No history → no average → no projection. Past days are already complete,
|
||||
// so there is nothing to project.
|
||||
let projected = null;
|
||||
if (avg && isToday) {
|
||||
if (avg && isToday && today) {
|
||||
const nowPt = today[today.length - 1];
|
||||
const avgAt = (x) => {
|
||||
const lo = Math.floor(x);
|
||||
@@ -2295,7 +2309,7 @@
|
||||
const dayLabel = isToday ? "Today" : fmtDay(0);
|
||||
const prevDayLabel = isToday ? "Yesterday" : fmtDay(1);
|
||||
|
||||
return { today, yesterday, avg, avgDays, projected, dayLabel, prevDayLabel };
|
||||
return { today, yesterday, avg, avgDays, projected, dayLabel, prevDayLabel, dayExcluded };
|
||||
}
|
||||
|
||||
function drawSleepTrendChart(curves, target) {
|
||||
@@ -2392,7 +2406,12 @@
|
||||
// write each curve's slept-hours total into its chip.
|
||||
const chip = (id) => document.getElementById(id);
|
||||
const hrs = (pts) => `${pts[pts.length - 1].y.toFixed(1)}h`;
|
||||
chip("legend-trend-today-text").textContent = `${curves.dayLabel} ${hrs(curves.today)}`;
|
||||
// No curve for a day that doesn't count, so no chip for it either — a
|
||||
// legend entry pointing at a line that isn't drawn is worse than none.
|
||||
chip("legend-trend-today").hidden = !curves.today;
|
||||
if (curves.today) {
|
||||
chip("legend-trend-today-text").textContent = `${curves.dayLabel} ${hrs(curves.today)}`;
|
||||
}
|
||||
const yLegend = chip("legend-trend-yesterday");
|
||||
yLegend.hidden = !curves.yesterday;
|
||||
if (curves.yesterday) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[
|
||||
{ "date": "2026-09-21", "text": "A day marked “not counted” no longer appears in the Sleep trend or the Walk trend. It was already left out of the average and out of the “yesterday” comparison, but the day you were actually looking at was still drawn as the boldest line on the chart — so the one day you had said not to trust was the one the panel led with. Now it is left off and its legend chip goes with it, leaving the average and yesterday, which is what you would want to see on a day like that" },
|
||||
{ "date": "2026-09-21", "text": "The Food (grams) chart has a trend line through it now, so you can see whether he is eating more as he grows — the daily bars bounce around enough to hide a steady climb. A line under the chart says what it amounts to in figures: “daily intake is up about 40 g a week — roughly 280 g a day then, 400 g a day now”. When the day-to-day variation is bigger than any trend, which is most of the time over a short window, it says so and gives the average instead — that is a real measurement, where the ends of the line would only be the line's own guess. Today is left out of the line, since the day isn't finished and including it would drag the line down every morning; days marked “not counted” are skipped too. The line follows the 7 / 14 / 30 day picker like the rest of the charts, and the sentence names the window so you can see it change when you switch. If there aren't four complete days to fit it says so rather than leaving you with an empty chart, and if some meals have no amount recorded it says how many, because those days read lower than they really were" },
|
||||
{ "date": "2026-09-20", "text": "Fixed the page being wider than the screen on a phone, which is why it had started letting you zoom out. The month grid behind the date was the main culprit: it was centred on the date button, which sits near the right edge, so part of the panel hung off the side of the screen. It is anchored to the edge of the bar now and stays on screen at any width. Also fixed a long unbroken word — a link, or something copied off a food bag — in a history note, an exercise name or its instructions pushing its row wider than the screen instead of wrapping" },
|
||||
{ "date": "2026-09-20", "text": "Fixed three things that went wrong around a day marked “not counted”. The Timing panel measured “how long since the last pee” from before the marked day rather than from the actual last one, so the marker sat far out to the right. The Sleep and Walk trends drew the marked day's own curve as a flat zero when you were looking at that day — marking a day means don't let it drag the average, not pretend nothing happened on it. And a nap that started on a marked day and ended the next morning vanished from that next day's figures, even though the next day wasn't marked and the puppy really did sleep those hours" },
|
||||
|
||||
+2
-2
@@ -311,7 +311,7 @@
|
||||
<h2>Sleep trend</h2>
|
||||
<svg id="chart-sleep-trend" class="chart-svg" viewBox="0 0 320 220" role="img" aria-label="Cumulative sleep hours through the selected day, the day before it, the recent average and (for today) the projected end-of-day total, with the age-based sleep goal band"></svg>
|
||||
<div class="legend">
|
||||
<span class="lg trend-today"><span class="sw"></span><span id="legend-trend-today-text">Today</span></span>
|
||||
<span class="lg trend-today" id="legend-trend-today"><span class="sw"></span><span id="legend-trend-today-text">Today</span></span>
|
||||
<span class="lg trend-projected" id="legend-trend-projected" hidden><span class="sw"></span><span id="legend-trend-projected-text">Projected</span></span>
|
||||
<span class="lg trend-yesterday" id="legend-trend-yesterday"><span class="sw"></span><span id="legend-trend-yesterday-text">Yesterday</span></span>
|
||||
<span class="lg trend-avg" id="legend-trend-avg"><span class="sw"></span><span id="legend-trend-avg-text">7-day avg</span></span>
|
||||
@@ -346,7 +346,7 @@
|
||||
<h2>Walk trend</h2>
|
||||
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, and the recent average"></svg>
|
||||
<div class="legend">
|
||||
<span class="lg wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
|
||||
<span class="lg wtrend-today" id="legend-wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
|
||||
<span class="lg wtrend-yesterday" id="legend-wtrend-yesterday"><span class="sw"></span><span id="legend-wtrend-yesterday-text">Yesterday</span></span>
|
||||
<span class="lg wtrend-avg" id="legend-wtrend-avg"><span class="sw"></span><span id="legend-wtrend-avg-text">7-day avg</span></span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user