liuaini před 1 týdnem
rodič
revize
0569ce58ef
23 změnil soubory, kde provedl 1072 přidání a 1 odebrání
  1. 66 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeCapacityDaily.java
  2. 57 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeConsumerCountDaily.java
  3. 48 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeConsumerRelation.java
  4. 40 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeCurrentEvent.java
  5. 64 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossDaily.java
  6. 64 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeLineLossDetail.java
  7. 93 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeSnapshotDetail.java
  8. 52 0
      common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeVoltageEvent.java
  9. 7 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeCapacityDailyMapper.java
  10. 7 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeConsumerCountDailyMapper.java
  11. 9 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeConsumerRelationMapper.java
  12. 7 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeCurrentEventMapper.java
  13. 7 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDailyMapper.java
  14. 15 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeLineLossDetailMapper.java
  15. 15 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeSnapshotDetailMapper.java
  16. 7 0
      common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeVoltageEventMapper.java
  17. 165 0
      common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeCapacityDailyScheduler.java
  18. 129 0
      common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeConsumerCountDailyScheduler.java
  19. 132 0
      common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeLineLossDailyScheduler.java
  20. 46 0
      common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeVoltageCleanupScheduler.java
  21. 19 0
      common/common-se/src/main/resources/mapper/FhzgSeLineLossDetailMapper.xml
  22. 23 0
      common/common-se/src/main/resources/mapper/FhzgSeSnapshotDetailMapper.xml
  23. 0 1
      services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/controller/optimization/IndicatorController.java

+ 66 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeCapacityDaily.java

@@ -0,0 +1,66 @@
+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.Data;
+import lombok.Builder;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 供电能力日汇总
+ *
+ * @author lsl
+ * @since 2026-07-25
+ */
+@TableName("fhzg_se_capacity_daily")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeCapacityDaily 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;
+
+    /** 当天最大负载率% */
+    private BigDecimal maxLoadRate;
+
+    /** 当天平均负载率% */
+    private BigDecimal avgLoadRate;
+
+    /** 当日重载事件数(从事件表统计,一条事件算1) */
+    private Integer heavyCount;
+
+    /** 当日过载事件数(从事件表统计,一条事件算1) */
+    private Integer overloadCount;
+
+    /** 总断面数 */
+    private Integer totalSnapshots;
+
+    private java.time.LocalDateTime createTime;
+
+    private java.time.LocalDateTime updateTime;
+}

+ 57 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeConsumerCountDaily.java

@@ -0,0 +1,57 @@
+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.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 分层分级用户数量日表(IEC用户电压指数分母)
+ * <p>
+ * dim_level = county/substation/feeder 三档,每档一行
+ * 数据源:fhzg_se_consumer_relation,每天凌晨1点刷昨天
+ *
+ * @author lsl
+ * @since 2026-07-30
+ */
+@TableName("fhzg_se_consumer_count_daily")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeConsumerCountDaily implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 统计日期 */
+    private LocalDate statDate;
+
+    /** 维度级别:county/substation/feeder */
+    private String dimLevel;
+
+    /** 区县ID */
+    private String countyId;
+
+    /** 变电站ID */
+    private String subsId;
+
+    /** 馈线ID */
+    private String feederId;
+
+    /** 该维度下用户数 */
+    private Integer consumerCount;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+}

+ 48 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeConsumerRelation.java

@@ -0,0 +1,48 @@
+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.time.LocalDateTime;
+
+/**
+ * 用户与配变关系表(中台推入)
+ *
+ * @author lsl
+ * @since 2026-07-23
+ */
+@TableName("fhzg_se_consumer_relation")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeConsumerRelation implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户编号 */
+    private String custNo;
+
+    private String customerName;
+    private String mvtransName;
+    private String mvtransId;
+    private String countyId;
+    private String cityId;
+    private String feederId;
+    private String subsId;
+
+    /** 电压等级(0.4/10/20/6等) */
+    private String voltageLevel;
+
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 40 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeCurrentEvent.java

