55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import type { PowerSample, QuarterHourStatus } from "../types.js";
|
|
|
|
export const BLOCK_DURATION_MS = 15 * 60 * 1000;
|
|
|
|
export function blockStartFor(timestampMs: number): number {
|
|
return Math.floor(timestampMs / BLOCK_DURATION_MS) * BLOCK_DURATION_MS;
|
|
}
|
|
|
|
/**
|
|
* Computes the running and projected average power for the current 15-minute
|
|
* billing block, given a step-function reading of power samples (each sample
|
|
* holds until the next one arrives). Samples outside the current block are
|
|
* ignored; if the block has no samples yet, power is assumed to be 0 so far.
|
|
*/
|
|
export function computeQuarterHourStatus(
|
|
samples: PowerSample[],
|
|
nowMs: number,
|
|
targetW: number,
|
|
): QuarterHourStatus {
|
|
const blockStartMs = blockStartFor(nowMs);
|
|
const blockEndMs = blockStartMs + BLOCK_DURATION_MS;
|
|
|
|
const relevant = samples
|
|
.filter((s) => s.timestampMs >= blockStartMs && s.timestampMs <= nowMs)
|
|
.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
|
|
let energyWms = 0;
|
|
let cursorMs = blockStartMs;
|
|
let cursorWatts = relevant.length > 0 ? relevant[0].watts : 0;
|
|
|
|
for (const sample of relevant) {
|
|
energyWms += cursorWatts * (sample.timestampMs - cursorMs);
|
|
cursorMs = sample.timestampMs;
|
|
cursorWatts = sample.watts;
|
|
}
|
|
energyWms += cursorWatts * (nowMs - cursorMs);
|
|
|
|
const elapsedMs = nowMs - blockStartMs;
|
|
const runningAverageW = elapsedMs > 0 ? energyWms / elapsedMs : 0;
|
|
|
|
const remainingMs = blockEndMs - nowMs;
|
|
const projectedEnergyWms = energyWms + cursorWatts * remainingMs;
|
|
const projectedAverageW = projectedEnergyWms / BLOCK_DURATION_MS;
|
|
|
|
return {
|
|
blockStartMs,
|
|
blockEndMs,
|
|
elapsedFractionOfBlock: elapsedMs / BLOCK_DURATION_MS,
|
|
runningAverageW,
|
|
projectedAverageW,
|
|
targetW,
|
|
overTarget: projectedAverageW > targetW,
|
|
};
|
|
}
|