diff --git a/src/lib/insights/blood-pressure-status.ts b/src/lib/insights/blood-pressure-status.ts index fde03d4e8..c7f03e84c 100644 --- a/src/lib/insights/blood-pressure-status.ts +++ b/src/lib/insights/blood-pressure-status.ts @@ -57,11 +57,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - if (values.length === 0) return 0; - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -74,14 +69,29 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path; see `.planning/round-v1433-analytics-500-report.md` §"Carry- + // over". These helpers are fed bounded windows today so the crash + // never reached them, but the spread allocates a transient args array + // on every call — the fold is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; } diff --git a/src/lib/insights/bmi-status.ts b/src/lib/insights/bmi-status.ts index c669f932a..af3fe42ef 100644 --- a/src/lib/insights/bmi-status.ts +++ b/src/lib/insights/bmi-status.ts @@ -42,11 +42,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - if (values.length === 0) return 0; - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -59,14 +54,30 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path and is the same anti-pattern fixed in `summarize()` (see + // `.planning/round-v1433-analytics-500-report.md` §"Carry-over"). + // These helpers feed off bounded windows today so they did not crash, + // but the spread allocates a transient args array on every call; + // folding once is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; } diff --git a/src/lib/insights/general-status.ts b/src/lib/insights/general-status.ts index f89abdfaf..e48101dc7 100644 --- a/src/lib/insights/general-status.ts +++ b/src/lib/insights/general-status.ts @@ -51,10 +51,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -63,14 +59,29 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path; see `.planning/round-v1433-analytics-500-report.md` §"Carry- + // over". These helpers are fed bounded windows today so the crash + // never reached them, but the spread allocates a transient args array + // on every call — the fold is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; } diff --git a/src/lib/insights/mood-status.ts b/src/lib/insights/mood-status.ts index ad413d742..f8acfb72f 100644 --- a/src/lib/insights/mood-status.ts +++ b/src/lib/insights/mood-status.ts @@ -49,11 +49,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - if (values.length === 0) return 0; - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -66,14 +61,29 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path; see `.planning/round-v1433-analytics-500-report.md` §"Carry- + // over". These helpers are fed bounded windows today so the crash + // never reached them, but the spread allocates a transient args array + // on every call — the fold is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; } diff --git a/src/lib/insights/pulse-status.ts b/src/lib/insights/pulse-status.ts index a206b4deb..a5a5887ac 100644 --- a/src/lib/insights/pulse-status.ts +++ b/src/lib/insights/pulse-status.ts @@ -48,11 +48,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - if (values.length === 0) return 0; - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -65,14 +60,29 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path; see `.planning/round-v1433-analytics-500-report.md` §"Carry- + // over". These helpers are fed bounded windows today so the crash + // never reached them, but the spread allocates a transient args array + // on every call — the fold is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; } diff --git a/src/lib/insights/weight-status.ts b/src/lib/insights/weight-status.ts index b8e1be4ec..4f0a72adf 100644 --- a/src/lib/insights/weight-status.ts +++ b/src/lib/insights/weight-status.ts @@ -52,11 +52,6 @@ function round(value: number, digits = 1): number { return Math.round(value * factor) / factor; } -function average(values: number[]): number { - if (values.length === 0) return 0; - return values.reduce((sum, value) => sum + value, 0) / values.length; -} - function normalizeSummaryText(value: string): string { return stripChartTokens(value).replace(/\s+/g, " ").trim(); } @@ -69,14 +64,29 @@ function summarizeSeries(series: Array<{ value: number }>) { if (series.length === 0) return null; const first = series[0].value; const last = series[series.length - 1].value; + // v1.4.33 — fold sum/min/max into a single walk. The previous + // `Math.min(...series.map(...))` / `Math.max(...series.map(...))` + // spread tripped V8's ~125 000-arg ceiling on the bound /api/analytics + // path; see `.planning/round-v1433-analytics-500-report.md` §"Carry- + // over". These helpers are fed bounded windows today so the crash + // never reached them, but the spread allocates a transient args array + // on every call — the fold is both stack-safe and cheaper. + let sum = 0; + let minVal = series[0].value; + let maxVal = series[0].value; + for (const entry of series) { + sum += entry.value; + if (entry.value < minVal) minVal = entry.value; + if (entry.value > maxVal) maxVal = entry.value; + } return { points: series.length, start: round(first, 2), end: round(last, 2), delta: round(last - first, 2), - mean: round(average(series.map((entry) => entry.value)), 2), - min: round(Math.min(...series.map((entry) => entry.value)), 2), - max: round(Math.max(...series.map((entry) => entry.value)), 2), + mean: round(sum / series.length, 2), + min: round(minVal, 2), + max: round(maxVal, 2), }; }