TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import { TIER_BASE_INTERVAL, TIER_MAX_INTERVAL, TIER_MIN_INTERVAL, type Tier } from "./taxonomy";23/**4 * Adaptive polling. The next interval depends on tier bounds, how recently the sensor5 * changed, how often it changes, error state and HTTP cache behaviour (304s are cheap6 * so we can afford to poll a bit faster).7 */8export interface ScheduleInput {9 tier: Tier;10 baseIntervalSeconds?: number | null;11 lastChangeAt?: Date | null;12 /** changes in the last 7 days */13 changes7d: number;14 /** meaningful events in the last 7 days */15 events7d: number;16 consecutiveErrors: number;17 /** last run served 304 Not Modified */18 lastWas304: boolean;19 now?: Date;20}2122export function nextIntervalSeconds(i: ScheduleInput): number {23 const now = i.now ?? new Date();24 const base = i.baseIntervalSeconds ?? TIER_BASE_INTERVAL[i.tier];25 const min = TIER_MIN_INTERVAL[i.tier];26 const max = TIER_MAX_INTERVAL[i.tier];27 let interval = base;2829 if (i.lastChangeAt) {30 const ageMin = (now.getTime() - i.lastChangeAt.getTime()) / 60000;31 if (ageMin < 15) interval = base / 6; // burst window32 else if (ageMin < 60) interval = base / 3;33 else if (ageMin < 24 * 60) interval = base / 1.5;34 else if (ageMin > 14 * 24 * 60) interval = base * 3;35 else if (ageMin > 7 * 24 * 60) interval = base * 2;36 } else {37 interval = base * 1.5;38 }3940 // Frequent changers get tighter polling; the weight is bounded so the tier still rules.41 const perDay = i.changes7d / 7;42 if (perDay >= 3) interval *= 0.6;43 else if (perDay >= 1) interval *= 0.8;44 if (i.events7d >= 3) interval *= 0.8;4546 if (i.lastWas304) interval *= 0.85;4748 if (i.consecutiveErrors > 0) interval = Math.max(interval, base) * Math.min(16, 2 ** i.consecutiveErrors);4950 // ±10 % jitter to avoid thundering herds51 const jitter = 1 + (Math.random() * 0.2 - 0.1);52 return Math.round(Math.max(min, Math.min(max * (i.consecutiveErrors ? 4 : 1), interval * jitter)));53}54