()
| 194 | * 3. Updates the chartData field in model_stats |
| 195 | */ |
| 196 | export async function syncInternalUsageStats(): Promise<void> { |
| 197 | // Fetch mode rankings data from PostHog |
| 198 | const chartDataUpdates = await fetchModeRankingsData(); |
| 199 | |
| 200 | // Fetch weekly token usage data and merge it |
| 201 | const weeklyTokenUsage = await fetchWeeklyTokenUsage(); |
| 202 | for (const [model, data] of Object.entries(weeklyTokenUsage)) { |
| 203 | chartDataUpdates[model] = { ...chartDataUpdates[model], ...data }; |
| 204 | } |
| 205 | |
| 206 | // Get ALL existing model stats to match normalized model names to openrouterId |
| 207 | // This ensures we update any model in the database, not just preferred ones |
| 208 | const allModelStats = await db.select().from(modelStats); |
| 209 | const modelStatsMap = new Map(allModelStats.map(stat => [stat.openrouterId, stat])); |
| 210 | |
| 211 | console.log(`[sync-internal-data] Found ${allModelStats.length} models in database`); |
| 212 | |
| 213 | // Track updates |
| 214 | let updatedCount = 0; |
| 215 | let skippedCount = 0; |
| 216 | |
| 217 | // Update each model's chartData with mode rankings |
| 218 | for (const [normalizedModelName, updateData] of Object.entries(chartDataUpdates)) { |
| 219 | // Try to find matching model in database |
| 220 | // The normalized model name from PostHog might not exactly match openrouterId |
| 221 | // We need to handle various cases like: |
| 222 | // - "claude-sonnet-4.5" -> "anthropic/claude-sonnet-4.5" |
| 223 | // - "grok-code-fast-1" -> "x-ai/grok-code-fast-1" |
| 224 | // - etc. |
| 225 | |
| 226 | let matchingModel = modelStatsMap.get(normalizedModelName); |
| 227 | |
| 228 | // If no exact match, try with common provider prefixes |
| 229 | if (!matchingModel) { |
| 230 | for (const provider of MODEL_PROVIDERS) { |
| 231 | const withProvider = `${provider}/${normalizedModelName}`; |
| 232 | matchingModel = modelStatsMap.get(withProvider); |
| 233 | if (matchingModel) break; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | // Try with :free suffix - any model might have this |
| 238 | if (!matchingModel) { |
| 239 | const withFreeSuffix = `${normalizedModelName}:free`; |
| 240 | matchingModel = modelStatsMap.get(withFreeSuffix); |
| 241 | } |
| 242 | |
| 243 | // Also try with provider prefix + :free suffix |
| 244 | if (!matchingModel) { |
| 245 | for (const provider of MODEL_PROVIDERS) { |
| 246 | const withProviderAndSuffix = `${provider}/${normalizedModelName}:free`; |
| 247 | matchingModel = modelStatsMap.get(withProviderAndSuffix); |
| 248 | if (matchingModel) break; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | if (!matchingModel) { |
| 253 | skippedCount++; |
no test coverage detected