first commit

This commit is contained in:
rafaeldpsilva
2025-09-09 13:46:42 +01:00
commit a7a18e6295
77 changed files with 8678 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
"""
Room metrics business logic service
Business Layer - handles room-related aggregations and business operations
"""
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
import logging
from models import RoomMetrics, CO2Status, OccupancyLevel
from ..infrastructure.repositories import (
SensorReadingRepository, RoomMetricsRepository, RedisRepository
)
logger = logging.getLogger(__name__)
class RoomService:
"""Service for room-related business operations"""
def __init__(self):
self.sensor_reading_repo = SensorReadingRepository()
self.room_metrics_repo = RoomMetricsRepository()
self.redis_repo = RedisRepository()
async def update_room_metrics(self, room: str) -> bool:
"""Calculate and store room-level metrics"""
if not room:
return False
try:
# Get recent readings for this room (last 5 minutes)
recent_readings = await self.sensor_reading_repo.get_recent_by_room(
room=room,
minutes=5
)
if not recent_readings:
return False
# Calculate aggregated metrics
metrics = await self._calculate_room_metrics(room, recent_readings)
# Store in MongoDB
stored = await self.room_metrics_repo.create(metrics)
# Cache in Redis
if stored:
await self.redis_repo.set_room_metrics(room, metrics.dict())
logger.debug(f"Updated room metrics for {room}")
return stored
except Exception as e:
logger.error(f"Error updating room metrics for {room}: {e}")
return False
async def _calculate_room_metrics(self, room: str, readings: List[Dict]) -> RoomMetrics:
"""Calculate aggregated metrics for a room based on recent readings"""
# Group readings by sensor
sensors_data = {}
for reading in readings:
sensor_id = reading["sensor_id"]
if sensor_id not in sensors_data:
sensors_data[sensor_id] = []
sensors_data[sensor_id].append(reading)
# Initialize value arrays
energy_values = []
co2_values = []
temperature_values = []
humidity_values = []
motion_detected = False
# Extract values from readings
for sensor_readings in sensors_data.values():
for reading in sensor_readings:
if reading.get("energy"):
energy_values.append(reading["energy"]["value"])
if reading.get("co2"):
co2_values.append(reading["co2"]["value"])
if reading.get("temperature"):
temperature_values.append(reading["temperature"]["value"])
if reading.get("humidity"):
humidity_values.append(reading["humidity"]["value"])
if reading.get("motion") and reading["motion"].get("value") == "Detected":
motion_detected = True
# Get sensor types present
sensor_types = list(set(
reading.get("sensor_type")
for reading in readings
if reading.get("sensor_type")
))
# Initialize metrics object
metrics = RoomMetrics(
room=room,
timestamp=int(datetime.utcnow().timestamp()),
sensor_count=len(sensors_data),
active_sensors=list(sensors_data.keys()),
sensor_types=sensor_types,
motion_detected=motion_detected
)
# Calculate energy metrics
if energy_values:
metrics.energy = self._calculate_energy_metrics(energy_values)
# Calculate CO2 metrics and occupancy
if co2_values:
metrics.co2 = self._calculate_co2_metrics(co2_values)
metrics.occupancy_estimate = self._estimate_occupancy_from_co2(
metrics.co2["average"]
)
# Calculate temperature metrics
if temperature_values:
metrics.temperature = self._calculate_temperature_metrics(temperature_values)
# Calculate humidity metrics
if humidity_values:
metrics.humidity = self._calculate_humidity_metrics(humidity_values)
# Set last activity time if motion detected
if motion_detected:
metrics.last_activity = datetime.utcnow()
return metrics
def _calculate_energy_metrics(self, values: List[float]) -> Dict[str, Any]:
"""Calculate energy consumption metrics"""
return {
"current": sum(values),
"average": sum(values) / len(values),
"total": sum(values),
"peak": max(values),
"unit": "kWh"
}
def _calculate_co2_metrics(self, values: List[float]) -> Dict[str, Any]:
"""Calculate CO2 level metrics"""
avg_co2 = sum(values) / len(values)
return {
"current": avg_co2,
"average": avg_co2,
"max": max(values),
"min": min(values),
"status": self._get_co2_status(avg_co2).value,
"unit": "ppm"
}
def _calculate_temperature_metrics(self, values: List[float]) -> Dict[str, Any]:
"""Calculate temperature metrics"""
avg_temp = sum(values) / len(values)
return {
"current": avg_temp,
"average": avg_temp,
"max": max(values),
"min": min(values),
"unit": "°C"
}
def _calculate_humidity_metrics(self, values: List[float]) -> Dict[str, Any]:
"""Calculate humidity metrics"""
avg_humidity = sum(values) / len(values)
return {
"current": avg_humidity,
"average": avg_humidity,
"max": max(values),
"min": min(values),
"unit": "%"
}
def _get_co2_status(self, co2_level: float) -> CO2Status:
"""Determine CO2 status based on level"""
if co2_level < 400:
return CO2Status.GOOD
elif co2_level < 1000:
return CO2Status.MODERATE
elif co2_level < 5000:
return CO2Status.POOR
else:
return CO2Status.CRITICAL
def _estimate_occupancy_from_co2(self, co2_level: float) -> OccupancyLevel:
"""Estimate occupancy level based on CO2 levels"""
if co2_level < 600:
return OccupancyLevel.LOW
elif co2_level < 1200:
return OccupancyLevel.MEDIUM
else:
return OccupancyLevel.HIGH
async def get_all_rooms(self) -> Dict[str, Any]:
"""Get list of all rooms with sensor counts and latest metrics"""
try:
rooms = await self.sensor_reading_repo.get_distinct_rooms()
room_data = []
for room in rooms:
# Get sensor count for each room
sensor_ids = await self.sensor_reading_repo.get_distinct_sensor_ids_by_room(room)
sensor_count = len(sensor_ids)
# Get latest room metrics from cache
room_metrics = await self.redis_repo.get_room_metrics(room)
room_data.append({
"room": room,
"sensor_count": sensor_count,
"sensor_ids": sensor_ids,
"latest_metrics": room_metrics
})
return {
"rooms": room_data,
"count": len(room_data)
}
except Exception as e:
logger.error(f"Error getting rooms: {e}")
return {"rooms": [], "count": 0}
async def get_room_data(self, room_name: str, start_time: Optional[int] = None,
end_time: Optional[int] = None, limit: int = 100) -> Dict[str, Any]:
"""Get historical data for a specific room"""
try:
# Build query for time range
query = {"room": room_name}
if start_time or end_time:
time_query = {}
if start_time:
time_query["$gte"] = datetime.fromtimestamp(start_time)
if end_time:
time_query["$lte"] = datetime.fromtimestamp(end_time)
query["created_at"] = time_query
# Get room metrics
room_metrics = await self.room_metrics_repo.get_by_room(room_name, limit)
# Get sensor readings for the room
sensor_readings = await self.sensor_reading_repo.get_by_query(
query=query,
sort_by="timestamp",
sort_order="desc",
limit=limit
)
return {
"room": room_name,
"room_metrics": room_metrics,
"sensor_readings": sensor_readings
}
except Exception as e:
logger.error(f"Error getting room data for {room_name}: {e}")
return {
"room": room_name,
"room_metrics": [],
"sensor_readings": []
}