mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
feat(quota): add burnRate EMA estimator and time-to-exhaustion (B/F6)
This commit is contained in:
74
src/lib/quota/burnRate.ts
Normal file
74
src/lib/quota/burnRate.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* burnRate.ts — Burn-rate EMA estimator for quota consumption.
|
||||
*
|
||||
* Computes an exponential moving average (alpha=0.3) over a series of
|
||||
* (timestamp, consumed) samples and projects time to exhaustion.
|
||||
*
|
||||
* Part of: Group B — Quota Sharing Engine (plan 22, frente F6).
|
||||
*/
|
||||
|
||||
const EMA_ALPHA = 0.3;
|
||||
|
||||
export interface BurnRateSample {
|
||||
ts: number; // epoch ms
|
||||
consumed: number; // cumulative consumed value at this ts
|
||||
}
|
||||
|
||||
export interface BurnRateResult {
|
||||
/** Estimated tokens (or units) consumed per second. */
|
||||
tokensPerSecond: number;
|
||||
/**
|
||||
* Estimated milliseconds until the remaining quota is exhausted.
|
||||
* null if rate is 0 or the caller did not provide a remaining value.
|
||||
*/
|
||||
timeToExhaustionMs: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the current burn rate from a series of samples.
|
||||
*
|
||||
* @param history Array of { ts, consumed } ordered oldest → newest.
|
||||
* Needs at least 2 entries; fewer returns zeros.
|
||||
* @param remaining Optional remaining quota (same unit as consumed).
|
||||
* When provided, `timeToExhaustionMs` is calculated.
|
||||
*/
|
||||
export function computeBurnRate(
|
||||
history: BurnRateSample[],
|
||||
remaining?: number
|
||||
): BurnRateResult {
|
||||
if (history.length < 2) {
|
||||
return { tokensPerSecond: 0, timeToExhaustionMs: null };
|
||||
}
|
||||
|
||||
// Build EMA over consecutive deltas.
|
||||
let emaRate = 0;
|
||||
let initialized = false;
|
||||
|
||||
for (let i = 1; i < history.length; i++) {
|
||||
const deltaConsumed = history[i].consumed - history[i - 1].consumed;
|
||||
const deltaTs = history[i].ts - history[i - 1].ts; // ms
|
||||
|
||||
if (deltaTs <= 0) continue; // skip duplicate or out-of-order timestamps
|
||||
|
||||
const instantRate = deltaConsumed / (deltaTs / 1000); // per second
|
||||
|
||||
if (!initialized) {
|
||||
emaRate = instantRate;
|
||||
initialized = true;
|
||||
} else {
|
||||
emaRate = EMA_ALPHA * instantRate + (1 - EMA_ALPHA) * emaRate;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
return { tokensPerSecond: 0, timeToExhaustionMs: null };
|
||||
}
|
||||
|
||||
const safeRate = Math.max(0, emaRate);
|
||||
const timeToExhaustionMs =
|
||||
safeRate > 0 && remaining !== undefined && remaining >= 0
|
||||
? (remaining / safeRate) * 1000
|
||||
: null;
|
||||
|
||||
return { tokensPerSecond: safeRate, timeToExhaustionMs };
|
||||
}
|
||||
Reference in New Issue
Block a user