|
|
@@ -1,10 +1,17 @@
|
|
|
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.core.bean.ApiResult;
|
|
|
import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.demo.RemoteTsdbProxyService;
|
|
|
+import com.usky.demo.domain.EnergyItemSumQueryVO;
|
|
|
+import com.usky.demo.domain.EnergyItemSumResultVO;
|
|
|
import com.usky.ems.domain.*;
|
|
|
+import com.usky.ems.mapper.DmpProductMapper;
|
|
|
import com.usky.ems.mapper.EmsDeviceMapper;
|
|
|
import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
|
|
|
+import com.usky.ems.mapper.EmsProjectConversionFactorMapper;
|
|
|
import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
|
|
|
import com.usky.ems.mapper.EmsProjectMapper;
|
|
|
import com.usky.ems.service.EmsModelService;
|
|
|
@@ -15,6 +22,7 @@ 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.EmsSummaryResponse;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
@@ -23,6 +31,7 @@ import java.math.RoundingMode;
|
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.LocalTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
import java.time.temporal.TemporalAdjusters;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
@@ -38,6 +47,13 @@ import java.util.stream.Collectors;
|
|
|
@Service
|
|
|
public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
|
|
|
+ private static final DateTimeFormatter TSDB_TIME_FORMAT =
|
|
|
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
|
|
+ private static final BigDecimal FALLBACK_CO2_FACTOR = new BigDecimal("2.4589");
|
|
|
+ private static final BigDecimal FALLBACK_COAL_FACTOR = new BigDecimal("0.70");
|
|
|
+ private static final String FACTOR_NAME_COAL = "coal";
|
|
|
+ private static final String FACTOR_NAME_CO2 = "co2";
|
|
|
+
|
|
|
@Autowired
|
|
|
private EmsModelService emsModelService;
|
|
|
|
|
|
@@ -53,9 +69,18 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
@Autowired
|
|
|
private EmsProjectMapper emsProjectMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private EmsProjectConversionFactorMapper emsProjectConversionFactorMapper;
|
|
|
+
|
|
|
@Autowired
|
|
|
private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private DmpProductMapper dmpProductMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RemoteTsdbProxyService remoteTsdbProxyService;
|
|
|
+
|
|
|
@Override
|
|
|
public EmsProjectResponse getProject(Integer projectId) {
|
|
|
LambdaQueryWrapper<EmsProject> wrapper = new LambdaQueryWrapper<>();
|
|
|
@@ -192,54 +217,193 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 分类能耗统计(模拟数据版本)
|
|
|
- * 说明:
|
|
|
- * - 目前不接真实能耗库,仅按固定示例数据做一套完整计算逻辑,保证前端联调正常。
|
|
|
- * - 返回结构与原系统保持一致:Map<String, Object>。
|
|
|
+ * 分类能耗统计:按能源类型关联产品汇总 TSDB 用量,并计算环比/同比及折标煤、碳排放。
|
|
|
*/
|
|
|
@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");
|
|
|
+ public Map<String, Object> queryClassificationEnergy(Integer dateType, String identifier, Integer energyType, Long projectId) {
|
|
|
+ if (dateType == null || StringUtils.isBlank(identifier) || energyType == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long resolvedProjectId = resolveProjectId(projectId);
|
|
|
+ Map<String, BigDecimal> factorMap = loadConversionFactorMap(resolvedProjectId, energyType);
|
|
|
+ BigDecimal coalFactor = factorMap.getOrDefault(FACTOR_NAME_COAL, FALLBACK_COAL_FACTOR);
|
|
|
+ BigDecimal co2Factor = factorMap.getOrDefault(FACTOR_NAME_CO2, FALLBACK_CO2_FACTOR);
|
|
|
+
|
|
|
+ List<String> productCodeList = dmpProductMapper.selectProductCodesByEnergyType(
|
|
|
+ SecurityUtils.getTenantId(), energyType);
|
|
|
+ if (productCodeList == null || productCodeList.isEmpty()) {
|
|
|
+ return buildClassificationResult(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
|
|
|
+ coalFactor, co2Factor);
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime endTime = LocalDateTime.now();
|
|
|
+ LocalDateTime startTime = getStartTime(dateType, endTime);
|
|
|
+ if (startTime == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal data = sumEnergyByProductCodes(productCodeList, identifier, startTime, endTime);
|
|
|
+
|
|
|
+ LocalDateTime pariPassuEndTime = endTime.minusYears(1);
|
|
|
+ LocalDateTime pariPassuStartTime = getStartTime(dateType, pariPassuEndTime);
|
|
|
+ BigDecimal pariPassuData = sumEnergyByProductCodes(productCodeList, identifier,
|
|
|
+ pariPassuStartTime, pariPassuEndTime);
|
|
|
+
|
|
|
+ LocalDateTime sequentialEndTime;
|
|
|
+ if (dateType == 1) {
|
|
|
+ sequentialEndTime = endTime.minusDays(1);
|
|
|
+ } else if (dateType == 2) {
|
|
|
+ sequentialEndTime = endTime.minusMonths(1);
|
|
|
+ } else {
|
|
|
+ sequentialEndTime = endTime.minusYears(1);
|
|
|
+ }
|
|
|
+ LocalDateTime sequentialStartTime = getStartTime(dateType, sequentialEndTime);
|
|
|
+ BigDecimal sequentialData = sumEnergyByProductCodes(productCodeList, identifier,
|
|
|
+ sequentialStartTime, sequentialEndTime);
|
|
|
+
|
|
|
+ return buildClassificationResult(data, sequentialData, pariPassuData, coalFactor, co2Factor);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析当前租户项目 ID(未传则取第一个项目)
|
|
|
+ */
|
|
|
+ private Long resolveProjectId(Long projectId) {
|
|
|
+ LambdaQueryWrapper<EmsProject> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(EmsProject::getTenantId, SecurityUtils.getTenantId());
|
|
|
+ if (projectId != null) {
|
|
|
+ wrapper.eq(EmsProject::getId, projectId);
|
|
|
+ EmsProject project = emsProjectMapper.selectOne(wrapper);
|
|
|
+ return project != null ? project.getId() : null;
|
|
|
+ }
|
|
|
+ List<EmsProject> list = emsProjectMapper.selectList(wrapper);
|
|
|
+ return list.isEmpty() ? null : list.get(0).getId();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载折算系数:优先项目专属配置,否则退回 project_id=0 的公共配置
|
|
|
+ */
|
|
|
+ private Map<String, BigDecimal> loadConversionFactorMap(Long projectId, Integer energyType) {
|
|
|
+ LambdaQueryWrapper<EmsProjectConversionFactor> wrapper = Wrappers.lambdaQuery();
|
|
|
+ wrapper.eq(EmsProjectConversionFactor::getEnergyType, energyType);
|
|
|
+ if (projectId != null) {
|
|
|
+ wrapper.in(EmsProjectConversionFactor::getProjectId, projectId, 0L);
|
|
|
+ } else {
|
|
|
+ wrapper.eq(EmsProjectConversionFactor::getProjectId, 0L);
|
|
|
+ }
|
|
|
+ List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(wrapper);
|
|
|
+ if (factorList == null || factorList.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ List<EmsProjectConversionFactor> effectiveFactors = factorList;
|
|
|
+ if (projectId != null) {
|
|
|
+ effectiveFactors = factorList.stream()
|
|
|
+ .filter(f -> Objects.equals(f.getProjectId(), projectId))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ if (effectiveFactors.isEmpty()) {
|
|
|
+ effectiveFactors = factorList;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return effectiveFactors.stream()
|
|
|
+ .filter(f -> f.getName() != null && f.getValue() != null)
|
|
|
+ .collect(Collectors.toMap(EmsProjectConversionFactor::getName,
|
|
|
+ EmsProjectConversionFactor::getValue, (oldVal, newVal) -> oldVal));
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal sumEnergyByProductCodes(List<String> productCodeList, String fieldIdentifier,
|
|
|
+ LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ BigDecimal total = BigDecimal.ZERO;
|
|
|
+ String start = formatTsdbTime(startTime);
|
|
|
+ String end = formatTsdbTime(endTime);
|
|
|
+ for (String productCode : productCodeList) {
|
|
|
+ if (StringUtils.isBlank(productCode)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ EnergyItemSumQueryVO queryVO = new EnergyItemSumQueryVO();
|
|
|
+ queryVO.setIdentifier(fieldIdentifier);
|
|
|
+ queryVO.setSuperTable("super_" + productCode.trim());
|
|
|
+ queryVO.setStartTime(start);
|
|
|
+ queryVO.setEndTime(end);
|
|
|
+ ApiResult<EnergyItemSumResultVO> apiResult = remoteTsdbProxyService.sumEnergyItem(queryVO);
|
|
|
+ if (apiResult == null || apiResult.getData() == null || apiResult.getData().getSumDiff() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ total = total.add(apiResult.getData().getSumDiff());
|
|
|
+ }
|
|
|
+ return total;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildClassificationResult(BigDecimal data, BigDecimal sequentialData,
|
|
|
+ BigDecimal pariPassuData,
|
|
|
+ BigDecimal coal, BigDecimal co2Factor) {
|
|
|
+ if (data == null) {
|
|
|
+ data = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ if (sequentialData == null) {
|
|
|
+ sequentialData = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ if (pariPassuData == null) {
|
|
|
+ pariPassuData = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal coalAmount = data.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
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;
|
|
|
+ BigDecimal sequentialCon = BigDecimal.ZERO;
|
|
|
+ BigDecimal sequentialCost = BigDecimal.ZERO;
|
|
|
+ BigDecimal sequentialCoal = BigDecimal.ZERO;
|
|
|
+ BigDecimal sequentialCo2 = BigDecimal.ZERO;
|
|
|
+ if (sequentialData.compareTo(BigDecimal.ZERO) != 0) {
|
|
|
+ BigDecimal sequentialCoalAmountData = sequentialData.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
+ BigDecimal sequentialCo2AmountData = sequentialCoalAmountData.multiply(co2Factor)
|
|
|
+ .setScale(2, RoundingMode.UP);
|
|
|
+ sequentialCon = data.subtract(sequentialData).divide(sequentialData, 2, RoundingMode.UP);
|
|
|
+ sequentialCoal = coalAmount.subtract(sequentialCoalAmountData)
|
|
|
+ .divide(sequentialCoalAmountData, 2, RoundingMode.UP);
|
|
|
+ sequentialCo2 = co2Amount.subtract(sequentialCo2AmountData)
|
|
|
+ .divide(sequentialCo2AmountData, 2, RoundingMode.UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal pariPassCon = BigDecimal.ZERO;
|
|
|
+ BigDecimal pariPassCost = BigDecimal.ZERO;
|
|
|
+ BigDecimal pariPassCoal = BigDecimal.ZERO;
|
|
|
+ BigDecimal pariPassCo2 = BigDecimal.ZERO;
|
|
|
+ if (pariPassuData.compareTo(BigDecimal.ZERO) != 0) {
|
|
|
+ BigDecimal pariPassCoalAmountData = pariPassuData.multiply(coal).setScale(2, RoundingMode.UP);
|
|
|
+ BigDecimal pariPassCo2AmountData = pariPassCoalAmountData.multiply(co2Factor)
|
|
|
+ .setScale(2, RoundingMode.UP);
|
|
|
+ pariPassCon = data.subtract(pariPassuData).divide(pariPassuData, 2, RoundingMode.UP);
|
|
|
+ pariPassCoal = coalAmount.subtract(pariPassCoalAmountData)
|
|
|
+ .divide(pariPassCoalAmountData, 2, RoundingMode.UP);
|
|
|
+ pariPassCo2 = co2Amount.subtract(pariPassCo2AmountData)
|
|
|
+ .divide(pariPassCo2AmountData, 2, RoundingMode.UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal cost = BigDecimal.ZERO;
|
|
|
+ if (co2Amount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ cost = co2Amount.divide(new BigDecimal("18"), 2, RoundingMode.UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("consume", data);
|
|
|
+ map.put("sequentialCon", sequentialCon);
|
|
|
+ map.put("pariPassCon", pariPassCon);
|
|
|
+ map.put("sequential", sequentialData);
|
|
|
+ map.put("pariPass", pariPassuData);
|
|
|
+ map.put("cost", cost);
|
|
|
+ map.put("sequentialCost", sequentialCost);
|
|
|
+ map.put("pariPassCost", pariPassCost);
|
|
|
+ map.put("coalAmount", coalAmount);
|
|
|
+ map.put("sequentialCoal", sequentialCoal);
|
|
|
+ map.put("pariPassCoal", pariPassCoal);
|
|
|
+ map.put("co2Amount", co2Amount);
|
|
|
+ map.put("sequentialCo2", sequentialCo2);
|
|
|
+ map.put("pariPassCo2", pariPassCo2);
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String formatTsdbTime(LocalDateTime time) {
|
|
|
+ return time.format(TSDB_TIME_FORMAT);
|
|
|
}
|
|
|
|
|
|
/**
|