""" 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": [] }