@@ -0,0 +1,40 @@
+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.LocalDateTime;
+
+@TableName("fhzg_se_current_event")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeCurrentEvent implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String feederId;
+    private String feederName;
+    private String alarmType;
+    private String countyId;
+    private String countyName;
+    private String subsId;
+    private LocalDateTime firstOverTime;
+    private LocalDateTime triggerTime;
+    private BigDecimal triggerLoadRate;
+    private BigDecimal maxLoadRate;
+    private BigDecimal lastLoadRate;
+    private Integer status;
+    private LocalDateTime recoverTime;
+    /** 重构方案状态:0未触发 1生成中 2有方案 3无方案 -1调用失败 */
+    private Integer reconStatus;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

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

@@ -0,0 +1,64 @@
+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;
+}

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

@@ -0,0 +1,64 @@
+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.LocalDateTime;
+
+/**
+ * 线路线损明细(逐段逐断面)
+ *
+ * @author lsl
+ * @since 2026-07-30
+ */
+@TableName("fhzg_se_line_loss_detail")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeLineLossDetail implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 断面时间 */
+    private LocalDateTime snapTime;
+
+    /** 馈线ID */
+    private String feederId;
+
+    /** 段ID */
+    private String psrId;
+
+    /** 段类型: dxd-导线段, 0201-电缆段 */
+    private String psrType;
+
+    /** 区县ID */
+    private String countyId;
+
+    /** 变电站ID */
+    private String subsId;
+
+    /** 状估电流 kA */
+    private BigDecimal currentKa;
+
+    /** 段长度 米 */
+    private BigDecimal sbLengthM;
+
+    /** 电阻率 Ω/km */
+    private BigDecimal resistivity;
+
+    /** 线损功率 kW */
+    private BigDecimal lineLossKw;
+
+    private LocalDateTime createTime;
+}

+ 93 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeSnapshotDetail.java

@@ -0,0 +1,93 @@
+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.Data;
+import lombok.Builder;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 状估断面明细
+ *
+ * @author lsl
+ * @since 2026-07-23
+ */
+@TableName("fhzg_se_snapshot_detail")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeSnapshotDetail implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 断面时间(15分精度) */
+    private LocalDateTime snapshotTime;
+
+    /** 设备psrId */
+    private String deviceId;
+
+    /** feeder/mvtrans/consumer */
+    private String deviceType;
+
+    /** 所属馈线 */
+    private String feederId;
+
+    /** 馈线名称 */
+    private String feederName;
+
+    /** 所属变电站(冗余) */
+    private String subsId;
+
+    /** 所属区县(冗余) */
+    private String countyId;
+
+    /** 所属配变(用户专用) */
+    private String mvtransId;
+
+    /** 用户编号(用户专用) */
+    private String consumerId;
+
+    private String consumerName;
+
+    /** seoPs 有功MW(馈线) */
+    private BigDecimal activePower;
+
+    /** seoQs 无功MVar(馈线) */
+    private BigDecimal reactivePower;
+
+    /** seoIs 电流A(馈线) */
+    private BigDecimal currentValue;
+
+    /** seoUs 中压侧电压kV(馈线/配变) */
+    private BigDecimal voltageValue;
+
+    /** 负载率%(馈线) */
+    private BigDecimal loadRate;
+
+    /** seiLvUas 低压A相kV(配变) */
+    private BigDecimal lvVoltageA;
+
+    /** 配变容量MW */
+    private BigDecimal capacity;
+
+    /** 电压比值(馈线/10, 配变/0.22, 用户/0.22) */
+    private BigDecimal voltageRatio;
+
+    /** 0正常 1重载 2过载(只馈线) */
+    private Integer currentStatus;
+
+    /** 0正常 1越上限 2越下限(馈线/配变) | 1一般越上 2严重越上 3一般越下 4严重越下(用户) */
+    private Integer voltageStatus;
+
+    private LocalDateTime createTime;
+}

+ 52 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/entity/FhzgSeVoltageEvent.java

