lisonglin 7 часов назад
Родитель
Сommit
a67acbacf9
16 измененных файлов с 224 добавлено и 521 удалено
  1. 0 64
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossDaily.java
  2. 21 18
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossRate.java
  3. 0 7
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDailyMapper.java
  4. 0 15
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDetailMapper.java
  5. 15 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossRateMapper.java
  6. 0 114
      common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeLineLossDailyScheduler.java
  7. 0 19
      common/common-se/src/main/resources/mapper/FhzgSeLineLossDetailMapper.xml
  8. 2 0
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/common/SeMonitorThreshold.java
  9. 5 36
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/controller/optimization/IndicatorController.java
  10. 3 0
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/entity/dto/SeVoltageAlarmListReq.java
  11. 7 7
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/entity/vo/SeLineLossRateVO.java
  12. 2 2
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/SeIndicatorService.java
  13. 16 0
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeAlarmServiceImpl.java
  14. 37 77
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeIndicatorServiceImpl.java
  15. 116 127
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeSnapshotServiceImpl.java
  16. 0 35
      services/ruoyi-job/src/main/java/com/hdkj/lt/job/task/SeLineLossDailyTask.java

+ 0 - 64
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossDaily.java

@@ -1,64 +0,0 @@
-package com.hdkj.lt.bf.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-
-/**
- * 线路线损日汇总
- *
- * @author lsl
- * @since 2026-07-30
- */
-@TableName("fhzg_se_line_loss_daily")
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class FhzgSeLineLossDaily implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    @TableId(type = IdType.AUTO)
-    private Long id;
-
-    /** 统计日期 */
-    private LocalDate statDate;
-
-    /** 馈线ID */
-    private String feederId;
-
-    /** 馈线名称 */
-    private String feederName;
-
-    /** 区县ID */
-    private String countyId;
-
-    /** 变电站ID */
-    private String subsId;
-
-    /** 日线损电量 kWh */
-    private BigDecimal lineLossKwh;
-
-    /** 日最大线损功率 kW */
-    private BigDecimal maxLineLossKw;
-
-    /** 日平均线损功率 kW */
-    private BigDecimal avgLineLossKw;
-
-    /** 总断面数 */
-    private Integer totalSnapshots;
-
-    private LocalDateTime createTime;
-
-    private LocalDateTime updateTime;
-}

+ 21 - 18
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossDetail.java → common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossRate.java

@@ -13,17 +13,20 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 线路线损明细(逐段逐断面)
+ * 馈线线损率(每断面每馈线一条)
+ * <p>
+ * 口径:线损率 = (总功率 - 配变有功 - 中压用户接入点有功) / 总功率 × 100%
+ * 总功率取状估馈线块 seoPs(≈出线开关功率);配变/中压用户接入点有功同样取状估 seoPs。
  *
  *
  * @author lsl
  * @author lsl
- * @since 2026-07-30
+ * @since 2026-08
  */
  */
-@TableName("fhzg_se_line_loss_detail")
+@TableName("fhzg_se_line_loss_rate")
 @Data
 @Data
 @Builder
 @Builder
 @NoArgsConstructor
 @NoArgsConstructor
 @AllArgsConstructor
 @AllArgsConstructor
-public class FhzgSeLineLossDetail implements Serializable {
+public class FhzgSeLineLossRate implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
@@ -36,29 +39,29 @@ public class FhzgSeLineLossDetail implements Serializable {
     /** 馈线ID */
     /** 馈线ID */
     private String feederId;
     private String feederId;
 
 
-    /** 段ID */
-    private String psrId;
+    /** 馈线名称 */
+    private String feederName;
 
 
-    /** 段类型: dxd-导线段, 0201-电缆段 */
-    private String psrType;
+    /** 变电站ID */
+    private String subsId;
 
 
     /** 区县ID */
     /** 区县ID */
     private String countyId;
     private String countyId;
 
 
-    /** 变电站ID */
-    private String subsId;
+    /** 总功率(馈线状估有功, MW) */
+    private BigDecimal totalPower;
 
 
-    /** 状估电流 kA */
-    private BigDecimal currentKa;
+    /** 配变有功(MW) */
+    private BigDecimal transPower;
 
 
-    /** 段长度 米 */
-    private BigDecimal sbLengthM;
+    /** 中压用户接入点有功(MW) */
+    private BigDecimal consumerPower;
 
 
-    /** 电阻率 Ω/km */
-    private BigDecimal resistivity;
+    /** 线损功率 = 总功率-配变-用户 (MW) */
+    private BigDecimal lineLossPower;
 
 
-    /** 线损功率 kW */
-    private BigDecimal lineLossKw;
+    /** 线损率(%) */
+    private BigDecimal lineLossRate;
 
 
     private LocalDateTime createTime;
     private LocalDateTime createTime;
 }
 }

+ 0 - 7
common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDailyMapper.java

@@ -1,7 +0,0 @@
-package com.hdkj.lt.bf.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDaily;
-
-public interface FhzgSeLineLossDailyMapper extends BaseMapper<FhzgSeLineLossDaily> {
-}

+ 0 - 15
common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDetailMapper.java

@@ -1,15 +0,0 @@
-package com.hdkj.lt.bf.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDetail;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
-
-@Mapper
-public interface FhzgSeLineLossDetailMapper extends BaseMapper<FhzgSeLineLossDetail> {
-
-    /** 批量插入线损明细 */
-    int insertBatch(@Param("list") List<FhzgSeLineLossDetail> list);
-}

+ 15 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossRateMapper.java

@@ -0,0 +1,15 @@
+package com.hdkj.lt.bf.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hdkj.lt.bf.entity.FhzgSeLineLossRate;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 馈线线损率 Mapper
+ *
+ * @author lsl
+ * @since 2026-08
+ */
+@Mapper
+public interface FhzgSeLineLossRateMapper extends BaseMapper<FhzgSeLineLossRate> {
+}

+ 0 - 114
common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeLineLossDailyScheduler.java

