modular monolythic

This commit is contained in:
rafaeldpsilva
2025-12-20 00:51:04 +00:00
parent 6ed61b06e8
commit 4779eb9ded
24 changed files with 5135 additions and 0 deletions

View File

@@ -0,0 +1,475 @@
"""Sensors module API routes."""
import logging
from datetime import datetime
from fastapi import APIRouter, HTTPException, Depends, WebSocket, WebSocketDisconnect, Query, BackgroundTasks
from typing import Optional
from .models import (
SensorReading, SensorMetadata, RoomCreate, RoomUpdate, DataQuery, DataResponse,
SensorType, SensorStatus, HealthResponse
)
from .sensor_service import SensorService
from .room_service import RoomService
from .analytics_service import AnalyticsService
from .websocket_manager import WebSocketManager
from src.core.dependencies import get_sensors_db, get_redis
logger = logging.getLogger(__name__)
# Create router
router = APIRouter()
# WebSocket manager (shared across all route handlers)
websocket_manager = WebSocketManager()
# Dependency functions
async def get_sensor_service(db=Depends(get_sensors_db), redis=Depends(get_redis)):
return SensorService(db, redis)
async def get_room_service(db=Depends(get_sensors_db), redis=Depends(get_redis)):
return RoomService(db, redis)
async def get_analytics_service(db=Depends(get_sensors_db), redis=Depends(get_redis)):
return AnalyticsService(db, redis)
# Health check
@router.get("/health", response_model=HealthResponse)
async def health_check(db=Depends(get_sensors_db)):
"""Health check endpoint for sensors module"""
try:
await db.command("ping")
return HealthResponse(
service="sensors-module",
status="healthy",
timestamp=datetime.utcnow(),
version="1.0.0"
)
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(status_code=503, detail="Service Unavailable")
# WebSocket endpoint for real-time data
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint for real-time sensor data"""
await websocket_manager.connect(websocket)
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
await websocket_manager.disconnect(websocket)
# Sensor Management Routes
@router.get("/sensors/get")
async def get_sensors(
room: Optional[str] = Query(None, description="Filter by room"),
sensor_type: Optional[SensorType] = Query(None, description="Filter by sensor type"),
status: Optional[SensorStatus] = Query(None, description="Filter by status"),
service: SensorService = Depends(get_sensor_service)
):
"""Get all sensors with optional filtering"""
try:
sensors = await service.get_sensors(room=room, sensor_type=sensor_type, status=status)
return {
"sensors": sensors,
"count": len(sensors),
"filters": {
"room": room,
"sensor_type": sensor_type.value if sensor_type else None,
"status": status.value if status else None
}
}
except Exception as e:
logger.error(f"Error getting sensors: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/sensors/{sensor_id}")
async def get_sensor(sensor_id: str, service: SensorService = Depends(get_sensor_service)):
"""Get detailed sensor information"""
try:
sensor = await service.get_sensor_details(sensor_id)
if not sensor:
raise HTTPException(status_code=404, detail="Sensor not found")
return sensor
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting sensor {sensor_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/sensors/{sensor_id}/data")
async def get_sensor_data(
sensor_id: str,
start_time: Optional[int] = Query(None, description="Start timestamp (Unix)"),
end_time: Optional[int] = Query(None, description="End timestamp (Unix)"),
limit: int = Query(100, description="Maximum records to return"),
offset: int = Query(0, description="Records to skip"),
service: SensorService = Depends(get_sensor_service)
):
"""Get historical data for a specific sensor"""
try:
data = await service.get_sensor_data(
sensor_id=sensor_id,
start_time=start_time,
end_time=end_time,
limit=limit,
offset=offset
)
return DataResponse(
data=data["readings"],
total_count=data["total_count"],
query=DataQuery(
sensor_ids=[sensor_id],
start_time=start_time,
end_time=end_time,
limit=limit,
offset=offset
),
execution_time_ms=data.get("execution_time_ms", 0)
)
except Exception as e:
logger.error(f"Error getting sensor data for {sensor_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/sensors")
async def create_sensor(
sensor_data: SensorMetadata,
service: SensorService = Depends(get_sensor_service)
):
"""Register a new sensor"""
try:
result = await service.create_sensor(sensor_data)
return {
"message": "Sensor created successfully",
"sensor_id": sensor_data.sensor_id,
"created_at": result.get("created_at")
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error creating sensor: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/sensors/{sensor_id}")
async def update_sensor(
sensor_id: str,
update_data: dict,
service: SensorService = Depends(get_sensor_service)
):
"""Update sensor metadata"""
try:
result = await service.update_sensor(sensor_id, update_data)
if not result:
raise HTTPException(status_code=404, detail="Sensor not found")
return {
"message": "Sensor updated successfully",
"sensor_id": sensor_id,
"updated_at": datetime.utcnow().isoformat()
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating sensor {sensor_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/sensors/{sensor_id}")
async def delete_sensor(
sensor_id: str,
service: SensorService = Depends(get_sensor_service)
):
"""Delete a sensor and all its data"""
try:
result = await service.delete_sensor(sensor_id)
return {
"message": "Sensor deleted successfully",
"sensor_id": sensor_id,
"readings_deleted": result.get("readings_deleted", 0)
}
except Exception as e:
logger.error(f"Error deleting sensor {sensor_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# Room Management Routes
@router.get("/rooms/names")
async def get_room_names(service: RoomService = Depends(get_room_service)):
"""Get simple list of room names for dropdowns"""
try:
room_names = await service.get_all_room_names()
return {
"rooms": room_names,
"count": len(room_names)
}
except Exception as e:
logger.error(f"Error getting room names: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/rooms")
async def get_rooms(service: RoomService = Depends(get_room_service)):
"""Get all rooms with sensor counts and metrics"""
try:
rooms = await service.get_rooms()
return {
"rooms": rooms,
"count": len(rooms)
}
except Exception as e:
logger.error(f"Error getting rooms: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/rooms")
async def create_room(
room_data: RoomCreate,
service: RoomService = Depends(get_room_service)
):
"""Create a new room"""
try:
result = await service.create_room(room_data.dict())
return {
"message": "Room created successfully",
"room": result["name"],
"created_at": result["created_at"]
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error creating room: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/rooms/{room_name}")
async def update_room(
room_name: str,
room_data: RoomUpdate,
service: RoomService = Depends(get_room_service)
):
"""Update an existing room"""
try:
result = await service.update_room(room_name, room_data.dict(exclude_unset=True))
return {
"message": "Room updated successfully",
"room": result["name"],
"updated_at": result["updated_at"],
"modified": result["modified"]
}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
logger.error(f"Error updating room {room_name}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/rooms/{room_name}")
async def delete_room(room_name: str, service: RoomService = Depends(get_room_service)):
"""Delete a room"""
try:
result = await service.delete_room(room_name)
return {
"message": "Room deleted successfully",
**result
}
except Exception as e:
logger.error(f"Error deleting room {room_name}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/rooms/{room_name}")
async def get_room(room_name: str, service: RoomService = Depends(get_room_service)):
"""Get detailed room information"""
try:
room = await service.get_room_details(room_name)
if not room:
raise HTTPException(status_code=404, detail="Room not found")
return room
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting room {room_name}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/rooms/{room_name}/data")
async def get_room_data(
room_name: str,
start_time: Optional[int] = Query(None, description="Start timestamp (Unix)"),
end_time: Optional[int] = Query(None, description="End timestamp (Unix)"),
limit: int = Query(100, description="Maximum records to return"),
service: RoomService = Depends(get_room_service)
):
"""Get historical data for a specific room"""
try:
data = await service.get_room_data(
room_name=room_name,
start_time=start_time,
end_time=end_time,
limit=limit
)
return {
"room": room_name,
"room_metrics": data.get("room_metrics", []),
"sensor_readings": data.get("sensor_readings", []),
"period": {
"start_time": start_time,
"end_time": end_time
}
}
except Exception as e:
logger.error(f"Error getting room data for {room_name}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# Analytics Routes
@router.post("/data/query")
async def query_data(
query_params: DataQuery,
service: AnalyticsService = Depends(get_analytics_service)
):
"""Advanced data querying with multiple filters"""
try:
result = await service.query_data(query_params)
return result
except Exception as e:
logger.error(f"Error executing data query: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/analytics/summary")
async def get_analytics_summary(
hours: int = Query(24, description="Hours of data to analyze"),
service: AnalyticsService = Depends(get_analytics_service)
):
"""Get comprehensive analytics summary"""
try:
analytics = await service.get_analytics_summary(hours)
return analytics
except Exception as e:
logger.error(f"Error getting analytics summary: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/analytics/energy")
async def get_energy_analytics(
hours: int = Query(24),
room: Optional[str] = Query(None),
service: AnalyticsService = Depends(get_analytics_service)
):
"""Get energy-specific analytics"""
try:
analytics = await service.get_energy_analytics(hours, room)
return analytics
except Exception as e:
logger.error(f"Error getting energy analytics: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# Data Export
@router.get("/export")
async def export_data(
start_time: int = Query(..., description="Start timestamp (Unix)"),
end_time: int = Query(..., description="End timestamp (Unix)"),
sensor_ids: Optional[str] = Query(None, description="Comma-separated sensor IDs"),
format: str = Query("json", description="Export format (json, csv)"),
service: SensorService = Depends(get_sensor_service)
):
"""Export sensor data"""
try:
export_data_result = await service.export_data(
start_time=start_time,
end_time=end_time,
sensor_ids=sensor_ids,
format=format
)
return export_data_result
except Exception as e:
logger.error(f"Error exporting data: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# System Events
@router.get("/events")
async def get_events(
severity: Optional[str] = Query(None, description="Filter by severity"),
event_type: Optional[str] = Query(None, description="Filter by event type"),
hours: int = Query(24, description="Hours of events to retrieve"),
limit: int = Query(50, description="Maximum events to return"),
service: SensorService = Depends(get_sensor_service)
):
"""Get system events and alerts"""
try:
events = await service.get_events(
severity=severity,
event_type=event_type,
hours=hours,
limit=limit
)
return {
"events": events,
"count": len(events),
"period_hours": hours
}
except Exception as e:
logger.error(f"Error getting events: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# Real-time data ingestion endpoint
@router.post("/data/ingest")
async def ingest_sensor_data(
sensor_data: SensorReading,
background_tasks: BackgroundTasks,
service: SensorService = Depends(get_sensor_service),
room_service: RoomService = Depends(get_room_service)
):
"""Ingest real-time sensor data"""
try:
result = await service.ingest_sensor_data(sensor_data)
# Schedule background tasks
if sensor_data.room:
background_tasks.add_task(_update_room_metrics, room_service, sensor_data)
background_tasks.add_task(_broadcast_sensor_data, sensor_data)
return {
"message": "Sensor data ingested successfully",
"sensor_id": sensor_data.sensor_id,
"timestamp": sensor_data.timestamp
}
except Exception as e:
logger.error(f"Error ingesting sensor data: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# Background task helper functions
async def _update_room_metrics(room_service: RoomService, sensor_data: SensorReading):
"""Update room-level metrics when sensor data is received"""
try:
await room_service.update_room_metrics(sensor_data)
except Exception as e:
logger.error(f"Error updating room metrics: {e}")
async def _broadcast_sensor_data(sensor_data: SensorReading):
"""Broadcast sensor data to WebSocket clients"""
try:
await websocket_manager.broadcast_sensor_data(sensor_data)
except Exception as e:
logger.error(f"Error broadcasting sensor data: {e}")