1313from .models import PageView
1414
1515_HUMAN = or_ (PageView .device_type != "bot" , PageView .device_type .is_ (None ))
16+ _PAGE_ONLY = PageView .path .not_like ("%/api/%" )
17+ _API_ONLY = PageView .path .like ("%/api/%" )
1618
1719
1820def _since (days : int | None ) -> datetime | None :
@@ -37,7 +39,7 @@ def get_pageviews_count(db: Session, days: int = 7) -> int:
3739 since = datetime .now (UTC ) - timedelta (days = days )
3840 return (
3941 db .query (func .count (PageView .id ))
40- .filter (PageView .timestamp >= since , _HUMAN )
42+ .filter (PageView .timestamp >= since , _HUMAN , _PAGE_ONLY )
4143 .scalar () or 0
4244 )
4345
@@ -55,7 +57,7 @@ def get_unique_visitors(db: Session, days: int = 7) -> int:
5557 since = datetime .now (UTC ) - timedelta (days = days )
5658 return (
5759 db .query (func .count (distinct (PageView .session_id )))
58- .filter (PageView .timestamp >= since , PageView .session_id .isnot (None ), _HUMAN )
60+ .filter (PageView .timestamp >= since , PageView .session_id .isnot (None ), _HUMAN , _PAGE_ONLY )
5961 .scalar ()
6062 or 0
6163 )
@@ -65,7 +67,7 @@ def get_top_pages(db: Session, days: int = 7, limit: int = 10) -> list[dict]:
6567 since = datetime .now (UTC ) - timedelta (days = days )
6668 rows = (
6769 db .query (PageView .path , func .count (PageView .id ).label ("views" ))
68- .filter (PageView .timestamp >= since , _HUMAN )
70+ .filter (PageView .timestamp >= since , _HUMAN , _PAGE_ONLY )
6971 .group_by (PageView .path )
7072 .order_by (func .count (PageView .id ).desc ())
7173 .limit (limit )
@@ -78,7 +80,7 @@ def get_browser_breakdown(db: Session, days: int = 7, limit: int = 10) -> list[d
7880 since = datetime .now (UTC ) - timedelta (days = days )
7981 rows = (
8082 db .query (PageView .browser , func .count (PageView .id ).label ("count" ))
81- .filter (PageView .timestamp >= since , PageView .browser .isnot (None ))
83+ .filter (PageView .timestamp >= since , PageView .browser .isnot (None ), _PAGE_ONLY )
8284 .group_by (PageView .browser )
8385 .order_by (func .count (PageView .id ).desc ())
8486 .limit (limit )
@@ -91,7 +93,7 @@ def get_device_breakdown(db: Session, days: int = 7, limit: int = 10) -> list[di
9193 since = datetime .now (UTC ) - timedelta (days = days )
9294 rows = (
9395 db .query (PageView .device_type , func .count (PageView .id ).label ("count" ))
94- .filter (PageView .timestamp >= since , PageView .device_type .isnot (None ))
96+ .filter (PageView .timestamp >= since , PageView .device_type .isnot (None ), _PAGE_ONLY )
9597 .group_by (PageView .device_type )
9698 .order_by (func .count (PageView .id ).desc ())
9799 .limit (limit )
@@ -108,6 +110,7 @@ def get_referer_breakdown(db: Session, days: int = 7, limit: int = 10) -> list[d
108110 PageView .timestamp >= since ,
109111 PageView .referer_domain .isnot (None ),
110112 PageView .referer_domain != "" ,
113+ _PAGE_ONLY ,
111114 )
112115 .group_by (PageView .referer_domain )
113116 .order_by (func .count (PageView .id ).desc ())
@@ -122,7 +125,7 @@ def get_daily_pageviews(db: Session, days: int | None = 30) -> list[dict]:
122125 func .date (PageView .timestamp ).label ("day" ),
123126 func .count (PageView .id ).label ("views" ),
124127 func .count (distinct (PageView .session_id )).label ("visitors" ),
125- )
128+ ). filter ( _HUMAN , _PAGE_ONLY )
126129 if days :
127130 q = q .filter (PageView .timestamp >= datetime .now (UTC ) - timedelta (days = days ))
128131 rows = (
@@ -199,6 +202,8 @@ def get_response_time_percentiles(
199202 q = q .filter (PageView .timestamp >= since )
200203 if path :
201204 q = q .filter (PageView .path == path )
205+ else :
206+ q = q .filter (_PAGE_ONLY )
202207 values = [r [0 ] for r in q .all ()]
203208 if not values :
204209 return {"avg" : 0 , "p50" : 0 , "p95" : 0 , "p99" : 0 }
@@ -227,6 +232,8 @@ def get_daily_latency(
227232 q = q .filter (PageView .timestamp >= since )
228233 if path :
229234 q = q .filter (PageView .path == path )
235+ else :
236+ q = q .filter (_PAGE_ONLY )
230237
231238 results = []
232239 for day , rows in groupby (q .all (), key = attrgetter ("day" )):
@@ -353,4 +360,58 @@ def get_page_referer_breakdown(
353360
354361
355362def get_total_pageviews (db : Session ) -> int :
356- return db .query (func .count (PageView .id )).filter (_HUMAN ).scalar () or 0
363+ return db .query (func .count (PageView .id )).filter (_HUMAN , _PAGE_ONLY ).scalar () or 0
364+
365+
366+ # ---------------------------------------------------------------------------
367+ # API traffic queries
368+ # ---------------------------------------------------------------------------
369+
370+ def get_api_calls_count (db : Session , days : int = 7 ) -> int :
371+ since = datetime .now (UTC ) - timedelta (days = days )
372+ return (
373+ db .query (func .count (PageView .id ))
374+ .filter (PageView .timestamp >= since , _HUMAN , _API_ONLY )
375+ .scalar () or 0
376+ )
377+
378+
379+ def get_top_api_endpoints (db : Session , days : int = 7 , limit : int = 10 ) -> list [dict ]:
380+ since = datetime .now (UTC ) - timedelta (days = days )
381+ rows = (
382+ db .query (
383+ PageView .path ,
384+ func .count (PageView .id ).label ("calls" ),
385+ func .avg (PageView .response_time_ms ).label ("avg_ms" ),
386+ )
387+ .filter (PageView .timestamp >= since , _HUMAN , _API_ONLY )
388+ .group_by (PageView .path )
389+ .order_by (func .count (PageView .id ).desc ())
390+ .limit (limit )
391+ .all ()
392+ )
393+ return [
394+ {"path" : r .path , "calls" : r .calls , "avg_ms" : round (r .avg_ms or 0 , 1 )}
395+ for r in rows
396+ ]
397+
398+
399+ def get_api_latency_percentiles (db : Session , days : int = 7 ) -> dict :
400+ """Return {avg, p50, p95, p99} response times for API endpoints."""
401+ since = _since (days )
402+ q = (
403+ db .query (PageView .response_time_ms )
404+ .filter (PageView .response_time_ms .isnot (None ), _API_ONLY )
405+ .order_by (PageView .response_time_ms )
406+ )
407+ if since :
408+ q = q .filter (PageView .timestamp >= since )
409+ values = [r [0 ] for r in q .all ()]
410+ if not values :
411+ return {"avg" : 0 , "p50" : 0 , "p95" : 0 , "p99" : 0 }
412+ return {
413+ "avg" : round (sum (values ) / len (values ), 1 ),
414+ "p50" : round (_percentile (values , 50 ), 1 ),
415+ "p95" : round (_percentile (values , 95 ), 1 ),
416+ "p99" : round (_percentile (values , 99 ), 1 ),
417+ }
0 commit comments