Forráskód Böngészése

service-vpp模块代码更新

hanzhengyi 5 napja
szülő
commit
1d1b072ae4
17 módosított fájl, 9131 hozzáadás és 17 törlés
  1. 35 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/constant/VppTsdbConstants.java
  2. 56 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/CapabilityEvalController.java
  3. 1 1
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppAliyunSmsService.java
  4. 34 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppCapabilityEvalService.java
  5. 31 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppTsdbQueryService.java
  6. 353 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppCapabilityEvalServiceImpl.java
  7. 170 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppTsdbQueryServiceImpl.java
  8. 21 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityCategoryStatVO.java
  9. 27 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityEvalSummaryVO.java
  10. 31 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityHistoryVO.java
  11. 20 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityLoadCurveVO.java
  12. 19 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityLoadPointVO.java
  13. 364 0
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppCapabilityEvalHelper.java
  14. 16 15
      service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppSmsTemplateHelper.java
  15. 1 1
      service-vpp/service-vpp-biz/src/main/resources/bootstrap.yml
  16. 5636 0
      service-vpp/虚拟电厂平台接口文档V1.0.md
  17. 2316 0
      service-vpp/虚拟电厂平台数据库设计文档V1.1.md

+ 35 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/constant/VppTsdbConstants.java

@@ -0,0 +1,35 @@
+package com.usky.vpp.constant;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * VPP TDengine 指标常量(与 device_metrics 超级表及 EMS 存量表字段兼容)
+ */
+public final class VppTsdbConstants {
+
+    private VppTsdbConstants() {
+    }
+
+    /** 有功功率 / 实际负荷(优先匹配顺序) */
+    public static final List<String> ACTIVE_POWER_METRICS = Arrays.asList(
+            "active_power",
+            "totalActivePower",
+            "totalactivepower",
+            "activepowera"
+    );
+
+    /** 实时上调能力 */
+    public static final List<String> UP_CAPACITY_METRICS = Arrays.asList(
+            "up_capacity_kw",
+            "upCapacityKw"
+    );
+
+    /** 实时下调能力 */
+    public static final List<String> DOWN_CAPACITY_METRICS = Arrays.asList(
+            "down_capacity_kw",
+            "downCapacityKw"
+    );
+
+    public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+}

+ 56 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/CapabilityEvalController.java

@@ -0,0 +1,56 @@
+package com.usky.vpp.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.VppCapabilityEvalService;
+import com.usky.vpp.service.vo.CapabilityEvalSummaryVO;
+import com.usky.vpp.service.vo.CapabilityHistoryVO;
+import com.usky.vpp.service.vo.CapabilityLoadCurveVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * 可调能力评估接口
+ * <p>网关前缀: /prod-api/service-vpp/capability-eval</p>
+ */
+@RestController
+@RequestMapping("/capability-eval")
+public class CapabilityEvalController {
+
+    @Autowired
+    private VppCapabilityEvalService vppCapabilityEvalService;
+
+    /**
+     * 顶部汇总统计
+     * <p>支持按 siteId 筛选;返回总资源/负荷类/储能类/发电类户数与容量,及最大上下调能力。</p>
+     */
+    @GetMapping("/summary")
+    public ApiResult<CapabilityEvalSummaryVO> summary(@RequestParam(value = "siteId", required = false) Long siteId) {
+        return ApiResult.success(vppCapabilityEvalService.getSummary(siteId));
+    }
+
+    /**
+     * 可调能力曲线(基线负荷 vs 实际负荷)
+     * <p>默认统计当天,每 10 分钟一个点(00:00 ~ 23:50)。</p>
+     */
+    @GetMapping("/load-curve")
+    public ApiResult<CapabilityLoadCurveVO> loadCurve(@RequestParam(value = "siteId", required = false) Long siteId,
+                                                      @RequestParam(value = "date", required = false) String date) {
+        return ApiResult.success(vppCapabilityEvalService.getLoadCurve(siteId, date));
+    }
+
+    /**
+     * 历史响应记录(分页)
+     * <p>按站点下资源参与的需求响应事件倒序展示。</p>
+     */
+    @GetMapping("/history")
+    public ApiResult<CommonPage<CapabilityHistoryVO>> history(@RequestParam(value = "siteId", required = false) Long siteId,
+                                                            @RequestParam(required = false) Map<String, Object> params) {
+        return ApiResult.success(vppCapabilityEvalService.pageHistory(siteId, params));
+    }
+}

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppAliyunSmsService.java

@@ -10,7 +10,7 @@ public interface VppAliyunSmsService {
      *
      * @param phone        手机号
      * @param templateCode 模板 CODE
-     * @param templateParam JSON 模板变量,如 {"name":"张三","event":"EVE_001"}
+     * @param templateParam JSON 模板变量,如 {"meet":"夏季削峰","time":"2026-07-08 14:00:00","room":"某某公司"}
      */
     void sendTemplateSms(String phone, String templateCode, String templateParam);
 }

+ 34 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppCapabilityEvalService.java

@@ -0,0 +1,34 @@
+package com.usky.vpp.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.vo.CapabilityEvalSummaryVO;
+import com.usky.vpp.service.vo.CapabilityHistoryVO;
+import com.usky.vpp.service.vo.CapabilityLoadCurveVO;
+
+import java.util.Map;
+
+/**
+ * 可调能力评估业务接口
+ */
+public interface VppCapabilityEvalService {
+
+    /**
+     * 顶部汇总统计(总资源/负荷类/储能类/发电类 + 最大上下调能力)
+     *
+     * @param siteId 站点 ID,为空时统计全部站点
+     */
+    CapabilityEvalSummaryVO getSummary(Long siteId);
+
+    /**
+     * 可调能力曲线(基线负荷 vs 实际负荷)
+     *
+     * @param siteId 站点 ID,为空时统计全部站点
+     * @param date   统计日期 yyyy-MM-dd,默认当天
+     */
+    CapabilityLoadCurveVO getLoadCurve(Long siteId, String date);
+
+    /**
+     * 历史响应记录(分页)
+     */
+    CommonPage<CapabilityHistoryVO> pageHistory(Long siteId, Map<String, Object> params);
+}

+ 31 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppTsdbQueryService.java

@@ -0,0 +1,31 @@
+package com.usky.vpp.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * VPP 时序数据(TDengine)查询服务
+ */
+public interface VppTsdbQueryService {
+
+    /**
+     * 批量查询设备指标历史曲线
+     *
+     * @return deviceUuid -> metric -> time -> value
+     */
+    Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> queryDeviceMetricHistory(
+            List<String> deviceUuids,
+            LocalDateTime startTime,
+            LocalDateTime endTime,
+            List<String> metrics);
+
+    /**
+     * 批量查询设备最新指标值
+     *
+     * @return deviceUuid -> metric -> value
+     */
+    Map<String, Map<String, BigDecimal>> queryLatestMetrics(List<String> deviceUuids, List<String> metrics);
+}

+ 353 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppCapabilityEvalServiceImpl.java

@@ -0,0 +1,353 @@
+package com.usky.vpp.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.vpp.domain.VppDevice;
+import com.usky.vpp.domain.VppDrEvaluation;
+import com.usky.vpp.domain.VppDrEvent;
+import com.usky.vpp.domain.VppDrParticipation;
+import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.domain.VppSite;
+import com.usky.vpp.mapper.VppDeviceMapper;
+import com.usky.vpp.mapper.VppDrEvaluationMapper;
+import com.usky.vpp.mapper.VppDrEventMapper;
+import com.usky.vpp.mapper.VppDrParticipationMapper;
+import com.usky.vpp.mapper.VppResourcePointMapper;
+import com.usky.vpp.mapper.VppSiteMapper;
+import com.usky.vpp.constant.VppTsdbConstants;
+import com.usky.vpp.service.VppCapabilityEvalService;
+import com.usky.vpp.service.VppTsdbQueryService;
+import com.usky.vpp.service.vo.CapabilityCategoryStatVO;
+import com.usky.vpp.service.vo.CapabilityEvalSummaryVO;
+import com.usky.vpp.service.vo.CapabilityHistoryVO;
+import com.usky.vpp.service.vo.CapabilityLoadCurveVO;
+import com.usky.vpp.service.vo.CapabilityLoadPointVO;
+import com.usky.vpp.util.VppAuditHelper;
+import com.usky.vpp.util.VppCapabilityEvalHelper;
+import com.usky.vpp.util.VppPageHelper;
+import com.usky.vpp.util.VppSiteResourceHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+@Service
+public class VppCapabilityEvalServiceImpl implements VppCapabilityEvalService {
+
+    private static final int PARTICIPATE_STATUS_ACCEPT = 1;
+    private static final int BASELINE_DAYS_AGO = 7;
+    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    @Autowired
+    private VppResourcePointMapper resourcePointMapper;
+    @Autowired
+    private VppSiteMapper siteMapper;
+    @Autowired
+    private VppDeviceMapper deviceMapper;
+    @Autowired
+    private VppDrEventMapper eventMapper;
+    @Autowired
+    private VppDrParticipationMapper participationMapper;
+    @Autowired
+    private VppDrEvaluationMapper evaluationMapper;
+    @Autowired
+    private VppSiteResourceHelper siteResourceHelper;
+    @Autowired
+    private VppTsdbQueryService vppTsdbQueryService;
+
+    @Override
+    public CapabilityEvalSummaryVO getSummary(Long siteId) {
+        VppSite site = resolveSite(siteId);
+        List<VppResourcePoint> resources = listResources(siteId);
+        Map<Long, VppSite> siteMap = siteResourceHelper.loadSiteMap(resources);
+        List<VppDevice> devices = loadDevices(resources);
+
+        CapabilityEvalSummaryVO summary = new CapabilityEvalSummaryVO();
+        summary.setSiteId(siteId);
+        summary.setSiteName(site != null ? site.getSiteName() : "全部站点");
+        summary.setTotal(buildCategoryStat(VppCapabilityEvalHelper.CATEGORY_TOTAL, resources, siteMap));
+        summary.setLoad(buildCategoryStat(VppCapabilityEvalHelper.CATEGORY_LOAD, resources, siteMap));
+        summary.setStorage(buildCategoryStat(VppCapabilityEvalHelper.CATEGORY_STORAGE, resources, siteMap));
+        summary.setGeneration(buildCategoryStat(VppCapabilityEvalHelper.CATEGORY_GENERATION, resources, siteMap));
+
+        BigDecimal maxUp = BigDecimal.ZERO;
+        BigDecimal maxDown = BigDecimal.ZERO;
+        for (VppResourcePoint resource : resources) {
+            if (resource.getIsControl() == null || resource.getIsControl() != 1) {
+                continue;
+            }
+            maxUp = maxUp.add(resource.getMaxUpKw() != null ? resource.getMaxUpKw() : BigDecimal.ZERO);
+            maxDown = maxDown.add(resource.getMinDownKw() != null ? resource.getMinDownKw() : BigDecimal.ZERO);
+        }
+        applyRealtimeAdjustCapacity(resources, devices, summary, maxUp, maxDown);
+        return summary;
+    }
+
+    private void applyRealtimeAdjustCapacity(List<VppResourcePoint> resources,
+                                             List<VppDevice> devices,
+                                             CapabilityEvalSummaryVO summary,
+                                             BigDecimal fallbackUp,
+                                             BigDecimal fallbackDown) {
+        List<String> deviceUuids = resolveDeviceUuids(resources, devices);
+        if (deviceUuids.isEmpty()) {
+            summary.setMaxUpKw(fallbackUp);
+            summary.setMaxDownKw(fallbackDown);
+            return;
+        }
+
+        List<String> metrics = new ArrayList<>();
+        metrics.addAll(VppTsdbConstants.UP_CAPACITY_METRICS);
+        metrics.addAll(VppTsdbConstants.DOWN_CAPACITY_METRICS);
+        Map<String, Map<String, BigDecimal>> latest = vppTsdbQueryService.queryLatestMetrics(deviceUuids, metrics);
+
+        BigDecimal realtimeUp = VppCapabilityEvalHelper.sumLatestMetric(latest, VppTsdbConstants.UP_CAPACITY_METRICS);
+        BigDecimal realtimeDown = VppCapabilityEvalHelper.sumLatestMetric(latest, VppTsdbConstants.DOWN_CAPACITY_METRICS);
+        summary.setMaxUpKw(realtimeUp.compareTo(BigDecimal.ZERO) > 0 ? realtimeUp : fallbackUp);
+        summary.setMaxDownKw(realtimeDown.compareTo(BigDecimal.ZERO) > 0 ? realtimeDown : fallbackDown);
+    }
+
+    @Override
+    public CapabilityLoadCurveVO getLoadCurve(Long siteId, String date) {
+        VppSite site = resolveSite(siteId);
+        LocalDate statDate = parseDate(date);
+        List<VppResourcePoint> resources = listResources(siteId);
+        List<VppDevice> devices = loadDevices(resources);
+
+        CapabilityLoadCurveVO curve = new CapabilityLoadCurveVO();
+        curve.setSiteId(siteId);
+        curve.setSiteName(site != null ? site.getSiteName() : "全部站点");
+        curve.setDate(statDate.format(DATE_FMT));
+        LoadCurveBuildResult buildResult = buildLoadCurvePoints(resources, devices, siteId, statDate);
+        curve.setPoints(buildResult.points);
+        curve.setDataSource(buildResult.dataSource);
+        return curve;
+    }
+
+    private static class LoadCurveBuildResult {
+        private final List<CapabilityLoadPointVO> points;
+        private final String dataSource;
+
+        private LoadCurveBuildResult(List<CapabilityLoadPointVO> points, String dataSource) {
+            this.points = points;
+            this.dataSource = dataSource;
+        }
+    }
+
+    private LoadCurveBuildResult buildLoadCurvePoints(List<VppResourcePoint> resources,
+                                                      List<VppDevice> devices,
+                                                      Long siteId,
+                                                      LocalDate statDate) {
+        List<String> deviceUuids = resolveDeviceUuids(resources, devices);
+        if (!deviceUuids.isEmpty()) {
+            LocalDateTime dayStart = statDate.atStartOfDay();
+            LocalDateTime dayEnd = statDate.atTime(23, 59, 59);
+            LocalDate baselineDate = statDate.minusDays(BASELINE_DAYS_AGO);
+            LocalDateTime baselineStart = baselineDate.atStartOfDay();
+            LocalDateTime baselineEnd = baselineDate.atTime(23, 59, 59);
+
+            List<String> powerMetrics = new ArrayList<>(VppTsdbConstants.ACTIVE_POWER_METRICS);
+            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> actualData =
+                    vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, dayStart, dayEnd, powerMetrics);
+            if (VppCapabilityEvalHelper.hasTsdbPowerData(actualData, powerMetrics)) {
+                Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> baselineData =
+                        vppTsdbQueryService.queryDeviceMetricHistory(deviceUuids, baselineStart, baselineEnd, powerMetrics);
+                return new LoadCurveBuildResult(
+                        VppCapabilityEvalHelper.buildLoadCurveFromTsdb(actualData, baselineData, statDate, powerMetrics),
+                        "tsdb");
+            }
+        }
+        return new LoadCurveBuildResult(
+                VppCapabilityEvalHelper.buildLoadCurve(resources, devices, siteId, statDate),
+                "mock");
+    }
+
+    @Override
+    public CommonPage<CapabilityHistoryVO> pageHistory(Long siteId, Map<String, Object> params) {
+        resolveSite(siteId);
+        Set<Long> resourceIds = listResources(siteId).stream()
+                .map(VppResourcePoint::getId)
+                .collect(Collectors.toSet());
+        if (resourceIds.isEmpty()) {
+            return emptyPage(params);
+        }
+
+        List<VppDrParticipation> participations = participationMapper.selectList(
+                new LambdaQueryWrapper<VppDrParticipation>()
+                        .in(VppDrParticipation::getResourceId, resourceIds)
+                        .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_STATUS_ACCEPT)
+                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        Set<Long> eventIds = participations.stream()
+                .map(VppDrParticipation::getEventId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (eventIds.isEmpty()) {
+            return emptyPage(params);
+        }
+
+        List<VppDrEvent> events = eventMapper.selectBatchIds(eventIds).stream()
+                .filter(e -> !VppAuditHelper.isDeleted(e.getDeleteFlag()))
+                .sorted(Comparator.comparing(VppDrEvent::getStartTime, Comparator.nullsLast(Comparator.reverseOrder())))
+                .collect(Collectors.toList());
+
+        Map<Long, VppDrEvaluation> evaluationMap = evaluationMapper.selectList(
+                new LambdaQueryWrapper<VppDrEvaluation>()
+                        .in(VppDrEvaluation::getEventId, eventIds)
+                        .eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED))
+                .stream()
+                .collect(Collectors.toMap(VppDrEvaluation::getEventId, e -> e, (a, b) -> a));
+
+        List<CapabilityHistoryVO> records = events.stream()
+                .map(event -> toHistoryVo(event, evaluationMap.get(event.getId())))
+                .collect(Collectors.toList());
+
+        long current = VppPageHelper.of(params).getCurrent();
+        long size = VppPageHelper.of(params).getSize();
+        int from = (int) Math.min((current - 1) * size, records.size());
+        int to = (int) Math.min(from + size, records.size());
+        List<CapabilityHistoryVO> pageRecords = from >= records.size()
+                ? Collections.emptyList() : records.subList(from, to);
+        return new CommonPage<>(pageRecords, (long) records.size(), current, size);
+    }
+
+    private CapabilityCategoryStatVO buildCategoryStat(String category,
+                                                       List<VppResourcePoint> resources,
+                                                       Map<Long, VppSite> siteMap) {
+        CapabilityCategoryStatVO stat = new CapabilityCategoryStatVO();
+        stat.setCategory(category);
+        stat.setCategoryLabel(VppCapabilityEvalHelper.categoryLabel(category));
+        stat.setCustomerCount(0);
+        stat.setCapacityKw(BigDecimal.ZERO);
+
+        Set<Long> customerIds = new HashSet<>();
+        for (VppResourcePoint resource : resources) {
+            if (!matchesCategory(category, resource.getResourceType())) {
+                continue;
+            }
+            BigDecimal cap = resource.getCapacityKw() != null ? resource.getCapacityKw() : BigDecimal.ZERO;
+            stat.setCapacityKw(stat.getCapacityKw().add(cap));
+            Long customerId = siteResourceHelper.resolveCustomerId(resource, siteMap);
+            if (customerId != null) {
+                customerIds.add(customerId);
+            }
+        }
+        stat.setCustomerCount(customerIds.size());
+        return stat;
+    }
+
+    private boolean matchesCategory(String category, String resourceType) {
+        if (VppCapabilityEvalHelper.CATEGORY_TOTAL.equals(category)) {
+            return true;
+        }
+        return category.equals(VppCapabilityEvalHelper.resolveCategory(resourceType));
+    }
+
+    private CapabilityHistoryVO toHistoryVo(VppDrEvent event, VppDrEvaluation evaluation) {
+        CapabilityHistoryVO vo = new CapabilityHistoryVO();
+        vo.setEventId(event.getId());
+        vo.setEventCode(event.getEventId());
+        vo.setEventName(event.getEventName());
+        vo.setEventType(event.getEventType());
+        vo.setEventTypeLabel(VppCapabilityEvalHelper.eventTypeLabel(event.getEventType()));
+        vo.setResponseType(event.getResponseType());
+        vo.setResponseTypeLabel(VppCapabilityEvalHelper.responseTypeLabel(event.getResponseType()));
+        vo.setStartTime(event.getStartTime());
+        vo.setEndTime(event.getEndTime());
+        vo.setTargetCapacityKw(event.getClearedCapacityKw() != null
+                ? event.getClearedCapacityKw() : event.getTargetCapacityKw());
+        if (evaluation != null) {
+            vo.setActualCapacityKw(evaluation.getActualCapacityKw());
+            vo.setQualifiedRate(evaluation.getQualifiedRate());
+        }
+        return vo;
+    }
+
+    private List<VppResourcePoint> listResources(Long siteId) {
+        LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<VppResourcePoint>()
+                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED);
+        if (siteId != null) {
+            wrapper.eq(VppResourcePoint::getSiteId, siteId);
+        }
+        return resourcePointMapper.selectList(wrapper);
+    }
+
+    private List<VppDevice> loadDevices(List<VppResourcePoint> resources) {
+        Set<Long> deviceIds = resources.stream()
+                .map(VppResourcePoint::getDeviceId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (deviceIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return deviceMapper.selectBatchIds(deviceIds).stream()
+                .filter(d -> !VppAuditHelper.isDeleted(d.getDeleteFlag()))
+                .collect(Collectors.toList());
+    }
+
+    private List<String> resolveDeviceUuids(List<VppResourcePoint> resources, List<VppDevice> devices) {
+        if (resources == null || resources.isEmpty() || devices == null || devices.isEmpty()) {
+            return Collections.emptyList();
+        }
+        Map<Long, VppDevice> deviceMap = devices.stream()
+                .collect(Collectors.toMap(VppDevice::getId, d -> d, (a, b) -> a));
+        Set<String> uuids = new LinkedHashSet<>();
+        for (VppResourcePoint resource : resources) {
+            if (resource.getDeviceId() == null) {
+                continue;
+            }
+            VppDevice device = deviceMap.get(resource.getDeviceId());
+            if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
+                continue;
+            }
+            if (device.getCommStatus() != null && device.getCommStatus() == 0) {
+                continue;
+            }
+            uuids.add(device.getDeviceUuid().trim());
+        }
+        return new ArrayList<>(uuids);
+    }
+
+    private VppSite resolveSite(Long siteId) {
+        if (siteId == null) {
+            return null;
+        }
+        VppSite site = siteMapper.selectById(siteId);
+        if (site == null || VppAuditHelper.isDeleted(site.getDeleteFlag())) {
+            throw new BusinessException("站点不存在");
+        }
+        return site;
+    }
+
+    private LocalDate parseDate(String date) {
+        if (!StringUtils.hasText(date)) {
+            return LocalDate.now();
+        }
+        try {
+            return LocalDate.parse(date.trim(), DATE_FMT);
+        } catch (DateTimeParseException ex) {
+            throw new BusinessException("日期格式无效,请使用 yyyy-MM-dd");
+        }
+    }
+
+    private CommonPage<CapabilityHistoryVO> emptyPage(Map<String, Object> params) {
+        long current = VppPageHelper.of(params).getCurrent();
+        long size = VppPageHelper.of(params).getSize();
+        return new CommonPage<>(new ArrayList<>(), 0L, current, size);
+    }
+}

+ 170 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppTsdbQueryServiceImpl.java

@@ -0,0 +1,170 @@
+package com.usky.vpp.service.impl;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.demo.RemoteTsdbProxyService;
+import com.usky.demo.domain.HistorysInnerRequestVO;
+import com.usky.demo.domain.HistorysInnerResultVO;
+import com.usky.demo.domain.LastInnerQueryVO;
+import com.usky.demo.domain.LastInnerResultVO;
+import com.usky.demo.domain.MetricVO;
+import com.usky.vpp.constant.VppTsdbConstants;
+import com.usky.vpp.service.VppTsdbQueryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * TDengine 时序数据查询(通过 service-tsdb Feign,与 service-ems 一致)
+ */
+@Service
+public class VppTsdbQueryServiceImpl implements VppTsdbQueryService {
+
+    private static final Logger log = LoggerFactory.getLogger(VppTsdbQueryServiceImpl.class);
+
+    private static final DateTimeFormatter TSDB_FLEXIBLE_TIME_FORMAT = new DateTimeFormatterBuilder()
+            .appendPattern("yyyy-MM-dd HH:mm:ss")
+            .optionalStart()
+            .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+            .optionalEnd()
+            .toFormatter();
+
+    @Autowired
+    private RemoteTsdbProxyService remoteTsdbProxyService;
+
+    @Override
+    public Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> queryDeviceMetricHistory(
+            List<String> deviceUuids,
+            LocalDateTime startTime,
+            LocalDateTime endTime,
+            List<String> metrics) {
+        if (CollectionUtils.isEmpty(deviceUuids) || startTime == null || endTime == null) {
+            return Collections.emptyMap();
+        }
+
+        HistorysInnerRequestVO request = new HistorysInnerRequestVO();
+        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));
+        request.setStartTime(startTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));
+        request.setEndTime(endTime.format(DateTimeFormatter.ofPattern(VppTsdbConstants.TIME_FORMAT)));
+        if (!CollectionUtils.isEmpty(metrics)) {
+            request.setMetrics(new ArrayList<>(metrics));
+        }
+
+        try {
+            ApiResult<List<HistorysInnerResultVO>> result = remoteTsdbProxyService.queryHistoryDeviceData(request);
+            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {
+                log.warn("TDengine 历史查询无数据, devices={}, start={}, end={}",
+                        deviceUuids.size(), request.getStartTime(), request.getEndTime());
+                return Collections.emptyMap();
+            }
+            return parseHistoryResult(result.getData());
+        } catch (Exception ex) {
+            log.error("TDengine 历史查询失败: {}", ex.getMessage(), ex);
+            return Collections.emptyMap();
+        }
+    }
+
+    @Override
+    public Map<String, Map<String, BigDecimal>> queryLatestMetrics(List<String> deviceUuids, List<String> metrics) {
+        if (CollectionUtils.isEmpty(deviceUuids)) {
+            return Collections.emptyMap();
+        }
+
+        LastInnerQueryVO request = new LastInnerQueryVO();
+        request.setDeviceuuid(new ArrayList<>(new LinkedHashSet<>(deviceUuids)));
+        if (!CollectionUtils.isEmpty(metrics)) {
+            request.setMetrics(new ArrayList<>(metrics));
+        }
+
+        try {
+            ApiResult<List<LastInnerResultVO>> result = remoteTsdbProxyService.queryLastDeviceData(request);
+            if (result == null || !result.isSuccess() || CollectionUtils.isEmpty(result.getData())) {
+                return Collections.emptyMap();
+            }
+            return parseLatestResult(result.getData());
+        } catch (Exception ex) {
+            log.error("TDengine 最新值查询失败: {}", ex.getMessage(), ex);
+            return Collections.emptyMap();
+        }
+    }
+
+    private Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> parseHistoryResult(
+            List<HistorysInnerResultVO> historyDataList) {
+        Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceMetricMap = new HashMap<>();
+        for (HistorysInnerResultVO result : historyDataList) {
+            if (result == null || !StringUtils.hasText(result.getDeviceuuid()) || CollectionUtils.isEmpty(result.getMetrics())) {
+                continue;
+            }
+            String deviceUuid = result.getDeviceuuid();
+            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap =
+                    deviceMetricMap.computeIfAbsent(deviceUuid, key -> new HashMap<>());
+
+            for (MetricVO metric : result.getMetrics()) {
+                if (metric == null || !StringUtils.hasText(metric.getMetric()) || CollectionUtils.isEmpty(metric.getMetricItems())) {
+                    continue;
+                }
+                TreeMap<LocalDateTime, BigDecimal> timeValueMap =
+                        metricMap.computeIfAbsent(metric.getMetric(), key -> new TreeMap<>());
+                for (Map<String, Object> point : metric.getMetricItems()) {
+                    if (point == null || point.get("timestamp") == null || point.get("value") == null) {
+                        continue;
+                    }
+                    try {
+                        LocalDateTime timestamp = parseTimestamp(point.get("timestamp").toString());
+                        BigDecimal value = new BigDecimal(point.get("value").toString());
+                        timeValueMap.put(timestamp, value);
+                    } catch (Exception ignored) {
+                        // skip invalid point
+                    }
+                }
+            }
+        }
+        return deviceMetricMap;
+    }
+
+    private Map<String, Map<String, BigDecimal>> parseLatestResult(List<LastInnerResultVO> latestList) {
+        Map<String, Map<String, BigDecimal>> result = new HashMap<>();
+        for (LastInnerResultVO item : latestList) {
+            if (item == null || !StringUtils.hasText(item.getDeviceuuid()) || item.getMetrics() == null) {
+                continue;
+            }
+            Map<String, BigDecimal> metricValues = new HashMap<>();
+            item.getMetrics().forEach((metric, value) -> {
+                if (value == null) {
+                    return;
+                }
+                try {
+                    metricValues.put(metric, new BigDecimal(value.toString()).setScale(4, RoundingMode.HALF_UP));
+                } catch (Exception ignored) {
+                    // skip invalid value
+                }
+            });
+            result.put(item.getDeviceuuid(), metricValues);
+        }
+        return result;
+    }
+
+    private LocalDateTime parseTimestamp(String timestampStr) {
+        String normalized = timestampStr.trim();
+        if (normalized.endsWith(".0")) {
+            normalized = normalized.substring(0, normalized.length() - 2);
+        }
+        return LocalDateTime.parse(normalized, TSDB_FLEXIBLE_TIME_FORMAT);
+    }
+}

+ 21 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityCategoryStatVO.java

@@ -0,0 +1,21 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 可调能力评估 - 分类统计(户数/容量)
+ */
+@Data
+public class CapabilityCategoryStatVO {
+
+    /** TOTAL / LOAD / STORAGE / GENERATION */
+    private String category;
+    /** 分类中文名 */
+    private String categoryLabel;
+    /** 户数(去重客户数) */
+    private Integer customerCount;
+    /** 装机容量 kW */
+    private BigDecimal capacityKw;
+}

+ 27 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityEvalSummaryVO.java

@@ -0,0 +1,27 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 可调能力评估 - 顶部汇总卡片
+ */
+@Data
+public class CapabilityEvalSummaryVO {
+
+    private Long siteId;
+    private String siteName;
+    /** 总资源 */
+    private CapabilityCategoryStatVO total;
+    /** 负荷类 */
+    private CapabilityCategoryStatVO load;
+    /** 储能类 */
+    private CapabilityCategoryStatVO storage;
+    /** 发电类 */
+    private CapabilityCategoryStatVO generation;
+    /** 最大上调能力 kW */
+    private BigDecimal maxUpKw;
+    /** 最大下调能力 kW */
+    private BigDecimal maxDownKw;
+}

+ 31 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityHistoryVO.java

@@ -0,0 +1,31 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 可调能力评估 - 历史响应记录
+ */
+@Data
+public class CapabilityHistoryVO {
+
+    private Long eventId;
+    private String eventCode;
+    private String eventName;
+    /** 1削峰 2填谷 */
+    private Integer eventType;
+    private String eventTypeLabel;
+    /** 1日前 2日内 3秒级 */
+    private Integer responseType;
+    private String responseTypeLabel;
+    private LocalDateTime startTime;
+    private LocalDateTime endTime;
+    /** 目标/申报容量 kW */
+    private BigDecimal targetCapacityKw;
+    /** 实际响应容量 kW */
+    private BigDecimal actualCapacityKw;
+    /** 达标率 % */
+    private BigDecimal qualifiedRate;
+}

+ 20 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityLoadCurveVO.java

@@ -0,0 +1,20 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 可调能力曲线
+ */
+@Data
+public class CapabilityLoadCurveVO {
+
+    private Long siteId;
+    private String siteName;
+    /** 统计日期 yyyy-MM-dd */
+    private String date;
+    /** 数据来源:tsdb / mock */
+    private String dataSource;
+    private List<CapabilityLoadPointVO> points;
+}

+ 19 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/CapabilityLoadPointVO.java

@@ -0,0 +1,19 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 可调能力曲线 - 单时间点负荷
+ */
+@Data
+public class CapabilityLoadPointVO {
+
+    /** 时刻 HH:mm */
+    private String time;
+    /** 基线负荷 kW */
+    private BigDecimal baselineLoadKw;
+    /** 实际负荷 kW */
+    private BigDecimal actualLoadKw;
+}

+ 364 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppCapabilityEvalHelper.java

@@ -0,0 +1,364 @@
+package com.usky.vpp.util;
+
+import com.usky.vpp.domain.VppDevice;
+import com.usky.vpp.domain.VppResourcePoint;
+import com.usky.vpp.service.vo.CapabilityLoadPointVO;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * 可调能力评估辅助(TSDB 聚合 + 模拟数据兜底)
+ */
+public final class VppCapabilityEvalHelper {
+
+    public static final String CATEGORY_TOTAL = "TOTAL";
+    public static final String CATEGORY_LOAD = "LOAD";
+    public static final String CATEGORY_STORAGE = "STORAGE";
+    public static final String CATEGORY_GENERATION = "GENERATION";
+
+    private static final Set<String> LOAD_TYPES = new HashSet<>(Arrays.asList("IND_LOAD", "COM_BLDG", "EVCS"));
+    private static final Set<String> STORAGE_TYPES = Collections.singleton("ESS");
+    private static final Set<String> GENERATION_TYPES = Collections.singleton("PV");
+
+    private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("HH:mm");
+
+    private VppCapabilityEvalHelper() {
+    }
+
+    public static String categoryLabel(String category) {
+        if (category == null) {
+            return "未知";
+        }
+        switch (category) {
+            case CATEGORY_TOTAL:
+                return "总资源";
+            case CATEGORY_LOAD:
+                return "负荷类";
+            case CATEGORY_STORAGE:
+                return "储能类";
+            case CATEGORY_GENERATION:
+                return "发电类";
+            default:
+                return category;
+        }
+    }
+
+    public static String resolveCategory(String resourceType) {
+        if (resourceType == null) {
+            return CATEGORY_LOAD;
+        }
+        String type = resourceType.toUpperCase();
+        if (GENERATION_TYPES.contains(type)) {
+            return CATEGORY_GENERATION;
+        }
+        if (STORAGE_TYPES.contains(type)) {
+            return CATEGORY_STORAGE;
+        }
+        if (LOAD_TYPES.contains(type)) {
+            return CATEGORY_LOAD;
+        }
+        return CATEGORY_LOAD;
+    }
+
+    public static String eventTypeLabel(Integer eventType) {
+        if (eventType == null) {
+            return "未知";
+        }
+        return eventType == 1 ? "削峰" : eventType == 2 ? "填谷" : "未知";
+    }
+
+    public static String responseTypeLabel(Integer responseType) {
+        if (responseType == null) {
+            return "未知";
+        }
+        switch (responseType) {
+            case 1:
+                return "日前";
+            case 2:
+                return "日内";
+            case 3:
+                return "秒级";
+            default:
+                return "未知";
+        }
+    }
+
+    /**
+     * 基于 TDengine 历史数据构建负荷曲线(每 10 分钟一个点)
+     * <p>实际负荷取统计日实测;基线负荷取 7 日前同时段作为参考基线。</p>
+     */
+    public static List<CapabilityLoadPointVO> buildLoadCurveFromTsdb(
+            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> actualData,
+            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> baselineData,
+            LocalDate date,
+            List<String> powerMetrics) {
+        List<CapabilityLoadPointVO> points = new ArrayList<>();
+        for (int minute = 0; minute < 1440; minute += 10) {
+            LocalTime time = LocalTime.of(minute / 60, minute % 60);
+            LocalDateTime actualStart = date.atTime(time);
+            LocalDateTime actualEnd = actualStart.plusMinutes(10).minusSeconds(1);
+            LocalDateTime baselineStart = date.minusDays(7).atTime(time);
+            LocalDateTime baselineEnd = baselineStart.plusMinutes(10).minusSeconds(1);
+
+            BigDecimal actual = sumBucketPower(actualData, powerMetrics, actualStart, actualEnd);
+            BigDecimal baseline = sumBucketPower(baselineData, powerMetrics, baselineStart, baselineEnd);
+            if (baseline == null || baseline.compareTo(BigDecimal.ZERO) <= 0) {
+                baseline = actual;
+            }
+
+            CapabilityLoadPointVO point = new CapabilityLoadPointVO();
+            point.setTime(time.format(TIME_FMT));
+            point.setBaselineLoadKw(scale(baseline));
+            point.setActualLoadKw(scale(actual));
+            points.add(point);
+        }
+        return points;
+    }
+
+    /**
+     * 汇总设备最新上下调能力(TDengine 实时指标)
+     */
+    public static BigDecimal sumLatestMetric(Map<String, Map<String, BigDecimal>> latestData,
+                                             List<String> metricCandidates) {
+        if (latestData == null || latestData.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (Map<String, BigDecimal> metrics : latestData.values()) {
+            BigDecimal value = pickMetric(metrics, metricCandidates);
+            if (value != null) {
+                total = total.add(value);
+            }
+        }
+        return total.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    public static boolean hasTsdbPowerData(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceMetricData,
+                                           List<String> powerMetrics) {
+        if (deviceMetricData == null || deviceMetricData.isEmpty()) {
+            return false;
+        }
+        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap : deviceMetricData.values()) {
+            for (String metric : powerMetrics) {
+                TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
+                if (series != null && !series.isEmpty()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 生成当日负荷曲线(每 10 分钟一个点,00:00 ~ 23:50)- 模拟兜底
+     */
+    public static List<CapabilityLoadPointVO> buildLoadCurve(List<VppResourcePoint> resources,
+                                                           List<VppDevice> devices,
+                                                           Long siteId,
+                                                           LocalDate date) {
+        List<CapabilityLoadPointVO> points = new ArrayList<>();
+        long seed = (siteId != null ? siteId : 0L) + date.toEpochDay();
+        for (int minute = 0; minute < 1440; minute += 10) {
+            LocalTime time = LocalTime.of(minute / 60, minute % 60);
+            double hourFactor = timeFactor(time);
+            BigDecimal baseline = aggregateLoad(resources, devices, seed, minute, hourFactor, 0);
+            BigDecimal actual = aggregateLoad(resources, devices, seed, minute, hourFactor, 1);
+            CapabilityLoadPointVO point = new CapabilityLoadPointVO();
+            point.setTime(time.format(TIME_FMT));
+            point.setBaselineLoadKw(baseline);
+            point.setActualLoadKw(actual);
+            points.add(point);
+        }
+        return points;
+    }
+
+    private static BigDecimal aggregateLoad(List<VppResourcePoint> resources,
+                                            List<VppDevice> devices,
+                                            long seed,
+                                            int minuteOfDay,
+                                            double hourFactor,
+                                            int seriesSalt) {
+        if (resources == null || resources.isEmpty()) {
+            double base = 800 * hourFactor + pseudoRandom(seed, minuteOfDay, seriesSalt) * 120;
+            return BigDecimal.valueOf(base).setScale(2, RoundingMode.HALF_UP);
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (VppResourcePoint resource : resources) {
+            VppDevice device = findDevice(devices, resource.getDeviceId());
+            if (device != null && device.getCommStatus() != null && device.getCommStatus() == 0) {
+                continue;
+            }
+            BigDecimal cap = resource.getCapacityKw() != null ? resource.getCapacityKw() : BigDecimal.valueOf(100);
+            double typeFactor = typeLoadFactor(resource.getResourceType());
+            double noise = 0.85 + pseudoRandom(seed, resource.getId() != null ? resource.getId() : 0L, minuteOfDay, seriesSalt) * 0.3;
+            double value = cap.doubleValue() * hourFactor * typeFactor * noise;
+            if (CATEGORY_GENERATION.equals(resolveCategory(resource.getResourceType()))) {
+                value *= 0.15;
+            }
+            total = total.add(BigDecimal.valueOf(value));
+        }
+        if (seriesSalt == 1) {
+            total = total.multiply(BigDecimal.valueOf(1.02 + pseudoRandom(seed, 999, minuteOfDay, seriesSalt) * 0.08));
+        }
+        return total.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private static BigDecimal sumBucketPower(Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> deviceData,
+                                             List<String> metricCandidates,
+                                             LocalDateTime bucketStart,
+                                             LocalDateTime bucketEnd) {
+        if (deviceData == null || deviceData.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap : deviceData.values()) {
+            TreeMap<LocalDateTime, BigDecimal> series = pickSeries(metricMap, metricCandidates);
+            if (series == null || series.isEmpty()) {
+                continue;
+            }
+            BigDecimal avg = averageInRange(series, bucketStart, bucketEnd);
+            if (avg != null) {
+                total = total.add(avg);
+            }
+        }
+        return total;
+    }
+
+    private static TreeMap<LocalDateTime, BigDecimal> pickSeries(Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap,
+                                                                 List<String> metricCandidates) {
+        if (metricMap == null || metricMap.isEmpty()) {
+            return null;
+        }
+        for (String metric : metricCandidates) {
+            TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
+            if (series != null && !series.isEmpty()) {
+                return series;
+            }
+        }
+        for (Map.Entry<String, TreeMap<LocalDateTime, BigDecimal>> entry : metricMap.entrySet()) {
+            if (entry.getValue() != null && !entry.getValue().isEmpty()) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    private static BigDecimal pickMetric(Map<String, BigDecimal> metrics, List<String> metricCandidates) {
+        if (metrics == null || metrics.isEmpty()) {
+            return null;
+        }
+        for (String metric : metricCandidates) {
+            BigDecimal value = metrics.get(metric);
+            if (value != null) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    private static BigDecimal averageInRange(TreeMap<LocalDateTime, BigDecimal> series,
+                                             LocalDateTime startInclusive,
+                                             LocalDateTime endInclusive) {
+        BigDecimal sum = BigDecimal.ZERO;
+        int count = 0;
+        for (Map.Entry<LocalDateTime, BigDecimal> entry : series.subMap(startInclusive, true, endInclusive, true).entrySet()) {
+            if (entry.getValue() != null) {
+                sum = sum.add(entry.getValue());
+                count++;
+            }
+        }
+        if (count == 0) {
+            Map.Entry<LocalDateTime, BigDecimal> floor = series.floorEntry(startInclusive);
+            if (floor != null && floor.getValue() != null) {
+                return floor.getValue();
+            }
+            return null;
+        }
+        return sum.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP);
+    }
+
+    private static BigDecimal scale(BigDecimal value) {
+        if (value == null) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private static VppDevice findDevice(List<VppDevice> devices, Long deviceId) {
+        if (devices == null || deviceId == null) {
+            return null;
+        }
+        return devices.stream().filter(d -> deviceId.equals(d.getId())).findFirst().orElse(null);
+    }
+
+    /**
+     * 日内负荷形态:夜间低、早晚峰、午间次峰
+     */
+    private static double timeFactor(LocalTime time) {
+        int hour = time.getHour();
+        int minute = time.getMinute();
+        double h = hour + minute / 60.0;
+        if (h < 6) {
+            return 0.45 + h / 6 * 0.15;
+        }
+        if (h < 9) {
+            return 0.6 + (h - 6) / 3 * 0.35;
+        }
+        if (h < 12) {
+            return 0.95 + (h - 9) / 3 * 0.05;
+        }
+        if (h < 14) {
+            return 0.85;
+        }
+        if (h < 17) {
+            return 0.9 + (h - 14) / 3 * 0.1;
+        }
+        if (h < 21) {
+            return 1.0 - (h - 17) / 4 * 0.35;
+        }
+        return 0.5 - (h - 21) / 3 * 0.15;
+    }
+
+    private static double typeLoadFactor(String resourceType) {
+        if (resourceType == null) {
+            return 0.7;
+        }
+        switch (resourceType.toUpperCase()) {
+            case "IND_LOAD":
+                return 0.75;
+            case "COM_BLDG":
+                return 0.65;
+            case "EVCS":
+                return 0.55;
+            case "ESS":
+                return 0.4;
+            case "PV":
+                return 1.0;
+            default:
+                return 0.7;
+        }
+    }
+
+    private static double pseudoRandom(long seed, long a, int b, int c) {
+        long mixed = seed * 31L + a * 17L + b * 13L + c * 7L;
+        return (mixed % 1000) / 1000.0;
+    }
+
+    private static double pseudoRandom(long seed, int minuteOfDay, int seriesSalt) {
+        long mixed = seed * 31L + minuteOfDay * 17L + seriesSalt * 13L;
+        return (mixed % 1000) / 1000.0;
+    }
+}

+ 16 - 15
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppSmsTemplateHelper.java

@@ -10,11 +10,11 @@ import java.time.format.DateTimeFormatter;
 /**
  * 邀约短信模板参数构建
  * <p>
- * 阿里云模板变量需与控制台一致,默认使用:name、event、capacity、deadline
+ * 阿里云模板变量需与控制台一致,默认使用:meet、time、room(SMS_465362899)
  */
 public final class VppSmsTemplateHelper {
 
-    private static final DateTimeFormatter DEADLINE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+    private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     private VppSmsTemplateHelper() {
     }
@@ -23,20 +23,21 @@ public final class VppSmsTemplateHelper {
                                             VppDrEvent event,
                                             VppCustomer customer,
                                             VppCustomerContact contact) {
-        String name = contact != null && contact.getContactName() != null
-                ? contact.getContactName()
-                : (customer != null ? customer.getCustomerName() : "");
-        String eventCode = event != null ? event.getEventId() : "";
-        String capacity = invitation.getDemandCapacityKw() != null
-                ? invitation.getDemandCapacityKw().stripTrailingZeros().toPlainString()
-                : "";
-        String deadline = invitation.getReplyDeadline() != null
-                ? DEADLINE_FORMAT.format(invitation.getReplyDeadline())
+        String meet = event != null && event.getEventName() != null
+                ? event.getEventName()
+                : (event != null ? event.getEventId() : "");
+        String time = "";
+        if (event != null && event.getStartTime() != null) {
+            time = TIME_FORMAT.format(event.getStartTime());
+        } else if (invitation.getReplyDeadline() != null) {
+            time = TIME_FORMAT.format(invitation.getReplyDeadline());
+        }
+        String room = customer != null && customer.getCustomerName() != null
+                ? customer.getCustomerName()
                 : "";
-        return "{\"name\":\"" + escapeJson(name)
-                + "\",\"event\":\"" + escapeJson(eventCode)
-                + "\",\"capacity\":\"" + escapeJson(capacity)
-                + "\",\"deadline\":\"" + escapeJson(deadline) + "\"}";
+        return "{\"meet\":\"" + escapeJson(meet)
+                + "\",\"time\":\"" + escapeJson(time)
+                + "\",\"room\":\"" + escapeJson(room) + "\"}";
     }
 
     private static String escapeJson(String value) {

+ 1 - 1
service-vpp/service-vpp-biz/src/main/resources/bootstrap.yml

@@ -35,4 +35,4 @@ vpp:
   sms:
     enabled: true
     sign-name: 上海永天科技股份有限公司
-    invitation-template-code: SMS_xxxxxx
+    invitation-template-code: SMS_465362899

+ 5636 - 0
service-vpp/虚拟电厂平台接口文档V1.0.md

@@ -0,0 +1,5636 @@
+# 虚拟电厂平台接口文档
+
+| 项目 | 内容 |
+|------|------|
+| 文档名称 | 虚拟电厂平台接口文档 |
+| 文档版本 | V1.0.3 |
+| 编制依据 | 《API设计文档0304.md》《虚拟电厂平台详细设计文档 V1.0》《虚拟电厂平台数据库设计文档 V1.1》 |
+| 参考规范 | DLT 1867—2024、《虚拟电厂运管管理平台与虚拟电厂信息交互规范》(2025-12-17) |
+| 编制日期 | 2026-06-23 |
+| 更新日期 | 2026-07-04 |
+| 文档状态 | 已对齐 service-vpp 运管平台联调实现(V1.0.3,新增邀约管理接口) |
+
+---
+
+## 目录
+
+- [1. 概述](#1-概述)
+  - [1.4 数据存储与接口映射](#14-数据存储与接口映射)
+- [2. 内部 REST API(Web/移动端)](#2-内部-rest-apiweb移动端)
+  - [2.1 通用规范](#21-通用规范)
+    - [2.1.7 通用审计与租户字段](#217-通用审计与租户字段)
+  - [2.2 认证授权](#22-认证授权)
+  - [2.3 首页与看板](#23-首页与看板)
+  - [2.4 客户管理](#24-客户管理)
+  - [2.5 合同管理](#25-合同管理)
+  - [2.6 资源管理](#26-资源管理)
+    - [2.6.6 可调能力评估](#266-可调能力评估)
+  - [2.7 运行监控](#27-运行监控)
+  - [2.8 告警管理](#28-告警管理)
+  - [2.9 需求响应](#29-需求响应)
+  - [2.10 结算管理](#210-结算管理)
+  - [2.11 信息报送](#211-信息报送)
+  - [2.12 统计报表与数据分析](#212-统计报表与数据分析)
+  - [2.13 系统管理](#213-系统管理)
+  - [2.14 移动端专用](#214-移动端专用)
+  - [2.15 运管平台主动对接(Web 管理)](#215-运管平台主动对接web-管理)
+- [3. 运管平台对接 API(UN/DN)](#3-运管平台对接-apiundn)
+  - [3.1 交互概述](#31-交互概述)
+  - [3.2 安全机制](#32-安全机制)
+  - [3.3 通用服务](#33-通用服务)
+  - [3.4 注册服务](#34-注册服务)
+  - [3.5 询问服务(Poll)](#35-询问服务poll)
+  - [3.6 报告服务](#36-报告服务)
+  - [3.7 事件与参与服务](#37-事件与参与服务)
+  - [3.8 信息对象服务(车网互动)](#38-信息对象服务车网互动)
+  - [3.9 UN 侧错误码](#39-un-侧错误码)
+  - [3.10 平台实现与配置(service-vpp)](#310-平台实现与配置service-vpp)
+- [4. 外部监管报送接口](#4-外部监管报送接口)
+- [5. 附录](#5-附录)
+  - [5.4 内部 API 与数据表映射](#54-内部-api-与数据表映射节选)
+  - [5.6 UN 联调 JSON 样例](#56-un-联调-json-样例)
+
+---
+
+## 1. 概述
+
+### 1.1 接口体系
+
+本平台接口分为三类:
+
+| 类型 | 调用方向 | 协议 | 用途 |
+|------|----------|------|------|
+| 内部 REST API | 前端/移动端 → 平台后端 | HTTPS + JSON + JWT | 业务管理、监控、运营 |
+| 运管平台 API(DN) | UN ↔ DN | HTTPS + JSON + 国密 SM2/SM3 | 注册、数据上报、需求响应 |
+| 监管报送 API | 平台 → 外部机构 | HTTPS/文件 | 信息报送 |
+
+### 1.2 环境地址
+
+| 环境 | 平台地址(示例) | 内部 API 前缀 | 运管平台 DN 地址(示例) |
+|------|------------------|---------------|--------------------------|
+| 开发 | `http://192.168.10.151` | `/prod-api/service-vpp` | `http://{内网IP}:{PORT}` |
+| 测试 | `https://test.example.com` | `/prod-api/service-vpp` | 联调时向 UN 提供公网 IP+端口 |
+| 生产 | `https://vpp.example.com` | `/prod-api/service-vpp` | 联调时向 UN 提供公网 IP+端口 |
+
+### 1.3 路径与版本策略
+
+与《API设计文档0304.md》保持一致,内部 REST API 采用 **网关 + 服务模块** 路径格式:
+
+| 层级 | 格式 | 示例 |
+|------|------|------|
+| 网关前缀 | `/prod-api` | 统一 API 网关入口 |
+| 服务模块 | `/service-vpp` | 虚拟电厂微服务标识 |
+| 业务路径 | `/{module}/{resource}[/{id}][/action]` | `/alarm/rule`、`/customer/access` |
+
+**完整示例**:`GET /prod-api/service-vpp/customer?current=1&size=10`
+
+- 运管平台(UN/DN)接口路径不变:服务名即路径,如 `/TokenRequest`、`/Poll`
+- 内部 API 破坏性变更通过新增服务版本(如 `service-vpp-v2`)或网关路由版本控制
+
+### 1.4 数据存储与接口映射
+
+内部 REST API 路径格式见 §1.3;持久化按《虚拟电厂平台数据库设计文档 V1.0》落库:
+
+| 接口模块 | 主要数据表 | 说明 |
+|----------|------------|------|
+| 认证/用户/角色/菜单 | `sys_user`、`sys_role`、`sys_menu`、`sys_user_role`、`sys_role_menu` | 沿用 usky-cloud `sys_*` |
+| 系统配置/字典/操作日志 | `sys_config`、`sys_dict_type`、`sys_dict_data`、`sys_oper_log` | 配置键建议 `vpp.` 前缀 |
+| 告警规则 | `rule_engine`、`rule_engine_device`、`rule_engine_condition`、`rule_engine_cron` | 沿用 usky-cloud `rule_*` |
+| 告警记录/统计/通知 | `base_alarm`、`base_alarm_type`、`base_alarm_notice`、`base_alarm_statistic` | 沿用 usky-cloud `base_*` |
+| 设备状态/远程控制 | `dmp_device`、`dmp_device_status`、`dmp_device_command` + `vpp_device_control_log` | 物联网命令写 `dmp_*`,业务摘要写 `vpp_*` |
+| 可调能力评估 | `vpp_site`、`vpp_resource_point`、`vpp_device`、`vpp_dr_*` + TDengine `device_metrics` | `/capability-eval`;曲线经 `service-tsdb` 查询 |
+| 电子档案 | `vpp_file_archive` | VPP 专属;`bizType` + `bizId` 关联合同/客户等 |
+| 需求响应/运管 UN | `vpp_dr_*`、`vpp_registration` | VPP 专属;邀约台账 `vpp_dr_invitation`;UN 报文 `account` ↔ `vpp_customer.account_no` |
+
+> 接口 JSON 字段采用 **camelCase**;落库字段为 **snake_case**,由服务端映射。租户隔离统一使用 `tenantId`(对应各表 `tenant_id`)。
+
+---
+
+## 2. 内部 REST API(Web/移动端)
+
+### 2.1 通用规范
+
+> 本节与《API设计文档0304.md》「通用规范」对齐。
+
+#### 2.1.1 请求头
+
+```
+Content-Type: application/json
+Authorization: Bearer {token}
+```
+
+| Header | 必填 | 说明 |
+|--------|------|------|
+| `Authorization` | 是(登录接口除外) | `Bearer {token}` |
+| `Content-Type` | 是 | `application/json`(上传接口为 `multipart/form-data`) |
+| `X-Tenant-Id` | 否 | 租户 ID;多租户场景下可由 Token 解析,显式传参时优先 |
+
+#### 2.1.2 统一响应格式
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": {},
+  "exception": null
+}
+```
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| status | string | 接口调用状态:`SUCCESS` / `FAIL` |
+| code | int | 业务状态码 |
+| msg | string | 提示信息 |
+| data | object | 业务数据 |
+| exception | string/null | 异常信息,正常为 null |
+
+#### 2.1.3 响应状态码
+
+| code | 说明 |
+|------|------|
+| 200 | 成功 |
+| 400 | 请求参数错误 |
+| 401 | 未授权,请先登录 |
+| 403 | 无权限访问 |
+| 404 | 资源不存在 |
+| 500 | 服务器内部错误 |
+
+**业务错误码(节选)**:
+
+| code | 说明 |
+|------|------|
+| 1001 | 用户名或密码错误 |
+| 1002 | 账号已被禁用 |
+| 2001 | 客户/资源/设备不存在 |
+| 2002 | 设备已离线 |
+| 3001 | 合同状态不允许当前操作 |
+| 4001 | 告警记录不存在 |
+| 5001 | 系统配置错误 |
+
+#### 2.1.4 分页规范
+
+**请求参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| current | int | 否 | 页码,默认 1 |
+| size | int | 否 | 每页数量,默认 10,最大 100 |
+| sortField | string | 否 | 排序字段,如 `create_time` |
+| sortOrder | string | 否 | `asc` / `desc`,默认 `desc` |
+
+**响应 data 结构**:
+
+```json
+{
+  "records": [],
+  "total": 100,
+  "current": 1,
+  "size": 10,
+  "totalPages": 10
+}
+```
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| records | array | 当前页数据列表 |
+| total | int | 总记录数 |
+| current | int | 当前页码 |
+| size | int | 每页记录数 |
+| totalPages | int | 总页数 |
+
+#### 2.1.5 时间格式
+
+所有时间字段统一使用 ISO 8601 格式:`YYYY-MM-DDTHH:mm:ss`
+
+示例:`2026-06-23T10:15:00`
+
+#### 2.1.6 接口描述约定
+
+各业务接口采用统一描述要素(与 0304 文档一致):
+
+| 要素 | 说明 |
+|------|------|
+| **接口地址** | `METHOD /prod-api/service-vpp/...` |
+| **功能描述** | 接口业务用途 |
+| **请求参数** | Query / Body 字段表或 JSON 示例 |
+| **响应示例** | 含 `status`/`code`/`msg`/`data` 的完整 JSON |
+| **字段说明** | 关键字段与数据表映射(如有) |
+
+#### 2.1.7 通用审计与租户字段
+
+VPP 业务实体(`vpp_*` 表)在列表/详情响应中可能包含以下公共字段:
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| tenantId | int | 租户 ID,多租户隔离 | tenant_id |
+| createTime | string | 创建时间(ISO 8601) | create_time |
+| updateTime | string | 更新时间(ISO 8601) | update_time |
+| createdBy | string | 创建人账号(≤30 字符) | created_by |
+| updatedBy | string | 更新人账号 | updated_by |
+| deleteFlag | int | 删除标识:0 未删除 / 1 已删除 | delete_flag |
+| deletedAt | string/null | 软删除时间,未删除时为 null | deleted_at |
+
+> 列表查询默认过滤 `deleteFlag=0`;软删除接口设置 `deleteFlag=1` 并写入 `deletedAt`。
+
+---
+
+### 2.2 认证授权
+
+#### 2.2.1 用户登录
+
+**接口地址**:`POST /prod-api/service-vpp/auth/login`
+
+**功能描述**:用户登录,返回 Token 与权限信息。
+
+**权限**:公开
+
+**请求体**(映射 `sys_user`):
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| username | string | 是 | 用户账号(sys_user.user_name) |
+| password | string | 是 | 登录密码 |
+
+**请求体**:
+
+```json
+{
+  "username": "operator01",
+  "password": "******"
+}
+```
+
+**响应示例**(对应 `sys_user` + `sys_role`):
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "登录成功",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiJ9...",
+    "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
+    "expiresIn": 7200,
+    "user": {
+      "userId": 1001,
+      "userName": "operator01",
+      "nickName": "张三",
+      "phonenumber": "13800138000",
+      "deptId": 101,
+      "tenantId": 1,
+      "status": "0",
+      "roles": [
+        { "roleId": 10, "roleName": "运营人员", "roleKey": "vpp:operator" }
+      ],
+      "permissions": ["dashboard:view", "alarm:manage"]
+    }
+  },
+  "exception": null
+}
+```
+
+> `permissions` 由 `sys_role_menu` 关联的 `sys_menu.perms` 聚合得到。
+
+#### 2.2.2 刷新 Token
+
+**接口地址**:`POST /prod-api/service-vpp/auth/refresh`
+
+**功能描述**:使用 refreshToken 换取新的 accessToken。
+
+**权限**:公开(需有效 refreshToken)
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| refreshToken | string | 是 | 登录时返回的刷新令牌 |
+
+```json
+{
+  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "刷新成功",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiJ9...",
+    "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
+    "expiresIn": 7200
+  },
+  "exception": null
+}
+```
+
+#### 2.2.3 退出登录
+
+**接口地址**:`POST /prod-api/service-vpp/auth/logout`
+
+**功能描述**:注销当前会话,使 Token 失效。
+
+**权限**:已登录用户
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "退出成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.2.4 获取当前用户信息
+
+**接口地址**:`GET /prod-api/service-vpp/auth/me`
+
+**功能描述**:获取当前登录用户详情及权限列表。
+
+**权限**:已登录用户
+
+**请求参数**:无
+
+**响应示例**(对应 `sys_user` + `sys_role` + `sys_menu`):
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "userId": 1001,
+    "userName": "operator01",
+    "nickName": "张三",
+    "phonenumber": "13800138000",
+    "email": "zhangsan@example.com",
+    "deptId": 101,
+    "tenantId": 1,
+    "status": "0",
+    "roles": [
+      { "roleId": 10, "roleName": "运营人员", "roleKey": "vpp:operator" }
+    ],
+    "permissions": ["dashboard:view", "alarm:manage", "customer:view"]
+  },
+  "exception": null
+}
+```
+
+---
+
+### 2.3 首页与看板
+
+#### 2.3.1 首页核心指标
+
+**接口地址**:`GET /prod-api/service-vpp/dashboard/summary`
+
+**功能描述**:获取首页核心运营指标汇总。
+
+**权限**:`dashboard:view`
+
+**请求参数**:无
+
+**响应 data 字段**(聚合 `vpp_energy_summary_daily`、`vpp_device`、`base_alarm`、`vpp_dr_event`):
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| todayGenKwh | number | 今日发电量 kWh |
+| todayUseKwh | number | 今日用电量 kWh |
+| todayRevenue | number | 今日收益(元) |
+| onlineDeviceCount | int | 在线设备数 |
+| pendingAlarmCount | int | 待处理告警数 |
+| activeDrEventCount | int | 进行中需求响应事件数 |
+
+**响应 data**:
+
+```json
+{
+  "todayGenKwh": 12580.5,
+  "todayUseKwh": 9820.3,
+  "todayRevenue": 15680.0,
+  "onlineDeviceCount": 128,
+  "pendingAlarmCount": 5,
+  "activeDrEventCount": 1
+}
+```
+
+#### 2.3.2 GIS 资源分布
+
+**接口地址**:`GET /prod-api/service-vpp/dashboard/map`
+
+**功能描述**:获取资源点地理分布及实时状态,用于 GIS 地图展示。
+
+**权限**:`dashboard:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceType | string | 否 | 资源类型:PV/ESS/EVCS/IND_LOAD/COM_BLDG |
+| runStatus | int | 否 | 运行状态:0离线 1在线 2故障 3维护 |
+
+**响应 data.records 元素**(映射 `vpp_resource_point` + 实时功率):
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| resourceId | long | 资源点 ID |
+| resourceCode | string | 资源编号 |
+| resourceName | string | 资源名称 |
+| resourceType | string | 资源类型 |
+| longitude | number | 经度 |
+| latitude | number | 纬度 |
+| runStatus | int | 运行状态 |
+| activePowerKw | number | 当前有功功率 kW |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "resourceId": 10001,
+  "resourceCode": "RES-001",
+  "resourceName": "浦东光伏站",
+  "resourceType": "PV",
+  "longitude": 121.544379,
+  "latitude": 31.221517,
+  "runStatus": 1,
+  "activePowerKw": 320.5
+}
+```
+
+#### 2.3.3 实时曲线
+
+**接口地址**:`GET /prod-api/service-vpp/dashboard/curve`
+
+**功能描述**:获取首页实时/历史曲线数据。
+
+**权限**:`dashboard:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| type | string | 是 | 曲线类型:load(负荷)/ pv(发电)/ soc(储能 SOC) |
+| date | string | 否 | 日期,格式 YYYY-MM-DD,默认当天 |
+| resourceId | long | 否 | 资源点 ID,不传则全站汇总 |
+
+**响应 data**(数据来源 TDengine `vpp_device_metrics`):
+
+```json
+{
+  "type": "pv",
+  "date": "2026-06-23",
+  "points": [
+    { "time": "2026-06-23T00:00:00", "value": 0.0 },
+    { "time": "2026-06-23T08:00:00", "value": 120.5 },
+    { "time": "2026-06-23T12:00:00", "value": 450.8 }
+  ]
+}
+```
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| type | string | 曲线类型 |
+| date | string | 查询日期 |
+| points | array | 时序数据点 |
+| points[].time | string | 时间点(ISO 8601) |
+| points[].value | number | 指标值 |
+
+#### 2.3.4 资源概览统计
+
+**接口地址**:`GET /prod-api/service-vpp/resource/overview`
+
+**功能描述**:按资源类型统计资源点数量、容量及在线率。
+
+**权限**:`resource:view`
+
+**请求参数**:无
+
+**响应 data**(聚合 `vpp_resource_point`):
+
+```json
+{
+  "totalCount": 56,
+  "totalCapacityKw": 28500.0,
+  "onlineRate": 0.92,
+  "byType": [
+    { "resourceType": "PV", "count": 20, "capacityKw": 12000.0, "onlineCount": 19 },
+    { "resourceType": "ESS", "count": 8, "capacityKw": 5000.0, "onlineCount": 7 },
+    { "resourceType": "EVCS", "count": 15, "capacityKw": 6500.0, "onlineCount": 14 },
+    { "resourceType": "IND_LOAD", "count": 10, "capacityKw": 4000.0, "onlineCount": 9 },
+    { "resourceType": "COM_BLDG", "count": 3, "capacityKw": 1000.0, "onlineCount": 3 }
+  ]
+}
+```
+
+---
+
+### 2.4 客户管理
+
+#### 2.4.1 提交准入申请
+
+**接口地址**:`POST /prod-api/service-vpp/customer/access`
+
+**功能描述**:提交客户准入申请。
+
+**权限**:`customer:access:create`
+
+**请求体**(映射 `vpp_customer_access`):
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| accountNo | string | 是 | 电力户号 |
+| customerName | string | 是 | 客户名称 |
+| customerType | int | 是 | 1高压 2低压商用 3居民充电桩 4自有资产 |
+| powerCompany | string | 是 | 供电公司简称 |
+| contractCapacity | number | 是 | 用电容量 kVA/kW |
+| businessLicenseUrl | string | 否 | 营业执照附件 URL |
+| creditStatus | int | 否 | 1正常 2关注 3不良 |
+
+**请求体**:
+
+```json
+{
+  "accountNo": "3100060012345",
+  "customerName": "上海某某有限公司",
+  "customerType": 2,
+  "powerCompany": "市北",
+  "contractCapacity": 500.0,
+  "businessLicenseUrl": "https://oss.example.com/license.pdf",
+  "creditStatus": 1
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "提交成功",
+  "data": {
+    "id": 5001,
+    "applyNo": "ACC-202606230001",
+    "accessStatus": 0,
+    "applyAt": "2026-06-23T09:30:00"
+  },
+  "exception": null
+}
+```
+
+#### 2.4.2 准入申请列表
+
+**接口地址**:`GET /prod-api/service-vpp/customer/access`
+
+**功能描述**:分页查询准入申请列表。
+
+**权限**:`customer:access:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| accessStatus | int | 否 | 0待审核 1通过 2驳回 |
+| applyAtStart | string | 否 | 申请开始时间 |
+| applyAtEnd | string | 否 | 申请结束时间 |
+| current | int | 否 | 页码,默认 1 |
+| size | int | 否 | 每页数量,默认 10 |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "id": 5001,
+  "applyNo": "ACC-202606230001",
+  "accountNo": "3100060012345",
+  "customerName": "上海某某有限公司",
+  "customerType": 2,
+  "powerCompany": "市北",
+  "contractCapacity": 500.0,
+  "accessStatus": 0,
+  "auditOpinion": null,
+  "auditAt": null,
+  "customerId": null,
+  "applyAt": "2026-06-23T09:30:00"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_customer_access`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 准入申请 ID | id |
+| applyNo | string | 申请编号 | apply_no |
+| accountNo | string | 电力户号 | account_no |
+| customerName | string | 客户名称 | customer_name |
+| customerType | int | 1高压 2低压商用 3居民充电桩 4自有资产 | customer_type |
+| powerCompany | string | 供电公司简称 | power_company |
+| contractCapacity | number | 用电容量 kVA/kW | contract_capacity |
+| accessStatus | int | 0待审核 1通过 2驳回 | access_status |
+| auditOpinion | string/null | 审核意见 | audit_opinion |
+| auditAt | string/null | 审核时间 | audit_at |
+| customerId | long/null | 通过后关联的客户 ID | customer_id |
+| applyAt | string | 申请时间 | apply_at |
+
+#### 2.4.3 准入审核
+
+**接口地址**:`PUT /prod-api/service-vpp/customer/access/{id}/audit`
+
+**功能描述**:审核准入申请;通过时自动创建 `vpp_customer` 记录。
+
+**权限**:`customer:access:audit`
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | long | 是 | 准入申请 ID |
+
+**请求体**:
+
+```json
+{
+  "passed": true,
+  "auditOpinion": "资质齐全,准予准入"
+}
+```
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| passed | boolean | 是 | true 通过 / false 驳回 |
+| auditOpinion | string | 否 | 审核意见 |
+
+**响应示例**(通过时):
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "审核通过",
+  "data": {
+    "id": 5001,
+    "accessStatus": 1,
+    "customerId": 1001,
+    "auditAt": "2026-06-23T10:00:00"
+  },
+  "exception": null
+}
+```
+
+#### 2.4.4 客户列表
+
+**接口地址**:`GET /prod-api/service-vpp/customer`
+
+**功能描述**:分页查询客户档案列表。
+
+**权限**:`customer:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| customerName | string | 否 | 客户名称(模糊) |
+| accountNo | string | 否 | 电力户号 |
+| lifecycleStatus | int | 否 | 1待准入 2已准入 3已签约 4履约中 5待续约 6已续约 7已解约 |
+| customerType | int | 否 | 客户类型 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_customer`):
+
+```json
+{
+  "id": 1001,
+  "accountNo": "3100060012345",
+  "customerName": "上海某某有限公司",
+  "customerType": 2,
+  "powerCompany": "市北",
+  "contractCapacity": 500.0,
+  "creditStatus": 1,
+  "lifecycleStatus": 4,
+  "province": "上海市",
+  "city": "上海市",
+  "district": "浦东新区",
+  "createTime": "2026-06-23T10:00:00"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_customer`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 客户 ID | id |
+| accountNo | string | 电力户号,全局唯一 | account_no |
+| customerName | string | 客户名称 | customer_name |
+| customerType | int | 客户类型 | customer_type |
+| powerCompany | string | 供电公司简称 | power_company |
+| contractCapacity | number | 用电容量 | contract_capacity |
+| creditStatus | int | 1正常 2关注 3不良 | credit_status |
+| lifecycleStatus | int | 生命周期状态(见 §2.4.4 查询参数) | lifecycle_status |
+| province | string | 省 | province |
+| city | string | 市 | city |
+| district | string | 区 | district |
+| createTime | string | 创建时间 | create_time |
+
+#### 2.4.5 客户详情
+
+**接口地址**:`GET /prod-api/service-vpp/customer/{id}`
+
+**功能描述**:获取客户详情,含联系人、合同数、资源点数及需求响应能力。
+
+**权限**:`customer:view`
+
+**响应 data 示例**:
+
+```json
+{
+  "id": 1001,
+  "accountNo": "3100060012345",
+  "customerName": "上海某某有限公司",
+  "customerType": 2,
+  "powerCompany": "市北",
+  "contractCapacity": 500.0,
+  "creditStatus": 1,
+  "lifecycleStatus": 4,
+  "drNotifyMinutes": 30,
+  "drUpCapacityKw": 200.0,
+  "drDownCapacityKw": 300.0,
+  "province": "上海市",
+  "city": "上海市",
+  "district": "浦东新区",
+  "address": "张江路100号",
+  "businessLicenseUrl": "https://oss.example.com/license.pdf",
+  "remark": null,
+  "contacts": [
+    {
+      "id": 2001,
+      "contactName": "李四",
+      "contactPhone": "13800138000",
+      "contactEmail": "lisi@example.com",
+      "isPrimary": 1,
+      "position": "电气负责人"
+    }
+  ],
+  "contractCount": 2,
+  "resourceCount": 3,
+  "createTime": "2026-06-23T10:00:00"
+}
+```
+
+**响应 data 字段说明**(在列表字段基础上扩展):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| drNotifyMinutes | int | 需求响应提前通知分钟数,默认 30 | dr_notify_minutes |
+| drUpCapacityKw | number | 登记上调能力 kW | dr_up_capacity_kw |
+| drDownCapacityKw | number | 登记下调能力 kW | dr_down_capacity_kw |
+| address | string | 详细地址 | address |
+| businessLicenseUrl | string | 营业执照附件 URL | business_license_url |
+| remark | string/null | 备注 | remark |
+| contacts | array | 联系人列表,元素见 §2.4.7 | vpp_customer_contact |
+| contractCount | int | 关联合同数(聚合) | — |
+| resourceCount | int | 关联资源点数(聚合) | — |
+
+#### 2.4.6 更新客户
+
+**接口地址**:`PUT /prod-api/service-vpp/customer/{id}`
+
+**功能描述**:更新客户档案信息。
+
+**权限**:`customer:edit`
+
+**请求体**(字段均可选,映射 `vpp_customer`):
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| customerName | string | 否 | 客户名称 | customer_name |
+| creditStatus | int | 否 | 1正常 2关注 3不良 | credit_status |
+| lifecycleStatus | int | 否 | 生命周期状态 | lifecycle_status |
+| drNotifyMinutes | int | 否 | 提前通知分钟数 | dr_notify_minutes |
+| drUpCapacityKw | number | 否 | 上调能力 kW | dr_up_capacity_kw |
+| drDownCapacityKw | number | 否 | 下调能力 kW | dr_down_capacity_kw |
+| province | string | 否 | 省 | province |
+| city | string | 否 | 市 | city |
+| district | string | 否 | 区 | district |
+| address | string | 否 | 详细地址 | address |
+| remark | string | 否 | 备注 | remark |
+
+```json
+{
+  "customerName": "上海某某有限公司",
+  "creditStatus": 1,
+  "lifecycleStatus": 4,
+  "drNotifyMinutes": 30,
+  "drUpCapacityKw": 200.0,
+  "drDownCapacityKw": 300.0,
+  "province": "上海市",
+  "city": "上海市",
+  "district": "浦东新区",
+  "address": "张江路100号",
+  "remark": "重点客户"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "更新成功",
+  "data": { "id": 1001 },
+  "exception": null
+}
+```
+
+#### 2.4.7 客户联系人管理
+
+**数据表**:`vpp_customer_contact`
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/customer/{id}/contact` | 联系人列表 |
+| POST | `/prod-api/service-vpp/customer/{id}/contact` | 新增联系人 |
+| PUT | `/prod-api/service-vpp/customer/{id}/contact/{contactId}` | 更新联系人 |
+| DELETE | `/prod-api/service-vpp/customer/{id}/contact/{contactId}` | 删除联系人 |
+
+**新增/更新请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| contactName | string | 是 | 联系人姓名 |
+| contactPhone | string | 是 | 联系电话 |
+| contactEmail | string | 否 | 邮箱 |
+| isPrimary | int | 是 | 是否主联系人:0否 1是 |
+| position | string | 否 | 职务 |
+
+```json
+{
+  "contactName": "李四",
+  "contactPhone": "13800138000",
+  "contactEmail": "lisi@example.com",
+  "isPrimary": 1,
+  "position": "电气负责人"
+}
+```
+
+**列表响应 data 示例**:
+
+```json
+[
+  {
+    "id": 2001,
+    "customerId": 1001,
+    "contactName": "李四",
+    "contactPhone": "13800138000",
+    "contactEmail": "lisi@example.com",
+    "isPrimary": 1,
+    "position": "电气负责人"
+  }
+]
+```
+
+---
+
+### 2.5 合同管理
+
+#### 2.5.1 合同模板列表
+
+**接口地址**:`GET /prod-api/service-vpp/contract/template`
+
+**功能描述**:查询合同模板列表。
+
+**权限**:`contract:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| contractType | int | 否 | 1购售电 2需求响应合作 3聚合代理 4服务代理 5居民充电桩 6自有资产 |
+| isEnabled | int | 否 | 是否启用:0否 1是 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_contract_template`):
+
+```json
+{
+  "id": 10,
+  "templateCode": "DR_COOP_V1",
+  "templateName": "需求响应合作协议模板",
+  "contractType": 2,
+  "version": "1.0",
+  "fileUrl": "https://oss.example.com/template/dr_coop.docx",
+  "variablesJson": { "customerName": "客户名称", "shareRatio": "分成比例" },
+  "isEnabled": 1
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_contract_template`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 模板 ID | id |
+| templateCode | string | 模板编码 | template_code |
+| templateName | string | 模板名称 | template_name |
+| contractType | int | 合同类型 | contract_type |
+| version | string | 版本号 | version |
+| fileUrl | string | 模板文件 URL | file_url |
+| variablesJson | object | 模板变量占位符定义 | variables_json |
+| isEnabled | int | 0禁用 1启用 | is_enabled |
+
+#### 2.5.2 创建合同
+
+**接口地址**:`POST /prod-api/service-vpp/contract`
+
+**功能描述**:创建合同草稿。
+
+**权限**:`contract:create`
+
+**数据表**:`vpp_contract`
+
+**请求体**:
+
+```json
+{
+  "customerId": 1001,
+  "templateId": 10,
+  "contractType": 2,
+  "contractName": "需求响应合作协议",
+  "signDate": "2026-01-15",
+  "effectiveDate": "2026-01-15",
+  "expireDate": "2026-12-31",
+  "shareRatio": 70.0,
+  "priceJson": {},
+  "fileUrl": "https://oss.example.com/contract.pdf"
+}
+```
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| customerId | long | 是 | 客户 ID |
+| templateId | long | 否 | 模板 ID |
+| contractType | int | 是 | 合同类型 |
+| contractName | string | 是 | 合同名称 |
+| signDate | string | 否 | 签订日期 YYYY-MM-DD |
+| effectiveDate | string | 否 | 生效日期 |
+| expireDate | string | 否 | 到期日期 |
+| shareRatio | number | 否 | 分成比例% |
+| priceJson | object | 否 | 电价条款 JSON |
+| accountInfoJson | object | 否 | 分成账户信息 |
+| fileUrl | string | 否 | 合同文件 URL |
+| remark | string | 否 | 备注 |
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "创建成功",
+  "data": {
+    "id": 3001,
+    "contractNo": "CT-202601150001",
+    "contractStatus": 0
+  },
+  "exception": null
+}
+```
+
+> 合同附件除 `fileUrl` 外,建议通过 `POST /prod-api/service-vpp/archive` 写入 `vpp_file_archive`(`bizType=CONTRACT`,`bizId=合同ID`),便于多附件管理。
+
+#### 2.5.3 合同列表
+
+**接口地址**:`GET /prod-api/service-vpp/contract`
+
+**功能描述**:分页查询合同列表。
+
+**权限**:`contract:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| contractNo | string | 否 | 合同编号 |
+| customerName | string | 否 | 客户名称(模糊) |
+| accountNo | string | 否 | 电力户号 |
+| contractStatus | int | 否 | 0草稿 1审核中 2已生效 3已到期 4已终止 |
+| contractType | int | 否 | 合同类型 |
+| signDateStart | string | 否 | 签订日期起 |
+| signDateEnd | string | 否 | 签订日期止 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_contract`):
+
+```json
+{
+  "id": 3001,
+  "contractNo": "CT-202601150001",
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "contractType": 2,
+  "contractName": "需求响应合作协议",
+  "contractStatus": 2,
+  "signDate": "2026-01-15",
+  "effectiveDate": "2026-01-15",
+  "expireDate": "2026-12-31",
+  "shareRatio": 70.0
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_contract`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 合同 ID | id |
+| contractNo | string | 合同编号 | contract_no |
+| customerId | long | 客户 ID | customer_id |
+| customerName | string | 客户名称(关联) | — |
+| contractType | int | 合同类型 | contract_type |
+| contractName | string | 合同名称 | contract_name |
+| contractStatus | int | 0草稿 1审核中 2已生效 3已到期 4已终止 | contract_status |
+| signDate | string | 签订日期 YYYY-MM-DD | sign_date |
+| effectiveDate | string | 生效日期 | effective_date |
+| expireDate | string | 到期日期 | expire_date |
+| shareRatio | number | 分成比例 % | share_ratio |
+
+#### 2.5.4 合同详情
+
+**接口地址**:`GET /prod-api/service-vpp/contract/{id}`
+
+**功能描述**:获取合同详情及审核流水。
+
+**权限**:`contract:view`
+
+**响应 data 示例**:
+
+```json
+{
+  "id": 3001,
+  "contractNo": "CT-202601150001",
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "templateId": 10,
+  "contractType": 2,
+  "contractName": "需求响应合作协议",
+  "contractStatus": 2,
+  "signDate": "2026-01-15",
+  "effectiveDate": "2026-01-15",
+  "expireDate": "2026-12-31",
+  "fileUrl": "https://oss.example.com/contract.pdf",
+  "shareRatio": 70.0,
+  "priceJson": { "peakPrice": 1.2, "valleyPrice": 0.4 },
+  "accountInfoJson": { "bankName": "工商银行", "accountNo": "6222****" },
+  "remark": null,
+  "auditLogs": [
+    {
+      "action": 1,
+      "opinion": "提交审核",
+      "operatorName": "张三",
+      "operatedAt": "2026-01-10T10:00:00"
+    },
+    {
+      "action": 2,
+      "opinion": "审核通过",
+      "operatorName": "李四",
+      "operatedAt": "2026-01-12T14:00:00"
+    }
+  ],
+  "archives": [
+    { "id": 5001, "fileName": "需求响应合作协议.pdf", "fileUrl": "https://oss.example.com/contract.pdf" }
+  ]
+}
+```
+
+**响应 data 字段说明**(在列表字段基础上扩展):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| templateId | long | 模板 ID | template_id |
+| fileUrl | string | 合同文件 URL | file_url |
+| priceJson | object | 电价条款 JSON | price_json |
+| accountInfoJson | object | 分成账户信息 JSON | account_info_json |
+| remark | string/null | 备注 | remark |
+| auditLogs | array | 审核流水 | vpp_contract_audit_log |
+| auditLogs[].action | int | 1提交 2通过 3驳回 4归档 | action |
+| auditLogs[].opinion | string | 审核意见 | opinion |
+| auditLogs[].operatorName | string | 操作人姓名 | created_by |
+| auditLogs[].operatedAt | string | 操作时间 | create_time |
+| archives | array | 电子档案列表 | vpp_file_archive |
+
+#### 2.5.5 提交合同审核
+
+**接口地址**:`POST /prod-api/service-vpp/contract/{id}/submit`
+
+**功能描述**:将草稿合同提交审核,`contractStatus` 变更为 1。
+
+**权限**:`contract:submit`
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "提交成功",
+  "data": { "id": 3001, "contractStatus": 1 },
+  "exception": null
+}
+```
+
+#### 2.5.6 合同审核
+
+**接口地址**:`PUT /prod-api/service-vpp/contract/{id}/audit`
+
+**功能描述**:审核合同,写入 `vpp_contract_audit_log`。
+
+**权限**:`contract:audit`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| passed | boolean | 是 | true 通过 / false 驳回 |
+| opinion | string | 否 | 审核意见 |
+
+```json
+{
+  "passed": true,
+  "opinion": "审核通过"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "审核通过",
+  "data": { "id": 3001, "contractStatus": 2 },
+  "exception": null
+}
+```
+
+#### 2.5.7 合同归档
+
+**接口地址**:`POST /prod-api/service-vpp/contract/{id}/archive`
+
+**功能描述**:合同归档,写入审核流水(action=4)。
+
+**权限**:`contract:archive`
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "归档成功",
+  "data": { "id": 3001 },
+  "exception": null
+}
+```
+
+---
+
+### 2.6 资源管理
+
+#### 2.6.1 资源点列表
+
+**接口地址**:`GET /prod-api/service-vpp/resource`
+
+**功能描述**:分页查询资源点列表。
+
+**权限**:`resource:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceName | string | 否 | 资源名称(模糊) |
+| resourceCode | string | 否 | 资源编号 |
+| resourceType | string | 否 | PV/ESS/EVCS/IND_LOAD/COM_BLDG |
+| district | string | 否 | 区/区域 |
+| runStatus | int | 否 | 0离线 1在线 2故障 3维护 |
+| customerId | long | 否 | 所属客户 ID |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_resource_point`):
+
+```json
+{
+  "id": 2001,
+  "resourceCode": "RES-PV-001",
+  "resourceName": "浦东光伏站",
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "resourceType": "PV",
+  "capacityKw": 500.0,
+  "adjustableKw": 0,
+  "district": "浦东新区",
+  "runStatus": 1,
+  "responsePriority": 5,
+  "createTime": "2026-01-20T10:00:00"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_resource_point`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 资源点 ID | id |
+| resourceCode | string | 资源编号,全局唯一 | resource_code |
+| resourceName | string | 资源名称 | resource_name |
+| customerId | long | 所属客户 ID | customer_id |
+| customerName | string | 客户名称(关联查询) | — |
+| resourceType | string | PV/ESS/EVCS/IND_LOAD/COM_BLDG | resource_type |
+| capacityKw | number | 装机容量 kW | capacity_kw |
+| adjustableKw | number | 可调容量 kW | adjustable_kw |
+| district | string | 区/区域 | district |
+| runStatus | int | 0离线 1在线 2故障 3维护 | run_status |
+| responsePriority | int | 响应优先级 1-10 | response_priority |
+| createTime | string | 创建时间 | create_time |
+
+#### 2.6.2 资源看板(实时)
+
+**接口地址**:`GET /prod-api/service-vpp/resource/board`
+
+**功能描述**:获取资源点实时运行看板数据(Redis 缓存 + TDengine)。
+
+**权限**:`resource:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceType | string | 否 | 资源类型 |
+| district | string | 否 | 区/区域 |
+| runStatus | int | 否 | 运行状态 |
+| customerId | long | 否 | 客户 ID |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "resourceId": 2001,
+  "resourceCode": "RES-PV-001",
+  "resourceName": "浦东光伏站",
+  "resourceType": "PV",
+  "runStatus": 1,
+  "activePowerKw": 320.5,
+  "voltage": 380.0,
+  "current": 520.3,
+  "soc": null,
+  "evChargePowerKw": null,
+  "abnormal": false
+}
+```
+
+**响应 data.records 字段说明**:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| resourceId | long | 资源点 ID |
+| resourceCode | string | 资源编号 |
+| resourceName | string | 资源名称 |
+| resourceType | string | 资源类型 |
+| runStatus | int | 运行状态 |
+| activePowerKw | number | 当前有功功率 kW(实时) |
+| voltage | number | 电压 V |
+| current | number | 电流 A |
+| soc | number/null | 储能 SOC%,非储能资源为 null |
+| evChargePowerKw | number/null | 充电桩充电功率 kW |
+| abnormal | boolean | 是否异常(超阈值/离线超时等) |
+
+#### 2.6.3 新增资源点
+
+**接口地址**:`POST /prod-api/service-vpp/resource`
+
+**功能描述**:新增资源点及运行参数配置。
+
+**权限**:`resource:create`
+
+**数据表**:`vpp_resource_point` + `vpp_resource_point_config`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| resourceCode | string | 是 | 资源编号 | resource_code |
+| resourceName | string | 是 | 资源名称 | resource_name |
+| customerId | long | 是 | 所属客户 ID | customer_id |
+| resourceType | string | 是 | PV/ESS/EVCS/IND_LOAD/COM_BLDG | resource_type |
+| capacityKw | number | 是 | 装机容量 kW | capacity_kw |
+| adjustableKw | number | 否 | 可调容量 kW | adjustable_kw |
+| province | string | 否 | 省 | province |
+| city | string | 否 | 市 | city |
+| district | string | 否 | 区/区域 | district |
+| address | string | 否 | 地址 | address |
+| longitude | number | 否 | 经度 | longitude |
+| latitude | number | 否 | 纬度 | latitude |
+| ownerName | string | 否 | 业主名称 | owner_name |
+| contactName | string | 否 | 联系人 | contact_name |
+| contactPhone | string | 否 | 联系电话 | contact_phone |
+| responsePriority | int | 否 | 响应优先级 1-10 | response_priority |
+| config | object | 否 | 运行参数配置,见下表 | vpp_resource_point_config |
+| config.collectIntervalSec | int | 否 | 采集间隔(秒) | collect_interval_sec |
+| config.powerUpperLimit | number | 否 | 功率上限 kW | power_upper_limit |
+| config.powerLowerLimit | number | 否 | 功率下限 kW | power_lower_limit |
+| config.socUpperLimit | number | 否 | SOC 上限 % | soc_upper_limit |
+| config.socLowerLimit | number | 否 | SOC 下限 % | soc_lower_limit |
+| config.offlineTimeoutSec | int | 否 | 离线判定超时(秒) | offline_timeout_sec |
+
+```json
+{
+  "resourceCode": "RES-PV-001",
+  "resourceName": "浦东光伏站",
+  "customerId": 1001,
+  "resourceType": "PV",
+  "capacityKw": 500.0,
+  "adjustableKw": 0,
+  "province": "上海市",
+  "city": "上海市",
+  "district": "浦东新区",
+  "address": "张江路100号",
+  "longitude": 121.544379,
+  "latitude": 31.221517,
+  "ownerName": "上海某某有限公司",
+  "contactName": "李四",
+  "contactPhone": "13800138000",
+  "responsePriority": 5,
+  "config": {
+    "collectIntervalSec": 900,
+    "powerUpperLimit": 550.0,
+    "offlineTimeoutSec": 300
+  }
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "创建成功",
+  "data": { "id": 2001, "resourceCode": "RES-PV-001" },
+  "exception": null
+}
+```
+
+#### 2.6.4 更新/删除资源点
+
+**GET 资源详情** — `GET /prod-api/service-vpp/resource/{id}`
+
+**响应 data 示例**(`vpp_resource_point` + `vpp_resource_point_config`):
+
+```json
+{
+  "id": 2001,
+  "resourceCode": "RES-PV-001",
+  "resourceName": "浦东光伏站",
+  "customerId": 1001,
+  "resourceType": "PV",
+  "capacityKw": 500.0,
+  "adjustableKw": 0,
+  "province": "上海市",
+  "city": "上海市",
+  "district": "浦东新区",
+  "address": "张江路100号",
+  "longitude": 121.544379,
+  "latitude": 31.221517,
+  "ownerName": "上海某某有限公司",
+  "contactName": "李四",
+  "contactPhone": "13800138000",
+  "runStatus": 1,
+  "responsePriority": 5,
+  "unResourceId": null,
+  "config": {
+    "collectIntervalSec": 900,
+    "powerUpperLimit": 550.0,
+    "powerLowerLimit": null,
+    "socUpperLimit": null,
+    "socLowerLimit": null,
+    "offlineTimeoutSec": 300
+  },
+  "devices": [
+    { "id": 3001, "deviceCode": "DEV-INV-001", "deviceName": "1#逆变器", "commStatus": 1 }
+  ]
+}
+```
+
+**响应 data 字段说明**(映射 `vpp_resource_point` + `vpp_resource_point_config`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 资源点 ID | id |
+| resourceCode | string | 资源编号 | resource_code |
+| resourceName | string | 资源名称 | resource_name |
+| customerId | long | 所属客户 ID | customer_id |
+| resourceType | string | 资源类型 | resource_type |
+| capacityKw | number | 装机容量 kW | capacity_kw |
+| adjustableKw | number | 可调容量 kW | adjustable_kw |
+| province/city/district/address | string | 地址信息 | province 等 |
+| longitude/latitude | number | 经纬度 | longitude/latitude |
+| ownerName | string | 业主名称 | owner_name |
+| contactName/contactPhone | string | 联系人信息 | contact_name/phone |
+| runStatus | int | 运行状态 | run_status |
+| responsePriority | int | 响应优先级 | response_priority |
+| unResourceId | string/null | 运管平台分路资源 ID | un_resource_id |
+| config | object | 运行参数配置 | vpp_resource_point_config |
+| devices | array | 关联设备列表,元素见 §2.6.5 | vpp_device |
+
+**PUT 更新资源** — `PUT /prod-api/service-vpp/resource/{id}`
+
+请求体字段同新增,均可选。
+
+**DELETE 软删除** — `DELETE /prod-api/service-vpp/resource/{id}`
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "删除成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.6.5 设备管理
+
+**数据表**:`vpp_device`(关联 `dmp_device`)
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/resource/{resourceId}/device` | 设备列表 |
+| POST | `/prod-api/service-vpp/resource/{resourceId}/device` | 新增设备 |
+| PUT | `/prod-api/service-vpp/device/{id}` | 更新设备 |
+| DELETE | `/prod-api/service-vpp/device/{id}` | 软删除 |
+
+**新增/更新请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| deviceCode | string | 是 | 设备编号(与 dmp_device.device_id 一致) |
+| deviceName | string | 是 | 设备名称 |
+| deviceType | string | 是 | INVERTER/PCS/EVCS/METER/Gateway 等 |
+| manufacturer | string | 否 | 厂商 |
+| model | string | 否 | 型号 |
+| ratedPowerKw | number | 否 | 额定功率 kW |
+| gatewayId | string | 否 | IoT 网关标识 |
+| registerDmp | boolean | 否 | 是否同步注册 dmp_device |
+| remark | string | 否 | 备注 |
+
+```json
+{
+  "deviceCode": "DEV-INV-001",
+  "deviceName": "1#逆变器",
+  "deviceType": "INVERTER",
+  "manufacturer": "华为",
+  "model": "SUN2000-50KTL",
+  "ratedPowerKw": 50.0,
+  "registerDmp": true
+}
+```
+
+**设备列表响应 data.records 元素**:
+
+```json
+{
+  "id": 3001,
+  "deviceCode": "DEV-INV-001",
+  "deviceName": "1#逆变器",
+  "resourceId": 2001,
+  "deviceType": "INVERTER",
+  "manufacturer": "华为",
+  "model": "SUN2000-50KTL",
+  "ratedPowerKw": 50.0,
+  "commStatus": 1,
+  "runStatus": 1,
+  "firmwareVersion": "V100R001",
+  "lastOnlineAt": "2026-06-23T10:15:00",
+  "dmpDeviceId": "DEV-INV-001",
+  "dmpDeviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
+}
+```
+
+**设备对象关键字段**:
+
+| 字段 | 数据表 | 说明 |
+|------|--------|------|
+| deviceCode | vpp_device.device_code | 业务设备编码 |
+| dmpDeviceId | dmp_device.device_id | 物联网设备 ID(与 deviceCode 通常一致) |
+| dmpDeviceUuid | dmp_device.device_uuid | 物联网 UUID |
+| commStatus | dmp_device_status.device_status | 1 在线 / 2 离线 |
+
+#### 2.6.6 可调能力评估
+
+> **界面映射**:Web「可调能力评估」页(站点筛选、顶部汇总卡片、可调能力曲线、历史响应)。  
+> **数据表**:`vpp_site`、`vpp_resource_point`、`vpp_device`、`vpp_dr_participation`、`vpp_dr_event`、`vpp_dr_evaluation`  
+> **时序数据**:TDengine `device_metrics`(经 `service-tsdb` Feign 查询,子表 `_{device_uuid}`)
+
+**模块前缀**:`/prod-api/service-vpp/capability-eval`
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/capability-eval/summary` | 顶部汇总统计 |
+| GET | `/prod-api/service-vpp/capability-eval/load-curve` | 可调能力曲线(基线 vs 实际负荷) |
+| GET | `/prod-api/service-vpp/capability-eval/history` | 历史响应记录(分页) |
+
+**站点下拉**:复用 `GET /prod-api/service-vpp/site`(§2.6 站点管理)。
+
+**权限**:`resource:view`
+
+##### 2.6.6.1 顶部汇总统计
+
+**接口地址**:`GET /prod-api/service-vpp/capability-eval/summary`
+
+**功能描述**:按站点(或全部站点)统计总资源/负荷类/储能类/发电类的户数与装机容量,并返回最大上调、最大下调能力。
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| siteId | long | 否 | 站点 ID;不传则统计全部站点 |
+
+**分类规则**:
+
+| category | 说明 | resource_type 映射 |
+|----------|------|------------------|
+| TOTAL | 总资源 | 全部 |
+| LOAD | 负荷类 | IND_LOAD、COM_BLDG、EVCS |
+| STORAGE | 储能类 | ESS |
+| GENERATION | 发电类 | PV |
+
+**最大上下调能力**:
+
+- 优先汇总 TDengine 最新 `up_capacity_kw` / `down_capacity_kw`(在线设备,`vpp_device.device_uuid` 关联子表)
+- 无时序数据时回退 `vpp_resource_point.max_up_kw` / `min_down_kw`(`is_control=1`)
+
+**响应 data 示例**:
+
+```json
+{
+  "siteId": 1,
+  "siteName": "示范站点",
+  "total": {
+    "category": "TOTAL",
+    "categoryLabel": "总资源",
+    "customerCount": 3,
+    "capacityKw": 5500.0
+  },
+  "load": {
+    "category": "LOAD",
+    "categoryLabel": "负荷类",
+    "customerCount": 3,
+    "capacityKw": 3300.0
+  },
+  "storage": {
+    "category": "STORAGE",
+    "categoryLabel": "储能类",
+    "customerCount": 1,
+    "capacityKw": 1100.0
+  },
+  "generation": {
+    "category": "GENERATION",
+    "categoryLabel": "发电类",
+    "customerCount": 1,
+    "capacityKw": 1100.0
+  },
+  "maxUpKw": 120.0,
+  "maxDownKw": 110.0
+}
+```
+
+**响应 data 字段说明**:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| siteId | long | 站点 ID(全部站点时为 null) |
+| siteName | string | 站点名称;全部站点时为「全部站点」 |
+| total / load / storage / generation | object | 分类统计对象 |
+| *.category | string | TOTAL / LOAD / STORAGE / GENERATION |
+| *.categoryLabel | string | 分类中文名 |
+| *.customerCount | int | 户数(站点关联客户去重) |
+| *.capacityKw | number | 装机容量 kW(`capacity_kw` 汇总) |
+| maxUpKw | number | 最大上调能力 kW |
+| maxDownKw | number | 最大下调能力 kW |
+
+##### 2.6.6.2 可调能力曲线
+
+**接口地址**:`GET /prod-api/service-vpp/capability-eval/load-curve`
+
+**功能描述**:返回指定日期的负荷曲线,用于「可调能力曲线」Tab。每 10 分钟一个点(`00:00` ~ `23:50`),对比基线负荷与实际负荷。
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| siteId | long | 否 | 站点 ID;不传则聚合全部站点资源 |
+| date | string | 否 | 统计日期 `yyyy-MM-dd`,默认当天 |
+
+**数据来源(dataSource)**:
+
+| 值 | 说明 |
+|----|------|
+| tsdb | 从 TDengine 查询实测有功功率并聚合 |
+| mock | 无 TSDB 数据时使用模拟曲线(联调兜底) |
+
+**TSDB 查询说明**:
+
+- 经 `service-tsdb` 的 `RemoteTsdbProxyService.queryHistoryDeviceData` 批量查询
+- 设备标识:`vpp_device.device_uuid`,子表 `_{device_uuid}`
+- 有功功率指标匹配顺序:`active_power` → `totalActivePower` → `totalactivepower` → `activepowera`
+- **实际负荷**:统计日各 10 分钟桶内多设备功率均值之和
+- **基线负荷**:7 日前同时段功率作为参考基线;若基线无数据则与实际负荷一致
+
+**响应 data 示例**:
+
+```json
+{
+  "siteId": 1,
+  "siteName": "示范站点",
+  "date": "2026-07-07",
+  "dataSource": "tsdb",
+  "points": [
+    {
+      "time": "11:40",
+      "baselineLoadKw": 907.57,
+      "actualLoadKw": 967.65
+    },
+    {
+      "time": "11:50",
+      "baselineLoadKw": 912.30,
+      "actualLoadKw": 970.12
+    }
+  ]
+}
+```
+
+**响应 data 字段说明**:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| siteId | long | 站点 ID |
+| siteName | string | 站点名称 |
+| date | string | 统计日期 yyyy-MM-dd |
+| dataSource | string | tsdb / mock |
+| points | array | 曲线数据点 |
+| points[].time | string | 时刻 HH:mm |
+| points[].baselineLoadKw | number | 基线负荷 kW |
+| points[].actualLoadKw | number | 实际负荷 kW |
+
+##### 2.6.6.3 历史响应记录
+
+**接口地址**:`GET /prod-api/service-vpp/capability-eval/history`
+
+**功能描述**:分页查询站点下资源曾参与的需求响应事件,用于「历史响应」Tab,按事件开始时间倒序。
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| siteId | long | 否 | 站点 ID;不传则查全部站点资源参与记录 |
+| current | int | 否 | 页码,默认 1 |
+| size | int | 否 | 每页数量,默认 10,最大 100 |
+
+**响应 data.records 示例**:
+
+```json
+{
+  "records": [
+    {
+      "eventId": 1,
+      "eventCode": "EVT_20260707_001",
+      "eventName": "削峰响应",
+      "eventType": 1,
+      "eventTypeLabel": "削峰",
+      "responseType": 1,
+      "responseTypeLabel": "日前",
+      "startTime": "2026-07-07T14:00:00",
+      "endTime": "2026-07-07T16:00:00",
+      "targetCapacityKw": 500.0,
+      "actualCapacityKw": 480.0,
+      "qualifiedRate": 96.0
+    }
+  ],
+  "total": 1,
+  "current": 1,
+  "size": 10
+}
+```
+
+**响应 data.records 字段说明**:
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| eventId | long | 事件 ID | vpp_dr_event.id |
+| eventCode | string | 事件编码 | vpp_dr_event.event_id |
+| eventName | string | 事件名称 | vpp_dr_event.event_name |
+| eventType | int | 1削峰 2填谷 | vpp_dr_event.event_type |
+| eventTypeLabel | string | 事件类型中文 | — |
+| responseType | int | 1日前 2日内 3秒级 | vpp_dr_event.response_type |
+| responseTypeLabel | string | 响应类型中文 | — |
+| startTime | string | 响应开始时间 | vpp_dr_event.start_time |
+| endTime | string | 响应结束时间 | vpp_dr_event.end_time |
+| targetCapacityKw | number | 目标/出清容量 kW | cleared_capacity_kw 或 target_capacity_kw |
+| actualCapacityKw | number | 实际响应容量 kW | vpp_dr_evaluation.actual_capacity_kw |
+| qualifiedRate | number | 达标率 % | vpp_dr_evaluation.qualified_rate |
+
+> **与 §2.9.3 的区别**:§2.9.3 为**单个 DR 事件**的能力评估(参与前判断);本节为**运营看板级**可调能力评估(按站点汇总资源分类、日负荷曲线与历史参与记录)。
+
+---
+
+### 2.7 运行监控
+
+#### 2.7.1 实时监控数据
+
+**接口地址**:`GET /prod-api/service-vpp/monitor/realtime`
+
+**功能描述**:获取资源点/设备实时监控数据。
+
+**权限**:`monitor:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceId | long | 否 | 资源点 ID(与 deviceId/customerId 三选一或组合) |
+| deviceId | long | 否 | 设备 ID |
+| customerId | long | 否 | 客户 ID,返回该客户下所有资源实时数据 |
+
+**响应 data**:
+
+```json
+{
+  "resourceId": 2001,
+  "devices": [
+    {
+      "deviceId": 3001,
+      "deviceCode": "DEV-INV-001",
+      "commStatus": 1,
+      "runStatus": 1,
+      "activePowerKw": 320.5,
+      "voltage": 380.0,
+      "current": 520.3,
+      "soc": null,
+      "lastOnlineAt": "2026-06-23T10:15:00.000+08:00"
+    }
+  ]
+}
+```
+
+#### 2.7.2 历史数据查询
+
+**接口地址**:`GET /prod-api/service-vpp/monitor/history`
+
+**功能描述**:查询设备/资源历史运行数据(TDengine)。
+
+**权限**:`monitor:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceIds | string | 否 | 资源点 ID,逗号分隔 |
+| deviceIds | string | 否 | 设备 ID,逗号分隔 |
+| metrics | string | 是 | 指标名,逗号分隔,如 activePowerKw,voltage,soc |
+| startTime | string | 是 | 开始时间 ISO 8601 |
+| endTime | string | 是 | 结束时间 ISO 8601 |
+| granularity | string | 否 | 粒度:15m/1h/1d,默认 15m |
+
+**响应 data 示例**:
+
+```json
+{
+  "deviceId": 3001,
+  "deviceCode": "DEV-INV-001",
+  "metric": "activePowerKw",
+  "granularity": "15m",
+  "points": [
+    { "time": "2026-06-23T08:00:00", "value": 120.5 },
+    { "time": "2026-06-23T08:15:00", "value": 135.2 },
+    { "time": "2026-06-23T08:30:00", "value": 148.0 }
+  ]
+}
+```
+
+#### 2.7.3 设备远程控制
+
+**接口地址**:`POST /prod-api/service-vpp/device/{id}/control`
+
+**功能描述**:向设备下发远程控制指令。
+
+**权限**:`device:control`
+
+**路径参数**:`id` — 设备 ID(`vpp_device.id`)
+
+**请求体**:
+
+```json
+{
+  "controlType": "START",
+  "params": {
+    "targetPowerKw": 100
+  }
+}
+```
+
+**controlType 枚举**:`START`、`STOP`、`CONFIG`、`OTA`
+
+**落库说明**:
+
+- 命令下发记录写入 `dmp_device_command`(`device_uuid`、`command_content`、`command_status` 等)。
+- 业务侧摘要写入 `vpp_device_control_log`,可通过 `dmpCommandId` 关联物联网命令记录。
+- 设备在线状态以 `dmp_device_status` 为准(`device_status`:1 在线、2 离线)。
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "指令已下发",
+  "data": {
+    "controlLogId": 8001,
+    "dmpCommandId": "CMD-20260623001",
+    "controlResult": 2
+  },
+  "exception": null
+}
+```
+
+| 响应字段 | 说明 |
+|----------|------|
+| controlLogId | vpp_device_control_log.id |
+| dmpCommandId | dmp_device_command 命令 ID |
+| controlResult | 0失败 1成功 2执行中 |
+
+#### 2.7.4 控制日志
+
+**接口地址**:`GET /prod-api/service-vpp/device/{id}/controlLog`
+
+**功能描述**:查询设备远程控制历史记录。
+
+**权限**:`device:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| controlType | string | 否 | START/STOP/CONFIG/OTA |
+| controlResult | int | 否 | 0失败 1成功 2执行中 |
+| startTime | string | 否 | 操作开始时间 |
+| endTime | string | 否 | 操作结束时间 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_device_control_log`):
+
+```json
+{
+  "id": 8001,
+  "deviceId": 3001,
+  "controlType": "START",
+  "controlParams": { "targetPowerKw": 100 },
+  "controlResult": 1,
+  "resultMessage": "执行成功",
+  "createdBy": "operator01",
+  "createTime": "2026-06-23T10:20:00",
+  "dmpCommandId": "CMD-20260623001"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_device_control_log`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 控制日志 ID | id |
+| deviceId | long | 设备 ID | device_id |
+| controlType | string | START/STOP/CONFIG/OTA | control_type |
+| controlParams | object | 控制参数 JSON | control_params |
+| controlResult | int | 0失败 1成功 2执行中 | control_result |
+| resultMessage | string | 执行结果说明 | result_message |
+| createdBy | string | 操作人账号 | created_by |
+| createTime | string | 操作时间 | create_time |
+| dmpCommandId | string | 物联网命令 ID(关联 dmp_device_command) | dmp_command_id |
+
+---
+
+### 2.8 告警管理
+
+> **数据表**:规则 → `rule_engine*`;告警记录 → `base_alarm`;类型 → `base_alarm_type`;通知 → `base_alarm_notice` / `base_alarm_notice_result`;统计 → `base_alarm_statistic`。
+
+#### 2.8.1 告警规则 CRUD
+
+| 方法 | URL | 说明 | 数据表 |
+|------|-----|------|--------|
+| GET | `/prod-api/service-vpp/alarm/rule` | 规则列表 | `rule_engine` |
+| POST | `/prod-api/service-vpp/alarm/rule` | 新增规则 | `rule_engine` + 关联表 |
+| GET | `/prod-api/service-vpp/alarm/rule/{id}` | 规则详情 | `rule_engine` |
+| PUT | `/prod-api/service-vpp/alarm/rule/{id}` | 更新规则 | `rule_engine` |
+| DELETE | `/prod-api/service-vpp/alarm/rule/{id}` | 删除规则 | `rule_engine` |
+| GET | `/prod-api/service-vpp/alarm/rule/{id}/device` | 规则绑定设备 | `rule_engine_device` |
+| PUT | `/prod-api/service-vpp/alarm/rule/{id}/device` | 更新绑定设备 | `rule_engine_device` |
+| GET | `/prod-api/service-vpp/alarm/rule/{id}/log` | 规则执行日志 | `rule_engine_log` |
+
+**查询参数(列表)**:`name`、`status`(0 停用 / 1 启用)、`tenantId`、`current`、`size`
+
+**创建/更新规则请求体示例**(映射 `rule_engine` + `rule_engine_device`):
+
+```json
+{
+  "name": "功率超限110%",
+  "status": 1,
+  "descr": "光伏功率超过额定110%触发告警",
+  "detail": {
+    "metric": "activePowerKw",
+    "operator": ">",
+    "thresholdRatio": 1.1,
+    "action": "alarmEvent",
+    "alarmType": "PWR_OVER",
+    "alarmGrade": 2
+  },
+  "devices": [
+    {
+      "deviceId": "DEV-INV-001",
+      "deviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+      "identifier": "activePower",
+      "productId": 100
+    }
+  ],
+  "conditions": [
+    {
+      "optionalCondition": "AND",
+      "expression": "activePowerKw > ratedPower * 1.1",
+      "descr": "功率超限",
+      "type": 1
+    }
+  ],
+  "cronExpressions": ["0 */5 * * * ?"],
+  "tenantId": 1
+}
+```
+
+| 请求字段 | 数据表字段 | 说明 |
+|----------|------------|------|
+| name | rule_engine.name | 规则名称(≤20 字符) |
+| status | rule_engine.status | 0 停用 / 1 启用 |
+| descr | rule_engine.descr | 规则描述 |
+| detail | rule_engine.detail | JSON,含阈值、动作;`action=alarmEvent` 时写入 `base_alarm` |
+| devices[] | rule_engine_device | deviceId 对应 `vpp_device.device_code` |
+| conditions[] | rule_engine_condition | type:1 触发条件 / 2 约束条件 |
+| cronExpressions[] | rule_engine_cron.cron | CRON 表达式 |
+
+**规则详情响应 data 示例**:
+
+```json
+{
+  "id": 12,
+  "name": "功率超限110%",
+  "status": 1,
+  "descr": "光伏功率超过额定110%触发告警",
+  "detail": { "action": "alarmEvent", "alarmGrade": 2 },
+  "devices": [],
+  "conditions": [],
+  "cronExpressions": ["0 */5 * * * ?"],
+  "tenantId": 1,
+  "createTime": "2026-06-01T10:00:00.000+08:00",
+  "updateTime": "2026-06-20T15:30:00.000+08:00"
+}
+```
+
+**规则列表响应 data.records 元素**:
+
+```json
+{
+  "id": 12,
+  "name": "功率超限110%",
+  "status": 1,
+  "descr": "光伏功率超过额定110%触发告警",
+  "deviceCount": 5,
+  "tenantId": 1,
+  "createTime": "2026-06-01T10:00:00.000+08:00"
+}
+```
+
+**更新绑定设备** — `PUT /alarm/rule/{id}/device`
+
+**请求体**:
+
+```json
+{
+  "devices": [
+    {
+      "deviceId": "DEV-INV-001",
+      "deviceUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+      "identifier": "activePower",
+      "productId": 100
+    }
+  ]
+}
+```
+
+**规则执行日志** — `GET /alarm/rule/{id}/log`
+
+**查询参数**:`startTime`、`endTime`、`current`、`size`
+
+**响应 data.records 元素**(映射 `rule_engine_log`):
+
+```json
+{
+  "id": 501,
+  "ruleId": 12,
+  "executeTime": "2026-06-23T10:15:00.000+08:00",
+  "executeResult": 1,
+  "executeMessage": "规则命中,已触发 alarmEvent",
+  "alarmId": 158001
+}
+```
+
+#### 2.8.2 告警类型字典
+
+**接口地址**:`GET /prod-api/service-vpp/alarm/type`
+
+**功能描述**:获取告警类型字典列表。
+
+**权限**:`alarm:view`
+
+**数据表**:`base_alarm_type`
+
+**请求参数**:无
+
+**响应 data.records 元素**:
+
+```json
+{
+  "id": 1,
+  "typeCode": "PWR_OVER",
+  "typeName": "功率越限",
+  "typeAbbrevia": "越限",
+  "alarmDesc": "设备运行功率超过设定阈值"
+}
+```
+
+#### 2.8.3 告警列表
+
+**接口地址**:`GET /prod-api/service-vpp/alarm`
+
+**功能描述**:分页查询告警记录列表。
+
+**权限**:`alarm:view`
+
+**数据表**:`base_alarm`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| handleStatus | int | 否 | 0未处理 1已处理 |
+| alarmGrade | int | 否 | 告警等级 1-5 |
+| alarmType | string | 否 | 告警类型编码 |
+| deviceId | string | 否 | 设备编号 |
+| alarmObject | string | 否 | 告警对象(模糊) |
+| alarmTimeStart | string | 否 | 告警时间起 |
+| alarmTimeEnd | string | 否 | 告警时间止 |
+| tenantId | long | 否 | 租户 ID |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `base_alarm`):
+
+```json
+{
+  "id": 158001,
+  "deviceId": "DEV-INV-001",
+  "alarmTime": "2026-06-23T10:15:00.000+08:00",
+  "alarmType": "PWR_OVER",
+  "alarmObject": "浦东光伏站",
+  "alarmData": "550.2",
+  "alarmAttribute": "activePowerKw",
+  "alarmContent": "功率超限110%",
+  "alarmGrade": 2,
+  "alarmAddress": "上海市浦东新区张江路100号",
+  "handleStatus": 0,
+  "handleBy": null,
+  "handleTime": null,
+  "handleContent": null,
+  "alarmFalse": 0,
+  "sitePhoto": null,
+  "tenantId": 1
+}
+```
+
+#### 2.8.4 告警处理(确认/派单/处理合一)
+
+`base_alarm` 以 `handle_status`、`handle_by`、`handle_time`、`handle_content` 承载闭环,以下接口为业务语义封装。
+
+##### 2.8.4.1 确认告警
+
+**接口地址**:`POST /prod-api/service-vpp/alarm/{id}/confirm`
+
+**功能描述**:确认已知晓告警,可选记录操作人到 `sys_oper_log`。
+
+**权限**:`alarm:manage`
+
+**路径参数**:`id` — 告警记录 ID(`base_alarm.id`)
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "确认成功",
+  "data": { "id": 158001 },
+  "exception": null
+}
+```
+
+##### 2.8.4.2 派单
+
+**接口地址**:`POST /prod-api/service-vpp/alarm/{id}/dispatch`
+
+**功能描述**:指派告警处理人,预填 `handle_by` 信息。
+
+**权限**:`alarm:manage`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| handlerName | string | 是 | 处理人姓名 |
+| handlerPhone | string | 是 | 处理人电话 |
+
+```json
+{
+  "handlerName": "李四",
+  "handlerPhone": "13800138000"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "派单成功",
+  "data": { "id": 158001, "handleBy": "李四" },
+  "exception": null
+}
+```
+
+##### 2.8.4.3 处理告警
+
+**接口地址**:`POST /prod-api/service-vpp/alarm/{id}/handle`
+
+**功能描述**:完成告警处理闭环,更新 `base_alarm.handle_*` 字段。
+
+**权限**:`alarm:manage`
+
+**落库字段**:`handle_status=1`、`handle_by`、`handle_time`、`handle_content`、`site_photo`
+
+**处理请求体**:
+
+```json
+{
+  "handleContent": "已远程重启设备,恢复正常",
+  "handleBy": "张三",
+  "handlePhone": "13900139000",
+  "sitePhoto": "https://oss.example.com/photo.jpg"
+}
+```
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| handleContent | string | 是 | 处理说明 |
+| handleBy | string | 是 | 处理人姓名 |
+| handlePhone | string | 否 | 处理人电话 |
+| sitePhoto | string | 否 | 现场照片 URL |
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "处理完成",
+  "data": {
+    "id": 158001,
+    "handleStatus": 1,
+    "handleBy": "张三",
+    "handleTime": "2026-06-23T10:30:00.000+08:00"
+  },
+  "exception": null
+}
+```
+
+#### 2.8.5 误报关闭
+
+**接口地址**:`POST /prod-api/service-vpp/alarm/{id}/closeFalse`
+
+**功能描述**:将告警标记为误报并关闭。
+
+**权限**:`alarm:manage`
+
+**路径参数**:`id` — 告警记录 ID
+
+**请求参数**:无
+
+**落库**:`base_alarm.alarm_false = 1`,`handle_status = 1`
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "已关闭",
+  "data": { "id": 158001, "alarmFalse": 1, "handleStatus": 1 },
+  "exception": null
+}
+```
+
+#### 2.8.6 告警通知配置
+
+**数据表**:`base_alarm_notice`(配置)、`base_alarm_notice_result`(推送结果)
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/alarm/notice` | 通知配置列表 |
+| POST | `/prod-api/service-vpp/alarm/notice` | 新增通知配置 |
+| PUT | `/prod-api/service-vpp/alarm/notice/{id}` | 更新通知配置 |
+
+**列表查询参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| productCode | string | 否 | 产品编码,VPP 固定为 VPP |
+| typeCode | string | 否 | 告警类型编码 |
+| tenantId | long | 否 | 租户 ID |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**列表响应 data.records 元素**:
+
+```json
+{
+  "id": 101,
+  "productCode": "VPP",
+  "typeCode": "PWR_OVER",
+  "typeName": "功率越限",
+  "receiver1": "1001,1002",
+  "receiver2": "1003",
+  "timeOut1": 30,
+  "timeOut2": 60,
+  "noticeMethod": "1,2,4",
+  "tenantId": 1
+}
+```
+
+**创建通知配置请求体示例**:
+
+```json
+{
+  "productCode": "VPP",
+  "typeCode": "PWR_OVER",
+  "receiver1": "1001,1002",
+  "receiver2": "1003",
+  "timeOut1": 30,
+  "timeOut2": 60,
+  "noticeMethod": "1,2,4",
+  "tenantId": 1
+}
+```
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| productCode | string | 是 | 产品编码 |
+| typeCode | string | 是 | 告警类型编码 |
+| receiver1 | string | 是 | 一级接收人 userId,逗号分隔 |
+| receiver2 | string | 否 | 二级接收人 |
+| receiver3 | string | 否 | 三级接收人 |
+| timeOut1 | int | 否 | 一级超时升级(分钟) |
+| timeOut2 | int | 否 | 二级超时升级(分钟) |
+| noticeMethod | string | 是 | 通知方式,逗号分隔 |
+| tenantId | long | 否 | 租户 ID |
+
+> `noticeMethod`:1 站内 / 2 微信 / 3 语音 / 4 短信 / 5 邮件(与 `base_alarm_notice_result.notice_type` 一致)。
+
+**创建/更新响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "保存成功",
+  "data": { "id": 101 },
+  "exception": null
+}
+```
+
+#### 2.8.7 告警统计
+
+**接口地址**:`GET /prod-api/service-vpp/alarm/statistics`
+
+**功能描述**:按日/月维度统计告警数量。
+
+**权限**:`alarm:view`
+
+**数据表**:`base_alarm_statistic`(汇总)或 `base_alarm`(实时聚合)
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| startDate | string | 是 | 开始日期 YYYY-MM-DD |
+| endDate | string | 是 | 结束日期 YYYY-MM-DD |
+| statisticDimen | int | 否 | 1日 2月,默认 1 |
+| tenantId | long | 否 | 租户 ID |
+
+**响应 data 示例**:
+
+```json
+{
+  "alarmNum": 128,
+  "pitfallNum": 5,
+  "statisticDimen": 1,
+  "statisticTime": "2026-06-23T00:00:00.000+08:00"
+}
+```
+
+---
+
+### 2.9 需求响应
+
+> **实现说明(service-vpp)**  
+> - 运管平台邀约经 `DistributeEventRequest` 入库 `vpp_dr_event`(Poll 主动拉取或 UN 推送 `/DistributeEventRequest`)。  
+> - Web 端参与/出清成功后,若 `vpp.un.outbound-enabled=true`,自动向 UN 发送 `CreateOptRequest` / `CreateCqRequest`。  
+> - 出清公示确认:Web 调用 `ack-clearing`,或配置 `auto-ack-clearing=true` 在 Poll 入库后自动发送 `CreateEventResponse`。  
+> - 参与记录按电力户号 `vpp_customer.account_no` 与 UN 报文 `list[].account` 映射。
+
+#### 2.9.0 事件状态枚举
+
+| eventStatus | 说明 | 典型触发 |
+|-------------|------|----------|
+| 0 | 待参与 | 邀约入库 |
+| 1 | 已申报 | Web 参与 / UN 申报反馈 |
+| 2 | 执行中 | 出清确认 / Web 出清 |
+| 3 | 已结束 | Web 结束事件 |
+| 4 | 已取消 | Web 拒绝 / UN optOut |
+
+#### 2.9.1 事件列表
+
+**接口地址**:`GET /prod-api/service-vpp/dr/event`
+
+**功能描述**:分页查询需求响应事件列表。
+
+**权限**:`dr:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| eventStatus | int | 否 | 0待参与 1已申报 2执行中 3已结束 4已取消 |
+| responseType | int | 否 | 1日前 2日内 3秒级 |
+| eventType | int | 否 | 1削峰 2填谷 |
+| startTime | string | 否 | 响应开始时间起 |
+| endTime | string | 否 | 响应开始时间止 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_dr_event`):
+
+```json
+{
+  "id": 6001,
+  "eventId": "EVE_20260623001",
+  "eventName": "夏季削峰需求响应",
+  "responseType": 1,
+  "eventType": 1,
+  "startTime": "2026-06-23T14:00:00",
+  "endTime": "2026-06-23T16:00:00",
+  "targetCapacityKw": 1500.0,
+  "clearedCapacityKw": 1200.0,
+  "subsidyPrice": 3.5,
+  "eventStatus": 2
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_dr_event`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 本地事件 ID | id |
+| eventId | string | 运管平台事件 ID | event_id |
+| eventName | string | 事件名称 | event_name |
+| responseType | int | 1日前 2日内 3秒级 | response_type |
+| eventType | int | 1削峰 2填谷 | event_type |
+| startTime | string | 响应开始时间 | start_time |
+| endTime | string | 响应结束时间 | end_time |
+| targetCapacityKw | number | 目标容量 kW | target_capacity_kw |
+| clearedCapacityKw | number | 出清容量 kW | cleared_capacity_kw |
+| subsidyPrice | number | 补贴单价(元/kWh 或协议约定单位) | subsidy_price |
+| eventStatus | int | 事件状态,见 §2.9.0 | event_status |
+
+#### 2.9.2 事件详情
+
+**接口地址**:`GET /prod-api/service-vpp/dr/event/{id}`
+
+**功能描述**:获取事件详情,含活动期、补贴标准、出清容量及参与资源列表。
+
+**权限**:`dr:view`
+
+**响应 data 示例**:
+
+```json
+{
+  "id": 6001,
+  "eventId": "EVE_20260623001",
+  "eventName": "夏季削峰需求响应",
+  "responseType": 1,
+  "eventType": 1,
+  "startTime": "2026-06-23T14:00:00",
+  "endTime": "2026-06-23T16:00:00",
+  "targetCapacityKw": 1500.0,
+  "clearedCapacityKw": 1200.0,
+  "subsidyPrice": 3.5,
+  "eventStatus": 2,
+  "participations": [
+    {
+      "id": 7001,
+      "customerId": 1001,
+      "customerName": "上海某某有限公司",
+      "resourceId": 2001,
+      "resourceName": "工业负荷A",
+      "participateStatus": 1,
+      "declaredCapacityKw": 500.0,
+      "clearedCapacityKw": 450.0,
+      "declaredAt": "2026-06-22T16:00:00"
+    }
+  ]
+}
+```
+
+**响应 data 字段说明**(在列表字段基础上扩展):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| participations | array | 参与记录列表 | vpp_dr_participation |
+| participations[].id | long | 参与记录 ID | id |
+| participations[].customerId | long | 客户 ID | customer_id |
+| participations[].customerName | string | 客户名称(关联) | — |
+| participations[].resourceId | long | 资源点 ID | resource_id |
+| participations[].resourceName | string | 资源名称(关联) | — |
+| participations[].participateStatus | int | 0待确认 1已参与 2已拒绝 | participate_status |
+| participations[].declaredCapacityKw | number | 申报容量 kW | declared_capacity_kw |
+| participations[].clearedCapacityKw | number | 出清容量 kW | cleared_capacity_kw |
+| participations[].declaredAt | string | 申报时间 | declared_at |
+
+#### 2.9.2.1 新增事件
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event`
+
+**功能描述**:手工录入需求响应事件(联调/演练场景);`eventId` 不传时自动生成,初始状态为**待参与(0)**。运管平台同步事件请走 UN `DistributeEventRequest` 入库。
+
+**权限**:`dr:manage`
+
+**数据表**:`vpp_dr_event`
+
+**请求体字段说明**:
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| eventId | string | 否 | 运管平台事件 ID;不传则自动生成 `EVE_yyyyMMddHHmmss_XXXX` | event_id |
+| eventName | string | 否 | 事件名称;不传则使用 eventId | event_name |
+| responseType | int | 是 | 1日前 2日内 3秒级 | response_type |
+| eventType | int | 否 | 1削峰 2填谷 | event_type |
+| startTime | string | 是 | 响应开始时间(ISO 8601) | start_time |
+| endTime | string | 是 | 响应结束时间(ISO 8601) | end_time |
+| targetCapacityKw | number | 是 | 目标容量 kW,须 > 0 | target_capacity_kw |
+| clearedCapacityKw | number | 否 | 出清容量 kW | cleared_capacity_kw |
+| subsidyPrice | number | 否 | 补贴标准 元/kWh | subsidy_price |
+
+**请求体示例**:
+
+```json
+{
+  "eventName": "夏季削峰需求响应(演练)",
+  "responseType": 1,
+  "eventType": 1,
+  "startTime": "2026-06-23T14:00:00",
+  "endTime": "2026-06-23T16:00:00",
+  "targetCapacityKw": 1500.0,
+  "subsidyPrice": 3.5
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "id": 6001,
+    "eventId": "EVE_20260623140000_A1B2",
+    "eventName": "夏季削峰需求响应(演练)",
+    "responseType": 1,
+    "eventType": 1,
+    "startTime": "2026-06-23T14:00:00",
+    "endTime": "2026-06-23T16:00:00",
+    "targetCapacityKw": 1500.0,
+    "subsidyPrice": 3.5,
+    "eventStatus": 0
+  },
+  "exception": null
+}
+```
+
+#### 2.9.2.2 修改事件
+
+**接口地址**:`PUT /prod-api/service-vpp/dr/event/{id}`
+
+**功能描述**:修改手工录入的事件信息。
+
+**权限**:`dr:manage`
+
+**路径参数**:`id` — 本地事件 ID(`vpp_dr_event.id`)
+
+**前置条件**:
+
+- `eventStatus=0`(待参与)
+- 无关联参与记录、执行明细、评估报告
+
+**请求体**:字段同 §2.9.2.1(`eventId` 不可修改,传了也忽略)
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.9.2.3 删除事件
+
+**接口地址**:`DELETE /prod-api/service-vpp/dr/event/{id}`
+
+**功能描述**:软删除需求响应事件(`deleteFlag=1`)。
+
+**权限**:`dr:manage`
+
+**路径参数**:`id` — 本地事件 ID
+
+**前置条件**:
+
+- `eventStatus` 为 0(待参与)或 4(已取消)
+- 无关联参与记录、执行明细、评估报告
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.9.3 能力评估
+
+**接口地址**:`GET /prod-api/service-vpp/dr/event/{id}/capability`
+
+**功能描述**:评估事件可调能力,聚合客户登记能力及资源实时可用性。
+
+**权限**:`dr:view`
+
+**数据表**:`vpp_customer`(dr_up/down_capacity_kw)+ `vpp_resource_point`
+
+**请求参数**:无
+
+**响应 data**:
+
+```json
+{
+  "totalUpCapacityKw": 1500.0,
+  "totalDownCapacityKw": 2000.0,
+  "avgResponseTimeSec": 120,
+  "avgRampSpeedKwMin": 50.0,
+  "resources": [
+    {
+      "resourceId": 2001,
+      "resourceName": "工业负荷A",
+      "upCapacityKw": 500.0,
+      "downCapacityKw": 800.0,
+      "responsePriority": 1,
+      "available": true
+    }
+  ]
+}
+```
+
+**响应 data 字段说明**:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| totalUpCapacityKw | number | 汇总上调能力 kW |
+| totalDownCapacityKw | number | 汇总下调能力 kW |
+| avgResponseTimeSec | int | 平均响应时间(秒) |
+| avgRampSpeedKwMin | number | 平均爬坡速度 kW/min |
+| resources | array | 分资源能力明细 |
+| resources[].resourceId | long | 资源点 ID |
+| resources[].resourceName | string | 资源名称 |
+| resources[].upCapacityKw | number | 上调能力 kW |
+| resources[].downCapacityKw | number | 下调能力 kW |
+| resources[].responsePriority | int | 响应优先级 |
+| resources[].available | boolean | 当前是否可用(在线且未故障) |
+
+#### 2.9.4 参与/拒绝事件
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event/{id}/participate`
+
+**功能描述**:确认参与或拒绝需求响应事件,写入 `vpp_dr_participation`;**已启用 outbound 时同步 UN `CreateOptRequest`**。
+
+**权限**:`dr:participate`
+
+**路径参数**:`id` — 事件 ID(`vpp_dr_event.id`)
+
+**参与请求参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| participate | boolean | 是 | true 参与 / false 拒绝 |
+| strategyId | long | 否 | 响应策略 ID(参与时) |
+| declaredCapacityKw | number | 否 | 申报总容量 kW |
+| resources | array | 否 | 资源分拆列表 |
+| resources[].resourceId | long | 是 | 资源点 ID |
+| resources[].declaredCapacityKw | number | 是 | 申报容量 kW |
+| reason | string | 否 | 拒绝原因(拒绝时必填) |
+
+**请求体(参与)**:
+
+```json
+{
+  "participate": true,
+  "strategyId": 10,
+  "declaredCapacityKw": 1200.0,
+  "resources": [
+    { "resourceId": 2001, "declaredCapacityKw": 500.0 },
+    { "resourceId": 2002, "declaredCapacityKw": 700.0 }
+  ]
+}
+```
+
+**请求体(拒绝)**:
+
+```json
+{
+  "participate": false,
+  "reason": "可调容量不足"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "申报成功",
+  "data": {
+    "eventId": 6001,
+    "participateStatus": 1,
+    "declaredCapacityKw": 1200.0
+  },
+  "exception": null
+}
+```
+
+#### 2.9.5 出清分拆申报
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event/{id}/clearing`
+
+**功能描述**:出清后按资源分拆申报容量,写入 `vpp_dr_execution` 并更新事件为执行中;**已启用 outbound 时同步 UN `CreateCqRequest`**。
+
+**权限**:`dr:participate`
+
+**请求体**(更新 `vpp_dr_participation.cleared_capacity_kw`):
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| totalClearedCapacityKw | number | 是 | 出清总容量 kW |
+| resources | array | 是 | 资源分拆列表 |
+| resources[].resourceId | long | 是 | 资源点 ID |
+| resources[].clearedCapacityKw | number | 是 | 分拆出清容量 kW |
+
+```json
+{
+  "totalClearedCapacityKw": 1200.0,
+  "resources": [
+    { "resourceId": 2001, "clearedCapacityKw": 450.0 },
+    { "resourceId": 2002, "clearedCapacityKw": 750.0 }
+  ]
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "分拆申报成功",
+  "data": { "eventId": "EVE_20260623001", "totalClearedCapacityKw": 1200.0 },
+  "exception": null
+}
+```
+
+#### 2.9.6 执行监控
+
+**接口地址**:`GET /prod-api/service-vpp/dr/event/{id}/monitor`
+
+**功能描述**:实时监控事件执行进度,数据来自 `vpp_dr_execution`。
+
+**权限**:`dr:view`
+
+**请求参数**:无
+
+**响应 data**:
+
+```json
+{
+  "eventId": "EVE_20260623001",
+  "targetCapacityKw": 1200.0,
+  "actualCapacityKw": 1050.0,
+  "progressPercent": 87.5,
+  "remainingMinutes": 45,
+  "resources": [
+    {
+      "resourceId": 2001,
+      "targetKw": 500.0,
+      "actualKw": 480.0,
+      "executeStatus": 1
+    }
+  ]
+}
+```
+
+**响应 data 字段说明**:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| eventId | string | 运管平台事件 ID |
+| targetCapacityKw | number | 目标/出清容量 kW |
+| actualCapacityKw | number | 当前实际响应容量 kW |
+| progressPercent | number | 执行进度百分比 |
+| remainingMinutes | int | 剩余执行分钟数 |
+| resources | array | 分资源执行明细(`vpp_dr_execution`) |
+| resources[].resourceId | long | 资源点 ID |
+| resources[].targetKw | number | 目标调节量 kW |
+| resources[].actualKw | number | 实际调节量 kW |
+| resources[].executeStatus | int | 0待执行 1执行中 2已完成 3异常 |
+
+#### 2.9.7 手动干预
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event/{id}/intervene`
+
+**功能描述**:执行中手动切换策略或调整执行。
+
+**权限**:`dr:intervene`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| action | string | 是 | ADJUST(切换策略)/ PAUSE(暂停)/ RESUME(恢复) |
+| strategyId | long | 否 | 目标策略 ID(action=ADJUST 时必填) |
+| remark | string | 否 | 干预说明 |
+
+```json
+{
+  "action": "ADJUST",
+  "strategyId": 11,
+  "remark": "实际响应偏低,切换备用策略"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "干预指令已下发",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.9.8 确认出清公示
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event/{id}/ack-clearing`
+
+**功能描述**:向 UN 发送 `CreateEventResponse` 确认出清公示,事件状态推进为**执行中(2)**,并初始化 `vpp_dr_execution`(若尚未创建)。
+
+**权限**:`dr:participate`
+
+**前置条件**:`eventStatus=1`(已申报),且已完成出清公示 Poll 入库(`clearedCapacityKw` 有值)
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null,
+  "exception": null
+}
+```
+
+> 等价 UN 报文见 §3.7.4;也可由 `vpp.un.auto-ack-clearing=true` 在 Poll 收到出清公示后自动触发。
+
+#### 2.9.9 结束事件
+
+**接口地址**:`POST /prod-api/service-vpp/dr/event/{id}/complete`
+
+**功能描述**:将执行中事件标记为**已结束(3)**,汇总执行明细写入 `vpp_dr_evaluation`(若不存在则创建)。
+
+**权限**:`dr:intervene`
+
+**前置条件**:`eventStatus=2`,且存在执行明细
+
+**请求参数**:无
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.9.10 效果评估报告
+
+**接口地址**:`GET /prod-api/service-vpp/dr/event/{id}/evaluation`
+
+**功能描述**:获取事件响应效果评估报告。
+
+**权限**:`dr:view`
+
+**响应 data 示例**(映射 `vpp_dr_evaluation`):
+
+```json
+{
+  "eventId": 6001,
+  "eventName": "夏季削峰需求响应",
+  "targetCapacityKw": 1200.0,
+  "actualCapacityKw": 1050.0,
+  "responseDurationMin": 120,
+  "subsidyAmount": 7350.0,
+  "qualifiedRate": 87.5,
+  "reportFileUrl": "https://oss.example.com/dr/eval_6001.pdf",
+  "evaluatedAt": "2026-06-23T18:00:00",
+  "resourceDetails": [
+    {
+      "resourceId": 2001,
+      "resourceName": "工业负荷A",
+      "targetKw": 450.0,
+      "actualKw": 420.0,
+      "qualifiedRate": 93.3
+    }
+  ]
+}
+```
+
+**响应 data 字段说明**(映射 `vpp_dr_evaluation`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| eventId | long | 本地事件 ID | event_id |
+| eventName | string | 事件名称 | — |
+| targetCapacityKw | number | 目标容量 kW | target_capacity_kw |
+| actualCapacityKw | number | 实际响应容量 kW | actual_capacity_kw |
+| responseDurationMin | int | 响应持续分钟数 | response_duration_min |
+| subsidyAmount | number | 补贴金额(元) | subsidy_amount |
+| qualifiedRate | number | 达标率 % | qualified_rate |
+| reportFileUrl | string | 评估报告文件 URL | report_file_url |
+| evaluatedAt | string | 评估时间 | evaluated_at |
+| resourceDetails | array | 分资源评估明细 | — |
+| resourceDetails[].resourceId | long | 资源点 ID | — |
+| resourceDetails[].resourceName | string | 资源名称 | — |
+| resourceDetails[].targetKw | number | 目标 kW | — |
+| resourceDetails[].actualKw | number | 实际 kW | — |
+| resourceDetails[].qualifiedRate | number | 达标率 % | — |
+
+#### 2.9.11 响应策略 CRUD
+
+**数据表**:`vpp_dr_strategy` + `vpp_dr_strategy_resource`
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/dr/strategy` | 策略列表 |
+| POST | `/prod-api/service-vpp/dr/strategy` | 新增策略 |
+| GET | `/prod-api/service-vpp/dr/strategy/{id}` | 策略详情 |
+| PUT | `/prod-api/service-vpp/dr/strategy/{id}` | 更新策略 |
+| DELETE | `/prod-api/service-vpp/dr/strategy/{id}` | 删除策略 |
+
+**列表查询参数**:
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| strategyName | string | 否 | 策略名称(模糊) | strategy_name |
+| responseType | int | 否 | 适用响应类型:1日前 2日内 3秒级 | response_type |
+| isEnabled | int | 否 | 0禁用 1启用 | is_enabled |
+| current | int | 否 | 页码 | — |
+| size | int | 否 | 每页数量 | — |
+
+**创建/更新请求体字段说明**(映射 `vpp_dr_strategy` + `vpp_dr_strategy_resource`):
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| strategyCode | string | 是 | 策略编码,全局唯一 | strategy_code |
+| strategyName | string | 是 | 策略名称 | strategy_name |
+| responseType | int | 是 | 适用响应类型:1日前 2日内 3秒级 | response_type |
+| adjustStepKw | number | 否 | 调节步长 kW | adjust_step_kw |
+| executeMode | int | 否 | 执行模式:1并行 2串行,默认 1 | execute_mode |
+| strategyConfig | object/string | 否 | 策略扩展配置 JSON;**请求可传 JSON 对象或 JSON 字符串**,服务端统一序列化后落库 | strategy_config |
+| isDefault | int | 否 | 是否默认策略:0否 1是,默认 0 | is_default |
+| isEnabled | int | 否 | 是否启用:0否 1是,默认 1 | is_enabled |
+| resources | array | 否 | 关联资源列表 | vpp_dr_strategy_resource |
+| resources[].resourceId | long | 是 | 资源点 ID | resource_id |
+| resources[].priority | int | 否 | 优先级序号,默认 0 | priority |
+| resources[].maxAdjustKw | number | 否 | 最大调节量 kW | max_adjust_kw |
+
+**创建/更新请求体**:
+
+```json
+{
+  "strategyCode": "DR_PEAK_V1",
+  "strategyName": "削峰默认策略",
+  "responseType": 1,
+  "adjustStepKw": 50.0,
+  "executeMode": 1,
+  "isDefault": 1,
+  "isEnabled": 1,
+  "strategyConfig": { "rampMinutes": 5 },
+  "resources": [
+    { "resourceId": 2001, "priority": 1, "maxAdjustKw": 500.0 },
+    { "resourceId": 2002, "priority": 2, "maxAdjustKw": 800.0 }
+  ]
+}
+```
+
+**策略详情响应 data 示例**:
+
+```json
+{
+  "id": 10,
+  "strategyCode": "DR_PEAK_V1",
+  "strategyName": "削峰默认策略",
+  "responseType": 1,
+  "adjustStepKw": 50.0,
+  "executeMode": 1,
+  "strategyConfig": "{\"rampMinutes\":5}",
+  "isDefault": 1,
+  "isEnabled": 1,
+  "resources": [
+    { "resourceId": 2001, "resourceName": "工业负荷A", "priority": 1, "maxAdjustKw": 500.0 }
+  ]
+}
+```
+
+**策略列表/详情响应字段说明**(映射 `vpp_dr_strategy`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 策略 ID | id |
+| strategyCode | string | 策略编码 | strategy_code |
+| strategyName | string | 策略名称 | strategy_name |
+| responseType | int | 适用响应类型 | response_type |
+| adjustStepKw | number | 调节步长 kW | adjust_step_kw |
+| executeMode | int | 1并行 2串行 | execute_mode |
+| strategyConfig | string | 策略扩展配置(JSON 字符串,对应库表 JSON 列;前端需 `JSON.parse` 后使用) | strategy_config |
+| isDefault | int | 是否默认策略 | is_default |
+| isEnabled | int | 是否启用 | is_enabled |
+| resources | array | 关联资源(详情接口返回,含 resourceName) | vpp_dr_strategy_resource |
+| resources[].resourceId | long | 资源点 ID | resource_id |
+| resources[].resourceName | string | 资源名称(关联查询,仅详情) | — |
+| resources[].priority | int | 优先级序号 | priority |
+| resources[].maxAdjustKw | number | 最大调节量 kW | max_adjust_kw |
+| tenantId | int | 租户 ID | tenant_id |
+| createTime | string | 创建时间 | create_time |
+| createdBy | string | 创建人 | created_by |
+
+> **strategyConfig 入参说明**:`POST/PUT /dr/strategy` 请求体中 `strategyConfig` 支持 JSON 对象(推荐)或 JSON 字符串两种格式;响应中为 JSON 字符串。
+
+#### 2.9.12 邀约管理
+
+> **数据表**:`vpp_dr_invitation`(关联 `vpp_dr_event`、`vpp_customer`、`vpp_customer_contact`、`vpp_dr_participation`)  
+> **界面映射**:Web「邀约管理」列表与「添加邀约」弹窗。  
+> **业务说明**:聚合商向签约企业下发需求响应邀约;同一事件对同一企业仅允许一条有效邀约。客户确认参与后,可关联 `vpp_dr_participation` 并同步 UN `CreateOptRequest`(见 §2.9.4)。
+
+##### 2.9.12.0 枚举
+
+**replyStatus(回复情况)**
+
+| 值 | 说明 | 界面展示 |
+|----|------|----------|
+| 0 | 待回复 | 待回复 |
+| 1 | 参与 | 参与 |
+| 2 | 拒绝 | 拒绝 |
+| 3 | 超时 | 超时未回复 |
+
+**responseStatus(响应状态)**
+
+| 值 | 说明 | 界面展示 |
+|----|------|----------|
+| 0 | 待响应 | 待响应 |
+| 1 | 已申报 | 响应已申报 |
+| 2 | 执行中 | 响应执行中 |
+| 3 | 已结束 | 响应已结束 |
+| 4 | 已取消 | 已取消 |
+
+**smsNotifyStatus(短信通知)**
+
+| 值 | 说明 |
+|----|------|
+| 0 | 未发送 |
+| 1 | 已发送 |
+| 2 | 发送失败 |
+
+**transactionType(交易类型)**
+
+| 值 | 说明 |
+|----|------|
+| 1 | 削峰 |
+| 2 | 填谷 |
+
+##### 2.9.12.1 邀约列表
+
+**接口地址**:`GET /prod-api/service-vpp/dr/invitation`
+
+**功能描述**:分页查询企业邀约台账,支持按下发日期、响应类型、企业名称筛选。
+
+**权限**:`dr:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| issueDateStart | string | 否 | 下发日期起(yyyy-MM-dd) | issue_time |
+| issueDateEnd | string | 否 | 下发日期止(yyyy-MM-dd) | issue_time |
+| responseType | int | 否 | 1日前 2日内 3秒级 | response_type |
+| customerId | long | 否 | 客户 ID | customer_id |
+| customerName | string | 否 | 企业名称(模糊) | — |
+| drEventId | long | 否 | 本地事件 ID | dr_event_id |
+| replyStatus | int | 否 | 回复情况 | reply_status |
+| responseStatus | int | 否 | 响应状态 | response_status |
+| current | int | 否 | 页码 | — |
+| size | int | 否 | 每页数量 | — |
+
+**响应 data.records 示例**:
+
+```json
+{
+  "id": 1,
+  "invitationNo": "INV_20260704120000_A1B2",
+  "drEventId": 1,
+  "drEventCode": "EVE_20260430001",
+  "customerId": 1001,
+  "customerName": "用户站",
+  "executeStartDate": "2026-05-01",
+  "executeEndDate": "2026-05-01",
+  "replyDeadline": "2026-04-28T18:00:00",
+  "issueTime": "2026-04-27T10:00:00",
+  "transactionType": 1,
+  "responseType": 1,
+  "demandCapacityKw": 500.0,
+  "declaredCapacityKw": 480.0,
+  "replyStatus": 1,
+  "responseStatus": 1,
+  "smsNotifyStatus": 0
+}
+```
+
+**响应 data.records 字段说明**:
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 邀约 ID | id |
+| invitationNo | string | 邀约编号 | invitation_no |
+| drEventId | long | 本地事件 ID | dr_event_id |
+| drEventCode | string | 需求响应编号(关联 `vpp_dr_event.event_id`) | — |
+| customerId | long | 客户 ID | customer_id |
+| customerName | string | 企业名称(关联) | — |
+| executeStartDate | string | 执行开始日期 | execute_start_date |
+| executeEndDate | string | 执行结束日期 | execute_end_date |
+| replyDeadline | string | 邀约回复截止日期 | reply_deadline |
+| issueTime | string | 下发时间 | issue_time |
+| transactionType | int | 交易类型:1削峰 2填谷 | transaction_type |
+| responseType | int | 1日前 2日内 3秒级 | response_type |
+| demandCapacityKw | number | 需求容量 kW | demand_capacity_kw |
+| declaredCapacityKw | number | 已申报容量 kW | declared_capacity_kw |
+| replyStatus | int | 回复情况,见 §2.9.12.0 | reply_status |
+| responseStatus | int | 响应状态,见 §2.9.12.0 | response_status |
+| smsNotifyStatus | int | 短信通知状态 | sms_notify_status |
+
+##### 2.9.12.2 邀约详情
+
+**接口地址**:`GET /prod-api/service-vpp/dr/invitation/{id}`
+
+**功能描述**:获取单条邀约详情,含关联事件与客户主联系人信息。
+
+**权限**:`dr:view`
+
+**响应 data**:在列表字段基础上扩展 `remark`、`participationId`、`smsContactId`、`smsContactName`、`smsContactPhone`、`smsNotifyAt` 及 §2.1.7 审计字段。
+
+##### 2.9.12.3 新增邀约
+
+**接口地址**:`POST /prod-api/service-vpp/dr/invitation`
+
+**功能描述**:向指定企业下发需求响应邀约;`invitationNo` 不传时自动生成 `INV_yyyyMMddHHmmss_XXXX`;`issueTime` 默认当前时间;`transactionType`、`responseType` 未传时从关联事件复制。
+
+**权限**:`dr:manage`
+
+**数据表**:`vpp_dr_invitation`
+
+**请求体字段说明**:
+
+| 参数名 | 类型 | 必填 | 说明 | 数据表字段 |
+|--------|------|------|------|------------|
+| drEventId | long | 是 | 需求响应事件 ID(`vpp_dr_event.id`) | dr_event_id |
+| customerId | long | 是 | 企业客户 ID | customer_id |
+| executeStartDate | string | 是 | 执行开始日期(yyyy-MM-dd) | execute_start_date |
+| executeEndDate | string | 是 | 执行结束日期(yyyy-MM-dd) | execute_end_date |
+| replyDeadline | string | 是 | 邀约回复截止日期(ISO 8601) | reply_deadline |
+| demandCapacityKw | number | 是 | 需求容量 kW,须 > 0 | demand_capacity_kw |
+| declaredCapacityKw | number | 否 | 已申报容量 kW;新建时可预填 0 | declared_capacity_kw |
+| transactionType | int | 否 | 交易类型;默认取事件 `eventType` | transaction_type |
+| responseType | int | 否 | 响应类型;默认取事件 `responseType` | response_type |
+| smsContactId | long | 否 | 短信通知联系人;默认取客户主联系人 | sms_contact_id |
+| remark | string | 否 | 备注 | remark |
+
+**请求体示例**:
+
+```json
+{
+  "drEventId": 1,
+  "customerId": 1001,
+  "executeStartDate": "2026-05-01",
+  "executeEndDate": "2026-05-01",
+  "replyDeadline": "2026-04-28T18:00:00",
+  "demandCapacityKw": 500.0,
+  "declaredCapacityKw": 0
+}
+```
+
+**业务规则**:
+
+- 同一 `drEventId` + `customerId` 不可重复创建(`uk_dr_invitation_event_customer`)
+- 初始 `replyStatus=0`(待回复)、`responseStatus=0`(待响应)、`smsNotifyStatus=0`(未发送)
+- `executeStartDate` ≤ `executeEndDate`;`replyDeadline` 应早于执行开始时间
+
+**响应 data**:返回完整邀约对象(含生成的 `id`、`invitationNo`、`issueTime`)。
+
+##### 2.9.12.4 修改邀约
+
+**接口地址**:`PUT /prod-api/service-vpp/dr/invitation/{id}`
+
+**功能描述**:修改待回复状态的邀约信息。
+
+**权限**:`dr:manage`
+
+**前置条件**:`replyStatus=0`(待回复)且 `responseStatus=0`(待响应)
+
+**可修改字段**:`executeStartDate`、`executeEndDate`、`replyDeadline`、`demandCapacityKw`、`declaredCapacityKw`、`transactionType`、`responseType`、`smsContactId`、`remark`
+
+##### 2.9.12.5 删除邀约
+
+**接口地址**:`DELETE /prod-api/service-vpp/dr/invitation/{id}`
+
+**功能描述**:软删除邀约记录(`deleteFlag=1`)。
+
+**权限**:`dr:manage`
+
+**前置条件**:`replyStatus` 为 0、2 或 3,且 `responseStatus=0`;已关联 `participationId` 的不可删除
+
+##### 2.9.12.6 发送短信通知
+
+**接口地址**:`POST /prod-api/service-vpp/dr/invitation/{id}/notify-sms`
+
+**功能描述**:向邀约联系人发送阿里云短信;成功后更新 `smsNotifyStatus=1`、`smsNotifyAt`。
+
+**权限**:`dr:manage`
+
+**配置项**(Nacos 共享 + `service-vpp` 本地):
+
+| 配置键 | 说明 |
+|--------|------|
+| `sms.ali.region-id` | 阿里云区域,如 `cn-hangzhou` |
+| `sms.ali.access-key-id` | AccessKey ID |
+| `sms.ali.access-key-secret` | AccessKey Secret |
+| `vpp.sms.sign-name` | 短信签名 |
+| `vpp.sms.invitation-template-code` | 邀约默认模板 CODE(需在阿里云单独申请,勿与告警模板混用) |
+| `vpp.sms.enabled` | 是否启用,默认 `true` |
+
+**阿里云模板变量**(代码默认构建,须与控制台模板占位符**完全一致**;默认模板 `SMS_465362899`):
+
+| 变量名 | 说明 | 示例 |
+|--------|------|------|
+| `meet` | 需求响应事件名称 | 夏季削峰需求响应 |
+| `time` | 响应开始时间(无则取回复截止时间) | 2026-07-08 14:00:00 |
+| `room` | 客户名称 | 上海某某有限公司 |
+
+> **注意**:告警模块使用的 `SMS_463791105` 变量为 `deviceName`、`time`、`alarmType`、`alarmContent`,**不能**直接作为邀约模板。若控制台模板变量名不同,请在请求体传入 `templateParam` 覆盖。
+
+**请求体(可选)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| contactId | long | 否 | 覆盖默认联系人;不传则使用 `smsContactId` 或客户主联系人 |
+| templateCode | string | 否 | 覆盖默认模板 CODE;不传则使用 `vpp.sms.invitation-template-code` |
+| templateParam | string | 否 | 覆盖默认模板变量 JSON;须与 `templateCode` 对应模板占位符一致 |
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "短信已发送",
+  "data": {
+    "smsNotifyStatus": 1,
+    "smsNotifyAt": "2026-07-04T17:30:00"
+  },
+  "exception": null
+}
+```
+
+##### 2.9.12.7 企业回复邀约
+
+**接口地址**:`POST /prod-api/service-vpp/dr/invitation/{id}/reply`
+
+**功能描述**:企业确认参与或拒绝邀约;参与时写入/更新 `vpp_dr_participation` 并回写 `participationId`、`declaredCapacityKw`;**已启用 outbound 时同步 UN `CreateOptRequest`**。
+
+**权限**:`dr:participate`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| participate | boolean | 是 | true 参与 / false 拒绝 |
+| declaredCapacityKw | number | 否 | 申报容量 kW(参与时必填) |
+| reason | string | 否 | 拒绝原因(拒绝时必填) |
+
+**状态流转**:
+
+- 参与:`replyStatus=1`,`responseStatus=1`(已申报)
+- 拒绝:`replyStatus=2`,`responseStatus=4`(已取消)
+- 超过 `replyDeadline` 未回复:由定时任务置 `replyStatus=3`
+
+---
+
+### 2.10 结算管理
+
+> **数据表**:`vpp_energy_reading_monthly`、`vpp_settlement_bill`、`vpp_settlement_bill_detail`、`vpp_payment_record`
+
+#### 2.10.1 月度电量核算
+
+**接口地址**:`GET /prod-api/service-vpp/settlement/energyReading`
+
+**功能描述**:查询月度电量核算结果。
+
+**权限**:`settlement:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| customerId | long | 否 | 客户 ID |
+| resourceId | long | 否 | 资源点 ID |
+| settleYear | int | 否 | 结算年 |
+| settleMonth | int | 否 | 结算月 1-12 |
+| calcStatus | int | 否 | 0待核算 1已核算 2异常 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_energy_reading_monthly`):
+
+```json
+{
+  "id": 9001,
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "resourceId": 2001,
+  "resourceName": "浦东光伏站",
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "totalEnergyKwh": 12580.5,
+  "peakEnergyKwh": 3200.0,
+  "flatEnergyKwh": 4500.0,
+  "valleyEnergyKwh": 3800.0,
+  "sharpEnergyKwh": 1080.5,
+  "genEnergyKwh": 8500.0,
+  "calcStatus": 1,
+  "calcAt": "2026-06-01T08:00:00"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_energy_reading_monthly`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 记录 ID | id |
+| customerId | long | 客户 ID | customer_id |
+| customerName | string | 客户名称(关联) | — |
+| resourceId | long | 资源点 ID | resource_id |
+| resourceName | string | 资源名称(关联) | — |
+| settleYear | int | 结算年 | settle_year |
+| settleMonth | int | 结算月 1-12 | settle_month |
+| totalEnergyKwh | number | 总电量 kWh | total_energy_kwh |
+| peakEnergyKwh | number | 峰段电量 | peak_energy_kwh |
+| flatEnergyKwh | number | 平段电量 | flat_energy_kwh |
+| valleyEnergyKwh | number | 谷段电量 | valley_energy_kwh |
+| sharpEnergyKwh | number | 尖段电量 | sharp_energy_kwh |
+| genEnergyKwh | number | 发电量 kWh | gen_energy_kwh |
+| calcStatus | int | 0待核算 1已核算 2异常 | calc_status |
+| calcAt | string | 核算时间 | calc_at |
+
+#### 2.10.2 触发电量核算
+
+**接口地址**:`POST /prod-api/service-vpp/settlement/energyReading/calculate`
+
+**功能描述**:触发指定月份电量核算任务。
+
+**权限**:`settlement:calculate`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| settleYear | int | 是 | 结算年 |
+| settleMonth | int | 是 | 结算月 |
+| customerId | long | 否 | 客户 ID,不传则全量核算 |
+
+```json
+{
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "customerId": 1001
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "核算任务已提交",
+  "data": { "taskId": "CALC-202605-001", "customerCount": 1 },
+  "exception": null
+}
+```
+
+#### 2.10.3 账单列表
+
+**接口地址**:`GET /prod-api/service-vpp/settlement/bill`
+
+**功能描述**:分页查询电费账单列表。
+
+**权限**:`settlement:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| customerId | long | 否 | 客户 ID |
+| paymentStatus | int | 否 | 0未缴 1部分 2已缴 3逾期 |
+| settleYear | int | 否 | 结算年 |
+| settleMonth | int | 否 | 结算月 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**(映射 `vpp_settlement_bill`):
+
+```json
+{
+  "id": 10001,
+  "billNo": "BILL-202605-1001",
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "totalEnergyKwh": 12580.5,
+  "totalAmount": 15680.0,
+  "paidAmount": 0,
+  "paymentStatus": 0,
+  "dueDate": "2026-06-15",
+  "generatedAt": "2026-06-01T10:00:00"
+}
+```
+
+**响应 data.records 字段说明**(映射 `vpp_settlement_bill`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 账单 ID | id |
+| billNo | string | 账单编号 | bill_no |
+| customerId | long | 客户 ID | customer_id |
+| customerName | string | 客户名称(关联) | — |
+| settleYear | int | 结算年 | settle_year |
+| settleMonth | int | 结算月 | settle_month |
+| totalEnergyKwh | number | 总电量 kWh | total_energy_kwh |
+| totalAmount | number | 账单总金额(元) | total_amount |
+| paidAmount | number | 已缴金额(元) | paid_amount |
+| paymentStatus | int | 0未缴 1部分 2已缴 3逾期 | payment_status |
+| dueDate | string | 缴费截止日期 | due_date |
+| generatedAt | string | 账单生成时间 | generated_at |
+
+#### 2.10.4 生成账单
+
+**接口地址**:`POST /prod-api/service-vpp/settlement/bill/generate`
+
+**功能描述**:根据月度电量核算生成电费账单。
+
+**权限**:`settlement:generate`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| settleYear | int | 是 | 结算年 |
+| settleMonth | int | 是 | 结算月 |
+| customerIds | array | 否 | 客户 ID 列表,不传则按已核算客户批量生成 |
+
+```json
+{
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "customerIds": [1001, 1002]
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "账单生成成功",
+  "data": { "generatedCount": 2, "billIds": [10001, 10002] },
+  "exception": null
+}
+```
+
+#### 2.10.5 账单详情
+
+**接口地址**:`GET /prod-api/service-vpp/settlement/bill/{id}`
+
+**功能描述**:获取账单详情及分时明细。
+
+**权限**:`settlement:view`
+
+**响应 data 示例**:
+
+```json
+{
+  "id": 10001,
+  "billNo": "BILL-202605-1001",
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "accountNo": "3100060012345",
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "totalEnergyKwh": 12580.5,
+  "totalAmount": 15680.0,
+  "paidAmount": 0,
+  "paymentStatus": 0,
+  "dueDate": "2026-06-15",
+  "billFileUrl": "https://oss.example.com/bill/10001.pdf",
+  "details": [
+    { "timePeriod": 1, "timePeriodName": "尖", "energyKwh": 1080.5, "price": 1.5, "amount": 1620.75 },
+    { "timePeriod": 2, "timePeriodName": "峰", "energyKwh": 3200.0, "price": 1.2, "amount": 3840.0 },
+    { "timePeriod": 3, "timePeriodName": "平", "energyKwh": 4500.0, "price": 0.8, "amount": 3600.0 },
+    { "timePeriod": 4, "timePeriodName": "谷", "energyKwh": 3800.0, "price": 0.4, "amount": 1520.0 }
+  ]
+}
+```
+
+#### 2.10.6 账单下载
+
+**接口地址**:`GET /prod-api/service-vpp/settlement/bill/{id}/download`
+
+**功能描述**:下载账单 PDF 文件。
+
+**权限**:`settlement:view`
+
+**响应**:`Content-Type: application/pdf` 文件流;或返回 `billFileUrl` 重定向。
+
+#### 2.10.7 缴费记录
+
+**GET 缴费记录列表** — `GET /prod-api/service-vpp/settlement/bill/{id}/payment`
+
+**响应 data 示例**(映射 `vpp_payment_record`):
+
+```json
+[
+  {
+    "id": 11001,
+    "billId": 10001,
+    "paymentAmount": 8000.0,
+    "paymentMethod": 1,
+    "paymentMethodName": "线下转账",
+    "paymentDate": "2026-06-10",
+    "voucherUrl": "https://oss.example.com/voucher/11001.jpg",
+    "remark": "首期付款",
+    "createdBy": "operator01",
+    "createdByName": "张三",
+    "createTime": "2026-06-10T14:00:00"
+  }
+]
+```
+
+**缴费记录字段说明**(映射 `vpp_payment_record`):
+
+| 字段名 | 类型 | 说明 | 数据表字段 |
+|--------|------|------|------------|
+| id | long | 缴费记录 ID | id |
+| billId | long | 账单 ID | bill_id |
+| paymentAmount | number | 缴费金额(元) | payment_amount |
+| paymentMethod | int | 1线下转账 2电费抵扣 3其他 | payment_method |
+| paymentMethodName | string | 缴费方式名称(字典翻译) | — |
+| paymentDate | string | 缴费日期 YYYY-MM-DD | payment_date |
+| voucherUrl | string | 缴费凭证 URL | voucher_url |
+| remark | string | 对账备注 | remark |
+| createdBy | string | 录入人账号 | created_by |
+| createdByName | string | 录入人姓名(关联) | — |
+| createTime | string | 录入时间 | create_time |
+
+**POST 录入线下缴费** — `POST /prod-api/service-vpp/settlement/bill/{id}/payment`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| paymentAmount | number | 是 | 缴费金额 |
+| paymentMethod | int | 是 | 1线下转账 2电费抵扣 3其他 |
+| paymentDate | string | 是 | 缴费日期 YYYY-MM-DD |
+| voucherUrl | string | 否 | 缴费凭证 URL |
+| remark | string | 否 | 对账备注 |
+
+```json
+{
+  "paymentAmount": 8000.0,
+  "paymentMethod": 1,
+  "paymentDate": "2026-06-10",
+  "voucherUrl": "https://oss.example.com/voucher/11001.jpg",
+  "remark": "首期付款"
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "录入成功",
+  "data": {
+    "id": 11001,
+    "billId": 10001,
+    "paidAmount": 8000.0,
+    "paymentStatus": 1
+  },
+  "exception": null
+}
+```
+
+---
+
+### 2.11 信息报送
+
+> **数据表**:`vpp_report_task`、`vpp_report_record`、`vpp_report_data`、`vpp_report_audit_log`
+
+#### 2.11.1 报送任务列表
+
+**接口地址**:`GET /prod-api/service-vpp/report/task`
+
+**功能描述**:分页查询监管报送任务列表。
+
+**权限**:`report:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| taskStatus | int | 否 | 0待填报 1待审核 2报送中 3已完成 4已逾期 |
+| reportOrg | int | 否 | 1交易中心 2能监局 3经信委 4国网上海 |
+| reportCycle | int | 否 | 1月报 2年报 3临时 |
+| deadlineStart | string | 否 | 截止日期起 |
+| deadlineEnd | string | 否 | 截止日期止 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**taskStatus**:0待填报 1待审核 2报送中 3已完成 4已逾期
+
+**响应 data.records 元素**(映射 `vpp_report_task`):
+
+```json
+{
+  "id": 12001,
+  "taskNo": "RPT-202605-001",
+  "taskName": "2026年5月虚拟电厂运行月报",
+  "reportOrg": 1,
+  "reportOrgName": "交易中心",
+  "reportCycle": 1,
+  "reportType": "VPP_MONTHLY",
+  "deadline": "2026-06-10T18:00:00",
+  "taskStatus": 0,
+  "assigneeId": 1001,
+  "assigneeName": "张三",
+  "remindDays": 3
+}
+```
+
+#### 2.11.2 报送记录详情
+
+**接口地址**:`GET /prod-api/service-vpp/report/record/{id}`
+
+**功能描述**:获取报送记录详情及审核流水。
+
+**权限**:`report:view`
+
+**响应 data 示例**(映射 `vpp_report_record` + `vpp_report_audit_log`):
+
+```json
+{
+  "id": 13001,
+  "taskId": 12001,
+  "taskName": "2026年5月虚拟电厂运行月报",
+  "recordNo": "REC-202605-001",
+  "reportPeriod": "2026-05",
+  "submitMode": 1,
+  "recordStatus": 0,
+  "reportFileUrl": null,
+  "receiptFileUrl": null,
+  "retryCount": 0,
+  "submittedAt": null,
+  "auditLogs": [],
+  "formData": null,
+  "isDraft": true
+}
+```
+
+#### 2.11.3 获取预填报数据
+
+**接口地址**:`GET /prod-api/service-vpp/report/record/{id}/prefill`
+
+**功能描述**:从客户、合同、结算、DR 等模块自动拉取预填报数据。
+
+**权限**:`report:edit`
+
+**响应 data 示例**:
+
+```json
+{
+  "reportPeriod": "2026-05",
+  "customerCount": 56,
+  "totalCapacityKw": 28500.0,
+  "totalGenKwh": 1250000.0,
+  "totalUseKwh": 980000.0,
+  "drEventCount": 3,
+  "drTotalSubsidy": 45000.0,
+  "formData": {
+    "basicInfo": { "vppName": "伍继虚拟电厂", "reportMonth": "2026-05" },
+    "resourceStats": { "pvCount": 20, "essCount": 8 },
+    "energyData": { "genKwh": 1250000.0, "useKwh": 980000.0 }
+  }
+}
+```
+
+#### 2.11.4 保存填报数据
+
+**接口地址**:`PUT /prod-api/service-vpp/report/record/{id}/data`
+
+**功能描述**:保存或暂存报送填报数据,写入 `vpp_report_data`。
+
+**权限**:`report:edit`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| formData | object | 是 | 填报表单 JSON |
+| isDraft | boolean | 是 | true 草稿 / false 定稿 |
+
+```json
+{
+  "formData": {
+    "basicInfo": { "vppName": "伍继虚拟电厂", "reportMonth": "2026-05" },
+    "energyData": { "genKwh": 1250000.0, "useKwh": 980000.0 }
+  },
+  "isDraft": true
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "保存成功",
+  "data": { "recordId": 13001, "isDraft": true },
+  "exception": null
+}
+```
+
+#### 2.11.5 校验填报数据
+
+**接口地址**:`POST /prod-api/service-vpp/report/record/{id}/validate`
+
+**功能描述**:校验填报数据完整性与逻辑一致性。
+
+**权限**:`report:edit`
+
+**请求参数**:无
+
+**响应 data 示例**:
+
+```json
+{
+  "valid": false,
+  "errors": [
+    { "field": "energyData.genKwh", "message": "发电量不能为负数" },
+    { "field": "resourceStats.pvCount", "message": "光伏数量与系统统计不一致" }
+  ]
+}
+```
+
+#### 2.11.6 提交审核
+
+**接口地址**:`POST /prod-api/service-vpp/report/record/{id}/submitAudit`
+
+**功能描述**:提交报送记录进入审核流程,`recordStatus` 变更为 1。
+
+**权限**:`report:submit`
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "提交成功",
+  "data": { "recordId": 13001, "recordStatus": 1 },
+  "exception": null
+}
+```
+
+#### 2.11.7 报送审核
+
+**接口地址**:`PUT /prod-api/service-vpp/report/record/{id}/audit`
+
+**功能描述**:审核报送记录,写入 `vpp_report_audit_log`。
+
+**权限**:`report:audit`
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| passed | boolean | 是 | true 通过 / false 驳回 |
+| opinion | string | 否 | 审核意见 |
+| auditLevel | int | 是 | 1填报 2复核 3审批 |
+
+```json
+{
+  "passed": true,
+  "opinion": "数据准确",
+  "auditLevel": 2
+}
+```
+
+#### 2.11.8 执行报送
+
+**接口地址**:`POST /prod-api/service-vpp/report/record/{id}/submit`
+
+**功能描述**:向监管机构执行报送(接口推送或生成文件),失败自动重试(次数由 `vpp.report.retry.max` 配置)。
+
+**权限**:`report:submit`
+
+**响应 data 示例**:
+
+```json
+{
+  "recordId": 13001,
+  "recordStatus": 3,
+  "submitMode": 1,
+  "submittedAt": "2026-06-08T10:00:00",
+  "reportFileUrl": "https://oss.example.com/report/13001.xlsx",
+  "receiptFileUrl": "https://oss.example.com/receipt/13001.pdf"
+}
+```
+
+#### 2.11.9 历史报送查询
+
+**接口地址**:`GET /prod-api/service-vpp/report/history`
+
+**功能描述**:查询历史报送记录。
+
+**权限**:`report:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| reportOrg | int | 否 | 报送机构 |
+| reportPeriod | string | 否 | 报送周期,如 2026-05 |
+| recordStatus | int | 否 | 0草稿 1待审核 2报送中 3成功 4失败 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "id": 13001,
+  "recordNo": "REC-202605-001",
+  "taskName": "2026年5月虚拟电厂运行月报",
+  "reportOrg": 1,
+  "reportPeriod": "2026-05",
+  "recordStatus": 3,
+  "submittedAt": "2026-06-08T10:00:00",
+  "receiptFileUrl": "https://oss.example.com/receipt/13001.pdf"
+}
+```
+
+---
+
+### 2.12 统计报表与数据分析
+
+> **数据来源**:`vpp_energy_summary_daily`、`vpp_energy_reading_monthly`、`vpp_settlement_bill`、`vpp_dr_evaluation`
+
+#### 2.12.1 能源统计报表
+
+**接口地址**:`GET /prod-api/service-vpp/statistics/energy`
+
+**功能描述**:按资源/时间维度统计发用电量。
+
+**权限**:`statistics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceIds | string | 否 | 资源点 ID,逗号分隔 |
+| startDate | string | 是 | 开始日期 YYYY-MM-DD |
+| endDate | string | 是 | 结束日期 YYYY-MM-DD |
+| granularity | string | 否 | day/month,默认 day |
+
+**响应 data 示例**:
+
+```json
+{
+  "summary": { "totalGenKwh": 125000.0, "totalUseKwh": 98000.0, "greenRatio": 72.5 },
+  "series": [
+    { "date": "2026-06-01", "genKwh": 4200.0, "useKwh": 3200.0 },
+    { "date": "2026-06-02", "genKwh": 4500.0, "useKwh": 3100.0 }
+  ]
+}
+```
+
+#### 2.12.2 月度运行报告
+
+**接口地址**:`GET /prod-api/service-vpp/statistics/monthlyReport`
+
+**功能描述**:生成指定资源点月度运行报告数据。
+
+**权限**:`statistics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| resourceId | long | 否 | 资源点 ID,不传则全站 |
+| year | int | 是 | 年份 |
+| month | int | 是 | 月份 1-12 |
+
+**响应 data 示例**:
+
+```json
+{
+  "year": 2026,
+  "month": 5,
+  "resourceName": "浦东光伏站",
+  "genEnergyKwh": 85000.0,
+  "useEnergyKwh": 12000.0,
+  "maxPowerKw": 480.0,
+  "avgPowerKw": 320.5,
+  "onlineRate": 0.95,
+  "alarmCount": 3,
+  "drParticipateCount": 1
+}
+```
+
+#### 2.12.3 电费结算报表
+
+**接口地址**:`GET /prod-api/service-vpp/statistics/settlementReport`
+
+**功能描述**:电费结算汇总报表。
+
+**权限**:`statistics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| settleYear | int | 是 | 结算年 |
+| settleMonth | int | 否 | 结算月 |
+| customerId | long | 否 | 客户 ID |
+
+**响应 data 示例**:
+
+```json
+{
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "customerCount": 56,
+  "totalEnergyKwh": 680000.0,
+  "totalAmount": 850000.0,
+  "paidAmount": 620000.0,
+  "unpaidAmount": 230000.0,
+  "overdueCount": 3
+}
+```
+
+#### 2.12.4 响应结算报表
+
+**接口地址**:`GET /prod-api/service-vpp/statistics/drSettlement`
+
+**功能描述**:需求响应补贴结算汇总。
+
+**权限**:`statistics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| startDate | string | 是 | 开始日期 |
+| endDate | string | 是 | 结束日期 |
+
+**响应 data 示例**:
+
+```json
+{
+  "eventCount": 5,
+  "totalTargetCapacityKw": 6000.0,
+  "totalActualCapacityKw": 5200.0,
+  "avgQualifiedRate": 86.7,
+  "totalSubsidyAmount": 45000.0,
+  "events": [
+    {
+      "eventId": "EVE_20260623001",
+      "eventName": "夏季削峰需求响应",
+      "subsidyAmount": 7350.0,
+      "qualifiedRate": 87.5
+    }
+  ]
+}
+```
+
+#### 2.12.5 总能耗统计
+
+**接口地址**:`GET /prod-api/service-vpp/analytics/energyTotal`
+
+**功能描述**:总能耗/总费用统计,支持多维度视图。
+
+**权限**:`analytics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| view | string | 否 | amount(电量)/ cost(费用),默认 amount |
+| granularity | string | 否 | day/month/year,默认 month |
+| startDate | string | 是 | 开始日期 |
+| endDate | string | 是 | 结束日期 |
+
+**响应 data 示例**:
+
+```json
+{
+  "view": "amount",
+  "granularity": "month",
+  "total": 680000.0,
+  "unit": "kWh",
+  "points": [
+    { "period": "2026-01", "value": 620000.0 },
+    { "period": "2026-02", "value": 580000.0 }
+  ]
+}
+```
+
+#### 2.12.6 分时用电分析
+
+**接口地址**:`GET /prod-api/service-vpp/analytics/timeOfUse`
+
+**功能描述**:按尖峰平谷分析用电结构。
+
+**权限**:`analytics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| customerId | long | 否 | 客户 ID |
+| startDate | string | 是 | 开始日期 |
+| endDate | string | 是 | 结束日期 |
+
+**响应 data 示例**:
+
+```json
+{
+  "totalEnergyKwh": 12580.5,
+  "periods": [
+    { "timePeriod": 1, "name": "尖", "energyKwh": 1080.5, "ratio": 0.086 },
+    { "timePeriod": 2, "name": "峰", "energyKwh": 3200.0, "ratio": 0.254 },
+    { "timePeriod": 3, "name": "平", "energyKwh": 4500.0, "ratio": 0.358 },
+    { "timePeriod": 4, "name": "谷", "energyKwh": 3800.0, "ratio": 0.302 }
+  ]
+}
+```
+
+#### 2.12.7 同比/环比分析
+
+**接口地址**:`GET /prod-api/service-vpp/analytics/trend`
+
+**功能描述**:指标同比/环比趋势分析。
+
+**权限**:`analytics:view`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| metric | string | 是 | 指标:genKwh/useKwh/revenue/subsidy |
+| compareType | string | 是 | yoy(同比)/ mom(环比) |
+| period | string | 是 | 统计周期,如 2026-05 或 2026 |
+
+**响应 data 示例**:
+
+```json
+{
+  "metric": "genKwh",
+  "compareType": "yoy",
+  "currentPeriod": "2026-05",
+  "currentValue": 1250000.0,
+  "compareValue": 1100000.0,
+  "changeRate": 0.136,
+  "changeDirection": "up"
+}
+```
+
+#### 2.12.8 报表导出
+
+**接口地址**:`GET /prod-api/service-vpp/statistics/{type}/export`
+
+**功能描述**:导出统计报表文件。
+
+**权限**:`statistics:export`
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| type | string | 是 | energy/monthlyReport/settlementReport/drSettlement |
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| format | string | 否 | pdf/xlsx,默认 xlsx |
+| 其他 | — | — | 与对应统计接口查询参数一致 |
+
+**响应**:文件流(`Content-Disposition: attachment`)
+
+---
+
+### 2.13 系统管理
+
+> **数据表**:用户/角色/菜单 → `sys_*`;配置 → `sys_config`;字典 → `sys_dict_*`;操作日志 → `sys_oper_log`;电子档案 → `vpp_file_archive`。
+
+#### 2.13.1 用户管理
+
+| 方法 | URL | 说明 | 数据表 |
+|------|-----|------|--------|
+| GET | `/prod-api/service-vpp/system/user` | 用户列表 | `sys_user` |
+| POST | `/prod-api/service-vpp/system/user` | 创建用户 | `sys_user` |
+| GET | `/prod-api/service-vpp/system/user/{userId}` | 用户详情 | `sys_user` |
+| PUT | `/prod-api/service-vpp/system/user/{userId}` | 更新用户 | `sys_user` |
+| DELETE | `/prod-api/service-vpp/system/user/{userId}` | 禁用/删除 | `sys_user`(`del_flag`) |
+| PUT | `/prod-api/service-vpp/system/user/{userId}/role` | 分配角色 | `sys_user_role` |
+| PUT | `/prod-api/service-vpp/system/user/{userId}/resetPassword` | 重置密码 | `sys_user` |
+
+**查询参数(列表)**:`userName`、`nickName`、`phonenumber`、`status`、`deptId`、`tenantId`、`current`、`size`
+
+**创建用户请求体示例**:
+
+```json
+{
+  "userName": "operator02",
+  "nickName": "王五",
+  "password": "******",
+  "phonenumber": "13800138001",
+  "email": "wangwu@example.com",
+  "deptId": 101,
+  "status": "0",
+  "roleIds": [10, 11],
+  "tenantId": 1
+}
+```
+
+**用户列表响应 data.records 元素**(映射 `sys_user`):
+
+```json
+{
+  "userId": 1001,
+  "userName": "operator01",
+  "nickName": "张三",
+  "phonenumber": "13800138000",
+  "email": "zhangsan@example.com",
+  "deptId": 101,
+  "deptName": "运营部",
+  "status": "0",
+  "tenantId": 1,
+  "createTime": "2026-01-01T10:00:00",
+  "roles": [{ "roleId": 10, "roleName": "运营人员" }]
+}
+```
+
+**分配角色请求体** — `PUT /system/user/{userId}/role`:
+
+```json
+{ "roleIds": [10, 11] }
+```
+
+**重置密码请求体** — `PUT /system/user/{userId}/resetPassword`:
+
+```json
+{ "password": "NewPass@123" }
+```
+
+#### 2.13.2 角色与菜单权限
+
+| 方法 | URL | 说明 | 数据表 |
+|------|-----|------|--------|
+| GET | `/prod-api/service-vpp/system/role` | 角色列表 | `sys_role` |
+| POST | `/prod-api/service-vpp/system/role` | 创建角色 | `sys_role` |
+| GET | `/prod-api/service-vpp/system/role/{roleId}` | 角色详情 | `sys_role` |
+| PUT | `/prod-api/service-vpp/system/role/{roleId}` | 更新角色 | `sys_role` |
+| DELETE | `/prod-api/service-vpp/system/role/{roleId}` | 删除角色 | `sys_role` |
+| GET | `/prod-api/service-vpp/system/menu` | 菜单树(含按钮权限) | `sys_menu` |
+| GET | `/prod-api/service-vpp/system/role/{roleId}/menu` | 角色已分配菜单 | `sys_role_menu` |
+| PUT | `/prod-api/service-vpp/system/role/{roleId}/menu` | 分配菜单 | `sys_role_menu` |
+
+**分配菜单请求体**:
+
+```json
+{
+  "menuIds": [1001, 1002, 1003]
+}
+```
+
+> 按钮级权限由 `sys_menu.perms` 标识(如 `alarm:manage`),前端按 `menuType`(M 目录 / C 菜单 / F 按钮)渲染。
+
+**角色列表响应 data.records 元素**(映射 `sys_role`):
+
+```json
+{
+  "roleId": 10,
+  "roleName": "运营人员",
+  "roleKey": "vpp:operator",
+  "roleSort": 1,
+  "status": "0",
+  "remark": "虚拟电厂运营角色",
+  "createTime": "2026-01-01T10:00:00"
+}
+```
+
+**创建/更新角色请求体**:
+
+```json
+{
+  "roleName": "运营人员",
+  "roleKey": "vpp:operator",
+  "roleSort": 1,
+  "status": "0",
+  "remark": "虚拟电厂运营角色"
+}
+```
+
+**菜单树响应 data 示例**(映射 `sys_menu`):
+
+```json
+[
+  {
+    "menuId": 1000,
+    "menuName": "虚拟电厂",
+    "parentId": 0,
+    "menuType": "M",
+    "path": "/vpp",
+    "perms": null,
+    "children": [
+      {
+        "menuId": 1001,
+        "menuName": "告警管理",
+        "parentId": 1000,
+        "menuType": "C",
+        "path": "alarm",
+        "perms": "alarm:view",
+        "children": [
+          { "menuId": 1002, "menuName": "告警处理", "menuType": "F", "perms": "alarm:manage" }
+        ]
+      }
+    ]
+  }
+]
+```
+
+#### 2.13.3 系统配置
+
+| 方法 | URL | 数据表 |
+|------|-----|--------|
+| GET | `/prod-api/service-vpp/system/config` | `sys_config` |
+| GET | `/prod-api/service-vpp/system/config/{configKey}` | `sys_config` |
+| PUT | `/prod-api/service-vpp/system/config/{configKey}` | `sys_config` |
+
+**查询参数**:`configName`、`tenantId`
+
+**更新配置请求体**:
+
+```json
+{
+  "configValue": "900",
+  "configName": "默认采集频率秒",
+  "remark": "VPP 全局采集间隔"
+}
+```
+
+VPP 推荐配置键(`config_key`):
+
+| configKey | 说明 |
+|-----------|------|
+| vpp.collect.interval.default | 默认采集频率(秒) |
+| vpp.data.retention.years | 时序数据保留年数 |
+| vpp.alarm.escalate.minutes | 告警升级间隔(分钟) |
+| vpp.report.retry.max | 报送失败最大重试次数 |
+| vpp.dr.notify.minutes.default | 需求响应默认提前通知(分钟) |
+
+#### 2.13.4 数据字典
+
+| 方法 | URL | 数据表 |
+|------|-----|--------|
+| GET | `/prod-api/service-vpp/system/dict/type` | `sys_dict_type` |
+| GET | `/prod-api/service-vpp/system/dict/data/{dictType}` | `sys_dict_data` |
+
+**字典类型列表响应 data 示例**:
+
+```json
+[
+  {
+    "dictId": 1,
+    "dictName": "客户类型",
+    "dictType": "vpp_customer_type",
+    "status": "0",
+    "remark": "VPP客户类型枚举"
+  }
+]
+```
+
+**字典数据响应 data 示例**:
+
+```json
+[
+  { "dictCode": 1, "dictLabel": "高压", "dictValue": "1", "dictSort": 1, "status": "0" },
+  { "dictCode": 2, "dictLabel": "低压商用", "dictValue": "2", "dictSort": 2, "status": "0" },
+  { "dictCode": 3, "dictLabel": "居民充电桩", "dictValue": "3", "dictSort": 3, "status": "0" },
+  { "dictCode": 4, "dictLabel": "自有资产", "dictValue": "4", "dictSort": 4, "status": "0" }
+]
+```
+
+#### 2.13.5 操作日志
+
+**接口地址**:`GET /prod-api/service-vpp/system/operLog`
+
+**功能描述**:分页查询系统操作审计日志。
+
+**权限**:`system:operLog:view`
+
+**数据表**:`sys_oper_log`
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| operName | string | 否 | 操作人账号 |
+| title | string | 否 | 模块标题 |
+| businessType | int | 否 | 业务类型 |
+| status | int | 否 | 0正常 1异常 |
+| startTime | string | 否 | 操作时间起 |
+| endTime | string | 否 | 操作时间止 |
+| tenantId | long | 否 | 租户 ID |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "operId": 181001,
+  "title": "告警管理",
+  "businessType": 2,
+  "operName": "operator01",
+  "operUrl": "/prod-api/service-vpp/alarm/158001/handle",
+  "operIp": "10.0.0.12",
+  "status": 0,
+  "operTime": "2026-06-23T10:20:00.000+08:00",
+  "consumingTime": 125
+}
+```
+
+#### 2.13.6 电子档案
+
+| 方法 | URL | 说明 | 数据表 |
+|------|-----|------|--------|
+| GET | `/prod-api/service-vpp/archive` | 档案列表 | `vpp_file_archive` |
+| POST | `/prod-api/service-vpp/archive` | 上传档案(multipart) | `vpp_file_archive` |
+| GET | `/prod-api/service-vpp/archive/{id}` | 档案详情/预览 | `vpp_file_archive` |
+| GET | `/prod-api/service-vpp/archive/{id}/download` | 下载 | `vpp_file_archive` |
+| DELETE | `/prod-api/service-vpp/archive/{id}` | 软删除 | `vpp_file_archive` |
+
+**查询参数**:`bizType`(CONTRACT / CUSTOMER / RESOURCE 等)、`bizId`、`fileType`、`current`、`size`
+
+**上传表单字段**:
+
+| 字段 | 必填 | 说明 |
+|------|------|------|
+| file | 是 | 文件流 |
+| bizType | 是 | 业务类型 |
+| bizId | 是 | 业务主键 ID |
+| remark | 否 | 备注 |
+
+**响应 data 示例**:
+
+```json
+{
+  "id": 5001,
+  "fileName": "需求响应合作协议.pdf",
+  "fileUrl": "https://oss.example.com/vpp/contract/5001.pdf",
+  "fileType": "pdf",
+  "fileSize": 2048576,
+  "bizType": "CONTRACT",
+  "bizId": 3001,
+  "createdBy": 1001,
+  "createTime": "2026-06-23T09:00:00.000+08:00"
+}
+```
+
+---
+
+### 2.14 移动端专用
+
+移动端与 Web 共用 `/prod-api/service-vpp` 接口,以下补充移动端典型场景。
+
+#### 2.14.1 运营端首页
+
+**接口地址**:`GET /prod-api/service-vpp/mobile/operator/home`
+
+**功能描述**:运营端移动端首页聚合数据。
+
+**权限**:`dashboard:view`
+
+**响应 data 示例**:
+
+```json
+{
+  "summary": {
+    "todayGenKwh": 12580.5,
+    "onlineDeviceCount": 128,
+    "pendingAlarmCount": 5,
+    "activeDrEventCount": 1
+  },
+  "recentAlarms": [
+    { "id": 158001, "alarmContent": "功率超限110%", "alarmTime": "2026-06-23T10:15:00", "alarmGrade": 2 }
+  ],
+  "activeDrEvents": [
+    { "id": 6001, "eventName": "夏季削峰需求响应", "startTime": "2026-06-23T14:00:00", "eventStatus": 2 }
+  ]
+}
+```
+
+#### 2.14.2 业主端我的资源
+
+**接口地址**:`GET /prod-api/service-vpp/mobile/owner/resource`
+
+**功能描述**:按当前登录用户关联的 `customerId` 过滤资源列表及实时状态。
+
+**权限**:已登录业主用户
+
+**响应 data 示例**:
+
+```json
+{
+  "customerId": 1001,
+  "customerName": "上海某某有限公司",
+  "resources": [
+    {
+      "resourceId": 2001,
+      "resourceName": "浦东光伏站",
+      "resourceType": "PV",
+      "capacityKw": 500.0,
+      "runStatus": 1,
+      "activePowerKw": 320.5,
+      "todayGenKwh": 1250.0
+    }
+  ]
+}
+```
+
+#### 2.14.3 业主端账单
+
+**接口地址**:`GET /prod-api/service-vpp/mobile/owner/bill`
+
+**功能描述**:业主端查看本人账单列表。
+
+**权限**:已登录业主用户
+
+**请求参数(Query)**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| paymentStatus | int | 否 | 缴费状态 |
+| current | int | 否 | 页码 |
+| size | int | 否 | 每页数量 |
+
+**响应 data.records 元素**:
+
+```json
+{
+  "id": 10001,
+  "billNo": "BILL-202605-1001",
+  "settleYear": 2026,
+  "settleMonth": 5,
+  "totalAmount": 15680.0,
+  "paidAmount": 0,
+  "paymentStatus": 0,
+  "dueDate": "2026-06-15"
+}
+```
+
+#### 2.14.4 消息通知
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/prod-api/service-vpp/mobile/message` | 消息列表 |
+| PUT | `/prod-api/service-vpp/mobile/message/{id}/read` | 标记已读 |
+
+**消息列表查询参数**:`msgType`(alarm/dr/bill/system)、`isRead`(0/1)、`current`、`size`
+
+**消息列表响应 data.records 元素**(关联 `base_alarm_notice_result` / `sys_notice`):
+
+```json
+{
+  "id": 14001,
+  "msgType": "alarm",
+  "title": "功率超限告警",
+  "content": "浦东光伏站功率超过额定110%",
+  "isRead": 0,
+  "bizId": 158001,
+  "createTime": "2026-06-23T10:15:00"
+}
+```
+
+**标记已读响应**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "操作成功",
+  "data": null,
+  "exception": null
+}
+```
+
+#### 2.14.5 微信订阅消息绑定
+
+**接口地址**:`POST /prod-api/service-vpp/mobile/wechat/subscribe`
+
+**功能描述**:绑定微信 openId 并订阅消息模板。
+
+**权限**:已登录用户
+
+**请求体**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| code | string | 是 | 微信登录 code |
+| templateIds | array | 是 | 订阅消息模板 ID 列表 |
+
+```json
+{
+  "code": "wx_login_code_xxx",
+  "templateIds": ["TEMPLATE_ALARM_001", "TEMPLATE_DR_001"]
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "status": "SUCCESS",
+  "code": 200,
+  "msg": "绑定成功",
+  "data": { "openId": "oXXXX", "subscribedCount": 2 },
+  "exception": null
+}
+```
+
+---
+
+### 2.15 运管平台主动对接(Web 管理)
+
+> 网关前缀:`/prod-api/service-vpp/un`  
+> 实现类:`UnIntegrationController` → `VppUnIntegrationService`  
+> 用于联调阶段手动触发 Token、注册、Poll;生产环境建议配合 `poll-enabled` 定时任务。
+
+#### 2.15.1 刷新 UN Token
+
+**接口地址**:`POST /prod-api/service-vpp/un/token/refresh`
+
+**功能描述**:向 UN 发送 `TokenRequest`,刷新本地 Token 缓存(默认 25 分钟有效,UN 侧 JWT 约 30 分钟)。
+
+**权限**:`system:admin` 或联调专用角色(建议)
+
+**前置条件**:`vpp.un.outbound-enabled=true` 且已配置 `vpp.un.base-url`、`vpp.un.dn-id`
+
+**响应 data**:UN `TokenResponse` 原文(含 `token` 字段)
+
+#### 2.15.2 注册 UN
+
+**接口地址**:`POST /prod-api/service-vpp/un/register`
+
+**功能描述**:向 UN 发送 `CreateRegistrationRequest`,成功后 `registrationID` 写入 `vpp_registration` 及配置缓存。
+
+**响应 data**:UN `CreateRegistrationResponse` 原文
+
+#### 2.15.3 手动 Poll
+
+**接口地址**:`POST /prod-api/service-vpp/un/poll`
+
+**功能描述**:向 UN 发送 `Poll`,解析响应并自动入库/应答:
+
+| Poll 响应 root | 平台动作 |
+|----------------|----------|
+| PollResponse | 无待处理指令 |
+| DistributeEventRequest | 入库 `vpp_dr_event`,同步参与记录 |
+| ReregistrationRequest | 自动重新注册 |
+| CreateReportRequest | 回 `CreateReportResponse` 并按类型上报报告 |
+
+**响应 data**:UN Poll 响应原文
+
+#### 2.15.4 配置项(Nacos / bootstrap.yml)
+
+| 配置键 | 类型 | 默认 | 说明 |
+|--------|------|------|------|
+| vpp.un.base-url | string | — | UN 基地址,如 `https://27.115.117.6:32001` |
+| vpp.un.dn-id | string | — | 运营商 ID |
+| vpp.un.dn-name | string | — | 运营商名称(注册报文) |
+| vpp.un.transport-address | string | — | DN 公网回调地址 |
+| vpp.un.outbound-enabled | boolean | false | 启用 DN→UN 主动调用 |
+| vpp.un.poll-enabled | boolean | false | 启用 Poll 定时任务 |
+| vpp.un.poll-interval-sec | int | 10 | Poll 间隔(秒) |
+| vpp.un.crypto-enabled | boolean | false | 启用 SM2/SM3 加解密 |
+| vpp.un.un-public-key | string | — | UN 公钥 Base64 |
+| vpp.un.dn-public-key | string | — | DN 公钥 Base64 |
+| vpp.un.dn-private-key | string | — | DN 私钥 Base64 |
+| vpp.un.price-down-coeff | string | 0.8 | 申报价格下调系数 |
+| vpp.un.token-header | string | Authorization | Token 请求头 |
+| vpp.un.token-prefix | string | Bearer  | Token 前缀 |
+| vpp.un.token-ttl-minutes | int | 25 | 本地 Token 缓存分钟数 |
+| vpp.un.auto-ack-clearing | boolean | false | Poll 出清公示后自动 CreateEventResponse |
+| vpp.un.auto-register-on-startup | boolean | false | 启动时自动 Token + 注册 |
+
+---
+
+## 3. 运管平台对接 API(UN/DN)
+
+### 3.1 交互概述
+
+依据 DLT 1867—2024 及《虚拟电厂接口联调须知》,虚拟电厂运营商平台(**DN**)与运管平台(**UN**)通过 REST(HTTP) 交互。
+
+**角色说明**:
+
+- **DN**:本平台,需暴露 HTTP 服务供 UN 调用,同时主动调用 UN
+- **UN**:上海虚拟电厂运管平台
+
+**交互模式**:
+
+| 模式 | 说明 |
+|------|------|
+| 主动推送 | DN 向 UN 发送注册、数据报告 |
+| 被动拉取 | UN 通过 Poll 下发事件、报告请求、重新注册指令 |
+| 轮询 | DN 定时 Poll(建议 ≤30s,注册返回 `pollFreq` 如 PT10S) |
+
+**联调顺序(摘要)**:
+
+1. 获取 Token → 创建注册 → 元数据报告注册
+2. 实时/曲线数据上报
+3. 启动 Poll → 拉取事件 → 参与申报 → 出清分拆 → 确认公示
+4. 负荷预测/可调节能力预测上报
+
+### 3.2 安全机制
+
+| 项目 | 规范 |
+|------|------|
+| 传输 | HTTPS |
+| 请求方式 | POST |
+| 请求体加密 | SM2,使用 **UN 公钥**加密,Base64 编码 |
+| 数字签名 | SM3withSM2,使用 **DN 私钥**签名,Base64 后放入请求头 `X-Sign` |
+| 响应验签 | 从响应头取 `X-Sign`,UN 公钥验签 |
+| 响应解密 | Base64 解码后,DN 私钥 SM2 解密 |
+| Token | JWT,请求头 `Authorization: Bearer {token}`,有效期 **30 分钟** |
+| DN 被动接口 | UN 调用 DN 时:`crypto-enabled=true` 则请求体 SM2 解密 + `X-Sign` 验签;响应体 SM2 加密 + 签名(`VppUnDnCryptoRequestAdvice` / `ResponseAdvice`) |
+
+**密钥**:UN 分配 UN 公钥、DN 公钥、DN 私钥(均 Base64 编码)。联调初期可设 `vpp.un.crypto-enabled=false` 走明文 JSON。
+
+### 3.3 通用服务
+
+#### 3.3.1 获取 Token
+
+**接口地址**:`POST {UN_BASE}/TokenRequest`
+
+**调用方**:DN → UN
+
+**落库**:无(Token 缓存 Redis)
+
+**请求体(明文结构,实际需 SM2 加密 + SM3withSM2 签名)**:
+
+| 字段名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| root | string | 是 | 固定 `TokenRequest` |
+| version | int | 是 | 协议版本,当前为 1 |
+| requestID | string | 是 | UUID,请求唯一标识 |
+| dnID | string | 是 | 虚拟电厂运营商 ID |
+
+```json
+{
+  "root": "TokenRequest",
+  "version": 1,
+  "requestID": "bf992657-d1b1-4634-badd-2be6f1275e4f",
+  "dnID": "10010103000002"
+}
+```
+
+**响应体(TokenResponse)**:
+
+```json
+{
+  "root": "TokenResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "bf992657-d1b1-4634-badd-2be6f1275e4f",
+  "dnID": "10010103000002",
+  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
+}
+```
+
+| 响应字段 | 类型 | 说明 |
+|----------|------|------|
+| root | string | `TokenResponse` |
+| code | int | 200 成功 |
+| description | string | 结果描述 |
+| requestID | string | 与请求一致 |
+| dnID | string | 运营商 ID |
+| token | string | JWT,有效期 30 分钟 |
+
+---
+
+### 3.4 注册服务
+
+#### 3.4.1 创建注册请求
+
+**接口地址**:`POST {UN_BASE}/CreateRegistrationRequest`
+
+**功能描述**:首次注册或重新注册,成功后持久化至 `vpp_registration`。
+
+**调用方**:DN → UN
+
+**请求体(CreateRegistrationRequest)**:
+
+```json
+{
+  "root": "CreateRegistrationRequest",
+  "version": 1,
+  "requestID": "8c03c417-3147-4d33-9d00-1ebbd71b7b09",
+  "dnID": "10010103000002",
+  "dnName": "上海市自动需求响应平台",
+  "registrationID": "208916e4-5b3f-440c-b60c-ffd5b6c37f2a",
+  "reportOnly": false,
+  "pullMode": true,
+  "signature": false,
+  "transport": "REST",
+  "transportAddress": "http://27.115.117.6:9000"
+}
+```
+
+| 字段 | 说明 |
+|------|------|
+| registrationID | 首次注册为空;重新注册必须携带首次返回的 ID |
+| transportAddress | DN 服务公网地址,供 UN 回调 |
+| pullMode | true 表示 UN 通过 Poll 下发指令 |
+
+**响应字段说明**(映射 `vpp_registration`):
+
+| 响应字段 | 类型 | 说明 | 落库字段 |
+|----------|------|------|----------|
+| registrationID | string | 注册 ID | vpp_registration.registration_id |
+| pollFreq | string | Poll 频率 ISO8601 | — |
+| unID | string | 运管平台 ID | — |
+| code | int | 200 成功 | vpp_registration.reg_status=1 |
+
+**响应体(CreateRegistrationResponse)**:
+
+```json
+{
+  "root": "CreateRegistrationResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "8c03c417-3147-4d33-9d00-1ebbd71b7b09",
+  "dnID": "10010103000002",
+  "unID": "test_un",
+  "registrationID": "93eaa056-8553-4eb8-8113-15590cb98224",
+  "pollFreq": "PT10S",
+  "transport": ["REST"],
+  "serviceSpecific": []
+}
+```
+
+> **重要**:`registrationID` 需持久化至 `vpp_registration` 表,重新注册时必带,否则返回 `WrongRegistrationID`。
+
+#### 3.4.2 DN 侧被动接口(UN 调用 DN)
+
+DN 需实现 HTTP POST 服务端点(`UnDnController`,默认端口 **9906**,路径与服务名一致)。联调时 UN 访问 `{transportAddress}/{服务名}`。
+
+| 服务名 | DN 路径 | 说明 | 实现状态 |
+|--------|---------|------|----------|
+| TokenRequest | `/TokenRequest` | Token 请求(联调回显) | 已实现 |
+| CreateRegistrationRequest | `/CreateRegistrationRequest` | 注册(联调回显) | 已实现 |
+| RegisterReportRequest | `/RegisterReportRequest` | 元数据报告注册回显 | 已实现 |
+| MomentDataReportRequest | `/MomentDataReportRequest` | 实时数据报告回显 | 已实现 |
+| IntervalDataReportRequest | `/IntervalDataReportRequest` | 曲线数据报告回显 | 已实现 |
+| Poll | `/Poll` | UN 询问 DN(联调回显 PollResponse) | 已实现 |
+| **DistributeEventRequest** | `/DistributeEventRequest` | **事件邀约/反馈/出清公示推送** | **已实现,入库 vpp_dr_event** |
+| CreateOptRequest | `/CreateOptRequest` | 参与申报同步 + CreateOptResponse | 已实现 |
+| CreateCqRequest | `/CreateCqRequest` | 分拆出清同步 + CreateCqResponse | 已实现 |
+| CreateEventResponse | `/CreateEventResponse` | 出清公示确认 + 标准响应 | 已实现 |
+
+**被动接口成功响应扩展字段**(便于联调):
+
+| 字段 | 说明 |
+|------|------|
+| eventID | 运管平台事件 ID |
+| internalId | 本地 `vpp_dr_event.id` |
+| eventStatus | 本地事件状态(0~4) |
+
+---
+
+### 3.5 询问服务(Poll)
+
+#### 3.5.1 Poll 请求
+
+**接口地址**:`POST {UN_BASE}/Poll`
+
+**功能描述**:DN 定时轮询 UN,获取待处理指令(事件、报告请求、重新注册等)。
+
+**调用方**:DN → UN
+
+**频率**:注册返回 `pollFreq`,建议 ≤30s
+
+**请求体**:
+
+| 字段名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| root | string | 是 | 固定 `Poll` |
+| version | int | 是 | 协议版本 |
+| requestID | string | 是 | UUID |
+| dnID | string | 是 | 运营商 ID |
+
+```json
+{
+  "root": "Poll",
+  "version": 1,
+  "requestID": "bf992657-d1b1-4634-badd-2be6f1275e4f",
+  "dnID": "10010103000002"
+}
+```
+
+**无指令时响应(PollResponse)**:
+
+```json
+{
+  "root": "PollResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "bf992657-d1b1-4634-badd-2be6f1275e4f",
+  "dnID": "10010103000002"
+}
+```
+
+#### 3.5.2 Poll 可返回的指令类型
+
+| 响应 root | 说明 | DN 后续动作(service-vpp 自动/手动) |
+|-----------|------|--------------------------------------|
+| PollResponse | 无待处理指令 | 无 |
+| ReregistrationRequest | 重新注册 | `VppUnIntegrationService.register()` |
+| DistributeEventRequest | 事件邀约/申报反馈/分拆通知/出清公示 | `VppDrEventIngestService.ingestDistributeEvent` |
+| CreateReportRequest | 拉取数据报告 | `VppUnReportService.handleCreateReportRequest` |
+
+**定时 Poll**:`vpp.un.poll-enabled=true` 时,`VppUnPollScheduler` 按 `poll-interval-sec` 执行(默认 10s)。
+
+**Token 失效重试**:HTTP 客户端检测 `TokenExpiredException` 等消息后自动刷新 Token 并重试一次。
+
+---
+
+### 3.6 报告服务
+
+#### 3.6.0 CreateReportRequest 应答流程(Poll 触发)
+
+UN 通过 Poll 返回 `CreateReportRequest` 后,DN 按以下顺序处理(`VppUnReportServiceImpl`):
+
+1. `POST {UN_BASE}/CreateReportResponse` — 确认收到报告请求  
+2. 按 `reportRequestID` 上报对应报告:
+
+| reportRequestID | 后续上报接口 | 数据来源(当前实现) |
+|---------------|--------------|----------------------|
+| MetaDataReport | RegisterReportRequest | `vpp_resource_point`(unResourceId / resourceCode) |
+| IntervalDataReport | IntervalDataReportRequest | 在线资源点 adjustableKw(占位,待接 TDengine 96 点) |
+| MomentDataReport | MomentDataReportRequest | 同上 |
+
+> 完整 96 点曲线、能力预测等需后续对接 TDengine 与预测服务。
+
+#### 3.6.1 元数据报告注册
+
+**接口地址**:`POST {UN_BASE}/RegisterReportRequest`
+
+**功能描述**:向 UN 注册可上报的数据维度与量测点定义。
+
+**调用方**:DN → UN
+
+**落库**:`vpp_registration.meta_report_id`
+
+**请求体(摘要)**:
+
+```json
+{
+  "root": "RegisterReportRequest",
+  "version": 1,
+  "requestID": "53dc702a-e400-4b06-bb06-335ea876171b",
+  "dnID": "10010103000002",
+  "reportRequestID": "MetaDataReport",
+  "report": [
+    {
+      "createdDateTime": "2019-11-30 12:00:00",
+      "reportDescription": {
+        "rID": 0,
+        "metric": { "metricName": "AP", "multiplier": "k", "symbol": "W" },
+        "reportDataSource": { "resourceID": ["10010103000002"] },
+        "readingType": "Direct_Read",
+        "samplingRate": { "minPeriod": "PT15M", "maxPeriod": "PT15M" }
+      }
+    }
+  ]
+}
+```
+
+**数据维度**:虚拟电厂运营商、聚合用户(户号)、分路可调节资源。
+
+**主要量测点(MetricName)**:
+
+| 量测点 | 单位 | 说明 |
+|--------|------|------|
+| AP | kW | 有功功率 |
+| AP_E | kWh | 用电量 |
+| AP_PE | kWh | 发电量 |
+| 实时上调/下调能力 | kW | 仅运营商维度 |
+| 实时响应时间 | s | 仅运营商维度 |
+| 实时爬坡速度 | kW/min | 仅运营商维度 |
+| 日前申报上调/下调能力 | kW | 预测数据 |
+
+**响应体(RegisterReportResponse)**:
+
+```json
+{
+  "root": "RegisterReportResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "53dc702a-e400-4b06-bb06-335ea876171b",
+  "dnID": "10010103000002",
+  "reportRequestID": "MetaDataReport"
+}
+```
+
+**上报日志**:每次调用写入 `vpp_report_log`(`report_type=RegisterReport`)。
+
+#### 3.6.2 实时数据报告(MomentDataReportRequest)
+
+**接口地址**:`POST {UN_BASE}/MomentDataReportRequest`
+
+**功能描述**:上报 15 分钟级实时运行数据及能力数据。
+
+**数据来源**:TDengine `device_metrics` 聚合
+
+**落库**:`vpp_report_log`(`report_type=MomentData`)
+
+| 项目 | 规格 |
+|------|------|
+| 密度 | 15 分钟 |
+| 频率 | 每 15 分钟,24 小时不间断 |
+| 延迟 | ≤ 30 分钟 |
+| 时标含义 | 00:15:00 代表 00:00:00~00:15:00 的平均/累加 |
+
+**请求体示例**:
+
+```json
+{
+  "root": "MomentDataReportRequest",
+  "version": 1,
+  "requestID": "a4af721e-6a52-4191-af84-71b778d48d6f",
+  "dnID": "10010103000002",
+  "reportRequestID": "MomentDataReport",
+  "createdDateTime": "2026-06-23 10:15:00",
+  "report": [
+    {
+      "resourceID": "10010103000002",
+      "metricName": "AP",
+      "value": 1250.5,
+      "multiplier": "k",
+      "symbol": "W"
+    },
+    {
+      "resourceID": "RES-PV-001",
+      "metricName": "AP",
+      "value": 320.5,
+      "multiplier": "k",
+      "symbol": "W"
+    }
+  ]
+}
+```
+
+**能力数据**:须提前 30 分钟上报(如 9:00 上报 9:30 的能力)。
+
+#### 3.6.3 曲线数据报告(IntervalDataReportRequest)
+
+**接口地址**:`POST {UN_BASE}/IntervalDataReportRequest`
+
+**功能描述**:上报上一日 96 点有功功率曲线及日前申报能力。
+
+**数据来源**:TDengine 日聚合 + `vpp_energy_summary_daily`
+
+**落库**:`vpp_report_log`(`report_type=IntervalData`)
+
+| 项目 | 规格 |
+|------|------|
+| 频率 | 每日一次 |
+| 截止 | 次日 06:00 前 |
+| 数据点 | 96 点/日(15 分钟间隔,00:15~次日 00:00) |
+| 内容 | 上日有功功率 96 点 + 日前申报能力等 |
+
+**请求体示例(节选)**:
+
+```json
+{
+  "root": "IntervalDataReportRequest",
+  "version": 1,
+  "requestID": "b5bf832f-7b63-5202-bg95-82c889e59g7g",
+  "dnID": "10010103000002",
+  "reportRequestID": "IntervalDataReport",
+  "createdDateTime": "2026-06-24 05:30:00",
+  "report": [
+    {
+      "resourceID": "10010103000002",
+      "metricName": "AP",
+      "intervals": [
+        { "time": "2026-06-23 00:15:00", "value": 980.0 },
+        { "time": "2026-06-23 00:30:00", "value": 965.5 }
+      ]
+    }
+  ]
+}
+```
+
+#### 3.6.4 可调节能力负荷预测(RegulateForecastReportRequest)
+
+**接口地址**:`POST {UN_BASE}/RegulateForecastReportRequest`
+
+**功能描述**:上报次日可调节能力负荷预测数据。
+
+**调用方**:DN → UN
+
+**数据来源**:`vpp_customer`(dr_up/down_capacity_kw)+ 资源预测算法
+
+**落库**:`vpp_report_log`(`report_type=RegulateForecast`)
+
+**报送要求**:每日 6:00 前完成次日数据报送;须包含 96 点预测数据。
+
+**请求体示例(节选)**:
+
+```json
+{
+  "root": "RegulateForecastReportRequest",
+  "version": 1,
+  "requestID": "c6cg943g-8c74-6313-ch06-93d990f60h8h",
+  "dnID": "10010103000002",
+  "forecastDate": "2026-06-24",
+  "upCapacityKw": [
+    { "time": "2026-06-24 00:15:00", "value": 1500.0 }
+  ],
+  "downCapacityKw": [
+    { "time": "2026-06-24 00:15:00", "value": 2000.0 }
+  ]
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "root": "RegulateForecastReportResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "c6cg943g-8c74-6313-ch06-93d990f60h8h",
+  "dnID": "10010103000002"
+}
+```
+
+#### 3.6.5 数据报告统一响应(DataReportResponse)
+
+```json
+{
+  "root": "DataReportResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "a4af721e-6a52-4191-af84-71b778d48d6f",
+  "dnID": "10010103000002"
+}
+```
+
+---
+
+### 3.7 事件与参与服务
+
+#### 3.7.1 事件下发(DistributeEventRequest)
+
+通过 **Poll 返回** 或 **UN 推送** `POST {DN}/DistributeEventRequest` 到达 DN,解析后写入 `vpp_dr_event`(`raw_payload` 存原文)。
+
+**Event 结构(联调 JSON 20251127)**:
+
+| 节点 | 说明 |
+|------|------|
+| events[].descriptor | eventID、notification、control、filing、lastFiling、deadline 等 |
+| events[].activePeriod[] | dtstart、dtend(活动期,可多段) |
+| events[].signals.signal[] | ENERGY_PRICE → subsidyPrice;DEMAND_CHARGE → targetCapacityKw |
+| events[].target.resources[] | 户号申报/出清明细(account + values[].value) |
+
+**descriptor 关键字段映射(入库)**:
+
+| UN 字段 | 本地字段 | 映射规则 |
+|---------|----------|----------|
+| notification | responseType | mediumterm→1 日前,days→2 日内,near→3 秒级 |
+| control | eventType | peakClipping→1 削峰,fillValley→2 填谷 |
+| DEMAND_CHARGE signal | targetCapacityKw | 取 irregular.values[0].value |
+| ENERGY_PRICE signal | subsidyPrice | 取 irregular.values[0].value |
+| filing/lastFiling/resources | eventStatus 阶段 | 见 `VppUnEventPhase` |
+
+**业务阶段识别(VppUnEventPhase)**:
+
+| 阶段 | 条件 | 本地状态 |
+|------|------|----------|
+| INVITATION | filing=true,resources 空 | 0 待参与 |
+| DECLARE_FEEDBACK | filing=true,resources 有申报量 | 1 已申报 |
+| SPLIT_NOTICE | filing=false,lastFiling=true | 1 已申报 |
+| CLEARING_PUBLICITY | resources 含出清量 | 1 已申报(待 ack-clearing) |
+
+**Poll/推送示例(节选,完整样例见 §5.6)**:
+
+```json
+{
+  "root": "DistributeEventRequest",
+  "version": 1,
+  "requestID": "a18b7934-43fe-4cae-bafc-5d185dd21e34",
+  "dnID": "10010103000052",
+  "events": [
+    {
+      "descriptor": {
+        "eventID": "YY#1100_10010103000052",
+        "control": "peakClipping",
+        "notification": "mediumterm",
+        "filing": true,
+        "deadline": "2025-11-28 12:59:00"
+      },
+      "activePeriod": [
+        { "dtstart": "2025-11-28T13:00:00", "dtend": "2025-11-28T18:00:00" }
+      ],
+      "signals": {
+        "signal": [
+          {
+            "signalName": "ENERGY_PRICE",
+            "intervals": { "irregular": { "values": [{ "value": 6 }] } }
+          },
+          {
+            "signalName": "DEMAND_CHARGE",
+            "intervals": { "irregular": { "values": [{ "value": 400 }] } }
+          }
+        ]
+      },
+      "target": { "resources": [] }
+    }
+  ]
+}
+```
+
+**参与记录同步**:`target.resources[]` 或 `CreateOptRequest.list[]` 中 `account` 匹配 `vpp_customer.account_no`,写入 `vpp_dr_participation`。
+
+#### 3.7.2 创建参与请求(CreateOptRequest)
+
+**接口地址**:`POST {UN_BASE}/CreateOptRequest`
+
+**功能描述**:向 UN 申报参与(optIn)或拒绝(optOut)。
+
+**触发方式**:
+
+- Web:`POST /dr/event/{id}/participate`(outbound 启用时自动上报)
+- 联调:UN 推送至 DN `/CreateOptRequest`(本地同步 + 回显 CreateOptResponse)
+
+**参与请求体(optIn,官方格式)**:
+
+```json
+{
+  "root": "CreateOptRequest",
+  "version": 1,
+  "requestID": "111",
+  "dnID": "10010103000052",
+  "eventID": "YY#1100_10010103000052",
+  "optType": "optIn",
+  "createdDateTime": "2025-11-27 13:34:00",
+  "priceDownCoeff": "0.8",
+  "list": [
+    { "load": "10000", "account": "3101330499683", "needzk": "是" },
+    { "load": "90000", "account": "3100149000967", "needzk": "否" }
+  ]
+}
+```
+
+| 字段 | 说明 |
+|------|------|
+| optType | optIn 参与 / optOut 拒绝 |
+| list[].account | 电力户号(对应 vpp_customer.account_no) |
+| list[].load | 申报容量 kW(字符串) |
+| list[].needzk | 是否需要直控:是/否 |
+| priceDownCoeff | 价格下调系数,默认 0.8 |
+
+**拒绝请求体(optOut)**:
+
+```json
+{
+  "root": "CreateOptRequest",
+  "version": 1,
+  "requestID": "111",
+  "dnID": "10010103000052",
+  "eventID": "YY#1100_10010103000052",
+  "optType": "optOut"
+}
+```
+
+**响应(CreateOptResponse)**:回显请求字段 + `code=200`。
+
+#### 3.7.3 分拆出清申报(CreateCqRequest)
+
+**接口地址**:`POST {UN_BASE}/CreateCqRequest`
+
+**功能描述**:末位运营商按户号分拆出清量;结构与 CreateOptRequest 一致,地址改为 `/CreateCqRequest`。
+
+**触发方式**:Web `POST /dr/event/{id}/clearing`(outbound 启用时自动上报)
+
+**请求体示例**:
+
+```json
+{
+  "root": "CreateCqRequest",
+  "version": 1,
+  "requestID": "111",
+  "dnID": "10010103000052",
+  "eventID": "YY#1100_10010103000052",
+  "optType": "optIn",
+  "createdDateTime": "2025-11-27 13:34:00",
+  "priceDownCoeff": "0.8",
+  "list": [
+    { "load": "10000", "account": "3101330499683" },
+    { "load": "90000", "account": "3100149000967" }
+  ]
+}
+```
+
+> 削峰出清量为负数、填谷为正数(UN 侧约定);本地 Web 出清使用正值 kW,上报 UN 前按户号汇总。
+
+#### 3.7.4 确认出清公示(CreateEventResponse)
+
+**接口地址**:`POST {UN_BASE}/CreateEventResponse`
+
+**功能描述**:收到 UN 出清公示(Poll 返回 DistributeEventRequest / CLEARING_PUBLICITY)后确认。
+
+**触发方式**:
+
+- Web:`POST /dr/event/{id}/ack-clearing`
+- 配置:`vpp.un.auto-ack-clearing=true`
+
+**请求体(官方格式)**:
+
+```json
+{
+  "root": "CreateEventResponse",
+  "version": 1,
+  "code": 200,
+  "description": "ok",
+  "requestID": "a42bb01d-2395-4038-a95c-7691a9273fc4",
+  "dnID": "10010103000002",
+  "eventResponses": [
+    {
+      "optType": "optIn",
+      "code": 200,
+      "description": "ok",
+      "requestID": "9ab66db1-8fd4-4b03-8fd0-c97005a7e33f",
+      "qualifiedEventID": {
+        "eventID": "YY#1100_10010103000052",
+        "modificationNumber": 0
+      }
+    }
+  ]
+}
+```
+
+**本地效果**:`vpp_dr_event.eventStatus=2`,初始化 `vpp_dr_execution`。
+
+#### 3.7.5 需求响应交互时序
+
+```mermaid
+sequenceDiagram
+    participant Web as Web/移动端
+    participant DN as 虚拟电厂平台(DN)
+    participant UN as 运管平台(UN)
+
+    DN->>UN: TokenRequest
+    DN->>UN: CreateRegistrationRequest
+    loop poll-interval-sec
+        DN->>UN: Poll
+        UN-->>DN: DistributeEventRequest(邀约)
+    end
+    Web->>DN: POST /dr/event/{id}/participate
+    DN->>UN: CreateOptRequest(optIn)
+    UN-->>DN: Poll → DistributeEventRequest(申报反馈)
+    UN-->>DN: Poll → DistributeEventRequest(分拆通知)
+    Web->>DN: POST /dr/event/{id}/clearing
+    DN->>UN: CreateCqRequest
+    UN-->>DN: Poll → DistributeEventRequest(出清公示)
+    Web->>DN: POST /dr/event/{id}/ack-clearing
+    DN->>UN: CreateEventResponse
+    Web->>DN: POST /dr/event/{id}/complete
+    Web->>DN: GET /dr/event/{id}/evaluation
+```
+
+---
+
+### 3.8 信息对象服务(车网互动)
+
+> 车网互动相关接口为车网项目个性化能力,一般 VPP 运营商可不开发。
+
+| 方向 | 说明 |
+|------|------|
+| UN → DN | Poll 返回 ObjectMessage(启停充放电、功率调节) |
+| DN → UN | ObjectMessageResponse(接收确认) |
+| DN → UN | ObjectMessage(执行结果通知) |
+
+---
+
+### 3.9 UN 侧错误码
+
+| code | description | 处理建议 |
+|------|-------------|----------|
+| 200 | ok | 正常 |
+| 400 | WrongRoot | 检查接口名称 |
+| 400 | UnregisteredDn | 重新 CreateRegistrationRequest |
+| 400 | DnNotExist | 检查 dnID |
+| 400 | WrongRegistrationID | 检查 registrationID |
+| 400 | EventNotExist | 事件不存在 |
+| 400 | LoadForecastDataNot96Points | 补全 96 点预测数据 |
+| 400 | JsonWrong | 检查 JSON 格式 |
+| 500 | NoToken | 先获取 Token |
+| 500 | JWTDecodeException | Token 解码失败 |
+| 500 | TokenExpiredException | Token 过期,重新获取 |
+| 500 | JWTVerificationException | Token 无效 |
+
+**Token 异常响应示例**:
+
+```json
+{
+  "timestamp": "2021-11-12T08:29:14.038+0000",
+  "status": 500,
+  "error": "Internal Server Error",
+  "message": "JWTVerificationException",
+  "path": "/QueryRegistrationRequest"
+}
+```
+
+---
+
+### 3.10 平台实现与配置(service-vpp)
+
+#### 3.10.1 代码模块映射
+
+| 能力 | 主要类 | 说明 |
+|------|--------|------|
+| DN 被动接口 | `UnDnController` / `VppUnDnServiceImpl` | UN 调用 DN |
+| DN 主动客户端 | `VppUnHttpExecutor` / `VppUnClient` | DN 调用 UN |
+| Token 缓存 | `VppUnTokenHolder` | 自动刷新、失败重试 |
+| 国密 | `VppUnCryptoService` + `VppUnDnCrypto*Advice` | SM2/SM3withSM2 |
+| 事件入库 | `VppDrEventIngestServiceImpl` / `VppUnEventParser` | DistributeEventRequest 解析 |
+| 参与同步 | `VppUnDrSyncServiceImpl` | account → vpp_dr_participation |
+| 执行初始化 | `VppDrExecutionBootstrapServiceImpl` | ack 后创建 execution |
+| Poll 调度 | `VppUnPollScheduler` | 定时 Poll |
+| 报告应答 | `VppUnReportServiceImpl` | CreateReportRequest 链路 |
+| Web 联调 | `UnIntegrationController` | /un/token、/register、/poll |
+| 报文构造 | `VppUnMessageBuilder` | 标准 JSON 报文 |
+| 启动注册 | `VppUnBootstrapRunner` | auto-register-on-startup |
+
+#### 3.10.2 服务端口与路径
+
+| 类型 | 地址示例 |
+|------|----------|
+| 内部 Web API | `http://{host}:9906` 经网关 `/prod-api/service-vpp/...` |
+| DN 被动 UN 接口 | `http://{公网IP}:9906/TokenRequest` 等(无网关前缀) |
+| UN 主动调用目标 | `vpp.un.base-url` + `/Poll`、`/CreateOptRequest` 等 |
+
+#### 3.10.3 联调检查清单
+
+1. 配置 `vpp.un.dn-id`、`transport-address`、密钥(或 crypto-enabled=false)  
+2. `POST /un/token/refresh` → 获取 Token  
+3. `POST /un/register` → 保存 registrationID  
+4. `vpp.un.poll-enabled=true` 或手动 `POST /un/poll` → 事件入库  
+5. Web 参与/出清/确认 → 验证 UN 侧收到 Opt/Cq/EventResponse  
+6. 客户档案 `account_no` 与 UN 户号一致  
+
+---
+
+## 4. 外部监管报送接口
+
+采用适配器模式,由报送服务统一调度,各机构独立实现。内部任务与记录对应 `vpp_report_task`、`vpp_report_record`、`vpp_report_data`。
+
+| 机构 | 适配器 | 对接方式 | reportOrg 枚举 | 说明 |
+|------|--------|----------|----------------|------|
+| 上海电力交易中心 | `TradeCenterReportAdapter` | REST API / 文件上传 | 1 | 按交易中心规范 |
+| 华东能监局 | `EnergyBureauReportAdapter` | 文件报送 | 2 | 生成标准格式文件 |
+| 上海市经信委 | `EconomyInfoReportAdapter` | REST API | 3 | 按月/年/临时 |
+| 国网上海电力 | `StateGridReportAdapter` | REST API | 4 | 运行与响应数据 |
+
+**报送重试策略**:失败后自动重试,次数由 `sys_config.vpp.report.retry.max` 控制(默认 3 次),间隔 1min / 5min / 15min(指数退避)。
+
+**内部触发接口**:`POST /prod-api/service-vpp/report/record/{id}/submit`(见 2.11.8)
+
+### 4.1 交易中心 REST 报送(示例)
+
+**接口地址**:`POST {TRADE_CENTER_BASE}/vpp/report/submit`(由适配器封装)
+
+**功能描述**:向上海电力交易中心推送月度运行数据。
+
+**触发方式**:内部 `TradeCenterReportAdapter` 调用
+
+**请求体**(由 `vpp_report_data.form_data` 转换):
+
+```json
+{
+  "reportPeriod": "2026-05",
+  "vppName": "伍继虚拟电厂",
+  "totalCapacityKw": 28500.0,
+  "totalGenKwh": 1250000.0,
+  "totalUseKwh": 980000.0,
+  "drEventCount": 3,
+  "drTotalSubsidy": 45000.0,
+  "attachments": [
+    { "fileName": "运行月报.xlsx", "fileUrl": "https://oss.example.com/report/13001.xlsx" }
+  ]
+}
+```
+
+**响应示例**:
+
+```json
+{
+  "code": 200,
+  "message": "报送成功",
+  "receiptNo": "TC-202606080001",
+  "receiptUrl": "https://trade.example.com/receipt/TC-202606080001.pdf"
+}
+```
+
+**落库**:更新 `vpp_report_record.record_status=3`、`receipt_file_url`、`submitted_at`。
+
+### 4.2 能监局文件报送(示例)
+
+**接口地址**:文件上传至指定 SFTP/OSS 目录(由 `EnergyBureauReportAdapter` 封装)
+
+**请求**:生成符合能监局模板的标准格式文件(xlsx/pdf)
+
+**响应**:文件上传成功回执;`vpp_report_record.receipt_file_url` 存储回执路径
+
+### 4.3 经信委 REST 报送(示例)
+
+**接口地址**:`POST {ECONOMY_BASE}/api/vpp/monthlyReport`
+
+**请求头**:`Authorization: Bearer {机构Token}`
+
+**请求体**:同 4.1,`reportOrg=3`
+
+**响应**:`{ "success": true, "recordId": "EIC-202605-001" }`
+
+### 4.4 国网上海 REST 报送(示例)
+
+**接口地址**:`POST {STATE_GRID_BASE}/vpp/data/upload`
+
+**请求体**:运行数据 + 需求响应执行数据(来源 `vpp_dr_evaluation`、`vpp_energy_summary_daily`)
+
+**响应**:`{ "status": "ACCEPTED", "batchId": "SG-20260608001" }`
+
+---
+
+## 5. 附录
+
+### 5.1 内部 API 模块索引
+
+| 模块 | 路径前缀 | 主要接口数 |
+|------|----------|------------|
+| 认证 | `/prod-api/service-vpp/auth` | 4 |
+| 首页 | `/prod-api/service-vpp/dashboard` | 4 |
+| 客户 | `/prod-api/service-vpp/customer` | 10+ |
+| 合同 | `/prod-api/service-vpp/contract` | 8+ |
+| 资源 | `/prod-api/service-vpp/resource` | 10+ |
+| 监控 | `/prod-api/service-vpp/monitor` | 4 |
+| 告警 | `/prod-api/service-vpp/alarm` | 15+ |
+| 需求响应 | `/prod-api/service-vpp/dr` | 20+ |
+| 运管联调 | `/prod-api/service-vpp/un` | 3 |
+| 结算 | `/prod-api/service-vpp/settlement` | 8+ |
+| 报送 | `/prod-api/service-vpp/report` | 10+ |
+| 统计 | `/prod-api/service-vpp/statistics` | 6+ |
+| 分析 | `/prod-api/service-vpp/analytics` | 4 |
+| 系统 | `/prod-api/service-vpp/system` | 10+ |
+| 移动端 | `/prod-api/service-vpp/mobile` | 6+ |
+
+### 5.2 运管平台 API 索引
+
+| 服务类型 | 接口 | 方向 | 实现 |
+|----------|------|------|------|
+| 通用 | TokenRequest | DN→UN | 已实现 |
+| 注册 | CreateRegistrationRequest | DN→UN | 已实现 |
+| 询问 | Poll | DN→UN | 已实现(含定时) |
+| 事件 | DistributeEventRequest | UN→DN / Poll | 已实现 |
+| 报告 | CreateReportResponse | DN→UN | 已实现 |
+| 报告 | RegisterReportRequest | DN→UN | 已实现(Poll 触发) |
+| 报告 | MomentDataReportRequest | DN→UN | 已实现(Poll 触发,占位数据) |
+| 报告 | IntervalDataReportRequest | DN→UN | 已实现(Poll 触发,占位数据) |
+| 报告 | RegulateForecastReportRequest | DN→UN | 待完善 |
+| 参与 | CreateOptRequest | DN→UN | 已实现 |
+| 参与 | CreateCqRequest | DN→UN | 已实现 |
+| 事件 | CreateEventResponse | DN→UN | 已实现 |
+| 对象 | ObjectMessage | UN↔DN | 未实现 |
+
+### 5.3 权限码清单(节选)
+
+| 权限码 | 说明 |
+|--------|------|
+| dashboard:view | 首页查看 |
+| customer:access:create | 提交准入 |
+| customer:access:audit | 准入审核 |
+| contract:create | 创建合同 |
+| contract:audit | 合同审核 |
+| resource:manage | 资源管理 |
+| device:control | 设备远程控制 |
+| alarm:manage | 告警管理 |
+| dr:participate | 需求响应参与 |
+| dr:manage | 需求响应管理(含邀约下发) |
+| settlement:manage | 结算管理 |
+| report:submit | 信息报送 |
+| system:admin | 系统管理 |
+
+### 5.4 内部 API 与数据表映射(节选)
+
+| API 路径前缀 | 主要数据表 | 类型 |
+|--------------|------------|------|
+| `/prod-api/service-vpp/auth`、`/prod-api/service-vpp/system/user` | `sys_user`、`sys_user_role` | usky `sys_*` |
+| `/prod-api/service-vpp/system/role`、`/prod-api/service-vpp/system/menu` | `sys_role`、`sys_menu`、`sys_role_menu` | usky `sys_*` |
+| `/prod-api/service-vpp/system/config` | `sys_config` | usky `sys_*` |
+| `/prod-api/service-vpp/system/dict` | `sys_dict_type`、`sys_dict_data` | usky `sys_*` |
+| `/prod-api/service-vpp/system/operLog` | `sys_oper_log` | usky `sys_*` |
+| `/prod-api/service-vpp/alarm/rule` | `rule_engine`、`rule_engine_device`、`rule_engine_condition`、`rule_engine_cron` | usky `rule_*` |
+| `/prod-api/service-vpp/alarm` | `base_alarm`、`base_alarm_type` | usky `base_*` |
+| `/prod-api/service-vpp/alarm/notice` | `base_alarm_notice`、`base_alarm_notice_result` | usky `base_*` |
+| `/prod-api/service-vpp/device/{id}/control` | `dmp_device_command`、`vpp_device_control_log` | usky `dmp_*` + `vpp_*` |
+| `/prod-api/service-vpp/archive` | `vpp_file_archive` | VPP 专属 |
+| `/prod-api/service-vpp/resource` | `vpp_resource_point`、`vpp_device`、`vpp_site` | VPP 专属 |
+| `/prod-api/service-vpp/capability-eval` | `vpp_site`、`vpp_resource_point`、`vpp_device`、`vpp_dr_participation`、`vpp_dr_event`、`vpp_dr_evaluation` + TDengine `device_metrics` | VPP 专属 + 时序库 |
+| `/prod-api/service-vpp/customer` 等核心业务 | `vpp_customer`、`vpp_contract`、`vpp_resource_point` 等 | VPP 专属 |
+| `/prod-api/service-vpp/settlement` | `vpp_energy_reading_monthly`、`vpp_settlement_bill`、`vpp_payment_record` | VPP 专属 |
+| `/prod-api/service-vpp/report` | `vpp_report_task`、`vpp_report_record`、`vpp_report_data` | VPP 专属 |
+| `/prod-api/service-vpp/dr` | `vpp_dr_event`、`vpp_dr_invitation`、`vpp_dr_participation`、`vpp_dr_strategy`、`vpp_dr_execution`、`vpp_dr_evaluation` | VPP 专属 |
+| `/prod-api/service-vpp/un` | 调用 UN,落库同 DR + `vpp_registration` | VPP 专属 |
+| 运管平台 DN 被动路径 | `vpp_dr_event`、`vpp_dr_participation`、`vpp_registration` | VPP 专属 |
+| 运管平台 UN 主动客户端 | `vpp_registration`、`vpp_report_log`(报告日志待完善) | VPP 专属 |
+| 监控/统计时序数据 | TDengine `device_metrics` | 时序库 |
+
+### 5.5 参考文档
+
+1. 《API设计文档0304.md》(通用规范与路径格式)
+2. 《虚拟电厂平台详细设计文档 V1.0》
+3. 《虚拟电厂平台数据库设计文档 V1.0》
+4. 《虚拟电厂接口联调须知》(2025-12-11)
+5. 《虚拟电厂运管管理平台-虚拟电厂接口联调 JSON 报文实例》(2025-11-27)
+6. DLT 1867—2024《电力需求响应信息交互规范》
+
+### 5.6 UN 联调 JSON 样例
+
+工程内置样例(与联调文档 20251127 对齐),路径:
+
+`service-vpp/service-vpp-biz/src/main/resources/un-samples/`
+
+| 文件名 | root | 用途 |
+|--------|------|------|
+| poll-request.json | Poll | DN 主动 Poll |
+| distribute-event-invitation.json | DistributeEventRequest | 邀约入库测试 |
+| create-opt-request.json | CreateOptRequest | 参与申报 |
+| create-cq-request.json | CreateCqRequest | 分拆出清 |
+| create-event-response.json | CreateEventResponse | 出清公示确认 |
+
+**本地被动接口测试示例**:
+
+```http
+POST http://localhost:9906/DistributeEventRequest
+Content-Type: application/json
+
+(Body 使用 distribute-event-invitation.json 内容)
+```
+
+---
+
+**文档结束**

+ 2316 - 0
service-vpp/虚拟电厂平台数据库设计文档V1.1.md

@@ -0,0 +1,2316 @@
+# 虚拟电厂平台数据库设计文档
+
+| 项目 | 内容 |
+|------|------|
+| 文档名称 | 虚拟电厂平台数据库设计文档 |
+| 文档版本 | V1.0 |
+| 编制依据 | 《虚拟电厂平台详细设计文档 V1.0》 |
+| 数据库 | MySQL 8.0+(InnoDB) |
+| 时序库 | TDengine 3.x(设备运行数据) |
+| 编制日期 | 2026-07-03 |
+| 更新日期 | 2026-07-04 |
+| 文档状态 | 已对齐代码库 vpp_schema.sql(V1.1,新增邀约管理表) |
+
+---
+
+## 目录
+
+1. [概述](#1-概述)
+2. [设计规范](#2-设计规范)
+3. [数据库与表清单](#3-数据库与表清单)
+4. [实体关系设计](#4-实体关系设计)
+5. [数据表详细设计(VPP 专属)](#5-数据表详细设计)
+   - [5A. 沿用 usky-cloud 数据表](#5a-沿用-usky-cloud-数据表详细说明)
+6. [索引设计汇总](#6-索引设计汇总)
+7. [时序库设计](#7-时序库设计)
+8. [初始化与枚举数据](#8-初始化与枚举数据)
+9. [附录](#9-附录)
+
+---
+
+## 1. 概述
+
+### 1.1 编写目的
+
+本文档在《虚拟电厂平台详细设计文档 V1.0》基础上,给出虚拟电厂**专属业务表**(`vpp_*`)的表结构、字段说明及 DDL;告警、规则引擎、系统管理、物联网设备等与 usky-cloud 平台重叠的模块**沿用 `base_*`、`sys_*`、`dmp_*`、`rule_*` 前缀表**,不重复创建。
+
+### 1.2 存储分工
+
+| 存储 | 用途 | 数据特征 |
+|------|------|----------|
+| MySQL 8.0 | 客户、合同、资源档案、结算、报送、告警、需求响应、系统权限等 | 结构化、事务性、关联查询 |
+| TDengine | 设备 15 分钟级运行曲线、实时监测指标 | 高写入、时序查询 |
+| Redis | 会话、Token、实时指标缓存 | 缓存 |
+| OSS/MinIO | 合同 PDF、档案、报表文件 | 大文件 |
+
+### 1.3 逻辑库划分
+
+建议单实例多 Schema 或单库前缀区分:
+
+| 逻辑库/前缀 | 说明 |
+|-------------|------|
+| `usky-cloud`(已有) | 平台公共库:复用 `base_*`(告警)、`rule_*`(规则引擎)、`sys_*`(用户权限/配置)、`dmp_*`(物联网设备),见 `usky-cloud.sql` |
+| `vpp` | 虚拟电厂专属业务表,前缀 `vpp_*` |
+
+### 1.4 与 usky-cloud 平台表复用说明
+
+虚拟电厂作为 usky-cloud 平台上的业务应用,**复用 `usky-cloud.sql` 中 `base_*`、`sys_*`、`dmp_*`、`rule_*`(`rule_engine*`)前缀的平台表**;`ems_*`、`sas_*`、`issue_*`、`file_upload` 等业务系统表**不沿用**。字段映射与关联方式见第 5A 章。
+
+| 业务模块 | 复用表(`base_*` / `sys_*` / `dmp_*` / `rule_*`) | 不再创建的 VPP 表 |
+|----------|-----------------------------------------------------|-------------------|
+| 告警规则/规则引擎 | `rule_engine`、`rule_engine_device`、`rule_engine_condition`、`rule_engine_cron`、`rule_engine_log` | vpp_alarm_rule |
+| 告警记录/闭环 | `base_alarm`、`base_alarm_type` | vpp_alarm_record、vpp_alarm_handle_log |
+| 告警通知/统计 | `base_alarm_notice`、`base_alarm_notice_result`、`base_alarm_statistic` | — |
+| 物联网设备/采集 | `dmp_device`、`dmp_device_status`、`dmp_device_command`、`dmp_product` 等 | — |
+| 用户与权限 | `sys_user`、`sys_role`、`sys_menu`、`sys_user_role`、`sys_role_menu`、`sys_dept` | vpp_sys_user、vpp_sys_role、vpp_sys_permission、vpp_sys_user_role、vpp_sys_role_permission |
+| 系统配置/字典 | `sys_config`、`sys_dict_type`、`sys_dict_data` | vpp_sys_config |
+| 操作/登录日志 | `sys_oper_log`、`sys_logininfor` | vpp_sys_operation_log |
+| 多租户 | `sys_tenant`、`sys_tenant_config`、`sys_user_tenant` | — |
+| 电子档案 | —(平台无 `base_*`/`sys_*`/`dmp_*`/`rule_*` 附件表) | 保留 `vpp_file_archive` |
+
+> **说明**:`vpp_device` 为 VPP 业务设备档案,通过 `device_code` 与 `dmp_device.device_id`、`device_uuid` 与 `dmp_device.device_uuid` 双向关联;物联网侧状态、命令、事件以 `dmp_*` 表为准。
+
+---
+
+## 2. 设计规范
+
+### 2.1 命名规范
+
+| 类型 | 规范 | 示例 |
+|------|------|------|
+| VPP 专属表名 | `vpp_` + 模块_实体,小写蛇形 | `vpp_customer` |
+| 平台复用表 | `base_*`、`sys_*`、`dmp_*`、`rule_*` 前缀 | `base_alarm`、`sys_user`、`rule_engine` |
+| 字段名 | 小写蛇形 | `customer_name` |
+| 主键 | `id`,BIGINT AUTO_INCREMENT | 数据库自增,从 1 开始;已有库重置见 `vpp_id_reset_from_one_migration.sql` |
+| 外键字段 | `{实体}_id` | `customer_id` |
+| 索引 | `idx_{表缩写}_{字段}` | `idx_customer_account_no` |
+| 唯一索引 | `uk_{表缩写}_{字段}` | `uk_customer_account_no` |
+
+### 2.2 公共字段
+
+**业务主表**(含软删除)统一包含:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 主键,NOT NULL |
+| tenant_id | INT | 租户 ID,可 NULL,关联 `sys_tenant.id` |
+| create_time | DATETIME(3) | 创建时间,NOT NULL |
+| update_time | DATETIME(3) | 更新时间,NOT NULL |
+| created_by | VARCHAR(30) | 创建人登录名,可 NULL |
+| updated_by | VARCHAR(30) | 更新人登录名,可 NULL |
+| delete_flag | INT(1) | 删除标识,NOT NULL,默认 0(0 未删除 1 已删除) |
+| deleted_at | DATETIME(3) | 软删除时间,NULL 表示未删除 |
+
+**关联表 / 明细表**(如 `vpp_settlement_bill_detail`、`vpp_dr_strategy_resource`)至少包含 `tenant_id`。
+
+**流水 / 日志表**(如 `vpp_contract_audit_log`、`vpp_device_control_log`、`vpp_report_audit_log`)采用精简审计字段:`tenant_id` + `create_time` + `created_by`(VARCHAR(30))。
+
+**准入审核说明**:`vpp_customer_access` 审核人写入 `updated_by`,审核时间保留 `audit_at` 业务字段,不再单独维护 `audit_by`。
+
+**软删除说明**:业务查询以 `delete_flag = 0` 过滤;执行软删除时同步设置 `delete_flag = 1` 与 `deleted_at`。
+
+### 2.3 通用 DDL 模板
+
+```sql
+CREATE TABLE vpp_example (
+    id          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键',
+    -- 业务字段 ...
+    tenant_id   INT          NULL COMMENT '租户ID',
+    create_time DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    update_time DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    created_by  VARCHAR(30)  NULL COMMENT '创建人',
+    updated_by  VARCHAR(30)  NULL COMMENT '更新人',
+    delete_flag INT(1)       NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    deleted_at  DATETIME(3)  NULL COMMENT '软删除时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='示例表';
+```
+
+### 2.4 数据类型约定
+
+| 场景 | 类型 |
+|------|------|
+| 名称/标题 | VARCHAR(200) |
+| 编码/户号 | VARCHAR(64) |
+| 金额/电价/电量 | DECIMAL(18,4) |
+| 容量/功率 | DECIMAL(12,4) |
+| 经纬度 | DECIMAL(10,6) |
+| 枚举/状态 | TINYINT 或 VARCHAR(32) |
+| 大文本/JSON | TEXT / JSON |
+| 附件 URL | VARCHAR(500) |
+
+---
+
+## 3. 数据库与表清单
+
+共 **29 张 VPP 专属表**(`vpp_*`)+ **沿用 usky-cloud 平台表(`base_*`/`sys_*`/`dmp_*`/`rule_*`)若干** + **1 套 TDengine 超级表**。
+
+### 3.1 客户与合同(6 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 1 | vpp_customer | 客户档案 |
+| 2 | vpp_customer_access | 准入申请 |
+| 3 | vpp_customer_contact | 客户联系人 |
+| 4 | vpp_contract | 合同 |
+| 5 | vpp_contract_template | 合同模板 |
+| 6 | vpp_contract_audit_log | 合同审核流水 |
+
+### 3.2 资源与设备(5 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 7 | vpp_site | 站点 |
+| 8 | vpp_site_config | 站点运行参数 |
+| 9 | vpp_resource_point | 资源点 |
+| 10 | vpp_device | 设备 |
+| 11 | vpp_device_control_log | 设备远程控制日志 |
+
+### 3.3 运行与汇总(2 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 12 | vpp_energy_reading_monthly | 月度电量核算 |
+| 13 | vpp_energy_summary_daily | 日度能源汇总 |
+
+### 3.4 结算(3 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 14 | vpp_settlement_bill | 电费账单 |
+| 15 | vpp_settlement_bill_detail | 账单分时明细 |
+| 16 | vpp_payment_record | 缴费记录 |
+
+### 3.5 信息报送(4 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 17 | vpp_report_task | 报送任务 |
+| 18 | vpp_report_record | 报送记录 |
+| 19 | vpp_report_data | 报送填报数据 |
+| 20 | vpp_report_audit_log | 报送审核流水 |
+
+### 3.6 告警与规则引擎(沿用 `base_*` + `rule_*`)
+
+#### 3.6.1 沿用 usky-cloud `rule_*` 规则引擎表(不新建)
+
+> 以下表已在 `usky-cloud.sql` 中定义,虚拟电厂**直接复用**;**不使用** `ems_alarm_event`、`ems_rule_engine_cron` 等 EMS 业务表。
+
+| 表名 | 说明 | 对应原 VPP 设计 |
+|------|------|-----------------|
+| `rule_engine` | 规则引擎主体(名称、状态、规则详情 JSON) | vpp_alarm_rule |
+| `rule_engine_condition` | 触发条件/约束条件 | — |
+| `rule_engine_cron` | 规则 CRON 调度 | — |
+| `rule_engine_device` | 规则关联设备与物模型标识符 | — |
+| `rule_engine_log` | 规则执行日志(含 alarmEvent 动作) | — |
+
+#### 3.6.2 沿用 usky-cloud `base_*` 告警表(不新建)
+
+| 表名 | 说明 | 对应原 VPP 设计 |
+|------|------|-----------------|
+| `base_alarm` | 统一告警记录(含处理人、处理时间、处理状态、误报标识) | vpp_alarm_record、vpp_alarm_handle_log |
+| `base_alarm_type` | 告警类型字典 | — |
+| `base_alarm_notice` | 告警通知配置(分级接收人、超时升级) | — |
+| `base_alarm_notice_result` | 告警通知推送结果 | — |
+| `base_alarm_statistic` | 告警统计汇总 | — |
+
+#### 3.6.3 沿用 usky-cloud `dmp_*` 物联网表(节选)
+
+| 表名 | 说明 | 与 VPP 关联 |
+|------|------|-------------|
+| `dmp_device` | 物联网设备注册信息 | `device_id` ↔ `vpp_device.device_code`;`device_uuid` ↔ `vpp_device.device_uuid` |
+| `dmp_device_status` | 设备在线/离线状态 | 同上 |
+| `dmp_device_command` | 设备下发命令记录 | 与 `vpp_device_control_log` 互补 |
+| `dmp_product` / `dmp_product_command` | 产品与物模型命令 | 产品维度配置 |
+| `dmp_gateway` | 网关设备 | 网关子设备关联 |
+| `dmp_data_event` / `dmp_data_info` | 设备事件与上报数据 | 规则引擎输入数据源 |
+
+### 3.7 需求响应(7 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 21 | vpp_dr_event | 需求响应事件 |
+| 22 | vpp_dr_participation | 事件参与记录 |
+| 23 | vpp_dr_invitation | 企业邀约记录(邀约管理界面) |
+| 24 | vpp_dr_strategy | 响应策略 |
+| 25 | vpp_dr_strategy_resource | 策略-资源关联 |
+| 26 | vpp_dr_execution | 响应执行明细 |
+| 27 | vpp_dr_evaluation | 响应效果评估 |
+
+### 3.8 运管平台对接(2 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 28 | vpp_registration | 运管平台注册信息 |
+| 29 | vpp_report_log | 数据上报日志 |
+
+### 3.9 系统管理(沿用 `sys_*`,不新建)
+
+> 以下 `sys_*` 表已在 `usky-cloud.sql` 中定义,虚拟电厂**直接复用**;**不使用** `ems_*`、`sas_*`、`issue_*` 等业务系统表。
+
+#### 3.9.1 VPP 专属(1 张)
+
+| 序号 | 表名 | 说明 |
+|------|------|------|
+| 30 | vpp_file_archive | 电子档案/附件元数据(平台无 `base_*`/`sys_*`/`dmp_*`/`rule_*` 附件表) |
+
+#### 3.9.2 沿用 usky-cloud `sys_*` 表(节选)
+
+| 表名 | 说明 | 对应原 VPP 设计 |
+|------|------|-----------------|
+| `sys_user` | 用户账号 | vpp_sys_user |
+| `sys_role` | 角色 | vpp_sys_role |
+| `sys_menu` | 菜单/按钮权限 | vpp_sys_permission |
+| `sys_role_menu` | 角色-菜单关联 | vpp_sys_role_permission |
+| `sys_user_role` | 用户-角色关联 | vpp_sys_user_role |
+| `sys_dept` | 部门组织 | — |
+| `sys_config` | 系统参数配置 | vpp_sys_config |
+| `sys_dict_type` / `sys_dict_data` | 数据字典 | — |
+| `sys_oper_log` | 操作审计日志 | vpp_sys_operation_log |
+| `sys_logininfor` | 登录访问记录 | — |
+| `sys_notice` | 通知公告 | — |
+| `sys_tenant` / `sys_tenant_config` / `sys_user_tenant` | 多租户 | — |
+
+---
+
+## 4. 实体关系设计
+
+```mermaid
+erDiagram
+    vpp_customer ||--o{ vpp_customer_access : applies
+    vpp_customer ||--o{ vpp_customer_contact : has
+    vpp_customer ||--o{ vpp_contract : signs
+    vpp_customer ||--o{ vpp_site : owns
+    vpp_site ||--|| vpp_site_config : configures
+    vpp_site ||--o{ vpp_resource_point : contains
+    vpp_resource_point ||--|| vpp_device : binds
+    vpp_device ||--o{ vpp_device_control_log : controls
+    vpp_customer ||--o{ vpp_settlement_bill : billed
+    vpp_settlement_bill ||--o{ vpp_settlement_bill_detail : contains
+    vpp_settlement_bill ||--o{ vpp_payment_record : pays
+    vpp_report_task ||--o{ vpp_report_record : generates
+    vpp_report_record ||--o| vpp_report_data : fills
+    vpp_dr_event ||--o{ vpp_dr_participation : includes
+    vpp_dr_event ||--o{ vpp_dr_invitation : invites
+    vpp_customer ||--o{ vpp_dr_invitation : receives
+    vpp_dr_invitation |o--o| vpp_dr_participation : links
+    vpp_dr_strategy ||--o{ vpp_dr_strategy_resource : binds
+    vpp_dr_event ||--o{ vpp_dr_execution : executes
+    vpp_dr_event ||--o| vpp_dr_evaluation : evaluates
+    rule_engine ||--o{ rule_engine_device : binds
+    vpp_device ||--o{ rule_engine_device : monitored
+    rule_engine ||--o{ base_alarm : triggers
+    vpp_device ||--o{ base_alarm : monitored
+    base_alarm ||--o{ base_alarm_notice_result : notifies
+    dmp_device ||--o{ dmp_device_status : status
+    vpp_device ||--|| dmp_device : maps
+    sys_user ||--o{ sys_user_role : assigns
+    sys_role ||--o{ sys_user_role : grants
+    vpp_contract ||--o{ vpp_file_archive : attaches
+```
+
+**核心外键关系(VPP 专属表)**:
+
+- `vpp_site.customer_id` → `vpp_customer.id`
+- `vpp_site_config.site_id` → `vpp_site.id`
+- `vpp_resource_point.site_id` → `vpp_site.id`
+- `vpp_resource_point.device_id` → `vpp_device.id`
+- `vpp_device.site_id` → `vpp_site.id`
+- `vpp_contract.customer_id` → `vpp_customer.id`
+- `vpp_settlement_bill.customer_id` → `vpp_customer.id`
+- `vpp_dr_participation.event_id` → `vpp_dr_event.id`
+- `vpp_dr_invitation.dr_event_id` → `vpp_dr_event.id`
+- `vpp_dr_invitation.customer_id` → `vpp_customer.id`
+- `vpp_dr_invitation.participation_id` → `vpp_dr_participation.id`(可空)
+- `vpp_dr_invitation.sms_contact_id` → `vpp_customer_contact.id`(可空)
+
+**与 usky-cloud 平台表逻辑关联**(`base_*` / `sys_*` / `dmp_*` / `rule_*`):
+
+- `rule_engine_device.device_id` → `vpp_device.device_code` / `dmp_device.device_id`
+- `rule_engine.detail` JSON 配置阈值、条件及 `alarmEvent` 动作;命中后写入 `base_alarm`
+- `base_alarm.device_id` → `vpp_device.device_code` 或 `dmp_device.device_id`
+- `dmp_device.device_id` ↔ `vpp_device.device_code`;`dmp_device.device_uuid` ↔ `vpp_device.device_uuid`(物联网与业务双向映射)
+- `sys_user_role.user_id` → `sys_user.user_id`(VPP 用户鉴权)
+- `vpp_file_archive.biz_type` + `biz_id` → `vpp_contract` / `vpp_customer` 等业务主键
+
+---
+
+## 5. 数据表详细设计
+
+### 5.1 vpp_customer(客户档案)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `account_no` | VARCHAR(32) | NOT NULL | 电力户号,全局唯一 |
+| `customer_name` | VARCHAR(200) | NOT NULL | 客户名称 |
+| `customer_type` | TINYINT | NOT NULL | 1高压 2低压商用 3居民充电桩 4自有资产 |
+| `power_company` | VARCHAR(50) | NOT NULL | 供电公司简称 |
+| `contract_capacity` | DECIMAL(12,4) | NOT NULL | 用电容量 kVA/kW |
+| `credit_status` | TINYINT | NULL | 1正常 2关注 3不良 |
+| `lifecycle_status` | TINYINT | NOT NULL | 1待准入 2已准入 3已签约 4履约中 5待续约 6已续约 7已解约 |
+| `dr_notify_minutes` | INT | NULL | 需求响应提前通知分钟数,默认30 |
+| `dr_up_capacity_kw` | DECIMAL(12,4) | NULL | 登记上调能力 kW |
+| `dr_down_capacity_kw` | DECIMAL(12,4) | NULL | 登记下调能力 kW |
+| `province` | VARCHAR(50) | NULL | 省 |
+| `city` | VARCHAR(50) | NULL | 市 |
+| `district` | VARCHAR(50) | NULL | 区 |
+| `address` | VARCHAR(500) | NULL | 详细地址 |
+| `business_license_url` | VARCHAR(500) | NULL | 营业执照附件 |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_customer_account_no`(account_no)
+- `idx_customer_name`(customer_name)
+- `idx_customer_lifecycle`(lifecycle_status, deleted_at)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_customer` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `account_no` VARCHAR(32) NOT NULL COMMENT '电力户号,全局唯一',
+    `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
+    `customer_type` TINYINT NOT NULL COMMENT '1高压 2低压商用 3居民充电桩 4自有资产',
+    `power_company` VARCHAR(50) NOT NULL COMMENT '供电公司简称',
+    `contract_capacity` DECIMAL(12,4) NOT NULL COMMENT '用电容量 kVA/kW',
+    `credit_status` TINYINT NULL COMMENT '1正常 2关注 3不良',
+    `lifecycle_status` TINYINT NOT NULL COMMENT '1待准入 2已准入 3已签约 4履约中 5待续约 6已续约 7已解约',
+    `dr_notify_minutes` INT NULL COMMENT '需求响应提前通知分钟数,默认30',
+    `dr_up_capacity_kw` DECIMAL(12,4) NULL COMMENT '登记上调能力 kW',
+    `dr_down_capacity_kw` DECIMAL(12,4) NULL COMMENT '登记下调能力 kW',
+    `province` VARCHAR(50) NULL COMMENT '省',
+    `city` VARCHAR(50) NULL COMMENT '市',
+    `district` VARCHAR(50) NULL COMMENT '区',
+    `address` VARCHAR(500) NULL COMMENT '详细地址',
+    `business_license_url` VARCHAR(500) NULL COMMENT '营业执照附件',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_customer_account_no` (account_no),
+    KEY `idx_customer_name` (customer_name),
+    KEY `idx_customer_lifecycle` (lifecycle_status, deleted_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户档案';
+```
+
+### 5.2 vpp_customer_access(准入申请)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `apply_no` | VARCHAR(32) | NOT NULL | 申请编号 |
+| `account_no` | VARCHAR(32) | NOT NULL | 电力户号 |
+| `customer_name` | VARCHAR(200) | NOT NULL | 客户名称 |
+| `customer_type` | TINYINT | NOT NULL | 客户类型 |
+| `power_company` | VARCHAR(50) | NOT NULL | 供电公司 |
+| `contract_capacity` | DECIMAL(12,4) | NOT NULL | 用电容量 |
+| `business_license_url` | VARCHAR(500) | NULL | 营业执照 |
+| `credit_status` | TINYINT | NULL | 信用状况 |
+| `access_status` | TINYINT | NOT NULL | 0待审核 1通过 2驳回 |
+| `audit_opinion` | VARCHAR(500) | NULL | 审核意见 |
+| `audit_at` | DATETIME(3) | NULL | 审核时间 |
+| `customer_id` | BIGINT | NULL | 通过后关联客户ID |
+| `apply_at` | DATETIME(3) | NOT NULL | 申请时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_access_apply_no`(apply_no)
+- `idx_access_status`(access_status, apply_at)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_customer_access` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `apply_no` VARCHAR(32) NOT NULL COMMENT '申请编号',
+    `account_no` VARCHAR(32) NOT NULL COMMENT '电力户号',
+    `customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称',
+    `customer_type` TINYINT NOT NULL COMMENT '客户类型',
+    `power_company` VARCHAR(50) NOT NULL COMMENT '供电公司',
+    `contract_capacity` DECIMAL(12,4) NOT NULL COMMENT '用电容量',
+    `business_license_url` VARCHAR(500) NULL COMMENT '营业执照',
+    `credit_status` TINYINT NULL COMMENT '信用状况',
+    `access_status` TINYINT NOT NULL COMMENT '0待审核 1通过 2驳回',
+    `audit_opinion` VARCHAR(500) NULL COMMENT '审核意见',
+    `audit_at` DATETIME(3) NULL COMMENT '审核时间',
+    `customer_id` BIGINT NULL COMMENT '通过后关联客户ID',
+    `apply_at` DATETIME(3) NOT NULL COMMENT '申请时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_access_apply_no` (apply_no),
+    KEY `idx_access_status` (access_status, apply_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='准入申请';
+```
+
+### 5.3 vpp_customer_contact(客户联系人)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `customer_id` | BIGINT | NOT NULL | 客户ID |
+| `contact_name` | VARCHAR(100) | NOT NULL | 联系人姓名 |
+| `contact_phone` | VARCHAR(20) | NOT NULL | 联系电话 |
+| `contact_email` | VARCHAR(100) | NULL | 邮箱 |
+| `is_primary` | TINYINT | NOT NULL | 是否主联系人 0否 1是 |
+| `position` | VARCHAR(50) | NULL | 职务 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `idx_contact_customer`(customer_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_customer_contact` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `contact_name` VARCHAR(100) NOT NULL COMMENT '联系人姓名',
+    `contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
+    `contact_email` VARCHAR(100) NULL COMMENT '邮箱',
+    `is_primary` TINYINT NOT NULL COMMENT '是否主联系人 0否 1是',
+    `position` VARCHAR(50) NULL COMMENT '职务',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_contact_customer` (customer_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户联系人';
+```
+
+### 5.4 vpp_contract(合同)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `contract_no` | VARCHAR(64) | NOT NULL | 合同编号 |
+| `customer_id` | BIGINT | NOT NULL | 客户ID |
+| `template_id` | BIGINT | NULL | 模板ID |
+| `contract_type` | TINYINT | NOT NULL | 1购售电 2需求响应合作 3聚合代理 4服务代理 5居民充电桩 6自有资产 |
+| `contract_name` | VARCHAR(200) | NOT NULL | 合同名称 |
+| `contract_status` | TINYINT | NOT NULL | 0草稿 1审核中 2已生效 3已到期 4已终止 |
+| `sign_date` | DATE | NULL | 签订日期 |
+| `effective_date` | DATE | NULL | 生效日期 |
+| `expire_date` | DATE | NULL | 到期日期 |
+| `file_url` | VARCHAR(500) | NULL | 合同文件URL |
+| `share_ratio` | DECIMAL(5,2) | NULL | 分成比例% |
+| `price_json` | JSON | NULL | 电价条款JSON |
+| `account_info_json` | JSON | NULL | 分成账户信息 |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_contract_no`(contract_no)
+- `idx_contract_customer`(customer_id)
+- `idx_contract_status`(contract_status, sign_date)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_contract` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `contract_no` VARCHAR(64) NOT NULL COMMENT '合同编号',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `template_id` BIGINT NULL COMMENT '模板ID',
+    `contract_type` TINYINT NOT NULL COMMENT '1购售电 2需求响应合作 3聚合代理 4服务代理 5居民充电桩 6自有资产',
+    `contract_name` VARCHAR(200) NOT NULL COMMENT '合同名称',
+    `contract_status` TINYINT NOT NULL COMMENT '0草稿 1审核中 2已生效 3已到期 4已终止',
+    `sign_date` DATE NULL COMMENT '签订日期',
+    `effective_date` DATE NULL COMMENT '生效日期',
+    `expire_date` DATE NULL COMMENT '到期日期',
+    `file_url` VARCHAR(500) NULL COMMENT '合同文件URL',
+    `share_ratio` DECIMAL(5,2) NULL COMMENT '分成比例%',
+    `price_json` JSON NULL COMMENT '电价条款JSON',
+    `account_info_json` JSON NULL COMMENT '分成账户信息',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_contract_no` (contract_no),
+    KEY `idx_contract_customer` (customer_id),
+    KEY `idx_contract_status` (contract_status, sign_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同';
+```
+
+### 5.5 vpp_contract_template(合同模板)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `template_code` | VARCHAR(32) | NOT NULL | 模板编码 |
+| `template_name` | VARCHAR(200) | NOT NULL | 模板名称 |
+| `contract_type` | TINYINT | NOT NULL | 合同类型 |
+| `version` | VARCHAR(20) | NOT NULL | 版本号 |
+| `file_url` | VARCHAR(500) | NOT NULL | 模板文件URL |
+| `variables_json` | JSON | NULL | 占位符变量定义 |
+| `is_enabled` | TINYINT | NOT NULL | 是否启用 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_template_code`(template_code)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_contract_template` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `template_code` VARCHAR(32) NOT NULL COMMENT '模板编码',
+    `template_name` VARCHAR(200) NOT NULL COMMENT '模板名称',
+    `contract_type` TINYINT NOT NULL COMMENT '合同类型',
+    `version` VARCHAR(20) NOT NULL COMMENT '版本号',
+    `file_url` VARCHAR(500) NOT NULL COMMENT '模板文件URL',
+    `variables_json` JSON NULL COMMENT '占位符变量定义',
+    `is_enabled` TINYINT NOT NULL COMMENT '是否启用',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_template_code` (template_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同模板';
+```
+
+### 5.6 vpp_contract_audit_log(合同审核流水)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `contract_id` | BIGINT | NOT NULL | 合同ID |
+| `action` | TINYINT | NOT NULL | 1提交 2通过 3驳回 4归档 |
+| `opinion` | VARCHAR(500) | NULL | 意见 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+
+**索引**:
+- `idx_contract_audit_contract`(contract_id, create_time)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_contract_audit_log` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `contract_id` BIGINT NOT NULL COMMENT '合同ID',
+    `action` TINYINT NOT NULL COMMENT '1提交 2通过 3驳回 4归档',
+    `opinion` VARCHAR(500) NULL COMMENT '意见',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    PRIMARY KEY (`id`),
+    KEY `idx_contract_audit_contract` (contract_id, create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同审核流水';
+```
+
+### 5.7 vpp_site(站点)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `site_code` | VARCHAR(64) | NOT NULL | 站点编号 |
+| `site_name` | VARCHAR(200) | NOT NULL | 站点名称 |
+| `customer_id` | BIGINT | NOT NULL | 所属客户ID |
+| `province` | VARCHAR(50) | NULL | 省 |
+| `city` | VARCHAR(50) | NULL | 市 |
+| `district` | VARCHAR(50) | NULL | 区/区域 |
+| `address` | VARCHAR(500) | NULL | 地址 |
+| `longitude` | DECIMAL(10,6) | NULL | 经度 |
+| `latitude` | DECIMAL(10,6) | NULL | 纬度 |
+| `owner_name` | VARCHAR(200) | NULL | 业主名称 |
+| `contact_name` | VARCHAR(100) | NULL | 联系人 |
+| `contact_phone` | VARCHAR(20) | NULL | 联系电话 |
+| `response_priority` | TINYINT | NULL | 响应优先级1-10 |
+| `un_resource_id` | VARCHAR(64) | NULL | 运管平台分路资源ID |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | BIGINT | NULL | 创建人 |
+| `updated_by` | BIGINT | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_site_code`(site_code)
+- `idx_site_customer`(customer_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_site` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `site_code` VARCHAR(64) NOT NULL COMMENT '站点编号',
+    `site_name` VARCHAR(200) NOT NULL COMMENT '站点名称',
+    `customer_id` BIGINT NOT NULL COMMENT '所属客户ID',
+    `province` VARCHAR(50) NULL COMMENT '省',
+    `city` VARCHAR(50) NULL COMMENT '市',
+    `district` VARCHAR(50) NULL COMMENT '区/区域',
+    `address` VARCHAR(500) NULL COMMENT '地址',
+    `longitude` DECIMAL(10,6) NULL COMMENT '经度',
+    `latitude` DECIMAL(10,6) NULL COMMENT '纬度',
+    `owner_name` VARCHAR(200) NULL COMMENT '业主名称',
+    `contact_name` VARCHAR(100) NULL COMMENT '联系人',
+    `contact_phone` VARCHAR(20) NULL COMMENT '联系电话',
+    `response_priority` TINYINT NULL COMMENT '响应优先级1-10',
+    `un_resource_id` VARCHAR(64) NULL COMMENT '运管平台分路资源ID',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` BIGINT NULL COMMENT '创建人',
+    `updated_by` BIGINT NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_site_code` (`site_code`),
+    KEY `idx_site_customer` (`customer_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点';
+```
+
+### 5.8 vpp_site_config(站点运行参数)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `site_id` | BIGINT | NOT NULL | 站点ID |
+| `collect_interval_sec` | INT | NOT NULL | 采集频率秒 60/300/900 |
+| `power_upper_limit` | DECIMAL(12,4) | NULL | 功率上限告警 |
+| `power_lower_limit` | DECIMAL(12,4) | NULL | 功率下限告警 |
+| `soc_upper_limit` | DECIMAL(5,2) | NULL | SOC上限% |
+| `soc_lower_limit` | DECIMAL(5,2) | NULL | SOC下限% |
+| `offline_timeout_sec` | INT | NULL | 离线超时秒 |
+| `alarm_rule_json` | JSON | NULL | 扩展告警规则 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_config_site`(site_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_site_config` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `site_id` BIGINT NOT NULL COMMENT '站点ID',
+    `collect_interval_sec` INT NOT NULL COMMENT '采集频率秒 60/300/900',
+    `power_upper_limit` DECIMAL(12,4) NULL COMMENT '功率上限告警',
+    `power_lower_limit` DECIMAL(12,4) NULL COMMENT '功率下限告警',
+    `soc_upper_limit` DECIMAL(5,2) NULL COMMENT 'SOC上限%',
+    `soc_lower_limit` DECIMAL(5,2) NULL COMMENT 'SOC下限%',
+    `offline_timeout_sec` INT NULL COMMENT '离线超时秒',
+    `alarm_rule_json` JSON NULL COMMENT '扩展告警规则',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_config_site` (`site_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站点运行参数';
+```
+
+### 5.9 vpp_resource_point(资源点)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `resource_code` | VARCHAR(64) | NOT NULL | 资源编号 |
+| `resource_name` | VARCHAR(200) | NOT NULL | 资源名称 |
+| `site_id` | BIGINT | NOT NULL | 站点主键ID |
+| `device_id` | BIGINT | NOT NULL | 设备主键ID |
+| `resource_type` | VARCHAR(16) | NOT NULL | PV/ESS/EVCS/IND_LOAD/COM_BLDG |
+| `capacity_kw` | DECIMAL(12,4) | NOT NULL | 装机容量 kW |
+| `is_control` | INT(2) | NOT NULL, DEFAULT 0 | 是否可控 1是 0否 |
+| `is_support_peak` | INT(2) | NOT NULL, DEFAULT 0 | 是否可参与调峰 1是 0否 |
+| `is_support_fm` | INT(2) | NOT NULL, DEFAULT 0 | 是否可参与调频 1是 0否 |
+| `response_min` | INT | NULL | 辅助服务响应时间,单位 min |
+| `max_up_kw` | DECIMAL(12,4) | NULL | 最大上调容量 kW |
+| `min_down_kw` | DECIMAL(12,4) | NULL | 最小下调容量 kW |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_resource_code`(resource_code)
+- `idx_resource_site`(site_id)
+- `idx_resource_device`(device_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_resource_point` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `resource_code` VARCHAR(64) NOT NULL COMMENT '资源编号',
+    `resource_name` VARCHAR(200) NOT NULL COMMENT '资源名称',
+    `site_id` BIGINT NOT NULL COMMENT '站点主键ID',
+    `device_id` BIGINT NOT NULL COMMENT '设备主键ID',
+    `resource_type` VARCHAR(16) NOT NULL COMMENT '资源类型 PV/ESS/EVCS/IND_LOAD/COM_BLDG',
+    `capacity_kw` DECIMAL(12,4) NOT NULL COMMENT '装机容量 kW',
+    `is_control` INT(2) NOT NULL DEFAULT 0 COMMENT '是否可控;1:是 0: 否',
+    `is_support_peak` INT(2) NOT NULL DEFAULT 0 COMMENT '是否可参与调峰 1:是 0:否',
+    `is_support_fm` INT(2) NOT NULL DEFAULT 0 COMMENT '是否可参与调频 1:是 0:否',
+    `response_min` INT NULL COMMENT '辅助服务响应时间,单位min',
+    `max_up_kw` DECIMAL(12,4) NULL COMMENT '最大上调容量 kW',
+    `min_down_kw` DECIMAL(12,4) NULL COMMENT '最小下调容量 kW',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_resource_code` (`resource_code`),
+    KEY `idx_resource_site` (`site_id`),
+    KEY `idx_resource_device` (`device_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='资源点';
+```
+
+### 5.10 vpp_device(设备)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `device_code` | VARCHAR(64) | NOT NULL | 设备编号 |
+| `device_uuid` | VARCHAR(50) | NOT NULL | 设备uuid |
+| `device_name` | VARCHAR(200) | NOT NULL | 设备名称 |
+| `site_id` | BIGINT(20) | NOT NULL, FK | 所属站点ID,关联 `vpp_site.id`(原 `resource_id` 已废弃) |
+| `device_type` | VARCHAR(32) | NOT NULL | INVERTER/PCS/EVCS/METER/Gateway等 |
+| `manufacturer` | VARCHAR(100) | NULL | 厂商 |
+| `model` | VARCHAR(100) | NULL | 型号 |
+| `rated_power_kw` | DECIMAL(12,4) | NULL | 额定功率 |
+| `comm_status` | TINYINT | NOT NULL | 0离线 1在线 |
+| `run_status` | TINYINT | NOT NULL | 0停机 1运行 2故障 |
+| `firmware_version` | VARCHAR(50) | NULL | 固件版本 |
+| `last_online_at` | DATETIME(3) | NULL | 最后在线时间 |
+| `gateway_id` | VARCHAR(64) | NULL | IoT网关标识 |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_device_code`(device_code)
+- `uk_device_uuid`(device_uuid)
+- `idx_device_site`(site_id)
+- `idx_device_comm`(comm_status, run_status)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_device` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `device_code` VARCHAR(64) NOT NULL COMMENT '设备编号',
+    `device_uuid` VARCHAR(50) NOT NULL COMMENT '设备uuid',
+    `device_name` VARCHAR(200) NOT NULL COMMENT '设备名称',
+    `site_id` BIGINT(20) NOT NULL COMMENT '所属站点ID',
+    `device_type` VARCHAR(32) NOT NULL COMMENT 'INVERTER/PCS/EVCS/METER/Gateway等',
+    `manufacturer` VARCHAR(100) NULL COMMENT '厂商',
+    `model` VARCHAR(100) NULL COMMENT '型号',
+    `rated_power_kw` DECIMAL(12,4) NULL COMMENT '额定功率',
+    `comm_status` TINYINT NOT NULL COMMENT '0离线 1在线',
+    `run_status` TINYINT NOT NULL COMMENT '0停机 1运行 2故障',
+    `firmware_version` VARCHAR(50) NULL COMMENT '固件版本',
+    `last_online_at` DATETIME(3) NULL COMMENT '最后在线时间',
+    `gateway_id` VARCHAR(64) NULL COMMENT 'IoT网关标识',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_device_code` (device_code),
+    UNIQUE KEY `uk_device_uuid` (device_uuid),
+    KEY `idx_device_site` (site_id),
+    KEY `idx_device_comm` (comm_status, run_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备';
+```
+
+**字段变更说明**:历史版本曾使用 `resource_id` 表示「所属资源点ID」。站点模型引入后,设备归属站点而非资源点,字段已统一为 `site_id`(所属站点ID)。资源点通过 `vpp_resource_point.device_id` 关联设备。
+
+**存量库升级**(若仍存在 `resource_id` 列):
+
+```sql
+ALTER TABLE `vpp_device` CHANGE `resource_id` `site_id` BIGINT(20) NOT NULL COMMENT '所属站点ID';
+ALTER TABLE `vpp_device` DROP INDEX `idx_device_resource`;
+ALTER TABLE `vpp_device` ADD KEY `idx_device_site` (`site_id`);
+```
+
+也可执行 `schema/vpp_device_site_id_migration.sql`(幂等)。
+
+### 5.11 vpp_device_control_log(设备远程控制日志)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `device_id` | BIGINT | NOT NULL | 设备ID |
+| `control_type` | VARCHAR(32) | NOT NULL | START/STOP/CONFIG/OTA |
+| `control_params` | JSON | NULL | 控制参数 |
+| `control_result` | TINYINT | NOT NULL | 0失败 1成功 2执行中 |
+| `result_message` | VARCHAR(500) | NULL | 结果说明 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+
+**索引**:
+- `idx_control_device`(device_id, create_time)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_device_control_log` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `device_id` BIGINT NOT NULL COMMENT '设备ID',
+    `control_type` VARCHAR(32) NOT NULL COMMENT 'START/STOP/CONFIG/OTA',
+    `control_params` JSON NULL COMMENT '控制参数',
+    `control_result` TINYINT NOT NULL COMMENT '0失败 1成功 2执行中',
+    `result_message` VARCHAR(500) NULL COMMENT '结果说明',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    PRIMARY KEY (`id`),
+    KEY `idx_control_device` (device_id, create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备远程控制日志';
+```
+
+### 5.12 vpp_energy_reading_monthly(月度电量核算)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `customer_id` | BIGINT | NOT NULL | 客户ID |
+| `resource_id` | BIGINT | NULL | 资源点ID,可空表示户号级 |
+| `settle_year` | SMALLINT | NOT NULL | 结算年 |
+| `settle_month` | TINYINT | NOT NULL | 结算月 |
+| `total_energy_kwh` | DECIMAL(18,4) | NOT NULL | 总用电量 kWh |
+| `peak_energy_kwh` | DECIMAL(18,4) | NULL | 峰电量 |
+| `flat_energy_kwh` | DECIMAL(18,4) | NULL | 平电量 |
+| `valley_energy_kwh` | DECIMAL(18,4) | NULL | 谷电量 |
+| `sharp_energy_kwh` | DECIMAL(18,4) | NULL | 尖电量 |
+| `gen_energy_kwh` | DECIMAL(18,4) | NULL | 发电量 kWh |
+| `calc_status` | TINYINT | NOT NULL | 0待核算 1已核算 2异常 |
+| `calc_at` | DATETIME(3) | NULL | 核算时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_energy_monthly`(customer_id, resource_id, settle_year, settle_month)
+- `idx_energy_period`(settle_year, settle_month)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_energy_reading_monthly` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `resource_id` BIGINT NULL COMMENT '资源点ID,可空表示户号级',
+    `settle_year` SMALLINT NOT NULL COMMENT '结算年',
+    `settle_month` TINYINT NOT NULL COMMENT '结算月',
+    `total_energy_kwh` DECIMAL(18,4) NOT NULL COMMENT '总用电量 kWh',
+    `peak_energy_kwh` DECIMAL(18,4) NULL COMMENT '峰电量',
+    `flat_energy_kwh` DECIMAL(18,4) NULL COMMENT '平电量',
+    `valley_energy_kwh` DECIMAL(18,4) NULL COMMENT '谷电量',
+    `sharp_energy_kwh` DECIMAL(18,4) NULL COMMENT '尖电量',
+    `gen_energy_kwh` DECIMAL(18,4) NULL COMMENT '发电量 kWh',
+    `calc_status` TINYINT NOT NULL COMMENT '0待核算 1已核算 2异常',
+    `calc_at` DATETIME(3) NULL COMMENT '核算时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_energy_monthly` (customer_id, resource_id, settle_year, settle_month),
+    KEY `idx_energy_period` (settle_year, settle_month)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='月度电量核算';
+```
+
+### 5.13 vpp_energy_summary_daily(日度能源汇总)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `summary_date` | DATE | NOT NULL | 汇总日期 |
+| `resource_id` | BIGINT | NULL | 资源点ID,NULL表示全站 |
+| `resource_type` | VARCHAR(16) | NULL | 资源类型 |
+| `gen_energy_kwh` | DECIMAL(18,4) | NULL | 发电量 |
+| `use_energy_kwh` | DECIMAL(18,4) | NULL | 用电量 |
+| `green_ratio` | DECIMAL(5,2) | NULL | 绿电消纳比例% |
+| `max_power_kw` | DECIMAL(12,4) | NULL | 日最大有功功率 |
+| `avg_power_kw` | DECIMAL(12,4) | NULL | 日平均有功功率 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_summary_daily`(summary_date, resource_id)
+- `idx_summary_date`(summary_date)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_energy_summary_daily` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `summary_date` DATE NOT NULL COMMENT '汇总日期',
+    `resource_id` BIGINT NULL COMMENT '资源点ID,NULL表示全站',
+    `resource_type` VARCHAR(16) NULL COMMENT '资源类型',
+    `gen_energy_kwh` DECIMAL(18,4) NULL COMMENT '发电量',
+    `use_energy_kwh` DECIMAL(18,4) NULL COMMENT '用电量',
+    `green_ratio` DECIMAL(5,2) NULL COMMENT '绿电消纳比例%',
+    `max_power_kw` DECIMAL(12,4) NULL COMMENT '日最大有功功率',
+    `avg_power_kw` DECIMAL(12,4) NULL COMMENT '日平均有功功率',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_summary_daily` (summary_date, resource_id),
+    KEY `idx_summary_date` (summary_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='日度能源汇总';
+```
+
+### 5.14 vpp_settlement_bill(电费账单)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `bill_no` | VARCHAR(64) | NOT NULL | 账单编号 |
+| `customer_id` | BIGINT | NOT NULL | 客户ID |
+| `settle_year` | SMALLINT | NOT NULL | 结算年 |
+| `settle_month` | TINYINT | NOT NULL | 结算月 |
+| `total_energy_kwh` | DECIMAL(18,4) | NOT NULL | 总用电量 |
+| `total_amount` | DECIMAL(18,4) | NOT NULL | 应付金额 |
+| `paid_amount` | DECIMAL(18,4) | NOT NULL | 已付金额,默认0 |
+| `payment_status` | TINYINT | NOT NULL | 0未缴 1部分 2已缴 3逾期 |
+| `due_date` | DATE | NULL | 缴费截止日期 |
+| `bill_file_url` | VARCHAR(500) | NULL | 账单PDF |
+| `generated_at` | DATETIME(3) | NULL | 生成时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_bill_no`(bill_no)
+- `idx_bill_customer`(customer_id, settle_year, settle_month)
+- `idx_bill_payment`(payment_status)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_settlement_bill` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `bill_no` VARCHAR(64) NOT NULL COMMENT '账单编号',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `settle_year` SMALLINT NOT NULL COMMENT '结算年',
+    `settle_month` TINYINT NOT NULL COMMENT '结算月',
+    `total_energy_kwh` DECIMAL(18,4) NOT NULL COMMENT '总用电量',
+    `total_amount` DECIMAL(18,4) NOT NULL COMMENT '应付金额',
+    `paid_amount` DECIMAL(18,4) NOT NULL COMMENT '已付金额,默认0',
+    `payment_status` TINYINT NOT NULL COMMENT '0未缴 1部分 2已缴 3逾期',
+    `due_date` DATE NULL COMMENT '缴费截止日期',
+    `bill_file_url` VARCHAR(500) NULL COMMENT '账单PDF',
+    `generated_at` DATETIME(3) NULL COMMENT '生成时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_bill_no` (bill_no),
+    KEY `idx_bill_customer` (customer_id, settle_year, settle_month),
+    KEY `idx_bill_payment` (payment_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='电费账单';
+```
+
+### 5.15 vpp_settlement_bill_detail(账单分时明细)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `bill_id` | BIGINT | NOT NULL | 账单ID |
+| `time_period` | TINYINT | NOT NULL | 1尖 2峰 3平 4谷 |
+| `energy_kwh` | DECIMAL(18,4) | NOT NULL | 时段电量 |
+| `price` | DECIMAL(10,4) | NOT NULL | 时段电价 |
+| `amount` | DECIMAL(18,4) | NOT NULL | 时段金额 |
+| `tenant_id` | INT | NULL | 租户ID |
+
+**索引**:
+- `idx_bill_detail_bill`(bill_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_settlement_bill_detail` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `bill_id` BIGINT NOT NULL COMMENT '账单ID',
+    `time_period` TINYINT NOT NULL COMMENT '1尖 2峰 3平 4谷',
+    `energy_kwh` DECIMAL(18,4) NOT NULL COMMENT '时段电量',
+    `price` DECIMAL(10,4) NOT NULL COMMENT '时段电价',
+    `amount` DECIMAL(18,4) NOT NULL COMMENT '时段金额',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    PRIMARY KEY (`id`),
+    KEY `idx_bill_detail_bill` (bill_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账单分时明细';
+```
+
+### 5.16 vpp_payment_record(缴费记录)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `bill_id` | BIGINT | NOT NULL | 账单ID |
+| `payment_amount` | DECIMAL(18,4) | NOT NULL | 缴费金额 |
+| `payment_method` | TINYINT | NOT NULL | 1线下转账 2电费抵扣 3其他 |
+| `payment_date` | DATE | NOT NULL | 缴费日期 |
+| `voucher_url` | VARCHAR(500) | NULL | 缴费凭证 |
+| `remark` | VARCHAR(500) | NULL | 对账备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+
+**索引**:
+- `idx_payment_bill`(bill_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_payment_record` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `bill_id` BIGINT NOT NULL COMMENT '账单ID',
+    `payment_amount` DECIMAL(18,4) NOT NULL COMMENT '缴费金额',
+    `payment_method` TINYINT NOT NULL COMMENT '1线下转账 2电费抵扣 3其他',
+    `payment_date` DATE NOT NULL COMMENT '缴费日期',
+    `voucher_url` VARCHAR(500) NULL COMMENT '缴费凭证',
+    `remark` VARCHAR(500) NULL COMMENT '对账备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    PRIMARY KEY (`id`),
+    KEY `idx_payment_bill` (bill_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='缴费记录';
+```
+
+### 5.17 vpp_report_task(报送任务)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `task_no` | VARCHAR(64) | NOT NULL | 任务编号 |
+| `task_name` | VARCHAR(200) | NOT NULL | 任务名称 |
+| `report_org` | TINYINT | NOT NULL | 1交易中心 2能监局 3经信委 4国网上海 |
+| `report_cycle` | TINYINT | NOT NULL | 1月报 2年报 3临时 |
+| `report_type` | VARCHAR(64) | NOT NULL | 报表类型编码 |
+| `deadline` | DATETIME(3) | NOT NULL | 截止日期 |
+| `task_status` | TINYINT | NOT NULL | 0待填报 1待审核 2报送中 3已完成 4已逾期 |
+| `assignee_id` | BIGINT | NULL | 填报人 |
+| `remind_days` | INT | NULL | 提前提醒天数 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_report_task_no`(task_no)
+- `idx_report_task_status`(task_status, deadline)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_report_task` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `task_no` VARCHAR(64) NOT NULL COMMENT '任务编号',
+    `task_name` VARCHAR(200) NOT NULL COMMENT '任务名称',
+    `report_org` TINYINT NOT NULL COMMENT '1交易中心 2能监局 3经信委 4国网上海',
+    `report_cycle` TINYINT NOT NULL COMMENT '1月报 2年报 3临时',
+    `report_type` VARCHAR(64) NOT NULL COMMENT '报表类型编码',
+    `deadline` DATETIME(3) NOT NULL COMMENT '截止日期',
+    `task_status` TINYINT NOT NULL COMMENT '0待填报 1待审核 2报送中 3已完成 4已逾期',
+    `assignee_id` BIGINT NULL COMMENT '填报人',
+    `remind_days` INT NULL COMMENT '提前提醒天数',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_report_task_no` (task_no),
+    KEY `idx_report_task_status` (task_status, deadline)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送任务';
+```
+
+### 5.18 vpp_report_record(报送记录)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `task_id` | BIGINT | NOT NULL | 任务ID |
+| `record_no` | VARCHAR(64) | NOT NULL | 报送记录编号 |
+| `report_period` | VARCHAR(32) | NOT NULL | 报送周期标识 如2026-05 |
+| `submit_mode` | TINYINT | NOT NULL | 1接口推送 2手动上传 |
+| `record_status` | TINYINT | NOT NULL | 0草稿 1待审核 2报送中 3成功 4失败 |
+| `report_file_url` | VARCHAR(500) | NULL | 报送文件 |
+| `receipt_file_url` | VARCHAR(500) | NULL | 官方回执 |
+| `retry_count` | TINYINT | NOT NULL | 重试次数,默认0 |
+| `submitted_at` | DATETIME(3) | NULL | 报送时间 |
+| `archived_at` | DATETIME(3) | NULL | 归档时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_report_record_no`(record_no)
+- `idx_report_record_task`(task_id)
+- `idx_report_record_period`(report_period)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_report_record` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `task_id` BIGINT NOT NULL COMMENT '任务ID',
+    `record_no` VARCHAR(64) NOT NULL COMMENT '报送记录编号',
+    `report_period` VARCHAR(32) NOT NULL COMMENT '报送周期标识 如2026-05',
+    `submit_mode` TINYINT NOT NULL COMMENT '1接口推送 2手动上传',
+    `record_status` TINYINT NOT NULL COMMENT '0草稿 1待审核 2报送中 3成功 4失败',
+    `report_file_url` VARCHAR(500) NULL COMMENT '报送文件',
+    `receipt_file_url` VARCHAR(500) NULL COMMENT '官方回执',
+    `retry_count` TINYINT NOT NULL COMMENT '重试次数,默认0',
+    `submitted_at` DATETIME(3) NULL COMMENT '报送时间',
+    `archived_at` DATETIME(3) NULL COMMENT '归档时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_report_record_no` (record_no),
+    KEY `idx_report_record_task` (task_id),
+    KEY `idx_report_record_period` (report_period)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送记录';
+```
+
+### 5.19 vpp_report_data(报送填报数据)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `record_id` | BIGINT | NOT NULL | 报送记录ID |
+| `form_data` | JSON | NOT NULL | 填报表单JSON |
+| `validation_errors` | JSON | NULL | 校验异常项 |
+| `is_draft` | TINYINT | NOT NULL | 是否草稿 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_report_data_record`(record_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_report_data` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `record_id` BIGINT NOT NULL COMMENT '报送记录ID',
+    `form_data` JSON NOT NULL COMMENT '填报表单JSON',
+    `validation_errors` JSON NULL COMMENT '校验异常项',
+    `is_draft` TINYINT NOT NULL COMMENT '是否草稿',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_report_data_record` (record_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送填报数据';
+```
+
+### 5.20 vpp_report_audit_log(报送审核流水)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `record_id` | BIGINT | NOT NULL | 报送记录ID |
+| `audit_level` | TINYINT | NOT NULL | 1填报 2复核 3审批 |
+| `action` | TINYINT | NOT NULL | 1提交 2通过 3驳回 |
+| `opinion` | VARCHAR(500) | NULL | 意见 |
+| `operator_ip` | VARCHAR(64) | NULL | 操作IP |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+
+**索引**:
+- `idx_report_audit_record`(record_id, create_time)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_report_audit_log` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `record_id` BIGINT NOT NULL COMMENT '报送记录ID',
+    `audit_level` TINYINT NOT NULL COMMENT '1填报 2复核 3审批',
+    `action` TINYINT NOT NULL COMMENT '1提交 2通过 3驳回',
+    `opinion` VARCHAR(500) NULL COMMENT '意见',
+    `operator_ip` VARCHAR(64) NULL COMMENT '操作IP',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    PRIMARY KEY (`id`),
+    KEY `idx_report_audit_record` (record_id, create_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='报送审核流水';
+```
+
+### 5.21 vpp_dr_event(需求响应事件)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `event_id` | VARCHAR(64) | NOT NULL | 运管平台事件ID |
+| `event_name` | VARCHAR(200) | NULL | 事件名称 |
+| `response_type` | TINYINT | NOT NULL | 1日前 2日内 3秒级 |
+| `event_type` | TINYINT | NULL | 1削峰 2填谷 |
+| `start_time` | DATETIME(3) | NOT NULL | 响应开始 |
+| `end_time` | DATETIME(3) | NOT NULL | 响应结束 |
+| `target_capacity_kw` | DECIMAL(12,4) | NOT NULL | 目标容量 kW |
+| `cleared_capacity_kw` | DECIMAL(12,4) | NULL | 出清容量 kW |
+| `subsidy_price` | DECIMAL(10,4) | NULL | 补贴标准 元/kWh |
+| `event_status` | TINYINT | NOT NULL | 0待参与 1已申报 2执行中 3已结束 4已取消 |
+| `raw_payload` | JSON | NULL | 原始邀约报文 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_dr_event_id`(event_id)
+- `idx_dr_event_time`(start_time, event_status)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_event` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `event_id` VARCHAR(64) NOT NULL COMMENT '运管平台事件ID',
+    `event_name` VARCHAR(200) NULL COMMENT '事件名称',
+    `response_type` TINYINT NOT NULL COMMENT '1日前 2日内 3秒级',
+    `event_type` TINYINT NULL COMMENT '1削峰 2填谷',
+    `start_time` DATETIME(3) NOT NULL COMMENT '响应开始',
+    `end_time` DATETIME(3) NOT NULL COMMENT '响应结束',
+    `target_capacity_kw` DECIMAL(12,4) NOT NULL COMMENT '目标容量 kW',
+    `cleared_capacity_kw` DECIMAL(12,4) NULL COMMENT '出清容量 kW',
+    `subsidy_price` DECIMAL(10,4) NULL COMMENT '补贴标准 元/kWh',
+    `event_status` TINYINT NOT NULL COMMENT '0待参与 1已申报 2执行中 3已结束 4已取消',
+    `raw_payload` JSON NULL COMMENT '原始邀约报文',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_dr_event_id` (event_id),
+    KEY `idx_dr_event_time` (start_time, event_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求响应事件';
+```
+
+### 5.22 vpp_dr_participation(事件参与记录)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `event_id` | BIGINT | NOT NULL | 事件ID |
+| `customer_id` | BIGINT | NOT NULL | 客户ID |
+| `resource_id` | BIGINT | NULL | 资源点ID |
+| `participate_status` | TINYINT | NOT NULL | 0待确认 1参与 2拒绝 |
+| `declared_capacity_kw` | DECIMAL(12,4) | NULL | 申报容量 |
+| `cleared_capacity_kw` | DECIMAL(12,4) | NULL | 出清容量 |
+| `declared_at` | DATETIME(3) | NULL | 申报时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `idx_dr_part_event`(event_id)
+- `uk_dr_part`(event_id, customer_id, resource_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_participation` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `event_id` BIGINT NOT NULL COMMENT '事件ID',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `resource_id` BIGINT NULL COMMENT '资源点ID',
+    `participate_status` TINYINT NOT NULL COMMENT '0待确认 1参与 2拒绝',
+    `declared_capacity_kw` DECIMAL(12,4) NULL COMMENT '申报容量',
+    `cleared_capacity_kw` DECIMAL(12,4) NULL COMMENT '出清容量',
+    `declared_at` DATETIME(3) NULL COMMENT '申报时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_dr_part_event` (event_id),
+    UNIQUE KEY `uk_dr_part` (event_id, customer_id, resource_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='事件参与记录';
+```
+
+### 5.23 vpp_dr_invitation(企业邀约)
+
+> 对应 Web 端「邀约管理」界面:聚合商向签约企业下发需求响应邀约,记录回复截止、容量申报、短信通知及响应进度。与 `vpp_dr_participation` 区别:本表面向**运营邀约台账**(一事件一企业一条),参与记录在客户确认后通过 `participation_id` 关联。
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, AUTO_INCREMENT | 主键 |
+| `invitation_no` | VARCHAR(32) | NOT NULL | 邀约编号,唯一 |
+| `dr_event_id` | BIGINT | NOT NULL | 需求响应事件 ID |
+| `customer_id` | BIGINT | NOT NULL | 客户 ID |
+| `execute_start_date` | DATE | NOT NULL | 执行开始日期 |
+| `execute_end_date` | DATE | NOT NULL | 执行结束日期 |
+| `reply_deadline` | DATETIME(3) | NOT NULL | 邀约回复截止日期 |
+| `issue_time` | DATETIME(3) | NOT NULL | 下发时间 |
+| `transaction_type` | TINYINT | NULL | 交易类型:1削峰 2填谷 |
+| `response_type` | TINYINT | NOT NULL | 1日前 2日内 3秒级 |
+| `demand_capacity_kw` | DECIMAL(12,4) | NOT NULL | 需求容量 kW |
+| `declared_capacity_kw` | DECIMAL(12,4) | NULL | 已申报容量 kW |
+| `reply_status` | TINYINT | NOT NULL | 0待回复 1参与 2拒绝 3超时 |
+| `response_status` | TINYINT | NOT NULL | 0待响应 1已申报 2执行中 3已结束 4已取消 |
+| `sms_notify_status` | TINYINT | NOT NULL, DEFAULT 0 | 0未发送 1已发送 2发送失败 |
+| `sms_notify_at` | DATETIME(3) | NULL | 短信通知时间 |
+| `sms_contact_id` | BIGINT | NULL | 通知联系人 ID |
+| `participation_id` | BIGINT | NULL | 关联参与记录 ID |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户 ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_dr_invitation_no`(invitation_no)
+- `uk_dr_invitation_event_customer`(dr_event_id, customer_id)
+- `idx_dr_invitation_customer`(customer_id, reply_status)
+- `idx_dr_invitation_issue`(issue_time, response_type)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_invitation` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `invitation_no` VARCHAR(32) NOT NULL COMMENT '邀约编号',
+    `dr_event_id` BIGINT NOT NULL COMMENT '需求响应事件ID',
+    `customer_id` BIGINT NOT NULL COMMENT '客户ID',
+    `execute_start_date` DATE NOT NULL COMMENT '执行开始日期',
+    `execute_end_date` DATE NOT NULL COMMENT '执行结束日期',
+    `reply_deadline` DATETIME(3) NOT NULL COMMENT '邀约回复截止日期',
+    `issue_time` DATETIME(3) NOT NULL COMMENT '下发时间',
+    `transaction_type` TINYINT NULL COMMENT '交易类型 1削峰 2填谷',
+    `response_type` TINYINT NOT NULL COMMENT '1日前 2日内 3秒级',
+    `demand_capacity_kw` DECIMAL(12,4) NOT NULL COMMENT '需求容量 kW',
+    `declared_capacity_kw` DECIMAL(12,4) NULL COMMENT '已申报容量 kW',
+    `reply_status` TINYINT NOT NULL COMMENT '0待回复 1参与 2拒绝 3超时',
+    `response_status` TINYINT NOT NULL COMMENT '0待响应 1已申报 2执行中 3已结束 4已取消',
+    `sms_notify_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0未发送 1已发送 2发送失败',
+    `sms_notify_at` DATETIME(3) NULL COMMENT '短信通知时间',
+    `sms_contact_id` BIGINT NULL COMMENT '通知联系人ID',
+    `participation_id` BIGINT NULL COMMENT '关联参与记录ID',
+    `remark` VARCHAR(500) NULL COMMENT '备注',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_dr_invitation_no` (invitation_no),
+    UNIQUE KEY `uk_dr_invitation_event_customer` (dr_event_id, customer_id),
+    KEY `idx_dr_invitation_customer` (customer_id, reply_status),
+    KEY `idx_dr_invitation_issue` (issue_time, response_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求响应企业邀约';
+```
+
+### 5.24 vpp_dr_strategy(响应策略)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `strategy_code` | VARCHAR(64) | NOT NULL | 策略编码 |
+| `strategy_name` | VARCHAR(200) | NOT NULL | 策略名称 |
+| `response_type` | TINYINT | NOT NULL | 适用响应类型 |
+| `adjust_step_kw` | DECIMAL(12,4) | NULL | 调节步长 kW |
+| `execute_mode` | TINYINT | NOT NULL | 1并行 2串行 |
+| `strategy_config` | JSON | NULL | 策略扩展配置 |
+| `is_default` | TINYINT | NOT NULL | 是否默认策略 |
+| `is_enabled` | TINYINT | NOT NULL | 是否启用 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_dr_strategy_code`(strategy_code)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_strategy` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `strategy_code` VARCHAR(64) NOT NULL COMMENT '策略编码',
+    `strategy_name` VARCHAR(200) NOT NULL COMMENT '策略名称',
+    `response_type` TINYINT NOT NULL COMMENT '适用响应类型',
+    `adjust_step_kw` DECIMAL(12,4) NULL COMMENT '调节步长 kW',
+    `execute_mode` TINYINT NOT NULL COMMENT '1并行 2串行',
+    `strategy_config` JSON NULL COMMENT '策略扩展配置',
+    `is_default` TINYINT NOT NULL COMMENT '是否默认策略',
+    `is_enabled` TINYINT NOT NULL COMMENT '是否启用',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_dr_strategy_code` (strategy_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应策略';
+```
+
+### 5.25 vpp_dr_strategy_resource(策略-资源关联)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `strategy_id` | BIGINT | NOT NULL | 策略ID |
+| `resource_id` | BIGINT | NOT NULL | 资源点ID |
+| `priority` | TINYINT | NOT NULL | 优先级序号 |
+| `max_adjust_kw` | DECIMAL(12,4) | NULL | 最大调节量 |
+| `tenant_id` | INT | NULL | 租户ID |
+
+**索引**:
+- `uk_strategy_resource`(strategy_id, resource_id)
+- `idx_strategy_resource_priority`(strategy_id, priority)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_strategy_resource` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `strategy_id` BIGINT NOT NULL COMMENT '策略ID',
+    `resource_id` BIGINT NOT NULL COMMENT '资源点ID',
+    `priority` TINYINT NOT NULL COMMENT '优先级序号',
+    `max_adjust_kw` DECIMAL(12,4) NULL COMMENT '最大调节量',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_strategy_resource` (strategy_id, resource_id),
+    KEY `idx_strategy_resource_priority` (strategy_id, priority)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='策略-资源关联';
+```
+
+### 5.26 vpp_dr_execution(响应执行明细)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `event_id` | BIGINT | NOT NULL | 事件ID |
+| `resource_id` | BIGINT | NOT NULL | 资源点ID |
+| `target_kw` | DECIMAL(12,4) | NOT NULL | 目标调节量 |
+| `actual_kw` | DECIMAL(12,4) | NULL | 实际调节量 |
+| `execute_status` | TINYINT | NOT NULL | 0待执行 1执行中 2完成 3异常 |
+| `started_at` | DATETIME(3) | NULL | 开始时间 |
+| `finished_at` | DATETIME(3) | NULL | 结束时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `idx_dr_exec_event`(event_id)
+- `idx_dr_exec_resource`(resource_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_execution` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `event_id` BIGINT NOT NULL COMMENT '事件ID',
+    `resource_id` BIGINT NOT NULL COMMENT '资源点ID',
+    `target_kw` DECIMAL(12,4) NOT NULL COMMENT '目标调节量',
+    `actual_kw` DECIMAL(12,4) NULL COMMENT '实际调节量',
+    `execute_status` TINYINT NOT NULL COMMENT '0待执行 1执行中 2完成 3异常',
+    `started_at` DATETIME(3) NULL COMMENT '开始时间',
+    `finished_at` DATETIME(3) NULL COMMENT '结束时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_dr_exec_event` (event_id),
+    KEY `idx_dr_exec_resource` (resource_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应执行明细';
+```
+
+### 5.27 vpp_dr_evaluation(响应效果评估)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `event_id` | BIGINT | NOT NULL | 事件ID |
+| `actual_capacity_kw` | DECIMAL(12,4) | NULL | 实际响应容量 |
+| `response_duration_min` | INT | NULL | 响应时长分钟 |
+| `subsidy_amount` | DECIMAL(18,4) | NULL | 补贴金额 |
+| `qualified_rate` | DECIMAL(5,2) | NULL | 执行合格率% |
+| `report_file_url` | VARCHAR(500) | NULL | 评估报告URL |
+| `evaluated_at` | DATETIME(3) | NULL | 评估时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_dr_eval_event`(event_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_dr_evaluation` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `event_id` BIGINT NOT NULL COMMENT '事件ID',
+    `actual_capacity_kw` DECIMAL(12,4) NULL COMMENT '实际响应容量',
+    `response_duration_min` INT NULL COMMENT '响应时长分钟',
+    `subsidy_amount` DECIMAL(18,4) NULL COMMENT '补贴金额',
+    `qualified_rate` DECIMAL(5,2) NULL COMMENT '执行合格率%',
+    `report_file_url` VARCHAR(500) NULL COMMENT '评估报告URL',
+    `evaluated_at` DATETIME(3) NULL COMMENT '评估时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_dr_eval_event` (event_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='响应效果评估';
+```
+
+### 5.28 vpp_registration(运管平台注册信息)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `dn_id` | VARCHAR(64) | NOT NULL | 虚拟电厂运营商ID |
+| `registration_id` | VARCHAR(128) | NOT NULL | 运管平台registrationID |
+| `reg_status` | TINYINT | NOT NULL | 0未注册 1已注册 2需重新注册 |
+| `registered_at` | DATETIME(3) | NULL | 注册时间 |
+| `meta_report_id` | VARCHAR(128) | NULL | 元数据报告ID |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 创建人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `uk_vpp_dn_id`(dn_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_registration` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `dn_id` VARCHAR(64) NOT NULL COMMENT '虚拟电厂运营商ID',
+    `registration_id` VARCHAR(128) NOT NULL COMMENT '运管平台registrationID',
+    `reg_status` TINYINT NOT NULL COMMENT '0未注册 1已注册 2需重新注册',
+    `registered_at` DATETIME(3) NULL COMMENT '注册时间',
+    `meta_report_id` VARCHAR(128) NULL COMMENT '元数据报告ID',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    `create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    `update_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    `created_by` VARCHAR(30) NULL COMMENT '创建人',
+    `updated_by` VARCHAR(30) NULL COMMENT '更新人',
+    `delete_flag` INT(1) NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    `deleted_at` DATETIME(3) NULL COMMENT '软删除时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_vpp_dn_id` (dn_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='运管平台注册信息';
+```
+
+### 5.29 vpp_report_log(数据上报日志)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | PK, NOT NULL | 主键 |
+| `report_type` | VARCHAR(64) | NOT NULL | MomentData/IntervalData/RegisterReport等 |
+| `request_id` | VARCHAR(64) | NULL | 请求ID |
+| `report_status` | TINYINT | NOT NULL | 0失败 1成功 |
+| `response_code` | INT | NULL | 响应码 |
+| `error_message` | VARCHAR(500) | NULL | 错误信息 |
+| `payload_size` | INT | NULL | 报文大小字节 |
+| `reported_at` | DATETIME(3) | NOT NULL | 上报时间 |
+| `tenant_id` | INT | NULL | 租户ID |
+
+**索引**:
+- `idx_vpp_report_type_time`(report_type, reported_at)
+
+**DDL**:
+
+```sql
+CREATE TABLE `vpp_report_log` (
+    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `report_type` VARCHAR(64) NOT NULL COMMENT 'MomentData/IntervalData/RegisterReport等',
+    `request_id` VARCHAR(64) NULL COMMENT '请求ID',
+    `report_status` TINYINT NOT NULL COMMENT '0失败 1成功',
+    `response_code` INT NULL COMMENT '响应码',
+    `error_message` VARCHAR(500) NULL COMMENT '错误信息',
+    `payload_size` INT NULL COMMENT '报文大小字节',
+    `reported_at` DATETIME(3) NOT NULL COMMENT '上报时间',
+    `tenant_id` INT NULL COMMENT '租户ID',
+    PRIMARY KEY (`id`),
+    KEY `idx_vpp_report_type_time` (report_type, reported_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='数据上报日志';
+```
+
+### 5.30 vpp_file_archive(电子档案)
+
+| 字段名 | 类型 | 约束 | 说明 |
+|--------|------|------|------|
+| `id` | BIGINT | NOT NULL | 主键 |
+| `file_name` | VARCHAR(255) | NOT NULL | 原始文件名 |
+| `file_url` | VARCHAR(500) | NOT NULL | 存储路径 |
+| `file_type` | VARCHAR(32) | NOT NULL | 文件类型 |
+| `file_size` | BIGINT | NULL | 文件大小字节 |
+| `biz_type` | VARCHAR(32) | NOT NULL | 业务类型 |
+| `biz_id` | BIGINT | NOT NULL | 业务主键 |
+| `remark` | VARCHAR(500) | NULL | 备注 |
+| `tenant_id` | INT | NULL | 租户ID |
+| `create_time` | DATETIME(3) | NOT NULL, DEFAULT | 创建时间 |
+| `update_time` | DATETIME(3) | NOT NULL, DEFAULT | 更新时间 |
+| `created_by` | VARCHAR(30) | NULL | 上传人 |
+| `updated_by` | VARCHAR(30) | NULL | 更新人 |
+| `delete_flag` | INT(1) | NOT NULL, DEFAULT | 删除标识 0未删除 1已删除 |
+| `deleted_at` | DATETIME(3) | NULL | 软删除时间 |
+
+**索引**:
+- `idx_file_archive_biz`(biz_type, biz_id)
+
+**DDL**:
+
+```sql
+CREATE TABLE vpp_file_archive (
+    id          BIGINT         NOT NULL AUTO_INCREMENT COMMENT '主键',
+    file_name   VARCHAR(255)   NOT NULL COMMENT '原始文件名',
+    file_url    VARCHAR(500)   NOT NULL COMMENT '存储路径',
+    file_type   VARCHAR(32)    NOT NULL COMMENT '文件类型',
+    file_size   BIGINT         NULL COMMENT '文件大小字节',
+    biz_type    VARCHAR(32)    NOT NULL COMMENT '业务类型',
+    biz_id      BIGINT         NOT NULL COMMENT '业务主键',
+    remark      VARCHAR(500)   NULL COMMENT '备注',
+    tenant_id   INT            NULL COMMENT '租户ID',
+    create_time DATETIME(3)    NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
+    update_time DATETIME(3)    NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
+    created_by  VARCHAR(30)    NULL COMMENT '上传人',
+    updated_by  VARCHAR(30)    NULL COMMENT '更新人',
+    delete_flag INT(1)         NOT NULL DEFAULT 0 COMMENT '删除标识 0未删除 1已删除',
+    deleted_at  DATETIME(3)    NULL COMMENT '软删除时间',
+    PRIMARY KEY (id),
+    KEY idx_file_archive_biz (biz_type, biz_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='电子档案';
+```
+
+## 5A 沿用 usky-cloud 数据表详细说明
+
+本章描述虚拟电厂平台复用的 `usky-cloud.sql` 已有表结构。**沿用 `base_*`、`sys_*`、`dmp_*`、`rule_*`(`rule_engine*`)前缀表**;`ems_*`、`sas_*`、`issue_*`、`file_upload` 等**不在复用范围**。**不提供新建 DDL**,建表脚本以 `usky-cloud.sql` 为准。
+
+### 5A.1 复用原则
+
+| 原则 | 说明 |
+|------|------|
+| 前缀限定 | 复用 `base_*`、`sys_*`、`dmp_*`、`rule_*`;`ems_*`、`sas_*` 等业务系统表由各自子系统维护,VPP 不直接依赖 |
+| 不重复建表 | 告警规则、告警记录、用户权限、字典、物联网设备等能力由上述平台表承载 |
+| 业务关联 | VPP 表通过 `tenant_id`、`device_code`、`biz_type`/`biz_id` 等与平台表关联 |
+| 字段映射 | 应用层做枚举映射,不修改 usky-cloud 既有字段语义 |
+| VPP 补位 | 平台无对应前缀表的能力(附件 `vpp_file_archive`)由 VPP 专属表承担 |
+
+### 5A.2 告警规则引擎(`rule_*`)
+
+#### 5A.2.1 rule_engine(规则引擎 / 告警规则)
+
+| 字段名 | 类型 | 说明 | VPP 业务映射 |
+|--------|------|------|--------------|
+| id | bigint | 主键 | 规则 ID |
+| name | varchar(20) | 规则名称 | 对应告警规则名称 |
+| status | tinyint | 0停用 1启用 | 对应 is_enabled |
+| descr | varchar(200) | 规则描述 | — |
+| detail | json | 规则详情(阈值、条件、动作) | 对应 rule_config;动作可含 `alarmEvent` |
+| tenant_id | int | 租户号 | 多租户隔离 |
+
+关联表:`rule_engine_device`(绑定 `vpp_device.device_code`)、`rule_engine_cron`、`rule_engine_condition`。
+
+#### 5A.2.2 rule_engine_device(规则关联设备)
+
+| 字段名 | 类型 | 说明 | VPP 映射 |
+|--------|------|------|----------|
+| device_id | varchar(50) | 设备 ID | ↔ `vpp_device.device_code` / `dmp_device.device_id` |
+| device_uuid | varchar(50) | 设备 UUID | ↔ `vpp_device.device_uuid` / `dmp_device.device_uuid` |
+| identifier | varchar(50) | 物模型标识符 | 监测属性/事件 |
+| rule_engine_id | bigint | 规则 ID | 关联 `rule_engine.id` |
+| product_id | bigint | 产品 ID | 关联 `dmp_product` |
+
+索引:`uk_device_id_and_identifier_and_rule_engine_id`、`idx_rule_engine_id`。
+
+#### 5A.2.3 rule_engine_condition / rule_engine_cron
+
+- `rule_engine_condition`:`type` 1触发条件 2约束条件;`expression` 为条件表达式。
+- `rule_engine_cron`:规则定时调度,`cron` 为 CRON 表达式;`uk_rule_engine_id_and_cron` 防重复。
+
+#### 5A.2.4 rule_engine_log(规则执行日志)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| rule_engine_id | bigint | 规则 ID |
+| trigger_type | varchar(10) | device/space/cron |
+| action | varchar(50) | deviceControl/**alarmEvent**/workOrder |
+| detail | json | 执行详情 |
+| time | datetime | 执行时间 |
+
+`action=alarmEvent` 时,应用层将告警写入 `base_alarm`,并可通过 `base_alarm_notice` 触发通知。
+
+### 5A.3 告警管理(`base_*`)
+
+#### 5A.3.1 base_alarm(统一告警记录)
+
+| 字段名 | 类型 | 说明 | VPP 业务映射 |
+|--------|------|------|--------------|
+| id | int | 主键 | 告警 ID |
+| device_id | varchar(50) | 设备 ID | 对应 `vpp_device.device_code` / `dmp_device.device_id` |
+| alarm_time | datetime | 告警时间 | 对应 triggered_at |
+| alarm_type | varchar(50) | 告警类型编码 | 关联 `base_alarm_type.type_code` |
+| alarm_object | varchar(40) | 告警对象 | 资源点/设备名称 |
+| alarm_content | varchar(200) | 告警内容 | 告警描述 |
+| alarm_grade | int | 1–5 级 | 对应 alarm_level |
+| handle_by / handle_time / handle_content | — | 处理人/时间/内容 | 告警闭环(替代独立处理流水表) |
+| handle_status | int | 0未处理 1已处理 | 对应 alarm_status |
+| alarm_false | int | 0非误报 1误报 | 误报标记 |
+| site_photo | varchar(200) | 现场照片 URL | 处理附件 |
+| tenant_id / dept_id | int | 租户/组织 | 多租户隔离 |
+
+告警处理流水:优先使用 `base_alarm` 内 `handle_*` 字段;需完整审计轨迹时同步写入 `sys_oper_log`。
+
+#### 5A.3.2 base_alarm_type(告警类型)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| type_code | varchar(50) | 类型编码 |
+| type_name | varchar(50) | 类型名称 |
+| alarm_desc | varchar(500) | 类型描述 |
+
+VPP 上线时在 `base_alarm_type` 增量维护虚拟电厂告警类型(如越限、通讯中断、响应偏差等)。
+
+#### 5A.3.3 base_alarm_notice / base_alarm_notice_result
+
+- `base_alarm_notice`:按 `product_code`、`type_code` 配置分级接收人(receiver1/2/3)、超时升级(time_out1/2)、通知方式(notice_method)。
+- `base_alarm_notice_result`:记录每次推送结果,`alarm_id` 关联 `base_alarm.id`。
+
+#### 5A.3.4 base_alarm_statistic
+
+按日/月维度汇总告警数量,供看板与报表使用。
+
+#### 5A.3.5 告警状态映射
+
+| base_alarm.handle_status | VPP 语义 |
+|--------------------------|----------|
+| 0 | 待处理/待确认 |
+| 1 | 已处理/已关闭 |
+
+| base_alarm.alarm_grade | VPP 语义 |
+|------------------------|----------|
+| 1 | 一级(最严重) |
+| 2–3 | 重要 |
+| 4–5 | 一般/提示 |
+
+#### 5A.3.6 rule_engine 与 base_alarm 协作
+
+`rule_engine` + `rule_engine_device` 存储告警规则配置;规则命中且 `action=alarmEvent` 时**写入 `base_alarm`**,不写入 `ems_alarm_event`。
+
+### 5A.4 物联网(`dmp_*`)
+
+#### 5A.4.1 dmp_device(设备信息)
+
+| 字段名 | 类型 | 说明 | VPP 映射 |
+|--------|------|------|----------|
+| device_id | varchar(50) | 平台设备编号 | ↔ `vpp_device.device_code` |
+| device_uuid | varchar(50) | 设备 UUID | ↔ `vpp_device.device_uuid` |
+| device_name | varchar(50) | 设备名称 | — |
+| product_code | varchar(50) | 产品编码 | 物模型产品 |
+| service_status | int | 1未激活 2已激活 3禁用 | 设备启用状态 |
+| tenant_id | int | 租户号 | 多租户 |
+
+#### 5A.4.2 dmp_device_status / dmp_device_command
+
+- `dmp_device_status`:在线/离线及最后上下线时间,供监控大屏与告警前置判断。
+- `dmp_device_command`:远程控制下发记录;VPP 侧 `vpp_device_control_log` 可记录业务层指令摘要并关联 `dmp_device_command.id`。
+
+#### 5A.4.3 dmp_product / dmp_gateway / dmp_data_event
+
+- `dmp_product`、`dmp_product_command`:产品与物模型命令定义。
+- `dmp_gateway`:网关及子设备拓扑。
+- `dmp_data_event`、`dmp_data_info`:设备事件与上报数据,作为 `rule_engine` 判定输入。
+
+### 5A.5 系统管理(`sys_*`)
+
+#### 5A.5.1 sys_user(用户)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| user_id | bigint | 主键 |
+| user_name | varchar(30) | 登录名 |
+| nick_name | varchar(100) | 显示名称 |
+| phonenumber | varchar(11) | 手机号 |
+| email | varchar(50) | 邮箱 |
+| password | varchar(100) | 密码 |
+| status | char(1) | 0正常 1停用 |
+| tenant_id | int | 租户 ID |
+
+#### 5A.5.2 sys_role / sys_user_role / sys_role_menu
+
+- `sys_role`:角色定义,含 `role_key` 权限字符串、`data_scope` 数据范围。
+- `sys_user_role`:用户与角色多对多关联。
+- `sys_role_menu`:角色与菜单关联;按钮级权限由 `sys_menu.perms` 标识。
+
+VPP 新增菜单项通过向 `sys_menu` 插入虚拟电厂模块菜单,并经 `sys_role_menu` 关联至角色。
+
+#### 5A.5.3 sys_menu(菜单权限)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| menu_id | bigint | 菜单 ID |
+| menu_name | varchar(50) | 菜单名称 |
+| parent_id | bigint | 父菜单 |
+| path / component | varchar | 路由与组件 |
+| menu_type | char(1) | M目录 C菜单 F按钮 |
+| perms | varchar(100) | 权限标识 |
+
+#### 5A.5.4 sys_config(系统配置)
+
+| 字段名 | 类型 | 说明 | VPP 配置示例 |
+|--------|------|------|--------------|
+| config_key | varchar(100) | 参数键 | vpp.collect.interval.default |
+| config_value | text | 参数值 | 900 |
+| config_name | varchar(100) | 参数名称 | 默认采集频率秒 |
+| tenant_id | int | 租户 ID | 多租户隔离 |
+
+#### 5A.5.5 sys_dict_type / sys_dict_data
+
+标准若依风格字典,VPP 业务枚举(客户类型、资源类型等)可通过 `dict_type` 扩展,或在应用层常量维护。
+
+#### 5A.5.6 sys_oper_log / sys_logininfor
+
+- `sys_oper_log`:操作审计(模块、方法、URL、参数、结果、耗时),对应原 `vpp_sys_operation_log`。
+- `sys_logininfor`:登录成功/失败记录。
+
+#### 5A.5.7 vpp_file_archive(VPP 专属附件)
+
+合同、验收报告等附件元数据存入 `vpp_file_archive`(`biz_type` + `biz_id` 关联业务主表),文件实体存 OSS;**不使用** `file_upload` 表。
+
+### 5A.6 VPP 业务表与 usky-cloud 关联关系
+
+```mermaid
+erDiagram
+    vpp_device ||--|| dmp_device : maps
+    dmp_device ||--o{ dmp_device_status : status
+    rule_engine ||--o{ rule_engine_device : binds
+    vpp_device ||--o{ rule_engine_device : monitored
+    rule_engine ||--o{ base_alarm : triggers
+    vpp_device ||--o{ base_alarm : source
+    base_alarm ||--o{ base_alarm_notice_result : notifies
+    sys_user ||--o{ sys_user_role : assigns
+    sys_role ||--o{ sys_user_role : grants
+    sys_role ||--o{ sys_role_menu : owns
+    sys_menu ||--o{ sys_role_menu : maps
+    vpp_contract ||--o{ vpp_file_archive : attaches
+    vpp_resource_point ||--o{ base_alarm : location
+```
+
+| VPP 表 | usky-cloud 关联方式 |
+|--------|---------------------|
+| vpp_device | `dmp_device.device_id` = `device_code`;`dmp_device.device_uuid` = `device_uuid`;`rule_engine_device.device_id` 绑定规则 |
+| rule_engine | 命中后写入 `base_alarm`;输入数据来自 `dmp_data_event` / TDengine |
+| vpp_resource_point | `base_alarm.alarm_object` 或扩展字段存资源点编码 |
+| vpp_contract / vpp_customer | `vpp_file_archive.biz_id` 关联 |
+| 全部 VPP 模块 | `sys_user` + `sys_role_menu` 控制访问 |
+| VPP 租户 | `sys_tenant.id` = 各表 `tenant_id` |
+
+---
+
+## 6. 索引设计汇总
+
+### 6.1 唯一索引
+
+| 表名 | 索引名 | 字段 | 说明 |
+|------|--------|------|------|
+| vpp_customer | uk_customer_account_no | account_no | 业务唯一 |
+| vpp_customer_access | uk_access_apply_no | apply_no | 业务唯一 |
+| vpp_contract | uk_contract_no | contract_no | 业务唯一 |
+| vpp_contract_template | uk_template_code | template_code | 业务唯一 |
+| vpp_site | uk_site_code | site_code | 业务唯一 |
+| vpp_site_config | uk_config_site | site_id | 业务唯一 |
+| vpp_resource_point | uk_resource_code | resource_code | 业务唯一 |
+| vpp_device | uk_device_code | device_code | 业务唯一 |
+| vpp_device | uk_device_uuid | device_uuid | 物联网唯一标识 |
+| vpp_energy_reading_monthly | uk_energy_monthly | customer_id, resource_id, settle_year, settle_month | 业务唯一 |
+| vpp_energy_summary_daily | uk_summary_daily | summary_date, resource_id | 业务唯一 |
+| vpp_settlement_bill | uk_bill_no | bill_no | 业务唯一 |
+| vpp_report_task | uk_report_task_no | task_no | 业务唯一 |
+| vpp_report_record | uk_report_record_no | record_no | 业务唯一 |
+| vpp_report_data | uk_report_data_record | record_id | 业务唯一 |
+| vpp_dr_event | uk_dr_event_id | event_id | 业务唯一 |
+| vpp_dr_participation | uk_dr_part | event_id, customer_id, resource_id | 业务唯一 |
+| vpp_dr_invitation | uk_dr_invitation_no | invitation_no | 业务唯一 |
+| vpp_dr_invitation | uk_dr_invitation_event_customer | dr_event_id, customer_id | 一事件一企业一条邀约 |
+| vpp_dr_strategy | uk_dr_strategy_code | strategy_code | 业务唯一 |
+| vpp_dr_strategy_resource | uk_strategy_resource | strategy_id, resource_id | 业务唯一 |
+| vpp_dr_evaluation | uk_dr_eval_event | event_id | 业务唯一 |
+| vpp_registration | uk_vpp_dn_id | dn_id | 业务唯一 |
+
+### 6.2 高频查询索引
+
+| 场景 | 表 | 索引 | 查询条件 |
+|------|-----|------|----------|
+| 客户列表 | vpp_customer | idx_customer_lifecycle | lifecycle_status + deleted_at |
+| 资源看板 | vpp_resource_point | idx_resource_site | site_id + resource_type |
+| 站点设备 | vpp_device | idx_device_site | site_id |
+| 告警待办 | base_alarm | — | tenant_id + alarm_time DESC |
+| 报送任务 | vpp_report_task | idx_report_task_status | task_status + deadline |
+| 需求响应 | vpp_dr_event | idx_dr_event_time | start_time + event_status |
+| 邀约管理 | vpp_dr_invitation | idx_dr_invitation_issue | issue_time + response_type |
+| 邀约管理 | vpp_dr_invitation | idx_dr_invitation_customer | customer_id + reply_status |
+| 账单查询 | vpp_settlement_bill | idx_bill_customer | customer_id + 年月 |
+| 操作审计 | sys_oper_log | — | oper_time + business_type |
+
+### 6.3 分区建议
+
+| 表名 | 分区策略 | 说明 |
+|------|----------|------|
+| sys_oper_log | RANGE 按 oper_time 月分区 | 操作日志归档 |
+| vpp_report_log | RANGE 按 reported_at 月分区 | 上报日志定期清理 |
+| base_alarm | 可选按 alarm_time 年分区 | 历史告警查询 |
+
+### 6.4 usky-cloud 复用表索引(摘录)
+
+索引定义以 `usky-cloud.sql` 为准,开发时重点关注:
+
+| 表 | 索引 | 用途 |
+|----|------|------|
+| base_alarm | — | 按 tenant_id、alarm_time 查询 |
+| sys_user | user_name | 登录名 |
+| sys_user_role | role_id + user_id | 用户角色 |
+| sys_role_menu | role_id + menu_id | 角色菜单 |
+| dmp_device | device_uuid | 设备唯一标识 |
+| rule_engine_device | uk_device_id_and_identifier_and_rule_engine_id | 设备规则绑定 |
+
+---
+
+## 7. 时序库设计
+
+设备 15 分钟级运行数据存储于 **TDengine**,不进入 MySQL 业务库。
+
+### 7.1 超级表定义
+
+```sql
+CREATE STABLE IF NOT EXISTS device_metrics (
+    ts              TIMESTAMP,
+    active_power    FLOAT       COMMENT '有功功率 kW',
+    voltage         FLOAT       COMMENT '电压 V',
+    current         FLOAT       COMMENT '电流 A',
+    energy_kwh      FLOAT       COMMENT '用电量 kWh 累加',
+    gen_energy_kwh  FLOAT       COMMENT '发电量 kWh 累加',
+    soc             FLOAT       COMMENT '储能 SOC %',
+    up_capacity_kw  FLOAT       COMMENT '实时上调能力 kW',
+    down_capacity_kw FLOAT      COMMENT '实时下调能力 kW',
+    status          TINYINT     COMMENT '设备状态'
+) TAGS (
+    device_id       NCHAR(32),
+    resource_id     NCHAR(32),
+    customer_id     NCHAR(32),
+    resource_type   NCHAR(16)
+);
+```
+
+### 7.2 子表命名
+
+```
+d_{device_id}
+```
+
+### 7.3 数据保留策略
+
+| 粒度 | 保留时长 | 说明 |
+|------|----------|------|
+| 15 分钟原始 | 2 年 | TDengine 热数据 |
+| 日汇总 | 永久 | 写入 vpp_energy_summary_daily |
+
+### 7.4 与 MySQL 关联
+
+- `device_metrics.device_id` TAG 对应 `vpp_device.device_code`
+- `device_metrics.resource_id` TAG 对应 `vpp_resource_point.resource_code`
+- 运管平台上报从 TDengine 聚合后生成 MomentDataReport / IntervalDataReport
+
+---
+
+## 8. 初始化与枚举数据
+
+### 8.1 系统角色与菜单(沿用 `sys_*`)
+
+角色、菜单、权限数据在 **usky-cloud 平台初始化**时维护。虚拟电厂上线时需:
+
+1. 在 `sys_menu` 中注册 VPP 模块菜单(`menu_mold` 设为业务功能)。
+2. 在 `sys_role` 中创建 VPP 业务角色(如运营人员、客户经理、报送专员等)。
+3. 通过 `sys_role_menu` 为角色分配菜单;按钮权限由 `sys_menu.perms` 控制。
+4. 通过 `sys_user_role` 为用户绑定角色。
+
+**不在 VPP 库单独 INSERT 用户主数据。**
+
+### 8.2 系统配置初始数据(`sys_config`)
+
+VPP 项目配置写入 `sys_config`(按 `tenant_id` 隔离),示例:
+
+```sql
+INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_time, remark, tenant_id) VALUES
+('默认采集频率', 'vpp.collect.interval.default', '900', 'N', NOW(), '秒', {tenant_id}),
+('时序保留年数', 'vpp.data.retention.years', '2', 'N', NOW(), '年', {tenant_id}),
+('告警升级间隔', 'vpp.alarm.escalate.minutes', '30', 'N', NOW(), '分钟', {tenant_id}),
+('报送最大重试', 'vpp.report.retry.max', '3', 'N', NOW(), '次', {tenant_id}),
+('需求响应提前通知', 'vpp.dr.notify.minutes.default', '30', 'N', NOW(), '分钟', {tenant_id});
+```
+
+### 8.3 枚举字典
+
+#### customer_type 客户类型
+
+| 值 | 含义 |
+|----|------|
+| 1 | 高压用户 |
+| 2 | 低压商用用户 |
+| 3 | 低压居民充电桩 |
+| 4 | 自有资产用户 |
+
+#### resource_type 资源类型
+
+| 值 | 含义 |
+|----|------|
+| PV | 光伏 |
+| ESS | 储能 |
+| EVCS | 充电桩 |
+| IND_LOAD | 工业负荷 |
+| COM_BLDG | 商业楼宇 |
+
+#### lifecycle_status 客户生命周期
+
+| 值 | 含义 |
+|----|------|
+| 1 | 待准入 |
+| 2 | 已准入 |
+| 3 | 已签约 |
+| 4 | 履约中 |
+| 5 | 待续约 |
+| 6 | 已续约 |
+| 7 | 已解约 |
+
+---
+
+## 9. 附录
+
+### 9.1 与详细设计文档表清单对照
+
+| 详细设计文档表名 | 本文档表名 | 说明 |
+|------------------|------------|------|
+| t_customer | vpp_customer | 前缀调整 |
+| t_customer_access | vpp_customer_access | 前缀调整 |
+| t_contract | vpp_contract | 前缀调整 |
+| t_contract_template | vpp_contract_template | 前缀调整 |
+| t_resource_point | vpp_resource_point | 前缀调整 |
+| t_device | vpp_device | 前缀调整 |
+| t_alarm_rule | rule_engine 等 | 沿用 usky-cloud `rule_*` |
+| t_alarm_record | base_alarm | 沿用 usky-cloud `base_*` |
+| t_dr_event | vpp_dr_event | 前缀调整 |
+| t_dr_strategy | vpp_dr_strategy | 前缀调整 |
+| t_dr_execution | vpp_dr_execution | 前缀调整 |
+| t_settlement_bill | vpp_settlement_bill | 前缀调整 |
+| t_report_task | vpp_report_task | 前缀调整 |
+| t_report_record | vpp_report_record | 前缀调整 |
+| t_sys_user | sys_user | 沿用 usky-cloud `sys_*` |
+| t_sys_role | sys_role | 沿用 usky-cloud `sys_*` |
+| t_sys_operation_log | sys_oper_log | 沿用 usky-cloud `sys_*` |
+| t_file_archive | vpp_file_archive | VPP 专属(平台无 base/sys/dmp/rule 附件表) |
+| — | `vpp_*` 表 | VPP 专属业务表(29 张) |
+| — | usky-cloud 平台表 | `base_*`/`sys_*`/`dmp_*`/`rule_*`,见第 5A 章 |
+
+### 9.2 建库脚本执行顺序
+
+**第一步:usky-cloud 平台库**(已存在则跳过)
+
+执行 `usky-cloud.sql` 中 `base_*`、`sys_*`、`dmp_*`、`rule_*` 平台公共表(不含 `ems_*`、`sas_*` 等业务系统表)。
+
+**第二步:VPP 专属表**(`schema/vpp_init.sql`)
+
+1. 基础档案:`vpp_customer` → `vpp_customer_access` → `vpp_customer_contact`
+2. 合同:`vpp_contract_template` → `vpp_contract` → `vpp_contract_audit_log` → `vpp_file_archive`
+3. 资源:`vpp_site` → `vpp_site_config` → `vpp_device` → `vpp_resource_point`
+4. 存量升级(按需):`vpp_device_site_id_migration.sql`(`resource_id` → `site_id`)
+5. 运行结算:`vpp_energy_*` → `vpp_settlement_*`
+6. 需求响应:`vpp_dr_*`
+7. 报送:`vpp_report_*`
+8. 运管对接:`vpp_registration` → `vpp_report_log`
+
+**第三步:VPP 业务配置**
+
+1. `sys_menu` 注册 VPP 菜单
+2. `sys_role` / `sys_user_role` 配置角色
+3. `sys_config` 写入 VPP 项目参数
+4. `base_alarm_type` / `sys_dict_*` 补充 VPP 字典项(按需)
+5. `dmp_device` 与 `vpp_device` 建立 `device_code` / `device_uuid` 映射(按需)
+6. `rule_engine` / `rule_engine_device` 配置 VPP 告警规则(按需)
+
+### 9.3 完整建库 SQL 文件
+
+可将本文档第 5 章各表 DDL 按 9.2 顺序合并为 `schema/t_init.sql` 一次性执行。
+
+---
+
+**文档结束**