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); } } }