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} یافت نشد"); } // 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 question = $@"این داده‌های تلمتری یک روز ({request.PersianDate}) از یک گلخانه هوشمند هستند: {dataBuilder} لطفاً یک تحلیل خلاصه و کاربردی از این داده‌ها بده که شامل موارد زیر باشه: 1. وضعیت کلی دما، رطوبت، نور و کیفیت هوا 2. روندهای مشاهده شده در طول روز 3. هر گونه نکته یا هشدار مهم 4. پیشنهادات برای بهبود شرایط گلخانه خلاصه و مفید باش (حداکثر 300 کلمه)."; // Send to DeepSeek var stopwatch = Stopwatch.StartNew(); ChatResponse? aiResponse; try { var chatRequest = new ChatRequest { Model = "deepseek-chat", Messages = new List { new() { Role = "system", Content = "تو یک متخصص کشاورزی و گلخانه هستی که داده‌های تلمتری رو تحلیل می‌کنی." }, 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; } }