11from fastapi import APIRouter , Depends , Query
22from sqlalchemy .ext .asyncio import AsyncSession
3- from sqlalchemy import select , desc , func
3+ from sqlalchemy import select , desc , func , case
44from sqlalchemy .sql .functions import coalesce
55from sqlalchemy import or_
66from datetime import datetime , timedelta , UTC
2727
2828
2929# I want a query parameter called "offset: <int>" and "limit: <int>"
30- @router .get ("/usage/realtime" , response_model = list [ UsageRealtimeResponse ] )
30+ @router .get ("/usage/realtime" , response_model = UsageRealtimeResponse )
3131async def get_usage_realtime (
3232 current_user : User = Depends (get_user_by_api_key ),
3333 db : AsyncSession = Depends (get_async_db ),
34- offset : int = Query (0 , ge = 0 ),
35- limit : int = Query (10 , ge = 1 ),
34+ page_index : int = Query (0 , ge = 0 ),
35+ page_size : int = Query (10 , ge = 1 ),
3636 forge_key : str = Query (None , min_length = 1 ),
3737 provider_name : str = Query (None , min_length = 1 ),
3838 model_name : str = Query (None , min_length = 1 ),
@@ -66,9 +66,11 @@ async def get_usage_realtime(
6666 UsageTracker .output_tokens .label ("output_tokens" ),
6767 UsageTracker .cached_tokens .label ("cached_tokens" ),
6868 UsageTracker .cost .label ("cost" ),
69+ UsageTracker .billable .label ("billable" ),
6970 func .extract (
7071 "epoch" , UsageTracker .updated_at - UsageTracker .created_at
7172 ).label ("duration" ),
73+ func .count ().over ().label ("total" ),
7274 )
7375 .join (ProviderKey , UsageTracker .provider_key_id == ProviderKey .id )
7476 .join (ForgeApiKey , UsageTracker .forge_key_id == ForgeApiKey .id )
@@ -87,18 +89,19 @@ async def get_usage_realtime(
8789 UsageTracker .updated_at .is_not (None ),
8890 )
8991 .order_by (desc (UsageTracker .created_at ))
90- .offset (offset )
91- .limit (limit )
92+ .offset (page_index * page_size )
93+ .limit (page_size )
9294 )
9395
9496 # Execute the query
9597 result = await db .execute (query )
9698 rows = result .fetchall ()
9799
98100 # Convert to list of dictionaries
99- usage_stats = []
101+ items = []
102+ total = 0
100103 for row in rows :
101- usage_stats .append (
104+ items .append (
102105 {
103106 "timestamp" : row .timestamp ,
104107 "forge_key" : row .forge_key ,
@@ -109,20 +112,27 @@ async def get_usage_realtime(
109112 "output_tokens" : row .output_tokens ,
110113 "cached_tokens" : row .cached_tokens ,
111114 "cost" : decimal .Decimal (row .cost ).normalize (),
115+ "billable" : row .billable ,
112116 "duration" : round (float (row .duration ), 2 )
113117 if row .duration is not None
114118 else 0.0 ,
115119 }
116120 )
117- return [UsageRealtimeResponse (** usage_stat ) for usage_stat in usage_stats ]
121+ total = row .total
122+ return UsageRealtimeResponse (
123+ items = items ,
124+ total = total ,
125+ page_size = page_size ,
126+ page_index = page_index ,
127+ )
118128
119129
120- @router .get ("/usage/realtime/clerk" , response_model = list [ UsageRealtimeResponse ] )
130+ @router .get ("/usage/realtime/clerk" , response_model = UsageRealtimeResponse )
121131async def get_usage_realtime_clerk (
122132 current_user : User = Depends (get_current_active_user_from_clerk ),
123133 db : AsyncSession = Depends (get_async_db ),
124- offset : int = Query (0 , ge = 0 ),
125- limit : int = Query (10 , ge = 1 ),
134+ page_index : int = Query (0 , ge = 0 ),
135+ page_size : int = Query (10 , ge = 1 ),
126136 forge_key : str = Query (None , min_length = 1 ),
127137 provider_name : str = Query (None , min_length = 1 ),
128138 model_name : str = Query (None , min_length = 1 ),
@@ -132,8 +142,8 @@ async def get_usage_realtime_clerk(
132142 return await get_usage_realtime (
133143 current_user ,
134144 db ,
135- offset ,
136- limit ,
145+ page_index ,
146+ page_size ,
137147 forge_key ,
138148 provider_name ,
139149 model_name ,
@@ -186,6 +196,7 @@ async def get_usage_summary(
186196 func .sum (UsageTracker .output_tokens ).label ("output_tokens" ),
187197 func .sum (UsageTracker .cached_tokens ).label ("cached_tokens" ),
188198 func .sum (UsageTracker .cost ).label ("cost" ),
199+ func .sum (case ((UsageTracker .billable , UsageTracker .cost ), else_ = 0 )).label ("charged_cost" ),
189200 )
190201 .join (ForgeApiKey , UsageTracker .forge_key_id == ForgeApiKey .id )
191202 .where (
@@ -208,6 +219,7 @@ async def get_usage_summary(
208219 "breakdown" : [],
209220 "total_tokens" : 0 ,
210221 "total_cost" : 0 ,
222+ "total_charged_cost" : 0 ,
211223 "total_input_tokens" : 0 ,
212224 "total_output_tokens" : 0 ,
213225 "total_cached_tokens" : 0 ,
@@ -217,6 +229,7 @@ async def get_usage_summary(
217229 "forge_key" : row .forge_key ,
218230 "tokens" : row .tokens ,
219231 "cost" : decimal .Decimal (row .cost ).normalize (),
232+ "charged_cost" : decimal .Decimal (row .charged_cost ).normalize (),
220233 "input_tokens" : row .input_tokens ,
221234 "output_tokens" : row .output_tokens ,
222235 "cached_tokens" : row .cached_tokens ,
@@ -226,6 +239,9 @@ async def get_usage_summary(
226239 data_points [row .time_point ]["total_cost" ] += decimal .Decimal (
227240 row .cost
228241 ).normalize ()
242+ data_points [row .time_point ]["total_charged_cost" ] += decimal .Decimal (
243+ row .charged_cost
244+ ).normalize ()
229245 data_points [row .time_point ]["total_input_tokens" ] += row .input_tokens
230246 data_points [row .time_point ]["total_output_tokens" ] += row .output_tokens
231247 data_points [row .time_point ]["total_cached_tokens" ] += row .cached_tokens
@@ -236,6 +252,7 @@ async def get_usage_summary(
236252 breakdown = data_point ["breakdown" ],
237253 total_tokens = data_point ["total_tokens" ],
238254 total_cost = data_point ["total_cost" ],
255+ total_charged_cost = data_point ["total_charged_cost" ],
239256 total_input_tokens = data_point ["total_input_tokens" ],
240257 total_output_tokens = data_point ["total_output_tokens" ],
241258 total_cached_tokens = data_point ["total_cached_tokens" ],
@@ -292,6 +309,7 @@ async def get_forge_keys_usage(
292309 func .sum (UsageTracker .output_tokens ).label ("output_tokens" ),
293310 func .sum (UsageTracker .cached_tokens ).label ("cached_tokens" ),
294311 func .sum (UsageTracker .cost ).label ("cost" ),
312+ func .sum (case ((UsageTracker .billable , UsageTracker .cost ), else_ = 0 )).label ("charged_cost" ),
295313 )
296314 .join (ForgeApiKey , UsageTracker .forge_key_id == ForgeApiKey .id )
297315 .where (
@@ -311,6 +329,7 @@ async def get_forge_keys_usage(
311329 forge_key = row .forge_key ,
312330 tokens = row .tokens ,
313331 cost = decimal .Decimal (row .cost ).normalize (),
332+ charged_cost = decimal .Decimal (row .charged_cost ).normalize (),
314333 input_tokens = row .input_tokens ,
315334 output_tokens = row .output_tokens ,
316335 cached_tokens = row .cached_tokens ,
0 commit comments