@@ -1,114 +0,0 @@
-package com.hdkj.lt.bf.scheduler;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDaily;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDetail;
-import com.hdkj.lt.bf.mapper.FhzgSeLineLossDailyMapper;
-import com.hdkj.lt.bf.mapper.FhzgSeLineLossDetailMapper;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 线路线损日汇总逻辑(公共模块)
- * <p>
- * 定时调度由 pwfhzg-job 通过 sys_job 配置触发(凌晨1点聚合昨天),
- * 也可由 bf 手动端点 /indicator/line-loss/manual 触发。
- *
- * @author lsl
- * @since 2026-07-30
- */
-@Slf4j
-@Component
-@RequiredArgsConstructor
-public class SeLineLossDailyScheduler {
-
-    private final FhzgSeLineLossDetailMapper lineLossDetailMapper;
-    private final FhzgSeLineLossDailyMapper lineLossDailyMapper;
-
-    /**
-     * 聚合指定日期的线损明细到日汇总表(手动触发时调用)
-     */
-    @Transactional(rollbackFor = Exception.class)
-    public String aggregateForDate(LocalDate date) {
-        LocalDateTime startTime = date.atStartOfDay();
-        LocalDateTime endTime = date.atTime(23, 59, 59);
-
-        // 按馈线 SQL 聚合
-        // lineLossKwh = SUM(line_loss_kw) × 0.25(15min 断面间隔换算 kWh)
-        QueryWrapper<FhzgSeLineLossDetail> wrapper = new QueryWrapper<FhzgSeLineLossDetail>()
-                .select("feeder_id",
-                        "MAX(county_id) AS county_id",
-                        "MAX(subs_id) AS subs_id",
-                        "COALESCE(SUM(line_loss_kw), 0) AS sum_kw",
-                        "COALESCE(MAX(line_loss_kw), 0) AS max_kw",
-                        "COUNT(line_loss_kw) AS cnt")
-                .ge("snap_time", startTime)
-                .le("snap_time", endTime)
-                .isNotNull("feeder_id")
-                .groupBy("feeder_id");
-
-        List<Map<String, Object>> rows = lineLossDetailMapper.selectMaps(wrapper);
-
-        if (rows == null || rows.isEmpty()) {
-            log.info("[线路线损日汇总] {} 无线损明细数据,跳过", date);
-            return date + " 无线损明细数据";
-        }
-
-        // 构建日汇总记录
-        List<FhzgSeLineLossDaily> dailyRecords = new ArrayList<>();
-        for (Map<String, Object> row : rows) {
-            String feederId = (String) row.get("feeder_id");
-            if (feederId == null) continue;
-
-            BigDecimal sumKw = (BigDecimal) row.get("sum_kw");
-            BigDecimal maxKw = (BigDecimal) row.get("max_kw");
-            int cnt = ((Number) row.get("cnt")).intValue();
-
-            // 线损电量 kWh = 线损功率 kW × 断面间隔(15min = 0.25h)
-            BigDecimal sumKwh = sumKw.multiply(BigDecimal.valueOf(0.25));
-
-            BigDecimal avgKw = cnt > 0
-                    ? sumKw.divide(BigDecimal.valueOf(cnt), 4, RoundingMode.HALF_UP)
-                    : BigDecimal.ZERO;
-
-            FhzgSeLineLossDaily daily = FhzgSeLineLossDaily.builder()
-                    .statDate(date)
-                    .feederId(feederId)
-                    .feederName(null)
-                    .countyId((String) row.get("county_id"))
-                    .subsId((String) row.get("subs_id"))
-                    .lineLossKwh(sumKwh.setScale(2, RoundingMode.HALF_UP))
-                    .maxLineLossKw(maxKw.compareTo(BigDecimal.ZERO) > 0 ? maxKw.setScale(4, RoundingMode.HALF_UP) : BigDecimal.ZERO)
-                    .avgLineLossKw(avgKw.setScale(4, RoundingMode.HALF_UP))
-                    .totalSnapshots(cnt)
-                    .createTime(LocalDateTime.now())
-                    .updateTime(LocalDateTime.now())
-                    .build();
-            dailyRecords.add(daily);
-        }
-
-        // 先删当天已有数据(避免重复运行时叠加)
-        LambdaQueryWrapper<FhzgSeLineLossDaily> deleteWrapper = new LambdaQueryWrapper<FhzgSeLineLossDaily>()
-                .eq(FhzgSeLineLossDaily::getStatDate, date);
-        lineLossDailyMapper.delete(deleteWrapper);
-
-        for (FhzgSeLineLossDaily record : dailyRecords) {
-            lineLossDailyMapper.insert(record);
-        }
-
-        String result = date + " 处理完成: " + dailyRecords.size() + " 条馈线线损日汇总";
-        log.info("[线路线损日汇总] {}", result);
-        return result;
-    }
-}

+ 0 - 19
common/common-se/src/main/resources/mapper/FhzgSeLineLossDetailMapper.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.hdkj.lt.bf.mapper.FhzgSeLineLossDetailMapper">
-
-    <!-- 批量插入线损明细 -->
-    <insert id="insertBatch">
-        INSERT INTO fhzg_se_line_loss_detail (
-            snap_time, feeder_id, psr_id, psr_type, county_id, subs_id,
-            current_ka, sb_length_m, resistivity, line_loss_kw, create_time
-        ) VALUES
-        <foreach collection="list" item="item" separator=",">
-            (
-            #{item.snapTime}, #{item.feederId}, #{item.psrId}, #{item.psrType}, #{item.countyId}, #{item.subsId},
-            #{item.currentKa}, #{item.sbLengthM}, #{item.resistivity}, #{item.lineLossKw}, #{item.createTime}
-            )
-        </foreach>
-    </insert>
-
-</mapper>

+ 2 - 0
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/common/SeMonitorThreshold.java

