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 _logger; public DailyReportService( GreenHomeDbContext context, IDeepSeekService deepSeekService, ILogger logger) { _context = context; _deepSeekService = deepSeekService; _logger = logger; } public async Task 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 { 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 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 { 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 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 { 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 }; } }