@@ -0,0 +1,52 @@
+package com.hdkj.lt.bf.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+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.LocalDateTime;
+
+@TableName("fhzg_se_voltage_event")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FhzgSeVoltageEvent implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String deviceId;
+    private String deviceName;
+    private String deviceType;
+    private String alarmLevel;
+    private String alarmType;
+    private String feederId;
+    private String feederName;
+    private String subsId;
+    private String countyId;
+    private String countyName;
+    private String mvtransId;
+    private String consumerId;
+    private BigDecimal threshold;
+    private LocalDateTime firstOverTime;
+    private LocalDateTime triggerTime;
+    private BigDecimal triggerValue;
+    private BigDecimal voltageRangeMax;
+    private BigDecimal voltageRangeMin;
+    private Integer overUpperMvtrans;
+    private Integer overLowerMvtrans;
+    @TableField("`last_value`")
+    private BigDecimal lastValue;
+    private Integer status;
+    private LocalDateTime recoverTime;
+    /** 重构方案状态:0未触发 1生成中 2有方案 3无方案 -1调用失败 */
+    private Integer reconStatus;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

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

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

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

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

+ 9 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/mapper/FhzgSeConsumerRelationMapper.java

@@ -0,0 +1,9 @@
+package com.hdkj.lt.bf.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.hdkj.lt.bf.entity.FhzgSeConsumerRelation;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FhzgSeConsumerRelationMapper extends BaseMapper<FhzgSeConsumerRelation> {
+}

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

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

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

@@ -0,0 +1,7 @@
+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> {
+}

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

@@ -0,0 +1,15 @@
+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/FhzgSeSnapshotDetailMapper.java

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

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

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

+ 165 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeCapacityDailyScheduler.java

