Clarify AI cost estimates with pricing coverage
This commit is contained in:
parent
11ebc0e91c
commit
5f2c240480
6 changed files with 36 additions and 0 deletions
|
|
@ -65,6 +65,12 @@ export const AICostDashboard: Component = () => {
|
|||
return data.provider_models.some((pm) => pm.pricing_known);
|
||||
});
|
||||
|
||||
const unpricedProviderModels = createMemo(() => {
|
||||
const data = summary();
|
||||
if (!data) return [];
|
||||
return (data.provider_models ?? []).filter((pm) => !pm.pricing_known && (pm.total_tokens ?? 0) > 0);
|
||||
});
|
||||
|
||||
const estimatedTotalUSD = createMemo(() => {
|
||||
const data = summary();
|
||||
if (!data || !anyPricingKnown()) return null;
|
||||
|
|
@ -412,6 +418,25 @@ export const AICostDashboard: Component = () => {
|
|||
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
USD is an estimate based on public list prices. It may differ from billing.
|
||||
<Show when={unpricedProviderModels().length > 0}>
|
||||
<span class="ml-2">
|
||||
Estimated spend is partial. Pricing is unknown for{' '}
|
||||
{unpricedProviderModels()
|
||||
.slice(0, 6)
|
||||
.map(
|
||||
(pm) =>
|
||||
`${PROVIDER_NAMES[pm.provider as keyof typeof PROVIDER_NAMES] || pm.provider}/${pm.model}`,
|
||||
)
|
||||
.join(', ')}
|
||||
<Show when={unpricedProviderModels().length > 6}>
|
||||
<span> (+{unpricedProviderModels().length - 6} more)</span>
|
||||
</Show>
|
||||
.
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={summary()?.pricing_as_of}>
|
||||
<span class="ml-2">Prices as of {summary()?.pricing_as_of}.</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ export interface AICostSummary {
|
|||
retention_days: number;
|
||||
effective_days: number;
|
||||
truncated: boolean;
|
||||
pricing_as_of?: string;
|
||||
provider_models: AICostProviderModelSummary[];
|
||||
use_cases: AICostUseCaseSummary[];
|
||||
targets: AICostTargetSummary[];
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ type modelPrice struct {
|
|||
|
||||
const pricingAsOf = "2025-12"
|
||||
|
||||
// PricingAsOf indicates the effective date of the pricing table used for estimation.
|
||||
func PricingAsOf() string {
|
||||
return pricingAsOf
|
||||
}
|
||||
|
||||
// NOTE: Keep this table small and conservative.
|
||||
// The goal is quick estimation and relative comparisons, not exact billing.
|
||||
var providerPrices = map[string][]modelPrice{
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ func (s *Store) GetSummary(days int) Summary {
|
|||
RetentionDays: retentionDays,
|
||||
EffectiveDays: effectiveDays,
|
||||
Truncated: truncated,
|
||||
PricingAsOf: PricingAsOf(),
|
||||
ProviderModels: providerModels,
|
||||
UseCases: summarizeUseCases(events),
|
||||
Targets: summarizeTargets(events),
|
||||
|
|
@ -356,6 +357,8 @@ type Summary struct {
|
|||
EffectiveDays int `json:"effective_days"`
|
||||
Truncated bool `json:"truncated"`
|
||||
|
||||
PricingAsOf string `json:"pricing_as_of,omitempty"`
|
||||
|
||||
ProviderModels []ProviderModelSummary `json:"provider_models"`
|
||||
UseCases []UseCaseSummary `json:"use_cases"`
|
||||
Targets []TargetSummary `json:"targets"`
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ func (s *Service) GetCostSummary(days int) cost.Summary {
|
|||
RetentionDays: cost.DefaultMaxDays,
|
||||
EffectiveDays: effectiveDays,
|
||||
Truncated: truncated,
|
||||
PricingAsOf: cost.PricingAsOf(),
|
||||
ProviderModels: []cost.ProviderModelSummary{},
|
||||
UseCases: []cost.UseCaseSummary{},
|
||||
DailyTotals: []cost.DailySummary{},
|
||||
|
|
|
|||
|
|
@ -2441,6 +2441,7 @@ func (h *AISettingsHandler) HandleGetAICostSummary(w http.ResponseWriter, r *htt
|
|||
} else {
|
||||
summary = cost.Summary{
|
||||
Days: days,
|
||||
PricingAsOf: cost.PricingAsOf(),
|
||||
ProviderModels: []cost.ProviderModelSummary{},
|
||||
DailyTotals: []cost.DailySummary{},
|
||||
Totals: cost.ProviderModelSummary{Provider: "all"},
|
||||
|
|
|
|||
Loading…
Reference in a new issue