export const DATA_SOURCE_IDS = [ 'oneOsMileage', 'hydrogenDatabase', 'electricDatabase', 'etcDatabase', ] as const; export type DataSourceId = typeof DATA_SOURCE_IDS[number]; interface Outcome { success: boolean; at: number; } const MAX_OUTCOMES = 100; const outcomes = new Map(); const startedAt = new Date().toISOString(); function lastMatching(entries: Outcome[], success: boolean): Outcome | undefined { for (let index = entries.length - 1; index >= 0; index -= 1) { if (entries[index].success === success) return entries[index]; } return undefined; } export function recordDataSourceOutcome( source: DataSourceId, success: boolean, at = Date.now(), ): void { const entries = outcomes.get(source) || []; entries.push({ success, at }); if (entries.length > MAX_OUTCOMES) entries.splice(0, entries.length - MAX_OUTCOMES); outcomes.set(source, entries); } export async function observeDataSource( source: DataSourceId, operation: () => Promise, ): Promise { try { const result = await operation(); recordDataSourceOutcome(source, true); return result; } catch (error) { recordDataSourceOutcome(source, false); throw error; } } export function getDataSourceTelemetry(now = new Date()) { return { semantics: 'observed-requests' as const, windowSize: MAX_OUTCOMES, processStartedAt: startedAt, checkedAt: now.toISOString(), sources: DATA_SOURCE_IDS.map(source => { const entries = outcomes.get(source) || []; const failures = entries.filter(entry => !entry.success).length; const last = entries.at(-1); const lastSuccess = lastMatching(entries, true); const lastFailure = lastMatching(entries, false); return { source, state: !last ? 'unobserved' as const : last.success ? 'available' as const : 'failing' as const, attempts: entries.length, failures, failureRate: entries.length > 0 ? Math.round(failures / entries.length * 10_000) / 10_000 : null, lastSuccessAt: lastSuccess ? new Date(lastSuccess.at).toISOString() : null, lastFailureAt: lastFailure ? new Date(lastFailure.at).toISOString() : null, }; }), }; } export function clearDataSourceTelemetry(): void { outcomes.clear(); }