Bläddra i källkod

统计报表相关接口

hanzhengyi 1 vecka sedan
förälder
incheckning
d9a0ee41bb
18 ändrade filer med 810 tillägg och 5 borttagningar
  1. 8 3
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  2. 47 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java
  3. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java
  4. 82 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java
  5. 55 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java
  6. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java
  7. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java
  8. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java
  9. 7 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java
  10. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java
  11. 75 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpProductServiceImpl.java
  12. 148 2
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java
  13. 110 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsEnergyItemCodeServiceImpl.java
  14. 29 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionLastValueDTO.java
  15. 27 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionOneValueDTO.java
  16. 36 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionValueReportDTO.java
  17. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyItemCodeVO.java
  18. 28 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/ProductFunctionVO.java

+ 8 - 3
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -2,6 +2,7 @@ package com.usky.ems.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
 import com.usky.ems.service.EmsReportService;
+import com.usky.ems.service.EmsModelService;
 import com.usky.ems.service.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -21,13 +22,17 @@ public class EmsReportController {
     @Autowired
     private EmsReportService emsReportService;
 
+    @Autowired
+    private EmsModelService emsModelService;
+
     // ---------- 能源报表 ----------
     @GetMapping("/energy/devices")
-    public ApiResult<EmsReportDevicesResponse> getEnergyDevices(
-            @RequestParam Long energyTypeId,
+    public ApiResult<List<EnergyTypeWrapperProductVO>> getEnergyDevices(
+            @RequestParam(required = false) Long energyTypeId,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsReportService.getEnergyDevices(energyTypeId, keyword, projectId));
+        // TODO: 如需按 energyTypeId/keyword/projectId 过滤,可在 EmsModelService 中扩展方法
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
     }
 
     @PostMapping("/energy/statistics")

+ 47 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java

@@ -0,0 +1,47 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)
+ * 表:ems_data_upload_shanghai_product
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_data_upload_shanghai_product")
+public class EmsDataUploadShanghaiProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 产品ID */
+    private Long productId;
+
+    /** 功能标识 */
+    private String identifier;
+
+    /** 自定义标识符 */
+    private String customIdentifier;
+
+    /** 更新人 */
+    private Long updatedBy;
+
+    /** 记录更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 创建人 */
+    private Long createdBy;
+
+    /** 记录创建时间 */
+    private LocalDateTime createTime;
+}
+

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java

@@ -0,0 +1,61 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 空间能耗计算公式(ems_energy_consumption_formula)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_energy_consumption_formula")
+public class EmsEnergyConsumptionFormula implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("space_id")
+    private Long spaceId;
+
+    @TableField("item_code")
+    private String itemCode;
+
+    private String name;
+
+    private String formula;
+
+    @TableField("compute_start_time")
+    private LocalDateTime computeStartTime;
+
+    @TableField("compute_end_time")
+    private LocalDateTime computeEndTime;
+
+    @TableField("data_time")
+    private LocalDateTime dataTime;
+
+    private Integer status;
+
+    private String cause;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 82 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java

@@ -0,0 +1,82 @@
+package com.usky.ems.enums;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * 时间维度枚举:D-日 / M-月 / Y-年
+ */
+public enum TemporalTypeEnum {
+
+    DAY("D"),
+    MONTH("M"),
+    YEAR("Y");
+
+    private final String code;
+
+    TemporalTypeEnum(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public static TemporalTypeEnum get(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (TemporalTypeEnum e : values()) {
+            if (e.code.equalsIgnoreCase(code)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 将开始时间归一化到当前时间维度的起点
+     */
+    public LocalDateTime normalizeStart(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MIN);
+            case MONTH:
+                LocalDate firstDayOfMonth = date.withDayOfMonth(1);
+                return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
+            case YEAR:
+                LocalDate firstDayOfYear = date.withDayOfYear(1);
+                return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
+            default:
+                return time;
+        }
+    }
+
+    /**
+     * 将结束时间归一化到当前时间维度的结束
+     */
+    public LocalDateTime normalizeEnd(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MAX);
+            case MONTH:
+                LocalDate lastDayOfMonth = date.withDayOfMonth(date.lengthOfMonth());
+                return LocalDateTime.of(lastDayOfMonth, LocalTime.MAX);
+            case YEAR:
+                LocalDate lastDayOfYear = date.withDayOfYear(date.lengthOfYear());
+                return LocalDateTime.of(lastDayOfYear, LocalTime.MAX);
+            default:
+                return time;
+        }
+    }
+}
+

