version 2
This commit is contained in:
126
src/GreenHome.Infrastructure/AIQueryService.cs
Normal file
126
src/GreenHome.Infrastructure/AIQueryService.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using GreenHome.Application;
|
||||
using GreenHome.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GreenHome.Infrastructure;
|
||||
|
||||
public sealed class AIQueryService : IAIQueryService
|
||||
{
|
||||
private readonly GreenHomeDbContext dbContext;
|
||||
private readonly ILogger<AIQueryService> logger;
|
||||
|
||||
public AIQueryService(
|
||||
GreenHomeDbContext dbContext,
|
||||
ILogger<AIQueryService> logger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AIQuery> SaveQueryAsync(AIQuery query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
dbContext.AIQueries.Add(query);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
logger.LogInformation("AI query saved: {QueryId}, Tokens: {TotalTokens}",
|
||||
query.Id, query.TotalTokens);
|
||||
|
||||
return query;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error saving AI query");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<AIQuery>> GetDeviceQueriesAsync(
|
||||
int deviceId,
|
||||
int take = 50,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await dbContext.AIQueries
|
||||
.Where(q => q.DeviceId == deviceId)
|
||||
.OrderByDescending(q => q.CreatedAt)
|
||||
.Take(take)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<AIQuery>> GetUserQueriesAsync(
|
||||
int userId,
|
||||
int take = 50,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await dbContext.AIQueries
|
||||
.Where(q => q.UserId == userId)
|
||||
.OrderByDescending(q => q.CreatedAt)
|
||||
.Take(take)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> GetDeviceTotalTokensAsync(
|
||||
int deviceId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await dbContext.AIQueries
|
||||
.Where(q => q.DeviceId == deviceId)
|
||||
.SumAsync(q => q.TotalTokens, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> GetUserTotalTokensAsync(
|
||||
int userId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await dbContext.AIQueries
|
||||
.Where(q => q.UserId == userId)
|
||||
.SumAsync(q => q.TotalTokens, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<AIQuery>> GetRecentQueriesAsync(
|
||||
int take = 20,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await dbContext.AIQueries
|
||||
.Include(q => q.Device)
|
||||
.Include(q => q.User)
|
||||
.OrderByDescending(q => q.CreatedAt)
|
||||
.Take(take)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<AIQueryStats> GetStatsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
var allQueries = await dbContext.AIQueries
|
||||
.Select(q => new
|
||||
{
|
||||
q.TotalTokens,
|
||||
q.PromptTokens,
|
||||
q.CompletionTokens,
|
||||
q.ResponseTimeMs,
|
||||
q.CreatedAt
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var todayQueries = allQueries.Where(q => q.CreatedAt >= today).ToList();
|
||||
|
||||
return new AIQueryStats
|
||||
{
|
||||
TotalQueries = allQueries.Count,
|
||||
TotalTokensUsed = allQueries.Sum(q => q.TotalTokens),
|
||||
TotalPromptTokens = allQueries.Sum(q => q.PromptTokens),
|
||||
TotalCompletionTokens = allQueries.Sum(q => q.CompletionTokens),
|
||||
AverageResponseTimeMs = allQueries.Any()
|
||||
? allQueries.Where(q => q.ResponseTimeMs.HasValue)
|
||||
.Average(q => q.ResponseTimeMs ?? 0)
|
||||
: 0,
|
||||
TodayQueries = todayQueries.Count,
|
||||
TodayTokens = todayQueries.Sum(q => q.TotalTokens)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
131
src/GreenHome.Infrastructure/AlertConditionService.cs
Normal file
131
src/GreenHome.Infrastructure/AlertConditionService.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using AutoMapper;
|
||||
using GreenHome.Application;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GreenHome.Infrastructure;
|
||||
|
||||
public sealed class AlertConditionService : IAlertConditionService
|
||||
{
|
||||
private readonly GreenHomeDbContext dbContext;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public AlertConditionService(GreenHomeDbContext dbContext, IMapper mapper)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<AlertConditionDto>> GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var conditions = await dbContext.AlertConditions
|
||||
.Include(x => x.Device)
|
||||
.Include(x => x.Rules)
|
||||
.Where(x => x.DeviceId == deviceId)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return mapper.Map<List<AlertConditionDto>>(conditions);
|
||||
}
|
||||
|
||||
public async Task<AlertConditionDto?> GetByIdAsync(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var condition = await dbContext.AlertConditions
|
||||
.Include(x => x.Device)
|
||||
.Include(x => x.Rules)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
return condition != null ? mapper.Map<AlertConditionDto>(condition) : null;
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(CreateAlertConditionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var condition = new Domain.AlertCondition
|
||||
{
|
||||
DeviceId = request.DeviceId,
|
||||
NotificationType = request.NotificationType,
|
||||
TimeType = request.TimeType,
|
||||
CallCooldownMinutes = request.CallCooldownMinutes,
|
||||
SmsCooldownMinutes = request.SmsCooldownMinutes,
|
||||
IsEnabled = request.IsEnabled,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// Add rules
|
||||
var rules = mapper.Map<List<Domain.AlertRule>>(request.Rules);
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
condition.Rules.Add(rule);
|
||||
}
|
||||
|
||||
dbContext.AlertConditions.Add(condition);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return condition.Id;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(UpdateAlertConditionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var condition = await dbContext.AlertConditions
|
||||
.Include(x => x.Rules)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
|
||||
|
||||
if (condition == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Alert condition not found: {request.Id}");
|
||||
}
|
||||
|
||||
condition.NotificationType = request.NotificationType;
|
||||
condition.TimeType = request.TimeType;
|
||||
condition.CallCooldownMinutes = request.CallCooldownMinutes;
|
||||
condition.SmsCooldownMinutes = request.SmsCooldownMinutes;
|
||||
condition.IsEnabled = request.IsEnabled;
|
||||
condition.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// Remove old rules and add new ones
|
||||
dbContext.AlertRules.RemoveRange(condition.Rules);
|
||||
condition.Rules.Clear();
|
||||
|
||||
var newRules = mapper.Map<List<Domain.AlertRule>>(request.Rules);
|
||||
foreach (var rule in newRules)
|
||||
{
|
||||
condition.Rules.Add(rule);
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var condition = await dbContext.AlertConditions
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
if (condition == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Alert condition not found: {id}");
|
||||
}
|
||||
|
||||
dbContext.AlertConditions.Remove(condition);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleEnabledAsync(int id, bool isEnabled, CancellationToken cancellationToken)
|
||||
{
|
||||
var condition = await dbContext.AlertConditions
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
if (condition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
condition.IsEnabled = isEnabled;
|
||||
condition.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using GreenHome.Application;
|
||||
using GreenHome.Sms.Ippanel;
|
||||
using GreenHome.VoiceCall.Avanak;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
@@ -12,280 +13,153 @@ public sealed class AlertService : IAlertService
|
||||
private readonly GreenHomeDbContext dbContext;
|
||||
private readonly IDeviceSettingsService deviceSettingsService;
|
||||
private readonly ISmsService smsService;
|
||||
private readonly IVoiceCallService voiceCallService;
|
||||
private readonly ISunCalculatorService sunCalculatorService;
|
||||
private readonly ILogger<AlertService> logger;
|
||||
private const int AlertCooldownMinutes = 10;
|
||||
|
||||
private sealed record AlertInfo(
|
||||
string Type,
|
||||
string Message,
|
||||
string ParameterName,
|
||||
decimal Value,
|
||||
string Status
|
||||
);
|
||||
|
||||
public AlertService(
|
||||
GreenHomeDbContext dbContext,
|
||||
IDeviceSettingsService deviceSettingsService,
|
||||
ISmsService smsService,
|
||||
IVoiceCallService voiceCallService,
|
||||
ISunCalculatorService sunCalculatorService,
|
||||
ILogger<AlertService> logger)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
this.deviceSettingsService = deviceSettingsService;
|
||||
this.smsService = smsService;
|
||||
this.voiceCallService = voiceCallService;
|
||||
this.sunCalculatorService = sunCalculatorService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken);
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get device with settings and user
|
||||
var device = await dbContext.Devices
|
||||
.Include(d => d.User)
|
||||
.FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken);
|
||||
|
||||
if (device == null || device.User == null)
|
||||
{
|
||||
logger.LogWarning("Device or user not found: DeviceId={DeviceId}", deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
var alerts = CollectAlerts(telemetry, settings, device.DeviceName);
|
||||
// Get device settings for location
|
||||
var settings = await deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken);
|
||||
|
||||
foreach (var alert in alerts)
|
||||
// Get all enabled alert conditions for this device
|
||||
var conditions = await dbContext.AlertConditions
|
||||
.Include(c => c.Rules)
|
||||
.Where(c => c.DeviceId == deviceId && c.IsEnabled)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!conditions.Any())
|
||||
{
|
||||
await SendAlertIfNeededAsync(deviceId, device.User.Id, device.DeviceName, alert, cancellationToken);
|
||||
logger.LogDebug("No enabled alert conditions for device: DeviceId={DeviceId}", deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if it's daytime or nighttime
|
||||
bool? isDaytime = null;
|
||||
if (settings?.Latitude != null && settings.Longitude != null)
|
||||
{
|
||||
isDaytime = sunCalculatorService.IsDaytime(DateTime.UtcNow, settings.Latitude.Value, settings.Longitude.Value);
|
||||
}
|
||||
|
||||
// Check each condition
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
// Check time type filter
|
||||
if (condition.TimeType == Domain.AlertTimeType.Day && isDaytime == false)
|
||||
{
|
||||
continue; // This condition is for daytime only, but it's nighttime
|
||||
}
|
||||
if (condition.TimeType == Domain.AlertTimeType.Night && isDaytime == true)
|
||||
{
|
||||
continue; // This condition is for nighttime only, but it's daytime
|
||||
}
|
||||
|
||||
// Check if all rules match (AND logic)
|
||||
var allRulesMatch = condition.Rules.All(rule => CheckRule(rule, telemetry));
|
||||
|
||||
if (allRulesMatch && condition.Rules.Any())
|
||||
{
|
||||
// All rules passed, send alert if cooldown period has passed
|
||||
await SendAlertForConditionAsync(condition, device, telemetry, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<AlertInfo> CollectAlerts(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName)
|
||||
private bool CheckRule(Domain.AlertRule rule, TelemetryDto telemetry)
|
||||
{
|
||||
var alerts = new List<AlertInfo>();
|
||||
// Get sensor value
|
||||
var sensorValue = rule.SensorType switch
|
||||
{
|
||||
Domain.SensorType.Temperature => telemetry.TemperatureC,
|
||||
Domain.SensorType.Humidity => telemetry.HumidityPercent,
|
||||
Domain.SensorType.Soil => telemetry.SoilPercent,
|
||||
Domain.SensorType.Gas => telemetry.GasPPM,
|
||||
Domain.SensorType.Lux => telemetry.Lux,
|
||||
_ => 0m
|
||||
};
|
||||
|
||||
CheckTemperatureAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckHumidityAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckSoilAlert(telemetry, deviceName, alerts);
|
||||
CheckGasAlert(telemetry, settings, deviceName, alerts);
|
||||
CheckLuxAlert(telemetry, settings, deviceName, alerts);
|
||||
|
||||
return alerts;
|
||||
// Check comparison
|
||||
return rule.ComparisonType switch
|
||||
{
|
||||
Domain.ComparisonType.GreaterThan => sensorValue > rule.Value1,
|
||||
Domain.ComparisonType.LessThan => sensorValue < rule.Value1,
|
||||
Domain.ComparisonType.Between => rule.Value2 != null && sensorValue >= rule.Value1 && sensorValue <= rule.Value2.Value,
|
||||
Domain.ComparisonType.OutOfRange => rule.Value2 != null && (sensorValue < rule.Value1 || sensorValue > rule.Value2.Value),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private void CheckTemperatureAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.TemperatureC > settings.MaxTemperature)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Temperature",
|
||||
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداکثر مجاز ({settings.MaxTemperature}) بیشتر است.",
|
||||
ParameterName: "دما",
|
||||
Value: telemetry.TemperatureC,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.TemperatureC < settings.MinTemperature)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Temperature",
|
||||
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداقل مجاز ({settings.MinTemperature}) کمتر است.",
|
||||
ParameterName: "دما",
|
||||
Value: telemetry.TemperatureC,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckHumidityAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.HumidityPercent > settings.MaxHumidityPercent)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Humidity",
|
||||
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداکثر مجاز ({settings.MaxHumidityPercent}%) بیشتر است.",
|
||||
ParameterName: "رطوبت",
|
||||
Value: telemetry.HumidityPercent,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.HumidityPercent < settings.MinHumidityPercent)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Humidity",
|
||||
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداقل مجاز ({settings.MinHumidityPercent}%) کمتر است.",
|
||||
ParameterName: "رطوبت",
|
||||
Value: telemetry.HumidityPercent,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSoilAlert(TelemetryDto telemetry, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.SoilPercent > 100)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Soil",
|
||||
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
|
||||
ParameterName: "رطوبت خاک",
|
||||
Value: telemetry.SoilPercent,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.SoilPercent < 0)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Soil",
|
||||
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
|
||||
ParameterName: "رطوبت خاک",
|
||||
Value: telemetry.SoilPercent,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckGasAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.GasPPM > settings.MaxGasPPM)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Gas",
|
||||
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداکثر مجاز ({settings.MaxGasPPM}) بیشتر است.",
|
||||
ParameterName: "گاز Co",
|
||||
Value: telemetry.GasPPM,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.GasPPM < settings.MinGasPPM)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Gas",
|
||||
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداقل مجاز ({settings.MinGasPPM}) کمتر است.",
|
||||
ParameterName: "گاز Co",
|
||||
Value: telemetry.GasPPM,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckLuxAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
|
||||
{
|
||||
if (telemetry.Lux > settings.MaxLux)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Lux",
|
||||
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداکثر مجاز ({settings.MaxLux}) بیشتر است.",
|
||||
ParameterName: "نور",
|
||||
Value: telemetry.Lux,
|
||||
Status: "بالاتر"
|
||||
));
|
||||
}
|
||||
else if (telemetry.Lux < settings.MinLux)
|
||||
{
|
||||
alerts.Add(new AlertInfo(
|
||||
Type: "Lux",
|
||||
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداقل مجاز ({settings.MinLux}) کمتر است.",
|
||||
ParameterName: "نور",
|
||||
Value: telemetry.Lux,
|
||||
Status: "پایینتر"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAlertIfNeededAsync(
|
||||
int deviceId,
|
||||
int userId,
|
||||
string deviceName,
|
||||
AlertInfo alert,
|
||||
private async Task SendAlertForConditionAsync(
|
||||
Domain.AlertCondition condition,
|
||||
Domain.Device device,
|
||||
TelemetryDto telemetry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Check if alert was sent in the last 10 minutes
|
||||
var cooldownTime = DateTime.UtcNow.AddMinutes(-AlertCooldownMinutes);
|
||||
// Determine cooldown based on notification type
|
||||
var cooldownMinutes = condition.NotificationType == Domain.AlertNotificationType.Call
|
||||
? condition.CallCooldownMinutes
|
||||
: condition.SmsCooldownMinutes;
|
||||
|
||||
// Check if alert was sent recently
|
||||
var cooldownTime = DateTime.UtcNow.AddMinutes(-cooldownMinutes);
|
||||
var recentAlert = await dbContext.AlertNotifications
|
||||
.Where(a => a.DeviceId == deviceId &&
|
||||
a.UserId == userId &&
|
||||
a.AlertType == alert.Type &&
|
||||
.Where(a => a.DeviceId == device.Id &&
|
||||
a.UserId == device.User.Id &&
|
||||
a.AlertConditionId == condition.Id &&
|
||||
a.SentAt >= cooldownTime)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (recentAlert != null)
|
||||
{
|
||||
logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, AlertType={AlertType}", deviceId, alert.Type);
|
||||
logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, ConditionId={ConditionId}, Type={Type}",
|
||||
device.Id, condition.Id, condition.NotificationType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user to send SMS
|
||||
var user = await dbContext.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
// Build alert message
|
||||
var message = BuildAlertMessage(condition, device.DeviceName, telemetry);
|
||||
|
||||
if (user == null || string.IsNullOrWhiteSpace(user.Mobile))
|
||||
{
|
||||
logger.LogWarning("User not found or mobile is empty: UserId={UserId}", userId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send SMS and collect response/errors
|
||||
string? messageOutboxIdsJson = null;
|
||||
// Send notification
|
||||
string? messageOutboxIds = null;
|
||||
string? errorMessage = null;
|
||||
bool isSent = false;
|
||||
|
||||
try
|
||||
{
|
||||
var smsResponse = await smsService.SendPatternSmsAsync(new PatternSmsRequest
|
||||
if (condition.NotificationType == Domain.AlertNotificationType.SMS)
|
||||
{
|
||||
Recipients = [user.Mobile],
|
||||
PatternCode = "64di3w9kb0fxvif",
|
||||
Variables = new Dictionary<string, string> {
|
||||
{ "name", deviceName },
|
||||
{ "parameter", alert.ParameterName },
|
||||
{ "value", alert.Value.ToString("F1") },
|
||||
{ "status", alert.Status },
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
if (smsResponse != null)
|
||||
{
|
||||
// Check if SMS was sent successfully
|
||||
if (smsResponse.Meta.Status && smsResponse.Data != null && smsResponse.Data.MessageOutboxIds != null && smsResponse.Data.MessageOutboxIds.Count > 0)
|
||||
{
|
||||
// Success - save message outbox IDs
|
||||
messageOutboxIdsJson = JsonSerializer.Serialize(smsResponse.Data.MessageOutboxIds);
|
||||
isSent = true;
|
||||
logger.LogInformation("Alert SMS sent: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, OutboxIds={OutboxIds}",
|
||||
deviceId, userId, alert.Type, messageOutboxIdsJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed - save error from meta
|
||||
var errors = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(smsResponse.Meta.Message))
|
||||
{
|
||||
errors.Add(smsResponse.Meta.Message);
|
||||
}
|
||||
if (smsResponse.Meta.Errors != null && smsResponse.Meta.Errors.Count > 0)
|
||||
{
|
||||
foreach (var error in smsResponse.Meta.Errors)
|
||||
{
|
||||
errors.Add($"{error.Key}: {string.Join(", ", error.Value)}");
|
||||
}
|
||||
}
|
||||
if (errors.Count == 0)
|
||||
{
|
||||
errors.Add("SMS sending failed with unknown error");
|
||||
}
|
||||
errorMessage = string.Join(" | ", errors);
|
||||
isSent = false;
|
||||
logger.LogWarning("Alert SMS failed: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, Error={Error}",
|
||||
deviceId, userId, alert.Type, errorMessage);
|
||||
}
|
||||
(isSent, messageOutboxIds, errorMessage) = await SendSmsAlertAsync(device.User.Mobile, device.DeviceName, message, cancellationToken);
|
||||
}
|
||||
else
|
||||
else // Call
|
||||
{
|
||||
errorMessage = "SMS service returned null response";
|
||||
isSent = false;
|
||||
logger.LogWarning("Alert SMS returned null: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}",
|
||||
deviceId, userId, alert.Type);
|
||||
(isSent, messageOutboxIds, errorMessage) = await SendCallAlertAsync(device.User.Mobile, device.DeviceName, message, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -296,17 +170,19 @@ public sealed class AlertService : IAlertService
|
||||
errorMessage += $" | InnerException: {ex.InnerException.Message}";
|
||||
}
|
||||
isSent = false;
|
||||
logger.LogError(ex, "Failed to send alert SMS: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}", deviceId, userId, alert.Type);
|
||||
logger.LogError(ex, "Failed to send alert: DeviceId={DeviceId}, ConditionId={ConditionId}, Type={Type}",
|
||||
device.Id, condition.Id, condition.NotificationType);
|
||||
}
|
||||
|
||||
// Save notification to database
|
||||
var notification = new Domain.AlertNotification
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
UserId = userId,
|
||||
AlertType = alert.Type,
|
||||
Message = alert.Message,
|
||||
MessageOutboxIds = messageOutboxIdsJson,
|
||||
DeviceId = device.Id,
|
||||
UserId = device.User.Id,
|
||||
AlertConditionId = condition.Id,
|
||||
NotificationType = condition.NotificationType,
|
||||
Message = message,
|
||||
MessageOutboxIds = messageOutboxIds,
|
||||
ErrorMessage = errorMessage,
|
||||
SentAt = DateTime.UtcNow,
|
||||
IsSent = isSent
|
||||
@@ -315,5 +191,137 @@ public sealed class AlertService : IAlertService
|
||||
dbContext.AlertNotifications.Add(notification);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private string BuildAlertMessage(Domain.AlertCondition condition, string deviceName, TelemetryDto telemetry)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
parts.Add($"هشدار گلخانه {deviceName}:");
|
||||
|
||||
foreach (var rule in condition.Rules.OrderBy(r => r.Order))
|
||||
{
|
||||
var sensorName = rule.SensorType switch
|
||||
{
|
||||
Domain.SensorType.Temperature => "دما",
|
||||
Domain.SensorType.Humidity => "رطوبت",
|
||||
Domain.SensorType.Soil => "رطوبت خاک",
|
||||
Domain.SensorType.Gas => "گاز",
|
||||
Domain.SensorType.Lux => "نور",
|
||||
_ => "سنسور"
|
||||
};
|
||||
|
||||
var sensorValue = rule.SensorType switch
|
||||
{
|
||||
Domain.SensorType.Temperature => telemetry.TemperatureC,
|
||||
Domain.SensorType.Humidity => telemetry.HumidityPercent,
|
||||
Domain.SensorType.Soil => telemetry.SoilPercent,
|
||||
Domain.SensorType.Gas => telemetry.GasPPM,
|
||||
Domain.SensorType.Lux => telemetry.Lux,
|
||||
_ => 0m
|
||||
};
|
||||
|
||||
var unit = rule.SensorType switch
|
||||
{
|
||||
Domain.SensorType.Temperature => "°C",
|
||||
Domain.SensorType.Humidity => "%",
|
||||
Domain.SensorType.Soil => "%",
|
||||
Domain.SensorType.Gas => "PPM",
|
||||
Domain.SensorType.Lux => "لوکس",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
var conditionText = rule.ComparisonType switch
|
||||
{
|
||||
Domain.ComparisonType.GreaterThan => $"{sensorName} ({sensorValue:F1}{unit}) بیشتر از {rule.Value1}{unit}",
|
||||
Domain.ComparisonType.LessThan => $"{sensorName} ({sensorValue:F1}{unit}) کمتر از {rule.Value1}{unit}",
|
||||
Domain.ComparisonType.Between => $"{sensorName} ({sensorValue:F1}{unit}) بین {rule.Value1} و {rule.Value2}{unit}",
|
||||
Domain.ComparisonType.OutOfRange => $"{sensorName} ({sensorValue:F1}{unit}) خارج از محدوده {rule.Value1} تا {rule.Value2}{unit}",
|
||||
_ => $"{sensorName}: {sensorValue:F1}{unit}"
|
||||
};
|
||||
|
||||
parts.Add(conditionText);
|
||||
}
|
||||
|
||||
return string.Join(" و ", parts);
|
||||
}
|
||||
|
||||
private async Task<(bool isSent, string? messageOutboxIds, string? errorMessage)> SendSmsAlertAsync(
|
||||
string mobile,
|
||||
string deviceName,
|
||||
string message,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var smsResponse = await smsService.SendPatternSmsAsync(new PatternSmsRequest
|
||||
{
|
||||
Recipients = [mobile],
|
||||
PatternCode = "64di3w9kb0fxvif",
|
||||
Variables = new Dictionary<string, string> {
|
||||
{ "name", deviceName },
|
||||
{ "parameter", "شرایط" },
|
||||
{ "value", message },
|
||||
{ "status", "هشدار" }
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
if (smsResponse != null && smsResponse.Meta.Status &&
|
||||
smsResponse.Data?.MessageOutboxIds != null &&
|
||||
smsResponse.Data.MessageOutboxIds.Count > 0)
|
||||
{
|
||||
var outboxIds = JsonSerializer.Serialize(smsResponse.Data.MessageOutboxIds);
|
||||
logger.LogInformation("Alert SMS sent: Mobile={Mobile}, OutboxIds={OutboxIds}", mobile, outboxIds);
|
||||
return (true, outboxIds, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errors = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(smsResponse?.Meta.Message))
|
||||
{
|
||||
errors.Add(smsResponse.Meta.Message);
|
||||
}
|
||||
if (smsResponse?.Meta.Errors != null)
|
||||
{
|
||||
foreach (var error in smsResponse.Meta.Errors)
|
||||
{
|
||||
errors.Add($"{error.Key}: {string.Join(", ", error.Value)}");
|
||||
}
|
||||
}
|
||||
var errorMsg = errors.Count > 0 ? string.Join(" | ", errors) : "Unknown SMS error";
|
||||
logger.LogWarning("Alert SMS failed: Mobile={Mobile}, Error={Error}", mobile, errorMsg);
|
||||
return (false, null, errorMsg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"Exception: {ex.Message}";
|
||||
logger.LogError(ex, "Exception sending SMS alert: Mobile={Mobile}", mobile);
|
||||
return (false, null, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool isSent, string? callId, string? errorMessage)> SendCallAlertAsync(
|
||||
string mobile,
|
||||
string deviceName,
|
||||
string message,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Implement voice call integration
|
||||
// For now, just log and return success
|
||||
logger.LogInformation("Voice call alert requested: Mobile={Mobile}, Message={Message}", mobile, message);
|
||||
|
||||
// Placeholder: In real implementation, call voiceCallService here
|
||||
// var callResponse = await voiceCallService.MakeCallAsync(mobile, message, cancellationToken);
|
||||
|
||||
return (true, null, "Voice call not yet implemented");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"Exception: {ex.Message}";
|
||||
logger.LogError(ex, "Exception sending call alert: Mobile={Mobile}", mobile);
|
||||
return (false, null, errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
229
src/GreenHome.Infrastructure/DailyReportService.cs
Normal file
229
src/GreenHome.Infrastructure/DailyReportService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<ProjectReference Include="..\GreenHome.Application\GreenHome.Application.csproj" />
|
||||
<ProjectReference Include="..\GreenHome.Domain\GreenHome.Domain.csproj" />
|
||||
<ProjectReference Include="..\GreenHome.Sms.Ippanel\GreenHome.Sms.Ippanel.csproj" />
|
||||
<ProjectReference Include="..\GreenHome.VoiceCall.Avanak\GreenHome.VoiceCall.Avanak.csproj" />
|
||||
<ProjectReference Include="..\GreenHome.AI.DeepSeek\GreenHome.AI.DeepSeek.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,10 +9,14 @@ public sealed class GreenHomeDbContext : DbContext
|
||||
public DbSet<Domain.Device> Devices => Set<Domain.Device>();
|
||||
public DbSet<Domain.TelemetryRecord> TelemetryRecords => Set<Domain.TelemetryRecord>();
|
||||
public DbSet<Domain.DeviceSettings> DeviceSettings => Set<Domain.DeviceSettings>();
|
||||
public DbSet<Domain.AlertCondition> AlertConditions => Set<Domain.AlertCondition>();
|
||||
public DbSet<Domain.AlertRule> AlertRules => Set<Domain.AlertRule>();
|
||||
public DbSet<Domain.User> Users => Set<Domain.User>();
|
||||
public DbSet<Domain.VerificationCode> VerificationCodes => Set<Domain.VerificationCode>();
|
||||
public DbSet<Domain.DeviceUser> DeviceUsers => Set<Domain.DeviceUser>();
|
||||
public DbSet<Domain.AlertNotification> AlertNotifications => Set<Domain.AlertNotification>();
|
||||
public DbSet<Domain.AIQuery> AIQueries => Set<Domain.AIQuery>();
|
||||
public DbSet<Domain.DailyReport> DailyReports => Set<Domain.DailyReport>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -50,14 +54,10 @@ public sealed class GreenHomeDbContext : DbContext
|
||||
{
|
||||
b.ToTable("DeviceSettings");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.DangerMaxTemperature).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.DangerMinTemperature).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MaxTemperature).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MinTemperature).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MaxLux).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MinLux).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MaxHumidityPercent).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.MinHumidityPercent).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.Province).HasMaxLength(100);
|
||||
b.Property(x => x.City).HasMaxLength(100);
|
||||
b.Property(x => x.Latitude).HasColumnType("decimal(9,6)");
|
||||
b.Property(x => x.Longitude).HasColumnType("decimal(9,6)");
|
||||
b.HasOne(x => x.Device)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DeviceId)
|
||||
@@ -65,6 +65,38 @@ public sealed class GreenHomeDbContext : DbContext
|
||||
b.HasIndex(x => x.DeviceId).IsUnique();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Domain.AlertCondition>(b =>
|
||||
{
|
||||
b.ToTable("AlertConditions");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.NotificationType).IsRequired().HasConversion<int>();
|
||||
b.Property(x => x.TimeType).IsRequired().HasConversion<int>();
|
||||
b.Property(x => x.CallCooldownMinutes).IsRequired();
|
||||
b.Property(x => x.SmsCooldownMinutes).IsRequired();
|
||||
b.Property(x => x.IsEnabled).IsRequired();
|
||||
b.HasOne(x => x.Device)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DeviceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasIndex(x => x.DeviceId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Domain.AlertRule>(b =>
|
||||
{
|
||||
b.ToTable("AlertRules");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.SensorType).IsRequired().HasConversion<int>();
|
||||
b.Property(x => x.ComparisonType).IsRequired().HasConversion<int>();
|
||||
b.Property(x => x.Value1).IsRequired().HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.Value2).HasColumnType("decimal(18,2)");
|
||||
b.Property(x => x.Order).IsRequired();
|
||||
b.HasOne(x => x.AlertCondition)
|
||||
.WithMany(c => c.Rules)
|
||||
.HasForeignKey(x => x.AlertConditionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasIndex(x => x.AlertConditionId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Domain.User>(b =>
|
||||
{
|
||||
b.ToTable("Users");
|
||||
@@ -103,7 +135,7 @@ public sealed class GreenHomeDbContext : DbContext
|
||||
{
|
||||
b.ToTable("AlertNotifications");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.AlertType).IsRequired().HasMaxLength(50);
|
||||
b.Property(x => x.NotificationType).IsRequired().HasConversion<int>();
|
||||
b.Property(x => x.Message).IsRequired().HasMaxLength(500);
|
||||
b.Property(x => x.MessageOutboxIds).HasMaxLength(500);
|
||||
b.Property(x => x.ErrorMessage).HasMaxLength(1000);
|
||||
@@ -115,7 +147,47 @@ public sealed class GreenHomeDbContext : DbContext
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.UserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
b.HasIndex(x => new { x.DeviceId, x.UserId, x.AlertType, x.SentAt });
|
||||
b.HasOne(x => x.AlertCondition)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.AlertConditionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
b.HasIndex(x => new { x.DeviceId, x.UserId, x.AlertConditionId, x.SentAt });
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Domain.AIQuery>(b =>
|
||||
{
|
||||
b.ToTable("AIQueries");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.Question).IsRequired();
|
||||
b.Property(x => x.Answer).IsRequired();
|
||||
b.Property(x => x.Model).HasMaxLength(100);
|
||||
b.HasOne(x => x.Device)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DeviceId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.UserId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
b.HasIndex(x => x.DeviceId);
|
||||
b.HasIndex(x => x.UserId);
|
||||
b.HasIndex(x => x.CreatedAt);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Domain.DailyReport>(b =>
|
||||
{
|
||||
b.ToTable("DailyReports");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10);
|
||||
b.Property(x => x.Analysis).IsRequired();
|
||||
b.Property(x => x.Model).HasMaxLength(100);
|
||||
b.HasOne(x => x.Device)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DeviceId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasIndex(x => new { x.DeviceId, x.PersianDate }).IsUnique();
|
||||
b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth });
|
||||
b.HasIndex(x => x.CreatedAt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
453
src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.Designer.cs
generated
Normal file
453
src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.Designer.cs
generated
Normal file
@@ -0,0 +1,453 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GreenHome.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(GreenHomeDbContext))]
|
||||
[Migration("20251216113127_AddAIQueryTable")]
|
||||
partial class AddAIQueryTable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double?>("Temperature")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AIQueries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AlertType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<bool>("IsSent")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MessageOutboxIds")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
|
||||
|
||||
b.ToTable("AlertNotifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("nvarchar(250)");
|
||||
|
||||
b.Property<string>("NeshanLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Devices", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("DangerMaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("DangerMinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MaxGasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MaxHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MaxLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("MinGasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MinHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DeviceSettings", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("DeviceId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DeviceUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("GasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("HumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("Lux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("ServerTimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("SoilPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("TemperatureC")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("TimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId", "ServerTimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "TimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("Telemetry", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Family")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4)
|
||||
.HasColumnType("nvarchar(4)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<DateTime?>("UsedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile", "Code", "IsUsed");
|
||||
|
||||
b.ToTable("VerificationCodes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAIQueryTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AIQueries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DeviceId = table.Column<int>(type: "int", nullable: true),
|
||||
Question = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Answer = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
PromptTokens = table.Column<int>(type: "int", nullable: false),
|
||||
CompletionTokens = table.Column<int>(type: "int", nullable: false),
|
||||
TotalTokens = table.Column<int>(type: "int", nullable: false),
|
||||
Model = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Temperature = table.Column<double>(type: "float", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ResponseTimeMs = table.Column<long>(type: "bigint", nullable: true),
|
||||
UserId = table.Column<int>(type: "int", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AIQueries", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AIQueries_Devices_DeviceId",
|
||||
column: x => x.DeviceId,
|
||||
principalTable: "Devices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_AIQueries_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AIQueries_CreatedAt",
|
||||
table: "AIQueries",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AIQueries_DeviceId",
|
||||
table: "AIQueries",
|
||||
column: "DeviceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AIQueries_UserId",
|
||||
table: "AIQueries",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AIQueries");
|
||||
}
|
||||
}
|
||||
}
|
||||
530
src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.Designer.cs
generated
Normal file
530
src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.Designer.cs
generated
Normal file
@@ -0,0 +1,530 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GreenHome.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(GreenHomeDbContext))]
|
||||
[Migration("20251216120357_adddailyreport")]
|
||||
partial class adddailyreport
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double?>("Temperature")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AIQueries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AlertType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<bool>("IsSent")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MessageOutboxIds")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
|
||||
|
||||
b.ToTable("AlertNotifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Analysis")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianDay")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("SampledRecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianDate")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("DailyReports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("nvarchar(250)");
|
||||
|
||||
b.Property<string>("NeshanLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Devices", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("DangerMaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("DangerMinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MaxGasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MaxHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MaxLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("MinGasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MinHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DeviceSettings", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("DeviceId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DeviceUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("GasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("HumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("Lux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("ServerTimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("SoilPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("TemperatureC")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("TimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId", "ServerTimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "TimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("Telemetry", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Family")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4)
|
||||
.HasColumnType("nvarchar(4)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<DateTime?>("UsedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile", "Code", "IsUsed");
|
||||
|
||||
b.ToTable("VerificationCodes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class adddailyreport : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DailyReports",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DeviceId = table.Column<int>(type: "int", nullable: false),
|
||||
PersianDate = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
PersianYear = table.Column<int>(type: "int", nullable: false),
|
||||
PersianMonth = table.Column<int>(type: "int", nullable: false),
|
||||
PersianDay = table.Column<int>(type: "int", nullable: false),
|
||||
Analysis = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
RecordCount = table.Column<int>(type: "int", nullable: false),
|
||||
SampledRecordCount = table.Column<int>(type: "int", nullable: false),
|
||||
PromptTokens = table.Column<int>(type: "int", nullable: false),
|
||||
CompletionTokens = table.Column<int>(type: "int", nullable: false),
|
||||
TotalTokens = table.Column<int>(type: "int", nullable: false),
|
||||
Model = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ResponseTimeMs = table.Column<long>(type: "bigint", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DailyReports", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DailyReports_Devices_DeviceId",
|
||||
column: x => x.DeviceId,
|
||||
principalTable: "Devices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_CreatedAt",
|
||||
table: "DailyReports",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_DeviceId_PersianDate",
|
||||
table: "DailyReports",
|
||||
columns: new[] { "DeviceId", "PersianDate" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_DeviceId_PersianYear_PersianMonth",
|
||||
table: "DailyReports",
|
||||
columns: new[] { "DeviceId", "PersianYear", "PersianMonth" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DailyReports");
|
||||
}
|
||||
}
|
||||
}
|
||||
626
src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs
generated
Normal file
626
src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs
generated
Normal file
@@ -0,0 +1,626 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GreenHome.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(GreenHomeDbContext))]
|
||||
[Migration("20251216131032_UpdateAlertSystemWithConditions")]
|
||||
partial class UpdateAlertSystemWithConditions
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double?>("Temperature")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AIQueries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("CallCooldownMinutes")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("NotificationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SmsCooldownMinutes")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TimeType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.ToTable("AlertConditions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AlertConditionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<bool>("IsSent")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MessageOutboxIds")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("NotificationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlertConditionId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "AlertConditionId", "SentAt");
|
||||
|
||||
b.ToTable("AlertNotifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AlertConditionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ComparisonType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SensorType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Value1")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal?>("Value2")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlertConditionId");
|
||||
|
||||
b.ToTable("AlertRules", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Analysis")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianDay")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("SampledRecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianDate")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("DailyReports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("nvarchar(250)");
|
||||
|
||||
b.Property<string>("NeshanLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Devices", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("City")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal?>("Latitude")
|
||||
.HasColumnType("decimal(9,6)");
|
||||
|
||||
b.Property<decimal?>("Longitude")
|
||||
.HasColumnType("decimal(9,6)");
|
||||
|
||||
b.Property<string>("Province")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DeviceSettings", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("DeviceId", "UserId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("DeviceUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("GasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("HumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("Lux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("ServerTimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("SoilPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("TemperatureC")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("TimestampUtc")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId", "ServerTimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "TimestampUtc");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("Telemetry", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Family")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4)
|
||||
.HasColumnType("nvarchar(4)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(11)
|
||||
.HasColumnType("nvarchar(11)");
|
||||
|
||||
b.Property<DateTime?>("UsedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Mobile", "Code", "IsUsed");
|
||||
|
||||
b.ToTable("VerificationCodes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
|
||||
.WithMany()
|
||||
.HasForeignKey("AlertConditionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AlertCondition");
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
|
||||
.WithMany("Rules")
|
||||
.HasForeignKey("AlertConditionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AlertCondition");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany("DeviceUsers")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.Navigation("Rules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.User", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateAlertSystemWithConditions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_AlertNotifications_DeviceId_UserId_AlertType_SentAt",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DangerMaxTemperature",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DangerMinTemperature",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxGasPPM",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxHumidityPercent",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxLux",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxTemperature",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinGasPPM",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinHumidityPercent",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinLux",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MinTemperature",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AlertType",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "City",
|
||||
table: "DeviceSettings",
|
||||
type: "nvarchar(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "Latitude",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(9,6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "Longitude",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(9,6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Province",
|
||||
table: "DeviceSettings",
|
||||
type: "nvarchar(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "AlertConditionId",
|
||||
table: "AlertNotifications",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "NotificationType",
|
||||
table: "AlertNotifications",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AlertConditions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DeviceId = table.Column<int>(type: "int", nullable: false),
|
||||
NotificationType = table.Column<int>(type: "int", nullable: false),
|
||||
TimeType = table.Column<int>(type: "int", nullable: false),
|
||||
CallCooldownMinutes = table.Column<int>(type: "int", nullable: false),
|
||||
SmsCooldownMinutes = table.Column<int>(type: "int", nullable: false),
|
||||
IsEnabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AlertConditions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AlertConditions_Devices_DeviceId",
|
||||
column: x => x.DeviceId,
|
||||
principalTable: "Devices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AlertRules",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
AlertConditionId = table.Column<int>(type: "int", nullable: false),
|
||||
SensorType = table.Column<int>(type: "int", nullable: false),
|
||||
ComparisonType = table.Column<int>(type: "int", nullable: false),
|
||||
Value1 = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
|
||||
Value2 = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
|
||||
Order = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AlertRules", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AlertRules_AlertConditions_AlertConditionId",
|
||||
column: x => x.AlertConditionId,
|
||||
principalTable: "AlertConditions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AlertNotifications_AlertConditionId",
|
||||
table: "AlertNotifications",
|
||||
column: "AlertConditionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AlertNotifications_DeviceId_UserId_AlertConditionId_SentAt",
|
||||
table: "AlertNotifications",
|
||||
columns: new[] { "DeviceId", "UserId", "AlertConditionId", "SentAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AlertConditions_DeviceId",
|
||||
table: "AlertConditions",
|
||||
column: "DeviceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AlertRules_AlertConditionId",
|
||||
table: "AlertRules",
|
||||
column: "AlertConditionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_AlertNotifications_AlertConditions_AlertConditionId",
|
||||
table: "AlertNotifications",
|
||||
column: "AlertConditionId",
|
||||
principalTable: "AlertConditions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_AlertNotifications_AlertConditions_AlertConditionId",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AlertRules");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AlertConditions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_AlertNotifications_AlertConditionId",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_AlertNotifications_DeviceId_UserId_AlertConditionId_SentAt",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "City",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Latitude",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Longitude",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Province",
|
||||
table: "DeviceSettings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AlertConditionId",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "NotificationType",
|
||||
table: "AlertNotifications");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DangerMaxTemperature",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DangerMinTemperature",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MaxGasPPM",
|
||||
table: "DeviceSettings",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MaxHumidityPercent",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MaxLux",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MaxTemperature",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MinGasPPM",
|
||||
table: "DeviceSettings",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MinHumidityPercent",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MinLux",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MinTemperature",
|
||||
table: "DeviceSettings",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AlertType",
|
||||
table: "AlertNotifications",
|
||||
type: "nvarchar(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AlertNotifications_DeviceId_UserId_AlertType_SentAt",
|
||||
table: "AlertNotifications",
|
||||
columns: new[] { "DeviceId", "UserId", "AlertType", "SentAt" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GreenHome.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDailyReportsTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DailyReports",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DeviceId = table.Column<int>(type: "int", nullable: false),
|
||||
PersianDate = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
PersianYear = table.Column<int>(type: "int", nullable: false),
|
||||
PersianMonth = table.Column<int>(type: "int", nullable: false),
|
||||
PersianDay = table.Column<int>(type: "int", nullable: false),
|
||||
Analysis = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
RecordCount = table.Column<int>(type: "int", nullable: false),
|
||||
SampledRecordCount = table.Column<int>(type: "int", nullable: false),
|
||||
PromptTokens = table.Column<int>(type: "int", nullable: false),
|
||||
CompletionTokens = table.Column<int>(type: "int", nullable: false),
|
||||
TotalTokens = table.Column<int>(type: "int", nullable: false),
|
||||
Model = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ResponseTimeMs = table.Column<long>(type: "bigint", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DailyReports", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DailyReports_Devices_DeviceId",
|
||||
column: x => x.DeviceId,
|
||||
principalTable: "Devices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_CreatedAt",
|
||||
table: "DailyReports",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_DeviceId_PersianDate",
|
||||
table: "DailyReports",
|
||||
columns: new[] { "DeviceId", "PersianDate" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DailyReports_DeviceId_PersianYear_PersianMonth",
|
||||
table: "DailyReports",
|
||||
columns: new[] { "DeviceId", "PersianYear", "PersianMonth" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DailyReports");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,100 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double?>("Temperature")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AIQueries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("CallCooldownMinutes")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("NotificationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SmsCooldownMinutes")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TimeType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.ToTable("AlertConditions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -30,10 +124,8 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AlertType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
b.Property<int>("AlertConditionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
@@ -54,6 +146,9 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("NotificationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
@@ -62,13 +157,114 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlertConditionId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
|
||||
b.HasIndex("DeviceId", "UserId", "AlertConditionId", "SentAt");
|
||||
|
||||
b.ToTable("AlertNotifications", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AlertConditionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ComparisonType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SensorType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Value1")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal?>("Value2")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlertConditionId");
|
||||
|
||||
b.ToTable("AlertRules", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Analysis")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PersianDate")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<int>("PersianDay")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PersianYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long?>("ResponseTimeMs")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("SampledRecordCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TotalTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DeviceId", "PersianDate")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||
|
||||
b.ToTable("DailyReports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -110,41 +306,27 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("City")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<decimal>("DangerMaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("DangerMinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("DeviceId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("MaxGasPPM")
|
||||
.HasColumnType("int");
|
||||
b.Property<decimal?>("Latitude")
|
||||
.HasColumnType("decimal(9,6)");
|
||||
|
||||
b.Property<decimal>("MaxHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
b.Property<decimal?>("Longitude")
|
||||
.HasColumnType("decimal(9,6)");
|
||||
|
||||
b.Property<decimal>("MaxLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MaxTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<int>("MinGasPPM")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MinHumidityPercent")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinLux")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("MinTemperature")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
b.Property<string>("Province")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
@@ -303,8 +485,42 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
b.ToTable("VerificationCodes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AIQuery", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
|
||||
.WithMany()
|
||||
.HasForeignKey("AlertConditionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
@@ -317,11 +533,35 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AlertCondition");
|
||||
|
||||
b.Navigation("Device");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertRule", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition")
|
||||
.WithMany("Rules")
|
||||
.HasForeignKey("AlertConditionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AlertCondition");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.DailyReport", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||
.WithMany()
|
||||
.HasForeignKey("DeviceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Device");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.HasOne("GreenHome.Domain.User", "User")
|
||||
@@ -363,6 +603,11 @@ namespace GreenHome.Infrastructure.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.AlertCondition", b =>
|
||||
{
|
||||
b.Navigation("Rules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GreenHome.Domain.Device", b =>
|
||||
{
|
||||
b.Navigation("DeviceUsers");
|
||||
|
||||
125
src/GreenHome.Infrastructure/SunCalculatorService.cs
Normal file
125
src/GreenHome.Infrastructure/SunCalculatorService.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using GreenHome.Application;
|
||||
|
||||
namespace GreenHome.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس محاسبه طلوع و غروب خورشید
|
||||
/// </summary>
|
||||
public sealed class SunCalculatorService : ISunCalculatorService
|
||||
{
|
||||
public bool IsDaytime(DateTime dateTime, decimal latitude, decimal longitude)
|
||||
{
|
||||
var lat = (double)latitude;
|
||||
var lng = (double)longitude;
|
||||
|
||||
// Calculate sunrise and sunset times
|
||||
var (sunrise, sunset) = CalculateSunriseSunset(dateTime, lat, lng);
|
||||
|
||||
// Check if current time is between sunrise and sunset
|
||||
var currentTime = dateTime.TimeOfDay;
|
||||
return currentTime >= sunrise && currentTime <= sunset;
|
||||
}
|
||||
|
||||
private (TimeSpan sunrise, TimeSpan sunset) CalculateSunriseSunset(DateTime date, double latitude, double longitude)
|
||||
{
|
||||
// Julian day calculation
|
||||
var julianDay = CalculateJulianDay(date);
|
||||
var julianCentury = (julianDay - 2451545.0) / 36525.0;
|
||||
|
||||
// Sun's mean longitude
|
||||
var sunMeanLongitude = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360;
|
||||
|
||||
// Sun's mean anomaly
|
||||
var sunMeanAnomaly = 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury);
|
||||
|
||||
// Earth's orbit eccentricity
|
||||
var eccentricity = 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury);
|
||||
|
||||
// Sun's equation of center
|
||||
var sunCenter = Math.Sin(ToRadians(sunMeanAnomaly)) * (1.914602 - julianCentury * (0.004817 + 0.000014 * julianCentury))
|
||||
+ Math.Sin(ToRadians(2 * sunMeanAnomaly)) * (0.019993 - 0.000101 * julianCentury)
|
||||
+ Math.Sin(ToRadians(3 * sunMeanAnomaly)) * 0.000289;
|
||||
|
||||
// Sun's true longitude
|
||||
var sunTrueLongitude = sunMeanLongitude + sunCenter;
|
||||
|
||||
// Sun's apparent longitude
|
||||
var sunApparentLongitude = sunTrueLongitude - 0.00569 - 0.00478 * Math.Sin(ToRadians(125.04 - 1934.136 * julianCentury));
|
||||
|
||||
// Mean oblique ecliptic
|
||||
var meanOblique = 23.0 + (26.0 + ((21.448 - julianCentury * (46.815 + julianCentury * (0.00059 - julianCentury * 0.001813)))) / 60.0) / 60.0;
|
||||
|
||||
// Oblique correction
|
||||
var obliqueCorrection = meanOblique + 0.00256 * Math.Cos(ToRadians(125.04 - 1934.136 * julianCentury));
|
||||
|
||||
// Sun's declination
|
||||
var declination = ToDegrees(Math.Asin(Math.Sin(ToRadians(obliqueCorrection)) * Math.Sin(ToRadians(sunApparentLongitude))));
|
||||
|
||||
// Equation of time
|
||||
var y = Math.Tan(ToRadians(obliqueCorrection / 2.0)) * Math.Tan(ToRadians(obliqueCorrection / 2.0));
|
||||
var equationOfTime = 4.0 * ToDegrees(y * Math.Sin(2.0 * ToRadians(sunMeanLongitude))
|
||||
- 2.0 * eccentricity * Math.Sin(ToRadians(sunMeanAnomaly))
|
||||
+ 4.0 * eccentricity * y * Math.Sin(ToRadians(sunMeanAnomaly)) * Math.Cos(2.0 * ToRadians(sunMeanLongitude))
|
||||
- 0.5 * y * y * Math.Sin(4.0 * ToRadians(sunMeanLongitude))
|
||||
- 1.25 * eccentricity * eccentricity * Math.Sin(2.0 * ToRadians(sunMeanAnomaly)));
|
||||
|
||||
// Hour angle sunrise (civil twilight: sun 6 degrees below horizon)
|
||||
var zenith = 90.833; // Official: 90 degrees 50 minutes
|
||||
var hourAngle = ToDegrees(Math.Acos(
|
||||
(Math.Cos(ToRadians(zenith)) / (Math.Cos(ToRadians(latitude)) * Math.Cos(ToRadians(declination))))
|
||||
- Math.Tan(ToRadians(latitude)) * Math.Tan(ToRadians(declination))
|
||||
));
|
||||
|
||||
// Calculate sunrise and sunset in minutes
|
||||
var solarNoon = (720.0 - 4.0 * longitude - equationOfTime) / 1440.0;
|
||||
var sunriseTime = solarNoon - hourAngle * 4.0 / 1440.0;
|
||||
var sunsetTime = solarNoon + hourAngle * 4.0 / 1440.0;
|
||||
|
||||
// Convert to local time (assume UTC offset for Iran: +3:30 = 210 minutes)
|
||||
// You should ideally calculate timezone offset based on longitude
|
||||
var utcOffsetMinutes = Math.Round(longitude / 15.0) * 60.0;
|
||||
|
||||
var sunriseMinutes = sunriseTime * 1440.0 + utcOffsetMinutes;
|
||||
var sunsetMinutes = sunsetTime * 1440.0 + utcOffsetMinutes;
|
||||
|
||||
// Handle edge cases
|
||||
if (sunriseMinutes < 0) sunriseMinutes += 1440;
|
||||
if (sunriseMinutes >= 1440) sunriseMinutes -= 1440;
|
||||
if (sunsetMinutes < 0) sunsetMinutes += 1440;
|
||||
if (sunsetMinutes >= 1440) sunsetMinutes -= 1440;
|
||||
|
||||
var sunrise = TimeSpan.FromMinutes(sunriseMinutes);
|
||||
var sunset = TimeSpan.FromMinutes(sunsetMinutes);
|
||||
|
||||
return (sunrise, sunset);
|
||||
}
|
||||
|
||||
private double CalculateJulianDay(DateTime date)
|
||||
{
|
||||
var year = date.Year;
|
||||
var month = date.Month;
|
||||
var day = date.Day;
|
||||
|
||||
if (month <= 2)
|
||||
{
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
var a = year / 100;
|
||||
var b = 2 - a + (a / 4);
|
||||
|
||||
return Math.Floor(365.25 * (year + 4716)) + Math.Floor(30.6001 * (month + 1)) + day + b - 1524.5;
|
||||
}
|
||||
|
||||
private double ToRadians(double degrees)
|
||||
{
|
||||
return degrees * Math.PI / 180.0;
|
||||
}
|
||||
|
||||
private double ToDegrees(double radians)
|
||||
{
|
||||
return radians * 180.0 / Math.PI;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user