|
@@ -8,15 +8,20 @@ import com.hdkj.lt.bf.entity.FhzgDwmVoltageOverLimitDaily;
|
|
|
import com.hdkj.lt.bf.entity.FhzgSeCapacityDaily;
|
|
import com.hdkj.lt.bf.entity.FhzgSeCapacityDaily;
|
|
|
import com.hdkj.lt.bf.entity.FhzgSeVoltageEvent;
|
|
import com.hdkj.lt.bf.entity.FhzgSeVoltageEvent;
|
|
|
import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
|
|
import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.FhzgSeConsumerCountDaily;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.FhzgSeConsumerRelation;
|
|
|
import com.hdkj.lt.bf.entity.dto.TodayOutageData;
|
|
import com.hdkj.lt.bf.entity.dto.TodayOutageData;
|
|
|
import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
|
|
import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
|
|
|
import com.hdkj.lt.bf.entity.vo.SeOutageDashboardVO;
|
|
import com.hdkj.lt.bf.entity.vo.SeOutageDashboardVO;
|
|
|
import com.hdkj.lt.bf.entity.vo.SeVoltageDashboardVO;
|
|
import com.hdkj.lt.bf.entity.vo.SeVoltageDashboardVO;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.vo.SeVoltageIndexVO;
|
|
|
import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
|
|
import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
|
|
|
import com.hdkj.lt.bf.mapper.FhzgDwmVoltageOverLimitDailyMapper;
|
|
import com.hdkj.lt.bf.mapper.FhzgDwmVoltageOverLimitDailyMapper;
|
|
|
import com.hdkj.lt.bf.mapper.FhzgSeCapacityDailyMapper;
|
|
import com.hdkj.lt.bf.mapper.FhzgSeCapacityDailyMapper;
|
|
|
import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
|
|
import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
|
|
|
import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
|
|
import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
|
|
|
|
|
+import com.hdkj.lt.bf.mapper.FhzgSeConsumerCountDailyMapper;
|
|
|
|
|
+import com.hdkj.lt.bf.mapper.FhzgSeConsumerRelationMapper;
|
|
|
import com.hdkj.lt.bf.service.SeIndicatorService;
|
|
import com.hdkj.lt.bf.service.SeIndicatorService;
|
|
|
import com.hdkj.lt.bf.service.SjztPwwyyqxtPrmPdrOutageMvDsService;
|
|
import com.hdkj.lt.bf.service.SjztPwwyyqxtPrmPdrOutageMvDsService;
|
|
|
import com.hdkj.fhzg.optimization.response.FeederGroupTreeNode;
|
|
import com.hdkj.fhzg.optimization.response.FeederGroupTreeNode;
|
|
@@ -30,6 +35,7 @@ import java.math.RoundingMode;
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
@@ -50,10 +56,15 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
|
|
private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
|
|
|
private final FhzgSeCapacityDailyMapper capacityDailyMapper;
|
|
private final FhzgSeCapacityDailyMapper capacityDailyMapper;
|
|
|
private final FhzgSeCurrentEventMapper currentEventMapper;
|
|
private final FhzgSeCurrentEventMapper currentEventMapper;
|
|
|
|
|
+ private final FhzgSeConsumerCountDailyMapper consumerCountDailyMapper;
|
|
|
|
|
+ private final FhzgSeConsumerRelationMapper consumerRelationMapper;
|
|
|
private final FeederGroupApplication feederGroupApplication;
|
|
private final FeederGroupApplication feederGroupApplication;
|
|
|
|
|
|
|
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
|
|
|
|
|
|
|
|
+ /** 每天小时数(分母时户数换算用) */
|
|
|
|
|
+ private static final BigDecimal HOURS_24 = new BigDecimal("24");
|
|
|
|
|
+
|
|
|
// ============================================================
|
|
// ============================================================
|
|
|
// 1. 停电时户数(复用已有逻辑,无时间维度)
|
|
// 1. 停电时户数(复用已有逻辑,无时间维度)
|
|
|
// ============================================================
|
|
// ============================================================
|
|
@@ -132,31 +143,32 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
*/
|
|
*/
|
|
|
private int[] countVoltageByAlarmLevel(String id, Integer type, String alarmLevel,
|
|
private int[] countVoltageByAlarmLevel(String id, Integer type, String alarmLevel,
|
|
|
LocalDateTime start, LocalDateTime end) {
|
|
LocalDateTime start, LocalDateTime end) {
|
|
|
- LambdaQueryWrapper<FhzgSeVoltageEvent> wrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
|
|
|
|
|
- .eq(FhzgSeVoltageEvent::getAlarmLevel, alarmLevel)
|
|
|
|
|
- .ge(FhzgSeVoltageEvent::getFirstOverTime, start)
|
|
|
|
|
- .lt(FhzgSeVoltageEvent::getFirstOverTime, end);
|
|
|
|
|
- applyNodeFilter(wrapper, id, type);
|
|
|
|
|
-
|
|
|
|
|
- List<FhzgSeVoltageEvent> events = voltageEventMapper.selectList(wrapper);
|
|
|
|
|
-
|
|
|
|
|
- Set<String> overDeviceIds = new HashSet<>();
|
|
|
|
|
- Set<String> underDeviceIds = new HashSet<>();
|
|
|
|
|
- int overTimes = 0, underTimes = 0;
|
|
|
|
|
-
|
|
|
|
|
- for (FhzgSeVoltageEvent e : events) {
|
|
|
|
|
- String t = e.getAlarmType();
|
|
|
|
|
- if (t == null) continue;
|
|
|
|
|
- if (t.contains("over")) {
|
|
|
|
|
- if (e.getDeviceId() != null) overDeviceIds.add(e.getDeviceId());
|
|
|
|
|
- overTimes++;
|
|
|
|
|
- } else if (t.contains("under")) {
|
|
|
|
|
- if (e.getDeviceId() != null) underDeviceIds.add(e.getDeviceId());
|
|
|
|
|
- underTimes++;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ QueryWrapper<FhzgSeVoltageEvent> qw = new QueryWrapper<>();
|
|
|
|
|
+ qw.select(
|
|
|
|
|
+ "COUNT(DISTINCT CASE WHEN alarm_type LIKE '%over%' THEN device_id END) AS over_cnt",
|
|
|
|
|
+ "COUNT(DISTINCT CASE WHEN alarm_type LIKE '%under%' THEN device_id END) AS under_cnt",
|
|
|
|
|
+ "COUNT(CASE WHEN alarm_type LIKE '%over%' THEN 1 END) AS over_times",
|
|
|
|
|
+ "COUNT(CASE WHEN alarm_type LIKE '%under%' THEN 1 END) AS under_times")
|
|
|
|
|
+ .eq("alarm_level", alarmLevel)
|
|
|
|
|
+ .ge("first_over_time", start)
|
|
|
|
|
+ .lt("first_over_time", end);
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ qw.eq("county_id", id);
|
|
|
|
|
+ } else if (type == 3) {
|
|
|
|
|
+ qw.eq("subs_id", id);
|
|
|
|
|
+ } else if (type == 4) {
|
|
|
|
|
+ qw.eq("feeder_id", id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return new int[]{overDeviceIds.size(), underDeviceIds.size(), overTimes, underTimes};
|
|
|
|
|
|
|
+ List<Map<String, Object>> rows = voltageEventMapper.selectMaps(qw);
|
|
|
|
|
+ if (rows.isEmpty()) return new int[]{0, 0, 0, 0};
|
|
|
|
|
+ Map<String, Object> row = rows.get(0);
|
|
|
|
|
+ return new int[]{
|
|
|
|
|
+ ((Number) row.getOrDefault("over_cnt", 0)).intValue(),
|
|
|
|
|
+ ((Number) row.getOrDefault("under_cnt", 0)).intValue(),
|
|
|
|
|
+ ((Number) row.getOrDefault("over_times", 0)).intValue(),
|
|
|
|
|
+ ((Number) row.getOrDefault("under_times", 0)).intValue()
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -189,7 +201,8 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
/**
|
|
/**
|
|
|
* 历史电压:查 ads_mrt_voltage_over_limit_daily_result,SUM 聚合到 SQL
|
|
* 历史电压:查 ads_mrt_voltage_over_limit_daily_result,SUM 聚合到 SQL
|
|
|
* 当月 = 月初~昨天;全年 = 1.1~昨天
|
|
* 当月 = 月初~昨天;全年 = 1.1~昨天
|
|
|
- * 17:00前查历史当月/全年 = 中台[到前天] + 我们alarm_event[昨天];17:00后 = 中台[到昨天]
|
|
|
|
|
|
|
+ * 自适应:查中台表 MAX(over_date) 判断数据最新到哪天,
|
|
|
|
|
+ * 中台未到昨天时 → 中台[到前天] + 事件表补[昨天];中台已到昨天 → 中台[到昨天]
|
|
|
*/
|
|
*/
|
|
|
private SeVoltageDashboardVO queryVoltageHistory(String id, Integer type, String timeScope) {
|
|
private SeVoltageDashboardVO queryVoltageHistory(String id, Integer type, String timeScope) {
|
|
|
LocalDate today = LocalDate.now();
|
|
LocalDate today = LocalDate.now();
|
|
@@ -202,12 +215,17 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
startDate = LocalDate.of(today.getYear(), 1, 1);
|
|
startDate = LocalDate.of(today.getYear(), 1, 1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 17:00前:中台到前天 + 我们昨天;17:00后:中台到昨天
|
|
|
|
|
- int hour = LocalDateTime.now().getHour();
|
|
|
|
|
- if (hour < 17) {
|
|
|
|
|
- endDate = today.minusDays(2);
|
|
|
|
|
|
|
+ String dimLevel = resolveDimLevel(type);
|
|
|
|
|
+
|
|
|
|
|
+ // 查中台日汇总表最新日期,判断是否需要补昨日
|
|
|
|
|
+ String maxOverDate = queryDailyMaxOverDate(dimLevel, id, type);
|
|
|
|
|
+ String yesterdayStr = today.minusDays(1).format(DATE_FMT);
|
|
|
|
|
+ boolean needFillYesterday = (maxOverDate == null || maxOverDate.compareTo(yesterdayStr) < 0);
|
|
|
|
|
+
|
|
|
|
|
+ if (needFillYesterday) {
|
|
|
|
|
+ endDate = today.minusDays(2); // 中台只到T-2,补昨日T-1
|
|
|
} else {
|
|
} else {
|
|
|
- endDate = today.minusDays(1);
|
|
|
|
|
|
|
+ endDate = today.minusDays(1); // 中台已有昨天
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 如果 endDate < startDate,返回空
|
|
// 如果 endDate < startDate,返回空
|
|
@@ -215,8 +233,6 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
return SeVoltageDashboardVO.builder().build();
|
|
return SeVoltageDashboardVO.builder().build();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String dimLevel = resolveDimLevel(type);
|
|
|
|
|
-
|
|
|
|
|
// 一条 SQL 聚合 16个count字段 + total_over_limit_hours
|
|
// 一条 SQL 聚合 16个count字段 + total_over_limit_hours
|
|
|
QueryWrapper<FhzgDwmVoltageOverLimitDaily> qw = new QueryWrapper<>();
|
|
QueryWrapper<FhzgDwmVoltageOverLimitDaily> qw = new QueryWrapper<>();
|
|
|
qw.select(
|
|
qw.select(
|
|
@@ -272,22 +288,48 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
BigDecimal totalHours = r != null && r.getTotalOverLimitHours() != null
|
|
BigDecimal totalHours = r != null && r.getTotalOverLimitHours() != null
|
|
|
? r.getTotalOverLimitHours() : BigDecimal.ZERO;
|
|
? r.getTotalOverLimitHours() : BigDecimal.ZERO;
|
|
|
|
|
|
|
|
- // 17:00前补昨日数据(从我们alarm_event查昨天)
|
|
|
|
|
- if (hour < 17) {
|
|
|
|
|
|
|
+ // 补昨日:中台无昨日数据时,从事件表SQL聚合,按 alarm_level 分流
|
|
|
|
|
+ if (needFillYesterday) {
|
|
|
LocalDateTime yesterdayStart = today.minusDays(1).atStartOfDay();
|
|
LocalDateTime yesterdayStart = today.minusDays(1).atStartOfDay();
|
|
|
LocalDateTime yesterdayEnd = today.minusDays(1).atTime(23, 59, 59);
|
|
LocalDateTime yesterdayEnd = today.minusDays(1).atTime(23, 59, 59);
|
|
|
- LambdaQueryWrapper<FhzgSeVoltageEvent> yesterdayWrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
|
|
|
|
|
- .ge(FhzgSeVoltageEvent::getTriggerTime, yesterdayStart)
|
|
|
|
|
- .le(FhzgSeVoltageEvent::getTriggerTime, yesterdayEnd)
|
|
|
|
|
- .eq(FhzgSeVoltageEvent::getStatus, 2);
|
|
|
|
|
- applyNodeFilter(yesterdayWrapper, id, type);
|
|
|
|
|
- List<FhzgSeVoltageEvent> yesterdayEvents = voltageEventMapper.selectList(yesterdayWrapper);
|
|
|
|
|
- for (FhzgSeVoltageEvent e : yesterdayEvents) {
|
|
|
|
|
- if ("voltage_over".equals(e.getAlarmType()) || e.getAlarmType() != null && e.getAlarmType().contains("over")) {
|
|
|
|
|
- overUpperMildTg++;
|
|
|
|
|
- } else if (e.getAlarmType() != null && e.getAlarmType().contains("under")) {
|
|
|
|
|
- overLowerMildTg++;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ QueryWrapper<FhzgSeVoltageEvent> agg = new QueryWrapper<>();
|
|
|
|
|
+ agg.select("alarm_level", "alarm_type", "COUNT(*) AS cnt")
|
|
|
|
|
+ .ge("trigger_time", yesterdayStart)
|
|
|
|
|
+ .le("trigger_time", yesterdayEnd)
|
|
|
|
|
+ .eq("status", 2)
|
|
|
|
|
+ .groupBy("alarm_level", "alarm_type");
|
|
|
|
|
+ if (type == 2) agg.eq("county_id", id);
|
|
|
|
|
+ else if (type == 3) agg.eq("subs_id", id);
|
|
|
|
|
+ else if (type == 4) agg.eq("feeder_id", id);
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> aggRows = voltageEventMapper.selectMaps(agg);
|
|
|
|
|
+ for (Map<String, Object> row : aggRows) {
|
|
|
|
|
+ String alarmLevel = (String) row.get("alarm_level");
|
|
|
|
|
+ String alarmType = (String) row.get("alarm_type");
|
|
|
|
|
+ int cnt = ((Number) row.getOrDefault("cnt", 0)).intValue();
|
|
|
|
|
+ if (alarmType == null) continue;
|
|
|
|
|
+ boolean isOver = alarmType.contains("over");
|
|
|
|
|
+ boolean isUnder = alarmType.contains("under");
|
|
|
|
|
+ if (!isOver && !isUnder) continue;
|
|
|
|
|
+ boolean isSevere = alarmType.contains("severe");
|
|
|
|
|
+
|
|
|
|
|
+ if ("consumer".equals(alarmLevel)) {
|
|
|
|
|
+ if (isOver) {
|
|
|
|
|
+ if (isSevere) overUpperSevereCust += cnt;
|
|
|
|
|
+ else overUpperMildCust += cnt;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (isSevere) overLowerSevereCust += cnt;
|
|
|
|
|
+ else overLowerMildCust += cnt;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ("mvtrans".equals(alarmLevel)) {
|
|
|
|
|
+ if (isOver) overUpperMildTg += cnt;
|
|
|
|
|
+ else overLowerMildTg += cnt;
|
|
|
|
|
+ } else if ("feeder".equals(alarmLevel)) {
|
|
|
|
|
+ if (isOver) overUpperMildFeeder += cnt;
|
|
|
|
|
+ else overLowerMildFeeder += cnt;
|
|
|
}
|
|
}
|
|
|
|
|
+ // generatrix → 跳过
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -419,12 +461,11 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
if (d.getAvgLoadRate() != null) {
|
|
if (d.getAvgLoadRate() != null) {
|
|
|
sumAvg = sumAvg.add(d.getAvgLoadRate());
|
|
sumAvg = sumAvg.add(d.getAvgLoadRate());
|
|
|
}
|
|
}
|
|
|
|
|
+ // 重过载事件数:日统计表存的就是当日事件表事件数,直接累加
|
|
|
if (d.getHeavyCount() != null) sumHeavy += d.getHeavyCount();
|
|
if (d.getHeavyCount() != null) sumHeavy += d.getHeavyCount();
|
|
|
if (d.getOverloadCount() != null) sumOverload += d.getOverloadCount();
|
|
if (d.getOverloadCount() != null) sumOverload += d.getOverloadCount();
|
|
|
}
|
|
}
|
|
|
- BigDecimal avgLoadRate = !dailyRecords.isEmpty()
|
|
|
|
|
- ? sumAvg.divide(BigDecimal.valueOf(dailyRecords.size()), 2, RoundingMode.HALF_UP)
|
|
|
|
|
- : BigDecimal.ZERO;
|
|
|
|
|
|
|
+ BigDecimal avgLoadRate = sumAvg.divide(BigDecimal.valueOf(dailyRecords.size()), 2, RoundingMode.HALF_UP);
|
|
|
int totalEvents = (int) (sumHeavy + sumOverload);
|
|
int totalEvents = (int) (sumHeavy + sumOverload);
|
|
|
vo = SeCapacityDashboardVO.builder()
|
|
vo = SeCapacityDashboardVO.builder()
|
|
|
.maxLoadRate(maxLoadRate)
|
|
.maxLoadRate(maxLoadRate)
|
|
@@ -531,4 +572,211 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
|
|
|
private int safeInt(Integer v) {
|
|
private int safeInt(Integer v) {
|
|
|
return v == null ? 0 : v;
|
|
return v == null ? 0 : v;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查中台日汇总表该维度的最新 over_date
|
|
|
|
|
+ */
|
|
|
|
|
+ private String queryDailyMaxOverDate(String dimLevel, String id, Integer type) {
|
|
|
|
|
+ QueryWrapper<FhzgDwmVoltageOverLimitDaily> qw = new QueryWrapper<>();
|
|
|
|
|
+ qw.select("MAX(over_date) AS max_over_date")
|
|
|
|
|
+ .eq("dim_level", dimLevel);
|
|
|
|
|
+ if (type == 2) qw.eq("maintainer_id", id);
|
|
|
|
|
+ else if (type == 3) qw.eq("substation_id", id);
|
|
|
|
|
+ else if (type == 4) qw.eq("feeder_id", id);
|
|
|
|
|
+ qw.last("LIMIT 1");
|
|
|
|
|
+ List<Map<String, Object>> records = voltageDailyMapper.selectMaps(qw);
|
|
|
|
|
+ if (records != null && !records.isEmpty()) {
|
|
|
|
|
+ Object val = records.get(0).get("max_over_date");
|
|
|
|
|
+ return val != null ? val.toString() : null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 4. IEC 用户电压指数
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * IEC 用户电压指数 = 用户越限总时长(上+下) / (总用户数 × 周期天数 × 24h) × 100%
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 分子(组合):
|
|
|
|
|
+ * 历史 = 中台 ads_mrt_voltage_over_limit_daily_result.total_over_limit_hours(SQL SUM);
|
|
|
|
|
+ * 昨日/今日 = 事件表 fhzg_se_voltage_event(alarm_level='consumer') Σ(recover_time - first_over_time)。
|
|
|
|
|
+ * 中台最新日期动态自适应(MAX(over_date)),事件表只补中台未覆盖的区间。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 分母 = 分层分级用户数 × 周期天数 × 24(时户数);
|
|
|
|
|
+ * 用户数取最近一天日表值(用户数日变化可忽略,无需逐日累加/历史回填)。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public SeVoltageIndexVO queryVoltageIndex(String id, Integer type, String timeScope) {
|
|
|
|
|
+ if (StringUtils.isBlank(id) || type == null || StringUtils.isBlank(timeScope)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
|
|
+ LocalDate yesterday = today.minusDays(1);
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal overLimitHours;
|
|
|
|
|
+ BigDecimal consumerHours;
|
|
|
|
|
+ BigDecimal avgConsumerCount;
|
|
|
|
|
+
|
|
|
|
|
+ if ("day".equalsIgnoreCase(timeScope)) {
|
|
|
|
|
+ // day:分子 = 今日事件表时长;分母 = 最近一天用户数 × 24
|
|
|
|
|
+ overLimitHours = sumConsumerEventHours(id, type,
|
|
|
|
|
+ today.atStartOfDay(), today.plusDays(1).atStartOfDay());
|
|
|
|
|
+ int count = latestConsumerCount(id, type, yesterday);
|
|
|
|
|
+ avgConsumerCount = BigDecimal.valueOf(count);
|
|
|
|
|
+ consumerHours = avgConsumerCount.multiply(HOURS_24);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // month/year:分子 = 中台[start~endDate] + 事件表[endDate+1~今天]
|
|
|
|
|
+ // 分母 = 最近一天用户数 × 实际天数 × 24
|
|
|
|
|
+ LocalDate startDate;
|
|
|
|
|
+ if ("month".equalsIgnoreCase(timeScope)) {
|
|
|
|
|
+ startDate = today.withDayOfMonth(1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ startDate = LocalDate.of(today.getYear(), 1, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 中台覆盖到哪天(自适应)
|
|
|
|
|
+ String dimLevel = resolveDimLevel(type);
|
|
|
|
|
+ String maxOverDate = queryDailyMaxOverDate(dimLevel, id, type);
|
|
|
|
|
+ LocalDate endDate = yesterday;
|
|
|
|
|
+ if (maxOverDate != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ endDate = LocalDate.parse(maxOverDate, DATE_FMT);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("中台 over_date 解析失败: {}, 用昨天", maxOverDate);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (endDate.isAfter(yesterday)) {
|
|
|
|
|
+ endDate = yesterday;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 分子:中台区间 + 事件表补区间
|
|
|
|
|
+ BigDecimal zhongtaiHours = sumZhongtaiOverLimitHours(id, type, startDate, endDate);
|
|
|
|
|
+ BigDecimal eventHours = endDate.isBefore(today)
|
|
|
|
|
+ ? sumConsumerEventHours(id, type,
|
|
|
|
|
+ endDate.plusDays(1).atStartOfDay(), today.plusDays(1).atStartOfDay())
|
|
|
|
|
+ : BigDecimal.ZERO;
|
|
|
|
|
+ overLimitHours = zhongtaiHours.add(eventHours);
|
|
|
|
|
+
|
|
|
|
|
+ // 分母:最近一天用户数 × 实际天数 × 24(用户数日变化可忽略,无需逐日累加)
|
|
|
|
|
+ int count = latestConsumerCount(id, type, yesterday);
|
|
|
|
|
+ long days = ChronoUnit.DAYS.between(startDate, today) + 1;
|
|
|
|
|
+ avgConsumerCount = BigDecimal.valueOf(count);
|
|
|
|
|
+ consumerHours = BigDecimal.valueOf(count)
|
|
|
|
|
+ .multiply(BigDecimal.valueOf(days))
|
|
|
|
|
+ .multiply(HOURS_24);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal voltageIndex = null;
|
|
|
|
|
+ if (consumerHours.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
|
|
+ voltageIndex = overLimitHours
|
|
|
|
|
+ .divide(consumerHours, 6, RoundingMode.HALF_UP)
|
|
|
|
|
+ .multiply(new BigDecimal("100"))
|
|
|
|
|
+ .setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return SeVoltageIndexVO.builder()
|
|
|
|
|
+ .voltageIndex(voltageIndex)
|
|
|
|
|
+ .overLimitHours(overLimitHours.setScale(2, RoundingMode.HALF_UP))
|
|
|
|
|
+ .consumerHours(consumerHours.setScale(0, RoundingMode.HALF_UP))
|
|
|
|
|
+ .avgConsumerCount(avgConsumerCount)
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 中台表越限总时长 SUM(分子历史部分)
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 注意:total_over_limit_hours 为中台提供的当日越限总时长(台区低压侧口径,近似用户维度)
|
|
|
|
|
+ */
|
|
|
|
|
+ private BigDecimal sumZhongtaiOverLimitHours(String id, Integer type,
|
|
|
|
|
+ LocalDate startDate, LocalDate endDate) {
|
|
|
|
|
+ if (startDate.isAfter(endDate)) {
|
|
|
|
|
+ return BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ QueryWrapper<FhzgDwmVoltageOverLimitDaily> qw = new QueryWrapper<>();
|
|
|
|
|
+ qw.select("COALESCE(SUM(total_over_limit_hours),0) AS total_hours")
|
|
|
|
|
+ .eq("dim_level", resolveDimLevel(type))
|
|
|
|
|
+ .ge("over_date", startDate.format(DATE_FMT))
|
|
|
|
|
+ .le("over_date", endDate.format(DATE_FMT));
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ qw.eq("maintainer_id", id);
|
|
|
|
|
+ } else if (type == 3) {
|
|
|
|
|
+ qw.eq("substation_id", id);
|
|
|
|
|
+ } else if (type == 4) {
|
|
|
|
|
+ qw.eq("feeder_id", id);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> rows = voltageDailyMapper.selectMaps(qw);
|
|
|
|
|
+ if (rows == null || rows.isEmpty() || rows.get(0).get("total_hours") == null) {
|
|
|
|
|
+ return BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ return new BigDecimal(rows.get(0).get("total_hours").toString());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 事件表用户越限总时长(小时):Σ(recover_time - first_over_time)
|
|
|
|
|
+ * 进行中事件(recover_time 为空)用当前时间兜底
|
|
|
|
|
+ */
|
|
|
|
|
+ private BigDecimal sumConsumerEventHours(String id, Integer type,
|
|
|
|
|
+ LocalDateTime start, LocalDateTime end) {
|
|
|
|
|
+ QueryWrapper<FhzgSeVoltageEvent> qw = new QueryWrapper<>();
|
|
|
|
|
+ qw.select("COALESCE(SUM(TIMESTAMPDIFF(SECOND, first_over_time, COALESCE(recover_time, NOW()))),0) AS secs")
|
|
|
|
|
+ .eq("alarm_level", "consumer")
|
|
|
|
|
+ .ge("first_over_time", start)
|
|
|
|
|
+ .lt("first_over_time", end);
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ qw.eq("county_id", id);
|
|
|
|
|
+ } else if (type == 3) {
|
|
|
|
|
+ qw.eq("subs_id", id);
|
|
|
|
|
+ } else if (type == 4) {
|
|
|
|
|
+ qw.eq("feeder_id", id);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> rows = voltageEventMapper.selectMaps(qw);
|
|
|
|
|
+ if (rows == null || rows.isEmpty() || rows.get(0).get("secs") == null) {
|
|
|
|
|
+ return BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ long secs = ((Number) rows.get(0).get("secs")).longValue();
|
|
|
|
|
+ return BigDecimal.valueOf(secs).divide(BigDecimal.valueOf(3600), 4, RoundingMode.HALF_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 日表最近一天的用户数(<= beforeDate)
|
|
|
|
|
+ * 日表无数据时兜底实时查用户关系表(仅首次部署/日表为空时触发,正常路径不实时查)
|
|
|
|
|
+ */
|
|
|
|
|
+ private int latestConsumerCount(String id, Integer type, LocalDate beforeDate) {
|
|
|
|
|
+ QueryWrapper<FhzgSeConsumerCountDaily> qw = new QueryWrapper<>();
|
|
|
|
|
+ qw.select("consumer_count")
|
|
|
|
|
+ .eq("dim_level", resolveDimLevel(type))
|
|
|
|
|
+ .le("stat_date", beforeDate)
|
|
|
|
|
+ .orderByDesc("stat_date")
|
|
|
|
|
+ .last("LIMIT 1");
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ qw.eq("county_id", id);
|
|
|
|
|
+ } else if (type == 3) {
|
|
|
|
|
+ qw.eq("subs_id", id);
|
|
|
|
|
+ } else if (type == 4) {
|
|
|
|
|
+ qw.eq("feeder_id", id);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> rows = consumerCountDailyMapper.selectMaps(qw);
|
|
|
|
|
+ if (rows != null && !rows.isEmpty() && rows.get(0).get("consumer_count") != null) {
|
|
|
|
|
+ return ((Number) rows.get(0).get("consumer_count")).intValue();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 兜底:日表未刷时实时 COUNT
|
|
|
|
|
+ QueryWrapper<FhzgSeConsumerRelation> cw = new QueryWrapper<>();
|
|
|
|
|
+ cw.select("COUNT(*) AS cnt");
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ cw.eq("county_id", id);
|
|
|
|
|
+ } else if (type == 3) {
|
|
|
|
|
+ cw.eq("subs_id", id);
|
|
|
|
|
+ } else if (type == 4) {
|
|
|
|
|
+ cw.eq("feeder_id", id);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> crows = consumerRelationMapper.selectMaps(cw);
|
|
|
|
|
+ if (crows != null && !crows.isEmpty() && crows.get(0).get("cnt") != null) {
|
|
|
|
|
+ return ((Number) crows.get(0).get("cnt")).intValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|