isa-model 0.3.9__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. isa_model/__init__.py +1 -1
  2. isa_model/client.py +732 -565
  3. isa_model/core/cache/redis_cache.py +401 -0
  4. isa_model/core/config/config_manager.py +53 -10
  5. isa_model/core/config.py +1 -1
  6. isa_model/core/database/__init__.py +1 -0
  7. isa_model/core/database/migrations.py +277 -0
  8. isa_model/core/database/supabase_client.py +123 -0
  9. isa_model/core/models/__init__.py +37 -0
  10. isa_model/core/models/model_billing_tracker.py +60 -88
  11. isa_model/core/models/model_manager.py +36 -18
  12. isa_model/core/models/model_repo.py +44 -38
  13. isa_model/core/models/model_statistics_tracker.py +234 -0
  14. isa_model/core/models/model_storage.py +0 -1
  15. isa_model/core/models/model_version_manager.py +959 -0
  16. isa_model/core/pricing_manager.py +2 -249
  17. isa_model/core/resilience/circuit_breaker.py +366 -0
  18. isa_model/core/security/secrets.py +358 -0
  19. isa_model/core/services/__init__.py +2 -4
  20. isa_model/core/services/intelligent_model_selector.py +101 -370
  21. isa_model/core/storage/hf_storage.py +1 -1
  22. isa_model/core/types.py +7 -0
  23. isa_model/deployment/cloud/modal/isa_audio_chatTTS_service.py +520 -0
  24. isa_model/deployment/cloud/modal/isa_audio_fish_service.py +0 -0
  25. isa_model/deployment/cloud/modal/isa_audio_openvoice_service.py +758 -0
  26. isa_model/deployment/cloud/modal/isa_audio_service_v2.py +1044 -0
  27. isa_model/deployment/cloud/modal/isa_embed_rerank_service.py +296 -0
  28. isa_model/deployment/cloud/modal/isa_video_hunyuan_service.py +423 -0
  29. isa_model/deployment/cloud/modal/isa_vision_ocr_service.py +519 -0
  30. isa_model/deployment/cloud/modal/isa_vision_qwen25_service.py +709 -0
  31. isa_model/deployment/cloud/modal/isa_vision_table_service.py +467 -323
  32. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +607 -180
  33. isa_model/deployment/cloud/modal/isa_vision_ui_service_optimized.py +660 -0
  34. isa_model/deployment/core/deployment_manager.py +6 -4
  35. isa_model/deployment/services/auto_hf_modal_deployer.py +894 -0
  36. isa_model/eval/benchmarks/__init__.py +27 -0
  37. isa_model/eval/benchmarks/multimodal_datasets.py +460 -0
  38. isa_model/eval/benchmarks.py +244 -12
  39. isa_model/eval/evaluators/__init__.py +8 -2
  40. isa_model/eval/evaluators/audio_evaluator.py +727 -0
  41. isa_model/eval/evaluators/embedding_evaluator.py +742 -0
  42. isa_model/eval/evaluators/vision_evaluator.py +564 -0
  43. isa_model/eval/example_evaluation.py +395 -0
  44. isa_model/eval/factory.py +272 -5
  45. isa_model/eval/isa_benchmarks.py +700 -0
  46. isa_model/eval/isa_integration.py +582 -0
  47. isa_model/eval/metrics.py +159 -6
  48. isa_model/eval/tests/unit/test_basic.py +396 -0
  49. isa_model/inference/ai_factory.py +44 -8
  50. isa_model/inference/services/audio/__init__.py +21 -0
  51. isa_model/inference/services/audio/base_realtime_service.py +225 -0
  52. isa_model/inference/services/audio/isa_tts_service.py +0 -0
  53. isa_model/inference/services/audio/openai_realtime_service.py +320 -124
  54. isa_model/inference/services/audio/openai_stt_service.py +32 -6
  55. isa_model/inference/services/base_service.py +17 -1
  56. isa_model/inference/services/embedding/__init__.py +13 -0
  57. isa_model/inference/services/embedding/base_embed_service.py +111 -8
  58. isa_model/inference/services/embedding/isa_embed_service.py +305 -0
  59. isa_model/inference/services/embedding/openai_embed_service.py +2 -4
  60. isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
  61. isa_model/inference/services/img/__init__.py +2 -2
  62. isa_model/inference/services/img/base_image_gen_service.py +24 -7
  63. isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
  64. isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
  65. isa_model/inference/services/img/services/replicate_flux.py +226 -0
  66. isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
  67. isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
  68. isa_model/inference/services/img/tests/test_img_client.py +297 -0
  69. isa_model/inference/services/llm/base_llm_service.py +30 -6
  70. isa_model/inference/services/llm/helpers/llm_adapter.py +63 -9
  71. isa_model/inference/services/llm/ollama_llm_service.py +2 -1
  72. isa_model/inference/services/llm/openai_llm_service.py +652 -55
  73. isa_model/inference/services/llm/yyds_llm_service.py +2 -1
  74. isa_model/inference/services/vision/__init__.py +5 -5
  75. isa_model/inference/services/vision/base_vision_service.py +118 -185
  76. isa_model/inference/services/vision/helpers/image_utils.py +11 -5
  77. isa_model/inference/services/vision/isa_vision_service.py +573 -0
  78. isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
  79. isa_model/serving/api/fastapi_server.py +88 -16
  80. isa_model/serving/api/middleware/auth.py +311 -0
  81. isa_model/serving/api/middleware/security.py +278 -0
  82. isa_model/serving/api/routes/analytics.py +486 -0
  83. isa_model/serving/api/routes/deployments.py +339 -0
  84. isa_model/serving/api/routes/evaluations.py +579 -0
  85. isa_model/serving/api/routes/logs.py +430 -0
  86. isa_model/serving/api/routes/settings.py +582 -0
  87. isa_model/serving/api/routes/unified.py +324 -165
  88. isa_model/serving/api/startup.py +304 -0
  89. isa_model/serving/modal_proxy_server.py +249 -0
  90. isa_model/training/__init__.py +100 -6
  91. isa_model/training/core/__init__.py +4 -1
  92. isa_model/training/examples/intelligent_training_example.py +281 -0
  93. isa_model/training/intelligent/__init__.py +25 -0
  94. isa_model/training/intelligent/decision_engine.py +643 -0
  95. isa_model/training/intelligent/intelligent_factory.py +888 -0
  96. isa_model/training/intelligent/knowledge_base.py +751 -0
  97. isa_model/training/intelligent/resource_optimizer.py +839 -0
  98. isa_model/training/intelligent/task_classifier.py +576 -0
  99. isa_model/training/storage/__init__.py +24 -0
  100. isa_model/training/storage/core_integration.py +439 -0
  101. isa_model/training/storage/training_repository.py +552 -0
  102. isa_model/training/storage/training_storage.py +628 -0
  103. {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/METADATA +13 -1
  104. isa_model-0.4.0.dist-info/RECORD +182 -0
  105. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
  106. isa_model/deployment/cloud/modal/register_models.py +0 -321
  107. isa_model/inference/adapter/unified_api.py +0 -248
  108. isa_model/inference/services/helpers/stacked_config.py +0 -148
  109. isa_model/inference/services/img/flux_professional_service.py +0 -603
  110. isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
  111. isa_model/inference/services/others/table_transformer_service.py +0 -61
  112. isa_model/inference/services/vision/doc_analysis_service.py +0 -640
  113. isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
  114. isa_model/inference/services/vision/ui_analysis_service.py +0 -823
  115. isa_model/scripts/inference_tracker.py +0 -283
  116. isa_model/scripts/mlflow_manager.py +0 -379
  117. isa_model/scripts/model_registry.py +0 -465
  118. isa_model/scripts/register_models.py +0 -370
  119. isa_model/scripts/register_models_with_embeddings.py +0 -510
  120. isa_model/scripts/start_mlflow.py +0 -95
  121. isa_model/scripts/training_tracker.py +0 -257
  122. isa_model-0.3.9.dist-info/RECORD +0 -138
  123. {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/WHEEL +0 -0
  124. {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,486 @@
1
+ """
2
+ Analytics API Routes
3
+
4
+ Provides comprehensive analytics data for the ISA Model Platform including
5
+ usage statistics, cost analysis, model performance, and user activity metrics.
6
+ """
7
+
8
+ from fastapi import APIRouter, Query, HTTPException, Depends
9
+ from pydantic import BaseModel
10
+ from typing import Optional, List, Dict, Any
11
+ import logging
12
+ from datetime import datetime, timedelta, date
13
+ import asyncpg
14
+ import os
15
+ from collections import defaultdict
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ router = APIRouter()
20
+
21
+ # Database connection configuration
22
+ DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@127.0.0.1:54322/postgres?options=-c%20search_path%3Ddev")
23
+
24
+ class AnalyticsDateRange(BaseModel):
25
+ start_date: Optional[str] = None
26
+ end_date: Optional[str] = None
27
+ period: Optional[str] = "7d" # 1d, 7d, 30d, 90d
28
+
29
+ class UsageStats(BaseModel):
30
+ total_requests: int
31
+ total_cost_usd: float
32
+ total_tokens: int
33
+ unique_models: int
34
+ period_label: str
35
+
36
+ class ModelUsageStats(BaseModel):
37
+ model_id: str
38
+ provider: str
39
+ service_type: str
40
+ total_requests: int
41
+ total_cost_usd: float
42
+ total_tokens: int
43
+ avg_daily_requests: float
44
+
45
+ class ServiceTypeStats(BaseModel):
46
+ service_type: str
47
+ total_requests: int
48
+ total_cost_usd: float
49
+ total_tokens: int
50
+ model_count: int
51
+
52
+ async def get_db_connection():
53
+ """Get database connection"""
54
+ try:
55
+ conn = await asyncpg.connect(DATABASE_URL)
56
+ return conn
57
+ except Exception as e:
58
+ logger.error(f"Failed to connect to database: {e}")
59
+ raise HTTPException(status_code=500, detail="Database connection failed")
60
+
61
+ def parse_date_range(period: str) -> tuple:
62
+ """Parse period string into start and end dates"""
63
+ end_date = date.today()
64
+
65
+ if period == "1d":
66
+ start_date = end_date - timedelta(days=1)
67
+ elif period == "7d":
68
+ start_date = end_date - timedelta(days=7)
69
+ elif period == "30d":
70
+ start_date = end_date - timedelta(days=30)
71
+ elif period == "90d":
72
+ start_date = end_date - timedelta(days=90)
73
+ else:
74
+ start_date = end_date - timedelta(days=7)
75
+
76
+ return start_date, end_date
77
+
78
+ @router.get("/overview")
79
+ async def get_analytics_overview(
80
+ period: str = Query("7d", description="Time period (1d, 7d, 30d, 90d)")
81
+ ):
82
+ """Get high-level analytics overview"""
83
+ try:
84
+ start_date, end_date = parse_date_range(period)
85
+
86
+ conn = await get_db_connection()
87
+ try:
88
+ # Get overall statistics
89
+ stats_query = """
90
+ SELECT
91
+ SUM(total_requests) as total_requests,
92
+ SUM(total_cost_usd) as total_cost_usd,
93
+ SUM(total_tokens) as total_tokens,
94
+ COUNT(DISTINCT model_id) as unique_models,
95
+ COUNT(DISTINCT provider) as unique_providers,
96
+ COUNT(DISTINCT service_type) as unique_services
97
+ FROM dev.model_statistics
98
+ WHERE date >= $1 AND date <= $2
99
+ """
100
+
101
+ stats_result = await conn.fetchrow(stats_query, start_date, end_date)
102
+
103
+ # Get daily trend data
104
+ trend_query = """
105
+ SELECT
106
+ date,
107
+ SUM(total_requests) as daily_requests,
108
+ SUM(total_cost_usd) as daily_cost,
109
+ SUM(total_tokens) as daily_tokens
110
+ FROM dev.model_statistics
111
+ WHERE date >= $1 AND date <= $2
112
+ GROUP BY date
113
+ ORDER BY date
114
+ """
115
+
116
+ trend_results = await conn.fetch(trend_query, start_date, end_date)
117
+
118
+ # Format response
119
+ overview = {
120
+ "summary": {
121
+ "total_requests": int(stats_result["total_requests"] or 0),
122
+ "total_cost_usd": float(stats_result["total_cost_usd"] or 0),
123
+ "total_tokens": int(stats_result["total_tokens"] or 0),
124
+ "unique_models": int(stats_result["unique_models"] or 0),
125
+ "unique_providers": int(stats_result["unique_providers"] or 0),
126
+ "unique_services": int(stats_result["unique_services"] or 0),
127
+ "period": period,
128
+ "start_date": start_date.isoformat(),
129
+ "end_date": end_date.isoformat()
130
+ },
131
+ "daily_trends": [
132
+ {
133
+ "date": row["date"].isoformat(),
134
+ "requests": int(row["daily_requests"] or 0),
135
+ "cost_usd": float(row["daily_cost"] or 0),
136
+ "tokens": int(row["daily_tokens"] or 0)
137
+ }
138
+ for row in trend_results
139
+ ]
140
+ }
141
+
142
+ return overview
143
+
144
+ finally:
145
+ await conn.close()
146
+
147
+ except Exception as e:
148
+ logger.error(f"Error getting analytics overview: {e}")
149
+ raise HTTPException(status_code=500, detail=f"Failed to get analytics overview: {str(e)}")
150
+
151
+ @router.get("/models")
152
+ async def get_model_analytics(
153
+ period: str = Query("7d", description="Time period"),
154
+ service_type: Optional[str] = Query(None, description="Filter by service type"),
155
+ limit: int = Query(20, description="Limit number of results")
156
+ ):
157
+ """Get model usage analytics"""
158
+ try:
159
+ start_date, end_date = parse_date_range(period)
160
+
161
+ conn = await get_db_connection()
162
+ try:
163
+ # Build query with optional service type filter
164
+ where_clause = "WHERE date >= $1 AND date <= $2"
165
+ params = [start_date, end_date]
166
+
167
+ if service_type:
168
+ where_clause += " AND service_type = $3"
169
+ params.append(service_type)
170
+
171
+ # Calculate days for average calculation
172
+ days_in_period = (end_date - start_date).days + 1
173
+
174
+ if service_type:
175
+ query = """
176
+ SELECT
177
+ model_id,
178
+ provider,
179
+ service_type,
180
+ SUM(total_requests) as total_requests,
181
+ SUM(total_cost_usd) as total_cost_usd,
182
+ SUM(total_tokens) as total_tokens,
183
+ ROUND(SUM(total_requests)::numeric / $4, 2) as avg_daily_requests
184
+ FROM dev.model_statistics
185
+ WHERE date >= $1 AND date <= $2 AND service_type = $3
186
+ GROUP BY model_id, provider, service_type
187
+ ORDER BY total_requests DESC
188
+ LIMIT $5
189
+ """
190
+ params = [start_date, end_date, service_type, days_in_period, limit]
191
+ else:
192
+ query = """
193
+ SELECT
194
+ model_id,
195
+ provider,
196
+ service_type,
197
+ SUM(total_requests) as total_requests,
198
+ SUM(total_cost_usd) as total_cost_usd,
199
+ SUM(total_tokens) as total_tokens,
200
+ ROUND(SUM(total_requests)::numeric / $3, 2) as avg_daily_requests
201
+ FROM dev.model_statistics
202
+ WHERE date >= $1 AND date <= $2
203
+ GROUP BY model_id, provider, service_type
204
+ ORDER BY total_requests DESC
205
+ LIMIT $4
206
+ """
207
+ params = [start_date, end_date, days_in_period, limit]
208
+
209
+ results = await conn.fetch(query, *params)
210
+
211
+ models = [
212
+ {
213
+ "model_id": row["model_id"],
214
+ "provider": row["provider"],
215
+ "service_type": row["service_type"],
216
+ "total_requests": int(row["total_requests"] or 0),
217
+ "total_cost_usd": float(row["total_cost_usd"] or 0),
218
+ "total_tokens": int(row["total_tokens"] or 0),
219
+ "avg_daily_requests": float(row["avg_daily_requests"] or 0)
220
+ }
221
+ for row in results
222
+ ]
223
+
224
+ return {
225
+ "models": models,
226
+ "period": period,
227
+ "service_type_filter": service_type,
228
+ "total_models": len(models)
229
+ }
230
+
231
+ finally:
232
+ await conn.close()
233
+
234
+ except Exception as e:
235
+ logger.error(f"Error getting model analytics: {e}")
236
+ raise HTTPException(status_code=500, detail=f"Failed to get model analytics: {str(e)}")
237
+
238
+ @router.get("/services")
239
+ async def get_service_analytics(
240
+ period: str = Query("7d", description="Time period")
241
+ ):
242
+ """Get service type analytics"""
243
+ try:
244
+ start_date, end_date = parse_date_range(period)
245
+
246
+ conn = await get_db_connection()
247
+ try:
248
+ query = """
249
+ SELECT
250
+ service_type,
251
+ SUM(total_requests) as total_requests,
252
+ SUM(total_cost_usd) as total_cost_usd,
253
+ SUM(total_tokens) as total_tokens,
254
+ COUNT(DISTINCT model_id) as model_count,
255
+ COUNT(DISTINCT provider) as provider_count
256
+ FROM dev.model_statistics
257
+ WHERE date >= $1 AND date <= $2
258
+ GROUP BY service_type
259
+ ORDER BY total_requests DESC
260
+ """
261
+
262
+ results = await conn.fetch(query, start_date, end_date)
263
+
264
+ services = [
265
+ {
266
+ "service_type": row["service_type"],
267
+ "total_requests": int(row["total_requests"] or 0),
268
+ "total_cost_usd": float(row["total_cost_usd"] or 0),
269
+ "total_tokens": int(row["total_tokens"] or 0),
270
+ "model_count": int(row["model_count"] or 0),
271
+ "provider_count": int(row["provider_count"] or 0)
272
+ }
273
+ for row in results
274
+ ]
275
+
276
+ return {
277
+ "services": services,
278
+ "period": period,
279
+ "total_services": len(services)
280
+ }
281
+
282
+ finally:
283
+ await conn.close()
284
+
285
+ except Exception as e:
286
+ logger.error(f"Error getting service analytics: {e}")
287
+ raise HTTPException(status_code=500, detail=f"Failed to get service analytics: {str(e)}")
288
+
289
+ @router.get("/costs")
290
+ async def get_cost_analytics(
291
+ period: str = Query("7d", description="Time period"),
292
+ breakdown: str = Query("daily", description="Breakdown type (daily, service, model, provider)")
293
+ ):
294
+ """Get detailed cost analytics"""
295
+ try:
296
+ start_date, end_date = parse_date_range(period)
297
+
298
+ conn = await get_db_connection()
299
+ try:
300
+ if breakdown == "daily":
301
+ query = """
302
+ SELECT
303
+ date,
304
+ SUM(total_cost_usd) as total_cost,
305
+ SUM(total_requests) as total_requests
306
+ FROM dev.model_statistics
307
+ WHERE date >= $1 AND date <= $2
308
+ GROUP BY date
309
+ ORDER BY date
310
+ """
311
+
312
+ elif breakdown == "service":
313
+ query = """
314
+ SELECT
315
+ service_type as category,
316
+ SUM(total_cost_usd) as total_cost,
317
+ SUM(total_requests) as total_requests
318
+ FROM dev.model_statistics
319
+ WHERE date >= $1 AND date <= $2
320
+ GROUP BY service_type
321
+ ORDER BY total_cost DESC
322
+ """
323
+
324
+ elif breakdown == "model":
325
+ query = """
326
+ SELECT
327
+ model_id as category,
328
+ SUM(total_cost_usd) as total_cost,
329
+ SUM(total_requests) as total_requests
330
+ FROM dev.model_statistics
331
+ WHERE date >= $1 AND date <= $2
332
+ GROUP BY model_id
333
+ ORDER BY total_cost DESC
334
+ LIMIT 10
335
+ """
336
+
337
+ elif breakdown == "provider":
338
+ query = """
339
+ SELECT
340
+ provider as category,
341
+ SUM(total_cost_usd) as total_cost,
342
+ SUM(total_requests) as total_requests
343
+ FROM dev.model_statistics
344
+ WHERE date >= $1 AND date <= $2
345
+ GROUP BY provider
346
+ ORDER BY total_cost DESC
347
+ """
348
+
349
+ else:
350
+ raise HTTPException(status_code=400, detail="Invalid breakdown type")
351
+
352
+ results = await conn.fetch(query, start_date, end_date)
353
+
354
+ cost_data = []
355
+ for row in results:
356
+ if breakdown == "daily":
357
+ cost_data.append({
358
+ "date": row["date"].isoformat(),
359
+ "total_cost": float(row["total_cost"] or 0),
360
+ "total_requests": int(row["total_requests"] or 0)
361
+ })
362
+ else:
363
+ cost_data.append({
364
+ "category": row["category"],
365
+ "total_cost": float(row["total_cost"] or 0),
366
+ "total_requests": int(row["total_requests"] or 0)
367
+ })
368
+
369
+ return {
370
+ "cost_data": cost_data,
371
+ "breakdown": breakdown,
372
+ "period": period,
373
+ "total_entries": len(cost_data)
374
+ }
375
+
376
+ finally:
377
+ await conn.close()
378
+
379
+ except Exception as e:
380
+ logger.error(f"Error getting cost analytics: {e}")
381
+ raise HTTPException(status_code=500, detail=f"Failed to get cost analytics: {str(e)}")
382
+
383
+ @router.get("/users")
384
+ async def get_user_analytics(
385
+ period: str = Query("7d", description="Time period"),
386
+ limit: int = Query(10, description="Limit number of results")
387
+ ):
388
+ """Get user activity analytics"""
389
+ try:
390
+ start_date, end_date = parse_date_range(period)
391
+ start_datetime = datetime.combine(start_date, datetime.min.time())
392
+ end_datetime = datetime.combine(end_date, datetime.max.time())
393
+
394
+ conn = await get_db_connection()
395
+ try:
396
+ # Get user usage statistics
397
+ user_query = """
398
+ SELECT
399
+ user_id,
400
+ COUNT(*) as total_requests,
401
+ SUM(cost_usd) as total_cost,
402
+ SUM(tokens_used) as total_tokens,
403
+ COUNT(DISTINCT DATE(created_at)) as active_days
404
+ FROM dev.user_usage_records
405
+ WHERE created_at >= $1 AND created_at <= $2
406
+ GROUP BY user_id
407
+ ORDER BY total_requests DESC
408
+ LIMIT $3
409
+ """
410
+
411
+ user_results = await conn.fetch(user_query, start_datetime, end_datetime, limit)
412
+
413
+ # Get daily user activity
414
+ daily_query = """
415
+ SELECT
416
+ DATE(created_at) as date,
417
+ COUNT(DISTINCT user_id) as active_users,
418
+ COUNT(*) as total_requests
419
+ FROM dev.user_usage_records
420
+ WHERE created_at >= $1 AND created_at <= $2
421
+ GROUP BY DATE(created_at)
422
+ ORDER BY date
423
+ """
424
+
425
+ daily_results = await conn.fetch(daily_query, start_datetime, end_datetime)
426
+
427
+ users = [
428
+ {
429
+ "user_id": row["user_id"],
430
+ "total_requests": int(row["total_requests"] or 0),
431
+ "total_cost": float(row["total_cost"] or 0),
432
+ "total_tokens": int(row["total_tokens"] or 0),
433
+ "active_days": int(row["active_days"] or 0)
434
+ }
435
+ for row in user_results
436
+ ]
437
+
438
+ daily_activity = [
439
+ {
440
+ "date": row["date"].isoformat(),
441
+ "active_users": int(row["active_users"] or 0),
442
+ "total_requests": int(row["total_requests"] or 0)
443
+ }
444
+ for row in daily_results
445
+ ]
446
+
447
+ return {
448
+ "top_users": users,
449
+ "daily_activity": daily_activity,
450
+ "period": period,
451
+ "total_users_shown": len(users)
452
+ }
453
+
454
+ finally:
455
+ await conn.close()
456
+
457
+ except Exception as e:
458
+ logger.error(f"Error getting user analytics: {e}")
459
+ raise HTTPException(status_code=500, detail=f"Failed to get user analytics: {str(e)}")
460
+
461
+ @router.get("/health")
462
+ async def analytics_health():
463
+ """Health check for analytics service"""
464
+ try:
465
+ conn = await get_db_connection()
466
+ try:
467
+ # Test database connectivity
468
+ result = await conn.fetchval("SELECT COUNT(*) FROM dev.model_statistics")
469
+
470
+ return {
471
+ "status": "healthy",
472
+ "service": "analytics",
473
+ "total_model_records": result,
474
+ "database": "connected"
475
+ }
476
+
477
+ finally:
478
+ await conn.close()
479
+
480
+ except Exception as e:
481
+ logger.error(f"Analytics health check failed: {e}")
482
+ return {
483
+ "status": "unhealthy",
484
+ "service": "analytics",
485
+ "error": str(e)
486
+ }