From 0be1d7ddbc2e7b0fd10922914d4e5360e56cb49a Mon Sep 17 00:00:00 2001 From: majdyz Date: Mon, 13 Apr 2026 01:23:26 +0000 Subject: [PATCH] fix(platform-cost): apply all dashboard filters to raw SQL percentile/bucket queries Raw SQL queries for percentile and histogram bucket distributions were only filtering by createdAt >= start, ignoring end, provider, user_id, model, and block_name. This caused percentile/bucket stats to silently include data outside the selected filter scope. Also: add P75 summary card to frontend, fix skeleton count (8->12) to match rendered card count, fix "Avg Cost / Request" subtitle to accurately say "cost-bearing requests", and add test assertion that raw queries receive active filter params. --- .../backend/backend/data/platform_cost.py | 50 ++++++++++++++++--- .../backend/data/platform_cost_test.py | 10 +++- .../components/PlatformCostContent.tsx | 11 +++- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/autogpt_platform/backend/backend/data/platform_cost.py b/autogpt_platform/backend/backend/data/platform_cost.py index e43ae147d4..3baa0a58a7 100644 --- a/autogpt_platform/backend/backend/data/platform_cost.py +++ b/autogpt_platform/backend/backend/data/platform_cost.py @@ -275,6 +275,42 @@ async def get_platform_cost_dashboard( "trackingAmount": True, } + # Build parameterised WHERE clause for the raw SQL percentile/bucket + # queries so they honour all active dashboard filters, not just start date. + raw_params: list = [start] + raw_where_clauses = [ + '"trackingType" = \'cost_usd\'', + '"createdAt" >= $1', + ] + param_idx = 2 # $1 is already start + + if end is not None: + raw_where_clauses.append(f'"createdAt" <= ${param_idx}') + raw_params.append(end) + param_idx += 1 + + if provider is not None: + raw_where_clauses.append(f'"provider" = ${param_idx}') + raw_params.append(provider.lower()) + param_idx += 1 + + if user_id is not None: + raw_where_clauses.append(f'"userId" = ${param_idx}') + raw_params.append(user_id) + param_idx += 1 + + if model is not None: + raw_where_clauses.append(f'"model" = ${param_idx}') + raw_params.append(model) + param_idx += 1 + + if block_name is not None: + raw_where_clauses.append(f'LOWER("blockName") = LOWER(${param_idx})') + raw_params.append(block_name) + param_idx += 1 + + raw_where = " AND ".join(raw_where_clauses) + # Run all six aggregation queries in parallel. ( by_provider_groups, @@ -317,7 +353,7 @@ async def get_platform_cost_dashboard( }, count=True, ), - # Percentile distribution of cost per request. + # Percentile distribution of cost per request (respects all filters). query_raw_with_schema( "SELECT" " percentile_cont(0.5) WITHIN GROUP" @@ -329,11 +365,10 @@ async def get_platform_cost_dashboard( " percentile_cont(0.99) WITHIN GROUP" ' (ORDER BY "costMicrodollars") as p99' ' FROM {schema_prefix}"PlatformCostLog"' - " WHERE \"trackingType\" = 'cost_usd'" - ' AND "createdAt" >= $1', - start, + f" WHERE {raw_where}", + *raw_params, ), - # Histogram buckets for cost distribution. + # Histogram buckets for cost distribution (respects all filters). query_raw_with_schema( "SELECT" " CASE" @@ -351,11 +386,10 @@ async def get_platform_cost_dashboard( " END as bucket," " COUNT(*) as count" ' FROM {schema_prefix}"PlatformCostLog"' - " WHERE \"trackingType\" = 'cost_usd'" - ' AND "createdAt" >= $1' + f" WHERE {raw_where}" " GROUP BY bucket" ' ORDER BY MIN("costMicrodollars")', - start, + *raw_params, ), ) diff --git a/autogpt_platform/backend/backend/data/platform_cost_test.py b/autogpt_platform/backend/backend/data/platform_cost_test.py index 1ddf2b3e8a..7649eff524 100644 --- a/autogpt_platform/backend/backend/data/platform_cost_test.py +++ b/autogpt_platform/backend/backend/data/platform_cost_test.py @@ -416,6 +416,7 @@ class TestGetPlatformCostDashboard: mock_actions.group_by = AsyncMock(side_effect=[[], [], [], []]) mock_actions.find_many = AsyncMock(return_value=[]) + raw_mock = AsyncMock(side_effect=[[], []]) with ( patch( "backend.data.platform_cost.PrismaLog.prisma", @@ -427,8 +428,7 @@ class TestGetPlatformCostDashboard: ), patch( "backend.data.platform_cost.query_raw_with_schema", - new_callable=AsyncMock, - side_effect=[[], []], + raw_mock, ), ): await get_platform_cost_dashboard( @@ -440,6 +440,12 @@ class TestGetPlatformCostDashboard: # The where dict passed to the first call should include createdAt first_call_kwargs = mock_actions.group_by.call_args_list[0][1] assert "createdAt" in first_call_kwargs.get("where", {}) + # Raw SQL queries should receive provider and user_id as parameters + assert raw_mock.await_count == 2 + raw_call_args = raw_mock.call_args_list[0][0] # positional args of 1st call + raw_params = raw_call_args[1:] # first arg is the query template + assert "openai" in raw_params + assert "u1" in raw_params def _make_prisma_log_row( diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/PlatformCostContent.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/PlatformCostContent.tsx index 8e785320dc..636f4f1dd8 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/PlatformCostContent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/PlatformCostContent.tsx @@ -205,7 +205,7 @@ export function PlatformCostContent({ searchParams }: Props) { {loading ? (
- {[...Array(8)].map((_, i) => ( + {[...Array(12)].map((_, i) => ( ))}
@@ -240,7 +240,7 @@ export function PlatformCostContent({ searchParams }: Props) { value={formatMicrodollars( dashboard.avg_cost_microdollars_per_request ?? 0, )} - subtitle="Known cost divided by total requests" + subtitle="Known cost divided by cost-bearing requests" /> +