import type { ApiTierState, ApiTierId, DeveloperEcosystem, TierServingMetrics } from '@ai-tycoon/shared'; import { API_TIER_ORDER, API_CONVERSION_RATES, API_TIER_CHURN_RATES, API_TOKENS_PER_DEVELOPER_PER_TICK, REJECTION_CHURN_MULTIPLIER, } from '@ai-tycoon/shared'; export interface ApiTickResult { apiTiers: ApiTierState; apiRevenue: number; totalApiTokenDemand: number; } export function processApiTiers( tiers: ApiTierState, playerDevCustomers: number, modelQuality: number, seasonalApiMultiplier: number, ecosystem: DeveloperEcosystem, apiPaidMetrics: TierServingMetrics, apiFreeMetrics: TierServingMetrics, ): ApiTickResult { const updated: ApiTierState = { tiers: { ...tiers.tiers }, totalDevelopers: 0, totalTokensPerTick: 0, }; for (const id of API_TIER_ORDER) { updated.tiers[id] = { ...tiers.tiers[id], config: { ...tiers.tiers[id].config } }; } if (modelQuality <= 0) { return { apiTiers: updated, apiRevenue: 0, totalApiTokenDemand: 0 }; } const targetFreeDevelopers = playerDevCustomers * 0.1 * seasonalApiMultiplier; const freeGrowth = (targetFreeDevelopers - updated.tiers.free.developerCount) * 0.03; updated.tiers.free.developerCount = Math.max(0, updated.tiers.free.developerCount + freeGrowth); const freeChurn = updated.tiers.free.developerCount * API_TIER_CHURN_RATES.free; updated.tiers.free.developerCount = Math.max(0, updated.tiers.free.developerCount - freeChurn); updated.tiers.free.churnRate = API_TIER_CHURN_RATES.free; const prevTierMap: Record = { free: null, payg: 'free', scale: 'payg', 'enterprise-api': 'scale', }; for (const id of API_TIER_ORDER) { if (id === 'free') continue; const tier = updated.tiers[id]; if (!tier.config.isActive) continue; const prevId = prevTierMap[id]; if (!prevId) continue; const prevTier = updated.tiers[prevId]; const convKey = `${prevId}->${id}`; const baseRate = API_CONVERSION_RATES[convKey] ?? 0; const ecosystemBoost = 1 + ecosystem.ecosystemScore / 200; const convRate = baseRate * Math.max(0.1, modelQuality) * ecosystemBoost * seasonalApiMultiplier; const converting = prevTier.developerCount * convRate; prevTier.developerCount = Math.max(0, prevTier.developerCount - converting); tier.developerCount += converting; tier.churnRate = API_TIER_CHURN_RATES[id]; const churned = tier.developerCount * tier.churnRate; tier.developerCount = Math.max(0, tier.developerCount - churned); } let totalDevelopers = 0; let totalTokens = 0; let apiRevenue = 0; for (const id of API_TIER_ORDER) { const tier = updated.tiers[id]; totalDevelopers += tier.developerCount; const tokensPerDev = API_TOKENS_PER_DEVELOPER_PER_TICK[id]; tier.tokensPerTick = tier.developerCount * tokensPerDev; totalTokens += tier.tokensPerTick; apiRevenue += tier.developerCount * (tier.config.monthlyFee / 86400); apiRevenue += (tier.tokensPerTick / 1_000_000) * tier.config.outputTokenPrice; } updated.totalDevelopers = totalDevelopers; updated.totalTokensPerTick = totalTokens; const freeRejectRate = apiFreeMetrics.demandTokens > 0 ? apiFreeMetrics.rejectedTokens / apiFreeMetrics.demandTokens : 0; if (freeRejectRate > 0) { const extraChurn = updated.tiers.free.developerCount * freeRejectRate * 0.01 * REJECTION_CHURN_MULTIPLIER; updated.tiers.free.developerCount = Math.max(0, updated.tiers.free.developerCount - extraChurn); } const paidRejectRate = apiPaidMetrics.demandTokens > 0 ? apiPaidMetrics.rejectedTokens / apiPaidMetrics.demandTokens : 0; if (paidRejectRate > 0) { for (const id of API_TIER_ORDER) { if (id === 'free') continue; const extraChurn = updated.tiers[id].developerCount * paidRejectRate * 0.005 * REJECTION_CHURN_MULTIPLIER; updated.tiers[id].developerCount = Math.max(0, updated.tiers[id].developerCount - extraChurn); } } return { apiTiers: updated, apiRevenue: Math.max(0, apiRevenue), totalApiTokenDemand: totalTokens, }; }