import type { SimulationMetrics } from '../strategies/types'; export type TrackedMetric = 'revenue' | 'subscribers' | 'developers' | 'bestModelCapability' | 'reputation' | 'totalFlops'; const TRACKED_METRICS: TrackedMetric[] = [ 'revenue', 'subscribers', 'developers', 'bestModelCapability', 'reputation', 'totalFlops', ]; export interface StagnationAlert { metric: TrackedMetric; startTick: number; endTick: number; durationTicks: number; stuckValue: number; era: string; } export interface ExponentialAlert { metric: TrackedMetric; tick: number; growthRate: number; consecutiveSamples: number; } export interface GrowthRateResult { stagnations: StagnationAlert[]; exponentialAlerts: ExponentialAlert[]; } function getMetricValue(m: SimulationMetrics, metric: TrackedMetric): number { return m[metric] as number; } export function analyzeGrowthRates( metrics: SimulationMetrics[], stagnationWindowSamples = 20, stagnationThreshold = 0.01, ): GrowthRateResult { const stagnations: StagnationAlert[] = []; const exponentialAlerts: ExponentialAlert[] = []; for (const metric of TRACKED_METRICS) { const growthRates: number[] = []; for (let i = 1; i < metrics.length; i++) { const prev = getMetricValue(metrics[i - 1], metric); const curr = getMetricValue(metrics[i], metric); if (prev > 0) { growthRates.push((curr - prev) / prev); } else { growthRates.push(curr > 0 ? 1 : 0); } } let stagnationStart: number | null = null; let flatCount = 0; for (let i = 0; i < growthRates.length; i++) { if (Math.abs(growthRates[i]) < stagnationThreshold) { if (stagnationStart === null) stagnationStart = i; flatCount++; } else { if (flatCount >= stagnationWindowSamples && stagnationStart !== null) { const startIdx = stagnationStart + 1; const endIdx = i + 1; stagnations.push({ metric, startTick: metrics[startIdx]?.tick ?? 0, endTick: metrics[endIdx]?.tick ?? metrics[metrics.length - 1]?.tick ?? 0, durationTicks: (metrics[endIdx]?.tick ?? 0) - (metrics[startIdx]?.tick ?? 0), stuckValue: getMetricValue(metrics[startIdx], metric), era: metrics[startIdx]?.era ?? 'unknown', }); } stagnationStart = null; flatCount = 0; } } if (flatCount >= stagnationWindowSamples && stagnationStart !== null) { const startIdx = stagnationStart + 1; stagnations.push({ metric, startTick: metrics[startIdx]?.tick ?? 0, endTick: metrics[metrics.length - 1]?.tick ?? 0, durationTicks: (metrics[metrics.length - 1]?.tick ?? 0) - (metrics[startIdx]?.tick ?? 0), stuckValue: getMetricValue(metrics[startIdx], metric), era: metrics[startIdx]?.era ?? 'unknown', }); } let expCount = 0; for (let i = 0; i < growthRates.length; i++) { if (growthRates[i] > 0.10) { expCount++; if (expCount >= 5) { exponentialAlerts.push({ metric, tick: metrics[i + 1]?.tick ?? 0, growthRate: growthRates[i], consecutiveSamples: expCount, }); } } else { expCount = 0; } } } return { stagnations, exponentialAlerts }; }