@@ -36,6 +36,8 @@ public final class SeMonitorThreshold {
     // === 负载率 ===
     // === 负载率 ===
     public static final BigDecimal LOAD_RATE_HEAVY = new BigDecimal("75");
     public static final BigDecimal LOAD_RATE_HEAVY = new BigDecimal("75");
     public static final BigDecimal LOAD_RATE_OVERLOAD = new BigDecimal("100");
     public static final BigDecimal LOAD_RATE_OVERLOAD = new BigDecimal("100");
+    /** 负载率异常阈值:超过该值视为异常数据(状估量测异常),不计入计算 */
+    public static final BigDecimal LOAD_RATE_ABNORMAL = new BigDecimal("200");
 
 
     // === 电压越限持续触发: 时间跨度 ≥ 60分钟 ===
     // === 电压越限持续触发: 时间跨度 ≥ 60分钟 ===
     public static final long TRIGGER_DURATION_MINUTES = 60L;
     public static final long TRIGGER_DURATION_MINUTES = 60L;

+ 5 - 36
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/controller/optimization/IndicatorController.java

@@ -9,7 +9,7 @@ import com.hdkj.lt.bf.entity.dto.SeOverloadAlarmListReq;
 import com.hdkj.lt.bf.entity.dto.SeVoltageAlarmListReq;
 import com.hdkj.lt.bf.entity.dto.SeVoltageAlarmListReq;
 import com.hdkj.lt.bf.entity.vo.PageResult;
 import com.hdkj.lt.bf.entity.vo.PageResult;
 import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
 import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
-import com.hdkj.lt.bf.entity.vo.SeLineLossVO;
+import com.hdkj.lt.bf.entity.vo.SeLineLossRateVO;
 import com.hdkj.lt.bf.entity.vo.FeederSelectVO;
 import com.hdkj.lt.bf.entity.vo.FeederSelectVO;
 import com.hdkj.lt.bf.entity.vo.SeAlarmDetailVO;
 import com.hdkj.lt.bf.entity.vo.SeAlarmDetailVO;
 import com.hdkj.lt.bf.entity.vo.SeAlarmListVO;
 import com.hdkj.lt.bf.entity.vo.SeAlarmListVO;
@@ -17,7 +17,6 @@ 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.scheduler.SeCapacityDailyScheduler;
 import com.hdkj.lt.bf.scheduler.SeCapacityDailyScheduler;
 import com.hdkj.lt.bf.scheduler.SeConsumerCountDailyScheduler;
 import com.hdkj.lt.bf.scheduler.SeConsumerCountDailyScheduler;
-import com.hdkj.lt.bf.scheduler.SeLineLossDailyScheduler;
 import com.hdkj.lt.bf.service.SeBlockVoltageEventService;
 import com.hdkj.lt.bf.service.SeBlockVoltageEventService;
 import com.hdkj.lt.bf.service.SeAlarmService;
 import com.hdkj.lt.bf.service.SeAlarmService;
 import com.hdkj.lt.bf.service.SeIndicatorService;
 import com.hdkj.lt.bf.service.SeIndicatorService;
@@ -57,7 +56,6 @@ public class IndicatorController extends BaseController {
     private final SeSnapshotService seSnapshotService;
     private final SeSnapshotService seSnapshotService;
     private final SeCapacityDailyScheduler seCapacityDailyScheduler;
     private final SeCapacityDailyScheduler seCapacityDailyScheduler;
     private final SeConsumerCountDailyScheduler seConsumerCountDailyScheduler;
     private final SeConsumerCountDailyScheduler seConsumerCountDailyScheduler;
-    private final SeLineLossDailyScheduler seLineLossDailyScheduler;
     private final SeBlockVoltageEventService seBlockVoltageEventService;
     private final SeBlockVoltageEventService seBlockVoltageEventService;
 
 
     /**
     /**
@@ -108,12 +106,14 @@ public class IndicatorController extends BaseController {
     }
     }
 
 
     /**
     /**
-     * 线损电量(独立接口,从供电能力拆出;today/month/year)
+     * 线损率(独立接口,从供电能力拆出;today/month/year)
+     * <p>
+     * 线损率 = (总功率 - 配变有功 - 中压用户接入点有功) / 总功率 × 100%,区间加权平均。
      *
      *
      * @param params {id, type(2/3/4), timeScope(today/month/year)}
      * @param params {id, type(2/3/4), timeScope(today/month/year)}
      */
      */
     @PostMapping("/line-loss")
     @PostMapping("/line-loss")
-    public ApiResponse<SeLineLossVO> lineLoss(@RequestBody Map<String, String> params) {
+    public ApiResponse<SeLineLossRateVO> lineLoss(@RequestBody Map<String, String> params) {
         String id = params.get("id");
         String id = params.get("id");
         String type = params.get("type");
         String type = params.get("type");
         String timeScope = params.get("timeScope");
         String timeScope = params.get("timeScope");
@@ -348,37 +348,6 @@ public class IndicatorController extends BaseController {
         }
         }
     }
     }
 
 
-    /**
-     * 手动触发线路线损日汇总刷数
-     * <p>
-     * 默认重跑今天的数据(覆盖更新:先删再插)。传 statDate 时重跑指定日期。
-     *
-     * @param params {statDate(可选, yyyy-MM-dd),默认当天}
-     */
-    @PostMapping("/line-loss/manual")
-    public ApiResponse<String> manualLineLossDaily(@RequestBody Map<String, String> params) {
-        String statDateStr = params.get("statDate");
-        LocalDate date;
-        if (StringUtils.isNotBlank(statDateStr)) {
-            try {
-                date = LocalDate.parse(statDateStr);
-            } catch (Exception e) {
-                return ApiResponse.fail("日期格式非法,期望 yyyy-MM-dd");
-            }
-        } else {
-            date = LocalDate.now();
-        }
-
-        log.info("[线路线损日汇总] 手动触发刷数 statDate={}", date);
-        try {
-            String result = seLineLossDailyScheduler.aggregateForDate(date);
-            return ApiResponse.success(result);
-        } catch (Exception e) {
-            log.error("[线路线损日汇总] 手动触发异常", e);
-            return ApiResponse.fail("执行异常: " + e.getMessage());
-        }
-    }
-
     private StateEstimation mergeCountyResults(List<StateEstimation> list) {
     private StateEstimation mergeCountyResults(List<StateEstimation> list) {
         if (list == null || list.isEmpty()) return null;
         if (list == null || list.isEmpty()) return null;
 
 

+ 3 - 0
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/entity/dto/SeVoltageAlarmListReq.java

@@ -32,6 +32,9 @@ public class SeVoltageAlarmListReq {
     @ApiModelProperty(value = "时间排序 asc/desc")
     @ApiModelProperty(value = "时间排序 asc/desc")
     private String orderByTime;
     private String orderByTime;
 
 
+    @ApiModelProperty(value = "越限方向筛选: 1=越上限 2=越下限 0/不传=全量")
+    private Integer orderByOvertype;
+
     @ApiModelProperty(value = "告警级别 feeder/mvtrans", required = true)
     @ApiModelProperty(value = "告警级别 feeder/mvtrans", required = true)
     private String alarmLevel;
     private String alarmLevel;
 
 

+ 7 - 7
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/entity/vo/SeLineLossVO.java → services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/entity/vo/SeLineLossRateVO.java

@@ -13,10 +13,10 @@ import java.io.Serializable;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
 
 
 /**
 /**
- * 线损电量 VO(独立接口,从供电能力拆出
+ * 线损率 VO(馈线级线损率,状估口径
  * <p>
  * <p>
- * 供电能力 /indicator/capacity 只返回负载率/重过载,线损单独 /indicator/line-loss 查询,
- * 避免线损明细聚合拖慢主接口
+ * 线损率 = (总功率 - 配变有功 - 中压用户接入点有功) / 总功率 × 100%
+ * 查询结果按区间加权平均(Σ线损功率 / Σ总功率)
  *
  *
  * @author lsl
  * @author lsl
  * @since 2026-08
  * @since 2026-08
@@ -25,12 +25,12 @@ import java.math.BigDecimal;
 @NoArgsConstructor
 @NoArgsConstructor
 @AllArgsConstructor
 @AllArgsConstructor
 @Builder
 @Builder
-@ApiModel(description = "线损电量")
-public class SeLineLossVO implements Serializable {
+@ApiModel(description = "线损")
+public class SeLineLossRateVO implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
-    @ApiModelProperty(value = "线路损耗电量(kWh)")
+    @ApiModelProperty(value = "线损率(%)")
     @JsonSerialize(using = BigDecimalSerializer.StripTrailingZerosSerializer.class)
     @JsonSerialize(using = BigDecimalSerializer.StripTrailingZerosSerializer.class)
-    private BigDecimal lineLossKwh;
+    private BigDecimal lineLossRate;
 }
 }

+ 2 - 2
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/SeIndicatorService.java

@@ -1,7 +1,7 @@
 package com.hdkj.lt.bf.service;
 package com.hdkj.lt.bf.service;
 
 
 import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
 import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
-import com.hdkj.lt.bf.entity.vo.SeLineLossVO;
+import com.hdkj.lt.bf.entity.vo.SeLineLossRateVO;
 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.entity.vo.SeVoltageIndexVO;
@@ -56,5 +56,5 @@ public interface SeIndicatorService {
      * @param type     节点类型 2=区县 3=变电站 4=馈线
      * @param type     节点类型 2=区县 3=变电站 4=馈线
      * @param timeScope today/month/year
      * @param timeScope today/month/year
      */
      */
-    SeLineLossVO queryLineLoss(String id, Integer type, String timeScope);
+    SeLineLossRateVO queryLineLoss(String id, Integer type, String timeScope);
 }
 }

+ 16 - 0
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeAlarmServiceImpl.java

@@ -62,6 +62,10 @@ public class SeAlarmServiceImpl implements SeAlarmService {
     private static final List<String> VOLTAGE_TYPES = Arrays.asList(
     private static final List<String> VOLTAGE_TYPES = Arrays.asList(
             "voltage_over", "voltage_under", "voltage_over_general", "voltage_over_severe",
             "voltage_over", "voltage_under", "voltage_over_general", "voltage_over_severe",
             "voltage_under_general", "voltage_under_severe");
             "voltage_under_general", "voltage_under_severe");
+    private static final List<String> VOLTAGE_OVER_TYPES = Arrays.asList(
+            "voltage_over", "voltage_over_general", "voltage_over_severe");
+    private static final List<String> VOLTAGE_UNDER_TYPES = Arrays.asList(
+            "voltage_under", "voltage_under_general", "voltage_under_severe");
 
 
     // ============================================================
     // ============================================================
     // 1. 重过载列表
     // 1. 重过载列表
@@ -132,12 +136,22 @@ public class SeAlarmServiceImpl implements SeAlarmService {
 
 
         String alarmLevel = "feeder".equals(req.getAlarmLevel()) ? "feeder" : "mvtrans";
         String alarmLevel = "feeder".equals(req.getAlarmLevel()) ? "feeder" : "mvtrans";
 
 
+        // 越限方向筛选: 1=越上限 2=越下限 0/不传=全量
+        Integer overtype = req.getOrderByOvertype();
+        List<String> overtypeFilter = null;
+        if (overtype != null && overtype == 1) {
+            overtypeFilter = VOLTAGE_OVER_TYPES;
+        } else if (overtype != null && overtype == 2) {
+            overtypeFilter = VOLTAGE_UNDER_TYPES;
+        }
+
         // 1. 实时电压事件(事件表,不分页全量拉取时间窗)
         // 1. 实时电压事件(事件表,不分页全量拉取时间窗)
         List<FhzgSeVoltageEvent> volEvents = voltageEventMapper.selectList(
         List<FhzgSeVoltageEvent> volEvents = voltageEventMapper.selectList(
                 new LambdaQueryWrapper<FhzgSeVoltageEvent>()
                 new LambdaQueryWrapper<FhzgSeVoltageEvent>()
                         .ge(FhzgSeVoltageEvent::getFirstOverTime, rangeStart)
                         .ge(FhzgSeVoltageEvent::getFirstOverTime, rangeStart)
                         .lt(FhzgSeVoltageEvent::getFirstOverTime, rangeEnd)
                         .lt(FhzgSeVoltageEvent::getFirstOverTime, rangeEnd)
                         .eq(FhzgSeVoltageEvent::getAlarmLevel, alarmLevel)
                         .eq(FhzgSeVoltageEvent::getAlarmLevel, alarmLevel)
+                        .in(overtypeFilter != null, FhzgSeVoltageEvent::getAlarmType, overtypeFilter)
                         .and(w -> applyVoltageFilter(w, req.getId(), req.getType()))
                         .and(w -> applyVoltageFilter(w, req.getId(), req.getType()))
                         .like(StringUtils.isNotBlank(req.getFeederName()),
                         .like(StringUtils.isNotBlank(req.getFeederName()),
                                 FhzgSeVoltageEvent::getFeederName, req.getFeederName()));
                                 FhzgSeVoltageEvent::getFeederName, req.getFeederName()));
@@ -151,6 +165,8 @@ public class SeAlarmServiceImpl implements SeAlarmService {
                     new LambdaQueryWrapper<FhzgSeBlockVoltageEvent>()
                     new LambdaQueryWrapper<FhzgSeBlockVoltageEvent>()
                             .ge(FhzgSeBlockVoltageEvent::getEventStartTime, rangeStart)
                             .ge(FhzgSeBlockVoltageEvent::getEventStartTime, rangeStart)
                             .lt(FhzgSeBlockVoltageEvent::getEventStartTime, rangeEnd)
                             .lt(FhzgSeBlockVoltageEvent::getEventStartTime, rangeEnd)
+                            .eq(overtypeFilter != null, FhzgSeBlockVoltageEvent::getOverLimitType,
+                                    overtypeFilter == VOLTAGE_OVER_TYPES ? "02" : "01")
                             .and(w -> applyBlockVoltageFilter(w, req.getId(), req.getType()))
                             .and(w -> applyBlockVoltageFilter(w, req.getId(), req.getType()))
                             .like(StringUtils.isNotBlank(req.getFeederName()),
                             .like(StringUtils.isNotBlank(req.getFeederName()),
                                     FhzgSeBlockVoltageEvent::getFeederName, req.getFeederName()));
                                     FhzgSeBlockVoltageEvent::getFeederName, req.getFeederName()));

+ 37 - 77
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeIndicatorServiceImpl.java

@@ -4,8 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDaily;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDetail;
+import com.hdkj.lt.bf.entity.FhzgSeLineLossRate;
 import com.hdkj.lt.bf.entity.FhzgDwmVoltageOverLimitDaily;
 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;
@@ -14,13 +13,12 @@ import com.hdkj.lt.bf.entity.FhzgSeConsumerCountDaily;
 import com.hdkj.lt.bf.entity.FhzgSeConsumerRelation;
 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.SeLineLossVO;
+import com.hdkj.lt.bf.entity.vo.SeLineLossRateVO;
 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.entity.vo.SeVoltageIndexVO;
 import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
-import com.hdkj.lt.bf.mapper.FhzgSeLineLossDailyMapper;
-import com.hdkj.lt.bf.mapper.FhzgSeLineLossDetailMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeLineLossRateMapper;
 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;
@@ -63,8 +61,7 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     private final FhzgSeCurrentEventMapper currentEventMapper;
     private final FhzgSeCurrentEventMapper currentEventMapper;
     private final FhzgSeConsumerCountDailyMapper consumerCountDailyMapper;
     private final FhzgSeConsumerCountDailyMapper consumerCountDailyMapper;
     private final FhzgSeConsumerRelationMapper consumerRelationMapper;
     private final FhzgSeConsumerRelationMapper consumerRelationMapper;
-    private final FhzgSeLineLossDetailMapper lineLossDetailMapper;
-    private final FhzgSeLineLossDailyMapper lineLossDailyMapper;
+    private final FhzgSeLineLossRateMapper lineLossRateMapper;
     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");
@@ -562,42 +559,49 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     }
     }
 
 
     /**
     /**
-     * 线损电量(独立接口,从供电能力拆出):
-     * today → 明细表实时 SUM;
-     * month/year → 历史查日汇总表 [startDate, yesterday] + 今日明细实时补
+     * 线损(独立接口,从供电能力拆出):
+     * 按区间从 fhzg_se_line_loss_rate 加权平均(Σ线损功率 / Σ总功率 × 100)。
+     * today → 今日 0 点至今;month → 月初至今;year → 年初至今
      */
      */
     @Override
     @Override
-    public SeLineLossVO queryLineLoss(String id, Integer type, String timeScope) {
+    public SeLineLossRateVO queryLineLoss(String id, Integer type, String timeScope) {
         if (StringUtils.isBlank(id) || type == null || StringUtils.isBlank(timeScope)) {
         if (StringUtils.isBlank(id) || type == null || StringUtils.isBlank(timeScope)) {
             return null;
             return null;
         }
         }
 
 
-        BigDecimal lineLossKwh;
+        LocalDateTime startTime;
+        LocalDateTime endTime = LocalDateTime.now();
         if ("today".equalsIgnoreCase(timeScope)) {
         if ("today".equalsIgnoreCase(timeScope)) {
-            lineLossKwh = queryLineLossKwhFromDetail(id, type,
-                    LocalDate.now().atStartOfDay(), LocalDateTime.now());
+            startTime = LocalDate.now().atStartOfDay();
+        } else if ("month".equalsIgnoreCase(timeScope)) {
+            startTime = LocalDate.now().withDayOfMonth(1).atStartOfDay();
         } else {
         } else {
-            // month/year:历史(日汇总 T+1,只到昨天)+ 今日(明细实时)
-            LocalDate today = LocalDate.now();
-            LocalDate startDate;
-            LocalDate endDate = today.minusDays(1);
-            if ("month".equalsIgnoreCase(timeScope)) {
-                startDate = today.withDayOfMonth(1);
-            } else {
-                startDate = LocalDate.of(today.getYear(), 1, 1);
-            }
-            BigDecimal histKwh = BigDecimal.ZERO;
-            if (!endDate.isBefore(startDate)) {
-                histKwh = queryLineLossKwhFromDaily(id, type, startDate, endDate);
-            }
-            BigDecimal todayKwh = queryLineLossKwhFromDetail(id, type,
-                    today.atStartOfDay(), LocalDateTime.now());
-            lineLossKwh = histKwh.add(todayKwh);
+            startTime = LocalDate.of(LocalDate.now().getYear(), 1, 1).atStartOfDay();
         }
         }
 
 
-        return SeLineLossVO.builder()
-                .lineLossKwh(lineLossKwh.setScale(2, RoundingMode.HALF_UP))
-                .build();
+        QueryWrapper<FhzgSeLineLossRate> qw = new QueryWrapper<>();
+        qw.select("COALESCE(SUM(line_loss_power), 0) AS total_loss",
+                "COALESCE(SUM(total_power), 0) AS total_power")
+                .ge("snap_time", startTime)
+                .le("snap_time", endTime);
+        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 = lineLossRateMapper.selectMaps(qw);
+        if (rows == null || rows.isEmpty() || rows.get(0).get("total_power") == null
+                || new BigDecimal(rows.get(0).get("total_power").toString()).compareTo(BigDecimal.ZERO) <= 0) {
+            return SeLineLossRateVO.builder().lineLossRate(BigDecimal.ZERO).build();
+        }
+        BigDecimal totalLoss = new BigDecimal(rows.get(0).get("total_loss").toString());
+        BigDecimal totalPower = new BigDecimal(rows.get(0).get("total_power").toString());
+        BigDecimal rate = totalLoss.multiply(BigDecimal.valueOf(100))
+                .divide(totalPower, 2, RoundingMode.HALF_UP);
+
+        return SeLineLossRateVO.builder().lineLossRate(rate).build();
     }
     }
 
 
     /**
     /**
@@ -880,48 +884,4 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
         }
         }
         return 0;
         return 0;
     }
     }
-
-    // ============================================================
-    // 8. 线损电量查询
-    // ============================================================
-
-    /**
-     * 今日线损电量:从明细表聚合 SUM(line_loss_kw) × 0.25h
-     */
-    private BigDecimal queryLineLossKwhFromDetail(String id, Integer type,
-                                                   LocalDateTime startTime, LocalDateTime endTime) {
-        QueryWrapper<FhzgSeLineLossDetail> qw = new QueryWrapper<>();
-        qw.select("COALESCE(SUM(line_loss_kw), 0) AS total_loss");
-        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);
-        qw.ge("snap_time", startTime).lt("snap_time", endTime);
-        List<Map<String, Object>> rows = lineLossDetailMapper.selectMaps(qw);
-        if (rows != null && !rows.isEmpty() && rows.get(0).get("total_loss") != null) {
-            BigDecimal sumKw = ((Number) rows.get(0).get("total_loss")).longValue() == 0
-                    ? BigDecimal.ZERO
-                    : new BigDecimal(rows.get(0).get("total_loss").toString());
-            // 每个断面间隔15min = 0.25h,线损功率 × 时间 = 电量
-            return sumKw.multiply(new BigDecimal("0.25")).setScale(2, RoundingMode.HALF_UP);
-        }
-        return BigDecimal.ZERO;
-    }
-
-    /**
-     * 历史线损电量:从日汇总表聚合 SUM(line_loss_kwh)
-     */
-    private BigDecimal queryLineLossKwhFromDaily(String id, Integer type,
-                                                  LocalDate startDate, LocalDate endDate) {
-        QueryWrapper<FhzgSeLineLossDaily> qw = new QueryWrapper<>();
-        qw.select("COALESCE(SUM(line_loss_kwh), 0) AS total_kwh");
-        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);
-        qw.ge("stat_date", startDate).le("stat_date", endDate);
-        List<Map<String, Object>> rows = lineLossDailyMapper.selectMaps(qw);
-        if (rows != null && !rows.isEmpty() && rows.get(0).get("total_kwh") != null) {
-            return new BigDecimal(rows.get(0).get("total_kwh").toString()).setScale(2, RoundingMode.HALF_UP);
-        }
-        return BigDecimal.ZERO;
-    }
 }
 }

