186 lines
7.3 KiB
C#
186 lines
7.3 KiB
C#
using GreenHome.Application;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace GreenHome.Infrastructure;
|
|
|
|
public sealed class MonthlyReportService : IMonthlyReportService
|
|
{
|
|
private readonly GreenHomeDbContext _context;
|
|
|
|
public MonthlyReportService(GreenHomeDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<MonthlyReportDto> GetMonthlyReportAsync(
|
|
int deviceId,
|
|
int year,
|
|
int month,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var device = await _context.Devices
|
|
.FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken);
|
|
|
|
if (device == null)
|
|
{
|
|
throw new InvalidOperationException($"دستگاه با شناسه {deviceId} یافت نشد");
|
|
}
|
|
|
|
// Get alert logs for the month
|
|
var alertLogs = await _context.AlertLogs
|
|
.Where(al => al.DeviceId == deviceId &&
|
|
al.SentAt.Year == year &&
|
|
al.SentAt.Month == month)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var totalAlerts = alertLogs.Count;
|
|
var smsAlerts = alertLogs.Count(al => al.NotificationType == Domain.AlertNotificationType.SMS);
|
|
var callAlerts = alertLogs.Count(al => al.NotificationType == Domain.AlertNotificationType.Call);
|
|
var successfulAlerts = alertLogs.Count(al => al.Status == Domain.AlertStatus.Success);
|
|
var failedAlerts = alertLogs.Count(al => al.Status == Domain.AlertStatus.Failed);
|
|
var powerOutageAlerts = alertLogs.Count(al => al.AlertType == Domain.AlertType.PowerOutage);
|
|
|
|
// Get telemetry statistics
|
|
var telemetryData = await _context.TelemetryRecords
|
|
.Where(t => t.DeviceId == deviceId &&
|
|
t.PersianYear == year &&
|
|
t.PersianMonth == month)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var totalTelemetryRecords = telemetryData.Count;
|
|
var avgTemp = telemetryData.Any() ? telemetryData.Average(t => t.TemperatureC) : 0;
|
|
var minTemp = telemetryData.Any() ? telemetryData.Min(t => t.TemperatureC) : 0;
|
|
var maxTemp = telemetryData.Any() ? telemetryData.Max(t => t.TemperatureC) : 0;
|
|
var avgHumidity = telemetryData.Any() ? telemetryData.Average(t => t.HumidityPercent) : 0;
|
|
var minHumidity = telemetryData.Any() ? telemetryData.Min(t => t.HumidityPercent) : 0;
|
|
var maxHumidity = telemetryData.Any() ? telemetryData.Max(t => t.HumidityPercent) : 0;
|
|
var avgLux = telemetryData.Any() ? telemetryData.Average(t => t.Lux) : 0;
|
|
var avgGas = telemetryData.Any() ? (int)telemetryData.Average(t => t.GasPPM) : 0;
|
|
var maxGas = telemetryData.Any() ? telemetryData.Max(t => t.GasPPM) : 0;
|
|
|
|
// Get user activity
|
|
var userReportsCount = await _context.UserDailyReports
|
|
.CountAsync(r => r.DeviceId == deviceId &&
|
|
r.PersianYear == year &&
|
|
r.PersianMonth == month,
|
|
cancellationToken);
|
|
|
|
var checklistCompletionsCount = await _context.ChecklistCompletions
|
|
.Where(cc => cc.Checklist.DeviceId == deviceId)
|
|
.CountAsync(cc => cc.PersianDate.StartsWith($"{year}/{month:D2}"), cancellationToken);
|
|
|
|
var dailyAnalysesCount = await _context.DailyReports
|
|
.CountAsync(dr => dr.DeviceId == deviceId &&
|
|
dr.PersianYear == year &&
|
|
dr.PersianMonth == month,
|
|
cancellationToken);
|
|
|
|
// Generate performance summary
|
|
var performanceSummary = GeneratePerformanceSummary(
|
|
totalTelemetryRecords,
|
|
totalAlerts,
|
|
successfulAlerts,
|
|
failedAlerts,
|
|
avgTemp,
|
|
avgHumidity,
|
|
avgLux,
|
|
avgGas,
|
|
userReportsCount,
|
|
checklistCompletionsCount);
|
|
|
|
return new MonthlyReportDto
|
|
{
|
|
DeviceId = deviceId,
|
|
DeviceName = device.DeviceName,
|
|
Year = year,
|
|
Month = month,
|
|
TotalAlerts = totalAlerts,
|
|
SmsAlerts = smsAlerts,
|
|
CallAlerts = callAlerts,
|
|
SuccessfulAlerts = successfulAlerts,
|
|
FailedAlerts = failedAlerts,
|
|
PowerOutageAlerts = powerOutageAlerts,
|
|
TotalTelemetryRecords = totalTelemetryRecords,
|
|
AverageTemperature = avgTemp,
|
|
MinTemperature = minTemp,
|
|
MaxTemperature = maxTemp,
|
|
AverageHumidity = avgHumidity,
|
|
MinHumidity = minHumidity,
|
|
MaxHumidity = maxHumidity,
|
|
AverageLux = avgLux,
|
|
AverageGasPPM = avgGas,
|
|
MaxGasPPM = maxGas,
|
|
UserDailyReportsCount = userReportsCount,
|
|
ChecklistCompletionsCount = checklistCompletionsCount,
|
|
DailyAnalysesCount = dailyAnalysesCount,
|
|
PerformanceSummary = performanceSummary,
|
|
GeneratedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
private string GeneratePerformanceSummary(
|
|
int totalRecords,
|
|
int totalAlerts,
|
|
int successfulAlerts,
|
|
int failedAlerts,
|
|
decimal avgTemp,
|
|
decimal avgHumidity,
|
|
decimal avgLux,
|
|
int avgGas,
|
|
int userReports,
|
|
int checklistCompletions)
|
|
{
|
|
var summary = new List<string>();
|
|
|
|
summary.Add($"📊 آمار کلی:");
|
|
summary.Add($" • تعداد رکوردهای ثبت شده: {totalRecords:N0}");
|
|
summary.Add($" • میانگین دما: {avgTemp:F1}°C");
|
|
summary.Add($" • میانگین رطوبت: {avgHumidity:F1}%");
|
|
summary.Add($" • میانگین نور: {avgLux:F0} لوکس");
|
|
if (avgGas > 0)
|
|
summary.Add($" • میانگین CO: {avgGas} PPM");
|
|
|
|
summary.Add("");
|
|
summary.Add($"🚨 هشدارها:");
|
|
summary.Add($" • تعداد کل: {totalAlerts}");
|
|
summary.Add($" • موفق: {successfulAlerts}");
|
|
if (failedAlerts > 0)
|
|
summary.Add($" • ناموفق: {failedAlerts} ⚠️");
|
|
|
|
if (userReports > 0 || checklistCompletions > 0)
|
|
{
|
|
summary.Add("");
|
|
summary.Add($"📝 فعالیت کاربران:");
|
|
if (userReports > 0)
|
|
summary.Add($" • گزارشهای روزانه: {userReports}");
|
|
if (checklistCompletions > 0)
|
|
summary.Add($" • تکمیل چکلیست: {checklistCompletions}");
|
|
}
|
|
|
|
// Performance rating
|
|
summary.Add("");
|
|
var rating = CalculatePerformanceRating(totalRecords, failedAlerts, totalAlerts);
|
|
summary.Add($"⭐ ارزیابی کلی: {rating}");
|
|
|
|
return string.Join("\n", summary);
|
|
}
|
|
|
|
private string CalculatePerformanceRating(int totalRecords, int failedAlerts, int totalAlerts)
|
|
{
|
|
if (totalRecords == 0)
|
|
return "بدون داده";
|
|
|
|
var failureRate = totalAlerts > 0 ? (double)failedAlerts / totalAlerts : 0;
|
|
|
|
if (failureRate == 0 && totalRecords > 1000)
|
|
return "عالی ✅";
|
|
else if (failureRate < 0.1 && totalRecords > 500)
|
|
return "خوب 👍";
|
|
else if (failureRate < 0.3)
|
|
return "متوسط ⚠️";
|
|
else
|
|
return "نیاز به بررسی 🔧";
|
|
}
|
|
}
|
|
|