+ 55 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java

@@ -0,0 +1,55 @@
+package com.usky.ems.enums;
+
+import java.util.Objects;
+
+/**
+ * 数值类型枚举,对应 leo.sql 中 ems_device_function_formula.value_type 字段:
+ * 0: 其他  1: 累计值  2: 波动值
+ */
+public enum ValueTypeEnum {
+
+    OTHER("其他", 0),
+    CUMULATIVE("累计值", 1),
+    FLUCTUATION("波动值", 2);
+
+    private final String name;
+    private final Short value;
+
+    ValueTypeEnum(String name, Integer value) {
+        this.name = name;
+        this.value = value.shortValue();
+    }
+
+    /**
+     * 根据数值获取名称
+     */
+    public static String getName(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e.getName();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据数值获取枚举实例;如果不存在则抛出异常
+     */
+    public static ValueTypeEnum getInstance(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("错误的值类型");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Short getValue() {
+        return value;
+    }
+}
+

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java

@@ -0,0 +1,12 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDataUploadShanghaiProduct;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)Mapper
+ * 表:ems_data_upload_shanghai_product
+ */
+public interface EmsDataUploadShanghaiProductMapper extends CrudMapper<EmsDataUploadShanghaiProduct> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsEnergyConsumptionFormula;
+
+/**
+ * 空间能耗计算公式 Mapper(ems_energy_consumption_formula)
+ */
+public interface EmsEnergyConsumptionFormulaMapper extends CrudMapper<EmsEnergyConsumptionFormula> {
+}
+

+ 23 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java

@@ -0,0 +1,23 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.DmpProduct;
+import com.usky.ems.enums.ValueTypeEnum;
+import com.usky.ems.service.vo.ProductFunctionVO;
+
+import java.util.List;
+
+/**
+ * 产品信息表 服务接口(service-ems)
+ */
+public interface DmpProductService extends CrudService<DmpProduct> {
+
+    /**
+     * 根据产品 ID 与数值类型获取功能列表
+     * 说明:当前实现基于 leo.sql 中 ems_data_upload_shanghai_product 表,
+     * 仅按 productId 过滤,不区分 valueTypeEnum。
+     */
+    List<ProductFunctionVO> getFunctions(Long id, ValueTypeEnum valueTypeEnum);
+
+}
+

+ 7 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java

@@ -2,6 +2,7 @@ package com.usky.ems.service;
 
 import com.usky.ems.service.vo.DeviceEnergyReportRequest;
 import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
+import com.usky.ems.service.vo.DeviceFunctionValueReportDTO;
 
 import java.util.List;
 
@@ -14,5 +15,11 @@ public interface EmsDeviceReportService {
      * 查询能耗报表用的设备列表,并按名称进行排序。
      */
     List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest request);
+
+    /**
+     * 展示设备能耗报表数据(按时间维度与功能点进行统计)。
+     * 返回值结构后续可根据实际前端展示需求进行扩展,这里使用 Object 以保持灵活性。
+     */
+    Object showEnergyReportData(DeviceFunctionValueReportDTO dto);
 }
 

+ 19 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java

@@ -0,0 +1,19 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.vo.EnergyItemCodeVO;
+
+import java.util.List;
+
+/**
+ * 能源分项编码 服务接口
+ */
+public interface EmsEnergyItemCodeService extends CrudService<EmsEnergyItemCode> {
+
+    /**
+     * 查询指定空间下的分项能耗配置(基于能耗公式和分项编码组装树形结构)。
+     */
+    List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId);
+}
+

+ 75 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/DmpProductServiceImpl.java

