mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
refactor(insights): fold Math.min/max spreads in the six status helpers
`bmi`, `pulse`, `general`, `weight`, `blood-pressure`, and `mood` status helpers all built their `summarizeSeries` block with `Math.min(...series.map(...))` and `Math.max(...series.map(...))`. The same anti-pattern hit `summarize()` in `src/lib/analytics/trends.ts` on 2026-05-16 and crashed `/api/analytics` with `RangeError: Maximum call stack size exceeded` once an Apple-Health-synced PULSE series outgrew V8's ~125 000-arg ceiling; `61107e0c` folded that one. The six insight helpers are fed bounded windows today so they never reached the ceiling, but the spread still allocates a transient args array on every call and the fold is both stack-safe and cheaper. Replace each `Math.min(...) / Math.max(...) / average(...)` triplet with a single walk that maintains running sum / min / max. The now-unused `average()` helper is dropped from each file. Pinned by the existing per-helper unit tests (326 passing in `src/lib/insights` / `src/lib/analytics` / `src/app/api/analytics`).
This commit is contained in:
1 parent
13b2c13caf
commit
b5060f1422
6 files changed
+109
-47
No files matched your search
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user