diff --git a/backend/internal/service/media_price_config.go b/backend/internal/service/media_price_config.go index 0a583b6c72..de5cf437fb 100644 --- a/backend/internal/service/media_price_config.go +++ b/backend/internal/service/media_price_config.go @@ -20,14 +20,15 @@ func videoPriceConfigFromAPIKey(apiKey *APIKey) *VideoPriceConfig { return nil } return &VideoPriceConfig{ - Price480P: apiKey.Group.VideoPrice480P, - Price720P: apiKey.Group.VideoPrice720P, - Price1080P: apiKey.Group.VideoPrice1080P, + Price480P: apiKey.Group.VideoPrice480P, + Price720P: apiKey.Group.VideoPrice720P, + Price1080P: apiKey.Group.VideoPrice1080P, + ModelPrices: apiKey.Group.VideoModelPrices, } } -func apiKeyHasConfiguredVideoPrice(apiKey *APIKey, resolution string) bool { - return apiKey != nil && apiKey.Group != nil && apiKey.Group.GetVideoPrice(resolution) != nil +func apiKeyHasConfiguredVideoPrice(apiKey *APIKey, model, resolution string) bool { + return apiKey != nil && apiKey.Group != nil && apiKey.Group.GetVideoPriceForModel(model, resolution) != nil } func webSearchPricePerCallFromAPIKey(apiKey *APIKey) *float64 { diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go index beb674474e..3bfa10e313 100644 --- a/backend/internal/service/openai_gateway_record_usage_test.go +++ b/backend/internal/service/openai_gateway_record_usage_test.go @@ -2218,6 +2218,53 @@ func TestOpenAIGatewayServiceRecordUsage_GroupVideoPriceOverridesChannelImagePri require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode) } +func TestOpenAIGatewayServiceRecordUsage_GroupVideoModelPriceOverridesFlatAndChannelPrice(t *testing.T) { + groupID := int64(129) + channelPrice := 0.201 + flatVideoPrice720P := 0.037 + modelVideoPrice720P := 0.123 + usageRepo := &openAIRecordUsageLogRepoStub{inserted: true} + svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil) + svc.resolver = newOpenAIImageChannelPricingResolverForTest(t, groupID, "grok-imagine-video-1.5-preview", channelPrice) + + err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{ + Result: &OpenAIForwardResult{ + RequestID: "resp_grok_video_model_price", + Model: "grok-imagine-video-1.5-preview", + BillingModel: "grok-imagine-video-1.5-preview", + ImageCount: 1, + VideoCount: 1, + VideoResolution: VideoBillingResolution720P, + VideoDurationSeconds: 2, + Duration: time.Second, + }, + APIKey: &APIKey{ + ID: 10129, + GroupID: i64p(groupID), + Group: &Group{ + ID: groupID, + Platform: PlatformGrok, + RateMultiplier: 1, + VideoRateIndependent: true, + VideoRateMultiplier: 1, + VideoPrice720P: &flatVideoPrice720P, + VideoModelPrices: map[string]map[string]float64{ + VideoPriceFamilyGrokImagineVideo15: {VideoBillingResolution720P: modelVideoPrice720P}, + }, + }, + }, + User: &User{ID: 20129}, + Account: &Account{ID: 30129, Platform: PlatformGrok}, + }) + + require.NoError(t, err) + require.NotNil(t, usageRepo.lastLog) + require.InDelta(t, modelVideoPrice720P*2, usageRepo.lastLog.TotalCost, 1e-12) + require.InDelta(t, modelVideoPrice720P*2, usageRepo.lastLog.ActualCost, 1e-12) + require.NotNil(t, usageRepo.lastLog.BillingMode) + require.Equal(t, string(BillingModeVideo), *usageRepo.lastLog.BillingMode) +} + func TestOpenAIGatewayServiceRecordUsage_HydratesGroupImagePriceWhenAuthSnapshotOmitsIt(t *testing.T) { groupID := int64(130) groupImagePrice2K := 0.021 diff --git a/backend/internal/service/openai_gateway_usage.go b/backend/internal/service/openai_gateway_usage.go index 0725c34cf7..1297b17ed7 100644 --- a/backend/internal/service/openai_gateway_usage.go +++ b/backend/internal/service/openai_gateway_usage.go @@ -586,13 +586,13 @@ func (s *OpenAIGatewayService) calculateOpenAIVideoCost( resolution := NormalizeVideoBillingResolutionOrDefault(result.VideoResolution) durationSeconds := NormalizeVideoBillingDurationSecondsOrDefault(result.VideoDurationSeconds) groupConfig := videoPriceConfigFromAPIKey(apiKey) - if apiKeyHasConfiguredVideoPrice(apiKey, resolution) { + if apiKeyHasConfiguredVideoPrice(apiKey, billingModel, resolution) { return s.billingService.CalculateVideoCost(billingModel, resolution, videoCount, durationSeconds, groupConfig, multiplier) } if refreshed := s.apiKeyWithFreshGroupMediaPricing(ctx, apiKey); refreshed != apiKey { apiKey = refreshed groupConfig = videoPriceConfigFromAPIKey(apiKey) - if apiKeyHasConfiguredVideoPrice(apiKey, resolution) { + if apiKeyHasConfiguredVideoPrice(apiKey, billingModel, resolution) { return s.billingService.CalculateVideoCost(billingModel, resolution, videoCount, durationSeconds, groupConfig, multiplier) } }