Browse Source

伍继2.0第一次代码提交:总能耗界面接口

fuyuchuan 10 hours ago
parent
commit
9f510c3662
47 changed files with 3563 additions and 45 deletions
  1. 22 0
      fiveep-common/fiveep-common-core/src/main/java/com/bizmatics/common/core/util/ZeroBigDecimalSerializer.java
  2. 62 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/ElectricityRateConfigController.java
  3. 21 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/ElectricityTimePriceController.java
  4. 47 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/HtAnalogDataController.java
  5. 8 5
      fiveep-controller/src/main/java/com/bizmatics/controller/web/MybatisGeneratorUtils.java
  6. 22 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteElectricityRecordController.java
  7. 21 0
      fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteInformationController.java
  8. 1 1
      fiveep-controller/src/main/resources/application-dev.properties
  9. 4 3
      fiveep-controller/src/main/resources/application-test.properties
  10. 131 0
      fiveep-model/src/main/java/com/bizmatics/model/ElectricityRateConfig.java
  11. 110 0
      fiveep-model/src/main/java/com/bizmatics/model/ElectricityTimePrice.java
  12. 3 1
      fiveep-model/src/main/java/com/bizmatics/model/HtAnalog173Data.java
  13. 4 2
      fiveep-model/src/main/java/com/bizmatics/model/HtAnalogData.java
  14. 3 3
      fiveep-model/src/main/java/com/bizmatics/model/SiteDynamicProperties.java
  15. 74 0
      fiveep-model/src/main/java/com/bizmatics/model/SiteElectricityRecord.java
  16. 98 0
      fiveep-model/src/main/java/com/bizmatics/model/SiteInformation.java
  17. 121 0
      fiveep-model/src/main/java/com/bizmatics/model/utils/RatioCalculator.java
  18. 24 0
      fiveep-model/src/main/java/com/bizmatics/model/vo/ElectricityAdditionalPriceVo.java
  19. 127 0
      fiveep-model/src/main/java/com/bizmatics/model/vo/ElectricityRateTimeConfigVo.java
  20. 194 0
      fiveep-model/src/main/java/com/bizmatics/model/vo/HtAnalogEnergyConsumptionVo.java
  21. 52 0
      fiveep-model/src/main/java/com/bizmatics/model/vo/HtAnalogEnergySegmentedVo.java
  22. 16 0
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/ElectricityRateConfigMapper.java
  23. 16 0
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/ElectricityTimePriceMapper.java
  24. 18 0
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/SiteElectricityRecordMapper.java
  25. 18 0
      fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/SiteInformationMapper.java
  26. 28 0
      fiveep-persistence/src/main/resources/mapper/mysql/ElectricityRateConfigMapper.xml
  27. 23 0
      fiveep-persistence/src/main/resources/mapper/mysql/ElectricityTimePriceMapper.xml
  28. 18 0
      fiveep-persistence/src/main/resources/mapper/mysql/SiteElectricityRecordMapper.xml
  29. 23 0
      fiveep-persistence/src/main/resources/mapper/mysql/SiteInformationMapper.xml
  30. 25 0
      fiveep-service/src/main/java/com/bizmatics/service/ElectricityRateConfigService.java
  31. 16 0
      fiveep-service/src/main/java/com/bizmatics/service/ElectricityTimePriceService.java
  32. 26 3
      fiveep-service/src/main/java/com/bizmatics/service/HtAnalogDataService.java
  33. 2 0
      fiveep-service/src/main/java/com/bizmatics/service/SiteDynamicPropertiesService.java
  34. 16 0
      fiveep-service/src/main/java/com/bizmatics/service/SiteElectricityRecordService.java
  35. 16 0
      fiveep-service/src/main/java/com/bizmatics/service/SiteInformationService.java
  36. 1 0
      fiveep-service/src/main/java/com/bizmatics/service/config/SecurityConfig.java
  37. 196 0
      fiveep-service/src/main/java/com/bizmatics/service/impl/ElectricityRateConfigServiceImpl.java
  38. 20 0
      fiveep-service/src/main/java/com/bizmatics/service/impl/ElectricityTimePriceServiceImpl.java
  39. 1561 14
      fiveep-service/src/main/java/com/bizmatics/service/impl/HtAnalogDataServiceImpl.java
  40. 13 0
      fiveep-service/src/main/java/com/bizmatics/service/impl/SiteDynamicPropertiesServiceImpl.java
  41. 20 0
      fiveep-service/src/main/java/com/bizmatics/service/impl/SiteElectricityRecordServiceImpl.java
  42. 20 0
      fiveep-service/src/main/java/com/bizmatics/service/impl/SiteInformationServiceImpl.java
  43. 1 1
      fiveep-service/src/main/java/com/bizmatics/service/impl/SiteServiceImpl.java
  44. 20 10
      fiveep-service/src/main/java/com/bizmatics/service/interceptor/CheckExecuteInterceptor.java
  45. 1 1
      fiveep-service/src/main/java/com/bizmatics/service/job/RatAnalogTask.java
  46. 298 0
      fiveep-service/src/main/java/com/bizmatics/service/job/SiteDailyElectricityCostTask.java
  47. 2 1
      fiveep-service/src/main/java/com/bizmatics/service/vo/SiteLoadAnalysisVO.java

+ 22 - 0
fiveep-common/fiveep-common-core/src/main/java/com/bizmatics/common/core/util/ZeroBigDecimalSerializer.java

@@ -0,0 +1,22 @@
+package com.bizmatics.common.core.util;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/8/12
+ */
+public class ZeroBigDecimalSerializer extends JsonSerializer<Object> {
+    @Override
+    public void serialize(Object value, JsonGenerator gen,
+                          SerializerProvider serializers) throws IOException {
+        gen.writeNumber(BigDecimal.ZERO);
+    }
+}

+ 62 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/ElectricityRateConfigController.java