+ 116 - 127
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeSnapshotServiceImpl.java

@@ -8,11 +8,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.common.SeMonitorThreshold;
 import com.hdkj.lt.bf.common.SeMonitorThreshold;
 import com.hdkj.lt.bf.common.SeSnapshotConstants;
 import com.hdkj.lt.bf.common.SeSnapshotConstants;
-import com.hdkj.lt.bf.entity.DwdShbDsCableSegmentBase;
-import com.hdkj.lt.bf.entity.DwdShbDsSpanSegmentBase;
 import com.hdkj.lt.bf.entity.DwdShbDsTransformerBase;
 import com.hdkj.lt.bf.entity.DwdShbDsTransformerBase;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
-import com.hdkj.lt.bf.entity.FhzgSeLineLossDetail;
+import com.hdkj.lt.bf.entity.FhzgSeLineLossRate;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorCurrent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorCurrent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorVoltage;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorVoltage;
 import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
 import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
@@ -21,13 +19,11 @@ import com.hdkj.lt.bf.event.MvtransOverVoltageEvent;
 import com.hdkj.lt.bf.event.ReconTriggerEvent;
 import com.hdkj.lt.bf.event.ReconTriggerEvent;
 import com.hdkj.lt.bf.mapper.FeederTopoMapper;
 import com.hdkj.lt.bf.mapper.FeederTopoMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
