Skip to content

Commit dcf5a73

Browse files
authored
Merge pull request #32 from tanzilahmed0/task-b24
Implemented Task b24
2 parents d5da333 + 95afcac commit dcf5a73

16 files changed

Lines changed: 921 additions & 501 deletions

backend/api/chat.py

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,8 @@ async def send_message(
286286
ai_content += f"\n\n**SQL Query:** `{query_result.sql_query}`"
287287
elif query_result.result_type == "chart":
288288
chart_type = "chart"
289-
if query_result.chart_config and query_result.chart_config.get('type'):
290-
chart_type = query_result.chart_config['type']
289+
if query_result.chart_config and query_result.chart_config.get("type"):
290+
chart_type = query_result.chart_config["type"]
291291
ai_content = f"I've created a {chart_type} visualization"
292292
if query_result.sql_query:
293293
ai_content += f"\n\n**SQL Query:** `{query_result.sql_query}`"
@@ -311,7 +311,9 @@ async def send_message(
311311
)
312312
MOCK_CHAT_MESSAGES[project_id].append(ai_message.model_dump())
313313

314-
response = SendMessageResponse(message=user_message, result=query_result, ai_message=ai_message)
314+
response = SendMessageResponse(
315+
message=user_message, result=query_result, ai_message=ai_message
316+
)
315317

316318
return ApiResponse(success=True, data=response)
317319

@@ -379,28 +381,30 @@ async def get_csv_preview(
379381
project_obj = project_service.get_project_by_id(project_uuid)
380382
if not project_obj:
381383
raise HTTPException(status_code=404, detail="Project not found")
382-
384+
383385
# Check if CSV file exists
384386
if not project_obj.csv_path:
385387
raise HTTPException(status_code=404, detail="CSV preview not available")
386-
388+
387389
# Load actual CSV data from storage
388390
preview = _load_csv_preview_from_storage(project_obj)
389-
391+
390392
if not preview:
391393
# Fallback to metadata-based preview if file loading fails
392394
preview = _generate_preview_from_metadata(project_obj)
393-
395+
394396
if not preview:
395397
raise HTTPException(status_code=404, detail="CSV preview not available")
396-
398+
397399
return ApiResponse(success=True, data=preview)
398-
400+
399401
except HTTPException:
400402
# Re-raise HTTPExceptions (like 404) as-is
401403
raise
402404
except Exception as e:
403-
raise HTTPException(status_code=500, detail=f"Error loading CSV preview: {str(e)}")
405+
raise HTTPException(
406+
status_code=500, detail=f"Error loading CSV preview: {str(e)}"
407+
)
404408

405409

406410
def _load_csv_preview_from_storage(project_obj) -> Optional[CSVPreview]:
@@ -409,37 +413,37 @@ def _load_csv_preview_from_storage(project_obj) -> Optional[CSVPreview]:
409413
from services.storage_service import storage_service
410414
import pandas as pd
411415
import io
412-
416+
413417
# Download CSV file from storage
414418
csv_bytes = storage_service.download_file(project_obj.csv_path)
415419
if not csv_bytes:
416420
return None
417-
421+
418422
# Read CSV into pandas DataFrame
419423
csv_buffer = io.BytesIO(csv_bytes)
420424
df = pd.read_csv(csv_buffer)
421-
425+
422426
# Get first 5 rows for preview
423427
preview_df = df.head(5)
424-
428+
425429
# Extract column information
426430
columns = list(df.columns)
427431
sample_data = preview_df.values.tolist()
428432
total_rows = len(df)
429-
433+
430434
# Determine data types
431435
data_types = {}
432436
for col in columns:
433437
dtype = str(df[col].dtype)
434-
if 'int' in dtype or 'float' in dtype:
435-
data_types[col] = 'number'
436-
elif 'datetime' in dtype or 'date' in dtype:
437-
data_types[col] = 'date'
438-
elif 'bool' in dtype:
439-
data_types[col] = 'boolean'
438+
if "int" in dtype or "float" in dtype:
439+
data_types[col] = "number"
440+
elif "datetime" in dtype or "date" in dtype:
441+
data_types[col] = "date"
442+
elif "bool" in dtype:
443+
data_types[col] = "boolean"
440444
else:
441-
data_types[col] = 'string'
442-
445+
data_types[col] = "string"
446+
443447
# Convert any non-serializable values to strings
444448
serializable_sample_data = []
445449
for row in sample_data:
@@ -452,14 +456,14 @@ def _load_csv_preview_from_storage(project_obj) -> Optional[CSVPreview]:
452456
else:
453457
serializable_row.append(value)
454458
serializable_sample_data.append(serializable_row)
455-
459+
456460
return CSVPreview(
457461
columns=columns,
458462
sample_data=serializable_sample_data,
459463
total_rows=total_rows,
460-
data_types=data_types
464+
data_types=data_types,
461465
)
462-
466+
463467
except Exception as e:
464468
logger.error(f"Error loading CSV preview from storage: {str(e)}")
465469
return None
@@ -470,37 +474,40 @@ def _generate_preview_from_metadata(project_obj) -> Optional[CSVPreview]:
470474
try:
471475
if not project_obj.columns_metadata:
472476
return None
473-
477+
474478
# Extract column names and types
475-
columns = [col.get('name', '') for col in project_obj.columns_metadata]
476-
data_types = {col.get('name', ''): col.get('type', 'unknown') for col in project_obj.columns_metadata}
477-
479+
columns = [col.get("name", "") for col in project_obj.columns_metadata]
480+
data_types = {
481+
col.get("name", ""): col.get("type", "unknown")
482+
for col in project_obj.columns_metadata
483+
}
484+
478485
# Generate sample data from metadata
479486
sample_data = []
480487
for i in range(min(5, project_obj.row_count or 5)): # Show max 5 sample rows
481488
row = []
482489
for col in project_obj.columns_metadata:
483-
sample_values = col.get('sample_values', [])
490+
sample_values = col.get("sample_values", [])
484491
if sample_values and len(sample_values) > i:
485492
row.append(sample_values[i])
486493
else:
487494
# Generate placeholder based on type
488-
col_type = col.get('type', 'string')
489-
if col_type == 'number':
495+
col_type = col.get("type", "string")
496+
if col_type == "number":
490497
row.append(0)
491-
elif col_type == 'date':
492-
row.append('2024-01-01')
498+
elif col_type == "date":
499+
row.append("2024-01-01")
493500
else:
494501
row.append(f"Sample {i+1}")
495502
sample_data.append(row)
496-
503+
497504
return CSVPreview(
498505
columns=columns,
499506
sample_data=sample_data,
500507
total_rows=project_obj.row_count or 0,
501-
data_types=data_types
508+
data_types=data_types,
502509
)
503-
510+
504511
except Exception as e:
505512
logger.error(f"Error generating preview from metadata: {str(e)}")
506513
return None

backend/api/health.py

Lines changed: 88 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import os
22
from datetime import datetime
3-
from typing import Any, Dict
43

54
from fastapi import APIRouter
65

76
from middleware.monitoring import query_performance_tracker
7+
from models.response_schemas import (
8+
ApiResponse,
9+
HealthDetail,
10+
HealthStatus,
11+
HealthChecks,
12+
HealthDetails,
13+
PerformanceMetrics,
14+
)
815
from services.database_service import get_db_service
916
from services.redis_service import redis_service
1017
from services.storage_service import storage_service
@@ -13,7 +20,7 @@
1320

1421

1522
@router.get("/")
16-
async def health_check() -> Dict[str, Any]:
23+
async def health_check() -> ApiResponse[HealthStatus]:
1724
"""Detailed health check endpoint with infrastructure service checks"""
1825

1926
# Check if we're in test environment
@@ -23,26 +30,24 @@ async def health_check() -> Dict[str, Any]:
2330

2431
if is_test_env:
2532
# Return healthy status for tests without connecting to real services
26-
return {
27-
"success": True,
28-
"data": {
29-
"status": "healthy",
30-
"service": "SmartQuery API",
31-
"version": "1.0.0",
32-
"timestamp": datetime.utcnow().isoformat() + "Z",
33-
"checks": {
34-
"database": True,
35-
"redis": True,
36-
"storage": True,
37-
"llm_service": False, # Will be implemented in Task B15
38-
},
39-
"details": {
40-
"database": {"status": "healthy", "message": "Test mode"},
41-
"redis": {"status": "healthy", "message": "Test mode"},
42-
"storage": {"status": "healthy", "message": "Test mode"},
43-
},
44-
},
45-
}
33+
health_status = HealthStatus(
34+
status="healthy",
35+
service="SmartQuery API",
36+
version="1.0.0",
37+
timestamp=datetime.utcnow().isoformat() + "Z",
38+
checks=HealthChecks(
39+
database=True,
40+
redis=True,
41+
storage=True,
42+
llm_service=False, # LLM service implemented
43+
),
44+
details=HealthDetails(
45+
database=HealthDetail(status="healthy", message="Test mode"),
46+
redis=HealthDetail(status="healthy", message="Test mode"),
47+
storage=HealthDetail(status="healthy", message="Test mode"),
48+
),
49+
)
50+
return ApiResponse(success=True, data=health_status)
4651

4752
# Check all services in production
4853
database_health = get_db_service().health_check()
@@ -58,30 +63,39 @@ async def health_check() -> Dict[str, Any]:
5863

5964
overall_status = "healthy" if all_healthy else "partial"
6065

61-
return {
62-
"success": True,
63-
"data": {
64-
"status": overall_status,
65-
"service": "SmartQuery API",
66-
"version": "1.0.0",
67-
"timestamp": datetime.utcnow().isoformat() + "Z",
68-
"checks": {
69-
"database": database_health.get("status") == "healthy",
70-
"redis": redis_health.get("status") == "healthy",
71-
"storage": storage_health.get("status") == "healthy",
72-
"llm_service": False, # Will be implemented in Task B15
73-
},
74-
"details": {
75-
"database": database_health,
76-
"redis": redis_health,
77-
"storage": storage_health,
78-
},
79-
},
80-
}
66+
# Create standardized response
67+
health_status = HealthStatus(
68+
status=overall_status,
69+
service="SmartQuery API",
70+
version="1.0.0",
71+
timestamp=datetime.utcnow().isoformat() + "Z",
72+
checks=HealthChecks(
73+
database=database_health.get("status") == "healthy",
74+
redis=redis_health.get("status") == "healthy",
75+
storage=storage_health.get("status") == "healthy",
76+
llm_service=True, # LLM service implemented
77+
),
78+
details=HealthDetails(
79+
database=HealthDetail(
80+
status=database_health.get("status", "unknown"),
81+
message=database_health.get("message", "No details available"),
82+
),
83+
redis=HealthDetail(
84+
status=redis_health.get("status", "unknown"),
85+
message=redis_health.get("message", "No details available"),
86+
),
87+
storage=HealthDetail(
88+
status=storage_health.get("status", "unknown"),
89+
message=storage_health.get("message", "No details available"),
90+
),
91+
),
92+
)
93+
94+
return ApiResponse(success=True, data=health_status)
8195

8296

8397
@router.get("/metrics")
84-
async def get_performance_metrics() -> Dict[str, Any]:
98+
async def get_performance_metrics() -> ApiResponse[PerformanceMetrics]:
8599
"""Get performance metrics for monitoring and bottleneck identification"""
86100

87101
try:
@@ -115,36 +129,37 @@ async def get_performance_metrics() -> Dict[str, Any]:
115129
# Identify bottlenecks (operations taking > 2 seconds on average)
116130
bottlenecks = [op for op in slowest_operations if op["avg_time"] > 2.0]
117131

118-
return {
119-
"success": True,
120-
"data": {
121-
"timestamp": datetime.utcnow().isoformat() + "Z",
122-
"summary": {
123-
"total_operations": total_operations,
124-
"total_time": round(total_time, 3),
125-
"average_time": round(avg_time_overall, 3),
126-
"unique_operations": len(operations_summary),
127-
},
128-
"operations": operations_summary,
129-
"slowest_operations": slowest_operations,
130-
"bottlenecks": bottlenecks,
131-
"performance_alerts": [
132-
f"Operation '{op['operation']}' averages {op['avg_time']:.3f}s per call"
133-
for op in bottlenecks
134-
],
132+
performance_metrics = PerformanceMetrics(
133+
timestamp=datetime.utcnow().isoformat() + "Z",
134+
summary={
135+
"total_operations": total_operations,
136+
"total_time": round(total_time, 3),
137+
"average_time": round(avg_time_overall, 3),
138+
"unique_operations": len(operations_summary),
135139
},
136-
}
140+
operations=operations_summary,
141+
slowest_operations=slowest_operations,
142+
bottlenecks=bottlenecks,
143+
performance_alerts=[
144+
f"Operation '{op['operation']}' averages {op['avg_time']:.3f}s per call"
145+
for op in bottlenecks
146+
],
147+
)
148+
149+
return ApiResponse(success=True, data=performance_metrics)
137150

138151
except Exception as e:
139-
return {
140-
"success": False,
141-
"error": f"Failed to retrieve performance metrics: {str(e)}",
142-
"data": {
143-
"timestamp": datetime.utcnow().isoformat() + "Z",
144-
"summary": {},
145-
"operations": {},
146-
"slowest_operations": [],
147-
"bottlenecks": [],
148-
"performance_alerts": [],
149-
},
150-
}
152+
# Return error in standardized format
153+
error_metrics = PerformanceMetrics(
154+
timestamp=datetime.utcnow().isoformat() + "Z",
155+
summary={},
156+
operations={},
157+
slowest_operations=[],
158+
bottlenecks=[],
159+
performance_alerts=[],
160+
)
161+
return ApiResponse(
162+
success=False,
163+
error=f"Failed to retrieve performance metrics: {str(e)}",
164+
data=error_metrics,
165+
)

0 commit comments

Comments
 (0)