@@ -0,0 +1,62 @@
+package com.bizmatics.controller.web;
+
+import com.bizmatics.common.core.bean.ApiResult;
+import com.bizmatics.model.vo.ElectricityRateTimeConfigVo;
+import com.bizmatics.service.ElectricityRateConfigService;
+import com.bizmatics.service.aop.BusinessType;
+import com.bizmatics.service.aop.Log;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * <p>
+ * 费率配置表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@RestController
+@RequestMapping("/electricityRateConfig")
+public class ElectricityRateConfigController {
+
+    @Autowired
+    private ElectricityRateConfigService rateConfigService;
+
+    /**
+     * 新增费率配置数据
+     */
+    @Log(title = "新增费率配置数据", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public ApiResult<Integer> add(@Validated @RequestBody ElectricityRateTimeConfigVo vo) {
+        return ApiResult.success(rateConfigService.add(vo));
+    }
+
+    /**
+     * 修改费率配置数据
+     */
+    @Log(title = "修改费率配置数据", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    public ApiResult<Integer> update(@Validated @RequestBody ElectricityRateTimeConfigVo vo) {
+        return ApiResult.success(rateConfigService.update(vo));
+    }
+
+    /**
+     * 删除费率配置数据
+     */
+    @Log(title = "删除费率配置数据", businessType = BusinessType.DELETE)
+    @DeleteMapping("/del/{siteId}")
+    public ApiResult<Integer> del(@PathVariable Integer siteId) {
+        return ApiResult.success(rateConfigService.del(siteId));
+    }
+
+    /**
+     * 查询费率配置数据
+     */
+    @GetMapping("/queryConfig")
+    public ApiResult<ElectricityRateTimeConfigVo> queryConfig(@RequestParam Integer siteId) {
+        return ApiResult.success(rateConfigService.queryConfig(siteId));
+    }
+}
+

+ 21 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/ElectricityTimePriceController.java

@@ -0,0 +1,21 @@
+package com.bizmatics.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 时段电价表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@Controller
+@RequestMapping("/electricityTimePrice")
+public class ElectricityTimePriceController {
+
+}
+

+ 47 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/HtAnalogDataController.java

@@ -3,6 +3,8 @@ package com.bizmatics.controller.web;
 
 import com.bizmatics.common.core.bean.ApiResult;
 import com.bizmatics.model.vo.DataManagementOneVO;
+import com.bizmatics.model.vo.HtAnalogEnergyConsumptionVo;
+import com.bizmatics.model.vo.HtAnalogEnergySegmentedVo;
 import com.bizmatics.model.vo.SingleLoopReportOneVo;
 import com.bizmatics.service.HtAnalogDataService;
 import com.bizmatics.service.aop.BusinessType;
@@ -11,9 +13,15 @@ import com.bizmatics.service.vo.CommonIcoVO;
 import com.bizmatics.service.vo.HadCountVO;
 import com.bizmatics.service.vo.RealScoreVO;
 import com.bizmatics.service.vo.TimeShareVO;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.List;
 
@@ -212,5 +220,44 @@ public class HtAnalogDataController {
                                                                  @RequestParam int type) {
         return ApiResult.success(htAnalogDataService.SingleLoopReportData(deviceCode, time, type));
     }
+
+    /**
+     * 总能耗-头部数据
+     *
+     * @param siteId 站点id
+     * @param consume 1.用量 2.费用
+     * @param time 时间
+     * @param timeType 时间类型 overview 概览,day 日,month 月,year 年
+     * @return 响应
+     */
+    @GetMapping("getEnergyConsumption")
+    public ApiResult<HtAnalogEnergyConsumptionVo> getEnergyConsumption(@RequestParam Integer siteId,
+                                                                       @RequestParam(required = false, defaultValue = "1") Integer consume,
+                                                                       @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time,
+                                                                       @RequestParam(required = false, defaultValue = "overview") String timeType) {
+        if (time == null) {
+            time = LocalDateTime.now();
+        }
+        return ApiResult.success(htAnalogDataService.getEnergyConsumption(siteId, consume, time, timeType));
+    }
+
+    /**
+     * 获取分段数据
+     * @param siteId 站点id
+     * @param queryPeriod 查询周期 day month year
+     * @param queryTime 查询时间
+     * @param queryType  查询类型(energy 能耗;electric 电;cost 费用)
+     * @return 分段图表数据
+     */
+    @GetMapping("getSegmentedData")
+    public ApiResult<HtAnalogEnergySegmentedVo> getSegmentedData(@RequestParam Integer siteId,
+                                                                 @RequestParam(required = false) String queryPeriod,
+                                                                 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime queryTime,
+                                                                 @RequestParam(required = false, defaultValue = "energy") String queryType) {
+        if (StringUtils.isEmpty(queryPeriod)) {
+            queryPeriod = "day";
+        }
+        return ApiResult.success(htAnalogDataService.getSegmentedData(siteId, queryPeriod, queryTime, queryType));
+    }
 }
 

+ 8 - 5
fiveep-controller/src/main/java/com/bizmatics/controller/web/MybatisGeneratorUtils.java

@@ -33,7 +33,7 @@ public class MybatisGeneratorUtils {
         String path = file.getAbsolutePath();
         gc.setOutputDir(path + "/src/main/java");  //生成路径(一般都是生成在此项目的src/main/java下面)
         //修改为自己的名字
-        gc.setAuthor("ya"); //设置作者
+        gc.setAuthor("fu"); //设置作者
         gc.setOpen(false);
         gc.setFileOverride(true); //第二次生成会把第一次生成的覆盖掉
         gc.setServiceName("%sService"); //生成的service接口名字首字母是否为I,这样设置就没有
@@ -43,10 +43,13 @@ public class MybatisGeneratorUtils {
         //2、数据源配置
         //修改数据源
         DataSourceConfig dsc = new DataSourceConfig();
-        dsc.setUrl("jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
+        dsc.setUrl("jdbc:mysql://192.168.10.165:3306/usky-electricity?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
+        // dsc.setUrl("jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
         dsc.setDriverName("com.mysql.cj.jdbc.Driver");
-        dsc.setUsername("usky");
-        dsc.setPassword("Yt#75Usky");
+        //dsc.setUsername("usky");
+        dsc.setUsername("root");
+        //dsc.setPassword("Yt#75Usky");
+        dsc.setPassword("yt123456");
         mpg.setDataSource(dsc);
 
         // 3、包配置
@@ -70,7 +73,7 @@ public class MybatisGeneratorUtils {
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombok
         //修改自己想要生成的表
-        strategy.setInclude(new String[]{"bulletin_alarm_config"});  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude(new String[]{"site_electricity_record"});  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 22 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteElectricityRecordController.java

@@ -0,0 +1,22 @@
+package com.bizmatics.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 站点电费记录表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2025-08-21
+ */
+@RestController
+@RequestMapping("/siteElectricityRecord")
+public class SiteElectricityRecordController {
+
+}
+

+ 21 - 0
fiveep-controller/src/main/java/com/bizmatics/controller/web/SiteInformationController.java

@@ -0,0 +1,21 @@
+package com.bizmatics.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-17
+ */
+@Controller
+@RequestMapping("/siteInformation")
+public class SiteInformationController {
+
+}
+

+ 1 - 1
fiveep-controller/src/main/resources/application-dev.properties

@@ -19,7 +19,7 @@ spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDa
 spring.datasource.dynamic.primary=usky-power
 #spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
 #spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://dianli.usky.cn:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
-spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
+#spring.datasource.dynamic.datasource.usky-power.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
 spring.datasource.dynamic.datasource.usky-power.username=usky
 spring.datasource.dynamic.datasource.usky-power.password=Yt#75Usky
 spring.datasource.dynamic.druid.initial-size=5                                                                       

+ 4 - 3
fiveep-controller/src/main/resources/application-test.properties

@@ -15,13 +15,14 @@ mybatis-plus.configuration.defaultStatementTimeout=3
 mybatis.refresh.enabled=true
 mybatis.refresh.delay-seconds=10
 mybatis.refresh.sleep-seconds=20
+mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
 # datasource
 spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
 spring.datasource.dynamic.primary=mast
-#spring.datasource.dynamic.datasource.mast.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
+#spring.datasource.dynamic.datasource.mast.url=jdbc:mysql://usky-cloud-mysql:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
 #spring.datasource.dynamic.datasource.mast.username=root
 #spring.datasource.dynamic.datasource.mast.password=yt123456
-spring.datasource.dynamic.datasource.mast.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
+spring.datasource.dynamic.datasource.mast.url=jdbc:mysql://101.133.214.75:3306/usky-electricity?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
 spring.datasource.dynamic.datasource.mast.username=usky
 spring.datasource.dynamic.datasource.mast.password=Yt#75Usky
 spring.datasource.dynamic.druid.initial-size=5
@@ -56,7 +57,7 @@ spring.datasource.druid.filter.slf4j.result-set-open-after-log-enabled=false
 spring.datasource.druid.filter.slf4j.result-set-close-after-log-enabled=false
 # jackson
 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
-spring.jackson.time-zone=GMT+0
+spring.jackson.time-zone=GMT+8
 spring.jackson.default-property-inclusion=always
 #spring.jackson.serialization.indent_output=true
 spring.jackson.serialization.fail-on-empty-beans=false

+ 131 - 0
fiveep-model/src/main/java/com/bizmatics/model/ElectricityRateConfig.java

@@ -0,0 +1,131 @@
+package com.bizmatics.model;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 费率配置表
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class ElectricityRateConfig implements Serializable {
+
+    private static final long serialVersionUID=1L;
+
+    /**
+     * 费率配置主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 月账单结算日(范围1-31)
+     */
+    private Integer billingDay;
+
+    /**
+     * 站点ID,外键
+     */
+    private Integer siteId;
+
+    /**
+     * 电表设备ID,外键
+     */
+    private String deviceCode;
+
+    /**
+     * 计费方式
+     * 1 按合同最大需量, 2 按实际最大需量, 3 按变压器容量
+     * 默认 2
+     */
+    private Integer billingMethod;
+
+    /**
+     * 需量电价(元/千瓦)
+     */
+    private BigDecimal demandPrice;
+
+    /**
+     * 申报需量(kW)
+     */
+    private BigDecimal declaredDemand;
+
+    /**
+     * 超过的百分比
+     */
+    private BigDecimal excessPercentage;
+
+    /**
+     * 超过部分的计费倍数
+     */
+    private BigDecimal additionalMultiplier;
+
+    /**
+     * 容量电价(元/千伏安)
+     */
+    private BigDecimal capacityPrice;
+
+    /**
+     * 额定容量(kVA)
+     */
+    private BigDecimal ratedCapacity;
+
+    /**
+     * 功率因数标准值
+     */
+    private BigDecimal powerFactorStandard;
+
+    /**
+     * 功率因数报警值
+     */
+    private BigDecimal powerFactorAlert;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private String creator;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime updateTime;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+
+}

+ 110 - 0
fiveep-model/src/main/java/com/bizmatics/model/ElectricityTimePrice.java

@@ -0,0 +1,110 @@
+package com.bizmatics.model;
+
+import java.math.BigDecimal;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 时段电价表
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class ElectricityTimePrice implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 时段电价表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 有效月份(范围1-12)
+     */
+    private Integer month;
+
+    /**
+     * 电价类型
+     * 1 分时电价,2 不分时电价
+     */
+    private Integer priceType;
+
+    /**
+     *电价 (分时则分尖峰平谷,不分时则为全天电价)
+     */
+    private BigDecimal electricityPrice;
+
+    /**
+     * 开始时间
+     */
+    @JsonFormat(pattern = "HH:mm:ss")
+    private LocalTime startTime;
+
+    /**
+     * 结束时间
+     */
+    @JsonFormat(pattern = "HH:mm:ss")
+    private LocalTime endTime;
+
+    /**
+     * 峰谷属性
+     * 1 尖, 2 峰, 3 平,4 谷
+     */
+    private Integer peakValleyAttribute;
+
+    /**
+     * 站点ID
+     */
+    private Integer siteId;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime updateTime;
+
+
+    /**
+     * 创建人
+     */
+    private String creator;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+}

+ 3 - 1
fiveep-model/src/main/java/com/bizmatics/model/HtAnalog173Data.java

@@ -1,6 +1,8 @@
 package com.bizmatics.model;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import com.baomidou.mybatisplus.annotation.TableId;
 import java.time.LocalDateTime;
@@ -101,7 +103,7 @@ public class HtAnalog173Data implements Serializable {
      * 三相正向有功电度
      */
     @TableField("Epp")
-    private Double Epp;
+    private BigDecimal Epp;
 
     /**
      * 尖段正向有功电度

+ 4 - 2
fiveep-model/src/main/java/com/bizmatics/model/HtAnalogData.java

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableField;
 import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Date;
 
 import lombok.Data;
@@ -119,7 +121,7 @@ public class HtAnalogData implements Serializable {
      * 三相正向有功电度
      */
     @TableField("Epp")
-    private Double Epp;
+    private BigDecimal Epp;
 
     /**
      * 尖段正向有功电度
@@ -407,7 +409,7 @@ public class HtAnalogData implements Serializable {
      * 上报时间
      */
     @TableField("dataTime")
-    private Date dataTime;
+    private LocalDateTime dataTime;
 
 
     @TableField(exist = false)

+ 3 - 3
fiveep-model/src/main/java/com/bizmatics/model/SiteDynamicProperties.java

@@ -23,7 +23,7 @@ import lombok.experimental.Accessors;
 @Accessors(chain = true)
 public class SiteDynamicProperties implements Serializable {
 
-    private static final long serialVersionUID=1L;
+    private static final long serialVersionUID = 1L;
 
     /**
      * 站点动态属性id
@@ -97,13 +97,13 @@ public class SiteDynamicProperties implements Serializable {
     private String voltageLevel;
 
     /**
-     * 拆标准煤:1\电力(等价)、2\电力(当量)
+     * 拆标准煤:1 电力(等价)、2 电力(当量),默认1
      */
     @TableField("demolition_standard_coal")
     private Integer demolitionStandardCoal;
 
     /**
-     * 拆标准煤
+     * 转换系数(吨标准煤/千瓦时),默认 0.000404
      */
     @TableField("demolition_standard_coal1")
     private String demolitionStandardCoal1;

+ 74 - 0
fiveep-model/src/main/java/com/bizmatics/model/SiteElectricityRecord.java

@@ -0,0 +1,74 @@
+package com.bizmatics.model;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import java.time.LocalDate;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 站点电费记录表
+ * </p>
+ *
+ * @author fu
+ * @since 2025-08-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class SiteElectricityRecord implements Serializable {
+
+    private static final long serialVersionUID=1L;
+
+    /**
+     * 站点日电费记录表ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 站点ID
+     */
+    private Integer siteId;
+
+    /**
+     * 设备编号
+     */
+    private String deviceCode;
+
+    /**
+     * 计费日期
+     */
+    private LocalDate date;
+
+    /**
+     * 峰时费用(元)
+     */
+    private BigDecimal peakCost;
+
+    /**
+     * 平时费用(元)
+     */
+    private BigDecimal flatCost;
+
+    /**
+     * 谷时费用(元)
+     */
+    private BigDecimal valleyCost;
+
+    /**
+     * 尖峰费用(元)
+     */
+    private BigDecimal sharpPeakCost;
+
+    /**
+     * 总费用(元)
+     */
+    private BigDecimal totalCost;
+
+
+}

+ 98 - 0
fiveep-model/src/main/java/com/bizmatics/model/SiteInformation.java

@@ -0,0 +1,98 @@
+package com.bizmatics.model;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-17
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class SiteInformation implements Serializable {
+
+    private static final long serialVersionUID=1L;
+
+    /**
+     * 站点信息备份表id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 户号
+     */
+    private String accountNumber;
+
+    /**
+     * 户名
+     */
+    private String accountName;
+
+    /**
+     * 监察
+     */
+    private String supervision;
+
+    /**
+     * 用户联系方式
+     */
+    private String contactInformation;
+
+    /**
+     * 流变变比
+     */
+    private String rheologicalRatio;
+
+    /**
+     * 单路电源容量
+     */
+    private String powerCapacity;
+
+    /**
+     * 所属站点ID
+     */
+    private Integer siteId;
+
+    /**
+     * 线路
+     */
+    private String line;
+
+    /**
+     * 台区编码名称
+     */
+    private String stationAreaCode;
+
+    /**
+     * 地址
+     */
+    private String address;
+
+    /**
+     * 备注
+     */
+    private String remarks;
+
+    /**
+     * 加征项目名称
+     */
+    private String additionalLevyProject;
+
+    /**
+     * 加征项目单价(元)
+     */
+    private BigDecimal additionalElectricityPrice;
+
+
+}

+ 121 - 0
fiveep-model/src/main/java/com/bizmatics/model/utils/RatioCalculator.java

@@ -0,0 +1,121 @@
+package com.bizmatics.model.utils;
+
+import com.bizmatics.model.HtAnalogData;
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/7/25
+ */
+
+
+public final class RatioCalculator {
+
+    private static enum Period {
+        DAY("日", (date, list) -> diffInRange(list,
+                date.withHour(0).withMinute(0), date)),
+
+        MONTH("月", (date, list) -> diffInRange(list,
+                date.withDayOfMonth(1).withHour(0).withMinute(0), date)),
+
+        YEAR("年", (date, list) -> diffInRange(list,
+                date.withMonth(1).withDayOfMonth(1).withHour(0).withMinute(0), date));
+
+        private final String desc;
+        private final BiFunction<LocalDateTime, List<HtAnalogData>, BigDecimal> extractor;
+
+        Period(String desc, BiFunction<LocalDateTime, List<HtAnalogData>, BigDecimal> extractor) {
+            this.desc = desc;
+            this.extractor = extractor;
+        }
+
+        BigDecimal value(LocalDateTime date, List<HtAnalogData> list) {
+            return extractor.apply(date, list);
+        }
+    }
+
+
+    public static BigDecimal getDayRatio(LocalDateTime date,
+                                         List<HtAnalogData> current,
+                                         List<HtAnalogData> previous) {
+        return ratio(date, current, previous, Period.DAY);
+    }
+
+    public static BigDecimal getMonthRatio(LocalDateTime date,
+                                           List<HtAnalogData> current,
+                                           List<HtAnalogData> previous) {
+        return ratio(date, current, previous, Period.MONTH);
+    }
+
+    public static BigDecimal getYearRatio(LocalDateTime date,
+                                          List<HtAnalogData> current,
+                                          List<HtAnalogData> previous) {
+        return ratio(date, current, previous, Period.YEAR);
+    }
+
+
+    private static BigDecimal ratio(LocalDateTime date,
+                                    List<HtAnalogData> current,
+                                    List<HtAnalogData> previous,
+                                    Period period) {
+        if (CollectionUtils.isEmpty(current) || CollectionUtils.isEmpty(previous)) {
+            return BigDecimal.ZERO;
+        }
+
+        BigDecimal cur = period.value(date, current);
+        BigDecimal pre = period.value(date.minus(period == Period.DAY ? 1 : 0,
+                        period == Period.DAY ? java.time.temporal.ChronoUnit.DAYS :
+                                period == Period.MONTH ? java.time.temporal.ChronoUnit.MONTHS :
+                                        java.time.temporal.ChronoUnit.YEARS),
+                previous);
+
+        return pre.compareTo(BigDecimal.ZERO) == 0
+                ? BigDecimal.ZERO
+                : cur.subtract(pre)
+                .divide(pre, 2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 区间 [start, end] 内 last - first
+     */
+    private static BigDecimal diffInRange(List<HtAnalogData> list,
+                                          LocalDateTime start,
+                                          LocalDateTime end) {
+        if (list == null || list.isEmpty()) return BigDecimal.ZERO;
+
+        List<HtAnalogData> filtered = list.stream()
+                .filter(Objects::nonNull)
+                .filter(d -> !d.getDataTime().isBefore(start) && !d.getDataTime().isAfter(end))
+                .sorted(Comparator.comparing(HtAnalogData::getDataTime))
+                .collect(Collectors.toList());
+
+        // 调试
+        if (filtered.size() >= 2) {
+            System.out.printf("区间 [%s, %s] 最早=%s 最晚=%s epp差=%s%n",
+                    start, end,
+                    filtered.get(0).getDataTime(),
+                    filtered.get(filtered.size() - 1).getDataTime(),
+                    filtered.get(filtered.size() - 1).getEpp()
+                            .subtract(filtered.get(0).getEpp()));
+        }
+
+        return filtered.size() < 2
+                ? BigDecimal.ZERO
+                : filtered.get(filtered.size() - 1).getEpp()
+                .subtract(filtered.get(0).getEpp());
+    }
+
+    private RatioCalculator() {
+    }
+}

+ 24 - 0
fiveep-model/src/main/java/com/bizmatics/model/vo/ElectricityAdditionalPriceVo.java

@@ -0,0 +1,24 @@
+package com.bizmatics.model.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/7/21
+ */
+@Data
+public class ElectricityAdditionalPriceVo {
+    /**
+     * 加征项目名称
+     */
+    private String additionalLevyProject;
+
+    /**
+     * 加征项目单价
+     */
+    private BigDecimal additionalElectricityPrice;
+}

+ 127 - 0
fiveep-model/src/main/java/com/bizmatics/model/vo/ElectricityRateTimeConfigVo.java

@@ -0,0 +1,127 @@
+package com.bizmatics.model.vo;
+
+import com.bizmatics.model.ElectricityTimePrice;
+import com.bizmatics.model.SiteInformation;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/7/17
+ */
+@Data
+public class ElectricityRateTimeConfigVo {
+
+    /**
+     * 费率配置主键ID
+     */
+    private Integer id;
+
+    /**
+     * 月账单结算日(范围1-31)
+     */
+    private Integer billingDay;
+
+    /**
+     * 站点ID,外键
+     */
+    private Integer siteId;
+
+    /**
+     * 电表设备ID,外键
+     */
+    private String deviceCode;
+
+    /**
+     * 计费方式
+     * 1 按合同最大需量, 2 按实际最大需量, 3 按变压器容量
+     * 默认 2
+     */
+    private Integer billingMethod;
+
+    /**
+     * 需量电价(元/千瓦)
+     */
+    private BigDecimal demandPrice;
+
+    /**
+     * 申报需量(kW)
+     */
+    private BigDecimal declaredDemand;
+
+    /**
+     * 超过的百分比
+     */
+    private BigDecimal excessPercentage;
+
+    /**
+     * 超过部分的计费倍数
+     */
+    private BigDecimal additionalMultiplier;
+
+    /**
+     * 容量电价(元/千伏安)
+     */
+    private BigDecimal capacityPrice;
+
+    /**
+     * 额定容量(kVA)
+     */
+    private BigDecimal ratedCapacity;
+
+    /**
+     * 功率因数标准值
+     */
+    private BigDecimal powerFactorStandard;
+
+    /**
+     * 功率因数报警值
+     */
+    private BigDecimal powerFactorAlert;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private String creator;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 时间电价信息
+     */
+    private List<ElectricityTimePrice> electricityTimePriceList;
+
+    /**
+     * 附加电价
+     */
+    private List<SiteInformation> electricityAdditionalPriceList;
+
+}

+ 194 - 0
fiveep-model/src/main/java/com/bizmatics/model/vo/HtAnalogEnergyConsumptionVo.java

@@ -0,0 +1,194 @@
+package com.bizmatics.model.vo;
+
+import com.bizmatics.common.core.util.ZeroBigDecimalSerializer;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/7/10
+ * @description: 能耗数据概览展示类
+ */
+@Data
+public class HtAnalogEnergyConsumptionVo {
+
+    /**
+     * 能耗单位 吨标准煤
+     */
+    private static final String ENERGY_UNIT = "吨标准煤";
+    private String energyUnit = ENERGY_UNIT;
+
+    /**
+     * 当日能耗(费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal today;
+
+    /**
+     * 当月能耗(费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal month;
+
+    /**
+     * 当年能耗(费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal year;
+
+    /**
+     * 昨日总能耗/费用(概览为总能耗,日月年为同期能耗/费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yesterday;
+
+    /**
+     * 上月总能耗/费用(概览为总能耗,日月年为同期能耗/费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastMonth;
+
+    /**
+     * 上年总能耗/费用(概览为总能耗,日月年为同期能耗/费用)
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastYear;
+
+
+    /**
+     * 当日电耗单位 千瓦时
+     */
+    private static final String ELECTRICITY_UNIT = "kWh";
+    private String electricityUnit = ELECTRICITY_UNIT;
+    /**
+     * 当日电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal todayElectricity;
+
+    /**
+     * 当月电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal monthElectricity;
+
+    /**
+     * 当年电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearElectricity;
+
+    /**
+     * 昨日总电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yesterdayElectricity;
+
+    /**
+     * 上月总电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastMonthElectricity;
+
+    /**
+     * 上年总电耗
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastYearElectricity;
+
+
+    /**
+     * 日同期能耗(费用)环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal todayRingRatio;
+
+    /**
+     * 月同期能耗(费用)环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal monthRingRatio;
+
+    /**
+     * 年同期能耗(费用)环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearRingRatio;
+
+
+    /**
+     * 能耗/费用(日月年)同比量
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearOnYear;
+    /**
+     * 能耗/费用(日月年)同比百分比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearOnYearPercent;
+
+    /**
+     * 费用单位
+     */
+    private static final String COST_UNIT = "元";
+    private String costUnit = COST_UNIT;
+
+    /**
+     * 当日费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal todayCost;
+
+    /**
+     * 当月费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal monthCost;
+
+    /**
+     * 当年费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearCost;
+
+    /**
+     * 昨日费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yesterdayCost;
+
+    /**
+     * 上月同期费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastMonthCost;
+
+    /**
+     * 上年同期费用
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal lastYearCost;
+
+    /**
+     * 日同期费用环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal dayCostRingRatio;
+
+    /**
+     * 月同期费用环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal monthCostRingRatio;
+
+    /**
+     * 年同期费用环比
+     */
+    @JsonSerialize(nullsUsing = ZeroBigDecimalSerializer.class)
+    private BigDecimal yearCostRingRatio;
+
+}

+ 52 - 0
fiveep-model/src/main/java/com/bizmatics/model/vo/HtAnalogEnergySegmentedVo.java

@@ -0,0 +1,52 @@
+package com.bizmatics.model.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 总能耗图表返回对象
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/8/13
+ */
+@Data
+public class HtAnalogEnergySegmentedVo {
+
+    /**
+     * 能耗用量/费用
+     **/
+    private List<BigDecimal> amountList;
+
+    /**
+     * 时间列表
+     **/
+    private List<LocalDateTime> timeKeys;
+
+    /**
+     * 单位
+     **/
+    private String unit;
+
+    /**
+     * 类型(日月年)
+     **/
+    private String type;
+
+    /**
+     * 当前值
+     **/
+    private BigDecimal current;
+
+    /**
+     * 同期值
+     **/
+    private BigDecimal previous;
+
+    /**
+     * 同期环比
+     **/
+    private BigDecimal ratio;
+}

+ 16 - 0
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/ElectricityRateConfigMapper.java

@@ -0,0 +1,16 @@
+package com.bizmatics.persistence.mapper;
+
+import com.bizmatics.model.ElectricityRateConfig;
+import com.bizmatics.common.mvc.base.CrudMapper;
+
+/**
+ * <p>
+ * 费率配置表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+public interface ElectricityRateConfigMapper extends CrudMapper<ElectricityRateConfig> {
+
+}

+ 16 - 0
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/ElectricityTimePriceMapper.java

@@ -0,0 +1,16 @@
+package com.bizmatics.persistence.mapper;
+
+import com.bizmatics.model.ElectricityTimePrice;
+import com.bizmatics.common.mvc.base.CrudMapper;
+
+/**
+ * <p>
+ * 时段电价表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+public interface ElectricityTimePriceMapper extends CrudMapper<ElectricityTimePrice> {
+
+}

+ 18 - 0
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/SiteElectricityRecordMapper.java

@@ -0,0 +1,18 @@
+package com.bizmatics.persistence.mapper;
+
+import com.bizmatics.model.SiteElectricityRecord;
+import com.bizmatics.common.mvc.base.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 站点电费记录表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2025-08-21
+ */
+@Repository
+public interface SiteElectricityRecordMapper extends CrudMapper<SiteElectricityRecord> {
+
+}

+ 18 - 0
fiveep-persistence/src/main/java/com/bizmatics/persistence/mapper/SiteInformationMapper.java

@@ -0,0 +1,18 @@
+package com.bizmatics.persistence.mapper;
+
+import com.bizmatics.model.SiteInformation;
+import com.bizmatics.common.mvc.base.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-17
+ */
+@Repository
+public interface SiteInformationMapper extends CrudMapper<SiteInformation> {
+
+}

+ 28 - 0
fiveep-persistence/src/main/resources/mapper/mysql/ElectricityRateConfigMapper.xml

@@ -0,0 +1,28 @@
+<?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.bizmatics.persistence.mapper.ElectricityRateConfigMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.bizmatics.model.ElectricityRateConfig">
+        <id column="id" property="id" />
+        <result column="billing_day" property="billingDay" />
+        <result column="site_id" property="siteId" />
+        <result column="device_code" property="deviceCode" />
+        <result column="billing_method" property="billingMethod" />
+        <result column="demand_price" property="demandPrice" />
+        <result column="declared_demand" property="declaredDemand" />
+        <result column="excess_percentage" property="excessPercentage" />
+        <result column="additional_multiplier" property="additionalMultiplier" />
+        <result column="capacity_price" property="capacityPrice" />
+        <result column="rated_capacity" property="ratedCapacity" />
+        <result column="power_factor_standard" property="powerFactorStandard" />
+        <result column="power_factor_alert" property="powerFactorAlert" />
+        <result column="create_time" property="createTime" />
+        <result column="creator" property="creator" />
+        <result column="dept_id" property="deptId" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 23 - 0
fiveep-persistence/src/main/resources/mapper/mysql/ElectricityTimePriceMapper.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.bizmatics.persistence.mapper.ElectricityTimePriceMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.bizmatics.model.ElectricityTimePrice">
+        <id column="id" property="id"/>
+        <result column="month" property="month"/>
+        <result column="price_type" property="priceType"/>
+        <result column="electricity_price" property="electricityPrice"/>
+        <result column="start_time" property="startTime"/>
+        <result column="end_time" property="endTime"/>
+        <result column="peak_valley_attribute" property="peakValleyAttribute"/>
+        <result column="site_id" property="siteId"/>
+        <result column="create_time" property="createTime"/>
+        <result column="creator" property="creator"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="tenant_id" property="tenantId"/>
+    </resultMap>
+
+</mapper>

+ 18 - 0
fiveep-persistence/src/main/resources/mapper/mysql/SiteElectricityRecordMapper.xml

@@ -0,0 +1,18 @@
+<?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.bizmatics.persistence.mapper.SiteElectricityRecordMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.bizmatics.model.SiteElectricityRecord">
+        <id column="id" property="id" />
+        <result column="site_id" property="siteId" />
+        <result column="device_code" property="deviceCode" />
+        <result column="date" property="date" />
+        <result column="peak_cost" property="peakCost" />
+        <result column="flat_cost" property="flatCost" />
+        <result column="valley_cost" property="valleyCost" />
+        <result column="sharp_peak_cost" property="sharpPeakCost" />
+        <result column="total_cost" property="totalCost" />
+    </resultMap>
+
+</mapper>

+ 23 - 0
fiveep-persistence/src/main/resources/mapper/mysql/SiteInformationMapper.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.bizmatics.persistence.mapper.SiteInformationMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.bizmatics.model.SiteInformation">
+        <id column="id" property="id" />
+        <result column="account_number" property="accountNumber" />
+        <result column="account_name" property="accountName" />
+        <result column="supervision" property="supervision" />
+        <result column="contact_information" property="contactInformation" />
+        <result column="rheological_ratio" property="rheologicalRatio" />
+        <result column="power_capacity" property="powerCapacity" />
+        <result column="site_id" property="siteId" />
+        <result column="line" property="line" />
+        <result column="station_area_code" property="stationAreaCode" />
+        <result column="address" property="address" />
+        <result column="remarks" property="remarks" />
+        <result column="additional_levy_project" property="additionalLevyProject" />
+        <result column="additional_electricity_price" property="additionalElectricityPrice" />
+    </resultMap>
+
+</mapper>

+ 25 - 0
fiveep-service/src/main/java/com/bizmatics/service/ElectricityRateConfigService.java

@@ -0,0 +1,25 @@
+package com.bizmatics.service;
+
+import com.bizmatics.model.ElectricityRateConfig;
+import com.bizmatics.common.mvc.base.CrudService;
+import com.bizmatics.model.vo.ElectricityRateTimeConfigVo;
+
+/**
+ * <p>
+ * 费率配置表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+public interface ElectricityRateConfigService extends CrudService<ElectricityRateConfig> {
+
+    Integer add(ElectricityRateTimeConfigVo vo);
+
+    Integer update(ElectricityRateTimeConfigVo vo);
+
+    Integer del(Integer siteId);
+
+    ElectricityRateTimeConfigVo queryConfig(Integer siteId);
+
+}

+ 16 - 0
fiveep-service/src/main/java/com/bizmatics/service/ElectricityTimePriceService.java

@@ -0,0 +1,16 @@
+package com.bizmatics.service;
+
+import com.bizmatics.model.ElectricityTimePrice;
+import com.bizmatics.common.mvc.base.CrudService;
+
+/**
+ * <p>
+ * 时段电价表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+public interface ElectricityTimePriceService extends CrudService<ElectricityTimePrice> {
+
+}

+ 26 - 3
fiveep-service/src/main/java/com/bizmatics/service/HtAnalogDataService.java

@@ -1,15 +1,16 @@
 package com.bizmatics.service;
 
+import com.bizmatics.common.core.bean.ApiResult;
 import com.bizmatics.common.mvc.base.CrudService;
 import com.bizmatics.model.HtAnalogData;
-import com.bizmatics.model.vo.DataManagementOneVO;
-import com.bizmatics.model.vo.HtAnalogDataOneVo;
-import com.bizmatics.model.vo.SingleLoopReportOneVo;
+import com.bizmatics.model.vo.*;
 import com.bizmatics.service.vo.CommonIcoVO;
 import com.bizmatics.service.vo.HadCountVO;
 import com.bizmatics.service.vo.RealScoreVO;
 import com.bizmatics.service.vo.TimeShareVO;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.List;
 
@@ -108,4 +109,26 @@ public interface HtAnalogDataService extends CrudService<HtAnalogData> {
 
     // 获取设备三相总有功功率值
     List<HtAnalogData> getP(List<String> deviceCode);
+
+    /**
+     * 获取设备三相有功功率值
+     *
+     * @param siteId 站点id
+     * @param consume 1.用量 2.费用
+     * @param time 时间
+     * @param timeType 时间类型 overview 概览,day 日,month 月,year 年
+     * @return 0.0
+     */
+    HtAnalogEnergyConsumptionVo getEnergyConsumption(Integer siteId, Integer consume, LocalDateTime time, String timeType);
+
+    /**
+     * 获取分段数据
+     *
+     * @param siteId 站点id
+     * @param queryPeriod 查询周期 day month year
+     * @param queryTime 查询时间
+     * @param queryType  查询类型(energy 能耗;electric 电;cost)
+     * @return HtAnalogEnergySegmentedVo
+     */
+    HtAnalogEnergySegmentedVo getSegmentedData(Integer siteId, String queryPeriod, LocalDateTime queryTime, String queryType);
 }

+ 2 - 0
fiveep-service/src/main/java/com/bizmatics/service/SiteDynamicPropertiesService.java

@@ -16,6 +16,8 @@ public interface SiteDynamicPropertiesService extends CrudService<SiteDynamicPro
     void siteDynamicPropertiesAdd(@Param("siteDynamicProperties") SiteDynamicProperties siteDynamicProperties);
 
     void SiteDynamicPropertiesUpdate(@Param("siteDynamicProperties") SiteDynamicProperties siteDynamicProperties);
+
+    SiteDynamicProperties selectOne(@Param("siteId") Integer siteId);
 }
 
 

+ 16 - 0
fiveep-service/src/main/java/com/bizmatics/service/SiteElectricityRecordService.java

@@ -0,0 +1,16 @@
+package com.bizmatics.service;
+
+import com.bizmatics.model.SiteElectricityRecord;
+import com.bizmatics.common.mvc.base.CrudService;
+
+/**
+ * <p>
+ * 站点电费记录表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-08-21
+ */
+public interface SiteElectricityRecordService extends CrudService<SiteElectricityRecord> {
+
+}

+ 16 - 0
fiveep-service/src/main/java/com/bizmatics/service/SiteInformationService.java

@@ -0,0 +1,16 @@
+package com.bizmatics.service;
+
+import com.bizmatics.model.SiteInformation;
+import com.bizmatics.common.mvc.base.CrudService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-17
+ */
+public interface SiteInformationService extends CrudService<SiteInformation> {
+
+}

+ 1 - 0
fiveep-service/src/main/java/com/bizmatics/service/config/SecurityConfig.java

@@ -98,6 +98,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                 .antMatchers("/login", "/register", "/captchaImage", "/getLoginStyle", "/sysTenantConfig/getTenantConfig", "/aliWeather").anonymous()
                 // "/site/siteLoadAnalysis"
+                // , "/electricityRateConfig/**"
                 .antMatchers(
                         HttpMethod.GET,
                         "/",

+ 196 - 0
fiveep-service/src/main/java/com/bizmatics/service/impl/ElectricityRateConfigServiceImpl.java

@@ -0,0 +1,196 @@
+package com.bizmatics.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.bizmatics.common.core.exception.BusinessException;
+import com.bizmatics.model.ElectricityRateConfig;
+import com.bizmatics.model.ElectricityTimePrice;
+import com.bizmatics.model.SiteInformation;
+import com.bizmatics.model.vo.ElectricityRateTimeConfigVo;
+import com.bizmatics.persistence.mapper.ElectricityRateConfigMapper;
+import com.bizmatics.persistence.mapper.ElectricityTimePriceMapper;
+import com.bizmatics.persistence.mapper.SiteInformationMapper;
+import com.bizmatics.service.ElectricityRateConfigService;
+import com.bizmatics.common.mvc.base.AbstractCrudService;
+import com.bizmatics.service.ElectricityTimePriceService;
+import com.bizmatics.service.SiteInformationService;
+import com.bizmatics.service.util.SecurityUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 费率配置表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@Service
+public class ElectricityRateConfigServiceImpl extends AbstractCrudService<ElectricityRateConfigMapper, ElectricityRateConfig> implements ElectricityRateConfigService {
+
+    @Autowired
+    private ElectricityRateConfigMapper rateConfigMapper;
+
+    @Autowired
+    private ElectricityTimePriceService timePriceService;
+
+    @Autowired
+    private ElectricityTimePriceMapper timePriceMapper;
+
+    @Autowired
+    private SiteInformationService siteInformationService;
+
+    @Autowired
+    private SiteInformationMapper siteInformationMapper;
+
+    @Transactional
+    @Override
+    public Integer add(ElectricityRateTimeConfigVo vo) {
+        String username = SecurityUtils.getUsername();
+        Long deptId = SecurityUtils.getUser().getDeptId();
+        Integer tenantId = SecurityUtils.getUser().getTenantId();
+        LocalDateTime now = LocalDateTime.now();
+
+        ElectricityRateConfig rateConfig = new ElectricityRateConfig();
+        verBillingDay(vo.getBillingDay());
+        verBillingMethod(vo.getBillingMethod());
+
+        BeanUtils.copyProperties(vo, rateConfig);
+        rateConfig.setCreateTime(now);
+        rateConfig.setCreator(username);
+        rateConfig.setDeptId(deptId);
+        rateConfig.setTenantId(tenantId);
+        int rateConfigCount = rateConfigMapper.insert(rateConfig);
+
+        if (CollectionUtils.isNotEmpty(vo.getElectricityTimePriceList())) {
+            vo.getElectricityTimePriceList().forEach(item -> {
+                item.setCreator(username);
+                item.setCreateTime(now);
+                item.setDeptId(deptId);
+                item.setTenantId(tenantId);
+                item.setSiteId(vo.getSiteId());
+            });
+            // 批量插入(默认批次 1000)
+            timePriceService.saveBatch(vo.getElectricityTimePriceList());
+        }
+
+        if (CollectionUtils.isNotEmpty(vo.getElectricityAdditionalPriceList())) {
+            vo.getElectricityAdditionalPriceList().forEach(item -> {
+                item.setRemarks("站点电费附加");
+            });
+            siteInformationService.saveBatch(vo.getElectricityAdditionalPriceList());
+        }
+
+        return rateConfigCount;
+    }
+
+    /**
+     * 校验计费日
+     * @param billingDay
+     */
+    private void verBillingDay(Integer billingDay) {
+        if (billingDay < 1 || billingDay > 31) {
+            throw new BusinessException("账期天数只能是1-31");
+        }
+    }
+
+    /**
+     * 验证账期方式
+     * @param billingMethod
+     */
+    private void verBillingMethod(Integer billingMethod) {
+        if (billingMethod < 1 || billingMethod > 3) {
+            throw new BusinessException("计费方式选择有误!");
+        }
+    }
+
+
+    @Override
+    public Integer update(ElectricityRateTimeConfigVo vo) {
+        String username = SecurityUtils.getUsername();
+        LocalDateTime now = LocalDateTime.now();
+        if (vo.getId() == null) {
+            throw new BusinessException("请选择要修改的费率配置!");
+        }
+        ElectricityRateConfig rateConfig = new ElectricityRateConfig();
+        verBillingDay(vo.getBillingDay());
+        verBillingMethod(vo.getBillingMethod());
+
+        BeanUtils.copyProperties(vo, rateConfig);
+        rateConfig.setUpdateBy(username);
+        rateConfig.setUpdateTime(now);
+        int updateCount = rateConfigMapper.updateById(rateConfig);
+
+        if (CollectionUtils.isNotEmpty(vo.getElectricityTimePriceList())) {
+            vo.getElectricityTimePriceList().forEach(item -> {
+                item.setUpdateBy(username);
+                item.setUpdateTime(now);
+            });
+            // 批量更新(默认批次 1000)
+            timePriceService.updateBatchById(vo.getElectricityTimePriceList());
+        }
+
+        if (CollectionUtils.isNotEmpty(vo.getElectricityAdditionalPriceList())) {
+            vo.getElectricityAdditionalPriceList().forEach(item -> {
+                item.setRemarks("站点电费附加,更新");
+            });
+            siteInformationService.updateBatchById(vo.getElectricityAdditionalPriceList());
+        }
+
+        return updateCount;
+    }
+
+    /**
+     * 删除站点电费配置数据
+     *
+     * @param siteId
+     * @return
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public Integer del(Integer siteId) {
+        LambdaQueryWrapper<ElectricityRateConfig> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ElectricityRateConfig::getSiteId, siteId);
+        int delete = rateConfigMapper.delete(queryWrapper);
+
+        LambdaQueryWrapper<ElectricityTimePrice> queryWrapper2 = new LambdaQueryWrapper<>();
+        queryWrapper2.eq(ElectricityTimePrice::getSiteId, siteId);
+        timePriceMapper.delete(queryWrapper2);
+
+        LambdaQueryWrapper<SiteInformation> queryWrapper3 = new LambdaQueryWrapper<>();
+        queryWrapper3.eq(SiteInformation::getSiteId, siteId);
+        siteInformationMapper.delete(queryWrapper3);
+
+        return delete;
+    }
+
+    @Override
+    public ElectricityRateTimeConfigVo queryConfig(Integer siteId) {
+        LambdaQueryWrapper<ElectricityRateConfig> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ElectricityRateConfig::getSiteId, siteId);
+        ElectricityRateConfig rateConfig = rateConfigMapper.selectOne(queryWrapper);
+        ElectricityRateTimeConfigVo vo = new ElectricityRateTimeConfigVo();
+        if (rateConfig == null) {
+            return vo;
+        }
+        BeanUtils.copyProperties(rateConfig, vo);
+
+        LambdaQueryWrapper<ElectricityTimePrice> queryWrapper2 = new LambdaQueryWrapper<>();
+        queryWrapper2.eq(ElectricityTimePrice::getSiteId, siteId);
+        List<ElectricityTimePrice> electricityTimePriceList = timePriceMapper.selectList(queryWrapper2);
+        vo.setElectricityTimePriceList(electricityTimePriceList);
+
+        LambdaQueryWrapper<SiteInformation> queryWrapper3 = new LambdaQueryWrapper<>();
+        queryWrapper3.eq(SiteInformation::getSiteId, siteId);
+        List<SiteInformation> electricityAdditionalPriceList = siteInformationMapper.selectList(queryWrapper3);
+        vo.setElectricityAdditionalPriceList(electricityAdditionalPriceList);
+
+        return vo;
+    }
+}

+ 20 - 0
fiveep-service/src/main/java/com/bizmatics/service/impl/ElectricityTimePriceServiceImpl.java

@@ -0,0 +1,20 @@
+package com.bizmatics.service.impl;
+
+import com.bizmatics.model.ElectricityTimePrice;
+import com.bizmatics.persistence.mapper.ElectricityTimePriceMapper;
+import com.bizmatics.service.ElectricityTimePriceService;
+import com.bizmatics.common.mvc.base.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 时段电价表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-16
+ */
+@Service
+public class ElectricityTimePriceServiceImpl extends AbstractCrudService<ElectricityTimePriceMapper, ElectricityTimePrice> implements ElectricityTimePriceService {
+
+}

+ 1561 - 14
fiveep-service/src/main/java/com/bizmatics/service/impl/HtAnalogDataServiceImpl.java

@@ -1,28 +1,24 @@
 package com.bizmatics.service.impl;
 
-
 import cn.afterturn.easypoi.excel.ExcelExportUtil;
 import cn.afterturn.easypoi.excel.entity.ExportParams;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.bizmatics.common.core.exception.BusinessException;
-import com.bizmatics.common.core.util.Arith;
-import com.bizmatics.common.core.util.BeanMapperUtils;
-import com.bizmatics.common.core.util.DateUtils;
-import com.bizmatics.common.core.util.FileUtils;
+import com.bizmatics.common.core.util.*;
 import com.bizmatics.common.mvc.base.AbstractCrudService;
 import com.bizmatics.common.spring.util.GlobalUtils;
 import com.bizmatics.common.spring.util.JsonUtils;
 import com.bizmatics.model.*;
 import com.bizmatics.model.vo.*;
-import com.bizmatics.persistence.mapper.HtAnalogDataMapper;
+import com.bizmatics.persistence.mapper.*;
 import com.bizmatics.service.*;
 import com.bizmatics.service.util.FieldEscapeUtils;
 import com.bizmatics.service.util.SecurityUtils;
 import com.bizmatics.service.vo.*;
 import com.fasterxml.jackson.core.type.TypeReference;
-import org.apache.ibatis.annotations.Select;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -31,9 +27,13 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.text.DateFormat;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -46,10 +46,10 @@ import static com.bizmatics.service.impl.RtAnalogDataServiceImpl.getLastDayOfMon
  * @author yq
  * @date 2021/7/20 16:49
  */
+@Slf4j
 @Service
 public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMapper, HtAnalogData> implements HtAnalogDataService {
 
-
     @Autowired
     private HadSiteStaticService hadSiteStaticService;
     @Autowired
@@ -64,6 +64,19 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
     @Autowired
     private HtAnalogDataMapper htAnalogDataMapper;
 
+    @Autowired
+    private DeviceMapper deviceMapper;
+    @Autowired
+    private HtAnalog173DataMapper htAnalog173DataMapper;
+    @Autowired
+    private ElectricityRateConfigMapper electricityRateConfigMapper;
+    @Autowired
+    private SiteInformationMapper siteInformationMapper;
+    @Autowired
+    private SiteElectricityRecordMapper siteElectricityRecordMapper;
+    @Autowired
+    private ElectricityTimePriceMapper electricityTimePriceMapper;
+
     @Override
     public HadCountVO selectCount() {
         HadCountVO hadCountVO = new HadCountVO();
@@ -283,7 +296,13 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
             date = hours;
             Date finalDate = date;
             Optional<HtAnalogData> htAnalogData = Optional.ofNullable(htList).flatMap(hts -> hts.stream()
-                    .filter(hads -> DateUtils.isEffectiveDate(hads.getDataTime(), finalDate, hours))
+                    .filter(hads -> {
+                        // LocalDateTime 转 Date
+                        Date date1 = Date.from(hads.getDataTime()
+                                .atZone(ZoneId.systemDefault())
+                                .toInstant());
+                        return DateUtils.isEffectiveDate(date1, finalDate, hours);
+                    })
                     .findFirst());
             iaList.add(htAnalogData.map(HtAnalogData::getIa).orElse(0.00));
             ibList.add(htAnalogData.map(HtAnalogData::getIb).orElse(0.00));
@@ -821,10 +840,10 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
         queryWrapper.eq(Device::getDeviceCode, deviceCode);
         List<Device> deviceList = deviceService.list(queryWrapper);
         String table = "ht_analog_data";
-        if (deviceList.size() > 0) {
-            if (deviceList.get(0).getDeviceType().equals("1")) {
+        if (!deviceList.isEmpty()) {
+            if ("1".equals(deviceList.get(0).getDeviceType())) {
                 table = "ht_analog_data";
-            } else if (deviceList.get(0).getDeviceType().equals("4")) {
+            } else if ("4".equals(deviceList.get(0).getDeviceType())) {
                 table = "ht_analog_173_data";
             } else {
                 throw new BusinessException("173用电设备无用电数据采集");
@@ -835,7 +854,7 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
 
         List<HtAnalogDataVo> maxMonthlyReport = baseMapper.getEnergyUseList(deviceCode, startTime, endTime, cycle, 1, table);
         List<HtAnalogDataVo> minMonthlyReport = baseMapper.getEnergyUseList(deviceCode, startTime, endTime, cycle, 2, table);
-        if (maxMonthlyReport.size() > 0) {
+        if (!maxMonthlyReport.isEmpty()) {
             for (int i = 0; i < maxMonthlyReport.size(); i++) {
                 BigDecimal b1 = new BigDecimal(maxMonthlyReport.get(i).getEpp().toString());
                 BigDecimal b2 = new BigDecimal(minMonthlyReport.get(i).getEpp().toString());
@@ -877,7 +896,7 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
         queryWrapper1.eq(Device::getDeviceCode, deviceCode);
         List<Device> deviceList = deviceService.list(queryWrapper1);
         String table = "ht_analog_data";
-        if (deviceList.size() > 0) {
+        if (!deviceList.isEmpty()) {
             if (deviceList.get(0).getDeviceType().equals("1")) {
                 table = "ht_analog_data";
             } else if (deviceList.get(0).getDeviceType().equals("4")) {
@@ -1474,4 +1493,1532 @@ public class HtAnalogDataServiceImpl extends AbstractCrudService<HtAnalogDataMap
     public List<HtAnalogData> getP(List<String> deviceCodes) {
         return htAnalogDataMapper.getP(deviceCodes);
     }
+
+    /**
+     * 总能耗计算
+     *
+     * @param siteId 站点ID
+     * @param time   时间
+     * @param timeType 时间类型 默认: overview 概览,day 日,month 月,year 年
+     * @return HtAnalogEnergyConsumptionVo
+     */
+    @Override
+    public HtAnalogEnergyConsumptionVo getEnergyConsumption(Integer siteId, Integer consume, LocalDateTime time, String timeType) {
+        if (siteId == null || siteId <= 0) {
+            throw new BusinessException("站点ID异常,请检查后再试");
+        }
+        HtAnalogEnergyConsumptionVo vo = new HtAnalogEnergyConsumptionVo();
+        List<Device> devices = getDevices(siteId);
+        if (devices.isEmpty()) {
+            log.info("站点{}无设备", siteId);
+            return new HtAnalogEnergyConsumptionVo();
+        }
+
+        // LocalDate time = localDateTime.toLocalDate();
+        // 上个月的同一天
+        // LocalDate sameDayLastMonth = time.minusMonths(1);
+        LocalDate today = time.toLocalDate();
+        LocalDate yesterday1 = today.minusDays(1);
+        LocalDate lastMonth = today.minusMonths(1);
+        LocalDate lastYear = today.minusYears(1);
+
+        List<String> data183 = new ArrayList<>();
+        List<HtAnalogData> dayList = new ArrayList<>();
+        List<HtAnalogData> monthList = new ArrayList<>();
+        List<HtAnalogData> yearList = new ArrayList<>();
+        List<HtAnalogData> yesterday = new ArrayList<>();
+        List<HtAnalogData> sameDayLastMonthList = new ArrayList<>();
+        List<HtAnalogData> sameDayLastYearList = new ArrayList<>();
+
+        List<String> data173 = new ArrayList<>();
+        List<HtAnalog173Data> dayList173 = new ArrayList<>();
+        List<HtAnalog173Data> monthList173 = new ArrayList<>();
+        List<HtAnalog173Data> yearList173 = new ArrayList<>();
+        List<HtAnalog173Data> yesterday173 = new ArrayList<>();
+        List<HtAnalog173Data> sameDayLastMonthList173 = new ArrayList<>();
+        List<HtAnalog173Data> sameDayLastYearList173 = new ArrayList<>();
+
+        for (Device device : devices) {
+            String deviceType = device.getDeviceType();
+            String deviceCode = device.getDeviceCode();
+            if ("1".equals(deviceType)) {
+                data183.add(deviceCode);
+            } else if ("4".equals(deviceType)) {
+                data173.add(deviceCode);
+            }
+        }
+
+        // 查询站点能耗转换情况(等价 or 当量)
+        SiteDynamicProperties siteDynamicProperties = siteDynamicPropertiesService.selectOne(siteId);
+        if (siteDynamicProperties == null) {
+            log.info("站点{}未配置能耗属性等数据", siteId);
+            return new HtAnalogEnergyConsumptionVo();
+        }
+        Integer demolitionStandardCoal = siteDynamicProperties.getDemolitionStandardCoal();
+        BigDecimal coefficient = new BigDecimal(siteDynamicProperties.getDemolitionStandardCoal1())
+                .divide(new BigDecimal(demolitionStandardCoal), 10, RoundingMode.DOWN);
+
+        switch (timeType) {
+            // 概览
+            case "overview":
+                if (!data183.isEmpty()) {
+                    dayList = getAnalogData(data183.get(0), time, 1);
+                    monthList = getAnalogData(data183.get(0), time, 2);
+                    yearList = getAnalogData(data183.get(0), time, 3);
+
+                    yesterday = getAnalogData(data183.get(0), time.minusDays(1), 1);
+                    // 上月同期
+                    sameDayLastMonthList = getAnalogData(data183.get(0), time, 22);
+                    // 上年同期
+                    sameDayLastYearList = getAnalogData(data183.get(0), time, 33);
+
+                    if (consume == 1) {
+                        // 昨日同期
+                        List<HtAnalogData> yesterdatSameTimeList = getAnalogData(data183.get(0), time, 11);
+                        List<HtAnalogData> sameDayLastMonth = getAnalogData(data183.get(0), time.minusMonths(1), 2);
+                        List<HtAnalogData> sameDayLastYear = getAnalogData(data183.get(0), time.minusYears(1), 3);
+
+                        vo.setTodayElectricity(getTotal(dayList));
+                        vo.setMonthElectricity(getTotal(monthList));
+                        vo.setYearElectricity(getTotal(yearList));
+                        vo.setYesterdayElectricity(getTotal(yesterday));
+                        vo.setLastMonthElectricity(getTotal(sameDayLastMonth));
+                        vo.setLastYearElectricity(getTotal(sameDayLastYear));
+
+                        vo.setTodayRingRatio(getRingRatio(dayList, yesterdatSameTimeList));
+                        vo.setMonthRingRatio(getRingRatio(monthList, sameDayLastMonthList));
+                        vo.setYearRingRatio(getRingRatio(yearList, sameDayLastYearList));
+
+                        vo.setToday(getTotal(dayList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYesterday(getTotal(yesterday).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setMonth(getTotal(monthList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastMonth(getTotal(sameDayLastMonth).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYear(getTotal(yearList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastYear(getTotal(sameDayLastYear).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+
+                        // 调试输出
+                        System.out.println("昨日同期:" + getTotal(yesterdatSameTimeList));
+                        System.out.println("上月同期:" + getTotal(sameDayLastMonthList));
+                        System.out.println("去年同期:" + getTotal(sameDayLastYearList));
+
+                    } else if (consume == 2) {
+                        // 1. 计算电量 & 基础费用
+                        ElectricityRateConfig cfg = getElectricityRateConfig(siteId);
+
+                        BigDecimal dayElectricity = getTotal(dayList);
+                        BigDecimal monthElectricity = getTotal(monthList);
+                        BigDecimal yearElectricity = getTotal(yearList);
+                        BigDecimal sameDayElectricity = getTotal(yesterday);
+                        BigDecimal sameMonthElectricity = getTotal(sameDayLastMonthList);
+                        BigDecimal sameYearElectricity = getTotal(sameDayLastYearList);
+
+                        BigDecimal dayBasic = getBasicExpenses(dayList, cfg, siteId, dayElectricity);
+                        BigDecimal monthBasic = getBasicExpenses(monthList, cfg, siteId, monthElectricity);
+                        BigDecimal yearBasic = getBasicExpenses(yearList, cfg, siteId, yearElectricity);
+                        BigDecimal sameDayBasic = getBasicExpenses(yesterday, cfg, siteId, sameDayElectricity);
+                        BigDecimal sameMonthBasic = getBasicExpenses(sameDayLastMonthList, cfg, siteId, sameMonthElectricity);
+                        BigDecimal sameYearBasic = getBasicExpenses(sameDayLastYearList, cfg, siteId, sameYearElectricity);
+
+                        // 3. 费用汇总
+                        BigDecimal dayCost = calculateTotalCost(calculateTimeFee(siteId, today, 1)).add(dayBasic);
+                        BigDecimal monthCost = calculateTotalCost(calculateTimeFee(siteId, today, 2)).add(monthBasic);
+                        BigDecimal yearCost = calculateTotalCost(calculateTimeFee(siteId, today, 3)).add(yearBasic);
+
+                        BigDecimal yesterdayCost = calculateTotalCost(calculateTimeFee(siteId, yesterday1, 1)).add(sameDayBasic);
+                        BigDecimal sameMonthCost = calculateTotalCost(calculateTimeFee(siteId, lastMonth, 22)).add(sameMonthBasic);
+                        BigDecimal sameYearCost = calculateTotalCost(calculateTimeFee(siteId, lastYear, 33)).add(sameYearBasic);
+
+                        // 4. 设置值
+                        vo.setTodayCost(dayCost);
+                        vo.setMonthCost(monthCost);
+                        vo.setYearCost(yearCost);
+                        vo.setYesterdayCost(yesterdayCost);
+                        vo.setLastMonthCost(sameMonthCost);
+                        vo.setLastYearCost(sameYearCost);
+
+                        // 日环比
+                        vo.setDayCostRingRatio(yesterdayCost.compareTo(BigDecimal.ZERO) != 0
+                                ? dayCost.subtract(yesterdayCost)
+                                .divide(yesterdayCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+                        // 月环比
+                        vo.setMonthCostRingRatio(sameMonthCost.compareTo(BigDecimal.ZERO) != 0
+                                ? monthCost.subtract(sameMonthCost)
+                                .divide(sameMonthCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+
+                        // 年环比
+                        vo.setYearCostRingRatio(sameYearCost.compareTo(BigDecimal.ZERO) != 0
+                                ? yearCost.subtract(sameYearCost)
+                                .divide(sameYearCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+                    }
+                } else if (!data173.isEmpty()) {
+                    dayList173 = get173Data(data173, time, 1);
+                    monthList173 = get173Data(data173, time, 2);
+                    yearList173 = get173Data(data173, time, 3);
+
+                    yesterday173 = get173Data(data173, time.minusDays(1), 1);
+                    sameDayLastMonthList173 = get173Data(data173, time.minusDays(1), 2);
+                    sameDayLastYearList173 = get173Data(data173, time.minusDays(1), 3);
+
+                    if (consume == 1) {
+                        // 电耗
+                        vo.setTodayElectricity(get173Total(dayList173));
+                        vo.setMonthElectricity(get173Total(monthList173));
+                        vo.setYearElectricity(get173Total(yearList173));
+                        vo.setYesterdayElectricity(get173Total(yesterday173));
+                        vo.setLastMonthElectricity(get173Total(sameDayLastMonthList173));
+                        vo.setLastYearElectricity(get173Total(sameDayLastYearList173));
+
+                        // 能耗
+                        vo.setToday(get173Total(dayList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setMonth(get173Total(monthList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYear(get173Total(yearList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYesterday(get173Total(yesterday173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastMonth(get173Total(sameDayLastMonthList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastYear(get173Total(sameDayLastYearList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+
+                        // 电耗环比
+                        vo.setTodayRingRatio(calcMoM(time, dayList173, yearList173));
+                        vo.setMonthRingRatio(calcMoM(time, monthList173, sameDayLastMonthList173));
+                        vo.setYearRingRatio(calcMoM(time, yearList173, sameDayLastYearList173));
+                    } else if (consume == 2) {
+
+                    }
+                } else {
+                    return new HtAnalogEnergyConsumptionVo();
+                }
+                break;
+            // 日
+            case "day":
+                if (consume == 1) {
+                    if (!data183.isEmpty()) {
+                        dayList = getAnalogData(data183.get(0), time, 1);
+                        // 此处为昨日同期电耗
+                        yesterday = getAnalogData(data183.get(0), time.minusDays(1), 11);
+
+                        // 电耗
+                        vo.setTodayElectricity(getTotal(dayList));
+                        // 昨日同期电耗
+                        vo.setYesterdayElectricity(getTotal(yesterday));
+                        // 能耗
+                        vo.setToday(getTotal(dayList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYesterday(getTotal(yesterday).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        // 日同期环比
+                        vo.setTodayRingRatio(getRingRatio(dayList, yesterday));
+
+                        // 上一年同日能耗
+                        List<HtAnalogData> analogData = getAnalogData(data183.get(0), time.minusYears(1), 1);
+                        BigDecimal sameDayLastYearEnergy = getTotal(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP);
+                        vo.setYearOnYear(sameDayLastYearEnergy);
+                        // 日能耗同比
+                        vo.setYearOnYearPercent(
+                                sameDayLastYearEnergy.compareTo(BigDecimal.ZERO) == 0
+                                        ? BigDecimal.ZERO
+                                        : getTotal(dayList).multiply(coefficient)
+                                        .divide(sameDayLastYearEnergy, 3, RoundingMode.HALF_UP)
+                        );
+                    } else if (!data173.isEmpty()) {
+                        dayList173 = get173Data(data173, time, 1);
+                        yesterday173 = get173Data(data173, time.minusDays(1), 1);
+
+                        // 电耗
+                        vo.setTodayElectricity(get173Total(dayList173));
+                        vo.setYesterdayElectricity(get173Total(yesterday173));
+                        // 能耗
+                        vo.setToday(get173Total(dayList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        // 昨日同期能耗
+                        vo.setYesterday(get173Total(yesterday173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+
+                        // 上一年同日能耗
+                        List<HtAnalog173Data> analogData = get173Data(data173, time.minusYears(1), 1);
+                        BigDecimal sameDayLastYearEnergy = get173Total(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP);
+                        vo.setYearOnYear(sameDayLastYearEnergy);
+                        // 日能耗同比
+                        vo.setYearOnYearPercent(get173Total(dayList173).multiply(coefficient).divide(sameDayLastYearEnergy, 2, RoundingMode.HALF_UP));
+                    }
+                } else if (consume == 2) {
+                    ElectricityRateConfig cfg = getElectricityRateConfig(siteId);
+                    if (!data183.isEmpty()) {
+                        dayList = getAnalogData(data183.get(0), time, 1);
+                        vo.setTodayElectricity(getTotal(dayList));
+
+                        dayList = getAnalogData(data183.get(0), time, 1);
+                        yesterday = getAnalogData(data183.get(0), time.minusDays(1), 1);
+
+                        BigDecimal dayElectricity = getTotal(dayList);
+                        BigDecimal sameDayElectricity = getTotal(yesterday);
+                        BigDecimal dayBasic = getBasicExpenses(dayList, cfg, siteId, dayElectricity);
+                        BigDecimal sameDayBasic = getBasicExpenses(yesterday, cfg, siteId, sameDayElectricity);
+                        BigDecimal dayCost = calculateTotalCost(calculateTimeFee(siteId, today, 1)).add(dayBasic);
+                        BigDecimal yesterdayCost = calculateTotalCost(calculateTimeFee(siteId, yesterday1, 1)).add(sameDayBasic);
+                        vo.setTodayCost(dayCost);
+                        vo.setYesterdayCost(yesterdayCost);
+                        vo.setDayCostRingRatio(yesterdayCost.compareTo(BigDecimal.ZERO) != 0
+                                ? dayCost.subtract(yesterdayCost)
+                                .divide(yesterdayCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+
+                        // 上一年同日能耗
+                        List<HtAnalogData> sameDayLastYear = getAnalogData(data183.get(0), time.minusYears(1), 1);
+                        BigDecimal sameDayLastYearElectricity = getTotal(sameDayLastYear);
+                        BigDecimal sameDayLastYearBasic = getBasicExpenses(sameDayLastYear, cfg, siteId, sameDayLastYearElectricity);
+                        BigDecimal sameDayLastYearCost = calculateTotalCost(calculateTimeFee(siteId, lastYear, 1)).add(sameDayLastYearBasic);
+                        vo.setYearOnYear(sameDayLastYearCost);
+                        vo.setYearOnYearPercent(sameDayLastYearCost.compareTo(BigDecimal.ZERO) != 0
+                                ? dayCost.subtract(sameDayLastYearCost)
+                                .divide(sameDayLastYearCost, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
+
+                    } else if (!data173.isEmpty()) {
+
+                    }
+                }
+                break;
+            // 月
+            case "month":
+                if (!data183.isEmpty()) {
+                    monthList = getAnalogData(data183.get(0), time, 2);
+                    sameDayLastMonthList = getAnalogData(data183.get(0), time.minusMonths(1), 22);
+                    // 上一年同月能耗
+                    List<HtAnalogData> analogData = getAnalogData(data183.get(0), time.minusYears(1), 2);
+                    if (consume == 1) {
+                        // 电耗
+                        vo.setMonthElectricity(getTotal(monthList));
+                        vo.setLastMonthElectricity(getTotal(sameDayLastMonthList));
+                        // 能耗
+                        vo.setMonth(getTotal(monthList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastMonth(getTotal(sameDayLastMonthList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        // 同期环比
+                        vo.setMonthRingRatio(getRingRatio(monthList, sameDayLastMonthList));
+
+                        BigDecimal sameMonthLastYearEnergy = getTotal(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP);
+                        vo.setYearOnYear(sameMonthLastYearEnergy);
+                        // 月能耗同比
+                        vo.setYearOnYearPercent(
+                                sameMonthLastYearEnergy.compareTo(BigDecimal.ZERO) == 0
+                                        ? BigDecimal.ZERO
+                                        : getTotal(monthList).multiply(coefficient)
+                                        .divide(sameMonthLastYearEnergy, 3, RoundingMode.HALF_UP)
+                        );
+                    } else if (consume == 2) {
+                        vo.setMonthElectricity(getTotal(monthList));
+
+                        ElectricityRateConfig cfg = getElectricityRateConfig(siteId);
+                        BigDecimal monthBasic = getBasicExpenses(monthList, cfg, siteId, getTotal(monthList));
+                        BigDecimal sameMonthBasic = getBasicExpenses(sameDayLastMonthList, cfg, siteId, getTotal(sameDayLastMonthList));
+                        BigDecimal monthCost = calculateTotalCost(calculateTimeFee(siteId, today, 2)).add(monthBasic);
+                        BigDecimal sameMonthCost = calculateTotalCost(calculateTimeFee(siteId, lastMonth, 22)).add(sameMonthBasic);
+                        vo.setMonthCost(monthCost);
+                        vo.setLastMonthCost(sameMonthCost);
+                        vo.setMonthCostRingRatio(sameMonthCost.compareTo(BigDecimal.ZERO) != 0
+                                ? monthCost.subtract(sameMonthCost)
+                                .divide(sameMonthCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+                        // 上一年同月能耗
+                        List<HtAnalogData> sameMonthLastYear = getAnalogData(data183.get(0), time.minusYears(1), 2);
+                        BigDecimal sameMonthLastYearElectricity = getTotal(sameMonthLastYear);
+                        BigDecimal sameMonthLastYearBasic = getBasicExpenses(sameMonthLastYear, cfg, siteId, sameMonthLastYearElectricity);
+                        BigDecimal sameMonthLastYearCost = calculateTotalCost(calculateTimeFee(siteId, lastMonth, 2)).add(sameMonthLastYearBasic);
+                        vo.setYearOnYear(sameMonthLastYearCost);
+                        vo.setYearOnYearPercent(sameMonthLastYearCost.compareTo(BigDecimal.ZERO) != 0
+                                ? monthCost.subtract(sameMonthLastYearCost)
+                                .divide(sameMonthLastYearCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+                    }
+
+                } else if (!data173.isEmpty()) {
+                    monthList173 = get173Data(data173, time, 2);
+                    sameDayLastMonthList173 = get173Data(data173, time.minusMonths(1), 2);
+
+                    // 电耗
+                    vo.setMonthElectricity(get173Total(monthList173));
+                    vo.setLastMonthElectricity(get173Total(sameDayLastMonthList173));
+                    // 能耗
+                    vo.setMonth(get173Total(monthList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    vo.setLastMonth(get173Total(sameDayLastMonthList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    // 月同期环比
+                    vo.setMonthRingRatio(calcMoM(time, monthList173, sameDayLastMonthList173));
+
+                    // 上一年同月能耗
+                    List<HtAnalog173Data> analogData = get173Data(data173, time.minusYears(1), 2);
+                    BigDecimal sameMonthLastYearEnergy = get173Total(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP);
+                    vo.setYearOnYear(sameMonthLastYearEnergy);
+                    // 月能耗同比
+                    vo.setYearOnYearPercent(get173Total(monthList173).multiply(coefficient).divide(sameMonthLastYearEnergy, 3, RoundingMode.HALF_UP));
+                }
+                break;
+            // 年
+            case "year":
+                if (!data183.isEmpty()) {
+                    yearList = getAnalogData(data183.get(0), time, 3);
+                    sameDayLastYearList = getAnalogData(data183.get(0), time.minusYears(1), 33);
+
+                    if (consume == 1) {
+                        // 电耗
+                        vo.setYearElectricity(getTotal(yearList));
+                        // 去年同期电耗
+                        vo.setLastYearElectricity(getTotal(sameDayLastYearList));
+                        // 能耗
+                        vo.setYear(getTotal(yearList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setLastYear(getTotal(sameDayLastYearList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        // 环比
+                        vo.setYearRingRatio(getRingRatio(yearList, sameDayLastYearList));
+                        // 同比
+                        vo.setYearOnYear(getTotal(sameDayLastYearList).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                        vo.setYearOnYearPercent(getRingRatio(yearList, sameDayLastYearList));
+                        // 上一年能耗
+                        // List<HtAnalogData> analogData = getAnalogData(data183.get(0), time.minusYears(1), 3);
+                        // vo.setYearOnYear(getTotal(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    } else if (consume == 2) {
+                        vo.setYearElectricity(getTotal(yearList));
+
+                        ElectricityRateConfig cfg = getElectricityRateConfig(siteId);
+                        BigDecimal yearBasic = getBasicExpenses(yearList, cfg, siteId, getTotal(yearList));
+                        BigDecimal sameYearBasic = getBasicExpenses(sameDayLastYearList, cfg, siteId, getTotal(sameDayLastYearList));
+                        BigDecimal yearCost = calculateTotalCost(calculateTimeFee(siteId, today, 3)).add(yearBasic);
+                        BigDecimal sameYearCost = calculateTotalCost(calculateTimeFee(siteId, lastYear, 33)).add(sameYearBasic);
+                        vo.setYearCost(yearCost);
+                        vo.setLastYearCost(sameYearCost);
+                        vo.setYearCostRingRatio(sameYearCost.compareTo(BigDecimal.ZERO) != 0
+                                ? yearCost.subtract(sameYearCost)
+                                .divide(sameYearCost, 2, RoundingMode.HALF_UP)
+                                : BigDecimal.ZERO);
+                        vo.setYearOnYear(sameYearCost);
+                        vo.setYearOnYearPercent(vo.getYearCostRingRatio());
+                    }
+                } else if (!data173.isEmpty()) {
+                    yearList173 = get173Data(data173, time, 3);
+                    sameDayLastYearList173 = get173Data(data173, time.minusYears(1), 3);
+
+                    // 电耗
+                    vo.setYearElectricity(get173Total(yearList173));
+                    vo.setLastYearElectricity(get173Total(sameDayLastYearList173));
+                    // 能耗
+                    vo.setYear(get173Total(yearList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    vo.setLastYear(get173Total(sameDayLastYearList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    // 环比
+                    vo.setYearRingRatio(calcMoM(time, yearList173, sameDayLastYearList173));
+                    // 同比
+                    vo.setYearOnYear(get173Total(sameDayLastYearList173).multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+                    vo.setYearOnYearPercent(calcMoM(time, yearList173, sameDayLastYearList173));
+
+                    // 上一年总能耗
+                    // List<HtAnalog173Data> analogData = get173Data(data173, time.minusYears(1), 3);
+                    // BigDecimal sameYearLastYearEnergy = get173Total(analogData).multiply(coefficient).setScale(3, RoundingMode.HALF_UP);
+                }
+                break;
+        }
+        return vo;
+    }
+
+    // 获取173数据
+    public List<HtAnalog173Data> get173Data(List<String> deviceCodeList, LocalDateTime localDateTime, int timeType) {
+
+        LocalDate data = localDateTime.toLocalDate();
+        LocalDate monthFirstDay = data.with(TemporalAdjusters.firstDayOfMonth());
+        LocalDate monthLastDay = data.with(TemporalAdjusters.lastDayOfMonth());
+        LocalDate yearFirstDay = data.with(TemporalAdjusters.firstDayOfYear());
+        LocalDate yearLastDay = data.with(TemporalAdjusters.lastDayOfYear());
+        LocalDateTime dayFirstTime = LocalDateTime.of(data, LocalTime.MIN);
+
+        LambdaQueryWrapper<HtAnalog173Data> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.in(HtAnalog173Data::getDeviceName, deviceCodeList);
+
+        switch (timeType) {
+            // 日
+            case 1:
+                queryWrapper.eq(HtAnalog173Data::getFreezingTime, data);
+                break;
+            // 月
+            case 2:
+                queryWrapper.between(HtAnalog173Data::getFreezingTime, monthFirstDay, monthLastDay);
+                break;
+            // 年
+            case 3:
+                queryWrapper.between(HtAnalog173Data::getFreezingTime, yearFirstDay, yearLastDay);
+                break;
+
+            // 同期
+            case 11:
+                queryWrapper.between(HtAnalog173Data::getDataTime, dayFirstTime, localDateTime);
+                break;
+            case 22:
+                queryWrapper.between(HtAnalog173Data::getFreezingTime, monthFirstDay, data);
+                break;
+            case 33:
+                queryWrapper.between(HtAnalog173Data::getFreezingTime, yearFirstDay, data);
+                break;
+            default:
+                log.error("173无效的时间类型: " + timeType);
+                return Collections.emptyList();
+        }
+
+        queryWrapper.select(HtAnalog173Data::getDeviceName,
+                        HtAnalog173Data::getFreezingTime,
+                        HtAnalog173Data::getEpp,
+                        HtAnalog173Data::getDataTime)
+                .orderByDesc(HtAnalog173Data::getDataTime);
+
+        return htAnalog173DataMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 获取173数据总量
+     */
+    public BigDecimal get173Total(List<HtAnalog173Data> dataList) {
+        if (CollectionUtils.isEmpty(dataList)) {
+            return BigDecimal.ZERO;
+        }
+        return dataList.stream()
+                .collect(Collectors.groupingBy(HtAnalog173Data::getDeviceName))
+                .values()
+                .stream()
+                .map(list -> list.stream()
+                        .sorted(Comparator.comparing(HtAnalog173Data::getDataTime))
+                        .map(HtAnalog173Data::getEpp)
+                        .reduce((first, last) -> last.subtract(first))
+                        .orElse(BigDecimal.ZERO))
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    /**
+     * 计算环比
+     * @param localDateTime  基准时间(含)
+     * @param dayList173     日数据集合
+     * @param yearList173    年数据集合
+     * @return 环比值,已四舍五入到小数点后两位
+     */
+    public BigDecimal calcMoM(LocalDateTime localDateTime,
+                              List<HtAnalog173Data> dayList173,
+                              List<HtAnalog173Data> yearList173) {
+
+        BigDecimal daySum = calcDeviceDiffSum(dayList173, localDateTime);
+        BigDecimal yearSum = calcDeviceDiffSum(yearList173, localDateTime);
+
+        if (yearSum.compareTo(BigDecimal.ZERO) == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        return daySum
+                // 先多保留几位,避免中间精度损失
+                .divide(yearSum, 4, RoundingMode.HALF_UP)
+                .setScale(3, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 通用内部方法:对给定集合按设备分组,并计算每台设备
+     * “localDateTime 之前”最后一条 epp - 第一条 epp 的差值总和
+     */
+    private BigDecimal calcDeviceDiffSum(List<HtAnalog173Data> list,
+                                         LocalDateTime localDateTime) {
+
+        if (CollectionUtils.isEmpty(list)) {
+            return BigDecimal.ZERO;
+        }
+
+        return list.stream()
+                .filter(d -> !d.getDataTime().isAfter(localDateTime))
+                .collect(Collectors.groupingBy(HtAnalog173Data::getDeviceName))
+                .values()
+                .stream()
+                .map(deviceList -> deviceList.stream()
+                        .sorted(Comparator.comparing(HtAnalog173Data::getDataTime))
+                        .map(HtAnalog173Data::getEpp)
+                        .reduce((first, last) -> last.subtract(first))
+                        .orElse(BigDecimal.ZERO))
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    /**
+     * 计算指定时期内的数据总量
+     */
+    public BigDecimal getTotal(List<HtAnalogData> dataList) {
+        if (dataList == null || dataList.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+
+        HtAnalogData max = dataList.get(0), min = dataList.get(dataList.size() - 1);
+        // for (int i = 1, size = dataList.size(); i < size; i++) {
+        //     HtAnalogData d = dataList.get(i);
+        //     if (d.getDataTime().isAfter(max.getDataTime())) {
+        //         max = d;
+        //     }
+        //     if (d.getDataTime().isBefore(min.getDataTime())) {
+        //         min = d;
+        //     }
+        // }
+        return max.getEpp().subtract(min.getEpp());
+    }
+
+    /**
+     * 获取站点下的设备
+     */
+    private List<Device> getDevices(Integer siteId) {
+        LambdaQueryWrapper<Device> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(Device::getSiteId, siteId)
+                .eq(Device::getEnable, 1)
+                .select(Device::getDeviceCode, Device::getDeviceType);
+        return deviceMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 获取设备三相有功功率值
+     * @param deviceCode 设备编码
+     * @param localDateTime       日期
+     * @param timeType   时间类型 日月年 123 日月年同期 11 22 33
+     * @return 设备三相有功功率值
+     */
+    public List<HtAnalogData> getAnalogData(String deviceCode, LocalDateTime localDateTime, int timeType) {
+        LocalDate data = localDateTime.toLocalDate();
+        LocalDate monthFirstDay = data.with(TemporalAdjusters.firstDayOfMonth());
+        LocalDateTime monthFirstTime = LocalDateTime.of(monthFirstDay, LocalTime.MIN);
+        LocalDate monthMonthLastDay = data.with(TemporalAdjusters.lastDayOfMonth());
+        LocalDate yearFirstDay = data.with(TemporalAdjusters.firstDayOfYear());
+        LocalDate yearLastDay = data.with(TemporalAdjusters.lastDayOfYear());
+        LocalDateTime yearFirstTime = LocalDateTime.of(yearFirstDay, LocalTime.MIN);
+        LocalDateTime dayFirstTime = LocalDateTime.of(data, LocalTime.MIN);
+
+        LambdaQueryWrapper<HtAnalogData> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.select(HtAnalogData::getDeviceName,
+                HtAnalogData::getFreezingTime,
+                HtAnalogData::getEpp,
+                HtAnalogData::getDataTime);
+        queryWrapper.eq(HtAnalogData::getDeviceName, deviceCode);
+        switch (timeType) {
+            // 日
+            case 1:
+                queryWrapper.eq(HtAnalogData::getFreezingTime, data);
+                break;
+            // 月
+            case 2:
+                queryWrapper.between(HtAnalogData::getFreezingTime, monthFirstDay, monthMonthLastDay);
+                break;
+            // 年
+            case 3:
+                queryWrapper.between(HtAnalogData::getFreezingTime, yearFirstDay, yearLastDay);
+                break;
+
+            // 昨日同期
+            case 11:
+                queryWrapper.between(HtAnalogData::getDataTime, dayFirstTime.minusDays(1), localDateTime.minusDays(1));
+                break;
+            // 上月同期
+            case 22:
+                queryWrapper.between(HtAnalogData::getDataTime, monthFirstTime.minusMonths(1), localDateTime.minusMonths(1));
+                break;
+            // 上年同期
+            case 33:
+                queryWrapper.between(HtAnalogData::getDataTime, yearFirstTime.minusYears(1), localDateTime.minusYears(1));
+                break;
+            default:
+                log.error("183无效的时间类型: " + timeType);
+                return Collections.emptyList();
+        }
+        queryWrapper.orderByDesc(HtAnalogData::getDataTime);
+        return htAnalogDataMapper.selectList(queryWrapper);
+    }
+
+// /**
+//  * 获取差值列表
+//  *
+//  * @param timeType 时间类型
+//  * @param timeList 时间列表
+//  * @return 时间值列表
+//  */
+// public List<BigDecimal> getTimeValues(List<HtAnalogData> source,
+//                                       Integer timeType,
+//                                       List<String> timeList) {
+//
+//     if (source == null || source.isEmpty() || timeList == null || timeList.isEmpty()) {
+//         return Collections.nCopies(timeList == null ? 0 : timeList.size(), BigDecimal.ZERO);
+//     }
+//
+//     // 提取时间“槽位”的映射函数
+//     Function<HtAnalogData, Integer> slotExtractor;
+//     switch (timeType) {
+//         // 日 -> 小时 0-23
+//         case 1:
+//             slotExtractor = d -> d.getDataTime().getHour();
+//             break;
+//         // 月 -> 天 1-31(需与 timeList 的索引对齐)
+//         case 2:
+//             slotExtractor = d -> d.getDataTime().getDayOfMonth() - 1;
+//             break;
+//         // 年 -> 月 1-12
+//         case 3:
+//             slotExtractor = d -> d.getDataTime().getMonthValue() - 1;
+//             break;
+//         default:
+//             throw new IllegalArgumentException("不支持的 timeType: " + timeType);
+//     }
+//
+//     // 按槽位分组并排序
+//     Map<Integer, List<HtAnalogData>> grouped = source.stream()
+//             .filter(Objects::nonNull)
+//             .sorted(Comparator.comparing(HtAnalogData::getDataTime))
+//             .collect(Collectors.groupingBy(slotExtractor));
+//
+//     // 构造结果,顺序与 timeList 保持一致
+//     List<BigDecimal> result = new ArrayList<>(Collections.nCopies(timeList.size(), BigDecimal.ZERO));
+//
+//     grouped.forEach((slot, list) -> {
+//         if (slot >= 0 && slot < timeList.size() && list.size() >= 2) {
+//             BigDecimal first = list.get(0).getEpp();
+//             BigDecimal last = list.get(list.size() - 1).getEpp();
+//             result.set(slot, last.subtract(first));
+//         }
+//     });
+//
+//     return result;
+// }
+
+    /**
+     * 获取环比
+     *
+     * @param curList  第一个列表
+     * @param preList 第二个列表
+     * @return 环比
+     */
+    public BigDecimal getRingRatio(List<HtAnalogData> curList,
+                                   List<HtAnalogData> preList) {
+        if (curList.isEmpty() || preList.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+
+        // 已倒序:0 是最新,size-1 是最早
+        BigDecimal curDiff = curList.get(0).getEpp()
+                .subtract(curList.get(curList.size() - 1).getEpp());
+        BigDecimal preDiff = preList.get(0).getEpp()
+                .subtract(preList.get(preList.size() - 1).getEpp());
+
+        if (preDiff.compareTo(BigDecimal.ZERO) == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        // 计算环比:(cur - pre) / pre
+        return curDiff.subtract(preDiff)
+                .divide(preDiff, 3, RoundingMode.HALF_UP);
+    }
+
+// @Override
+// public HtAnalogEnergySegmentedVo getSegmentedData(Integer siteId, String queryPeriod, LocalDateTime queryTime, String queryType) {
+//     HtAnalogEnergySegmentedVo vo = new HtAnalogEnergySegmentedVo();
+//     List<String> devices183 = new ArrayList<>();
+//     List<String> devices173 = new ArrayList<>();
+//
+//     List<HtAnalogData> data183 = new ArrayList<>();
+//     List<HtAnalogData> previousData183 = new ArrayList<>();
+//     List<HtAnalog173Data> data173 = new ArrayList<>();
+//     List<HtAnalog173Data> previousData173 = new ArrayList<>();
+//
+//     List<BigDecimal> amountList = new ArrayList<>();
+//     List<LocalDateTime> timeKeys = new ArrayList<>();
+//
+//     List<Device> devices = getDevices(siteId);
+//     if (devices.isEmpty()) {
+//         log.warn("站点 {} 无设备!", siteId);
+//         return vo;
+//     }
+//
+//     for (Device device : devices) {
+//         String deviceType = device.getDeviceType();
+//         String deviceCode = device.getDeviceCode();
+//         if ("1".equals(deviceType)) {
+//             devices183.add(deviceCode);
+//         } else if ("4".equals(deviceType)) {
+//             devices173.add(deviceCode);
+//         }
+//     }
+//     switch (queryPeriod) {
+//         case "hour":
+//             if (!devices183.isEmpty()) {
+//                 data183 = getAnalogData(devices183.get(0), queryTime, 1);
+//                 previousData183 = getAnalogData(devices183.get(0), queryTime.minusDays(1), 1);
+//
+//                 amountList =
+//                         timeKeys =
+//             } else if (!devices173.isEmpty()) {
+//                 data173 = get173Data(devices173, queryTime, 1);
+//                 previousData173 = get173Data(devices173, queryTime.minusDays(1), 1);
+//
+//                 vo.setRatio(getRingRatio(data183, previousData183));
+//             }
+//             break;
+//         case "day":
+//             if (!devices183.isEmpty()) {
+//                 data183 = getAnalogData(devices183.get(0), queryTime, 2);
+//                 previousData183 = getAnalogData(devices183.get(0), queryTime.minusMonths(1), 2);
+//
+//                 amountList =
+//                         timeKeys =
+//             } else if (!devices173.isEmpty()) {
+//                 data173 = get173Data(devices173, queryTime, 2);
+//                 previousData173 = get173Data(devices173, queryTime.minusMonths(1), 2);
+//             }
+//             break;
+//         case "month":
+//             if (!devices183.isEmpty()) {
+//                 data183 = getAnalogData(devices183.get(0), queryTime, 3);
+//                 previousData183 = getAnalogData(devices183.get(0), queryTime.minusYears(1), 3);
+//                 amountList =
+//                         timeKeys =
+//             } else if (!devices173.isEmpty()) {
+//                 data173 = get173Data(devices173, queryTime, 3);
+//                 previousData173 = get173Data(devices173, queryTime.minusYears(1), 3);
+//             }
+//             break;
+//         default:
+//             return vo;
+//     }
+//
+//     if (!data183.isEmpty()) {
+//         vo.setCurrent(getTotal(data183));
+//         vo.setPrevious(getTotal(previousData183));
+//         vo.setRatio(getRingRatio(data183, previousData183));
+//     } else if (!data173.isEmpty()) {
+//         vo.setCurrent(data173.get(0).getEpp());
+//         vo.setPrevious(previousData173.get(0).getEpp());
+//         vo.setRatio(calcMoM(queryTime, data173, previousData173));
+//     } else {
+//         return vo;
+//     }
+//
+//     vo.setTimeKeys(timeKeys);
+//     vo.setAmountList(amountList);
+//     vo.setUnit();
+//     vo.setType(queryPeriod);
+//     return vo;
+// }
+
+    /**
+     * 获取分段数据
+     * @param siteId 站点id
+     * @param queryPeriod 查询周期 day month year
+     * @param queryTime 查询时间
+     * @param queryType  查询类型(energy 能耗;electric 电;cost 费用)
+     * @return 分段图表数据
+     */
+    @Override
+    public HtAnalogEnergySegmentedVo getSegmentedData(Integer siteId, String queryPeriod, LocalDateTime queryTime, String queryType) {
+        HtAnalogEnergySegmentedVo vo = new HtAnalogEnergySegmentedVo();
+        vo.setType("day".equals(queryPeriod) ? "hour" : "month".equals(queryPeriod) ? "day" : "year".equals(queryPeriod) ? "month" : "day");
+
+        /* ====== 1. 准备设备列表 ====== */
+        List<String> devices183 = new ArrayList<>();
+        List<String> devices173 = new ArrayList<>();
+
+        BigDecimal current = BigDecimal.ZERO;
+        BigDecimal previous = BigDecimal.ZERO;
+        BigDecimal ratio = BigDecimal.ZERO;
+
+        List<Device> devices = getDevices(siteId);
+        if (devices.isEmpty()) {
+            log.warn("站点 {} 无设备!", siteId);
+            return vo;
+        }
+
+        for (Device device : devices) {
+            String deviceType = device.getDeviceType();
+            String deviceCode = device.getDeviceCode();
+            if ("1".equals(deviceType)) {
+                devices183.add(deviceCode);
+            } else if ("4".equals(deviceType)) {
+                devices173.add(deviceCode);
+            }
+        }
+        if (devices183.isEmpty() && devices173.isEmpty()) {
+            log.warn("站点 {} 无有效电力设备!", siteId);
+            return vo;
+        }
+
+        /* ====== 2. 计算时间片 timeKeys ====== */
+        LocalDate localDate = queryTime.toLocalDate();
+        LocalDate today = LocalDate.now();
+        List<LocalDateTime> timeKeys = buildTimeKeys(queryPeriod, localDate);
+
+        /* ====== 3. 查询原始数据 ====== */
+        List<HtAnalogData> current183 = new ArrayList<>();
+        List<HtAnalogData> previous183 = new ArrayList<>();
+        List<HtAnalog173Data> current173 = new ArrayList<>();
+        List<HtAnalog173Data> previous173 = new ArrayList<>();
+
+        int periodInt = periodType2Int(queryPeriod);
+        if (!devices183.isEmpty()) {
+            current183 = getAnalogData(devices183.get(0), queryTime, periodInt);
+            previous183 = getAnalogData(devices183.get(0), queryTime, periodInt == 1 ? 11 : periodInt == 2 ? 22 : periodInt == 3 ? 33 : 11);
+        } else {
+            current173 = get173Data(devices173, queryTime, periodInt);
+            previous173 = get173Data(devices173, queryTime, periodInt == 1 ? 11 : periodInt == 2 ? 22 : periodInt == 3 ? 33 : 11);
+        }
+
+        /* ====== 4. 计算 amountList 与总量 ====== */
+        List<BigDecimal> amountList = zeroList(timeKeys.size());
+        if (!current183.isEmpty()) {
+            int pastOrPresent = localDate.isBefore(today) ? 1 : 0;
+            amountList = buildAmountList(timeKeys, current183, periodInt, pastOrPresent);
+            current = (calcTotal(current183));
+            previous = (calcTotal(previous183));
+            ratio = (getRingRatio(current183, previous183));
+        } else if (!current173.isEmpty()) {
+            amountList = buildAmountList173(timeKeys, current173, periodInt);
+            current = (current173.get(0).getEpp());
+            previous = (previous173.get(0).getEpp());
+            ratio = (calcMoM(queryTime, current173, previous173));
+        }
+
+        // 查询站点能耗转换情况(等价 or 当量)
+        SiteDynamicProperties siteDynamicProperties = siteDynamicPropertiesService.selectOne(siteId);
+        if (siteDynamicProperties == null) {
+            log.warn("站点{}未配置能耗属性等数据", siteId);
+            vo.setRatio(BigDecimal.ZERO);
+            vo.setUnit("energy".equals(queryType) ? "吨标准煤" : "electric".equals(queryType) ? "kwh" : "cost".equals(queryType) ? "元" : "kWh");
+            vo.setCurrent(BigDecimal.ZERO);
+            vo.setPrevious(BigDecimal.ZERO);
+            vo.setTimeKeys(timeKeys);
+            vo.setAmountList(zeroList(timeKeys.size()));
+            return vo;
+        }
+        Integer demolitionStandardCoal = siteDynamicProperties.getDemolitionStandardCoal();
+        BigDecimal coefficient = new BigDecimal(siteDynamicProperties.getDemolitionStandardCoal1())
+                .divide(new BigDecimal(demolitionStandardCoal), 10, RoundingMode.DOWN);
+
+        // 根据类型设置单位 (energy 能耗;electric 电;cost)
+        if ("energy".equals(queryType)) {
+            vo.setUnit("吨标准煤");
+            vo.setCurrent(current.multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+            vo.setPrevious(previous.multiply(coefficient).setScale(3, RoundingMode.HALF_UP));
+            vo.setAmountList(energyConversion(amountList, coefficient));
+            vo.setRatio(ratio);
+        } else if ("electric".equals(queryType)) {
+            vo.setUnit("kwh");
+            vo.setCurrent(current);
+            vo.setPrevious(previous);
+            vo.setAmountList(amountList);
+            vo.setRatio(ratio);
+        } else if ("cost".equals(queryType)) {
+            vo.setUnit("元");
+            switch (queryPeriod) {
+                case "day":
+                    List<BigDecimal> dayCostList = cost(amountList, siteId, localDate.getMonthValue());
+                    vo.setAmountList(dayCostList);
+                    vo.setCurrent(dayCostList.stream()
+                            .filter(Objects::nonNull)
+                            .reduce(BigDecimal.ZERO, BigDecimal::add));
+                    break;
+                case "month": {
+                    List<SiteElectricityRecord> siteElectricityRecords = calculateTimeFee(siteId, localDate, 2);
+                    vo.setCurrent(siteElectricityRecords.stream()
+                            .filter(Objects::nonNull)
+                            .map(SiteElectricityRecord::getTotalCost)
+                            .reduce(BigDecimal.ZERO, BigDecimal::add));
+
+                    List<SiteElectricityRecord> monthRecords = calculateTimeFee(siteId, localDate, 2);
+                    vo.setAmountList(getMonthYearList(monthRecords, timeKeys, "month"));
+                    break;
+                }
+                case "year": {
+                    List<SiteElectricityRecord> siteElectricityRecords = calculateTimeFee(siteId, localDate, 3);
+                    vo.setCurrent(siteElectricityRecords.stream()
+                            .filter(Objects::nonNull)
+                            .map(SiteElectricityRecord::getTotalCost)
+                            .reduce(BigDecimal.ZERO, BigDecimal::add));
+
+                    List<SiteElectricityRecord> monthRecords = calculateTimeFee(siteId, localDate, 3);
+                    vo.setAmountList(getMonthYearList(monthRecords, timeKeys, "year"));
+                    break;
+                }
+            }
+        } else {
+            vo.setUnit("未知单位");
+        }
+        vo.setTimeKeys(timeKeys);
+        return vo;
+    }
+
+    /**
+     * 获取月、年费用列表
+     */
+    private List<BigDecimal> getMonthYearList(List<SiteElectricityRecord> records, List<LocalDateTime> timeKeys, String queryPeriod) {
+        List<BigDecimal> result = new ArrayList<>();
+        if ("month".equals(queryPeriod)) {
+            Map<LocalDate, SiteElectricityRecord> recordMap = records.stream()
+                    .collect(Collectors.toMap(SiteElectricityRecord::getDate, r -> r));
+            result = timeKeys.stream()
+                    .map(time -> {
+                        SiteElectricityRecord r = recordMap.get(time.toLocalDate());
+                        return r == null ? null : r.getTotalCost();
+                    })
+                    .collect(Collectors.toList());
+        } else if ("year".equals(queryPeriod)) {
+            Map<YearMonth, BigDecimal> monthMap = records.stream()
+                    .collect(Collectors.groupingBy(
+                            r -> YearMonth.from(r.getDate()),
+                            Collectors.reducing(BigDecimal.ZERO,
+                                    SiteElectricityRecord::getTotalCost,
+                                    BigDecimal::add)));
+
+            result =  timeKeys.stream()
+                    .map(t -> monthMap.getOrDefault(YearMonth.from(t), null))
+                    .collect(Collectors.toList());
+        }
+        return result;
+    }
+
+    /**
+     * 日费用计算
+     */
+    private List<BigDecimal> cost(List<BigDecimal> dataList, Integer siteId, Integer month) {
+        // 1. 预填充 24 个 null,避免 set 越界
+        List<BigDecimal> costList = new ArrayList<>(Collections.nCopies(24, null));
+
+        List<ElectricityTimePrice> priceList = getElectricityTimePrice(siteId);
+
+        // 2. 不分时
+        for (ElectricityTimePrice p : priceList) {
+            if (!month.equals(p.getMonth())) continue;
+            if (p.getPriceType().equals(2)) {
+                BigDecimal price = p.getElectricityPrice();
+                for (int h = 0; h < 24; h++) {
+                    BigDecimal val = dataList.get(h);
+                    costList.set(h, val == null ? null : val.multiply(price).setScale(2, RoundingMode.HALF_UP));
+                }
+                return costList;
+            }
+        }
+
+        // 3. 分时
+        for (ElectricityTimePrice p : priceList) {
+            if (!month.equals(p.getMonth()) || !p.getPriceType().equals(1)) continue;
+
+            int start = p.getStartTime().getHour();
+            int end = p.getEndTime().getHour();   // 约定 start < end
+            BigDecimal price = p.getElectricityPrice();
+
+            for (int h = start; h < end; h++) {
+                BigDecimal val = dataList.get(h);
+                costList.set(h, val == null ? null : val.multiply(price).setScale(2, RoundingMode.HALF_UP));
+            }
+        }
+
+        return costList;
+    }
+
+    /**
+     * 获取站点的时段计价
+     */
+    private List<ElectricityTimePrice> getElectricityTimePrice(Integer siteId) {
+        LambdaQueryWrapper<ElectricityTimePrice> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.select(ElectricityTimePrice::getMonth, ElectricityTimePrice::getStartTime, ElectricityTimePrice::getEndTime, ElectricityTimePrice::getElectricityPrice, ElectricityTimePrice::getPriceType)
+                .eq(ElectricityTimePrice::getSiteId, siteId);
+        return electricityTimePriceMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 能耗转换
+     */
+    private List<BigDecimal> energyConversion(List<BigDecimal> amountList, BigDecimal coefficient) {
+        return amountList.stream()
+                .map(e -> Optional.ofNullable(e)
+                        .map(v -> v.multiply(coefficient).setScale(3, RoundingMode.HALF_UP))
+                        .orElse(null))
+                .collect(Collectors.toList());
+    }
+
+    /** 根据 queryPeriod 生成“从周期第一天 00:00 开始”的列表 */
+    private List<LocalDateTime> buildTimeKeys(String queryPeriod, LocalDate base) {
+        List<LocalDateTime> list = new ArrayList<>();
+        switch (queryPeriod) {
+            case "day":
+                // 00:00 ~ 23:00 共 24 个时间点
+                for (int i = 0; i < 24; i++) {
+                    list.add(base.atStartOfDay().plusHours(i));
+                }
+                break;
+
+            case "month":
+                // 当月每一天的 00:00
+                YearMonth ym = YearMonth.from(base);
+                LocalDate start = ym.atDay(1);
+                int days = ym.lengthOfMonth();
+                for (int i = 0; i < days; i++) {
+                    list.add(start.plusDays(i).atStartOfDay());
+                }
+                break;
+
+            case "year":
+                // 当年 1~12 月,每月 1 号 00:00
+                LocalDate yearStart = base.withDayOfYear(1);
+                for (int i = 0; i < 12; i++) {
+                    list.add(yearStart.plusMonths(i).atStartOfDay());
+                }
+                break;
+
+            default:
+                throw new BusinessException("queryPeriod 参数错误!检查后重试");
+        }
+        return list;
+    }
+
+    /**
+     * 计算每个小时的【消耗电量】
+     * 规则:如果一小时内有多个数据,则用(lastEpp - firstEpp)
+     *      如果一小时内只有一个数据,则用(currentEpp - previousHourLastEpp)
+     *      如果没有数据,则使用0
+     */
+    /*private List<BigDecimal> buildAmountList(List<LocalDateTime> timeKeys,
+                                             List<HtAnalogData> src) {
+        if (src.isEmpty()) {
+            return zeroList(timeKeys.size());
+        }
+
+        // 先把原始数据按“小时起点”分组
+        Map<LocalDateTime, List<HtAnalogData>> groupByHour =
+                src.stream()
+                        .collect(Collectors.groupingBy(
+                                d -> alignToPeriod(d.getDataTime(), "hour")));
+
+        List<BigDecimal> result = new ArrayList<>(timeKeys.size());
+        BigDecimal previousLast = null; // 上一小时的最后一个值
+
+        for (LocalDateTime hourStart : timeKeys) {
+            List<HtAnalogData> oneHour = groupByHour.get(hourStart);
+            if (oneHour == null || oneHour.isEmpty()) {
+                result.add(BigDecimal.ZERO);
+            } else {
+                // 升序排序
+                oneHour.sort(Comparator.comparing(HtAnalogData::getDataTime));
+                BigDecimal first = oneHour.get(0).getEpp();
+                BigDecimal last = oneHour.get(oneHour.size() - 1).getEpp();
+
+                BigDecimal amount;
+                if (oneHour.size() == 1 && previousLast != null) {
+                    // 只有一条数据且有上一小时的数据,用上一小时最后值计算
+                    amount = last.subtract(previousLast);
+                } else {
+                    // 多条数据,用本小时内差值
+                    amount = last.subtract(first);
+                }
+
+                result.add(amount);
+                previousLast = last; // 更新上一小时的最后值
+            }
+        }
+        return result;
+    }*/
+    private List<BigDecimal> buildAmountList(List<LocalDateTime> timeKeys,
+                                             List<HtAnalogData> src,
+                                             int periodInt,
+                                             int periodPastOrPresent) {
+
+        String period = periodInt == 1 ? "hour"
+                : periodInt == 2 ? "day"
+                : periodInt == 3 ? "month"
+                : "hour";
+
+        if (src.isEmpty()) {
+            return zeroOrNullList(timeKeys.size(), periodPastOrPresent, period, timeKeys);
+        }
+
+        // 原始数据按周期起点分组
+        Map<LocalDateTime, List<HtAnalogData>> groupByPeriod =
+                src.stream()
+                        .collect(Collectors.groupingBy(
+                                d -> alignToPeriod(d.getDataTime(), period)));
+
+        // 当前周期起点
+        LocalDateTime nowPeriodStart = alignToPeriod(LocalDateTime.now(), period);
+
+        List<BigDecimal> result = new ArrayList<>(timeKeys.size());
+
+        for (LocalDateTime periodStart : timeKeys) {
+            // 如果要求对未来补 null 且当前 periodStart 晚于 nowPeriodStart
+            if (periodPastOrPresent == 0 && periodStart.isAfter(nowPeriodStart)) {
+                result.add(null);
+                continue;
+            }
+
+            List<HtAnalogData> oneGroup = groupByPeriod.get(periodStart);
+            if (oneGroup == null || oneGroup.isEmpty()) {
+                result.add(BigDecimal.ZERO);
+            } else {
+                oneGroup.sort(Comparator.comparing(HtAnalogData::getDataTime));
+                BigDecimal first = oneGroup.get(0).getEpp();
+                BigDecimal last = oneGroup.get(oneGroup.size() - 1).getEpp();
+                result.add(last.subtract(first));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 当 src 为空时,根据 periodPastOrPresent 决定补 0 还是补 null。
+     * periodPastOrPresent == 0 时,把未来时间补 null,其余补 0。
+     */
+    private List<BigDecimal> zeroOrNullList(int size,
+                                            int periodPastOrPresent,
+                                            String period,
+                                            List<LocalDateTime> timeKeys) {
+        if (periodPastOrPresent == 1) {
+            // 全部补 0
+            return Collections.nCopies(size, BigDecimal.ZERO);
+        }
+
+        LocalDateTime nowPeriodStart = alignToPeriod(LocalDateTime.now(), period);
+        List<BigDecimal> list = new ArrayList<>(size);
+        for (LocalDateTime periodStart : timeKeys) {
+            if (periodStart.isAfter(nowPeriodStart)) {
+                list.add(null);
+            } else {
+                list.add(BigDecimal.ZERO);
+            }
+        }
+        return list;
+    }
+
+    /**173 设备同理 */
+    /*private List<BigDecimal> buildAmountList173(List<LocalDateTime> timeKeys,
+                                                List<HtAnalog173Data> src) {
+        if (src.isEmpty()) {
+            return zeroList(timeKeys.size());
+        }
+
+        Map<LocalDateTime, List<HtAnalog173Data>> groupByHour =
+                src.stream()
+                        .collect(Collectors.groupingBy(
+                                d -> alignToPeriod(d.getDataTime(), "hour")));
+
+        List<BigDecimal> result = new ArrayList<>(timeKeys.size());
+        BigDecimal previousLast = null; // 上一小时的最后一个值
+
+        for (LocalDateTime hourStart : timeKeys) {
+            List<HtAnalog173Data> oneHour = groupByHour.get(hourStart);
+            if (oneHour == null || oneHour.isEmpty()) {
+                result.add(BigDecimal.ZERO);
+            } else {
+                oneHour.sort(Comparator.comparing(HtAnalog173Data::getDataTime));
+                BigDecimal first = oneHour.get(0).getEpp();
+                BigDecimal last = oneHour.get(oneHour.size() - 1).getEpp();
+
+                BigDecimal amount;
+                if (oneHour.size() == 1 && previousLast != null) {
+                    // 只有一条数据且有上一小时的数据,用上一小时最后值计算
+                    amount = last.subtract(previousLast);
+                } else {
+                    // 多条数据,用本小时内差值
+                    amount = last.subtract(first);
+                }
+
+                result.add(amount);
+                // 更新上一小时的最后值
+                previousLast = last;
+            }
+        }
+        return result;
+    }*/
+    private List<BigDecimal> buildAmountList173(List<LocalDateTime> timeKeys,
+                                                List<HtAnalog173Data> src,
+                                                int periodInt) {
+        String period = periodInt == 1 ? "hour" : periodInt == 2 ? "day" : periodInt == 3 ? "month" : "hour";
+        if (src.isEmpty()) {
+            return zeroList(timeKeys.size());
+        }
+
+        Map<LocalDateTime, List<HtAnalog173Data>> groupByHour =
+                src.stream()
+                        .collect(Collectors.groupingBy(
+                                d -> alignToPeriod(d.getDataTime(), period)));
+
+        List<BigDecimal> result = new ArrayList<>(timeKeys.size());
+
+        for (LocalDateTime hourStart : timeKeys) {
+            List<HtAnalog173Data> oneHour = groupByHour.get(hourStart);
+            if (oneHour == null || oneHour.isEmpty()) {
+                result.add(BigDecimal.ZERO);
+            } else {
+                oneHour.sort(Comparator.comparing(HtAnalog173Data::getDataTime));
+                BigDecimal first = oneHour.get(0).getEpp();
+                BigDecimal last = oneHour.get(oneHour.size() - 1).getEpp();
+                result.add(last.subtract(first));
+            }
+        }
+        return result;
+    }
+
+    /** 把一个时间对齐到时间片起点(小时/天/月) */
+    private LocalDateTime alignToPeriod(LocalDateTime t, String period) {
+        switch (period) {
+            case "hour":
+                return t.withMinute(0).withSecond(0).withNano(0);
+            case "day":
+                return t.toLocalDate().atStartOfDay();
+            case "month":
+                return t.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    /** 用“最后一条 - 第一条”计算周期总量 */
+    private BigDecimal calcTotal(List<HtAnalogData> list) {
+        if (list.size() < 2) {
+            return BigDecimal.ZERO;
+        }
+        return list.get(0).getEpp()
+                .subtract(list.get(list.size() - 1).getEpp());
+    }
+
+    private int periodType2Int(String period) {
+        switch (period) {
+            case "day":
+                return 1;
+            case "month":
+                return 2;
+            case "year":
+                return 3;
+            default:
+                throw new BusinessException("period 参数错误!检查后重试");
+        }
+    }
+
+    /** 返回一个固定长度的 0 列表,避免空数组 */
+    private List<BigDecimal> zeroList(int size) {
+        return new ArrayList<>(Collections.nCopies(size, BigDecimal.ZERO));
+    }
+
+    /** 获取一个站点的费率配置-基本电价配置 */
+    public ElectricityRateConfig getElectricityRateConfig(Integer siteId) {
+        LambdaQueryWrapper<ElectricityRateConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.select(ElectricityRateConfig::getExcessPercentage, ElectricityRateConfig::getAdditionalMultiplier,
+                        ElectricityRateConfig::getDemandPrice, ElectricityRateConfig::getDeclaredDemand, ElectricityRateConfig::getBillingMethod,
+                        ElectricityRateConfig::getRatedCapacity, ElectricityRateConfig::getCapacityPrice)
+                .eq(ElectricityRateConfig::getSiteId, siteId);
+        ElectricityRateConfig config = electricityRateConfigMapper.selectOne(wrapper);
+        if (config == null) {
+            throw new BusinessException("未找到该站点的电费配置!");
+        }
+        return config;
+    }
+
+    /**
+     * 获取基础电费
+     * @param list 历史数据
+     * @param config 费率配置
+     * @param siteId 站点ID
+     * @param electricity 电量
+     * @return 基础电费
+     */
+    public BigDecimal getBasicExpenses(List<HtAnalogData> list, ElectricityRateConfig config, Integer siteId, BigDecimal electricity) {
+
+        BigDecimal basicExpenses = BigDecimal.ZERO;
+        // 超过的百分比
+        BigDecimal excessPercentage = config.getExcessPercentage();
+        // 超过部分的计费倍数
+        BigDecimal multiple = config.getAdditionalMultiplier();
+        // 额定容量(kVA)
+        BigDecimal ratedCapacity = config.getRatedCapacity();
+        // 容量电价(元/ 千伏安)
+        BigDecimal capacityPrice = config.getCapacityPrice();
+        // 需量电价(元/ 千瓦)
+        BigDecimal demandPrice = config.getDemandPrice();
+        // 申报需量(kW)
+        BigDecimal declaredDemand = config.getDeclaredDemand();
+        // 基本电费核算方式(1 按合同最大需量, 2 按实际最大需量, 3 按变压器容量;默认 2)
+        Integer billingMethod = config.getBillingMethod();
+
+        switch (billingMethod) {
+            case 1:
+                // 按合同最大需量
+                basicExpenses = calculateContractDemandFee(list, declaredDemand, demandPrice, excessPercentage, multiple);
+                break;
+            case 2:
+                // 按实际最大需量
+                basicExpenses = calculateActualDemandFee(list, demandPrice);
+                break;
+            case 3:
+                // 按变压器容量
+                basicExpenses = calculateTransformerCapacityFee(ratedCapacity, capacityPrice);
+                break;
+        }
+
+        BigDecimal additionalElectricityPrice = additionalElectricityPrice(siteId, electricity);
+        if (additionalElectricityPrice.compareTo(BigDecimal.ZERO) > 0) {
+            basicExpenses = basicExpenses.add(additionalElectricityPrice);
+        }
+
+        return basicExpenses;
+    }
+
+    /**
+     * 获取附加电费
+     * @param siteId 站点ID
+     * @return 附加电费
+     */
+    private BigDecimal additionalElectricityPrice(Integer siteId, BigDecimal electricity) {
+        return getSiteInformation(siteId).stream()
+                .map(SiteInformation::getAdditionalElectricityPrice)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add)
+                .multiply(electricity)
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 获取站点附加电费信息
+     * @param siteId 站点ID
+     * @return 站点附加电费信息
+     */
+    // private List<SiteInformation> getSiteInformation(Integer siteId) {
+    //     // 构建查询条件,只查询需要的字段
+    //     LambdaQueryWrapper<SiteInformation> queryWrapper = new LambdaQueryWrapper<>();
+    //     queryWrapper.eq(SiteInformation::getSiteId, siteId);
+    //
+    //     // 查询站点信息
+    //     List<SiteInformation> siteInformations = siteInformationMapper.selectList(queryWrapper);
+    //
+    //     // 处理空集合(无记录时直接返回空)
+    //     if (CollectionUtils.isEmpty(siteInformations)) {
+    //         return Collections.emptyList();
+    //     }
+    //
+    //     // 筛选出附加电费不为null的记录
+    //     List<SiteInformation> nonNullPriceList = siteInformations.stream()
+    //             .filter(info -> info.getAdditionalElectricityPrice() != null)
+    //             .collect(Collectors.toList());
+    //
+    //     // 判断:若所有记录的附加电费都为null(即筛选后集合为空),则返回空集合;否则返回筛选结果
+    //     return nonNullPriceList.isEmpty() ? Collections.emptyList() : nonNullPriceList;
+    // }
+    private List<SiteInformation> getSiteInformation(Integer siteId) {
+        return siteInformationMapper.selectList(
+                        new LambdaQueryWrapper<SiteInformation>()
+                                .eq(SiteInformation::getSiteId, siteId))
+                .stream()
+                .filter(info -> info.getAdditionalElectricityPrice() != null)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 计算实际最大需求
+     * @param dataList 数据列表
+     * @return 实际最大需量电费
+     */
+    private BigDecimal calculateActualMaxDemand(List<HtAnalogData> dataList) {
+        BigDecimal maxDemand = BigDecimal.ZERO;
+
+        for (int i = 0; i < dataList.size() - 1; i++) {
+            HtAnalogData start = dataList.get(i);
+            HtAnalogData end = dataList.get(i + 1);
+
+            // 计算时间差(分钟)
+            long minutes = ChronoUnit.MINUTES.between(start.getDataTime(), end.getDataTime());
+
+            // 动态窗口:仅计算10~20分钟内的数据(避免跨度过大或过小)
+            if (minutes >= 10 && minutes <= 20) {
+                BigDecimal deltaEpp = end.getEpp().subtract(start.getEpp());
+                BigDecimal avgPower = deltaEpp.divide(new BigDecimal(minutes / 60.0), 3, RoundingMode.HALF_UP);
+                maxDemand = maxDemand.max(avgPower);
+            }
+        }
+        return maxDemand;
+    }
+
+    /**
+     * 计算合同最大需量电费(含超额惩罚)
+     * @param dataList 数据列表
+     * @param contractDemand 合同最大需量/申报需量(kW)
+     * @param demandPrice 需量电价
+     * @return 合同最大需量电费
+     */
+    private BigDecimal calculateContractDemandFee(List<HtAnalogData> dataList, BigDecimal contractDemand, BigDecimal demandPrice,
+                                                  BigDecimal excessPercentage, BigDecimal multiple) {
+        BigDecimal actualDemand = calculateActualMaxDemand(dataList);
+
+        // 允许n%浮动
+        BigDecimal overDemand = actualDemand.subtract(contractDemand.multiply(new BigDecimal(1).add(excessPercentage)));
+
+        if (overDemand.compareTo(BigDecimal.ZERO) <= 0) {
+            // 未超105%,按合同计费
+            return contractDemand.multiply(demandPrice);
+        } else {
+            // 超出部分按n倍计费
+            return contractDemand.multiply(demandPrice).add(overDemand.multiply(multiple).multiply(demandPrice));
+        }
+    }
+
+    /**
+     * 计算实际最大需量电费(无惩罚)
+     * @param dataList 数据列表
+     * @param demandPrice 需量电价
+     * @return 实际最大需量电费
+     */
+    private BigDecimal calculateActualDemandFee(List<HtAnalogData> dataList, BigDecimal demandPrice) {
+        BigDecimal actualDemand = calculateActualMaxDemand(dataList);
+        return actualDemand.multiply(demandPrice);
+    }
+
+    /**
+     * 计算变压器容量电费(固定值)
+     * @param transformerCapacity 变压器容量
+     * @param capacityPrice 变压器容量电价
+     * @return 变压器容量电费
+     * */
+    private BigDecimal calculateTransformerCapacityFee(BigDecimal transformerCapacity, BigDecimal capacityPrice) {
+        return transformerCapacity.multiply(capacityPrice);
+    }
+
+    /**
+     * 分时电费查询
+     * @param siteId 站点ID
+     * @param queryDate 查询日期
+     * @param queryType 查询类型
+     * */
+    private List<SiteElectricityRecord> calculateTimeFee(Integer siteId, LocalDate queryDate, Integer queryType) {
+        LocalDate monthFirstDay = queryDate.with(TemporalAdjusters.firstDayOfMonth());
+        LocalDate monthLastDay = queryDate.with(TemporalAdjusters.lastDayOfMonth());
+        LocalDate yearFirstDay = queryDate.with(TemporalAdjusters.firstDayOfYear());
+        LocalDate yearLastDay = queryDate.with(TemporalAdjusters.lastDayOfYear());
+
+        LambdaQueryWrapper<SiteElectricityRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.select(SiteElectricityRecord::getTotalCost, SiteElectricityRecord::getDate)
+                .eq(SiteElectricityRecord::getSiteId, siteId);
+
+        switch (queryType) {
+            case 1:
+                queryWrapper.eq(SiteElectricityRecord::getDate, queryDate);
+                break;
+            case 2:
+                queryWrapper.between(SiteElectricityRecord::getDate, monthFirstDay, monthLastDay);
+                break;
+            case 3:
+                queryWrapper.between(SiteElectricityRecord::getDate, yearFirstDay, yearLastDay);
+                break;
+            case 22:
+                queryWrapper.between(SiteElectricityRecord::getDate, monthFirstDay, queryDate);
+                break;
+            case 33:
+                queryWrapper.between(SiteElectricityRecord::getDate, yearFirstDay, queryDate);
+                break;
+        }
+
+        return siteElectricityRecordMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 计算总费用
+     *
+     * @param records 电费记录列表
+     * @return 总费用
+     */
+    private BigDecimal calculateTotalCost(List<SiteElectricityRecord> records) {
+        if (records == null || records.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+
+        return records.stream()
+                .map(SiteElectricityRecord::getTotalCost)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+
 }

+ 13 - 0
fiveep-service/src/main/java/com/bizmatics/service/impl/SiteDynamicPropertiesServiceImpl.java

@@ -1,5 +1,7 @@
 package com.bizmatics.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.bizmatics.common.mvc.base.AbstractCrudService;
 import com.bizmatics.model.SiteDynamicProperties;
 import com.bizmatics.model.system.SysUser;
@@ -29,4 +31,15 @@ public class SiteDynamicPropertiesServiceImpl extends AbstractCrudService<SiteDy
     public void SiteDynamicPropertiesUpdate(SiteDynamicProperties siteDynamicProperties) {
         this.updateById(siteDynamicProperties);
     }
+
+    @Override
+    public SiteDynamicProperties selectOne(Integer siteId) {
+        LambdaQueryWrapper<SiteDynamicProperties> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(SiteDynamicProperties::getSiteId, siteId)
+                .select(SiteDynamicProperties::getSiteId,
+                        SiteDynamicProperties::getDemolitionStandardCoal,
+                        SiteDynamicProperties::getDemolitionStandardCoal1,
+                        SiteDynamicProperties::getPowerFactor);
+        return this.getOne(queryWrapper);
+    }
 }

+ 20 - 0
fiveep-service/src/main/java/com/bizmatics/service/impl/SiteElectricityRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.bizmatics.service.impl;
+
+import com.bizmatics.model.SiteElectricityRecord;
+import com.bizmatics.persistence.mapper.SiteElectricityRecordMapper;
+import com.bizmatics.service.SiteElectricityRecordService;
+import com.bizmatics.common.mvc.base.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 站点电费记录表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-08-21
+ */
+@Service
+public class SiteElectricityRecordServiceImpl extends AbstractCrudService<SiteElectricityRecordMapper, SiteElectricityRecord> implements SiteElectricityRecordService {
+
+}

+ 20 - 0
fiveep-service/src/main/java/com/bizmatics/service/impl/SiteInformationServiceImpl.java

@@ -0,0 +1,20 @@
+package com.bizmatics.service.impl;
+
+import com.bizmatics.model.SiteInformation;
+import com.bizmatics.persistence.mapper.SiteInformationMapper;
+import com.bizmatics.service.SiteInformationService;
+import com.bizmatics.common.mvc.base.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-07-17
+ */
+@Service
+public class SiteInformationServiceImpl extends AbstractCrudService<SiteInformationMapper, SiteInformation> implements SiteInformationService {
+
+}

+ 1 - 1
fiveep-service/src/main/java/com/bizmatics/service/impl/SiteServiceImpl.java

@@ -449,7 +449,7 @@ public class SiteServiceImpl extends AbstractCrudService<SiteMapper, Site> imple
             deviceLoadAnalysis.setRatedCapacity(ratedCapacity);
             deviceLoadAnalysis.setRatedVoltage(device.getRatedVoltage());
 
-            Date dateTime = pList.stream()
+            LocalDateTime dateTime = pList.stream()
                     .filter(ht -> ht.getDeviceName().equals(device.getDeviceCode()))
                     .findFirst()
                     .map(HtAnalogData::getDataTime)

+ 20 - 10
fiveep-service/src/main/java/com/bizmatics/service/interceptor/CheckExecuteInterceptor.java

@@ -7,6 +7,7 @@ import com.bizmatics.common.mvc.utils.ServletUtils;
 import com.bizmatics.common.spring.util.JsonUtils;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
@@ -22,11 +23,12 @@ import java.util.Map;
  * @date 2022/1/6 13:09
  */
 @Component
+@Slf4j
 public class CheckExecuteInterceptor extends HandlerInterceptorAdapter {
 
     private static final String PATH = "http://iot.usky.cn";
 
-    private static final String CHECK_URL = String.format("%s%s",PATH,"/ytapi/admin/Manage/wooGiJavaApi");
+    private static final String CHECK_URL = String.format("%s%s", PATH, "/ytapi/admin/Manage/wooGiJavaApi");
 
     private static final String JWT_KEY = "U0JBUElKV1RkV2FuZzkyNjQ1NA==";
 
@@ -34,21 +36,30 @@ public class CheckExecuteInterceptor extends HandlerInterceptorAdapter {
 
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        Map<String,Object> map = new HashMap<>();
-        map.put("loginName","wj");
+        Map<String, Object> map = new HashMap<>();
+        map.put("loginName", "wj");
         String token = createToken(map);
         String wj = String.format("%s?token=%s", CHECK_URL, token);
         Integer result;
         try {
-            result = Integer.parseInt(HttpUtils.get(wj, null));
-        }catch (Exception e){
-            result =  0;
+            // 记录请求的URL和token(注意:生产环境token需脱敏)
+            log.info("请求验证URL: {}, token: {}", CHECK_URL, token.substring(0, 6) + "...");
+
+            // 执行远程请求并记录返回值
+            String responseStr = HttpUtils.get(wj, null);
+            log.info("验证服务返回原始数据: {}", responseStr);
+            result = Integer.parseInt(responseStr);
+            log.info("解析后的验证结果: {}", result);
+        } catch (Exception e) {
+            log.error("验证请求失败!URL: {}", wj, e);
+            result = 0;
         }
-        if (result != 1){
+        if (result != 1) {
+            log.warn("验证未通过!result={}, 触发系统异常提示", result);
             ApiResult<Object> error = ApiResult.error(SystemErrorCode.SYS_SYSTEM_ERROR.getCode(), "系统异常请联系管理员");
             ServletUtils.renderString(response, JsonUtils.toJson(error));
             return false;
-        }else {
+        } else {
             return super.preHandle(request, response, handler);
         }
     }
@@ -59,8 +70,7 @@ public class CheckExecuteInterceptor extends HandlerInterceptorAdapter {
      * @param claims 数据声明
      * @return 令牌
      */
-    private String createToken(Map<String, Object> claims)
-    {
+    private String createToken(Map<String, Object> claims) {
         String token = Jwts.builder()
                 .setClaims(claims)
                 .signWith(SignatureAlgorithm.HS512, JWT_KEY).compact();

+ 1 - 1
fiveep-service/src/main/java/com/bizmatics/service/job/RatAnalogTask.java

@@ -113,7 +113,7 @@ public class RatAnalogTask {
             }
             HtAnalogData htAnalogData = htAnalogDataMapper.selectByEndTime(dayStartTime, hours, siteId, null);
             Optional.ofNullable(htAnalogData).ifPresent(hta -> {
-                hta.setDataTime(hours);
+                hta.setDataTime(DateUtils.toLocalDateTime(hours));
                 hisList.add(hta);
             });
             dayStartTime = hours;

+ 298 - 0
fiveep-service/src/main/java/com/bizmatics/service/job/SiteDailyElectricityCostTask.java

@@ -0,0 +1,298 @@
+package com.bizmatics.service.job;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+
+/**
+ * 日电费计算定时任务
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/8/22
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SiteDailyElectricityCostTask implements ApplicationRunner {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    /** 应用启动后立即执行一次 */
+    @Override
+    public void run(ApplicationArguments args) {
+        log.info("项目启动,立即执行一次电费日结...");
+        // calculateDailyElectricityCost();
+    }
+
+    // 每天凌晨0点过5分执行,计算前一天的用电费用
+    @Scheduled(cron = "0 5 0 * * ?")
+    public void calculateDailyElectricityCost() {
+        LocalDate yesterday = LocalDate.now().minusDays(1);
+        log.info("开始计算{}的用电费用", yesterday);
+
+        try {
+            calculateElectricityCostForDate(yesterday);
+            log.info("{}的用电费用计算完成", yesterday);
+        } catch (Exception e) {
+            log.error("计算{}用电费用时发生错误", yesterday, e);
+        }
+    }
+
+    public void calculateElectricityCostForDate(LocalDate date) {
+        // 获取所有需要处理的站点和设备
+        Map<Integer, List<String>> siteDevicesMap = getSiteDevicesMap();
+
+        log.info("找到{}个站点需要处理", siteDevicesMap.size());
+
+        for (Map.Entry<Integer, List<String>> entry : siteDevicesMap.entrySet()) {
+            Integer siteId = entry.getKey();
+            List<String> deviceCodes = entry.getValue();
+
+            log.info("处理站点{},包含{}个设备", siteId, deviceCodes.size());
+
+            for (String deviceCode : deviceCodes) {
+                try {
+                    calculateSiteDeviceCost(siteId, deviceCode, date);
+                    log.debug("站点{}设备{}在{}的电费计算完成", siteId, deviceCode, date);
+                } catch (Exception e) {
+                    log.error("计算站点{}设备{}在{}的电费时出错", siteId, deviceCode, date, e);
+                }
+            }
+        }
+    }
+
+    private Map<Integer, List<String>> getSiteDevicesMap() {
+        Map<Integer, List<String>> siteDevicesMap = new HashMap<>();
+
+        String sql = "SELECT site_id, device_code FROM device WHERE device_type = '1' AND enable = 1";
+
+        jdbcTemplate.query(sql, rs -> {
+            Integer siteId = rs.getInt("site_id");
+            String deviceCode = rs.getString("device_code");
+
+            if (siteId != 0 && deviceCode != null) {
+                siteDevicesMap.computeIfAbsent(siteId, k -> new ArrayList<>()).add(deviceCode);
+            }
+        });
+
+        return siteDevicesMap;
+    }
+
+    private void calculateSiteDeviceCost(Integer siteId, String deviceCode, LocalDate date) {
+        // 获取该站点的时段电价配置
+        List<TimePriceConfig> timePriceConfigs = getTimePriceConfigs(siteId, date.getMonthValue());
+
+        if (timePriceConfigs.isEmpty()) {
+            log.warn("站点{}在{}年{}月没有电价配置", siteId, date.getYear(), date.getMonthValue());
+            return;
+        }
+
+        // 获取该设备在指定日期的电力数据
+        List<ElectricityData> electricityDataList = getElectricityData(deviceCode, date);
+
+        if (electricityDataList.size() < 2) {
+            log.warn("设备{}在{}的电力数据不足,只有{}条记录", deviceCode, date, electricityDataList.size());
+            return;
+        }
+
+        // 按时间排序电力数据
+        electricityDataList.sort(Comparator.comparing(ElectricityData::getDataTime));
+
+        // 初始化费用统计
+        BigDecimal sharpPeakCost = BigDecimal.ZERO;
+        BigDecimal peakCost = BigDecimal.ZERO;
+        BigDecimal flatCost = BigDecimal.ZERO;
+        BigDecimal valleyCost = BigDecimal.ZERO;
+        BigDecimal totalCost = BigDecimal.ZERO;
+
+        // 计算每个时间段的电费
+        for (int i = 1; i < electricityDataList.size(); i++) {
+            ElectricityData prev = electricityDataList.get(i - 1);
+            ElectricityData curr = electricityDataList.get(i);
+
+            // 计算用电量(当前值减去前一个值)
+            double usage = curr.getEpp() - prev.getEpp();
+
+            if (usage <= 0) {
+                log.debug("设备{}在{}到{}时间段用电量为负或零,跳过", deviceCode,
+                        prev.getDataTime(), curr.getDataTime());
+                continue;
+            }
+
+            BigDecimal electricityUsage = BigDecimal.valueOf(usage);
+
+            // 确定数据点所在的时间段(取中间时间点)
+            LocalDateTime startTime = prev.getDataTime();
+            LocalDateTime endTime = curr.getDataTime();
+            LocalTime midTime = startTime.plusSeconds(
+                    (endTime.toLocalTime().toSecondOfDay() - startTime.toLocalTime().toSecondOfDay()) / 2
+            ).toLocalTime();
+
+            TimePriceConfig config = findTimePriceConfig(timePriceConfigs, midTime);
+
+            if (config == null) {
+                log.debug("设备{}在时间{}没有找到对应的电价配置", deviceCode, midTime);
+                continue;
+            }
+
+            // 计算电费:用电量 × 电价
+            BigDecimal cost = electricityUsage.multiply(config.getElectricityPrice());
+            totalCost = totalCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+
+            switch (config.getPeakValleyAttribute()) {
+                case 1: // 尖
+                    sharpPeakCost = sharpPeakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    break;
+                case 2: // 峰
+                    peakCost = peakCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    break;
+                case 3: // 平
+                    flatCost = flatCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    break;
+                case 4: // 谷
+                    valleyCost = valleyCost.add(cost).setScale(2, RoundingMode.HALF_UP);
+                    break;
+                default:
+                    log.warn("未知的峰谷属性: {}", config.getPeakValleyAttribute());
+            }
+        }
+
+        // 保存计算结果
+        saveElectricityRecord(siteId, deviceCode, date, sharpPeakCost, peakCost,
+                flatCost, valleyCost, totalCost);
+
+        log.info("站点{}设备{}在{}的电费计算完成:尖峰{}元,峰{}元,平{}元,谷{}元,总计{}元",
+                siteId, deviceCode, date, sharpPeakCost, peakCost, flatCost, valleyCost, totalCost);
+    }
+
+    private List<TimePriceConfig> getTimePriceConfigs(Integer siteId, int month) {
+        String sql = "SELECT price_type, electricity_price, start_time, end_time, " +
+                "peak_valley_attribute FROM electricity_time_price " +
+                "WHERE site_id = ? AND month = ? ORDER BY start_time";
+
+        return jdbcTemplate.query(sql, new Object[]{siteId, month}, new RowMapper<TimePriceConfig>() {
+            @Override
+            public TimePriceConfig mapRow(ResultSet rs, int rowNum) throws SQLException {
+                TimePriceConfig config = new TimePriceConfig();
+                config.setPriceType(rs.getInt("price_type"));
+                config.setElectricityPrice(rs.getBigDecimal("electricity_price"));
+
+                // 处理时间字段
+                java.sql.Time startTime = rs.getTime("start_time");
+                java.sql.Time endTime = rs.getTime("end_time");
+                if (startTime != null) config.setStartTime(startTime.toLocalTime());
+                if (endTime != null) config.setEndTime(endTime.toLocalTime());
+
+                config.setPeakValleyAttribute(rs.getInt("peak_valley_attribute"));
+                return config;
+            }
+        });
+    }
+
+    private List<ElectricityData> getElectricityData(String deviceCode, LocalDate date) {
+        String sql = "SELECT Epp, dataTime FROM ht_analog_data " +
+                "WHERE deviceName = ? AND freezingTime = ? " +
+                "ORDER BY dataTime";
+
+        return jdbcTemplate.query(sql, new Object[]{deviceCode, date}, new RowMapper<ElectricityData>() {
+            @Override
+            public ElectricityData mapRow(ResultSet rs, int rowNum) throws SQLException {
+                ElectricityData data = new ElectricityData();
+                data.setEpp(rs.getDouble("Epp"));
+
+                java.sql.Timestamp dataTime = rs.getTimestamp("dataTime");
+                if (dataTime != null) {
+                    data.setDataTime(dataTime.toLocalDateTime());
+                }
+
+                return data;
+            }
+        });
+    }
+
+    private TimePriceConfig findTimePriceConfig(List<TimePriceConfig> configs, LocalTime time) {
+        for (TimePriceConfig config : configs) {
+            LocalTime startTime = config.getStartTime();
+            LocalTime endTime = config.getEndTime();
+
+            if (startTime != null && endTime != null) {
+                // 处理跨天的时间段(如23:00-01:00)
+                if (startTime.isAfter(endTime)) {
+                    if (!time.isBefore(startTime) || time.isBefore(endTime)) {
+                        return config;
+                    }
+                } else {
+                    if (!time.isBefore(startTime) && time.isBefore(endTime)) {
+                        return config;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private void saveElectricityRecord(Integer siteId, String deviceCode, LocalDate date,
+                                       BigDecimal sharpPeakCost, BigDecimal peakCost,
+                                       BigDecimal flatCost, BigDecimal valleyCost,
+                                       BigDecimal totalCost) {
+        String sql = "INSERT INTO site_electricity_record " +
+                "(site_id, device_code, date, peak_cost, flat_cost, " +
+                "valley_cost, sharp_peak_cost, total_cost) " +
+                "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " +
+                "ON DUPLICATE KEY UPDATE " +
+                "peak_cost = VALUES(peak_cost), " +
+                "flat_cost = VALUES(flat_cost), " +
+                "valley_cost = VALUES(valley_cost), " +
+                "sharp_peak_cost = VALUES(sharp_peak_cost), " +
+                "total_cost = VALUES(total_cost)";
+
+        try {
+            jdbcTemplate.update(sql,
+                    siteId,
+                    deviceCode,
+                    date,
+                    peakCost,
+                    flatCost,
+                    valleyCost,
+                    sharpPeakCost,
+                    totalCost
+            );
+            log.debug("成功保存站点{}设备{}的电费记录", siteId, deviceCode);
+        } catch (Exception e) {
+            log.error("保存站点{}设备{}的电费记录失败", siteId, deviceCode, e);
+            // 这里只是记录错误,不会影响其他设备的处理
+        }
+    }
+
+    // 内部配置类
+    @lombok.Data
+    private static class TimePriceConfig {
+        private int priceType;
+        private BigDecimal electricityPrice;
+        private LocalTime startTime;
+        private LocalTime endTime;
+        private int peakValleyAttribute;
+    }
+
+    // 电力数据类
+    @lombok.Data
+    private static class ElectricityData {
+        private double epp;
+        private LocalDateTime dataTime;
+    }
+
+}

+ 2 - 1
fiveep-service/src/main/java/com/bizmatics/service/vo/SiteLoadAnalysisVO.java

@@ -4,6 +4,7 @@ import com.bizmatics.service.config.CustomerDoubleSerialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.Date;
 
 /**
@@ -58,5 +59,5 @@ public class SiteLoadAnalysisVO {
     /**
      * 上报时间
      */
-    private Date dataTime;
+    private LocalDateTime dataTime;
 }