-import com.hdkj.lt.bf.mapper.FhzgSeLineLossDetailMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeLineLossRateMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorCurrentMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorCurrentMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorVoltageMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorVoltageMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
-import com.hdkj.lt.bf.mapper.DwdShbDsCableSegmentBaseMapper;
-import com.hdkj.lt.bf.mapper.DwdShbDsSpanSegmentBaseMapper;
 import com.hdkj.lt.bf.mapper.DwdShbDsTransformerBaseMapper;
 import com.hdkj.lt.bf.mapper.DwdShbDsTransformerBaseMapper;
 import com.hdkj.lt.bf.service.SeSnapshotService;
 import com.hdkj.lt.bf.service.SeSnapshotService;
 import com.hdkj.lt.core.bizms.modle.po.FeederTopo;
 import com.hdkj.lt.core.bizms.modle.po.FeederTopo;
@@ -59,9 +55,7 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
     private final FeederGroupApplication feederGroupApplication;
     private final FeederGroupApplication feederGroupApplication;
     private final ApplicationEventPublisher eventPublisher;
     private final ApplicationEventPublisher eventPublisher;
     private final FeederTopoMapper feederTopoMapper;
     private final FeederTopoMapper feederTopoMapper;
-    private final FhzgSeLineLossDetailMapper lineLossDetailMapper;
-    private final DwdShbDsSpanSegmentBaseMapper spanSegmentMapper;
-    private final DwdShbDsCableSegmentBaseMapper cableSegmentMapper;
+    private final FhzgSeLineLossRateMapper lineLossRateMapper;
 
 
     private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
 
