|
|
@@ -5,26 +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.service.vo.SiteDeclaredCapacityVO;
|
|
|
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;
|
|
|
@@ -42,7 +46,7 @@ import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.HashSet;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
@@ -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,146 +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));
|
|
|
- Map<Long, BigDecimal> subsidyByEvent = loadSubsidyAmountByEvent(eventIds, peakSiteIds, tenantId);
|
|
|
-
|
|
|
- List<DrMonitorRecordVO> records = events.stream()
|
|
|
- .map(e -> toRecordVo(e, byEvent.getOrDefault(e.getId(), Collections.emptyList()),
|
|
|
- subsidyByEvent.get(e.getId())))
|
|
|
+ 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());
|
|
|
@@ -222,96 +155,137 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public DrMonitorCurveVO getCurve(Long siteId, Integer baselineDays) {
|
|
|
- if (siteId == null) {
|
|
|
- throw new BusinessException("站点ID不能为空");
|
|
|
- }
|
|
|
- if (baselineDays != null && baselineDays != 3 && baselineDays != 5) {
|
|
|
- throw new BusinessException("基线负荷仅支持3天或5天维度查询!");
|
|
|
+ public DrMonitorCurveVO getCurve(Long invitationId) {
|
|
|
+ if (invitationId == null) {
|
|
|
+ throw new BusinessException("邀约ID不能为空");
|
|
|
}
|
|
|
Integer tenantId = SecurityUtils.getTenantId();
|
|
|
- List<VppResourcePoint> peakResources = listPeakResources(siteId, tenantId);
|
|
|
- if (peakResources.isEmpty()) {
|
|
|
- throw new BusinessException("所选站点下无可参与调峰的资源点!");
|
|
|
+ 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("邀约未关联站点");
|
|
|
}
|
|
|
- int resourceCount = peakResources.size();
|
|
|
-
|
|
|
- LocalDate today = LocalDate.now();
|
|
|
- LocalDateTime windowStart = today.atStartOfDay();
|
|
|
- LocalDateTime windowEnd = today.atTime(LocalTime.MAX);
|
|
|
- String responseStartTime = windowStart.format(DATE_TIME_FMT);
|
|
|
- String responseEndTime = windowEnd.format(DATE_TIME_FMT);
|
|
|
- String actualEndTime = windowEnd.format(DATE_TIME_FMT);
|
|
|
- int requiredDays = (baselineDays != null && baselineDays == 3) ? 3 : 5;
|
|
|
-
|
|
|
- BigDecimal actualAvg = avg(resolveSiteActualResponseKw(
|
|
|
- siteId, responseStartTime, responseEndTime, BigDecimal.ZERO), resourceCount);
|
|
|
|
|
|
- SiteBaselineVO baseline = baselineService.getSiteBaseline(
|
|
|
- siteId, responseStartTime, requiredDays, actualEndTime);
|
|
|
+ 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.setResponseDate(today.format(DATE_FMT));
|
|
|
- curve.setSiteId(siteId);
|
|
|
- curve.setActualResponseCapacityKw(actualAvg);
|
|
|
+ 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 (dataSources.isEmpty()) {
|
|
|
- // curve.setDataSource("mock");
|
|
|
- // }
|
|
|
-
|
|
|
if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
|
|
|
return curve;
|
|
|
}
|
|
|
for (BaselinePointVO point : baseline.getPoints()) {
|
|
|
- if (!StringUtils.hasText(point.getTime()) || !inCurveWindow(point.getTime(), windowStart, windowEnd)) {
|
|
|
+ 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(actualAvg);
|
|
|
+ curvePoint.setActualResponseCapacityKw(actualKw);
|
|
|
curve.getPoints().add(curvePoint);
|
|
|
}
|
|
|
return curve;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 实际响应容量复用申报容量链路 {@link VppBaselineService#getSiteDeclaredCapacity},
|
|
|
- * 取预估响应容量;无数据时回退邀约实际响应容量。
|
|
|
- */
|
|
|
- private BigDecimal resolveSiteActualResponseKw(Long siteId, String responseStartTime,
|
|
|
- String responseEndTime, BigDecimal fallbackKw) {
|
|
|
- if (siteId == null || !StringUtils.hasText(responseStartTime) || !StringUtils.hasText(responseEndTime)) {
|
|
|
- return nz(fallbackKw);
|
|
|
+ @Override
|
|
|
+ public List<DrMonitorSiteResourceVO> listEventSitePeakResources(Long eventId) {
|
|
|
+ if (eventId == null) {
|
|
|
+ throw new BusinessException("事件ID不能为空");
|
|
|
}
|
|
|
- try {
|
|
|
- SiteDeclaredCapacityVO capacity = baselineService.getSiteDeclaredCapacity(
|
|
|
- siteId, responseStartTime, responseEndTime);
|
|
|
- if (capacity != null && capacity.getEstimatedResponseCapacityKw() != null
|
|
|
- && capacity.getEstimatedResponseCapacityKw().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
- return capacity.getEstimatedResponseCapacityKw();
|
|
|
- }
|
|
|
- } catch (Exception ignored) {
|
|
|
- // 回退邀约实际响应容量,保持曲线可返回
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+ VppDrEvent event = eventMapper.selectById(eventId);
|
|
|
+ if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
|
|
|
+ throw new BusinessException("需求响应事件不存在");
|
|
|
+ }
|
|
|
+ if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
|
|
|
+ throw new BusinessException("无权操作其他租户的事件");
|
|
|
}
|
|
|
- return nz(fallbackKw);
|
|
|
- }
|
|
|
|
|
|
- private boolean inCurveWindow(String timeKey, LocalDateTime windowStart, LocalDateTime windowEnd) {
|
|
|
- try {
|
|
|
- LocalTime t = LocalTime.parse(timeKey.length() == 5 ? timeKey : timeKey.substring(0, 5));
|
|
|
- LocalDate date = windowStart.toLocalDate();
|
|
|
- LocalDateTime point = LocalDateTime.of(date, t);
|
|
|
- // 跨日窗口兜底:若点早于 windowStart 且 windowEnd 跨日,放到次日
|
|
|
- if (point.isBefore(windowStart) && windowEnd.toLocalDate().isAfter(date)) {
|
|
|
- point = point.plusDays(1);
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
- return !point.isBefore(windowStart) && !point.isAfter(windowEnd);
|
|
|
- } catch (Exception ex) {
|
|
|
- return true;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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) {
|
|
|
@@ -343,40 +317,34 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private List<VppResourcePoint> listPeakResources(Long siteId, Integer tenantId) {
|
|
|
- return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
- .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
|
|
|
- .eq(VppResourcePoint::getSiteId, siteId)
|
|
|
- .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId));
|
|
|
- }
|
|
|
-
|
|
|
- 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>()
|
|
|
@@ -387,63 +355,80 @@ 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 (event != null) {
|
|
|
+ BeanUtils.copyProperties(event, vo);
|
|
|
+ }
|
|
|
+ vo.setInvitationId(invitation.getId());
|
|
|
+ vo.setCustomerId(invitation.getCustomerId());
|
|
|
+ if (customer != null) {
|
|
|
+ vo.setCustomerName(customer.getCustomerName());
|
|
|
+ }
|
|
|
+ vo.setSiteId(invitation.getSiteId());
|
|
|
+ if (site != null) {
|
|
|
+ vo.setSiteName(site.getSiteName());
|
|
|
+ }
|
|
|
+ vo.setTargetCapacityKw(invitation.getDeclaredCapacityKw());
|
|
|
+ vo.setClearedCapacityKw(invitation.getActualResponseCapacityKw());
|
|
|
+ vo.setResponseCompletionRate(invitation.getResponseCompletionRate());
|
|
|
+ vo.setQualified(isCompletionQualified(invitation.getResponseCompletionRate()));
|
|
|
vo.setEstimatedSubsidyAmount(estimatedSubsidyAmount);
|
|
|
- if (CollectionUtils.isEmpty(invitations)) {
|
|
|
- vo.setResponseCompletionRate(null);
|
|
|
- vo.setQualified(false);
|
|
|
- return 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;
|
|
|
- }
|
|
|
+ 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 (rateCount > 0) {
|
|
|
- vo.setResponseCompletionRate(sum.divide(BigDecimal.valueOf(rateCount), SCALE, RoundingMode.HALF_UP));
|
|
|
+ if (event != null && event.getStartTime() != null) {
|
|
|
+ return event.getStartTime().format(DATE_TIME_FMT);
|
|
|
}
|
|
|
- vo.setQualified(allQualified && rateCount == invitations.size());
|
|
|
- return vo;
|
|
|
+ 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) {
|
|
|
@@ -451,15 +436,10 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
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) {
|