Files
GreenHomeBack/src/GreenHome.Infrastructure/DailyReportService.cs
2025-12-17 00:34:41 +03:30

480 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics;
using System.Text;
using GreenHome.AI.DeepSeek;
using GreenHome.Application;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace GreenHome.Infrastructure;
public class DailyReportService : IDailyReportService
{
private readonly GreenHomeDbContext _context;
private readonly IDeepSeekService _deepSeekService;
private readonly ILogger<DailyReportService> _logger;
public DailyReportService(
GreenHomeDbContext context,
IDeepSeekService deepSeekService,
ILogger<DailyReportService> logger)
{
_context = context;
_deepSeekService = deepSeekService;
_logger = logger;
}
public async Task<DailyReportResponse> GetOrCreateDailyReportAsync(
DailyReportRequest request,
CancellationToken cancellationToken)
{
// Validate Persian date format
if (!IsValidPersianDate(request.PersianDate, out var year, out var month, out var day))
{
throw new ArgumentException("تاریخ شمسی باید به فرمت yyyy/MM/dd باشد", nameof(request.PersianDate));
}
// Check if report already exists
var existingReport = await _context.DailyReports
.Include(r => r.Device)
.FirstOrDefaultAsync(
r => r.DeviceId == request.DeviceId && r.PersianDate == request.PersianDate,
cancellationToken);
if (existingReport != null)
{
_logger.LogInformation(
"گزارش روزانه برای دستگاه {DeviceId} و تاریخ {Date} از قبل موجود است",
request.DeviceId, request.PersianDate);
return new DailyReportResponse
{
Id = existingReport.Id,
DeviceId = existingReport.DeviceId,
DeviceName = existingReport.Device?.DeviceName ?? string.Empty,
PersianDate = existingReport.PersianDate,
Analysis = existingReport.Analysis,
RecordCount = existingReport.RecordCount,
SampledRecordCount = existingReport.SampledRecordCount,
TotalTokens = existingReport.TotalTokens,
CreatedAt = existingReport.CreatedAt,
FromCache = true
};
}
// Get device info
var device = await _context.Devices
.FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken);
if (device == null)
{
throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد");
}
// Get device settings (including ProductType if available)
var deviceSettings = await _context.DeviceSettings
.FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken);
// Query telemetry data for the specified date
var telemetryRecords = await _context.TelemetryRecords
.Where(t => t.DeviceId == request.DeviceId && t.PersianDate == request.PersianDate)
.OrderBy(t => t.TimestampUtc)
.Select(t => new
{
t.TimestampUtc,
t.TemperatureC,
t.HumidityPercent,
t.Lux,
t.GasPPM
})
.ToListAsync(cancellationToken);
if (telemetryRecords.Count == 0)
{
throw new InvalidOperationException(
$"هیچ رکوردی برای دستگاه {request.DeviceId} در تاریخ {request.PersianDate} یافت نشد");
}
// Sample records: take first record from every 20 records
var sampledRecords = telemetryRecords
.Select((record, index) => new { record, index })
.Where(x => x.index % 20 == 0)
.Select(x => x.record)
.ToList();
_logger.LogInformation(
"تعداد {TotalCount} رکورد یافت شد. نمونه‌برداری: {SampledCount} رکورد",
telemetryRecords.Count, sampledRecords.Count);
// Build the data string for AI
var dataBuilder = new StringBuilder();
dataBuilder.AppendLine("زمان | دما (°C) | رطوبت (%) | نور (Lux) | CO (PPM)");
dataBuilder.AppendLine("------|----------|-----------|-----------|----------");
foreach (var record in sampledRecords)
{
// Convert UTC to local time for display
var localTime = record.TimestampUtc.AddHours(3.5); // Iran timezone (UTC+3:30)
dataBuilder.AppendLine(
$"{localTime:HH:mm:ss} | {record.TemperatureC:F1} | {record.HumidityPercent:F1} | {record.Lux:F1} | {record.GasPPM}");
}
// Prepare the question for AI
var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $" محصول کشت شده: {deviceSettings.ProductType}."
: string.Empty;
var areaInfo = deviceSettings?.AreaSquareMeters != null
? $" مساحت گلخانه: {deviceSettings.AreaSquareMeters:F1} متر مربع."
: string.Empty;
var question = $@"این داده‌های تلمتری یک روز ({request.PersianDate}) از یک گلخانه هوشمند هستند.{productTypeInfo}{areaInfo}
{dataBuilder}
لطفاً یک تحلیل خلاصه و کاربردی از این داده‌ها بده که شامل موارد زیر باشه:
1. وضعیت کلی دما، رطوبت، نور و کیفیت هوا
2. روندهای مشاهده شده در طول روز
3. هر گونه نکته یا هشدار مهم
4. پیشنهادات برای بهبود شرایط گلخانه{(productTypeInfo != string.Empty ? " و رشد بهتر محصول" : string.Empty)}
خلاصه و مفید باش (حداکثر 300 کلمه).";
// Send to DeepSeek
var stopwatch = Stopwatch.StartNew();
ChatResponse? aiResponse;
try
{
var systemMessage = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری و داده‌های تلمتری رو تحلیل می‌کنی."
: "تو یک متخصص کشاورزی و گلخانه هستی که داده‌های تلمتری رو تحلیل می‌کنی.";
var chatRequest = new ChatRequest
{
Model = "deepseek-chat",
Messages = new List<ChatMessage>
{
new() { Role = "system", Content = systemMessage },
new() { Role = "user", Content = question }
},
Temperature = 0.7
};
aiResponse = await _deepSeekService.AskAsync(chatRequest, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "خطا در فراخوانی DeepSeek API");
throw new InvalidOperationException("خطا در دریافت تحلیل از سرویس هوش مصنوعی", ex);
}
stopwatch.Stop();
if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 ||
string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content))
{
throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی");
}
var analysis = aiResponse.Choices[0].Message!.Content;
// Save the report
var dailyReport = new Domain.DailyReport
{
DeviceId = request.DeviceId,
PersianDate = request.PersianDate,
PersianYear = year,
PersianMonth = month,
PersianDay = day,
Analysis = analysis,
RecordCount = telemetryRecords.Count,
SampledRecordCount = sampledRecords.Count,
PromptTokens = aiResponse.Usage?.PromptTokens ?? 0,
CompletionTokens = aiResponse.Usage?.CompletionTokens ?? 0,
TotalTokens = aiResponse.Usage?.TotalTokens ?? 0,
Model = aiResponse.Model,
ResponseTimeMs = stopwatch.ElapsedMilliseconds,
CreatedAt = DateTime.UtcNow
};
_context.DailyReports.Add(dailyReport);
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"گزارش روزانه جدید برای دستگاه {DeviceId} و تاریخ {Date} ایجاد شد. توکن مصرف شده: {Tokens}",
request.DeviceId, request.PersianDate, dailyReport.TotalTokens);
return new DailyReportResponse
{
Id = dailyReport.Id,
DeviceId = dailyReport.DeviceId,
DeviceName = device.DeviceName,
PersianDate = dailyReport.PersianDate,
Analysis = dailyReport.Analysis,
RecordCount = dailyReport.RecordCount,
SampledRecordCount = dailyReport.SampledRecordCount,
TotalTokens = dailyReport.TotalTokens,
CreatedAt = dailyReport.CreatedAt,
FromCache = false
};
}
private static bool IsValidPersianDate(string persianDate, out int year, out int month, out int day)
{
year = month = day = 0;
if (string.IsNullOrWhiteSpace(persianDate))
return false;
var parts = persianDate.Split('/');
if (parts.Length != 3)
return false;
if (!int.TryParse(parts[0], out year) || year < 1300 || year > 1500)
return false;
if (!int.TryParse(parts[1], out month) || month < 1 || month > 12)
return false;
if (!int.TryParse(parts[2], out day) || day < 1 || day > 31)
return false;
return true;
}
public async Task<DailyReportResponse> GetWeeklyAnalysisAsync(
WeeklyAnalysisRequest request,
CancellationToken cancellationToken)
{
// Get device info
var device = await _context.Devices
.FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken);
if (device == null)
{
throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد");
}
// Get device settings
var deviceSettings = await _context.DeviceSettings
.FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken);
// Query telemetry data for the week
var telemetryRecords = await _context.TelemetryRecords
.Where(t => t.DeviceId == request.DeviceId &&
string.Compare(t.PersianDate, request.StartDate) >= 0 &&
string.Compare(t.PersianDate, request.EndDate) <= 0)
.OrderBy(t => t.TimestampUtc)
.Select(t => new
{
t.TimestampUtc,
t.TemperatureC,
t.HumidityPercent,
t.Lux,
t.GasPPM,
t.PersianDate
})
.ToListAsync(cancellationToken);
if (telemetryRecords.Count == 0)
{
throw new InvalidOperationException(
$"هیچ رکوردی برای دستگاه {request.DeviceId} در بازه {request.StartDate} تا {request.EndDate} یافت نشد");
}
// Sample 1 per 100 records
var sampledRecords = telemetryRecords
.Select((record, index) => new { record, index })
.Where(x => x.index % 100 == 0)
.Select(x => x.record)
.ToList();
_logger.LogInformation(
"تعداد {TotalCount} رکورد یافت شد. نمونه‌برداری هفتگی: {SampledCount} رکورد",
telemetryRecords.Count, sampledRecords.Count);
// Build the data string
var dataBuilder = new StringBuilder();
dataBuilder.AppendLine("تاریخ | زمان | دما (°C) | رطوبت (%) | نور (Lux) | CO (PPM)");
dataBuilder.AppendLine("---------|----------|----------|-----------|-----------|----------");
foreach (var record in sampledRecords)
{
var localTime = record.TimestampUtc.AddHours(3.5);
dataBuilder.AppendLine(
$"{record.PersianDate} | {localTime:HH:mm} | {record.TemperatureC:F1} | {record.HumidityPercent:F1} | {record.Lux:F1} | {record.GasPPM}");
}
// Prepare AI prompt
var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $" محصول کشت شده: {deviceSettings.ProductType}."
: string.Empty;
var areaInfo = deviceSettings?.AreaSquareMeters != null
? $" مساحت گلخانه: {deviceSettings.AreaSquareMeters:F1} متر مربع."
: string.Empty;
var question = $@"این داده‌های تلمتری یک هفته ({request.StartDate} تا {request.EndDate}) از یک گلخانه هوشمند هستند.{productTypeInfo}{areaInfo}
{dataBuilder}
لطفاً یک تحلیل جامع هفتگی بده که شامل:
1. خلاصه روند هفتگی دما، رطوبت، نور و کیفیت هوا
2. مقایسه شرایط در روزهای مختلف هفته
3. نکات و هشدارهای مهم
4. توصیه‌ها برای هفته آینده
خلاصه و کاربردی باش (حداکثر 500 کلمه).";
var systemMessage = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری و داده‌های تلمتری رو تحلیل می‌کنی."
: "تو یک متخصص کشاورزی و گلخانه هستی که داده‌های تلمتری رو تحلیل می‌کنی.";
// Send to DeepSeek
var stopwatch = Stopwatch.StartNew();
var chatRequest = new ChatRequest
{
Model = "deepseek-chat",
Messages = new List<ChatMessage>
{
new() { Role = "system", Content = systemMessage },
new() { Role = "user", Content = question }
},
Temperature = 0.7
};
var aiResponse = await _deepSeekService.AskAsync(chatRequest, cancellationToken);
stopwatch.Stop();
if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 ||
string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content))
{
throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی");
}
return new DailyReportResponse
{
Id = 0,
DeviceId = request.DeviceId,
DeviceName = device.DeviceName,
PersianDate = $"{request.StartDate} تا {request.EndDate}",
Analysis = aiResponse.Choices[0].Message!.Content,
RecordCount = telemetryRecords.Count,
SampledRecordCount = sampledRecords.Count,
TotalTokens = aiResponse.Usage?.TotalTokens ?? 0,
CreatedAt = DateTime.UtcNow,
FromCache = false
};
}
public async Task<DailyReportResponse> GetMonthlyAnalysisAsync(
MonthlyAnalysisRequest request,
CancellationToken cancellationToken)
{
// Get device info
var device = await _context.Devices
.FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken);
if (device == null)
{
throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد");
}
// Get device settings
var deviceSettings = await _context.DeviceSettings
.FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken);
// Get all daily reports for this month
var dailyReports = await _context.DailyReports
.Where(dr => dr.DeviceId == request.DeviceId &&
dr.PersianYear == request.Year &&
dr.PersianMonth == request.Month)
.OrderBy(dr => dr.PersianDay)
.Select(dr => new { dr.PersianDate, dr.Analysis })
.ToListAsync(cancellationToken);
if (dailyReports.Count == 0)
{
throw new InvalidOperationException(
$"هیچ تحلیل روزانه‌ای برای دستگاه {request.DeviceId} در ماه {request.Month} سال {request.Year} یافت نشد");
}
// Build summary of daily analyses
var summaryBuilder = new StringBuilder();
summaryBuilder.AppendLine($"تحلیل‌های روزانه ماه {request.Month} سال {request.Year}:");
summaryBuilder.AppendLine();
foreach (var report in dailyReports)
{
summaryBuilder.AppendLine($"📅 {report.PersianDate}:");
summaryBuilder.AppendLine(report.Analysis);
summaryBuilder.AppendLine();
summaryBuilder.AppendLine("---");
summaryBuilder.AppendLine();
}
// Prepare AI prompt
var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $" محصول کشت شده: {deviceSettings.ProductType}."
: string.Empty;
var areaInfo = deviceSettings?.AreaSquareMeters != null
? $" مساحت گلخانه: {deviceSettings.AreaSquareMeters:F1} متر مربع."
: string.Empty;
var question = $@"این تحلیل‌های روزانه یک ماه ({request.Month}/{request.Year}) از یک گلخانه هوشمند هستند.{productTypeInfo}{areaInfo}
{summaryBuilder}
لطفاً یک تحلیل جامع ماهانه بده که شامل:
1. خلاصه کلی عملکرد ماه
2. روندهای اصلی و تغییرات مهم
3. نقاط قوت و ضعف
4. توصیه‌های کلیدی برای ماه آینده
5. نکات مهم برای بهبود بهره‌وری
جامع و کاربردی باش (حداکثر 800 کلمه).";
var systemMessage = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType)
? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری. تحلیل‌های روزانه رو بررسی کن و یک جمع‌بندی ماهانه جامع ارائه بده."
: "تو یک متخصص کشاورزی و گلخانه هستی. تحلیل‌های روزانه رو بررسی کن و یک جمع‌بندی ماهانه جامع ارائه بده.";
// Send to DeepSeek
var stopwatch = Stopwatch.StartNew();
var chatRequest = new ChatRequest
{
Model = "deepseek-chat",
Messages = new List<ChatMessage>
{
new() { Role = "system", Content = systemMessage },
new() { Role = "user", Content = question }
},
Temperature = 0.7
};
var aiResponse = await _deepSeekService.AskAsync(chatRequest, cancellationToken);
stopwatch.Stop();
if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 ||
string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content))
{
throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی");
}
return new DailyReportResponse
{
Id = 0,
DeviceId = request.DeviceId,
DeviceName = device.DeviceName,
PersianDate = $"ماه {request.Month} سال {request.Year}",
Analysis = aiResponse.Choices[0].Message!.Content,
RecordCount = dailyReports.Count,
SampledRecordCount = dailyReports.Count,
TotalTokens = aiResponse.Usage?.TotalTokens ?? 0,
CreatedAt = DateTime.UtcNow,
FromCache = false
};
}
}