@@ -170,11 +164,10 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
             }
             }
         }
         }
 
 
-        // === 3. 线路线段 → 线损计算 ===
-        JSONArray segments = root.getJSONArray("periodSegmentSeResult");
-        if (segments != null && !segments.isEmpty()) {
-            processSegmentsForLineLoss(segments, snapTime, feederMap);
-        }
+        // === 3. 馈线线损率(状估口径)===
+        // 线损率 = (总功率 - 配变有功 - 中压用户接入点有功) / 总功率 × 100%
+        // 总功率=馈线块 seoPs(≈出线开关功率);配变块=0302+0110;中压用户接入点块=370000
+        processLineLossRate(root, snapTime, feederMap, advanceState);
 
 
         if (!batch.isEmpty()) {
         if (!batch.isEmpty()) {
             // 防重:查该断面时刻已有的所有 device_id(精确到秒,无需再 IN 过滤)
             // 防重:查该断面时刻已有的所有 device_id(精确到秒,无需再 IN 过滤)
@@ -209,6 +202,13 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         if (feederId == null || feederId.isEmpty()) return null;  // 缺 psrId 直接丢弃,防 NULL 脏写
         if (feederId == null || feederId.isEmpty()) return null;  // 缺 psrId 直接丢弃,防 NULL 脏写
         String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
         String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
         BigDecimal loadRate = firstValue(f.getJSONArray("loadRates"));
         BigDecimal loadRate = firstValue(f.getJSONArray("loadRates"));
+        // 负载率超过 200% 视为异常数据(状估量测异常),不计入计算:
+        // loadRate 置 null → 不触发电流事件、不参与负载率聚合统计
+        if (loadRate != null && loadRate.compareTo(SeMonitorThreshold.LOAD_RATE_ABNORMAL) > 0) {
+            log.warn("馈线负载率异常(>{}),不计入计算: feederId={}, loadRate={}",
+                    SeMonitorThreshold.LOAD_RATE_ABNORMAL, feederId, loadRate);
+            loadRate = null;
+        }
         BigDecimal voltage = firstValue(f.getJSONArray("seoUs"));
         BigDecimal voltage = firstValue(f.getJSONArray("seoUs"));
         BigDecimal voltageRatio = voltage != null
         BigDecimal voltageRatio = voltage != null
                 ? voltage.divide(SeMonitorThreshold.FEEDER_BASE_KV, 4, RoundingMode.HALF_UP) : null;
                 ? voltage.divide(SeMonitorThreshold.FEEDER_BASE_KV, 4, RoundingMode.HALF_UP) : null;
@@ -522,129 +522,118 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
     }
     }
 
 
     // ============================================================
     // ============================================================
-    // 线线损计算
+    // 线线损计算(状估口径)
     // ============================================================
     // ============================================================
 
 
     /**
     /**
-     * 处理线路线段断面数据,计算逐段线损功率并写入明细表
+     * 馈线线损率计算(新算法,替代旧的逐段电流法)
      * <p>
      * <p>
-     * line_loss_kW = 3000 × I_kA² × r_Ω_per_km × (L_m / 1000)
-     * 电阻率: dxd(导线段)=0.4132Ω/km, 0201(电缆段)=0.313Ω/km
-     * 段长度从 dwd_shb_ds_span_segment_base(导线段)/ dwd_shb_ds_cable_segment_base(电缆段)的 length 取(单位:米)
-     * SE 返回的 seoIs 为安培,先 /1000 换算成 kA
+     * 线损率 = (总功率 - 配变有功 - 中压用户接入点有功) / 总功率 × 100%
+     * <ul>
+     *   <li>总功率 = periodFeederSeResult[].seoPs(馈线状估有功 Σ,≈出线开关功率)</li>
+     *   <li>配变有功 = periodMVTransSeResult[].seoPs(0302+0110 全部配变 Σ)</li>
+     *   <li>中压用户接入点有功 = periodEnergyConsumerSeResult[].seoPs(370000 Σ)</li>
+     * </ul>
+     * 每断面每馈线一条写入 fhzg_se_line_loss_rate;总功率缺失/为 0 的馈线跳过(仅 log)。
+     * backfill(advanceState=false)同样计算写库,仅跳过状态机推进。
      */
      */
