901db02a6b
CI / build-and-push (push) Successful in 28s
The old overload policy had dead controls (maxQueueDepth, rateLimitPerCustomer never read) and trivial flat penalties. This replaces it with a full serving pipeline where deployed models form a fleet, requests route through priority/degradation logic, and policy choices create meaningful strategic tradeoffs. New serving pipeline: fleet building from deployed models (size/quant/MoE multipliers), demand categorization by 5 priority tiers, enterprise capacity reservation, priority-ordered serving with overflow behaviors (queue/reject/degrade), auto-degradation to faster models under load, and Batch API to fill idle capacity at discounted rates. 4 new research nodes gate features progressively: Intelligent Request Routing, Priority Queue System, Request Batching, and Auto-Scaling. New dedicated Serving page with pipeline metrics, model fleet utilization, and research-gated policy controls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
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<ApiTierId, ApiTierId | null> = {
|
|
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,
|
|
};
|
|
}
|