version 2

This commit is contained in:
2025-12-16 16:52:40 +03:30
parent 61e86b1e96
commit 139924db94
52 changed files with 7350 additions and 321 deletions

View File

@@ -0,0 +1,229 @@
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} یافت نشد");
}
// 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<ChatMessage>
{
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;
}
}