Give the food trend's figures, not only its rate
"Up about 40 g a week" is a rate with nothing to anchor it: it says the line slopes without saying where it sits. The sentence now names both ends of the fit — "roughly 280 g a day then, 400 g a day now" — which is the reading anyone actually wants from a growth chart. When the fit is not trustworthy it quotes the average instead, and says the day-to-day variation is larger than any trend. That difference is the point. The average is a measurement and survives the noise; the ends of the line are the line's own output, and quoting them on a fit nobody should read would dress a guess up as a reading. Everything rounds to 10 g for the same reason — "287 g a day" would be false precision from four noisy points. The wording moves into a pure foodTrendSentence() so those rules can be checked, which is worth doing precisely because they are judgement rather than arithmetic: the checks now pin that a clear climb gives both figures, a flat run gives the average and no endpoints, a see-saw gives neither, and nothing is ever quoted to the gram.
This commit is contained in:
+34
-21
@@ -1713,30 +1713,43 @@
|
||||
// Says what the line means, and what it cannot mean. Kept in words under the
|
||||
// chart rather than as a figure on it: "up 40 g a week" is a claim, and it
|
||||
// needs the room to be qualified.
|
||||
function renderFoodTrendNote(days, trend) {
|
||||
const note = document.getElementById("grams-note");
|
||||
if (!note) return;
|
||||
const lines = [];
|
||||
// The window comes from the 7/14/30 picker, and naming it is the only way
|
||||
// the reader can tell that switching it changed the answer — the line
|
||||
// itself often moves too little to notice.
|
||||
const window = `the last ${chartDays()} days`;
|
||||
|
||||
// What the line is allowed to claim, in words. Separated from the drawing
|
||||
// because this is where the judgement lives: a straight line through noisy
|
||||
// points always has a slope, and stating it as a fact is how a chart starts
|
||||
// lying. Kept pure so the rules can be checked.
|
||||
//
|
||||
// Figures are rounded to 10 g. The fitted endpoints are model output, not
|
||||
// measurements — quoting "287 g" would dress a guess up as a reading.
|
||||
function foodTrendSentence(trend, windowDays) {
|
||||
const window = `the last ${windowDays} days`;
|
||||
if (!trend) {
|
||||
// Says why there is no line. Without this the chart looks broken on a
|
||||
// short window, or on one where most days are marked.
|
||||
lines.push(`Not enough complete days in ${window} to draw a trend — it needs four, and today doesn't count until it's over.`);
|
||||
} else {
|
||||
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;
|
||||
if (!trend.clear || slight) {
|
||||
lines.push(`Over ${window}, daily intake is roughly steady — day-to-day variation is larger than any trend.`);
|
||||
} else {
|
||||
lines.push(`Over ${window}, daily intake is ${trend.perWeek > 0 ? "up" : "down"} about ${perWeek} g a week.`);
|
||||
}
|
||||
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;
|
||||
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.`;
|
||||
}
|
||||
|
||||
function renderFoodTrendNote(days, trend) {
|
||||
const note = document.getElementById("grams-note");
|
||||
if (!note) return;
|
||||
// The window comes from the 7/14/30 picker, and naming it is the only way
|
||||
// the reader can tell that switching it changed the answer — the line
|
||||
// itself often moves too little to notice.
|
||||
const lines = [foodTrendSentence(trend, chartDays())];
|
||||
|
||||
const missing = days.reduce((s, d) => s + (d.mealsMissingGrams || 0), 0);
|
||||
if (missing > 0) {
|
||||
@@ -1745,7 +1758,7 @@
|
||||
}
|
||||
|
||||
note.textContent = lines.join(" ");
|
||||
note.hidden = lines.length === 0;
|
||||
note.hidden = false; // there is always a sentence now, even if it is "no trend"
|
||||
}
|
||||
|
||||
// Minutes walked per day. Hidden until there's a walk to show, like the
|
||||
|
||||
Reference in New Issue
Block a user