|
|
@@ -5,23 +5,30 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.usky.common.core.bean.CommonPage;
|
|
|
import com.usky.common.core.exception.BusinessException;
|
|
|
import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.vpp.constant.VppDrEventStatus;
|
|
|
+import com.usky.vpp.domain.VppCustomer;
|
|
|
import com.usky.vpp.domain.VppDrEvent;
|
|
|
import com.usky.vpp.domain.VppDrInvitation;
|
|
|
-import com.usky.vpp.domain.VppDrParticipation;
|
|
|
+import com.usky.vpp.domain.VppDrSubsidyPrediction;
|
|
|
import com.usky.vpp.domain.VppResourcePoint;
|
|
|
+import com.usky.vpp.domain.VppSite;
|
|
|
+import com.usky.vpp.mapper.VppCustomerMapper;
|
|
|
import com.usky.vpp.mapper.VppDrEventMapper;
|
|
|
import com.usky.vpp.mapper.VppDrInvitationMapper;
|
|
|
-import com.usky.vpp.mapper.VppDrParticipationMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrSubsidyPredictionMapper;
|
|
|
import com.usky.vpp.mapper.VppResourcePointMapper;
|
|
|
+import com.usky.vpp.mapper.VppSiteMapper;
|
|
|
import com.usky.vpp.service.VppBaselineService;
|
|
|
import com.usky.vpp.service.VppDrMonitorService;
|
|
|
import com.usky.vpp.service.vo.BaselinePointVO;
|
|
|
import com.usky.vpp.service.vo.DrMonitorCurveVO;
|
|
|
import com.usky.vpp.service.vo.DrMonitorRecordVO;
|
|
|
+import com.usky.vpp.service.vo.DrMonitorSiteResourceVO;
|
|
|
import com.usky.vpp.service.vo.DrMonitorSummaryVO;
|
|
|
import com.usky.vpp.service.vo.SiteBaselineVO;
|
|
|
import com.usky.vpp.util.VppAuditHelper;
|
|
|
import com.usky.vpp.util.VppPageHelper;
|
|
|
+import com.usky.vpp.util.VppResourceTypeHelper;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
@@ -36,12 +43,9 @@ import java.time.LocalTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
import java.util.ArrayList;
|
|
|
-import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.HashSet;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
@@ -51,15 +55,15 @@ import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 需求响应监测(响应记录)
|
|
|
- * <p>统一口径:站点 → is_support_peak=1 资源点 → 按资源点平均。</p>
|
|
|
+ * <p>统一口径:时间区间内执行中/已结束事件 → 参与且执行中/已结束的未删除邀约。</p>
|
|
|
*/
|
|
|
@Service
|
|
|
public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
|
|
|
- private static final int EVENT_STATUS_EXECUTING = 2;
|
|
|
- private static final int EVENT_STATUS_ENDED = 3;
|
|
|
+ private static final int EVENT_STATUS_EXECUTING = VppDrEventStatus.EXECUTING;
|
|
|
+ private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
|
|
|
+ private static final int REPLY_ACCEPT = 1;
|
|
|
private static final int SUPPORT_PEAK = 1;
|
|
|
- private static final int PARTICIPATE_ACCEPT = 1;
|
|
|
private static final int SCALE = 4;
|
|
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
@@ -71,142 +75,75 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
@Autowired
|
|
|
private VppDrInvitationMapper invitationMapper;
|
|
|
@Autowired
|
|
|
- private VppDrParticipationMapper participationMapper;
|
|
|
+ private VppCustomerMapper customerMapper;
|
|
|
@Autowired
|
|
|
private VppResourcePointMapper resourcePointMapper;
|
|
|
@Autowired
|
|
|
+ private VppSiteMapper siteMapper;
|
|
|
+ @Autowired
|
|
|
private VppBaselineService baselineService;
|
|
|
+ @Autowired
|
|
|
+ private VppDrSubsidyPredictionMapper subsidyPredictionMapper;
|
|
|
|
|
|
@Override
|
|
|
public DrMonitorSummaryVO getSummary(String startDate, String endDate, List<Long> siteIds) {
|
|
|
- Integer tenantId = SecurityUtils.getTenantId();
|
|
|
- LocalDateTime rangeStart = parseStart(startDate);
|
|
|
- LocalDateTime rangeEnd = parseEnd(endDate);
|
|
|
requireSiteIds(siteIds);
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+ List<VppDrInvitation> invitations = listMonitorInvitations(
|
|
|
+ parseStart(startDate), parseEnd(endDate), siteIds, tenantId);
|
|
|
|
|
|
- List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
|
|
|
DrMonitorSummaryVO vo = new DrMonitorSummaryVO();
|
|
|
- vo.setPeakResourceCount((long) peakResources.size());
|
|
|
- if (peakResources.isEmpty()) {
|
|
|
+ if (invitations.isEmpty()) {
|
|
|
return vo;
|
|
|
}
|
|
|
-
|
|
|
- Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
|
|
|
- Map<Long, Long> resourceSiteMap = peakResources.stream()
|
|
|
- .collect(Collectors.toMap(VppResourcePoint::getId, VppResourcePoint::getSiteId, (a, b) -> a));
|
|
|
-
|
|
|
- List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
|
|
|
- Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);
|
|
|
- List<VppDrEvent> rangedEvents = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd);
|
|
|
-
|
|
|
- Set<Long> countEventIds = rangedEvents.stream()
|
|
|
- .filter(e -> e.getEventStatus() != null
|
|
|
- && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
|
|
|
- .map(VppDrEvent::getId)
|
|
|
- .collect(Collectors.toSet());
|
|
|
- Set<Long> endedEventIds = rangedEvents.stream()
|
|
|
- .filter(e -> Objects.equals(e.getEventStatus(), EVENT_STATUS_ENDED))
|
|
|
- .map(VppDrEvent::getId)
|
|
|
- .collect(Collectors.toSet());
|
|
|
-
|
|
|
- // 每个资源点:参与的执行中/已结束事件数
|
|
|
- Map<Long, Set<Long>> resourceCountEvents = new HashMap<>();
|
|
|
- // 每个资源点:参与的已结束事件
|
|
|
- Map<Long, Set<Long>> resourceEndedEvents = new HashMap<>();
|
|
|
- for (VppDrParticipation p : participations) {
|
|
|
- if (p.getResourceId() == null || p.getEventId() == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (countEventIds.contains(p.getEventId())) {
|
|
|
- resourceCountEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
|
|
|
- }
|
|
|
- if (endedEventIds.contains(p.getEventId())) {
|
|
|
- resourceEndedEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Long countSum = 0L;
|
|
|
- for (VppResourcePoint resource : peakResources) {
|
|
|
- countSum += resourceCountEvents.getOrDefault(resource.getId(), Collections.emptySet()).size();
|
|
|
- }
|
|
|
- vo.setResponseCount(countSum);
|
|
|
-
|
|
|
- // 邀约按站点:容量与达标
|
|
|
- Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
|
|
|
- List<VppDrInvitation> endedInvitations = listInvitations(endedEventIds, peakSiteIds, tenantId);
|
|
|
- Map<Long, List<VppDrInvitation>> invitationsBySite = endedInvitations.stream()
|
|
|
- .filter(i -> i.getSiteId() != null)
|
|
|
- .collect(Collectors.groupingBy(VppDrInvitation::getSiteId));
|
|
|
-
|
|
|
- BigDecimal capacitySum = BigDecimal.ZERO;
|
|
|
- BigDecimal rateSum = BigDecimal.ZERO;
|
|
|
- int rateResourceCount = 0;
|
|
|
- for (VppResourcePoint resource : peakResources) {
|
|
|
- Long siteId = resourceSiteMap.get(resource.getId());
|
|
|
- Set<Long> endedForResource = resourceEndedEvents.getOrDefault(resource.getId(), Collections.emptySet());
|
|
|
- List<VppDrInvitation> siteInvitations = invitationsBySite.getOrDefault(siteId, Collections.emptyList())
|
|
|
- .stream()
|
|
|
- .filter(i -> endedForResource.contains(i.getDrEventId()))
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- BigDecimal resourceCapacity = BigDecimal.ZERO;
|
|
|
- long qualified = 0;
|
|
|
- for (VppDrInvitation invitation : siteInvitations) {
|
|
|
- resourceCapacity = resourceCapacity.add(nz(invitation.getActualResponseCapacityKw()));
|
|
|
- if (isCompletionQualified(invitation.getResponseCompletionRate())) {
|
|
|
- qualified++;
|
|
|
- }
|
|
|
- }
|
|
|
- capacitySum = capacitySum.add(resourceCapacity);
|
|
|
- if (!siteInvitations.isEmpty()) {
|
|
|
- rateSum = rateSum.add(BigDecimal.valueOf(qualified)
|
|
|
- .divide(BigDecimal.valueOf(siteInvitations.size()), 8, RoundingMode.HALF_UP));
|
|
|
- rateResourceCount++;
|
|
|
+ int total = invitations.size();
|
|
|
+ BigDecimal capacity = BigDecimal.ZERO;
|
|
|
+ long qualified = 0L;
|
|
|
+ for (VppDrInvitation invitation : invitations) {
|
|
|
+ capacity = capacity.add(nz(invitation.getActualResponseCapacityKw()));
|
|
|
+ if (isCompletionQualified(invitation.getResponseCompletionRate())) {
|
|
|
+ qualified++;
|
|
|
}
|
|
|
}
|
|
|
- vo.setResponseCapacityKw(avg(capacitySum, peakResources.size()));
|
|
|
- if (rateResourceCount > 0) {
|
|
|
- vo.setHistoricalQualifiedRate(avg(rateSum, rateResourceCount));
|
|
|
- }
|
|
|
+ vo.setResponseCount((long) total);
|
|
|
+ vo.setResponseCapacityKw(capacity.setScale(SCALE, RoundingMode.HALF_UP));
|
|
|
+ vo.setHistoricalQualifiedRate(BigDecimal.valueOf(qualified)
|
|
|
+ .divide(BigDecimal.valueOf(total), SCALE, RoundingMode.HALF_UP));
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public CommonPage<DrMonitorRecordVO> pageRecords(String startDate, String endDate,
|
|
|
List<Long> siteIds, Map<String, Object> params) {
|
|
|
- Integer tenantId = SecurityUtils.getTenantId();
|
|
|
- LocalDateTime rangeStart = parseStart(startDate);
|
|
|
- LocalDateTime rangeEnd = parseEnd(endDate);
|
|
|
requireSiteIds(siteIds);
|
|
|
-
|
|
|
- List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
|
|
|
- if (peakResources.isEmpty()) {
|
|
|
- Page<?> page = VppPageHelper.of(params);
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+ List<VppDrInvitation> invitations = listMonitorInvitations(
|
|
|
+ parseStart(startDate), parseEnd(endDate), siteIds, tenantId);
|
|
|
+ Page<?> page = VppPageHelper.of(params);
|
|
|
+ if (invitations.isEmpty()) {
|
|
|
return new CommonPage<>(Collections.emptyList(), 0L, page.getCurrent(), page.getSize());
|
|
|
}
|
|
|
|
|
|
- Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
|
|
|
- Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
|
|
|
- List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
|
|
|
- Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);
|
|
|
-
|
|
|
- List<VppDrEvent> events = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd).stream()
|
|
|
- .filter(e -> e.getEventStatus() != null
|
|
|
- && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
|
|
|
- .sorted(Comparator.comparing(VppDrEvent::getStartTime, Comparator.nullsLast(Comparator.reverseOrder())))
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- Set<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toSet());
|
|
|
- List<VppDrInvitation> invitations = listInvitations(eventIds, peakSiteIds, tenantId);
|
|
|
- Map<Long, List<VppDrInvitation>> byEvent = invitations.stream()
|
|
|
- .filter(i -> i.getDrEventId() != null)
|
|
|
- .collect(Collectors.groupingBy(VppDrInvitation::getDrEventId));
|
|
|
-
|
|
|
- List<DrMonitorRecordVO> records = events.stream()
|
|
|
- .map(e -> toRecordVo(e, byEvent.getOrDefault(e.getId(), Collections.emptyList())))
|
|
|
+ Set<Long> eventIds = invitations.stream()
|
|
|
+ .map(VppDrInvitation::getDrEventId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ Map<Long, VppDrEvent> eventMap = loadEventMapByIds(eventIds, tenantId);
|
|
|
+ Map<Long, VppCustomer> customerMap = loadCustomerMap(invitations);
|
|
|
+ Map<Long, VppSite> siteMap = loadSiteMap(invitations, tenantId);
|
|
|
+ Map<Long, BigDecimal> subsidyByEvent = loadSubsidyAmountByEvent(
|
|
|
+ eventIds, new LinkedHashSet<>(siteIds), tenantId);
|
|
|
+
|
|
|
+ List<DrMonitorRecordVO> records = invitations.stream()
|
|
|
+ .sorted(Comparator.comparing(
|
|
|
+ i -> eventMap.get(i.getDrEventId()) == null
|
|
|
+ ? null : eventMap.get(i.getDrEventId()).getStartTime(),
|
|
|
+ Comparator.nullsLast(Comparator.reverseOrder())))
|
|
|
+ .map(inv -> toRecordVo(inv, eventMap.get(inv.getDrEventId()),
|
|
|
+ customerMap.get(inv.getCustomerId()), siteMap.get(inv.getSiteId()),
|
|
|
+ subsidyByEvent.get(inv.getDrEventId())))
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
- Page<?> page = VppPageHelper.of(params);
|
|
|
long current = page.getCurrent();
|
|
|
long size = page.getSize();
|
|
|
int from = (int) Math.min((current - 1) * size, records.size());
|
|
|
@@ -218,104 +155,158 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public DrMonitorCurveVO getCurve(Long eventId, List<Long> siteIds) {
|
|
|
+ public DrMonitorCurveVO getCurve(Long invitationId) {
|
|
|
+ if (invitationId == null) {
|
|
|
+ throw new BusinessException("邀约ID不能为空");
|
|
|
+ }
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+ VppDrInvitation invitation = invitationMapper.selectById(invitationId);
|
|
|
+ if (invitation == null || VppAuditHelper.isDeleted(invitation.getDeleteFlag())) {
|
|
|
+ throw new BusinessException("邀约记录不存在");
|
|
|
+ }
|
|
|
+ if (tenantId != null && invitation.getTenantId() != null && !tenantId.equals(invitation.getTenantId())) {
|
|
|
+ throw new BusinessException("无权操作其他租户的邀约");
|
|
|
+ }
|
|
|
+ if (invitation.getSiteId() == null || invitation.getSiteId() <= 0) {
|
|
|
+ throw new BusinessException("邀约未关联站点");
|
|
|
+ }
|
|
|
+
|
|
|
+ VppDrEvent event = invitation.getDrEventId() != null
|
|
|
+ ? eventMapper.selectById(invitation.getDrEventId()) : null;
|
|
|
+ if (event != null && VppAuditHelper.isDeleted(event.getDeleteFlag())) {
|
|
|
+ event = null;
|
|
|
+ }
|
|
|
+ String responseStartTime = resolveInvitationResponseStartTime(invitation, event);
|
|
|
+ SiteBaselineVO baseline = baselineService.getSiteBaseline(invitation.getSiteId(), responseStartTime);
|
|
|
+ BigDecimal actualKw = nz(invitation.getActualResponseCapacityKw()).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ DrMonitorCurveVO curve = new DrMonitorCurveVO();
|
|
|
+ curve.setId(invitation.getId());
|
|
|
+ curve.setSiteId(invitation.getSiteId());
|
|
|
+ curve.setActualResponseCapacityKw(actualKw);
|
|
|
+ if (invitation.getExecuteStartDate() != null) {
|
|
|
+ curve.setResponseDate(invitation.getExecuteStartDate().format(DATE_FMT));
|
|
|
+ } else if (event != null && event.getStartTime() != null) {
|
|
|
+ curve.setResponseDate(event.getStartTime().toLocalDate().format(DATE_FMT));
|
|
|
+ }
|
|
|
+ if (event != null) {
|
|
|
+ curve.setEventId(event.getEventId());
|
|
|
+ }
|
|
|
+ if (baseline != null && StringUtils.hasText(baseline.getDataSource())) {
|
|
|
+ curve.setDataSource(baseline.getDataSource());
|
|
|
+ }
|
|
|
+ if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
|
|
|
+ return curve;
|
|
|
+ }
|
|
|
+ for (BaselinePointVO point : baseline.getPoints()) {
|
|
|
+ if (!StringUtils.hasText(point.getTime())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ DrMonitorCurveVO.Point curvePoint = new DrMonitorCurveVO.Point();
|
|
|
+ curvePoint.setTime(point.getTime());
|
|
|
+ curvePoint.setBaselineKw(nz(point.getPredictedBaselineKw()).setScale(SCALE, RoundingMode.HALF_UP));
|
|
|
+ curvePoint.setActualResponseCapacityKw(actualKw);
|
|
|
+ curve.getPoints().add(curvePoint);
|
|
|
+ }
|
|
|
+ return curve;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<DrMonitorSiteResourceVO> listEventSitePeakResources(Long eventId) {
|
|
|
if (eventId == null) {
|
|
|
throw new BusinessException("事件ID不能为空");
|
|
|
}
|
|
|
- requireSiteIds(siteIds);
|
|
|
Integer tenantId = SecurityUtils.getTenantId();
|
|
|
VppDrEvent event = eventMapper.selectById(eventId);
|
|
|
if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
|
|
|
- throw new BusinessException("事件不存在");
|
|
|
+ throw new BusinessException("需求响应事件不存在");
|
|
|
}
|
|
|
if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
|
|
|
- throw new BusinessException("事件不存在");
|
|
|
- }
|
|
|
- if (event.getStartTime() == null) {
|
|
|
- throw new BusinessException("事件缺少开始时间");
|
|
|
+ throw new BusinessException("无权操作其他租户的事件");
|
|
|
}
|
|
|
|
|
|
- List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
|
|
|
- if (peakResources.isEmpty()) {
|
|
|
- throw new BusinessException("所选站点下无可参与调峰的资源点");
|
|
|
- }
|
|
|
- int resourceCount = peakResources.size();
|
|
|
- Map<Long, Long> siteResourceCount = new HashMap<>();
|
|
|
- for (VppResourcePoint resource : peakResources) {
|
|
|
- siteResourceCount.merge(resource.getSiteId(), 1L, Long::sum);
|
|
|
+ List<VppDrInvitation> invitations = invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
|
|
|
+ .eq(VppDrInvitation::getDrEventId, eventId)
|
|
|
+ .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
|
|
|
+ Set<Long> siteIds = invitations.stream()
|
|
|
+ .map(VppDrInvitation::getSiteId)
|
|
|
+ .filter(id -> id != null && id > 0)
|
|
|
+ .collect(Collectors.toCollection(LinkedHashSet::new));
|
|
|
+ if (siteIds.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
}
|
|
|
- Set<Long> peakSiteIds = siteResourceCount.keySet();
|
|
|
|
|
|
- List<VppDrInvitation> invitations = listInvitations(
|
|
|
- Collections.singleton(event.getId()), peakSiteIds, tenantId);
|
|
|
- BigDecimal declaredSum = BigDecimal.ZERO;
|
|
|
- BigDecimal actualSum = BigDecimal.ZERO;
|
|
|
- for (VppDrInvitation invitation : invitations) {
|
|
|
- declaredSum = declaredSum.add(nz(invitation.getDeclaredCapacityKw()));
|
|
|
- actualSum = actualSum.add(nz(invitation.getActualResponseCapacityKw()));
|
|
|
- }
|
|
|
- BigDecimal declaredAvg = avg(declaredSum, resourceCount);
|
|
|
- BigDecimal actualAvg = avg(actualSum, resourceCount);
|
|
|
-
|
|
|
- String responseStartTime = event.getStartTime().format(DATE_TIME_FMT);
|
|
|
- // 按资源点加权:站点曲线 × 该站可调峰资源数,再 / 总资源数
|
|
|
- Map<String, BigDecimal> baselineWeighted = new LinkedHashMap<>();
|
|
|
- Map<String, BigDecimal> loadWeighted = new LinkedHashMap<>();
|
|
|
- Set<String> dataSources = new LinkedHashSet<>();
|
|
|
- List<Long> usedSiteIds = new ArrayList<>();
|
|
|
-
|
|
|
- for (Map.Entry<Long, Long> entry : siteResourceCount.entrySet()) {
|
|
|
- Long siteId = entry.getKey();
|
|
|
- BigDecimal weight = BigDecimal.valueOf(entry.getValue());
|
|
|
- SiteBaselineVO baseline = baselineService.getSiteBaseline(siteId, responseStartTime);
|
|
|
- if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
|
|
|
+ Map<Long, VppSite> siteMap = siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
|
|
|
+ .in(VppSite::getId, siteIds)
|
|
|
+ .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .eq(tenantId != null, VppSite::getTenantId, tenantId))
|
|
|
+ .stream()
|
|
|
+ .collect(Collectors.toMap(VppSite::getId, s -> s, (a, b) -> a));
|
|
|
+
|
|
|
+ List<VppResourcePoint> peakResources = listPeakResources(new ArrayList<>(siteIds), tenantId);
|
|
|
+ Map<Long, List<VppResourcePoint>> resourcesBySite = peakResources.stream()
|
|
|
+ .filter(r -> r.getSiteId() != null)
|
|
|
+ .collect(Collectors.groupingBy(VppResourcePoint::getSiteId));
|
|
|
+
|
|
|
+ List<DrMonitorSiteResourceVO> result = new ArrayList<>();
|
|
|
+ for (Long siteId : siteIds) {
|
|
|
+ VppSite site = siteMap.get(siteId);
|
|
|
+ if (site == null) {
|
|
|
continue;
|
|
|
}
|
|
|
- usedSiteIds.add(siteId);
|
|
|
- if (StringUtils.hasText(baseline.getDataSource())) {
|
|
|
- dataSources.add(baseline.getDataSource());
|
|
|
- }
|
|
|
- for (BaselinePointVO point : baseline.getPoints()) {
|
|
|
- if (!StringUtils.hasText(point.getTime())) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- baselineWeighted.merge(point.getTime(),
|
|
|
- nz(point.getPredictedBaselineKw()).multiply(weight), BigDecimal::add);
|
|
|
- loadWeighted.merge(point.getTime(),
|
|
|
- nz(point.getTodayActualKw()).multiply(weight), BigDecimal::add);
|
|
|
+ DrMonitorSiteResourceVO vo = new DrMonitorSiteResourceVO();
|
|
|
+ vo.setSiteId(site.getId());
|
|
|
+ vo.setSiteCode(site.getSiteCode());
|
|
|
+ vo.setSiteName(site.getSiteName());
|
|
|
+ List<VppResourcePoint> siteResources = resourcesBySite.getOrDefault(siteId, Collections.emptyList())
|
|
|
+ .stream()
|
|
|
+ .sorted(Comparator.comparing(VppResourcePoint::getId, Comparator.nullsLast(Long::compareTo)))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ for (VppResourcePoint resource : siteResources) {
|
|
|
+ vo.getResources().add(toResourceItem(resource));
|
|
|
}
|
|
|
+ result.add(vo);
|
|
|
}
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- BigDecimal divisor = BigDecimal.valueOf(resourceCount);
|
|
|
- DrMonitorCurveVO curve = new DrMonitorCurveVO();
|
|
|
- curve.setId(event.getId());
|
|
|
- curve.setEventId(event.getEventId());
|
|
|
- curve.setResponseDate(event.getStartTime().toLocalDate().format(DATE_FMT));
|
|
|
- curve.setSiteIds(usedSiteIds);
|
|
|
- curve.setDeclaredCapacityKw(declaredAvg);
|
|
|
- curve.setActualResponseCapacityKw(actualAvg);
|
|
|
- if (dataSources.isEmpty()) {
|
|
|
- curve.setDataSource("mock");
|
|
|
- } else if (dataSources.size() == 1) {
|
|
|
- curve.setDataSource(dataSources.iterator().next());
|
|
|
- } else {
|
|
|
- curve.setDataSource("mixed");
|
|
|
- }
|
|
|
-
|
|
|
- List<String> times = new ArrayList<>(baselineWeighted.keySet());
|
|
|
- times.sort(Comparator.naturalOrder());
|
|
|
- for (String time : times) {
|
|
|
- DrMonitorCurveVO.Point point = new DrMonitorCurveVO.Point();
|
|
|
- point.setTime(time);
|
|
|
- point.setBaselineKw(baselineWeighted.getOrDefault(time, BigDecimal.ZERO)
|
|
|
- .divide(divisor, SCALE, RoundingMode.HALF_UP));
|
|
|
- point.setLoadKw(loadWeighted.getOrDefault(time, BigDecimal.ZERO)
|
|
|
- .divide(divisor, SCALE, RoundingMode.HALF_UP));
|
|
|
- point.setDeclaredCapacityKw(declaredAvg);
|
|
|
- point.setActualResponseCapacityKw(actualAvg);
|
|
|
- curve.getPoints().add(point);
|
|
|
+ private static DrMonitorSiteResourceVO.ResourceItem toResourceItem(VppResourcePoint resource) {
|
|
|
+ DrMonitorSiteResourceVO.ResourceItem item = new DrMonitorSiteResourceVO.ResourceItem();
|
|
|
+ item.setResourceId(resource.getId());
|
|
|
+ item.setResourceCode(resource.getResourceCode());
|
|
|
+ item.setResourceName(resource.getResourceName());
|
|
|
+ item.setResourceType(resource.getResourceType());
|
|
|
+ item.setResourceTypeLabel(VppResourceTypeHelper.resourceTypeLabel(resource.getResourceType()));
|
|
|
+ item.setCapacityKw(resource.getCapacityKw());
|
|
|
+ item.setIsControl(resource.getIsControl());
|
|
|
+ item.setIsSupportPeak(resource.getIsSupportPeak());
|
|
|
+ item.setMaxUpKw(resource.getMaxUpKw());
|
|
|
+ item.setMinDownKw(resource.getMinDownKw());
|
|
|
+ item.setIsSupportFm(resource.getIsSupportFm());
|
|
|
+ item.setRemark(resource.getRemark());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, BigDecimal> loadSubsidyAmountByEvent(Set<Long> eventIds, Set<Long> siteIds, Integer tenantId) {
|
|
|
+ if (CollectionUtils.isEmpty(eventIds)) {
|
|
|
+ return Collections.emptyMap();
|
|
|
}
|
|
|
- return curve;
|
|
|
+ LambdaQueryWrapper<VppDrSubsidyPrediction> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.in(VppDrSubsidyPrediction::getDrEventId, eventIds)
|
|
|
+ .eq(tenantId != null, VppDrSubsidyPrediction::getTenantId, tenantId);
|
|
|
+ if (!CollectionUtils.isEmpty(siteIds)) {
|
|
|
+ wrapper.in(VppDrSubsidyPrediction::getSiteId, siteIds);
|
|
|
+ }
|
|
|
+ List<VppDrSubsidyPrediction> list = subsidyPredictionMapper.selectList(wrapper);
|
|
|
+ Map<Long, BigDecimal> result = new HashMap<>();
|
|
|
+ for (VppDrSubsidyPrediction item : list) {
|
|
|
+ if (item.getDrEventId() == null || item.getEstimatedSubsidyAmount() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ result.merge(item.getDrEventId(), item.getEstimatedSubsidyAmount(), BigDecimal::add);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
// ---------- loaders ----------
|
|
|
@@ -326,32 +317,34 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
|
|
|
- return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
- .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
|
|
|
- .in(VppResourcePoint::getSiteId, siteIds)
|
|
|
- .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId)
|
|
|
- .isNotNull(VppResourcePoint::getSiteId));
|
|
|
- }
|
|
|
-
|
|
|
- private List<VppDrParticipation> listParticipations(Set<Long> resourceIds, Integer tenantId) {
|
|
|
- if (CollectionUtils.isEmpty(resourceIds)) {
|
|
|
+ /**
|
|
|
+ * 时间区间内事件开始、结束均落在区间,且状态为执行中/已结束;再关联参与且执行中/已结束的未删除邀约。
|
|
|
+ */
|
|
|
+ private List<VppDrInvitation> listMonitorInvitations(LocalDateTime rangeStart, LocalDateTime rangeEnd,
|
|
|
+ List<Long> siteIds, Integer tenantId) {
|
|
|
+ List<VppDrEvent> events = eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
|
|
|
+ .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .in(VppDrEvent::getEventStatus, EVENT_STATUS_EXECUTING, EVENT_STATUS_ENDED)
|
|
|
+ .ge(VppDrEvent::getStartTime, rangeStart)
|
|
|
+ .le(VppDrEvent::getEndTime, rangeEnd)
|
|
|
+ .isNotNull(VppDrEvent::getStartTime)
|
|
|
+ .isNotNull(VppDrEvent::getEndTime)
|
|
|
+ .eq(tenantId != null, VppDrEvent::getTenantId, tenantId));
|
|
|
+ if (events.isEmpty()) {
|
|
|
return Collections.emptyList();
|
|
|
}
|
|
|
- return participationMapper.selectList(new LambdaQueryWrapper<VppDrParticipation>()
|
|
|
- .in(VppDrParticipation::getResourceId, resourceIds)
|
|
|
- .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_ACCEPT)
|
|
|
- .eq(tenantId != null, VppDrParticipation::getTenantId, tenantId));
|
|
|
+ Set<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toSet());
|
|
|
+ return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
|
|
|
+ .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .in(VppDrInvitation::getDrEventId, eventIds)
|
|
|
+ .in(VppDrInvitation::getSiteId, siteIds)
|
|
|
+ .eq(VppDrInvitation::getReplyStatus, REPLY_ACCEPT)
|
|
|
+ .in(VppDrInvitation::getResponseStatus, EVENT_STATUS_EXECUTING, EVENT_STATUS_ENDED)
|
|
|
+ .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
|
|
|
}
|
|
|
|
|
|
- private Map<Long, VppDrEvent> loadEventMap(List<VppDrParticipation> participations, Integer tenantId) {
|
|
|
- Set<Long> eventIds = participations.stream()
|
|
|
- .map(VppDrParticipation::getEventId)
|
|
|
- .filter(Objects::nonNull)
|
|
|
- .collect(Collectors.toSet());
|
|
|
- if (eventIds.isEmpty()) {
|
|
|
+ private Map<Long, VppDrEvent> loadEventMapByIds(Set<Long> eventIds, Integer tenantId) {
|
|
|
+ if (CollectionUtils.isEmpty(eventIds)) {
|
|
|
return Collections.emptyMap();
|
|
|
}
|
|
|
return eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
|
|
|
@@ -362,77 +355,91 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
.collect(Collectors.toMap(VppDrEvent::getId, e -> e, (a, b) -> a));
|
|
|
}
|
|
|
|
|
|
- private List<VppDrEvent> filterEventsByTime(Iterable<VppDrEvent> events,
|
|
|
- LocalDateTime rangeStart, LocalDateTime rangeEnd) {
|
|
|
- List<VppDrEvent> result = new ArrayList<>();
|
|
|
- for (VppDrEvent event : events) {
|
|
|
- if (event.getStartTime() == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (rangeStart != null && event.getStartTime().isBefore(rangeStart)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (rangeEnd != null && event.getStartTime().isAfter(rangeEnd)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- result.add(event);
|
|
|
+ private Map<Long, VppCustomer> loadCustomerMap(List<VppDrInvitation> invitations) {
|
|
|
+ Set<Long> customerIds = invitations.stream()
|
|
|
+ .map(VppDrInvitation::getCustomerId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ if (customerIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
}
|
|
|
- return result;
|
|
|
+ return customerMapper.selectList(new LambdaQueryWrapper<VppCustomer>()
|
|
|
+ .in(VppCustomer::getId, customerIds)
|
|
|
+ .eq(VppCustomer::getDeleteFlag, VppAuditHelper.NOT_DELETED))
|
|
|
+ .stream()
|
|
|
+ .collect(Collectors.toMap(VppCustomer::getId, c -> c, (a, b) -> a));
|
|
|
}
|
|
|
|
|
|
- private List<VppDrInvitation> listInvitations(Set<Long> eventIds, Set<Long> siteIds, Integer tenantId) {
|
|
|
- if (CollectionUtils.isEmpty(eventIds) || CollectionUtils.isEmpty(siteIds)) {
|
|
|
- return Collections.emptyList();
|
|
|
+ private Map<Long, VppSite> loadSiteMap(List<VppDrInvitation> invitations, Integer tenantId) {
|
|
|
+ Set<Long> siteIds = invitations.stream()
|
|
|
+ .map(VppDrInvitation::getSiteId)
|
|
|
+ .filter(id -> id != null && id > 0)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ if (siteIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
}
|
|
|
- return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
|
|
|
- .in(VppDrInvitation::getDrEventId, eventIds)
|
|
|
- .in(VppDrInvitation::getSiteId, siteIds)
|
|
|
- .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
|
|
|
+ return siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
|
|
|
+ .in(VppSite::getId, siteIds)
|
|
|
+ .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .eq(tenantId != null, VppSite::getTenantId, tenantId))
|
|
|
+ .stream()
|
|
|
+ .collect(Collectors.toMap(VppSite::getId, s -> s, (a, b) -> a));
|
|
|
}
|
|
|
|
|
|
- private DrMonitorRecordVO toRecordVo(VppDrEvent event, List<VppDrInvitation> invitations) {
|
|
|
+ private DrMonitorRecordVO toRecordVo(VppDrInvitation invitation, VppDrEvent event,
|
|
|
+ VppCustomer customer, VppSite site,
|
|
|
+ BigDecimal estimatedSubsidyAmount) {
|
|
|
DrMonitorRecordVO vo = new DrMonitorRecordVO();
|
|
|
- BeanUtils.copyProperties(event, vo);
|
|
|
- if (CollectionUtils.isEmpty(invitations)) {
|
|
|
- vo.setResponseCompletionRate(null);
|
|
|
- vo.setQualified(false);
|
|
|
- return vo;
|
|
|
+ if (event != null) {
|
|
|
+ BeanUtils.copyProperties(event, vo);
|
|
|
}
|
|
|
- BigDecimal sum = BigDecimal.ZERO;
|
|
|
- int rateCount = 0;
|
|
|
- boolean allQualified = true;
|
|
|
- for (VppDrInvitation invitation : invitations) {
|
|
|
- BigDecimal rate = invitation.getResponseCompletionRate();
|
|
|
- if (rate != null) {
|
|
|
- sum = sum.add(rate);
|
|
|
- rateCount++;
|
|
|
- }
|
|
|
- if (!isCompletionQualified(rate)) {
|
|
|
- allQualified = false;
|
|
|
- }
|
|
|
+ vo.setInvitationId(invitation.getId());
|
|
|
+ vo.setCustomerId(invitation.getCustomerId());
|
|
|
+ if (customer != null) {
|
|
|
+ vo.setCustomerName(customer.getCustomerName());
|
|
|
}
|
|
|
- if (rateCount > 0) {
|
|
|
- vo.setResponseCompletionRate(sum.divide(BigDecimal.valueOf(rateCount), SCALE, RoundingMode.HALF_UP));
|
|
|
+ vo.setSiteId(invitation.getSiteId());
|
|
|
+ if (site != null) {
|
|
|
+ vo.setSiteName(site.getSiteName());
|
|
|
}
|
|
|
- vo.setQualified(allQualified && rateCount == invitations.size());
|
|
|
+ vo.setTargetCapacityKw(invitation.getDeclaredCapacityKw());
|
|
|
+ vo.setClearedCapacityKw(invitation.getActualResponseCapacityKw());
|
|
|
+ vo.setResponseCompletionRate(invitation.getResponseCompletionRate());
|
|
|
+ vo.setQualified(isCompletionQualified(invitation.getResponseCompletionRate()));
|
|
|
+ vo.setEstimatedSubsidyAmount(estimatedSubsidyAmount);
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
+ private String resolveInvitationResponseStartTime(VppDrInvitation invitation, VppDrEvent event) {
|
|
|
+ if (invitation.getExecuteStartDate() != null) {
|
|
|
+ LocalTime timeOfDay = (event != null && event.getStartTime() != null)
|
|
|
+ ? event.getStartTime().toLocalTime() : LocalTime.MIN;
|
|
|
+ return LocalDateTime.of(invitation.getExecuteStartDate(), timeOfDay).format(DATE_TIME_FMT);
|
|
|
+ }
|
|
|
+ if (event != null && event.getStartTime() != null) {
|
|
|
+ return event.getStartTime().format(DATE_TIME_FMT);
|
|
|
+ }
|
|
|
+ throw new BusinessException("邀约缺少响应时间");
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
|
|
|
+ return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
+ .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
|
|
|
+ .in(VppResourcePoint::getSiteId, siteIds)
|
|
|
+ .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId)
|
|
|
+ .isNotNull(VppResourcePoint::getSiteId));
|
|
|
+ }
|
|
|
+
|
|
|
static boolean isCompletionQualified(BigDecimal rate) {
|
|
|
if (rate == null) {
|
|
|
return false;
|
|
|
}
|
|
|
BigDecimal scaled = rate.setScale(2, RoundingMode.HALF_UP);
|
|
|
- return scaled.compareTo(ONE.setScale(2, RoundingMode.HALF_UP)) == 0
|
|
|
- || scaled.compareTo(HUNDRED.setScale(2, RoundingMode.HALF_UP)) == 0;
|
|
|
- }
|
|
|
-
|
|
|
- private static BigDecimal avg(BigDecimal sum, int divisor) {
|
|
|
- if (divisor <= 0) {
|
|
|
- return BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+ if (scaled.compareTo(ONE) > 0) {
|
|
|
+ return scaled.compareTo(HUNDRED) >= 0;
|
|
|
}
|
|
|
- return nz(sum).divide(BigDecimal.valueOf(divisor), SCALE, RoundingMode.HALF_UP);
|
|
|
+ return scaled.compareTo(ONE) >= 0;
|
|
|
}
|
|
|
|
|
|
private static LocalDateTime parseStart(String date) {
|