-    private void processSegmentsForLineLoss(JSONArray segments, LocalDateTime snapTime, Map<String, String[]> feederMap) {
-        // 1. 按类型分组查段长度(档距段→span表,电缆段→cable表),过滤在运设备
-        Map<String, BigDecimal> lengthMap = new HashMap<>();
+    private void processLineLossRate(JSONObject root, LocalDateTime snapTime,
+                                     Map<String, String[]> feederMap, boolean advanceState) {
         try {
         try {
-            List<String> spanIds = new ArrayList<>();
-            List<String> cableIds = new ArrayList<>();
-            for (int i = 0; i < segments.size(); i++) {
-                JSONObject seg = segments.getJSONObject(i);
-                String pid = seg.getString("psrId");
-                String pt = seg.getString("psrType");
-                if (pid == null) continue;
-                if ("dxd".equals(pt)) spanIds.add(pid);
-                else if ("0201".equals(pt)) cableIds.add(pid);
-            }
-            if (!spanIds.isEmpty()) {
-                spanSegmentMapper.selectList(
-                        new LambdaQueryWrapper<DwdShbDsSpanSegmentBase>()
-                                .select(DwdShbDsSpanSegmentBase::getPsrId, DwdShbDsSpanSegmentBase::getLength)
-                                .in(DwdShbDsSpanSegmentBase::getPsrId, spanIds)
-                                .eq(DwdShbDsSpanSegmentBase::getPsrState, "20"))
-                        .forEach(r -> { if (r.getLength() != null) lengthMap.put(r.getPsrId(), r.getLength()); });
+            // 1. 配变有功:periodMVTransSeResult 按 feederId 汇总 seoPs
+            Map<String, BigDecimal> transPowerMap = new HashMap<>();
+            JSONArray mvtrans = root.getJSONArray("periodMVTransSeResult");
+            if (mvtrans != null) {
+                for (int i = 0; i < mvtrans.size(); i++) {
+                    JSONObject m = mvtrans.getJSONObject(i);
+                    String feederId = m.getString("feederId");
+                    BigDecimal p = firstValue(m.getJSONArray("seoPs"));
+                    if (feederId == null || p == null) continue;
+                    transPowerMap.merge(feederId, p, BigDecimal::add);
+                }
             }
             }
-            if (!cableIds.isEmpty()) {
-                cableSegmentMapper.selectList(
-                        new LambdaQueryWrapper<DwdShbDsCableSegmentBase>()
-                                .select(DwdShbDsCableSegmentBase::getPsrId, DwdShbDsCableSegmentBase::getLength)
-                                .in(DwdShbDsCableSegmentBase::getPsrId, cableIds)
-                                .eq(DwdShbDsCableSegmentBase::getPsrState, "20"))
-                        .forEach(r -> { if (r.getLength() != null) lengthMap.put(r.getPsrId(), r.getLength()); });
+
+            // 2. 中压用户接入点有功:periodEnergyConsumerSeResult 按 feederId 汇总 seoPs
+            Map<String, BigDecimal> consumerPowerMap = new HashMap<>();
+            JSONArray consumers = root.getJSONArray("periodEnergyConsumerSeResult");
+            if (consumers != null) {
+                for (int i = 0; i < consumers.size(); i++) {
+                    JSONObject c = consumers.getJSONObject(i);
+                    String feederId = c.getString("feederId");
+                    BigDecimal p = firstValue(c.getJSONArray("seoPs"));
+                    if (feederId == null || p == null) continue;
+                    consumerPowerMap.merge(feederId, p, BigDecimal::add);
+                }
             }
             }
-        } catch (Exception e) {
-            log.warn("查询段长度失败", e);
-        }
 
 
-        // 3. 逐段计算线损
-        List<FhzgSeLineLossDetail> batch = new ArrayList<>();
-        LocalDateTime now = LocalDateTime.now();
-        for (int i = 0; i < segments.size(); i++) {
-            JSONObject seg = segments.getJSONObject(i);
-            String psrId = seg.getString("psrId");
-            String psrType = seg.getString("psrType");
-            String feederId = seg.getString("feederId");
-            String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
-
-            BigDecimal current = firstValue(seg.getJSONArray("seoIs"));
-            if (current == null || current.compareTo(BigDecimal.ZERO) <= 0) continue;
-
-            BigDecimal sbLength = lengthMap.get(psrId);
-            if (sbLength == null || sbLength.compareTo(BigDecimal.ZERO) <= 0) continue;
-
-            // 电阻率
-            BigDecimal resistivity;
-            if ("0201".equals(psrType)) {
-                resistivity = new BigDecimal("0.313");      // 电缆段
-            } else if ("dxd".equals(psrType)) {
-                resistivity = new BigDecimal("0.4132");     // 导线段
-            } else {
-                log.debug("未知段类型跳过 psrType={}, psrId={}", psrType, psrId);
-                continue;
+            // 3. 馈线块:总功率 + 逐馈线计算线损率
+            JSONArray feeders = root.getJSONArray("periodFeederSeResult");
+            if (feeders == null || feeders.isEmpty()) {
+                return;
             }
             }
+            List<FhzgSeLineLossRate> batch = new ArrayList<>();
+            LocalDateTime now = LocalDateTime.now();
+            for (int i = 0; i < feeders.size(); i++) {
+                JSONObject f = feeders.getJSONObject(i);
+                String feederId = f.getString("psrId");
+                if (feederId == null || feederId.isEmpty()) continue;
 
 
-            // SE 返回电流为安培,换算成 kA:I_kA = I_A / 1000
-//            BigDecimal currentKa = current.divide(BigDecimal.valueOf(1000), 8, RoundingMode.HALF_UP);
-            BigDecimal currentKa = current.setScale(8, RoundingMode.HALF_UP);
-
-            // 3000 × I_kA² × r × (L_m / 1000) = 3 × I_kA² × r × L_m
-            BigDecimal lengthKm = sbLength.divide(BigDecimal.valueOf(1000), 8, RoundingMode.HALF_UP);
-            BigDecimal loss = BigDecimal.valueOf(3000)
-                    .multiply(currentKa.pow(2))
-                    .multiply(resistivity)
-                    .multiply(lengthKm)
-                    .setScale(4, RoundingMode.HALF_UP);
-
-            FhzgSeLineLossDetail detail = FhzgSeLineLossDetail.builder()
-                    .snapTime(snapTime)
-                    .feederId(feederId)
-                    .psrId(psrId)
-                    .psrType(psrType)
-                    .countyId(loc.length > 1 ? loc[1] : "")
-                    .subsId(loc.length > 0 ? loc[0] : "")
-                    .currentKa(currentKa)
-                    .sbLengthM(sbLength)
-                    .resistivity(resistivity)
-                    .lineLossKw(loss)
-                    .createTime(now)
-                    .build();
-            batch.add(detail);
-        }
+                BigDecimal totalPower = firstValue(f.getJSONArray("seoPs"));
+                if (totalPower == null || totalPower.compareTo(BigDecimal.ZERO) <= 0) {
+                    log.debug("馈线总功率缺失/为0, 跳过线损率: feederId={}", feederId);
+                    continue;
+                }
 
 
-        if (!batch.isEmpty()) {
-            // 防重:查该断面时刻已有的段(避免重复跑同一断面时叠加)
-            Set<String> existingPsrIds = new HashSet<>();
-            lineLossDetailMapper.selectList(
-                    new LambdaQueryWrapper<FhzgSeLineLossDetail>()
-                            .select(FhzgSeLineLossDetail::getPsrId)
-                            .eq(FhzgSeLineLossDetail::getSnapTime, snapTime)
-            ).forEach(e -> existingPsrIds.add(e.getPsrId()));
-
-            List<FhzgSeLineLossDetail> toInsert = new ArrayList<>();
-            for (FhzgSeLineLossDetail d : batch) {
-                if (existingPsrIds.contains(d.getPsrId())) continue;
-                toInsert.add(d);
+                BigDecimal transPower = transPowerMap.getOrDefault(feederId, BigDecimal.ZERO);
+                BigDecimal consumerPower = consumerPowerMap.getOrDefault(feederId, BigDecimal.ZERO);
+                BigDecimal lineLossPower = totalPower.subtract(transPower).subtract(consumerPower);
+                BigDecimal lineLossRate = lineLossPower.multiply(BigDecimal.valueOf(100))
+                        .divide(totalPower, 4, RoundingMode.HALF_UP);
+
+                String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
+                batch.add(FhzgSeLineLossRate.builder()
+                        .snapTime(snapTime)
+                        .feederId(feederId)
+                        .feederName(loc.length > 2 ? loc[2] : null)
+                        .subsId(loc.length > 0 ? loc[0] : "")
+                        .countyId(loc.length > 1 ? loc[1] : "")
+                        .totalPower(totalPower)
+                        .transPower(transPower)
+                        .consumerPower(consumerPower)
+                        .lineLossPower(lineLossPower)
+                        .lineLossRate(lineLossRate)
+                        .createTime(now)
+                        .build());
             }
             }
 
 
-            int inserted = 0;
-            if (!toInsert.isEmpty()) {
-                inserted = insertBatchSharded(rows -> lineLossDetailMapper.insertBatch(rows), toInsert);
+            if (!batch.isEmpty()) {
+                // 防重:查该断面时刻已有的馈线(避免重复跑同一断面时叠加)
+                Set<String> existingFeederIds = new HashSet<>();
+                lineLossRateMapper.selectList(
+                        new LambdaQueryWrapper<FhzgSeLineLossRate>()
+                                .select(FhzgSeLineLossRate::getFeederId)
+                                .eq(FhzgSeLineLossRate::getSnapTime, snapTime)
+                ).forEach(e -> existingFeederIds.add(e.getFeederId()));
+
+                List<FhzgSeLineLossRate> toInsert = new ArrayList<>();
+                for (FhzgSeLineLossRate d : batch) {
+                    if (existingFeederIds.contains(d.getFeederId())) continue;
+                    toInsert.add(d);
+                }
+
+                int inserted = 0;
+                if (!toInsert.isEmpty()) {
+                    inserted = insertBatchSharded(rows -> {
+                        toInsert.forEach(lineLossRateMapper::insert);
+                        return toInsert.size();
+                    }, toInsert);
+                }
+                log.info("馈线线损率计算完成 snapTime={}, 写入{}条, 跳过{}条(无功率{}条/重复{}条)",
+                        snapTime, inserted, batch.size() - inserted,
+                        batch.size() - toInsert.size(), existingFeederIds.size());
             }
             }
-            int skippedNoData = segments.size() - batch.size();
-            int skippedDup = batch.size() - toInsert.size();
-            log.info("线路线损计算完成 snapTime={}, 写入{}条, 跳过{}条(无数据{}条/重复{}条)",
-                    snapTime, inserted, skippedNoData + skippedDup, skippedNoData, skippedDup);
+        } catch (Exception e) {
+            log.warn("馈线线损率计算失败 snapTime={}", snapTime, e);
         }
         }
     }
     }
 
 
