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:
+27
-8
@@ -47,7 +47,7 @@ suite("today is left out, being half-eaten");
|
||||
// down; the fit should not see it at all.
|
||||
const t = app.foodTrend(days([400, 400, 400, 400, 50]));
|
||||
ok(t, "four complete days are enough");
|
||||
eq(Math.round(t.perWeek), 0, "a flat run stays flat despite today being low");
|
||||
eq(Math.round(t.change), 0, "a flat run stays flat despite today being low");
|
||||
eq(t.last, 3, "the line stops at the last complete day, not at today");
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ suite("it reports a direction only when the climb beats the scatter");
|
||||
{
|
||||
const rising = app.foodTrend(days([200, 250, 300, 350, 400, 450, 0]));
|
||||
ok(rising.clear, "a clean climb is reported");
|
||||
eq(Math.round(rising.perWeek), 350, "…at 50 g a day, which is 350 g a week");
|
||||
eq(Math.round(rising.change), 250, "…as the move across the five days it fitted, 200 g to 450 g");
|
||||
|
||||
const falling = app.foodTrend(days([450, 400, 350, 300, 250, 200, 0]));
|
||||
ok(falling.clear, "a clean fall is reported");
|
||||
ok(falling.perWeek < 0, "…with a negative weekly change");
|
||||
ok(falling.change < 0, "…with a negative change");
|
||||
|
||||
// Same mean, no direction, plenty of noise: the honest answer is "steady".
|
||||
const noisy = app.foodTrend(days([200, 500, 210, 480, 190, 520, 0]));
|
||||
@@ -83,20 +83,39 @@ suite("marked days are skipped without shifting the line");
|
||||
// The middle day is marked; the rest describe a clean 50 g/day climb. The fit
|
||||
// must ignore the hatch rather than reading it as a day of zero grams.
|
||||
const t = app.foodTrend(days([200, 250, 0, 350, 400, 450, 0], { excluded: [2] }));
|
||||
eq(Math.round(t.perWeek), 350, "the climb is unchanged by the marked day");
|
||||
eq(Math.round(t.change), 250, "the climb is unchanged by the marked day");
|
||||
ok(t.clear, "…and it is still clear, not drowned by a false zero");
|
||||
}
|
||||
|
||||
suite("what the sentence is allowed to say");
|
||||
{
|
||||
const say = (grams, opts) => words.foodTrendSentence(app.foodTrend(days(grams, opts)), 14);
|
||||
const say = (grams, opts, win = 14) => words.foodTrendSentence(app.foodTrend(days(grams, opts)), win);
|
||||
|
||||
const rising = say([200, 250, 300, 350, 400, 450, 0]);
|
||||
ok(/up about 350 g a week/.test(rising), "a clear climb gives the rate");
|
||||
ok(/200 g a day then/.test(rising) && /450 g a day now/.test(rising),
|
||||
"…and the figures at each end of the line, so it is not only a rate");
|
||||
ok(/up about 250 g/.test(rising), "a clear climb gives the size of the move");
|
||||
ok(/from roughly 200 g a day to 450 g/.test(rising), "…and the figures at each end");
|
||||
ok(/the last 14 days/.test(rising), "…named against the window it was fitted over");
|
||||
|
||||
// The defect this replaced: the move was quoted per week while the fit spans
|
||||
// at most five days on a 7-day window, so the figure and the two endpoints
|
||||
// disagreed and a reader who subtracted them found the sentence wrong.
|
||||
// Whatever the window, the three numbers in the sentence must reconcile.
|
||||
for (const [label, grams, win] of [
|
||||
["a steep 7-day fall", [460, 425, 390, 355, 320, 285, 0], 7],
|
||||
["a long 14-day climb", [200, 220, 240, 260, 280, 300, 320, 340, 360, 380, 400, 420, 440, 0], 14],
|
||||
["a gentle 30-day climb", [...Array(29).fill(0).map((_, i) => 300 + i * 12), 0], 30],
|
||||
]) {
|
||||
const s = say(grams, undefined, win);
|
||||
const m = s.match(/about (\d+) g — from roughly (\d+) g a day to (\d+) g/);
|
||||
ok(m, `${label}: the sentence has all three figures`);
|
||||
if (m) {
|
||||
const [, moved, from, to] = m.map(Number);
|
||||
eq(moved, Math.abs(to - from), `${label}: the move is exactly the difference of the two ends`);
|
||||
ok(new RegExp(`is ${to > from ? "up" : "down"} about`).test(s),
|
||||
`${label}: and the direction matches which end is larger`);
|
||||
}
|
||||
}
|
||||
|
||||
const steady = say([300, 302, 298, 301, 299, 300, 0]);
|
||||
ok(/roughly steady/.test(steady), "a flat run is called steady");
|
||||
ok(/averaging about 300 g a day/.test(steady),
|
||||
|
||||
Reference in New Issue
Block a user