|
@@ -1,14 +1,39 @@
|
|
|
package com.usky.ems.service.impl;
|
|
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.EmsProject;
|
|
import com.usky.ems.domain.EmsProject;
|
|
|
|
|
+import com.usky.ems.domain.EmsSpace;
|
|
|
|
|
+import com.usky.ems.domain.EmsProjectDeviceSystem;
|
|
|
|
|
+import com.usky.ems.domain.EmsProjectConversionFactor;
|
|
|
|
|
+import com.usky.ems.domain.EmsEnergyItemCode;
|
|
|
|
|
+import com.usky.ems.mapper.EmsDeviceMapper;
|
|
|
import com.usky.ems.mapper.EmsProjectMapper;
|
|
import com.usky.ems.mapper.EmsProjectMapper;
|
|
|
|
|
+import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
|
|
|
|
|
+import com.usky.ems.mapper.EmsProjectConversionFactorMapper;
|
|
|
|
|
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
|
|
|
|
|
+import com.usky.ems.service.EmsModelService;
|
|
|
import com.usky.ems.service.EmsOverviewService;
|
|
import com.usky.ems.service.EmsOverviewService;
|
|
|
|
|
+import com.usky.ems.service.EmsSpaceService;
|
|
|
|
|
+import com.usky.ems.service.vo.EmsEnergyTypeVO;
|
|
|
|
|
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
|
|
|
|
|
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
|
|
|
import com.usky.ems.service.vo.EmsProjectResponse;
|
|
import com.usky.ems.service.vo.EmsProjectResponse;
|
|
|
import com.usky.ems.service.vo.EmsSummaryResponse;
|
|
import com.usky.ems.service.vo.EmsSummaryResponse;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.LocalTime;
|
|
|
|
|
+import java.time.temporal.TemporalAdjusters;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 能源总览服务实现
|
|
* 能源总览服务实现
|
|
@@ -19,6 +44,24 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private EmsProjectMapper emsProjectMapper;
|
|
private EmsProjectMapper emsProjectMapper;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsModelService emsModelService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsDeviceMapper emsDeviceMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsSpaceService emsSpaceService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsProjectConversionFactorMapper emsProjectConversionFactorMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public EmsProjectResponse getProject(Long projectId) {
|
|
public EmsProjectResponse getProject(Long projectId) {
|
|
|
EmsProject project;
|
|
EmsProject project;
|
|
@@ -33,13 +76,57 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
}
|
|
}
|
|
|
EmsProjectResponse resp = new EmsProjectResponse();
|
|
EmsProjectResponse resp = new EmsProjectResponse();
|
|
|
resp.setId(project.getId());
|
|
resp.setId(project.getId());
|
|
|
|
|
+ resp.setSpaceId(project.getSpaceId());
|
|
|
resp.setName(project.getName());
|
|
resp.setName(project.getName());
|
|
|
- resp.setCode(project.getAbbreviation());
|
|
|
|
|
- resp.setDescription(project.getIntroduction());
|
|
|
|
|
|
|
+ resp.setAbbreviation(project.getAbbreviation());
|
|
|
resp.setArea(project.getArea());
|
|
resp.setArea(project.getArea());
|
|
|
- resp.setResidentPopulation(project.getResidentPopulation());
|
|
|
|
|
- resp.setCreateTime(project.getCreateTime());
|
|
|
|
|
- resp.setUpdateTime(project.getUpdateTime());
|
|
|
|
|
|
|
+ resp.setCommonArea(project.getCommonArea());
|
|
|
|
|
+ resp.setAirConditionedArea(project.getAirConditionedArea());
|
|
|
|
|
+ // 源字段为 Integer,这里转为 BigDecimal 以匹配 VO 定义
|
|
|
|
|
+ if (project.getResidentPopulation() != null) {
|
|
|
|
|
+ resp.setResidentPopulation(java.math.BigDecimal.valueOf(project.getResidentPopulation()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设备系统集合
|
|
|
|
|
+ List<EmsProjectDeviceSystem> sysList = emsProjectDeviceSystemMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsProjectDeviceSystem>()
|
|
|
|
|
+ .eq(EmsProjectDeviceSystem::getProjectId, project.getId())
|
|
|
|
|
+ );
|
|
|
|
|
+ if (sysList != null && !sysList.isEmpty()) {
|
|
|
|
|
+ resp.setDeviceSystemList(sysList.stream()
|
|
|
|
|
+ .map(EmsProjectDeviceSystem::getDeviceSystem)
|
|
|
|
|
+ .collect(Collectors.toList()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resp.setProvinceCode(project.getProvinceCode());
|
|
|
|
|
+ resp.setCityCode(project.getCityCode());
|
|
|
|
|
+ resp.setDistrictCode(project.getDistrictCode());
|
|
|
|
|
+ resp.setProvinceName(project.getProvinceName());
|
|
|
|
|
+ resp.setCityName(project.getCityName());
|
|
|
|
|
+ resp.setDistrictName(project.getDistrictName());
|
|
|
|
|
+ resp.setLocation(project.getLocation());
|
|
|
|
|
+ resp.setAddress(project.getAddress());
|
|
|
|
|
+ resp.setTypeId(project.getTypeId());
|
|
|
|
|
+ resp.setTypeName(project.getTypeName());
|
|
|
|
|
+ resp.setImage(project.getImage());
|
|
|
|
|
+ // imageUrl 视实际文件服务而定,这里暂不拼接,直接沿用 image 字段或留空
|
|
|
|
|
+ resp.setImageUrl(project.getImage());
|
|
|
|
|
+ resp.setIntroduction(project.getIntroduction());
|
|
|
|
|
+ resp.setPlatformName(project.getPlatformName());
|
|
|
|
|
+ resp.setLogo(project.getLogo());
|
|
|
|
|
+ resp.setLogoUrl(project.getLogo());
|
|
|
|
|
+ resp.setLogoMin(project.getLogoMin());
|
|
|
|
|
+ resp.setLogoMinUrl(project.getLogoMin());
|
|
|
|
|
+ resp.setUpdatedBy(project.getUpdatedBy());
|
|
|
|
|
+ resp.setCreatedBy(project.getCreatedBy());
|
|
|
|
|
+ if (project.getUpdateTime() != null) {
|
|
|
|
|
+ resp.setUpdateTime(project.getUpdateTime().toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (project.getCreateTime() != null) {
|
|
|
|
|
+ resp.setCreateTime(project.getCreateTime().toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ // 单位能耗暂无法从现有表直接计算,先置为空,后续可按业务补充
|
|
|
|
|
+ resp.setAreaCoal(null);
|
|
|
return resp;
|
|
return resp;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -50,4 +137,559 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
resp.setTimeValue(timeValue);
|
|
resp.setTimeValue(timeValue);
|
|
|
return resp;
|
|
return resp;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId) {
|
|
|
|
|
+ // 当前实现:直接基于能源类型列表(电/水/气)构造概览条目
|
|
|
|
|
+ List<EmsEnergyTypeVO> types = emsModelService.getEnergyTypeList();
|
|
|
|
|
+ return types.stream().map(t -> {
|
|
|
|
|
+ EmsOverviewEnergyItemVO vo = new EmsOverviewEnergyItemVO();
|
|
|
|
|
+ vo.setName(t.getName());
|
|
|
|
|
+ vo.setItemCode(t.getCode());
|
|
|
|
|
+ vo.setEnergyType(t.getId() != null ? t.getId().intValue() : null);
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId) {
|
|
|
|
|
+ Long pid = resolveProjectId(projectId);
|
|
|
|
|
+ if (pid == null) {
|
|
|
|
|
+ return java.util.Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 查询项目已配置的设备系统
|
|
|
|
|
+ List<EmsProjectDeviceSystem> systemList = emsProjectDeviceSystemMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsProjectDeviceSystem>()
|
|
|
|
|
+ .eq(EmsProjectDeviceSystem::getProjectId, pid)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (systemList == null || systemList.isEmpty()) {
|
|
|
|
|
+ return java.util.Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Integer> systems = systemList.stream()
|
|
|
|
|
+ .map(EmsProjectDeviceSystem::getDeviceSystem)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询设备:同项目、启用状态、设备系统在上述集合中
|
|
|
|
|
+ List<EmsDevice> devices = emsDeviceMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsDevice>()
|
|
|
|
|
+ .eq(EmsDevice::getProjectId, pid)
|
|
|
|
|
+ .eq(EmsDevice::getStatus, 1)
|
|
|
|
|
+ .in(EmsDevice::getDeviceSystem, systems)
|
|
|
|
|
+ );
|
|
|
|
|
+ java.util.Map<Integer, Long> deviceCountMap = devices.stream()
|
|
|
|
|
+ .filter(d -> d.getDeviceSystem() != null)
|
|
|
|
|
+ .collect(Collectors.groupingBy(EmsDevice::getDeviceSystem, Collectors.counting()));
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 组装统计 VO
|
|
|
|
|
+ List<EmsOverviewDeviceSystemStatVO> result = new java.util.ArrayList<>();
|
|
|
|
|
+ for (EmsProjectDeviceSystem s : systemList) {
|
|
|
|
|
+ Integer code = s.getDeviceSystem();
|
|
|
|
|
+ EmsOverviewDeviceSystemStatVO vo = new EmsOverviewDeviceSystemStatVO();
|
|
|
|
|
+ vo.setDeviceSystem(code);
|
|
|
|
|
+ vo.setName(deviceSystemName(code));
|
|
|
|
|
+ vo.setValue(deviceCountMap.getOrDefault(code, 0L).intValue());
|
|
|
|
|
+ result.add(vo);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分类能耗统计(模拟数据版本)
|
|
|
|
|
+ * 说明:
|
|
|
|
|
+ * - 目前不接真实能耗库,仅按固定示例数据做一套完整计算逻辑,保证前端联调正常。
|
|
|
|
|
+ * - 返回结构与原系统保持一致:Map<String, Object>。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId) {
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 基础模拟数据:本期/环比/同比能耗(单位:kWh 或折算后的统一单位)
|
|
|
|
|
+ BigDecimal consume = new BigDecimal("100.00");
|
|
|
|
|
+ BigDecimal sequentialCon = new BigDecimal("80.00"); // 上一期
|
|
|
|
|
+ BigDecimal pariPassCon = new BigDecimal("90.00"); // 同期
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟项目能耗折标系数、单价
|
|
|
|
|
+ BigDecimal coal = new BigDecimal("0.70"); // 折标系数(每单位能耗折算标煤)
|
|
|
|
|
+ BigDecimal unitPrice = new BigDecimal("1.00"); // 单价(每单位能耗金额)
|
|
|
|
|
+
|
|
|
|
|
+ // 本期费用 = 本期能耗 * 单价
|
|
|
|
|
+ BigDecimal cost = consume.multiply(unitPrice).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal sequentialCost = sequentialCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal pariPassCost = pariPassCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
|
|
|
|
|
+
|
|
|
|
|
+ // 标煤量 = 能耗 * 折标系数
|
|
|
|
|
+ BigDecimal coalAmount = consume.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal sequentialCoal = sequentialCon.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal pariPassCoal = pariPassCon.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
|
|
+
|
|
|
|
|
+ // CO2 排放量 = 标煤量 * 系数 2.4589(固定常量,模拟用)
|
|
|
|
|
+ BigDecimal co2Factor = new BigDecimal("2.4589");
|
|
|
|
|
+ BigDecimal co2Amount = coalAmount.multiply(co2Factor).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal sequentialCo2 = sequentialCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal pariPassCo2 = pariPassCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
|
|
|
|
|
+
|
|
|
|
|
+ result.put("consume", consume);
|
|
|
|
|
+ result.put("sequentialCon", sequentialCon);
|
|
|
|
|
+ result.put("pariPassCon", pariPassCon);
|
|
|
|
|
+ result.put("cost", cost);
|
|
|
|
|
+ result.put("sequentialCost", sequentialCost);
|
|
|
|
|
+ result.put("pariPassCost", pariPassCost);
|
|
|
|
|
+ result.put("coalAmount", coalAmount);
|
|
|
|
|
+ result.put("sequentialCoal", sequentialCoal);
|
|
|
|
|
+ result.put("pariPassCoal", pariPassCoal);
|
|
|
|
|
+ result.put("co2Amount", co2Amount);
|
|
|
|
|
+ result.put("sequentialCo2", sequentialCo2);
|
|
|
|
|
+ result.put("pariPassCo2", pariPassCo2);
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 能耗用能趋势(模拟数据版本)
|
|
|
|
|
+ * 参考原系统 queryEnergyTrend 逻辑,仅保留时间维度与同比的结构,数据采用固定规则模拟。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId) {
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 当前期与去年同期的时间范围
|
|
|
|
|
+ LocalDateTime endThisTime;
|
|
|
|
|
+ LocalDateTime startThisTime;
|
|
|
|
|
+ LocalDateTime endOldTime;
|
|
|
|
|
+ LocalDateTime startOldTime;
|
|
|
|
|
+
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ // 日:按小时
|
|
|
|
|
+ endThisTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ // 月:按天
|
|
|
|
|
+ endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
|
|
|
|
|
+ } else if (dateType == 3) {
|
|
|
|
|
+ // 年:按月
|
|
|
|
|
+ endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ startThisTime = getStartTime(dateType, endThisTime);
|
|
|
|
|
+ endOldTime = endThisTime.minusYears(1);
|
|
|
|
|
+ startOldTime = getStartTime(dateType, endOldTime);
|
|
|
|
|
+
|
|
|
|
|
+ // 按时间步长循环生成当前期与去年同期的模拟趋势数据
|
|
|
|
|
+ LocalDateTime curTime = startThisTime;
|
|
|
|
|
+ LocalDateTime oldTime = startOldTime;
|
|
|
|
|
+ int index = 0;
|
|
|
|
|
+
|
|
|
|
|
+ while (!curTime.isAfter(endThisTime)) {
|
|
|
|
|
+ Map<String, Object> row = new HashMap<>();
|
|
|
|
|
+ // 时间标签:当前期时间
|
|
|
|
|
+ row.put("time", curTime.toString());
|
|
|
|
|
+ // 当前期用能:示例 100 + index * 5
|
|
|
|
|
+ BigDecimal currentValue = BigDecimal.valueOf(100 + index * 5L);
|
|
|
|
|
+ // 去年同期用能:示例 80 + index * 5
|
|
|
|
|
+ BigDecimal lastYearValue = BigDecimal.valueOf(80 + index * 5L);
|
|
|
|
|
+ row.put("currentValue", currentValue);
|
|
|
|
|
+ row.put("lastYearValue", lastYearValue);
|
|
|
|
|
+ // 去年同期时间标签
|
|
|
|
|
+ row.put("lastYearTime", oldTime.toString());
|
|
|
|
|
+
|
|
|
|
|
+ list.add(row);
|
|
|
|
|
+
|
|
|
|
|
+ // 根据时间类型推进时间
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ curTime = curTime.plusHours(1);
|
|
|
|
|
+ oldTime = oldTime.plusHours(1);
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ curTime = curTime.plusDays(1);
|
|
|
|
|
+ oldTime = oldTime.plusDays(1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ curTime = curTime.plusMonths(1);
|
|
|
|
|
+ oldTime = oldTime.plusMonths(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ index++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算指定时间类型的开始时间
|
|
|
|
|
+ * dateType: 1-日(当天 00:00:00)、2-月(当月第一天 00:00:00)、3-年(当年第一天 00:00:00)
|
|
|
|
|
+ */
|
|
|
|
|
+ private LocalDateTime getStartTime(Integer dateType, LocalDateTime endTime) {
|
|
|
|
|
+ if (dateType == null || endTime == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ LocalDate firstDayOfMonth = endTime.toLocalDate().withDayOfMonth(1);
|
|
|
|
|
+ return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
|
|
|
|
|
+ } else if (dateType == 3) {
|
|
|
|
|
+ LocalDate firstDayOfYear = endTime.toLocalDate().with(TemporalAdjusters.firstDayOfYear());
|
|
|
|
|
+ return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
|
|
|
|
|
+ }
|
|
|
|
|
+ return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 建筑能耗分析(模拟数据版本)
|
|
|
|
|
+ * 参考原系统 queryBuildingRanking:按指定时间维度与能耗条目,对建筑/楼层进行能耗排名。
|
|
|
|
|
+ * 当前实现:空间信息(建筑/楼层)从 leo.ems_space 表真实查询,仅能耗值使用规则模拟。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId) {
|
|
|
|
|
+ List<Map<String, Object>> resultList = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 确定项目及根空间
|
|
|
|
|
+ Long projectId = resolveProjectId(null);
|
|
|
|
|
+ if (projectId == null) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+ EmsProject project = emsProjectMapper.selectById(projectId);
|
|
|
|
|
+ if (project == null || project.getSpaceId() == null) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+ Long rootSpaceId = project.getSpaceId();
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询空间列表:
|
|
|
|
|
+ // - 未指定 spaceId:取根空间下 type=3 的建筑
|
|
|
|
|
+ // - 指定 spaceId:取该空间下 type=4 的楼层
|
|
|
|
|
+ List<EmsSpace> spaceList;
|
|
|
|
|
+ if (spaceId == null) {
|
|
|
|
|
+ spaceList = emsSpaceService.list(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EmsSpace>()
|
|
|
|
|
+ .eq(EmsSpace::getRootId, rootSpaceId)
|
|
|
|
|
+ .eq(EmsSpace::getType, 3) // 3 = 建筑
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ spaceList = emsSpaceService.list(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EmsSpace>()
|
|
|
|
|
+ .eq(EmsSpace::getParentId, spaceId)
|
|
|
|
|
+ .eq(EmsSpace::getType, 4) // 4 = 楼层
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ if (spaceList == null || spaceList.isEmpty()) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 为每个空间模拟能耗值(真实系统中此处应从 TSDB 或能耗统计表获取)
|
|
|
|
|
+ BigDecimal base = new BigDecimal("1000");
|
|
|
|
|
+ BigDecimal step = new BigDecimal("150");
|
|
|
|
|
+ BigDecimal factor;
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ factor = new BigDecimal("1.5");
|
|
|
|
|
+ } else if (dateType == 3) {
|
|
|
|
|
+ factor = new BigDecimal("2.0");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < spaceList.size(); i++) {
|
|
|
|
|
+ EmsSpace space = spaceList.get(i);
|
|
|
|
|
+ if (space == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
|
|
+ map.put("name", space.getName());
|
|
|
|
|
+ BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(i))).multiply(factor);
|
|
|
|
|
+ map.put("value", value);
|
|
|
|
|
+ resultList.add(map);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 按 value 从大到小排序,形成能耗排名
|
|
|
|
|
+ resultList.sort((m1, m2) -> {
|
|
|
|
|
+ BigDecimal v1 = (BigDecimal) m1.get("value");
|
|
|
|
|
+ BigDecimal v2 = (BigDecimal) m2.get("value");
|
|
|
|
|
+ if (v1 == null && v2 == null) return 0;
|
|
|
|
|
+ if (v1 == null) return 1;
|
|
|
|
|
+ if (v2 == null) return -1;
|
|
|
|
|
+ return v2.compareTo(v1);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
|
|
|
|
|
+ * - 能源类型来自 EmsModelService.getEnergyTypeList(电/水/气)
|
|
|
|
|
+ * - 折算系数来自 leo.ems_project_conversion_factor(优先取项目专属,其次 project_id=0 的公共配置)
|
|
|
|
|
+ * - 真实 TSDB 能耗数据暂不可用,当前按规则模拟各能源类型的能耗值。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> queryOverviewTop(Integer dateType, Long projectId) {
|
|
|
|
|
+ Map<String, Object> resultMap = new HashMap<>();
|
|
|
|
|
+ if (dateType == null) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 时间范围,目前仅用于保持与原接口结构一致
|
|
|
|
|
+ LocalDateTime endTime = LocalDateTime.now();
|
|
|
|
|
+ LocalDateTime startTime = getStartTime(dateType, endTime);
|
|
|
|
|
+ if (startTime == null) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Long pid = resolveProjectId(projectId);
|
|
|
|
|
+ if (pid == null) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 项目信息(含面积、根空间 ID)
|
|
|
|
|
+ EmsProjectResponse project = getProject(pid);
|
|
|
|
|
+ if (project == null || project.getSpaceId() == null) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 能源类型列表(电/水/气)
|
|
|
|
|
+ List<EmsEnergyTypeVO> energyTypes = emsModelService.getEnergyTypeList();
|
|
|
|
|
+ if (energyTypes == null || energyTypes.isEmpty()) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<Integer> typeIds = energyTypes.stream()
|
|
|
|
|
+ .filter(t -> t.getId() != null)
|
|
|
|
|
+ .map(t -> t.getId().intValue())
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (typeIds.isEmpty()) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询折算系数(项目专属 + 公共)
|
|
|
|
|
+ List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsProjectConversionFactor>()
|
|
|
|
|
+ .in(EmsProjectConversionFactor::getEnergyType, typeIds)
|
|
|
|
|
+ .in(EmsProjectConversionFactor::getProjectId, pid, 0L)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (factorList == null || factorList.isEmpty()) {
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 优先使用项目专属配置;若没有则退回公共配置
|
|
|
|
|
+ Map<Integer, List<EmsProjectConversionFactor>> groupedByType = factorList.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(EmsProjectConversionFactor::getEnergyType));
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 模拟各能源类型能耗值(真实实现应从 TSDB 或历史统计表获取)
|
|
|
|
|
+ Map<Integer, BigDecimal> consumeMap = new HashMap<>();
|
|
|
|
|
+ BigDecimal base = new BigDecimal("1000");
|
|
|
|
|
+ BigDecimal step = new BigDecimal("200");
|
|
|
|
|
+ BigDecimal factor;
|
|
|
|
|
+ if (dateType == 1) {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ } else if (dateType == 2) {
|
|
|
|
|
+ factor = new BigDecimal("1.5");
|
|
|
|
|
+ } else if (dateType == 3) {
|
|
|
|
|
+ factor = new BigDecimal("2.0");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int index = 0;
|
|
|
|
|
+ for (Integer typeId : typeIds) {
|
|
|
|
|
+ BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
|
|
|
|
|
+ if (value.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
|
|
+ value = BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ consumeMap.put(typeId, value);
|
|
|
|
|
+ index++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal totalCon = consumeMap.values().stream()
|
|
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 计算各能源类型折算标煤与碳排放、占比
|
|
|
|
|
+ BigDecimal coalTotal = BigDecimal.ZERO;
|
|
|
|
|
+ BigDecimal co2Total = BigDecimal.ZERO;
|
|
|
|
|
+ List<Map<String, Object>> ratioList = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (EmsEnergyTypeVO typeVO : energyTypes) {
|
|
|
|
|
+ if (typeVO.getId() == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ Integer typeId = typeVO.getId().intValue();
|
|
|
|
|
+ BigDecimal con = consumeMap.get(typeId);
|
|
|
|
|
+ if (con == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<EmsProjectConversionFactor> conversionFactorList = groupedByType.get(typeId);
|
|
|
|
|
+ if (conversionFactorList == null || conversionFactorList.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 先筛出项目专属配置
|
|
|
|
|
+ List<EmsProjectConversionFactor> projectFactors = conversionFactorList.stream()
|
|
|
|
|
+ .filter(f -> pid.equals(f.getProjectId()))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ List<EmsProjectConversionFactor> effectiveFactors = projectFactors.isEmpty()
|
|
|
|
|
+ ? conversionFactorList
|
|
|
|
|
+ : projectFactors;
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, BigDecimal> factorMap = effectiveFactors.stream()
|
|
|
|
|
+ .filter(f -> f.getName() != null && f.getValue() != null)
|
|
|
|
|
+ .collect(Collectors.toMap(EmsProjectConversionFactor::getName, EmsProjectConversionFactor::getValue,
|
|
|
|
|
+ (oldVal, newVal) -> oldVal));
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal coal = factorMap.get("coal");
|
|
|
|
|
+ if (coal == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal coalAmount = coal.multiply(con).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ BigDecimal co2Amount = coalAmount.multiply(new BigDecimal("2.4589")).setScale(2, RoundingMode.UP);
|
|
|
|
|
+ coalTotal = coalTotal.add(coalAmount);
|
|
|
|
|
+ co2Total = co2Total.add(co2Amount);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> ratioMap = new HashMap<>();
|
|
|
|
|
+ ratioMap.put("name", typeVO.getName());
|
|
|
|
|
+ ratioMap.put("consume", coalAmount);
|
|
|
|
|
+ ratioMap.put("totalConsume", totalCon.multiply(coal).setScale(2, RoundingMode.UP));
|
|
|
|
|
+ ratioList.add(ratioMap);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 单位综合能耗(每平米标煤量)
|
|
|
|
|
+ BigDecimal unitCoal = BigDecimal.ZERO;
|
|
|
|
|
+ if (project.getArea() != null
|
|
|
|
|
+ && project.getArea().compareTo(BigDecimal.ZERO) != 0
|
|
|
|
|
+ && coalTotal.compareTo(BigDecimal.ZERO) != 0) {
|
|
|
|
|
+ unitCoal = coalTotal.divide(project.getArea(), 2, RoundingMode.UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resultMap.put("unitCoal", unitCoal);
|
|
|
|
|
+ resultMap.put("ratioList", ratioList);
|
|
|
|
|
+ resultMap.put("coalTotal", coalTotal);
|
|
|
|
|
+ resultMap.put("co2Total", co2Total);
|
|
|
|
|
+ return resultMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分项能耗占比
|
|
|
|
|
+ * - 分项定义来自 leo.ems_energy_item_code:给定 itemCode 作为父项,按 parent_code 查询下级分项;
|
|
|
|
|
+ * - 若无下级分项,则仅返回父项自身;
|
|
|
|
|
+ * - 各分项及汇总能耗数值暂按规则模拟。
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId) {
|
|
|
|
|
+ List<Map<String, Object>> resultList = new ArrayList<>();
|
|
|
|
|
+ if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Long pid = resolveProjectId(projectId);
|
|
|
|
|
+ if (pid == null) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 当前实现中 startTime/spaceId 仅为预留,不参与模拟计算
|
|
|
|
|
+ LocalDateTime endTime = LocalDateTime.now();
|
|
|
|
|
+ LocalDateTime startTime = getStartTime(dateType, endTime);
|
|
|
|
|
+ if (startTime == null) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 查询父分项编码
|
|
|
|
|
+ EmsEnergyItemCode parent = emsEnergyItemCodeMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsEnergyItemCode>()
|
|
|
|
|
+ .eq(EmsEnergyItemCode::getCode, itemCode)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (parent == null) {
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟父分项总能耗
|
|
|
|
|
+ BigDecimal total = simulateItemConsume(itemCode, dateType, 0);
|
|
|
|
|
+
|
|
|
|
|
+ // 查询子分项列表(按 parent_code)
|
|
|
|
|
+ List<EmsEnergyItemCode> children = emsEnergyItemCodeMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<EmsEnergyItemCode>()
|
|
|
|
|
+ .eq(EmsEnergyItemCode::getParentCode, parent.getCode())
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (children == null || children.isEmpty()) {
|
|
|
|
|
+ // 无下级分项:仅返回父项自身
|
|
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
|
|
+ map.put("name", parent.getName());
|
|
|
|
|
+ map.put("consume", total);
|
|
|
|
|
+ map.put("total", total);
|
|
|
|
|
+ resultList.add(map);
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int index = 0;
|
|
|
|
|
+ for (EmsEnergyItemCode child : children) {
|
|
|
|
|
+ if (child == null) continue;
|
|
|
|
|
+ BigDecimal consume = simulateItemConsume(child.getCode(), dateType, index);
|
|
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
|
|
+ map.put("name", child.getName());
|
|
|
|
|
+ map.put("consume", consume);
|
|
|
|
|
+ map.put("total", total);
|
|
|
|
|
+ resultList.add(map);
|
|
|
|
|
+ index++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return resultList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分项能耗模拟函数
|
|
|
|
|
+ * 根据 dateType 与 index 返回不同量级的示例值。
|
|
|
|
|
+ */
|
|
|
|
|
+ private BigDecimal simulateItemConsume(String code, Integer dateType, int index) {
|
|
|
|
|
+ BigDecimal base = new BigDecimal("500");
|
|
|
|
|
+ BigDecimal step = new BigDecimal("80");
|
|
|
|
|
+ BigDecimal factor;
|
|
|
|
|
+ if (dateType != null && dateType == 1) {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ } else if (dateType != null && dateType == 2) {
|
|
|
|
|
+ factor = new BigDecimal("1.5");
|
|
|
|
|
+ } else if (dateType != null && dateType == 3) {
|
|
|
|
|
+ factor = new BigDecimal("2.0");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ factor = BigDecimal.ONE;
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
|
|
|
|
|
+ if (value.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
|
|
+ value = BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ return value.setScale(2, RoundingMode.UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Long resolveProjectId(Long projectId) {
|
|
|
|
|
+ if (projectId != null) return projectId;
|
|
|
|
|
+ List<EmsProject> list = emsProjectMapper.selectList(null);
|
|
|
|
|
+ return list.isEmpty() ? null : list.get(0).getId();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 根据设备系统编码返回名称(与 dmp_product.deviceType 含义保持一致) */
|
|
|
|
|
+ private String deviceSystemName(Integer deviceSystem) {
|
|
|
|
|
+ if (deviceSystem == null) return "";
|
|
|
|
|
+ switch (deviceSystem) {
|
|
|
|
|
+ case 501:
|
|
|
|
|
+ return "监控系统";
|
|
|
|
|
+ case 502:
|
|
|
|
|
+ return "门禁系统";
|
|
|
|
|
+ case 503:
|
|
|
|
|
+ return "梯控系统";
|
|
|
|
|
+ case 504:
|
|
|
|
|
+ return "机房系统";
|
|
|
|
|
+ case 509:
|
|
|
|
|
+ return "环境系统";
|
|
|
|
|
+ case 510:
|
|
|
|
|
+ return "照明系统";
|
|
|
|
|
+ default:
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|