@@ -0,0 +1,75 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.DmpProduct;
+import com.usky.ems.domain.EmsDataUploadShanghaiProduct;
+import com.usky.ems.enums.ValueTypeEnum;
+import com.usky.ems.mapper.DmpProductMapper;
+import com.usky.ems.mapper.EmsDataUploadShanghaiProductMapper;
+import com.usky.ems.service.DmpProductService;
+import com.usky.ems.service.vo.ProductFunctionVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 产品信息表 服务实现类(service-ems)
+ */
+@Service
+public class DmpProductServiceImpl extends AbstractCrudService<DmpProductMapper, DmpProduct> implements DmpProductService {
+
+    @Autowired
+    private EmsDataUploadShanghaiProductMapper shanghaiProductMapper;
+
+    @Override
+    public List<ProductFunctionVO> getFunctions(Long id, ValueTypeEnum valueTypeEnum) {
+        // 参照原有实现:valueTypeEnum 为空返回全部,否则按值类型过滤
+        if (valueTypeEnum == null) {
+            LambdaQueryWrapper<EmsDataUploadShanghaiProduct> wrapper =
+                    Wrappers.<EmsDataUploadShanghaiProduct>lambdaQuery()
+                            .eq(EmsDataUploadShanghaiProduct::getProductId, id)
+                            .orderByAsc(EmsDataUploadShanghaiProduct::getIdentifier)
+                            .orderByDesc(EmsDataUploadShanghaiProduct::getUpdateTime);
+
+            List<EmsDataUploadShanghaiProduct> list = shanghaiProductMapper.selectList(wrapper);
+            return list.stream()
+                    .map(item -> new ProductFunctionVO(
+                            item.getId(),
+                            item.getProductId(),
+                            item.getIdentifier(),
+                            item.getCustomIdentifier()
+                    ))
+                    .collect(Collectors.toList());
+        }
+
+        // 预留:按值类型过滤,目前表结构中暂无 value_type 字段,仅根据 productId 查询
+        return getByProductIdAndValueType(id, valueTypeEnum.getValue());
+    }
+
+    /**
+     * 根据产品 ID 与值类型获取功能列表。
+     * 当前实现仅按 productId 过滤,后续可在 ems_data_upload_shanghai_product 表中增加 value_type 字段后扩展。
+     */
+    private List<ProductFunctionVO> getByProductIdAndValueType(Long id, Short valueType) {
+        LambdaQueryWrapper<EmsDataUploadShanghaiProduct> wrapper =
+                Wrappers.<EmsDataUploadShanghaiProduct>lambdaQuery()
+                        .eq(EmsDataUploadShanghaiProduct::getProductId, id)
+                        .orderByAsc(EmsDataUploadShanghaiProduct::getIdentifier)
+                        .orderByDesc(EmsDataUploadShanghaiProduct::getUpdateTime);
+
+        List<EmsDataUploadShanghaiProduct> list = shanghaiProductMapper.selectList(wrapper);
+        return list.stream()
+                .map(item -> new ProductFunctionVO(
+                        item.getId(),
+                        item.getProductId(),
+                        item.getIdentifier(),
+                        item.getCustomIdentifier()
+                ))
+                .collect(Collectors.toList());
+    }
+}
+

+ 148 - 2
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java

@@ -3,15 +3,16 @@ package com.usky.ems.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.ems.domain.EmsDevice;
 import com.usky.ems.domain.EmsDeviceFunction;
+import com.usky.ems.enums.TemporalTypeEnum;
 import com.usky.ems.mapper.EmsDeviceFunctionMapper;
 import com.usky.ems.mapper.EmsDeviceMapper;
 import com.usky.ems.service.EmsDeviceReportService;
 import com.usky.ems.service.EmsSpaceService;
-import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
-import com.usky.ems.service.vo.DeviceEnergyReportRequest;
+import com.usky.ems.service.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -106,6 +107,151 @@ public class EmsDeviceReportServiceImpl implements EmsDeviceReportService {
         return deviceList;
     }
 