@@ -0,0 +1,165 @@
+package com.hdkj.lt.bf.scheduler;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.hdkj.lt.bf.entity.FhzgSeCapacityDaily;
+import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
+import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
+import com.hdkj.lt.bf.mapper.FhzgSeCapacityDailyMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 供电能力日汇总逻辑(公共模块)
+ * <p>
+ * 定时调度由 pwfhzg-job 通过 sys_job 配置触发(凌晨1点聚合昨天),
+ * 也可由 bf 手动端点 /indicator/capacity/manual 触发。
+ *
+ * @author lsl
+ * @since 2026-07-25
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SeCapacityDailyScheduler {
+
+    private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
+    private final FhzgSeCurrentEventMapper currentEventMapper;
+    private final FhzgSeCapacityDailyMapper capacityDailyMapper;
+
+    /**
+     * 聚合指定日期的断面/事件数据到日汇总表(手动触发时调用)
+     * <p>
+     * 负载率从断面明细聚合,重过载事件数从事件表统计(与事件表口径一致)
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public String aggregateForDate(LocalDate date) {
+        LocalDateTime startTime = date.atStartOfDay();
+        LocalDateTime endTime = date.atTime(23, 59, 59);
+
+        // 查当天所有馈线断面(负载率)
+        LambdaQueryWrapper<FhzgSeSnapshotDetail> wrapper = new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
+                .eq(FhzgSeSnapshotDetail::getDeviceType, "feeder")
+                .isNotNull(FhzgSeSnapshotDetail::getLoadRate)
+                .ge(FhzgSeSnapshotDetail::getSnapshotTime, startTime)
+                .le(FhzgSeSnapshotDetail::getSnapshotTime, endTime);
+
+        List<FhzgSeSnapshotDetail> snapshots = snapshotDetailMapper.selectList(wrapper);
+
+        // 查当天重过载事件(事件数,口径与 fhzg_se_current_event 一致)
+        LambdaQueryWrapper<FhzgSeCurrentEvent> eventWrapper = new LambdaQueryWrapper<FhzgSeCurrentEvent>()
+                .ge(FhzgSeCurrentEvent::getFirstOverTime, startTime)
+                .lt(FhzgSeCurrentEvent::getFirstOverTime, date.plusDays(1).atStartOfDay())
+                .in(FhzgSeCurrentEvent::getAlarmType, "current_heavy", "current_overload");
+        List<FhzgSeCurrentEvent> events = currentEventMapper.selectList(eventWrapper);
+
+        if (snapshots.isEmpty() && events.isEmpty()) {
+            log.info("[供电能力日汇总] {} 无断面/事件数据,跳过", date);
+            return date + " 无断面/事件数据";
+        }
+
+        // 按馈线分组聚合
+        Map<String, DailyAccumulator> accumulatorMap = new HashMap<>();
+        for (FhzgSeSnapshotDetail s : snapshots) {
+            String feederId = s.getFeederId();
+            if (feederId == null) continue;
+
+            DailyAccumulator acc = accumulatorMap.computeIfAbsent(feederId, k -> new DailyAccumulator());
+            acc.countyId = s.getCountyId();
+            acc.subsId = s.getSubsId();
+            if (acc.feederName == null) acc.feederName = s.getFeederName();
+
+            BigDecimal loadRate = s.getLoadRate();
+            if (loadRate != null) {
+                if (loadRate.compareTo(acc.maxLoadRate) > 0) {
+                    acc.maxLoadRate = loadRate;
+                }
+                acc.sumLoadRate = acc.sumLoadRate.add(loadRate);
+                acc.totalSnapshots++;
+            }
+        }
+
+        // 事件数按馈线累加(同一条馈线当日多个事件算多次)
+        for (FhzgSeCurrentEvent e : events) {
+            String feederId = e.getFeederId();
+            if (feederId == null) continue;
+
+            DailyAccumulator acc = accumulatorMap.computeIfAbsent(feederId, k -> new DailyAccumulator());
+            if (acc.countyId == null) acc.countyId = e.getCountyId();
+            if (acc.subsId == null) acc.subsId = e.getSubsId();
+            if (acc.feederName == null) acc.feederName = e.getFeederName();
+
+            if ("current_heavy".equals(e.getAlarmType())) acc.heavyCount++;
+            else if ("current_overload".equals(e.getAlarmType())) acc.overloadCount++;
+        }
+
+        // 先删当天已有数据(避免重复运行时叠加)
+        LambdaQueryWrapper<FhzgSeCapacityDaily> deleteWrapper = new LambdaQueryWrapper<FhzgSeCapacityDaily>()
+                .eq(FhzgSeCapacityDaily::getStatDate, date);
+        capacityDailyMapper.delete(deleteWrapper);
+
+        // 批量插入
+        List<FhzgSeCapacityDaily> dailyRecords = new ArrayList<>();
+        for (Map.Entry<String, DailyAccumulator> entry : accumulatorMap.entrySet()) {
+            String feederId = entry.getKey();
+            DailyAccumulator acc = entry.getValue();
+
+            BigDecimal avgLoadRate = acc.totalSnapshots > 0
+                    ? acc.sumLoadRate.divide(BigDecimal.valueOf(acc.totalSnapshots), 2, RoundingMode.HALF_UP)
+                    : BigDecimal.ZERO;
+
+            FhzgSeCapacityDaily daily = FhzgSeCapacityDaily.builder()
+                    .statDate(date)
+                    .feederId(feederId)
+                    .feederName(acc.feederName)
+                    .countyId(acc.countyId)
+                    .subsId(acc.subsId)
+                    .maxLoadRate(acc.maxLoadRate)
+                    .avgLoadRate(avgLoadRate)
+                    .heavyCount(acc.heavyCount)
+                    .overloadCount(acc.overloadCount)
+                    .totalSnapshots(acc.totalSnapshots)
+                    .createTime(LocalDateTime.now())
+                    .updateTime(LocalDateTime.now())
+                    .build();
+            dailyRecords.add(daily);
+        }
+
+        // 批量插入
+        for (FhzgSeCapacityDaily record : dailyRecords) {
+            capacityDailyMapper.insert(record);
+        }
+
+        String result = date + " 处理完成: " + dailyRecords.size() + " 条馈线日汇总";
+        log.info("[供电能力日汇总] {}", result);
+        return result;
+    }
+
+    /**
+     * 日聚合累加器
+     */
+    private static class DailyAccumulator {
+        String countyId;
+        String subsId;
+        String feederName;
+        BigDecimal maxLoadRate = BigDecimal.ZERO;
+        BigDecimal sumLoadRate = BigDecimal.ZERO;
+        /** 当日重载事件数(从事件表统计) */
+        int heavyCount = 0;
+        /** 当日过载事件数(从事件表统计) */
+        int overloadCount = 0;
+        int totalSnapshots = 0;
+    }
+}

+ 129 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeConsumerCountDailyScheduler.java

