Files
sac4cps-backend/layers/business/analytics_service.py
rafaeldpsilva a7a18e6295 first commit
2025-09-09 13:46:42 +01:00

300 lines
12 KiB
Python

"""
Analytics business logic service
Business Layer - handles analytics calculations and data aggregations
"""
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
import logging
from ..infrastructure.repositories import SensorReadingRepository
logger = logging.getLogger(__name__)
class AnalyticsService:
"""Service for analytics and reporting operations"""
def __init__(self):
self.sensor_reading_repo = SensorReadingRepository()
async def get_analytics_summary(self, hours: int = 24) -> Dict[str, Any]:
"""Get comprehensive analytics summary for the specified time period"""
try:
start_time = datetime.utcnow() - timedelta(hours=hours)
# Sensor-level analytics pipeline
sensor_pipeline = [
{"$match": {"created_at": {"$gte": start_time}}},
{"$group": {
"_id": {
"sensor_id": "$sensor_id",
"room": "$room",
"sensor_type": "$sensor_type"
},
"reading_count": {"$sum": 1},
"avg_energy": {"$avg": "$energy.value"},
"total_energy": {"$sum": "$energy.value"},
"avg_co2": {"$avg": "$co2.value"},
"max_co2": {"$max": "$co2.value"},
"avg_temperature": {"$avg": "$temperature.value"},
"latest_timestamp": {"$max": "$timestamp"}
}},
{"$sort": {"total_energy": -1}}
]
sensor_analytics = await self.sensor_reading_repo.aggregate(sensor_pipeline)
# Room-level analytics pipeline
room_pipeline = [
{"$match": {"created_at": {"$gte": start_time}, "room": {"$ne": None}}},
{"$group": {
"_id": "$room",
"sensor_count": {"$addToSet": "$sensor_id"},
"total_energy": {"$sum": "$energy.value"},
"avg_co2": {"$avg": "$co2.value"},
"max_co2": {"$max": "$co2.value"},
"reading_count": {"$sum": 1}
}},
{"$project": {
"room": "$_id",
"sensor_count": {"$size": "$sensor_count"},
"total_energy": 1,
"avg_co2": 1,
"max_co2": 1,
"reading_count": 1
}},
{"$sort": {"total_energy": -1}}
]
room_analytics = await self.sensor_reading_repo.aggregate(room_pipeline)
# Calculate summary statistics
summary_stats = self._calculate_summary_stats(sensor_analytics, room_analytics)
return {
"period_hours": hours,
"start_time": start_time.isoformat(),
"sensor_analytics": sensor_analytics,
"room_analytics": room_analytics,
"summary": summary_stats
}
except Exception as e:
logger.error(f"Error getting analytics summary: {e}")
return {
"period_hours": hours,
"start_time": None,
"sensor_analytics": [],
"room_analytics": [],
"summary": {}
}
def _calculate_summary_stats(self, sensor_analytics: List[Dict],
room_analytics: List[Dict]) -> Dict[str, Any]:
"""Calculate summary statistics from analytics data"""
total_readings = sum(item["reading_count"] for item in sensor_analytics)
total_energy = sum(item.get("total_energy", 0) or 0 for item in sensor_analytics)
# Energy consumption insights
energy_insights = {
"total_consumption_kwh": round(total_energy, 2),
"average_consumption_per_sensor": (
round(total_energy / len(sensor_analytics), 2)
if sensor_analytics else 0
),
"top_energy_consumer": (
sensor_analytics[0]["_id"]["sensor_id"]
if sensor_analytics else None
)
}
# CO2 insights
co2_values = [item.get("avg_co2") for item in sensor_analytics if item.get("avg_co2")]
co2_insights = {
"average_co2_level": (
round(sum(co2_values) / len(co2_values), 1)
if co2_values else 0
),
"sensors_with_high_co2": len([
co2 for co2 in co2_values if co2 and co2 > 1000
]),
"sensors_with_critical_co2": len([
co2 for co2 in co2_values if co2 and co2 > 5000
])
}
return {
"total_sensors_analyzed": len(sensor_analytics),
"total_rooms_analyzed": len(room_analytics),
"total_readings": total_readings,
"energy_insights": energy_insights,
"co2_insights": co2_insights
}
async def get_energy_trends(self, hours: int = 168) -> Dict[str, Any]:
"""Get energy consumption trends (default: last week)"""
try:
start_time = datetime.utcnow() - timedelta(hours=hours)
# Hourly energy consumption pipeline
pipeline = [
{"$match": {
"created_at": {"$gte": start_time},
"energy.value": {"$exists": True}
}},
{"$group": {
"_id": {
"year": {"$year": "$created_at"},
"month": {"$month": "$created_at"},
"day": {"$dayOfMonth": "$created_at"},
"hour": {"$hour": "$created_at"}
},
"total_energy": {"$sum": "$energy.value"},
"sensor_count": {"$addToSet": "$sensor_id"},
"reading_count": {"$sum": 1}
}},
{"$project": {
"_id": 0,
"timestamp": {
"$dateFromParts": {
"year": "$_id.year",
"month": "$_id.month",
"day": "$_id.day",
"hour": "$_id.hour"
}
},
"total_energy": {"$round": ["$total_energy", 2]},
"sensor_count": {"$size": "$sensor_count"},
"reading_count": 1
}},
{"$sort": {"timestamp": 1}}
]
trends = await self.sensor_reading_repo.aggregate(pipeline)
# Calculate trend insights
insights = self._calculate_trend_insights(trends)
return {
"period_hours": hours,
"data_points": len(trends),
"trends": trends,
"insights": insights
}
except Exception as e:
logger.error(f"Error getting energy trends: {e}")
return {
"period_hours": hours,
"data_points": 0,
"trends": [],
"insights": {}
}
def _calculate_trend_insights(self, trends: List[Dict]) -> Dict[str, Any]:
"""Calculate insights from trend data"""
if not trends:
return {}
energy_values = [item["total_energy"] for item in trends]
# Peak and low consumption
max_consumption = max(energy_values)
min_consumption = min(energy_values)
avg_consumption = sum(energy_values) / len(energy_values)
# Find peak time
peak_item = max(trends, key=lambda x: x["total_energy"])
peak_time = peak_item["timestamp"]
return {
"peak_consumption_kwh": max_consumption,
"lowest_consumption_kwh": min_consumption,
"average_consumption_kwh": round(avg_consumption, 2),
"peak_time": peak_time.isoformat() if hasattr(peak_time, 'isoformat') else str(peak_time),
"consumption_variance": round(max_consumption - min_consumption, 2)
}
async def get_room_comparison(self, hours: int = 24) -> Dict[str, Any]:
"""Get room-by-room comparison analytics"""
try:
start_time = datetime.utcnow() - timedelta(hours=hours)
pipeline = [
{"$match": {
"created_at": {"$gte": start_time},
"room": {"$ne": None}
}},
{"$group": {
"_id": "$room",
"total_energy": {"$sum": "$energy.value"},
"avg_energy": {"$avg": "$energy.value"},
"avg_co2": {"$avg": "$co2.value"},
"max_co2": {"$max": "$co2.value"},
"avg_temperature": {"$avg": "$temperature.value"},
"sensor_count": {"$addToSet": "$sensor_id"},
"reading_count": {"$sum": 1}
}},
{"$project": {
"room": "$_id",
"_id": 0,
"total_energy": {"$round": [{"$ifNull": ["$total_energy", 0]}, 2]},
"avg_energy": {"$round": [{"$ifNull": ["$avg_energy", 0]}, 2]},
"avg_co2": {"$round": [{"$ifNull": ["$avg_co2", 0]}, 1]},
"max_co2": {"$round": [{"$ifNull": ["$max_co2", 0]}, 1]},
"avg_temperature": {"$round": [{"$ifNull": ["$avg_temperature", 0]}, 1]},
"sensor_count": {"$size": "$sensor_count"},
"reading_count": 1
}},
{"$sort": {"total_energy": -1}}
]
room_comparison = await self.sensor_reading_repo.aggregate(pipeline)
# Calculate comparison insights
insights = self._calculate_room_insights(room_comparison)
return {
"period_hours": hours,
"rooms_analyzed": len(room_comparison),
"comparison": room_comparison,
"insights": insights
}
except Exception as e:
logger.error(f"Error getting room comparison: {e}")
return {
"period_hours": hours,
"rooms_analyzed": 0,
"comparison": [],
"insights": {}
}
def _calculate_room_insights(self, room_data: List[Dict]) -> Dict[str, Any]:
"""Calculate insights from room comparison data"""
if not room_data:
return {}
# Energy insights
total_energy = sum(room["total_energy"] for room in room_data)
highest_consumer = room_data[0] if room_data else None
lowest_consumer = min(room_data, key=lambda x: x["total_energy"]) if room_data else None
# CO2 insights
rooms_with_high_co2 = [
room for room in room_data
if room.get("avg_co2", 0) > 1000
]
# Temperature insights
temp_values = [room.get("avg_temperature", 0) for room in room_data if room.get("avg_temperature")]
avg_building_temp = sum(temp_values) / len(temp_values) if temp_values else 0
return {
"total_building_energy_kwh": round(total_energy, 2),
"highest_energy_consumer": highest_consumer["room"] if highest_consumer else None,
"lowest_energy_consumer": lowest_consumer["room"] if lowest_consumer else None,
"rooms_with_high_co2": len(rooms_with_high_co2),
"high_co2_rooms": [room["room"] for room in rooms_with_high_co2],
"average_building_temperature": round(avg_building_temp, 1),
"total_active_sensors": sum(room["sensor_count"] for room in room_data)
}