@@ -914,11 +903,11 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         int deleted = snapshotDetailMapper.delete(
         int deleted = snapshotDetailMapper.delete(
                 new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
                 new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
                         .eq(FhzgSeSnapshotDetail::getSnapshotTime, snapTime));
                         .eq(FhzgSeSnapshotDetail::getSnapshotTime, snapTime));
-        // 线损明细同步删除(否则防重会跳过插入,保留旧值导致与断面不一致)
-        int deletedLoss = lineLossDetailMapper.delete(
-                new LambdaQueryWrapper<FhzgSeLineLossDetail>()
-                        .eq(FhzgSeLineLossDetail::getSnapTime, snapTime));
-        log.info("状估覆盖更新 snapTime={}, 删除断面{}条, 删除线损{}条", snapTime, deleted, deletedLoss);
+        // 线损同步删除(否则防重会跳过插入,保留旧值导致与断面不一致)
+        int deletedLoss = lineLossRateMapper.delete(
+                new LambdaQueryWrapper<FhzgSeLineLossRate>()
+                        .eq(FhzgSeLineLossRate::getSnapTime, snapTime));
+        log.info("状估覆盖更新 snapTime={}, 删除断面{}条, 删除线损{}条", snapTime, deleted, deletedLoss);
 
 
         // 重新走补数链路写入(此时防重逻辑不会跳过任何记录)
         // 重新走补数链路写入(此时防重逻辑不会跳过任何记录)
         // 注意:覆盖更新用于补历史断面,只写数据不推状态机——防止状态机时间倒退、
         // 注意:覆盖更新用于补历史断面,只写数据不推状态机——防止状态机时间倒退、

+ 0 - 35
services/ruoyi-job/src/main/java/com/hdkj/lt/job/task/SeLineLossDailyTask.java

@@ -1,35 +0,0 @@
-package com.hdkj.lt.job.task;
-
-import com.hdkj.lt.bf.scheduler.SeLineLossDailyScheduler;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDate;
-
-/**
- * 线路线损日汇总定时任务(凌晨1点聚合昨天)
- * <p>
- * sys_job 配置:invokeTarget = seLineLossDaily.noParams(),cron = 0 0 1 * * ?
- *
- * @author lsl
- * @since 2026-08-05
- */
-@Slf4j
-@Component("seLineLossDaily")
-@RequiredArgsConstructor
-public class SeLineLossDailyTask {
-
-    private final SeLineLossDailyScheduler seLineLossDailyScheduler;
-
-    public void noParams() {
-        LocalDate yesterday = LocalDate.now().minusDays(1);
-        log.info("[线路线损日汇总] 开始处理 {} 数据", yesterday);
-        try {
-            String result = seLineLossDailyScheduler.aggregateForDate(yesterday);
-            log.info("[线路线损日汇总] {} {}", yesterday, result);
-        } catch (Exception e) {
-            log.error("[线路线损日汇总] {} 处理失败", yesterday, e);
-        }
-    }
-}