@@ -0,0 +1,129 @@
+package com.hdkj.lt.bf.scheduler;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.hdkj.lt.bf.entity.FhzgSeConsumerCountDaily;
+import com.hdkj.lt.bf.entity.FhzgSeConsumerRelation;
+import com.hdkj.lt.bf.mapper.FhzgSeConsumerCountDailyMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeConsumerRelationMapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 分层分级用户数量日汇总逻辑(IEC用户电压指数分母)(公共模块)
+ * <p>
+ * 定时调度由 pwfhzg-job 通过 sys_job 配置触发(凌晨1点聚合昨天),
+ * 也可由 bf 手动端点 /indicator/consumer-count/manual 触发。
+ *
+ * @author lsl
+ * @since 2026-07-25
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SeConsumerCountDailyScheduler {
+
+    private final FhzgSeConsumerRelationMapper consumerRelationMapper;
+    private final FhzgSeConsumerCountDailyMapper consumerCountDailyMapper;
+
+    /** 维度级别常量 */
+    private static final String LEVEL_COUNTY = "county";
+    private static final String LEVEL_SUBSTATION = "substation";
+    private static final String LEVEL_FEEDER = "feeder";
+
+    /**
+     * 聚合指定日期的分层分级用户数(手动触发时调用)
+     * <p>
+     * 三档独立聚合(SQL GROUP BY,不加载明细到内存):
+     * county 档 = county_id 非空按区县分组;
+     * substation 档 = county_id+subs_id 非空按变电站分组;
+     * feeder 档 = 三级id均非空按馈线分组。
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public String aggregateForDate(LocalDate date) {
+        List<FhzgSeConsumerCountDaily> records = new ArrayList<>();
+
+        // 区县档
+        records.addAll(aggregateLevel(date, LEVEL_COUNTY));
+
+        // 变电站档
+        records.addAll(aggregateLevel(date, LEVEL_SUBSTATION));
+
+        // 馈线档
+        records.addAll(aggregateLevel(date, LEVEL_FEEDER));
+
+        if (records.isEmpty()) {
+            log.info("[用户数量日汇总] {} 无用户关系数据,跳过", date);
+            return date + " 无用户关系数据";
+        }
+
+        // 先删当天已有数据(幂等)
+        LambdaQueryWrapper<FhzgSeConsumerCountDaily> deleteWrapper =
+                new LambdaQueryWrapper<FhzgSeConsumerCountDaily>()
+                        .eq(FhzgSeConsumerCountDaily::getStatDate, date);
+        consumerCountDailyMapper.delete(deleteWrapper);
+
+        // 批量插入
+        for (FhzgSeConsumerCountDaily record : records) {
+            consumerCountDailyMapper.insert(record);
+        }
+
+        String result = date + " 处理完成: " + records.size() + " 行 (county " + countByLevel(records, LEVEL_COUNTY)
+                + " + substation " + countByLevel(records, LEVEL_SUBSTATION)
+                + " + feeder " + countByLevel(records, LEVEL_FEEDER) + ")";
+        log.info("[用户数量日汇总] {}", result);
+        return result;
+    }
+
+    /**
+     * 按指定维度档聚合用户数(一条 SQL GROUP BY)
+     */
+    private List<FhzgSeConsumerCountDaily> aggregateLevel(LocalDate date, String level) {
+        QueryWrapper<FhzgSeConsumerRelation> qw = new QueryWrapper<>();
+        qw.select("county_id", "subs_id", "feeder_id", "COUNT(*) AS cnt")
+                .isNotNull("county_id")
+                .groupBy("county_id");
+
+        if (LEVEL_SUBSTATION.equals(level)) {
+            qw.isNotNull("subs_id")
+                    .groupBy("county_id", "subs_id");
+        } else if (LEVEL_FEEDER.equals(level)) {
+            qw.isNotNull("subs_id")
+                    .isNotNull("feeder_id")
+                    .groupBy("county_id", "subs_id", "feeder_id");
+        }
+
+        List<java.util.Map<String, Object>> rows = consumerRelationMapper.selectMaps(qw);
+
+        List<FhzgSeConsumerCountDaily> records = new ArrayList<>();
+        for (java.util.Map<String, Object> row : rows) {
+            String countyId = (String) row.get("county_id");
+            String subsId = (String) row.get("subs_id");
+            String feederId = (String) row.get("feeder_id");
+            Number cnt = (Number) row.getOrDefault("cnt", 0);
+
+            records.add(FhzgSeConsumerCountDaily.builder()
+                    .statDate(date)
+                    .dimLevel(level)
+                    .countyId(countyId)
+                    .subsId(subsId)
+                    .feederId(feederId)
+                    .consumerCount(cnt.intValue())
+                    .createTime(LocalDateTime.now())
+                    .updateTime(LocalDateTime.now())
+                    .build());
+        }
+        return records;
+    }
+
+    private long countByLevel(List<FhzgSeConsumerCountDaily> records, String level) {
+        return records.stream().filter(r -> level.equals(r.getDimLevel())).count();
+    }
+}

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

