From 1651d1ad668e36122bc9188d00289a10ff51a3f6 Mon Sep 17 00:00:00 2001 From: abingyyds Date: Fri, 10 Apr 2026 14:59:21 +0800 Subject: [PATCH] Limit official equivalence display to GPT Claude and Gemini --- src/utils/officialEquiv.js | 93 +++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/src/utils/officialEquiv.js b/src/utils/officialEquiv.js index 506f44f..9d9b9fa 100644 --- a/src/utils/officialEquiv.js +++ b/src/utils/officialEquiv.js @@ -7,38 +7,38 @@ import { Q } from '../api'; // Patterns must match actual backend model_name format (e.g. "claude-opus-4-5-20251101"). const OFFICIAL_PRICES = [ // Claude — order matters: longer/more-specific first - { pattern: 'claude-opus-4-5', label: 'Claude Opus 4.5', inputPerMtok: 5 }, - { pattern: 'claude-opus-4-6', label: 'Claude Opus 4.6', inputPerMtok: 5 }, - { pattern: 'claude-opus-4-1', label: 'Claude Opus 4.1', inputPerMtok: 15 }, - { pattern: 'claude-opus-4', label: 'Claude Opus 4', inputPerMtok: 15 }, - { pattern: 'claude-3-opus', label: 'Claude 3 Opus', inputPerMtok: 15 }, - { pattern: 'claude-sonnet-4-5', label: 'Claude Sonnet 4.5', inputPerMtok: 3 }, - { pattern: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', inputPerMtok: 3 }, - { pattern: 'claude-sonnet-4', label: 'Claude Sonnet 4', inputPerMtok: 3 }, - { pattern: 'claude-3-7-sonnet', label: 'Claude 3.7 Sonnet', inputPerMtok: 3 }, - { pattern: 'claude-3-5-sonnet', label: 'Claude 3.5 Sonnet', inputPerMtok: 3 }, - { pattern: 'claude-haiku-4-5', label: 'Claude Haiku 4.5', inputPerMtok: 1 }, - { pattern: 'claude-3-5-haiku', label: 'Claude 3.5 Haiku', inputPerMtok: 0.8 }, - { pattern: 'claude-3-haiku', label: 'Claude 3 Haiku', inputPerMtok: 0.25 }, + { pattern: 'claude-opus-4-5', label: 'Claude Opus 4.5', inputPerMtok: 5, family: 'claudecode' }, + { pattern: 'claude-opus-4-6', label: 'Claude Opus 4.6', inputPerMtok: 5, family: 'claudecode' }, + { pattern: 'claude-opus-4-1', label: 'Claude Opus 4.1', inputPerMtok: 15, family: 'claudecode' }, + { pattern: 'claude-opus-4', label: 'Claude Opus 4', inputPerMtok: 15, family: 'claudecode' }, + { pattern: 'claude-3-opus', label: 'Claude 3 Opus', inputPerMtok: 15, family: 'claudecode' }, + { pattern: 'claude-sonnet-4-5', label: 'Claude Sonnet 4.5', inputPerMtok: 3, family: 'claudecode' }, + { pattern: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', inputPerMtok: 3, family: 'claudecode' }, + { pattern: 'claude-sonnet-4', label: 'Claude Sonnet 4', inputPerMtok: 3, family: 'claudecode' }, + { pattern: 'claude-3-7-sonnet', label: 'Claude 3.7 Sonnet', inputPerMtok: 3, family: 'claudecode' }, + { pattern: 'claude-3-5-sonnet', label: 'Claude 3.5 Sonnet', inputPerMtok: 3, family: 'claudecode' }, + { pattern: 'claude-haiku-4-5', label: 'Claude Haiku 4.5', inputPerMtok: 1, family: 'claudecode' }, + { pattern: 'claude-3-5-haiku', label: 'Claude 3.5 Haiku', inputPerMtok: 0.8, family: 'claudecode' }, + { pattern: 'claude-3-haiku', label: 'Claude 3 Haiku', inputPerMtok: 0.25, family: 'claudecode' }, // OpenAI — more specific first - { pattern: 'gpt-4o-mini', label: 'GPT-4o Mini', inputPerMtok: 0.15 }, - { pattern: 'gpt-4o-2024-05-13', label: 'GPT-4o', inputPerMtok: 5 }, - { pattern: 'chatgpt-4o-latest', label: 'GPT-4o', inputPerMtok: 5 }, - { pattern: 'gpt-4o', label: 'GPT-4o', inputPerMtok: 2.5 }, - { pattern: 'gpt-4.1-mini', label: 'GPT-4.1 Mini', inputPerMtok: 0.4 }, - { pattern: 'gpt-4.1-nano', label: 'GPT-4.1 Nano', inputPerMtok: 0.1 }, - { pattern: 'gpt-4.1', label: 'GPT-4.1', inputPerMtok: 2 }, - { pattern: 'gpt-5.4', label: 'GPT-5.4', inputPerMtok: 2.5 }, - { pattern: 'gpt-5.3', label: 'GPT-5.3', inputPerMtok: 1.75 }, - { pattern: 'gpt-5.2', label: 'GPT-5.2', inputPerMtok: 1.75 }, - { pattern: 'gpt-5.1', label: 'GPT-5.1', inputPerMtok: 1.25 }, + { pattern: 'gpt-4o-mini', label: 'GPT-4o Mini', inputPerMtok: 0.15, family: 'gpt' }, + { pattern: 'gpt-4o-2024-05-13', label: 'GPT-4o', inputPerMtok: 5, family: 'gpt' }, + { pattern: 'chatgpt-4o-latest', label: 'GPT-4o', inputPerMtok: 5, family: 'gpt' }, + { pattern: 'gpt-4o', label: 'GPT-4o', inputPerMtok: 2.5, family: 'gpt' }, + { pattern: 'gpt-4.1-mini', label: 'GPT-4.1 Mini', inputPerMtok: 0.4, family: 'gpt' }, + { pattern: 'gpt-4.1-nano', label: 'GPT-4.1 Nano', inputPerMtok: 0.1, family: 'gpt' }, + { pattern: 'gpt-4.1', label: 'GPT-4.1', inputPerMtok: 2, family: 'gpt' }, + { pattern: 'gpt-5.4', label: 'GPT-5.4', inputPerMtok: 2.5, family: 'gpt' }, + { pattern: 'gpt-5.3', label: 'GPT-5.3', inputPerMtok: 1.75, family: 'gpt' }, + { pattern: 'gpt-5.2', label: 'GPT-5.2', inputPerMtok: 1.75, family: 'gpt' }, + { pattern: 'gpt-5.1', label: 'GPT-5.1', inputPerMtok: 1.25, family: 'gpt' }, // Gemini — more specific first - { pattern: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite', inputPerMtok: 0.04 }, - { pattern: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', inputPerMtok: 0.15 }, - { pattern: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', inputPerMtok: 1.25 }, - { pattern: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', inputPerMtok: 0.1 }, - { pattern: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', inputPerMtok: 1.25 }, - { pattern: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', inputPerMtok: 0.075 }, + { pattern: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite', inputPerMtok: 0.04, family: 'gemini' }, + { pattern: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', inputPerMtok: 0.15, family: 'gemini' }, + { pattern: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', inputPerMtok: 1.25, family: 'gemini' }, + { pattern: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', inputPerMtok: 0.1, family: 'gemini' }, + { pattern: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', inputPerMtok: 1.25, family: 'gemini' }, + { pattern: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', inputPerMtok: 0.075, family: 'gemini' }, // DeepSeek { pattern: 'deepseek-r1', label: 'DeepSeek R1', inputPerMtok: 0.55 }, { pattern: 'deepseek-chat', label: 'DeepSeek V3', inputPerMtok: 0.27 }, @@ -46,16 +46,15 @@ const OFFICIAL_PRICES = [ ]; /** - * Find ALL official equivalents for a given quota. - * Returns array of { label, equivDollars } sorted by equivDollars desc. - * Only includes results where equivDollars > quotaDollars. - * Deduplicates by label (keeps best ratio per model label). + * Find official equivalents for the three headline families only: + * GPT, Claude Code, and Gemini. + * Returns at most one best result per family, in fixed display order. */ export function calcOfficialEquivList(models, quotaDollars) { if (!models || models.length === 0 || quotaDollars <= 0) return []; - // label -> best equivDollars - const bestByLabel = {}; + const familyOrder = ['gpt', 'claudecode', 'gemini']; + const bestByFamily = {}; for (const m of models) { const siteInputPrice = Number(m.input_price); @@ -65,13 +64,20 @@ export function calcOfficialEquivList(models, quotaDollars) { for (const official of OFFICIAL_PRICES) { if (modelName.includes(official.pattern.toLowerCase())) { + if (!official.family || !familyOrder.includes(official.family)) break; + const sitePricePerMtok = siteInputPrice * 1000; const ratio = official.inputPerMtok / sitePricePerMtok; const equivDollars = quotaDollars * ratio; if (equivDollars > quotaDollars) { - if (!bestByLabel[official.label] || equivDollars > bestByLabel[official.label]) { - bestByLabel[official.label] = equivDollars; + const currentBest = bestByFamily[official.family]; + if (!currentBest || equivDollars > currentBest.equivDollars) { + bestByFamily[official.family] = { + family: official.family, + label: official.label, + equivDollars, + }; } } break; @@ -79,10 +85,13 @@ export function calcOfficialEquivList(models, quotaDollars) { } } - return Object.entries(bestByLabel) - .map(([label, equivDollars]) => ({ label, equivDollars: Math.round(equivDollars) })) - .sort((a, b) => b.equivDollars - a.equivDollars) - .slice(0, 3); + return familyOrder + .map((family) => bestByFamily[family]) + .filter(Boolean) + .map((item) => ({ + label: item.label, + equivDollars: Math.round(item.equivDollars), + })); } /**