Stop the food trend's figures contradicting each other

The caption read like "down about 329 g a week — roughly 460 g a day then,
320 g a day now". Subtract the two amounts and you get 140 g, not 329. The
arithmetic behind it was self-consistent, but the sentence was not, and a
caption a reader can disprove by subtracting its own numbers is wrong whatever
the code was doing.

The rate was slope x 7, while the line only covers the complete days in the
window. Today is never fitted, being unfinished, so a 7-day window leaves at
most five days and any marked day takes another — in this case about three.
The rate was therefore stretched well past the days it was measured from, and
the two endpoints, which were not, could never agree with it.

It quotes the change between the two ends now, which is the one figure a reader
can check: "down about 140 g — from roughly 460 g a day to 320 g." The change
is derived from the rounded ends rather than from the slope, so the subtraction
works exactly rather than to within the rounding.

The weekly rate goes rather than being repaired. It cannot reconcile on a short
window, and it only ever meant anything where the fit spanned a week or more —
which is not something to leave as a trap for whichever window the reader
happens to have picked.

The check that let this through asserted the sentence contained certain
phrases, not that its numbers agreed with each other. There is now one that
parses all three figures back out and asserts the move is exactly the
difference of the ends, across each window length; it fails against the old
wording, which is the only evidence worth having that it would have caught this.
This commit is contained in:
Alexander Heldt
2026-09-21 21:01:27 +00:00
parent 556e4d75a8
commit 9e47aa53ff
3 changed files with 44 additions and 17 deletions
+15 -8
View File
@@ -1765,7 +1765,11 @@
return {
at: (i) => slope * i + intercept,
first, last,
perWeek: slope * 7, // change in a day's intake from one week to the next
// How much the daily figure moved across the days actually fitted. Not a
// per-week rate: on a 7-day window today is never fitted, so the span is
// at most five days and a weekly figure would be extrapolated past the
// data — leaving a sentence whose own endpoints contradicted it.
change: slope * (last - first),
mean: my,
clear: rise > residualSD, // the climb outruns the scatter
};
@@ -1858,19 +1862,22 @@
return `Not enough complete days in ${window} to draw a trend — it needs four, and today doesn't count until it's over.`;
}
const round10 = (v) => Math.round(Math.max(0, v) / 10) * 10;
const perWeek = Math.round(Math.abs(trend.perWeek));
// Under a twentieth of a typical day, a week apart, is not a trend anyone
// could act on, whatever the arithmetic says.
const slight = perWeek < 5 || perWeek < trend.mean * 0.05;
// Under a twentieth of a typical day is not a move anyone could act on,
// whatever the arithmetic says.
const slight = Math.abs(trend.change) < 5 || Math.abs(trend.change) < trend.mean * 0.05;
if (!trend.clear || slight) {
// The average is a real measurement and survives the noise; the fitted
// endpoints would not, so they are not quoted here.
return `Over ${window}, daily intake is roughly steady, averaging about ` +
`${round10(trend.mean)} g a day — day-to-day variation is larger than any trend.`;
}
return `Over ${window}, daily intake is ${trend.perWeek > 0 ? "up" : "down"} about ` +
`${perWeek} g a week — roughly ${round10(trend.at(trend.first))} g a day then, ` +
`${round10(trend.at(trend.last))} g a day now.`;
// The change is derived from the *rounded* ends rather than from the slope,
// so that subtracting the two figures on screen gives exactly the figure
// quoted. A reader who checks the arithmetic has to find it correct.
const from = round10(trend.at(trend.first));
const to = round10(trend.at(trend.last));
return `Over ${window}, daily intake is ${to > from ? "up" : "down"} about ` +
`${Math.abs(to - from)} g — from roughly ${from} g a day to ${to} g.`;
}
function renderFoodTrendNote(days, trend) {