@@ -0,0 +1,132 @@
+package com.hdkj.lt.bf.scheduler;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.HashMap;
+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);
+
+        // 查当天所有线损明细
+        LambdaQueryWrapper<FhzgSeLineLossDetail> wrapper = new LambdaQueryWrapper<FhzgSeLineLossDetail>()
+                .ge(FhzgSeLineLossDetail::getSnapTime, startTime)
+                .le(FhzgSeLineLossDetail::getSnapTime, endTime);
+
+        List<FhzgSeLineLossDetail> details = lineLossDetailMapper.selectList(wrapper);
+
+        if (details.isEmpty()) {
+            log.info("[线路线损日汇总] {} 无线损明细数据,跳过", date);
+            return date + " 无线损明细数据";
+        }
+
+        // 按馈线分组聚合
+        Map<String, DailyAccumulator> accumulatorMap = new HashMap<>();
+        for (FhzgSeLineLossDetail d : details) {
+            String feederId = d.getFeederId();
+            if (feederId == null) continue;
+
+            DailyAccumulator acc = accumulatorMap.computeIfAbsent(feederId, k -> new DailyAccumulator());
+            acc.countyId = d.getCountyId();
+            acc.subsId = d.getSubsId();
+
+            BigDecimal loss = d.getLineLossKw();
+            if (loss != null) {
+                // 线损电量 kWh = 线损功率 kW × 断面间隔(15min = 0.25h)
+                BigDecimal energy = loss.multiply(BigDecimal.valueOf(0.25));
+                acc.sumKwh = acc.sumKwh.add(energy);
+                if (loss.compareTo(acc.maxKw) > 0) {
+                    acc.maxKw = loss;
+                }
+                acc.sumKw = acc.sumKw.add(loss);
+                acc.count++;
+            }
+        }
+
+        // 先删当天已有数据(避免重复运行时叠加)
+        LambdaQueryWrapper<FhzgSeLineLossDaily> deleteWrapper = new LambdaQueryWrapper<FhzgSeLineLossDaily>()
+                .eq(FhzgSeLineLossDaily::getStatDate, date);
+        lineLossDailyMapper.delete(deleteWrapper);
+
+        // 构建日汇总记录
+        List<FhzgSeLineLossDaily> dailyRecords = new ArrayList<>();
+        for (Map.Entry<String, DailyAccumulator> entry : accumulatorMap.entrySet()) {
+            DailyAccumulator acc = entry.getValue();
+
+            BigDecimal avgKw = acc.count > 0
+                    ? acc.sumKw.divide(BigDecimal.valueOf(acc.count), 4, RoundingMode.HALF_UP)
+                    : BigDecimal.ZERO;
+
+            FhzgSeLineLossDaily daily = FhzgSeLineLossDaily.builder()
+                    .statDate(date)
+                    .feederId(entry.getKey())
+                    .feederName(null)
+                    .countyId(acc.countyId)
+                    .subsId(acc.subsId)
+                    .lineLossKwh(acc.sumKwh.setScale(2, RoundingMode.HALF_UP))
+                    .maxLineLossKw(acc.maxKw.compareTo(BigDecimal.ZERO) > 0 ? acc.maxKw.setScale(4, RoundingMode.HALF_UP) : BigDecimal.ZERO)
+                    .avgLineLossKw(avgKw.setScale(4, RoundingMode.HALF_UP))
+                    .totalSnapshots(acc.count)
+                    .createTime(LocalDateTime.now())
+                    .updateTime(LocalDateTime.now())
+                    .build();
+            dailyRecords.add(daily);
+        }
+
+        for (FhzgSeLineLossDaily record : dailyRecords) {
+            lineLossDailyMapper.insert(record);
+        }
+
+        String result = date + " 处理完成: " + dailyRecords.size() + " 条馈线线损日汇总";
+        log.info("[线路线损日汇总] {}", result);
+        return result;
+    }
+
+    /**
+     * 日聚合累加器
+     */
+    private static class DailyAccumulator {
+        String countyId;
+        String subsId;
+        BigDecimal sumKwh = BigDecimal.ZERO;
+        BigDecimal maxKw = BigDecimal.ZERO;
+        BigDecimal sumKw = BigDecimal.ZERO;
+        int count = 0;
+    }
+}

