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.
- isa_model/__init__.py +1 -1
- isa_model/client.py +732 -565
- isa_model/core/cache/redis_cache.py +401 -0
- isa_model/core/config/config_manager.py +53 -10
- isa_model/core/config.py +1 -1
- isa_model/core/database/__init__.py +1 -0
- isa_model/core/database/migrations.py +277 -0
- isa_model/core/database/supabase_client.py +123 -0
- isa_model/core/models/__init__.py +37 -0
- isa_model/core/models/model_billing_tracker.py +60 -88
- isa_model/core/models/model_manager.py +36 -18
- isa_model/core/models/model_repo.py +44 -38
- isa_model/core/models/model_statistics_tracker.py +234 -0
- isa_model/core/models/model_storage.py +0 -1
- isa_model/core/models/model_version_manager.py +959 -0
- isa_model/core/pricing_manager.py +2 -249
- isa_model/core/resilience/circuit_breaker.py +366 -0
- isa_model/core/security/secrets.py +358 -0
- isa_model/core/services/__init__.py +2 -4
- isa_model/core/services/intelligent_model_selector.py +101 -370
- isa_model/core/storage/hf_storage.py +1 -1
- isa_model/core/types.py +7 -0
- isa_model/deployment/cloud/modal/isa_audio_chatTTS_service.py +520 -0
- isa_model/deployment/cloud/modal/isa_audio_fish_service.py +0 -0
- isa_model/deployment/cloud/modal/isa_audio_openvoice_service.py +758 -0
- isa_model/deployment/cloud/modal/isa_audio_service_v2.py +1044 -0
- isa_model/deployment/cloud/modal/isa_embed_rerank_service.py +296 -0
- isa_model/deployment/cloud/modal/isa_video_hunyuan_service.py +423 -0
- isa_model/deployment/cloud/modal/isa_vision_ocr_service.py +519 -0
- isa_model/deployment/cloud/modal/isa_vision_qwen25_service.py +709 -0
- isa_model/deployment/cloud/modal/isa_vision_table_service.py +467 -323
- isa_model/deployment/cloud/modal/isa_vision_ui_service.py +607 -180
- isa_model/deployment/cloud/modal/isa_vision_ui_service_optimized.py +660 -0
- isa_model/deployment/core/deployment_manager.py +6 -4
- isa_model/deployment/services/auto_hf_modal_deployer.py +894 -0
- isa_model/eval/benchmarks/__init__.py +27 -0
- isa_model/eval/benchmarks/multimodal_datasets.py +460 -0
- isa_model/eval/benchmarks.py +244 -12
- isa_model/eval/evaluators/__init__.py +8 -2
- isa_model/eval/evaluators/audio_evaluator.py +727 -0
- isa_model/eval/evaluators/embedding_evaluator.py +742 -0
- isa_model/eval/evaluators/vision_evaluator.py +564 -0
- isa_model/eval/example_evaluation.py +395 -0
- isa_model/eval/factory.py +272 -5
- isa_model/eval/isa_benchmarks.py +700 -0
- isa_model/eval/isa_integration.py +582 -0
- isa_model/eval/metrics.py +159 -6
- isa_model/eval/tests/unit/test_basic.py +396 -0
- isa_model/inference/ai_factory.py +44 -8
- isa_model/inference/services/audio/__init__.py +21 -0
- isa_model/inference/services/audio/base_realtime_service.py +225 -0
- isa_model/inference/services/audio/isa_tts_service.py +0 -0
- isa_model/inference/services/audio/openai_realtime_service.py +320 -124
- isa_model/inference/services/audio/openai_stt_service.py +32 -6
- isa_model/inference/services/base_service.py +17 -1
- isa_model/inference/services/embedding/__init__.py +13 -0
- isa_model/inference/services/embedding/base_embed_service.py +111 -8
- isa_model/inference/services/embedding/isa_embed_service.py +305 -0
- isa_model/inference/services/embedding/openai_embed_service.py +2 -4
- isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
- isa_model/inference/services/img/__init__.py +2 -2
- isa_model/inference/services/img/base_image_gen_service.py +24 -7
- isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
- isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
- isa_model/inference/services/img/services/replicate_flux.py +226 -0
- isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
- isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
- isa_model/inference/services/img/tests/test_img_client.py +297 -0
- isa_model/inference/services/llm/base_llm_service.py +30 -6
- isa_model/inference/services/llm/helpers/llm_adapter.py +63 -9
- isa_model/inference/services/llm/ollama_llm_service.py +2 -1
- isa_model/inference/services/llm/openai_llm_service.py +652 -55
- isa_model/inference/services/llm/yyds_llm_service.py +2 -1
- isa_model/inference/services/vision/__init__.py +5 -5
- isa_model/inference/services/vision/base_vision_service.py +118 -185
- isa_model/inference/services/vision/helpers/image_utils.py +11 -5
- isa_model/inference/services/vision/isa_vision_service.py +573 -0
- isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
- isa_model/serving/api/fastapi_server.py +88 -16
- isa_model/serving/api/middleware/auth.py +311 -0
- isa_model/serving/api/middleware/security.py +278 -0
- isa_model/serving/api/routes/analytics.py +486 -0
- isa_model/serving/api/routes/deployments.py +339 -0
- isa_model/serving/api/routes/evaluations.py +579 -0
- isa_model/serving/api/routes/logs.py +430 -0
- isa_model/serving/api/routes/settings.py +582 -0
- isa_model/serving/api/routes/unified.py +324 -165
- isa_model/serving/api/startup.py +304 -0
- isa_model/serving/modal_proxy_server.py +249 -0
- isa_model/training/__init__.py +100 -6
- isa_model/training/core/__init__.py +4 -1
- isa_model/training/examples/intelligent_training_example.py +281 -0
- isa_model/training/intelligent/__init__.py +25 -0
- isa_model/training/intelligent/decision_engine.py +643 -0
- isa_model/training/intelligent/intelligent_factory.py +888 -0
- isa_model/training/intelligent/knowledge_base.py +751 -0
- isa_model/training/intelligent/resource_optimizer.py +839 -0
- isa_model/training/intelligent/task_classifier.py +576 -0
- isa_model/training/storage/__init__.py +24 -0
- isa_model/training/storage/core_integration.py +439 -0
- isa_model/training/storage/training_repository.py +552 -0
- isa_model/training/storage/training_storage.py +628 -0
- {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/METADATA +13 -1
- isa_model-0.4.0.dist-info/RECORD +182 -0
- isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
- isa_model/deployment/cloud/modal/register_models.py +0 -321
- isa_model/inference/adapter/unified_api.py +0 -248
- isa_model/inference/services/helpers/stacked_config.py +0 -148
- isa_model/inference/services/img/flux_professional_service.py +0 -603
- isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
- isa_model/inference/services/others/table_transformer_service.py +0 -61
- isa_model/inference/services/vision/doc_analysis_service.py +0 -640
- isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
- isa_model/inference/services/vision/ui_analysis_service.py +0 -823
- isa_model/scripts/inference_tracker.py +0 -283
- isa_model/scripts/mlflow_manager.py +0 -379
- isa_model/scripts/model_registry.py +0 -465
- isa_model/scripts/register_models.py +0 -370
- isa_model/scripts/register_models_with_embeddings.py +0 -510
- isa_model/scripts/start_mlflow.py +0 -95
- isa_model/scripts/training_tracker.py +0 -257
- isa_model-0.3.9.dist-info/RECORD +0 -138
- {isa_model-0.3.9.dist-info → isa_model-0.4.0.dist-info}/WHEEL +0 -0
- {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
|
+
}
|