правки по графикам
Gitea Actions Demo / build_and_push (push) Successful in 45s Details

This commit is contained in:
artem 2026-09-12 18:17:14 +03:00
parent 2cfd863dbf
commit f573aff39c
1 changed files with 62 additions and 0 deletions

View File

@ -0,0 +1,62 @@
/**
* Computes Normalized Power (NP) over a slice of a workout, matching the
* canonical Strava/TrainingPeaks algorithm and tolerating uneven sample dt.
*
* Exponential 4th-power smoothing with a 30 s time constant; points with
* null power or non-finite timestamps are skipped, as are samples with
* non-positive dt. Returns null when fewer than 2 valid points exist.
*
* @param times timestamps (ISO strings or ms numbers), 1 Hz
* @param powers power in watts, null where the sensor has no data
* @param startIdx inclusive start index
* @param endIdx exclusive end index
*/
export function normalizedPower(
times: Array<string | number>,
powers: Array<number | null>,
startIdx: number,
endIdx: number,
): number | null {
const tau = 30;
const end = Math.min(endIdx, times.length, powers.length);
const start = Math.max(0, startIdx);
// Collect valid points: finite timestamp + non-null power.
const t: number[] = [];
const p: number[] = [];
for (let i = start; i < end; i++) {
const raw = times[i];
const ms =
typeof raw === "number" && Number.isFinite(raw)
? raw
: Number(new Date(raw as string));
const w = powers[i];
if (!Number.isFinite(ms) || w === null || !Number.isFinite(w)) {
continue;
}
t.push(ms);
p.push(w);
}
const n = p.length;
if (n < 2) {
return null;
}
// Exponential smoothing of p^4; the last sample has no forward dt, so its
// 4th power is added to the sum as its own smoothing value.
let sm = Math.pow(p[0], 4);
let sum = sm;
let count = 1;
for (let i = 1; i < n; i++) {
const dt = (t[i] - t[i - 1]) / 1000;
if (dt <= 0) {
continue;
}
sm = sm + (Math.pow(p[i], 4) - sm) * (1 - Math.exp(-dt / tau));
sum += sm;
count += 1;
}
return Math.pow(sum / count, 0.25);
}