+ 46 - 0
common/common-se/src/main/java/com/hdkj/lt/bf/scheduler/SeVoltageCleanupScheduler.java

@@ -0,0 +1,46 @@
+package com.hdkj.lt.bf.scheduler;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.hdkj.lt.bf.entity.FhzgSeVoltageEvent;
+import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * 电压事件兜底清扫逻辑(公共模块)
+ * <p>
+ * 关闭超过24小时仍未恢复的活跃事件,防止因断档/数据缺失导致的事件失联。
+ * 正常恢复由断面处理时的状态机走,此任务仅做兜底。
+ * 定时调度由 pwfhzg-job 通过 sys_job 配置触发(凌晨3点)。
+ *
+ * @author lsl
+ * @since 2026-07-27
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SeVoltageCleanupScheduler {
+
+    private final FhzgSeVoltageEventMapper voltageEventMapper;
+
+    /**
+     * 清扫超过24小时未恢复的电压事件(手动触发时调用)
+     */
+    public int cleanupStaleVoltageEvents() {
+        LocalDateTime threshold = LocalDateTime.now().minusHours(24);
+        FhzgSeVoltageEvent update = new FhzgSeVoltageEvent();
+        update.setStatus(2);
+        update.setRecoverTime(LocalDateTime.now());
+        int updated = voltageEventMapper.update(update,
+                new LambdaQueryWrapper<FhzgSeVoltageEvent>()
+                        .eq(FhzgSeVoltageEvent::getStatus, 1)
+                        .lt(FhzgSeVoltageEvent::getTriggerTime, threshold));
+        if (updated > 0) {
+            log.info("[电压事件清扫] 关闭 {} 条超过24h未恢复的电压事件(triggerTime < {})", updated, threshold);
+        }
+        return updated;
+    }
+}

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

@@ -0,0 +1,19 @@
+<?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>

+ 23 - 0
common/common-se/src/main/resources/mapper/FhzgSeSnapshotDetailMapper.xml

@@ -0,0 +1,23 @@
+<?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.FhzgSeSnapshotDetailMapper">
+
+    <!-- 批量插入断面明细 -->
+    <insert id="insertBatch">
+        INSERT INTO fhzg_se_snapshot_detail (
+            snapshot_time, device_id, device_type, feeder_id, feeder_name,
+            subs_id, county_id, mvtrans_id, consumer_id, consumer_name,
+            active_power, reactive_power, current_value, voltage_value, load_rate,
+            lv_voltage_a, capacity, voltage_ratio, current_status, voltage_status, create_time
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.snapshotTime}, #{item.deviceId}, #{item.deviceType}, #{item.feederId}, #{item.feederName},
+            #{item.subsId}, #{item.countyId}, #{item.mvtransId}, #{item.consumerId}, #{item.consumerName},
+            #{item.activePower}, #{item.reactivePower}, #{item.currentValue}, #{item.voltageValue}, #{item.loadRate},
+            #{item.lvVoltageA}, #{item.capacity}, #{item.voltageRatio}, #{item.currentStatus}, #{item.voltageStatus}, #{item.createTime}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

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

@@ -31,7 +31,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.time.LocalDate;
-import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;