fix: cast usage run filter JSON values to Float instead of Integer (#574)

The duration and tokenUsage numberRange filters cast the JSON text value
to INTEGER, but some rows store call_duration_seconds as a JSON float
(0.0, written by get_call_duration when the pipeline never recorded a
start time). Postgres cannot cast the text '0.0' to integer, so any
duration filter on /organizations/usage/runs failed with
InvalidTextRepresentationError. Cast to FLOAT instead, matching the
duration sort clause, and make get_call_duration return an int so new
rows are written as integers.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Abhishek 2026-07-23 16:58:41 +05:30 committed by GitHub
parent eae30b3b21
commit cd20607628
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 10 additions and 11 deletions

View file

@ -3,7 +3,7 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy import Float, Integer, Text, and_, cast, func
from sqlalchemy import Float, Text, and_, cast, func
from sqlalchemy.dialects.postgresql import JSONB
from api.db.models import WorkflowRunModel
@ -211,17 +211,16 @@ def apply_workflow_run_filters(
if field == "usage_info.call_duration_seconds":
# Use ->> operator for compatibility with all PostgreSQL versions
# (subscript [] only works in PostgreSQL 14+)
# Cast to Float, not Integer: some rows store the value as
# a JSON float (e.g. 0.0), and Postgres can't cast the text
# '0.0' to integer.
duration_text = cast(WorkflowRunModel.usage_info, JSONB).op("->>")(
"call_duration_seconds"
)
if min_val is not None:
filter_conditions.append(
cast(duration_text, Integer) >= min_val
)
filter_conditions.append(cast(duration_text, Float) >= min_val)
if max_val is not None:
filter_conditions.append(
cast(duration_text, Integer) <= max_val
)
filter_conditions.append(cast(duration_text, Float) <= max_val)
elif field == "cost_info.total_cost_usd":
# Use ->> operator for compatibility with all PostgreSQL versions
@ -229,9 +228,9 @@ def apply_workflow_run_filters(
"total_cost_usd"
)
if min_val is not None:
filter_conditions.append(cast(cost_text, Integer) >= min_val)
filter_conditions.append(cast(cost_text, Float) >= min_val)
if max_val is not None:
filter_conditions.append(cast(cost_text, Integer) <= max_val)
filter_conditions.append(cast(cost_text, Float) <= max_val)
if filter_conditions:
base_query = base_query.where(and_(*filter_conditions))

View file

@ -113,10 +113,10 @@ class PipelineMetricsAggregator(FrameProcessor):
"""Get the aggregated STT usage metrics grouped by processor|||model."""
return self._stt_usage_metrics
def get_call_duration(self) -> float:
def get_call_duration(self) -> int:
"""Get call duration"""
if self._start_time is None:
return 0.0
return 0
if self._stop_time is None:
call_duration = time.time() - self._start_time