using EasyTemplate.Tool;
using EasyTemplate.Tool.Entity;
using EasyTemplate.Tool.Entity.App;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EasyTemplate.Service
{
///
/// 预警告统计服务
///
public class WarningService
{
private readonly SqlSugarRepository _prewarning;
private readonly SqlSugarRepository _record;
///
/// 累计状态快照(用于缓存中间计算结果)
///
private class AccumulatedState
{
public int AccOver { get; set; } // 累计超标记录
public int AccOverAlert { get; set; } // 累计严重超标记录
public int AccTotal { get; set; } // 累计总数
public int AccOver2 { get; set; } // 累计超标2记录
public int ContinueDays { get; set; } // 连续超标天数
public int ContinueDays2 { get; set; } // 连续超标2天数
public int ContinueDaysAlert { get; set; } // 连续严重超标天数
public int LastRate { get; set; } // 最后累计超标率
public int LastRate2 { get; set; } // 最后超标率2
public int LastRateAlert { get; set; } // 最后严重超标率
}
public WarningService(SqlSugarRepository prewarning, SqlSugarRepository record)
{
_prewarning = prewarning;
_record = record;
}
///
/// 获取 35 天内所有油枪的统计数据
///
public async Task> Get35DaysStatisticsAsync()
{
var endDate = DateOnly.FromDateTime(DateTime.Today);
var startDate = endDate.AddDays(-34); // 包含今天共 35 天
return await CalculateStatisticsAsync(startDate, endDate);
}
///
/// 按日期范围获取统计数据
///
public async Task> GetStatisticsByDateRangeAsync(DateOnly startDate, DateOnly endDate)
{
return await CalculateStatisticsAsync(startDate, endDate);
}
///
/// 按油枪和日期范围筛选统计数据
///
public async Task> GetStatisticsByFilterAsync(WarningStatisticsQuery query)
{
var endDate = query.EndDate ?? DateOnly.FromDateTime(DateTime.Today);
var startDate = query.StartDate ?? endDate.AddDays(-34);
var allStats = await CalculateStatisticsAsync(startDate, endDate);
// 按油枪筛选
if (query.NozzleId.HasValue)
{
allStats = allStats.Where(s => s.nozzle == query.NozzleId.Value).ToList();
}
return allStats;
}
///
/// 更新指定日期的统计数据(用于新交易插入后)
///
public async Task UpdateDailyStatisticsAsync(DateOnly date, int nozzleId)
{
try
{
Console.WriteLine($"[预警告统计] 开始更新油枪{nozzleId},日期:{date} 的统计数据");
// 重新计算该油枪在该日期的统计
var stats = await CalculateSingleDayStatisticsAsync(date, nozzleId);
if (stats != null)
{
Console.WriteLine($"[预警告统计] 更新成功 - 总笔数:{stats.daily_total}, 超标:{stats.daily_overproof}, 超标率:{stats.daily_overproofrate}%");
}
}
catch (Exception ex)
{
Console.WriteLine($"[预警告统计] 更新失败:{ex.Message}");
}
}
///
/// 计算单个日期的统计数据
///
private async Task CalculateSingleDayStatisticsAsync(DateOnly date, int nozzleId)
{
// 获取当天的预警告记录
var prewarning = await _prewarning.AsQueryable()
.Where(p => p.date == date && p.nozzle == nozzleId)
.FirstAsync();
if (prewarning == null)
{
return null;
}
return new WarningStatistics
{
nozzle = nozzleId,
statistics_date = date,
daily_total = prewarning.total,
daily_overproof = prewarning.overproof,
daily_overproofrate = prewarning.overproofrate,
daily_overproof_alert = prewarning.overproof_alert,
daily_overproofrate_alert = prewarning.overproofrate_alert,
daily_overproof_2 = prewarning.overproof_2,
daily_overproofrate_2 = prewarning.overproofrate_2,
is_accumulated = prewarning.total >= 5,
accumulated_days = 1
};
}
///
/// 核心统计计算逻辑(优化版:使用缓存避免重复计算)
///
private async Task> CalculateStatisticsAsync(DateOnly startDate, DateOnly endDate)
{
Console.WriteLine($"[预警告统计] 开始计算统计,范围:{startDate} 至 {endDate}");
var results = new List();
// 获取该日期范围内的所有预警告记录
var prewarnings = await _prewarning.AsQueryable()
.Where(p => p.date >= startDate && p.date <= endDate)
.ToListAsync();
// 按油枪分组
var groupedByNozzle = prewarnings.GroupBy(p => p.nozzle);
foreach (var nozzleGroup in groupedByNozzle)
{
var nozzleId = nozzleGroup.Key;
var sortedData = nozzleGroup.OrderBy(p => p.date).ToList();
// 创建状态缓存数组,存储每一天的累计状态快照
var stateCache = new AccumulatedState[sortedData.Count];
// 为每一天计算统计数据
for (int i = 0; i < sortedData.Count; i++)
{
var currentPrewarning = sortedData[i];
var currentDate = currentPrewarning.date;
var stats = new WarningStatistics
{
nozzle = nozzleId,
statistics_date = currentDate,
daily_total = currentPrewarning.total,
daily_overproof = currentPrewarning.overproof,
daily_overproofrate = currentPrewarning.overproofrate,
daily_overproof_alert = currentPrewarning.overproof_alert,
daily_overproofrate_alert = currentPrewarning.overproofrate_alert,
daily_overproof_2 = currentPrewarning.overproof_2,
daily_overproofrate_2 = currentPrewarning.overproofrate_2
};
// 计算 35 天累计统计(使用缓存优化)
CalculateAccumulatedStatisticsWithCache(sortedData, i, stats, stateCache);
results.Add(stats);
}
}
Console.WriteLine($"[预警告统计] 计算完成,共{results.Count}条记录");
return results;
}
///
/// 计算累计统计数据(优化版:使用状态缓存)
/// 规则:从最早日期正向迭代到当前日期,累加统计并在满足条件时清零
/// 使用前一天的缓存结果继续计算,避免重复计算
///
private void CalculateAccumulatedStatisticsWithCache(List sortedData, int currentIndex, WarningStatistics stats, AccumulatedState[] stateCache)
{
// 获取区域规则配置
bool isBeijingMode = VRRules.RefReachCount;
int refCount = VRRules.RefCount;
int refPrewarningIndex = VRRules.RefPrewarningIndex;
int refPrewarningIndexAlert = VRRules.RefPrewarningIndexAlert;
bool isZhejiangSpecial = VRRules.BCountAllDay;
// 如果是第一天,从头开始计算
if (currentIndex == 0)
{
var day = sortedData[0];
var newState = new AccumulatedState();
// 累加当天数据
newState.AccOver = day.overproof;
newState.AccOverAlert = day.overproof_alert;
newState.AccTotal = day.total;
newState.AccOver2 = day.overproof_2;
// 计算超标率
newState.LastRate = newState.AccTotal == 0 ? 0 : (newState.AccOver * 100) / newState.AccTotal;
newState.LastRate2 = newState.AccTotal == 0 ? 0 : (newState.AccOver2 * 100) / newState.AccTotal;
newState.LastRateAlert = newState.AccTotal == 0 ? 0 : (newState.AccOverAlert * 100) / newState.AccTotal;
// 设置统计结果
SetStatsFromState(stats, newState);
// 根据模式判断并更新连续天数
ApplyWarningRules(newState, isBeijingMode, refCount, refPrewarningIndex,
refPrewarningIndexAlert, isZhejiangSpecial);
// 保存到缓存
stateCache[0] = newState;
SetStatsFromState_continue_days(stats, newState);
return;
}
// 非第一天:复制前一天的状态
var previousState = stateCache[currentIndex - 1];
var currentState = new AccumulatedState
{
AccOver = previousState.AccOver,
AccOverAlert = previousState.AccOverAlert,
AccTotal = previousState.AccTotal,
AccOver2 = previousState.AccOver2,
ContinueDays = previousState.ContinueDays,
ContinueDays2 = previousState.ContinueDays2,
ContinueDaysAlert = previousState.ContinueDaysAlert
};
// 累加当天数据
var currentDay = sortedData[currentIndex];
currentState.AccOver += currentDay.overproof;
currentState.AccOverAlert += currentDay.overproof_alert;
currentState.AccTotal += currentDay.total;
currentState.AccOver2 += currentDay.overproof_2;
// 计算超标率
currentState.LastRate = currentState.AccTotal == 0 ? 0 : (currentState.AccOver * 100) / currentState.AccTotal;
currentState.LastRate2 = currentState.AccTotal == 0 ? 0 : (currentState.AccOver2 * 100) / currentState.AccTotal;
currentState.LastRateAlert = currentState.AccTotal == 0 ? 0 : (currentState.AccOverAlert * 100) / currentState.AccTotal;
// 设置统计结果
SetStatsFromState(stats, currentState);
// 根据模式判断并更新连续天数
ApplyWarningRules(currentState, isBeijingMode, refCount, refPrewarningIndex,
refPrewarningIndexAlert, isZhejiangSpecial);
// 保存到缓存
stateCache[currentIndex] = currentState;
SetStatsFromState_continue_days(stats, currentState);
}
///
/// 应用预警告规则(北京模式/标准模式)
///
private void ApplyWarningRules(AccumulatedState state, bool isBeijingMode, int refCount,
int refPrewarningIndex, int refPrewarningIndexAlert, bool isZhejiangSpecial)
{
if (isBeijingMode)//isBeijingMode
{
// === 北京模式(ref_reach_count)===
// 规则:超标率>=阈值 且 总笔数>=5 时,连续天数 +1
// 普通超标判断
if (state.LastRate >= refPrewarningIndex)
{
if (state.AccTotal >= 5)
{
state.ContinueDays++;
}
}
else
{
if (state.AccTotal >= 5)
{
state.ContinueDays = 0;
}
}
// 严重超标判断
if (state.LastRateAlert >= refPrewarningIndexAlert)
{
if (state.AccTotal >= 5)
{
state.ContinueDaysAlert++;
}
}
else
{
if (state.AccTotal >= 5)
{
state.ContinueDaysAlert = 0;
}
}
// 已经算入一天,清零累加器
state.AccOver = 0;
state.AccOverAlert = 0;
state.AccTotal = 0;
state.AccOver2 = 0;
}
else
{
// === 标准模式 ===
// 规则:总笔数>=ref_count(通常 5) 时才检查超标率
if (state.AccTotal >= refCount)
{
// 普通超标判断
if (state.LastRate >= refPrewarningIndex)
{
state.ContinueDays++;
}
else
{
state.ContinueDays = 0;
}
// 严重超标判断
if (state.LastRateAlert >= refPrewarningIndexAlert)
{
state.ContinueDaysAlert++;
}
else
{
state.ContinueDaysAlert = 0;
}
// 超标 2 判断(广东中石化)
if (state.LastRate2 >= refPrewarningIndex)
{
state.ContinueDays2++;
}
else
{
state.ContinueDays2 = 0;
}
// 已经算入一天,清零累加器
state.AccOver = 0;
state.AccOverAlert = 0;
state.AccTotal = 0;
state.AccOver2 = 0;
}
else
{
// 浙江中石化特殊逻辑
if (isZhejiangSpecial)
{
if (state.ContinueDays > 0)
{
state.ContinueDays++;
}
if (state.ContinueDaysAlert > 0)
{
state.ContinueDaysAlert++;
}
}
}
}
}
///
/// 从状态对象设置统计结果
///
private void SetStatsFromState(WarningStatistics stats, AccumulatedState state)
{
stats.continue_days = state.ContinueDays;
stats.continue_days_2 = state.ContinueDays2;
stats.continue_days_alert = state.ContinueDaysAlert;
stats.last_overproofrate = state.LastRate;
stats.last_overproofrate_2 = state.LastRate2;
stats.last_overproofrate_alert = state.LastRateAlert;
// 同时保留原有的累计字段(用于兼容)
stats.total_count = state.AccTotal;
stats.total_overproof = state.AccOver;
stats.total_overproof_alert = state.AccOverAlert;
stats.total_overproof_2 = state.AccOver2;
stats.total_overproofrate = state.LastRate;
stats.total_overproofrate_2 = state.LastRate2;
stats.total_overproofrate_alert = state.LastRateAlert;
}
private void SetStatsFromState_continue_days(WarningStatistics stats, AccumulatedState state)
{
stats.continue_days = state.ContinueDays;
stats.continue_days_2 = state.ContinueDays2;
stats.continue_days_alert = state.ContinueDaysAlert;
}
///
/// 计算累计统计数据(原始版本,保留用于兼容)
/// 规则:从最早日期正向迭代到当前日期,累加统计并在满足条件时清零
///
private void CalculateAccumulatedStatistics(List sortedData, int currentIndex, WarningStatistics stats)
{
// 累加器初始化(对应 C++ acc_over, acc_over_alert, acc_total, acc_over_2)
int acc_over = 0; // 累计超标记录
int acc_over_alert = 0; // 累计严重超标记录
int acc_total = 0; // 累计总数
int acc_over_2 = 0; // 累计超标 2 记录(广东中石化)
// 连续天数计数器
int continue_days = 0;
int continue_days_2 = 0;
int continue_days_alert = 0;
// 最后计算的超标率
int last_rate = 0;
int last_rate_2 = 0;
int last_rate_alert = 0;
// 获取区域规则配置
bool isBeijingMode = VRRules.RefReachCount; // 北京模式:ref_reach_count
int refCount = VRRules.RefCount; // 标准模式阈值(通常 5)
int refPrewarningIndex = VRRules.RefPrewarningIndex; // 超标率阈值
int refPrewarningIndexAlert = VRRules.RefPrewarningIndexAlert; // 严重超标率阈值
bool isZhejiangSpecial = VRRules.BCountAllDay; // 浙江中石化特殊逻辑
// 正向迭代:从第 0 天到当前天(对应 C++ mapDayWarning.begin() 到 end)
for (int i = 0; i <= currentIndex && i < sortedData.Count; i++)
{
var day = sortedData[i];
// 累加数据
acc_over += day.overproof;
acc_over_alert += day.overproof_alert;
acc_total += day.total;
acc_over_2 += day.overproof_2;
// 计算累计超标率(避免除以 0)
last_rate = acc_total == 0 ? 0 : (acc_over * 100) / acc_total;
last_rate_2 = acc_total == 0 ? 0 : (acc_over_2 * 100) / acc_total;
last_rate_alert = acc_total == 0 ? 0 : (acc_over_alert * 100) / acc_total;
// 更新警告对象中的累计值(用于显示)
// 注意:这里不直接设置 stats,因为可能还需要清零
if (isBeijingMode)
{
// === 北京模式(ref_reach_count)===
// 规则:超标率>=阈值 且 总笔数>=5 时,连续天数 +1
// 普通超标判断
if (last_rate >= refPrewarningIndex)
{
if (acc_total >= 5)
{
continue_days++;
}
}
else
{
if (acc_total >= 5)
{
continue_days = 0;
}
}
// 严重超标判断
if (last_rate_alert >= refPrewarningIndexAlert)
{
if (acc_total >= 5)
{
continue_days_alert++;
}
}
else
{
if (acc_total >= 5)
{
continue_days_alert = 0;
}
}
// 已经算入一天,清零累加器
acc_over = 0;
acc_over_alert = 0;
acc_total = 0;
acc_over_2 = 0;
}
else
{
// === 标准模式 ===
// 规则:总笔数>=ref_count(通常 5) 时才检查超标率
if (acc_total >= refCount)
{
// 普通超标判断
if (last_rate >= refPrewarningIndex)
{
continue_days++;
}
else
{
continue_days = 0;
}
// 严重超标判断
if (last_rate_alert >= refPrewarningIndexAlert)
{
continue_days_alert++;
}
else
{
continue_days_alert = 0;
}
// 超标 2 判断(广东中石化)
if (last_rate_2 >= refPrewarningIndex) // || day.continueoverproof == 1
{
continue_days_2++;
}
else
{
continue_days_2 = 0;
}
// 已经算入一天,清零累加器
acc_over = 0;
acc_over_alert = 0;
acc_total = 0;
acc_over_2 = 0;
}
else
{
// 该分支是 ref_count 大于 0(即需要进行累计)的情况下,
// 而 acc_total 小于 ref_count 时进入
if (isZhejiangSpecial)
{
// === 浙江中石化特殊逻辑 ===
// 不足 5 笔时延续之前的预警状态
if (continue_days > 0)
{
continue_days++;
}
if (continue_days_alert > 0)
{
continue_days_alert++;
}
}
}
}
}
// 设置最终统计结果
stats.continue_days = continue_days;
stats.continue_days_2 = continue_days_2;
stats.continue_days_alert = continue_days_alert;
stats.last_overproofrate = last_rate;
stats.last_overproofrate_2 = last_rate_2;
stats.last_overproofrate_alert = last_rate_alert;
// 同时保留原有的累计字段(用于兼容)
stats.total_count = acc_total;
stats.total_overproof = acc_over;
stats.total_overproof_alert = acc_over_alert;
stats.total_overproof_2 = acc_over_2;
stats.total_overproofrate = last_rate;
stats.total_overproofrate_2 = last_rate_2;
stats.total_overproofrate_alert = last_rate_alert;
}
///
/// 获取所有有数据的油枪 ID 列表
///
public async Task> GetAllNozzleIdsAsync()
{
var nozzles = await _prewarning.AsQueryable()
.Select(p => p.nozzle)
.Distinct()
.ToListAsync();
return nozzles;
}
///
/// 获取统计日期范围(最早到最新)
///
public async Task<(DateOnly minDate, DateOnly maxDate)> GetDateRangeAsync()
{
var dates = await _prewarning.AsQueryable()
.Select(p => p.date)
.ToListAsync();
if (dates == null || dates.Count == 0)
{
return (new DateOnly(), new DateOnly());
}
var minDate = dates.Min();
var maxDate = dates.Max();
return (minDate, maxDate);
}
}
}