+    @Override
+    public Object showEnergyReportData(DeviceFunctionValueReportDTO dto) {
+        checkParams(dto);
+        TemporalTypeEnum temporalTypeEnum = TemporalTypeEnum.get(dto.getTimeType());
+        LocalDateTime startLocalTime = dto.getStartTime();
+        LocalDateTime endLocalTime = dto.getEndTime();
+        String identifier = getDeviceFunctionIdentifierByEnergyType(dto.getEnergyType());
+        if (temporalTypeEnum != null) {
+            checkDateTime(startLocalTime, endLocalTime, temporalTypeEnum);
+            startLocalTime = temporalTypeEnum.normalizeStart(startLocalTime);
+            endLocalTime = temporalTypeEnum.normalizeEnd(endLocalTime);
+            DeviceFunctionLastValueDTO paramDTO = convertParamDTO(dto, startLocalTime, endLocalTime, temporalTypeEnum, identifier);
+            return asyncProcessData(dto, paramDTO, temporalTypeEnum);
+        } else {
+            DeviceFunctionOneValueDTO paramDTO = convertParamOneLastValueDTO(dto, startLocalTime, endLocalTime, identifier);
+            return getCustomizeEnergyReportVOList(dto, paramDTO, startLocalTime);
+        }
+    }
+
+    private void checkParams(DeviceFunctionValueReportDTO dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("请求参数不能为空");
+        }
+        if (dto.getProjectId() == null) {
+            throw new IllegalArgumentException("projectId 不能为空");
+        }
+        if (dto.getStartTime() == null || dto.getEndTime() == null) {
+            throw new IllegalArgumentException("开始时间和结束时间不能为空");
+        }
+    }
+
+    private void checkDateTime(LocalDateTime start, LocalDateTime end, TemporalTypeEnum type) {
+        if (start.isAfter(end)) {
+            throw new IllegalArgumentException("开始时间不能晚于结束时间");
+        }
+        // 如需限制时间跨度,可在此扩展逻辑(例如:按日最多查询31天)。
+    }
+
+    private String getDeviceFunctionIdentifierByEnergyType(Integer energyType) {
+        if (energyType == null) {
+            return "total_kwh";
+        }
+        switch (energyType) {
+            case 1:
+                return "total_kwh";
+            case 2:
+                return "total_water";
+            case 3:
+                return "total_gas";
+            default:
+                return "total_kwh";
+        }
+    }
+
+    private DeviceFunctionLastValueDTO convertParamDTO(DeviceFunctionValueReportDTO src,
+                                                       LocalDateTime start,
+                                                       LocalDateTime end,
+                                                       TemporalTypeEnum temporalTypeEnum,
+                                                       String identifier) {
+        DeviceFunctionLastValueDTO dto = new DeviceFunctionLastValueDTO();
+        dto.setProjectId(src.getProjectId());
+        dto.setDeviceIds(src.getDeviceIds());
+        dto.setAttributePointIds(src.getAttributePointIds());
+        dto.setTimeType(temporalTypeEnum != null ? temporalTypeEnum.getCode() : null);
+        dto.setStartTime(start);
+        dto.setEndTime(end);
+        dto.setIdentifier(identifier);
+        return dto;
+    }
+
+    private DeviceFunctionOneValueDTO convertParamOneLastValueDTO(DeviceFunctionValueReportDTO src,
+                                                                  LocalDateTime start,
+                                                                  LocalDateTime end,
+                                                                  String identifier) {
+        DeviceFunctionOneValueDTO dto = new DeviceFunctionOneValueDTO();
+        dto.setProjectId(src.getProjectId());
+        dto.setDeviceIds(src.getDeviceIds());
+        dto.setAttributePointIds(src.getAttributePointIds());
+        dto.setStartTime(start);
+        dto.setEndTime(end);
+        dto.setIdentifier(identifier);
+        return dto;
+    }
+
+    private Object asyncProcessData(DeviceFunctionValueReportDTO origin,
+                                    DeviceFunctionLastValueDTO paramDTO,
+                                    TemporalTypeEnum temporalTypeEnum) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("timeType", temporalTypeEnum != null ? temporalTypeEnum.getCode() : null);
+        result.put("startTime", paramDTO.getStartTime());
+        result.put("endTime", paramDTO.getEndTime());
+
+        // 模拟数据:根据 deviceIds 与 attributePointIds 生成简单的时间序列数据
+        List<Map<String, Object>> items = new ArrayList<>();
+        List<String> deviceIds = paramDTO.getDeviceIds() != null ? paramDTO.getDeviceIds() : Collections.emptyList();
+        List<Long> attrIds = paramDTO.getAttributePointIds() != null ? paramDTO.getAttributePointIds() : Collections.emptyList();
+
+        int index = 0;
+        for (String deviceId : deviceIds) {
+            for (Long attrId : attrIds) {
+                Map<String, Object> item = new HashMap<>();
+                item.put("deviceId", deviceId);
+                item.put("attributePointId", attrId);
+                item.put("identifier", paramDTO.getIdentifier());
+                // 简单模拟一个数值:100 + 10 * index
+                item.put("value", 100 + 10 * index);
+                item.put("unit", "kWh");
+                items.add(item);
+                index++;
+            }
+        }
+
+        result.put("items", items);
+        return result;
+    }
+
+    private Object getCustomizeEnergyReportVOList(DeviceFunctionValueReportDTO origin,
+                                                  DeviceFunctionOneValueDTO paramDTO,
+                                                  LocalDateTime startLocalTime) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("time", startLocalTime);
+
+        List<Map<String, Object>> items = new ArrayList<>();
+        List<String> deviceIds = paramDTO.getDeviceIds() != null ? paramDTO.getDeviceIds() : Collections.emptyList();
+        List<Long> attrIds = paramDTO.getAttributePointIds() != null ? paramDTO.getAttributePointIds() : Collections.emptyList();
+
+        int index = 0;
+        for (String deviceId : deviceIds) {
+            for (Long attrId : attrIds) {
+                Map<String, Object> item = new HashMap<>();
+                item.put("deviceId", deviceId);
+                item.put("attributePointId", attrId);
+                item.put("identifier", paramDTO.getIdentifier());
+                // 单点场景模拟一个数值:200 + 5 * index
+                item.put("value", 200 + 5 * index);
+                item.put("unit", "kWh");
+                items.add(item);
+                index++;
+            }
+        }
+
+        result.put("items", items);
+        return result;
+    }
+
     private DeviceEnergyReportItemVO deviceToVo(EmsDevice d) {
         DeviceEnergyReportItemVO vo = new DeviceEnergyReportItemVO();
         vo.setId(d.getId());

+ 110 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsEnergyItemCodeServiceImpl.java

@@ -0,0 +1,110 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsEnergyConsumptionFormula;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.mapper.EmsEnergyConsumptionFormulaMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.service.EmsEnergyItemCodeService;
+import com.usky.ems.service.vo.EnergyItemCodeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 能源分项编码 服务实现类
+ */
+@Service
+public class EmsEnergyItemCodeServiceImpl
+        extends AbstractCrudService<EmsEnergyItemCodeMapper, EmsEnergyItemCode>
+        implements EmsEnergyItemCodeService {
+
+    @Autowired
+    private EmsEnergyConsumptionFormulaMapper energyConsumptionFormulaMapper;
+
+    @Override
+    public List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+
+        LambdaQueryWrapper<EmsEnergyConsumptionFormula> lambdaQuery =
+                Wrappers.lambdaQuery(EmsEnergyConsumptionFormula.class)
+                        .eq(EmsEnergyConsumptionFormula::getSpaceId, spaceId);
+        List<EmsEnergyConsumptionFormula> formulaList = energyConsumptionFormulaMapper.selectList(lambdaQuery);
+        if (formulaList == null || formulaList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<String> codes = formulaList.stream()
+                .map(EmsEnergyConsumptionFormula::getItemCode)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (codes.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 直接从 ems_energy_item_code 表根据编码列表查询
+        List<EmsEnergyItemCode> itemCodeList = this.list(
+                Wrappers.<EmsEnergyItemCode>lambdaQuery()
+                        .in(EmsEnergyItemCode::getCode, codes)
+        );
+        if (itemCodeList == null || itemCodeList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // PO -> VO
+        List<EnergyItemCodeVO> itemCodes = itemCodeList.stream().map(po -> {
+            EnergyItemCodeVO vo = new EnergyItemCodeVO();
+            vo.setCode(po.getCode());
+            vo.setParentCode(po.getParentCode());
+            vo.setName(po.getName());
+            vo.setUnit(po.getUnit());
+            vo.setUnitName(po.getUnitName());
+            return vo;
+        }).collect(Collectors.toList());
+
+        // 仅保留配置了公式的分项
+        itemCodes = itemCodes.stream()
+                .filter(vo -> codes.contains(vo.getCode()))
+                .collect(Collectors.toList());
+
+        if (itemCodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<String, List<EnergyItemCodeVO>> listMap = itemCodes.stream()
+                .collect(Collectors.groupingBy(EnergyItemCodeVO::getParentCode));
+
+        Map<String, EmsEnergyConsumptionFormula> formulaMap = formulaList.stream()
+                .collect(Collectors.toMap(EmsEnergyConsumptionFormula::getItemCode, f -> f, (a, b) -> a));
+
+        List<EnergyItemCodeVO> result = new ArrayList<>(itemCodes);
+
+        for (EnergyItemCodeVO codeVO : itemCodes) {
+            List<EnergyItemCodeVO> children = listMap.get(codeVO.getCode());
+            if (children != null && !children.isEmpty()) {
+                codeVO.setChildren(children);
+                result.removeAll(children);
+            }
+
+            EmsEnergyConsumptionFormula formula = formulaMap.get(codeVO.getCode());
+            if (formula != null) {
+                codeVO.setId(formula.getId());
+                codeVO.setFormula(formula.getFormula());
+                if (formula.getName() != null) {
+                    codeVO.setName(formula.getName());
+                }
+            }
+        }
+
+        return result;
+    }
+}
+

+ 29 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionLastValueDTO.java

@@ -0,0 +1,29 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 设备功能点最后值查询参数 DTO(区间统计场景)
+ */
+@Data
+public class DeviceFunctionLastValueDTO {
+
+    private Long projectId;
+
+    private List<String> deviceIds;
+
+    private List<Long> attributePointIds;
+
+    private String timeType;
+
+    private LocalDateTime startTime;
+
+    private LocalDateTime endTime;
+
+    /** 功能标识符,如 total_kwh */
+    private String identifier;
+}
+

+ 27 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionOneValueDTO.java

@@ -0,0 +1,27 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 设备功能点单点值查询参数 DTO(自定义时间点场景)
+ */
+@Data
+public class DeviceFunctionOneValueDTO {
+
+    private Long projectId;
+
+    private List<String> deviceIds;
+
+    private List<Long> attributePointIds;
+
+    private LocalDateTime startTime;
+
+    private LocalDateTime endTime;
+
+    /** 功能标识符,如 total_kwh */
+    private String identifier;
+}
+

+ 36 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceFunctionValueReportDTO.java

@@ -0,0 +1,36 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 设备功能值报表查询请求 DTO
+ * 对应能耗报表数据统计接口的入参抽象。
+ */
+@Data
+public class DeviceFunctionValueReportDTO {
+
+    /** 项目ID */
+    private Long projectId;
+
+    /** 能源类型(电/水/气) */
+    private Integer energyType;
+
+    /** 时间维度:D-日 / M-月 / Y-年;对应 TemporalTypeEnum.code */
+    private String timeType;
+
+    /** 开始时间 */
+    private LocalDateTime startTime;
+
+    /** 结束时间 */
+    private LocalDateTime endTime;
+
+    /** 设备ID列表 */
+    private List<String> deviceIds;
+
+    /** 功能点ID列表(属性点位) */
+    private List<Long> attributePointIds;
+}
+

+ 32 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyItemCodeVO.java

@@ -0,0 +1,32 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 能源分项编码视图对象
+ */
+@Data
+public class EnergyItemCodeVO {
+
+    private Long id;
+
+    private String code;
+
+    private String parentCode;
+
+    private String name;
+
+    private String unit;
+
+    private String unitName;
+
+    /** 计算公式 */
+    private String formula;
+
+    /** 子分项 */
+    private List<EnergyItemCodeVO> children = new ArrayList<>();
+}
+

+ 28 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/ProductFunctionVO.java

@@ -0,0 +1,28 @@
+package com.usky.ems.service.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 产品功能标识视图对象
+ * 主要承载 ems_data_upload_shanghai_product 中的一条记录
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductFunctionVO {
+
+    /** 记录ID */
+    private Long id;
+
+    /** 产品ID */
+    private Long productId;
+
+    /** 功能标识 */
+    private String identifier;
+
+    /** 自定义标识符 */
+    private String customIdentifier;
+}
+