first commit
This commit is contained in:
262
layers/business/room_service.py
Normal file
262
layers/business/room_service.py
Normal 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": []
|
||||
}
|
||||
Reference in New Issue
Block a user