From 212b69060c40952e37b667e1b8466c5f898f0d3a Mon Sep 17 00:00:00 2001
From: yexb <553699424@qq.com>
Date: Fri, 12 Jun 2026 08:41:11 +0800
Subject: [PATCH] =?UTF-8?q?refactor(steady):=20=E9=87=8D=E6=9E=84=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E6=A0=A1=E9=AA=8C=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=96=B0?=
=?UTF-8?q?=E5=A2=9EPQDIF=E8=A7=A3=E6=9E=90=E9=A2=84=E7=95=99=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将数据校验中的缺失率相关字段替换为数据完整性字段
- 新增数据校验任务删除功能及相应测试
- 在tools模块中添加parse-pqdif子模块作为PQDIF文件解析预留
- 更新README文档以反映新的模块结构和依赖关系
- 优化数据校验统计汇总逻辑和测试覆盖
- 在entrance模块中集成parse-pqdif依赖
- 重构数据校验服务层实现和数据对象映射
---
README.md | 5 +-
entrance/pom.xml | 5 +
.../SteadyChecksquareCalculator.java | 13 -
.../SteadyChecksquareController.java | 12 +
.../pojo/po/SteadyChecksquareItemPO.java | 10 +-
.../po/SteadyChecksquareStatSummaryPO.java | 10 +-
.../pojo/po/SteadyChecksquareTaskPO.java | 4 +-
.../pojo/vo/SteadyChecksquareItemVO.java | 11 +-
.../vo/SteadyChecksquareStatSummaryVO.java | 11 +-
.../pojo/vo/SteadyChecksquareTaskVO.java | 4 +-
.../service/SteadyChecksquareService.java | 4 +
.../impl/SteadyChecksquareServiceImpl.java | 148 ++---
.../steady-checksquare-result-init.sql | 102 ++++
...dy-checksquare-result-upgrade_20260611.sql | 14 +
.../SteadyChecksquareControllerTest.java | 5 +
.../SteadyChecksquareServiceImplTest.java | 172 +++++-
.../steady-checksquare-api-debug_20260610.md | 522 ++++++++++++++++++
tools/README.md | 16 +-
tools/mms-mapping/pom.xml | 6 +
.../controller/MmsDeviceTypeController.java | 72 +++
.../icd/mapping/mapper/CsDevTypeMapper.java | 15 +
.../icd/mapping/mapper/CsIcdPathMapper.java | 10 +
.../mapper/mapping/CsDevTypeMapper.xml | 25 +
.../pojo/param/IcdCheckResultSaveParam.java | 25 +
.../icd/mapping/pojo/po/CsDevTypePO.java | 61 ++
.../icd/mapping/pojo/po/CsIcdPathPO.java | 67 +++
.../icd/mapping/pojo/vo/MmsDeviceTypeVO.java | 43 ++
.../pojo/vo/PqdifCheckPlaceholderVO.java | 19 +
.../mapping/service/MmsDeviceTypeService.java | 19 +
.../impl/MmsDeviceTypeServiceImpl.java | 141 +++++
tools/parse-pqdif/pom.xml | 55 ++
.../controller/ParsePqdifController.java | 46 ++
.../pojo/vo/PqdifParseResponse.java | 22 +
.../parsepqdif/service/ParsePqdifService.java | 12 +
.../service/impl/ParsePqdifServiceImpl.java | 24 +
tools/parse-pqdif/src/main/resources/.gitkeep | 1 +
tools/pom.xml | 1 +
37 files changed, 1606 insertions(+), 126 deletions(-)
create mode 100644 steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql
create mode 100644 steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql
create mode 100644 steady/steady-DataView/steady-checksquare-api-debug_20260610.md
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java
create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java
create mode 100644 tools/parse-pqdif/pom.xml
create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java
create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java
create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java
create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java
create mode 100644 tools/parse-pqdif/src/main/resources/.gitkeep
diff --git a/README.md b/README.md
index 7203266..3fedf8d 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
- `add-data`
- `add-ledger`
- `mms-mapping`
+- `parse-pqdif`
- `wave-tool`
## 启动入口
@@ -43,7 +44,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
- `entrance/src/main/java/com/njcn/gather/EntranceApplication.java`
-`entrance` 模块聚合了 `system`、`disk-monitor`、`dbms`、`deploy`、`user`、`detection`、`activate-tool`、`add-data`、`add-ledger`、`wave-tool`、`mms-mapping`,是当前运行时主入口。
+`entrance` 模块聚合了 `system`、`disk-monitor`、`dbms`、`deploy`、`user`、`detection`、`activate-tool`、`add-data`、`add-ledger`、`wave-tool`、`mms-mapping`、`parse-pqdif`,是当前运行时主入口。
## 技术基线
@@ -100,6 +101,8 @@ P0 已补齐基线文档,建议按以下顺序阅读:
- 当前为数据台账工具预留空模块
- `tools/mms-mapping`
- 负责 ICD 文件解析与 MMS 映射数据生成能力
+- `tools/parse-pqdif`
+ - 当前为 PQDIF 文件解析能力预留空方法骨架
- `tools/wave-tool`
- 负责波形文本解析与查看数据组装能力
diff --git a/entrance/pom.xml b/entrance/pom.xml
index 94f3867..c11af63 100644
--- a/entrance/pom.xml
+++ b/entrance/pom.xml
@@ -58,6 +58,11 @@
mms-mapping
1.0.0
+
+ com.njcn.gather
+ parse-pqdif
+ 1.0.0
+
com.njcn.gather
add-data
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java
index 36e5aed..64cc441 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java
@@ -45,19 +45,6 @@ public class SteadyChecksquareCalculator {
return result;
}
- public int maxContinuousMissingMinutes(List segments) {
- int result = 0;
- if (segments == null) {
- return result;
- }
- for (SteadyChecksquareSegmentVO segment : segments) {
- if (segment != null && STATUS_MISSING.equals(segment.getStatus()) && segment.getDurationMinutes() != null) {
- result = Math.max(result, segment.getDurationMinutes());
- }
- }
- return result;
- }
-
private SteadyChecksquareSegmentVO buildSegment(LocalDateTime startTime, LocalDateTime endTime, String status,
int pointCount, int intervalMinutes) {
SteadyChecksquareSegmentVO segment = new SteadyChecksquareSegmentVO();
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java
index c0bfa9a..d3f0c71 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java
@@ -28,6 +28,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+
/**
* 数据校验接口。
*/
@@ -60,6 +62,16 @@ public class SteadyChecksquareController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
+ @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
+ @ApiOperation("删除数据校验任务")
+ @PostMapping("/delete")
+ public HttpResult delete(@RequestBody List taskIds) {
+ String methodDescribe = getMethodDescribe("delete");
+ LogUtil.njcnDebug(log, "{},开始删除数据校验任务,taskIds={}", methodDescribe, taskIds);
+ boolean result = checksquareService.delete(taskIds);
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
+ }
+
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询数据校验任务详情")
@GetMapping("/detail")
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java
index 6a56ea7..5612589 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java
@@ -40,12 +40,10 @@ public class SteadyChecksquareItemPO implements Serializable {
private Integer actualPointCount;
@TableField("missing_point_count")
private Integer missingPointCount;
- @TableField("missing_rate")
- private BigDecimal missingRate;
- @TableField("missing_rate_text")
- private String missingRateText;
- @TableField("max_continuous_missing_minutes")
- private Integer maxContinuousMissingMinutes;
+ @TableField("data_integrity")
+ private BigDecimal dataIntegrity;
+ @TableField("data_integrity_text")
+ private String dataIntegrityText;
@TableField("abnormal")
private Integer abnormal;
@TableField("abnormal_point_count")
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java
index 5f4697c..685ab20 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java
@@ -34,12 +34,10 @@ public class SteadyChecksquareStatSummaryPO implements Serializable {
private Integer actualPointCount;
@TableField("missing_point_count")
private Integer missingPointCount;
- @TableField("missing_rate")
- private BigDecimal missingRate;
- @TableField("missing_rate_text")
- private String missingRateText;
- @TableField("max_continuous_missing_minutes")
- private Integer maxContinuousMissingMinutes;
+ @TableField("data_integrity")
+ private BigDecimal dataIntegrity;
+ @TableField("data_integrity_text")
+ private String dataIntegrityText;
@TableField("create_time")
private LocalDateTime createTime;
}
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java
index bcc75b2..06c25f9 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java
@@ -42,8 +42,8 @@ public class SteadyChecksquareTaskPO implements Serializable {
private Integer itemCount;
@TableField("abnormal_item_count")
private Integer abnormalItemCount;
- @TableField("max_missing_rate")
- private BigDecimal maxMissingRate;
+ @TableField("min_data_integrity")
+ private BigDecimal minDataIntegrity;
@TableField("result_message")
private String resultMessage;
@TableField("state")
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java
index 400e37a..c1c11c4 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java
@@ -48,14 +48,11 @@ public class SteadyChecksquareItemVO implements Serializable {
@ApiModelProperty("缺失点数")
private Integer missingPointCount;
- @ApiModelProperty("缺失率")
- private BigDecimal missingRate;
+ @ApiModelProperty("数据完整性")
+ private BigDecimal dataIntegrity;
- @ApiModelProperty("缺失率文本")
- private String missingRateText;
-
- @ApiModelProperty("最大连续缺失时长,单位分钟")
- private Integer maxContinuousMissingMinutes;
+ @ApiModelProperty("数据完整性文本")
+ private String dataIntegrityText;
@ApiModelProperty("指标值大小关系是否异常")
private Boolean abnormal;
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java
index 0cb2b36..8f85735 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java
@@ -34,12 +34,9 @@ public class SteadyChecksquareStatSummaryVO implements Serializable {
@ApiModelProperty("缺失点数")
private Integer missingPointCount;
- @ApiModelProperty("缺失率")
- private BigDecimal missingRate;
+ @ApiModelProperty("数据完整性")
+ private BigDecimal dataIntegrity;
- @ApiModelProperty("缺失率文本")
- private String missingRateText;
-
- @ApiModelProperty("最大连续缺失时长,单位分钟")
- private Integer maxContinuousMissingMinutes;
+ @ApiModelProperty("数据完整性文本")
+ private String dataIntegrityText;
}
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java
index c8904ec..571e979 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java
@@ -46,8 +46,8 @@ public class SteadyChecksquareTaskVO implements Serializable {
@ApiModelProperty("异常检测项数量")
private Integer abnormalItemCount;
- @ApiModelProperty("最大缺失率")
- private BigDecimal maxMissingRate;
+ @ApiModelProperty("最低数据完整性")
+ private BigDecimal minDataIntegrity;
@ApiModelProperty("创建时间")
private String createTime;
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java
index f5a34d6..2e7ae00 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java
@@ -8,6 +8,8 @@ import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
+import java.util.List;
+
/**
* 数据校验服务。
*/
@@ -17,6 +19,8 @@ public interface SteadyChecksquareService {
SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param);
+ boolean delete(List taskIds);
+
SteadyChecksquareQueryVO detail(String taskId);
SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType);
diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java
index c62f42c..b694172 100644
--- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java
+++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java
@@ -1,6 +1,7 @@
package com.njcn.gather.steady.checksquare.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
@@ -127,6 +128,25 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return toCreateVO(task);
}
+ @Override
+ public boolean delete(List taskIds) {
+ List ids = normalizeTextList(taskIds);
+ if (ids.isEmpty()) {
+ throw fail("数据校验任务 ID 不能为空");
+ }
+ List tasks = taskService.lambdaQuery()
+ .in(SteadyChecksquareTaskPO::getId, ids)
+ .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED)
+ .list();
+ if (tasks == null || tasks.size() != ids.size()) {
+ throw fail("数据校验任务不存在或已删除");
+ }
+ if (transactionTemplate != null) {
+ return Boolean.TRUE.equals(transactionTemplate.execute(status -> deleteTasksAndItems(ids)));
+ }
+ return deleteTasksAndItems(ids);
+ }
+
@Override
public SteadyChecksquareQueryVO detail(String taskId) {
SteadyChecksquareTaskPO task = requireTask(taskId);
@@ -198,6 +218,21 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return result;
}
+ private boolean deleteTasksAndItems(List taskIds) {
+ LambdaUpdateWrapper taskWrapper = new LambdaUpdateWrapper()
+ .set(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_DELETED)
+ .in(SteadyChecksquareTaskPO::getId, taskIds)
+ .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
+ boolean taskResult = taskService.update(taskWrapper);
+ // 检测项同步置为删除态,避免已删任务下的 item-detail 被继续访问。
+ LambdaUpdateWrapper itemWrapper = new LambdaUpdateWrapper()
+ .set(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_DELETED)
+ .in(SteadyChecksquareItemPO::getTaskId, taskIds)
+ .eq(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_ENABLED);
+ itemService.update(itemWrapper);
+ return taskResult;
+ }
+
private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) {
validateParam(param);
String lineId = trimToNull(param.getLineId());
@@ -256,7 +291,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_SUCCESS);
task.setItemCount(result.getItems().size());
task.setAbnormalItemCount(countAbnormalItems(result.getItems()));
- task.setMaxMissingRate(maxMissingRate(result.getItems()));
+ task.setMinDataIntegrity(minDataIntegrity(result.getItems()));
task.setResultMessage("数据校验完成");
task.setState(SteadyChecksquareConst.STATE_ENABLED);
task.setCreateTime(now);
@@ -298,9 +333,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
po.setExpectedPointCount(nullToZero(item.getExpectedPointCount()));
po.setActualPointCount(nullToZero(item.getActualPointCount()));
po.setMissingPointCount(nullToZero(item.getMissingPointCount()));
- po.setMissingRate(item.getMissingRate() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : item.getMissingRate());
- po.setMissingRateText(item.getMissingRateText());
- po.setMaxContinuousMissingMinutes(nullToZero(item.getMaxContinuousMissingMinutes()));
+ po.setDataIntegrity(item.getDataIntegrity() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : item.getDataIntegrity());
+ po.setDataIntegrityText(item.getDataIntegrityText());
po.setAbnormal(toFlag(item.getAbnormal()));
po.setAbnormalPointCount(nullToZero(item.getAbnormalPointCount()));
po.setHarmonicParityAbnormal(toFlag(item.getHarmonicParityAbnormal()));
@@ -323,9 +357,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
po.setExpectedPointCount(nullToZero(summary.getExpectedPointCount()));
po.setActualPointCount(nullToZero(summary.getActualPointCount()));
po.setMissingPointCount(nullToZero(summary.getMissingPointCount()));
- po.setMissingRate(summary.getMissingRate() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : summary.getMissingRate());
- po.setMissingRateText(summary.getMissingRateText());
- po.setMaxContinuousMissingMinutes(nullToZero(summary.getMaxContinuousMissingMinutes()));
+ po.setDataIntegrity(summary.getDataIntegrity() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : summary.getDataIntegrity());
+ po.setDataIntegrityText(summary.getDataIntegrityText());
po.setCreateTime(now);
result.add(po);
}
@@ -463,13 +496,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
result.setIndicatorName(indicator.getName());
result.setHarmonicOrder(null);
result.setIntervalMinutes(intervalMinutes);
- result.setHasData(anyHasData(orderItems));
result.setExpectedPointCount(averageInteger(orderItems, "expectedPointCount"));
result.setActualPointCount(averageInteger(orderItems, "actualPointCount"));
result.setMissingPointCount(averageInteger(orderItems, "missingPointCount"));
- result.setMissingRate(averageRate(orderItems));
- result.setMissingRateText(formatRateText(result.getMissingRate()));
- result.setMaxContinuousMissingMinutes(averageInteger(orderItems, "maxContinuousMissingMinutes"));
+ result.setDataIntegrity(averageDataIntegrity(orderItems));
+ result.setHasData(hasDataByIntegrity(result.getDataIntegrity()));
+ result.setDataIntegrityText(formatRateText(result.getDataIntegrity()));
result.setAbnormal(anyAbnormal(orderItems));
result.setAbnormalPointCount(averageAbnormalCount(orderItems, "abnormalPointCount", result.getAbnormal()));
result.setHarmonicParityAbnormal(anyHarmonicParityAbnormal(orderItems));
@@ -501,13 +533,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
summary.setStatType(entry.getKey());
summary.setSupported(true);
- summary.setHasData(anySummaryHasData(entry.getValue()));
summary.setExpectedPointCount(averageSummaryInteger(entry.getValue(), "expectedPointCount"));
summary.setActualPointCount(averageSummaryInteger(entry.getValue(), "actualPointCount"));
summary.setMissingPointCount(averageSummaryInteger(entry.getValue(), "missingPointCount"));
- summary.setMissingRate(averageSummaryRate(entry.getValue()));
- summary.setMissingRateText(formatRateText(summary.getMissingRate()));
- summary.setMaxContinuousMissingMinutes(averageSummaryInteger(entry.getValue(), "maxContinuousMissingMinutes"));
+ summary.setDataIntegrity(averageSummaryDataIntegrity(entry.getValue()));
+ summary.setHasData(hasDataByIntegrity(summary.getDataIntegrity()));
+ summary.setDataIntegrityText(formatRateText(summary.getDataIntegrity()));
result.add(summary);
}
return result;
@@ -549,8 +580,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
int totalExpected = 0;
int totalActual = 0;
- int maxContinuousMissingMinutes = 0;
- boolean hasData = false;
for (String statType : indicator.getSupportStats()) {
Set actualSlots = queryMergedActualSlots(lineId, indicator, harmonicOrder, statType, startTime, endTime, intervalMinutes);
Set effectiveActualSlots = retainExpectedSlots(slots, actualSlots);
@@ -561,17 +590,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
item.getStatDetails().add(detail);
totalExpected += summary.getExpectedPointCount();
totalActual += summary.getActualPointCount();
- maxContinuousMissingMinutes = Math.max(maxContinuousMissingMinutes, summary.getMaxContinuousMissingMinutes());
- hasData = hasData || Boolean.TRUE.equals(summary.getHasData());
}
- item.setHasData(hasData);
item.setExpectedPointCount(totalExpected);
item.setActualPointCount(totalActual);
item.setMissingPointCount(Math.max(0, totalExpected - totalActual));
- item.setMissingRate(calculateRate(item.getMissingPointCount(), totalExpected));
- item.setMissingRateText(formatRateText(item.getMissingRate()));
- item.setMaxContinuousMissingMinutes(maxContinuousMissingMinutes);
+ item.setDataIntegrity(calculateDataIntegrity(totalActual, totalExpected));
+ item.setHasData(hasDataByIntegrity(item.getDataIntegrity()));
+ item.setDataIntegrityText(formatRateText(item.getDataIntegrity()));
fillValueOrderRuleResult(item, lineId, indicator, harmonicOrder, startTime, endTime, intervalMinutes);
return item;
}
@@ -676,13 +702,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
summary.setStatType(statType);
summary.setSupported(true);
- summary.setHasData(actualCount > 0);
summary.setExpectedPointCount(expectedCount);
summary.setActualPointCount(actualCount);
summary.setMissingPointCount(Math.max(0, expectedCount - actualCount));
- summary.setMissingRate(calculateRate(summary.getMissingPointCount(), expectedCount));
- summary.setMissingRateText(formatRateText(summary.getMissingRate()));
- summary.setMaxContinuousMissingMinutes(calculator.maxContinuousMissingMinutes(segments));
+ summary.setDataIntegrity(calculateDataIntegrity(actualCount, expectedCount));
+ summary.setHasData(hasDataByIntegrity(summary.getDataIntegrity()));
+ summary.setDataIntegrityText(formatRateText(summary.getDataIntegrity()));
return summary;
}
@@ -884,7 +909,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
vo.setTaskStatus(task.getTaskStatus());
vo.setItemCount(task.getItemCount());
vo.setAbnormalItemCount(task.getAbnormalItemCount());
- vo.setMaxMissingRate(task.getMaxMissingRate());
+ vo.setMinDataIntegrity(task.getMinDataIntegrity());
vo.setCreateTime(formatTime(task.getCreateTime()));
return vo;
}
@@ -915,9 +940,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
vo.setExpectedPointCount(item.getExpectedPointCount());
vo.setActualPointCount(item.getActualPointCount());
vo.setMissingPointCount(item.getMissingPointCount());
- vo.setMissingRate(item.getMissingRate());
- vo.setMissingRateText(item.getMissingRateText());
- vo.setMaxContinuousMissingMinutes(item.getMaxContinuousMissingMinutes());
+ vo.setDataIntegrity(item.getDataIntegrity());
+ vo.setDataIntegrityText(item.getDataIntegrityText());
vo.setAbnormal(toBoolean(item.getAbnormal()));
vo.setAbnormalPointCount(item.getAbnormalPointCount());
vo.setHarmonicParityAbnormal(toBoolean(item.getHarmonicParityAbnormal()));
@@ -933,39 +957,34 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
vo.setExpectedPointCount(summary.getExpectedPointCount());
vo.setActualPointCount(summary.getActualPointCount());
vo.setMissingPointCount(summary.getMissingPointCount());
- vo.setMissingRate(summary.getMissingRate());
- vo.setMissingRateText(summary.getMissingRateText());
- vo.setMaxContinuousMissingMinutes(summary.getMaxContinuousMissingMinutes());
+ vo.setDataIntegrity(summary.getDataIntegrity());
+ vo.setDataIntegrityText(summary.getDataIntegrityText());
return vo;
}
private int countAbnormalItems(List items) {
int count = 0;
for (SteadyChecksquareItemVO item : items) {
- if (Boolean.TRUE.equals(item.getAbnormal()) || Boolean.TRUE.equals(item.getHarmonicParityAbnormal())) {
+ if (!Boolean.TRUE.equals(item.getHasData())
+ || Boolean.TRUE.equals(item.getAbnormal())
+ || Boolean.TRUE.equals(item.getHarmonicParityAbnormal())) {
count++;
}
}
return count;
}
- private BigDecimal maxMissingRate(List items) {
- BigDecimal max = BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
+ private BigDecimal minDataIntegrity(List items) {
+ BigDecimal min = BigDecimal.ONE.setScale(6, RoundingMode.HALF_UP);
+ if (items == null || items.isEmpty()) {
+ return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
+ }
for (SteadyChecksquareItemVO item : items) {
- if (item.getMissingRate() != null && item.getMissingRate().compareTo(max) > 0) {
- max = item.getMissingRate();
+ if (item.getDataIntegrity() != null && item.getDataIntegrity().compareTo(min) < 0) {
+ min = item.getDataIntegrity();
}
}
- return max;
- }
-
- private Boolean anyHasData(List items) {
- for (SteadyChecksquareItemVO item : items) {
- if (Boolean.TRUE.equals(item.getHasData())) {
- return true;
- }
- }
- return false;
+ return min;
}
private Boolean anyAbnormal(List items) {
@@ -986,15 +1005,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return false;
}
- private Boolean anySummaryHasData(List summaries) {
- for (SteadyChecksquareStatSummaryVO summary : summaries) {
- if (Boolean.TRUE.equals(summary.getHasData())) {
- return true;
- }
- }
- return false;
- }
-
private Integer averageInteger(List items, String fieldName) {
if (items == null || items.isEmpty()) {
return 0;
@@ -1025,24 +1035,24 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return new BigDecimal(total).divide(new BigDecimal(summaries.size()), 0, RoundingMode.HALF_UP).intValue();
}
- private BigDecimal averageRate(List items) {
+ private BigDecimal averageDataIntegrity(List items) {
if (items == null || items.isEmpty()) {
return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
}
BigDecimal total = BigDecimal.ZERO;
for (SteadyChecksquareItemVO item : items) {
- total = total.add(item.getMissingRate() == null ? BigDecimal.ZERO : item.getMissingRate());
+ total = total.add(item.getDataIntegrity() == null ? BigDecimal.ZERO : item.getDataIntegrity());
}
return total.divide(new BigDecimal(items.size()), 6, RoundingMode.HALF_UP);
}
- private BigDecimal averageSummaryRate(List summaries) {
+ private BigDecimal averageSummaryDataIntegrity(List summaries) {
if (summaries == null || summaries.isEmpty()) {
return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
}
BigDecimal total = BigDecimal.ZERO;
for (SteadyChecksquareStatSummaryVO summary : summaries) {
- total = total.add(summary.getMissingRate() == null ? BigDecimal.ZERO : summary.getMissingRate());
+ total = total.add(summary.getDataIntegrity() == null ? BigDecimal.ZERO : summary.getDataIntegrity());
}
return total.divide(new BigDecimal(summaries.size()), 6, RoundingMode.HALF_UP);
}
@@ -1057,9 +1067,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
if ("missingPointCount".equals(fieldName)) {
return nullToZero(item.getMissingPointCount());
}
- if ("maxContinuousMissingMinutes".equals(fieldName)) {
- return nullToZero(item.getMaxContinuousMissingMinutes());
- }
if ("abnormalPointCount".equals(fieldName)) {
return nullToZero(item.getAbnormalPointCount());
}
@@ -1079,9 +1086,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
if ("missingPointCount".equals(fieldName)) {
return nullToZero(summary.getMissingPointCount());
}
- if ("maxContinuousMissingMinutes".equals(fieldName)) {
- return nullToZero(summary.getMaxContinuousMissingMinutes());
- }
return 0;
}
@@ -1089,11 +1093,15 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return value == null ? 0 : value;
}
- private BigDecimal calculateRate(int missingCount, int expectedCount) {
+ private BigDecimal calculateDataIntegrity(int actualCount, int expectedCount) {
if (expectedCount <= 0) {
return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
}
- return new BigDecimal(missingCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP);
+ return new BigDecimal(actualCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP);
+ }
+
+ private boolean hasDataByIntegrity(BigDecimal dataIntegrity) {
+ return dataIntegrity != null && dataIntegrity.compareTo(BigDecimal.ZERO) > 0;
}
private String formatRateText(BigDecimal rate) {
diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql
new file mode 100644
index 0000000..0aa1a77
--- /dev/null
+++ b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql
@@ -0,0 +1,102 @@
+CREATE TABLE IF NOT EXISTS `steady_checksquare_task` (
+ `id` VARCHAR(64) NOT NULL COMMENT '主键',
+ `task_no` VARCHAR(64) NOT NULL COMMENT '检测任务编号',
+ `line_id` VARCHAR(64) NOT NULL COMMENT '监测点ID',
+ `line_name` VARCHAR(255) NULL COMMENT '监测点名称',
+ `time_start` DATETIME NOT NULL COMMENT '检测开始时间',
+ `time_end` DATETIME NOT NULL COMMENT '检测结束时间',
+ `interval_minutes` INT NULL COMMENT '默认统计间隔,单位分钟',
+ `indicator_codes_json` JSON NULL COMMENT '请求指标编码列表',
+ `indicator_codes_text` VARCHAR(2000) NULL COMMENT '请求指标编码检索文本,格式 |code1|code2|',
+ `task_status` VARCHAR(32) NOT NULL DEFAULT 'SUCCESS' COMMENT '任务状态:SUCCESS/FAIL',
+ `item_count` INT NOT NULL DEFAULT 0 COMMENT '检测项数量',
+ `abnormal_item_count` INT NOT NULL DEFAULT 0 COMMENT '异常检测项数量',
+ `min_data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '最低数据完整性',
+ `result_message` VARCHAR(2000) NULL COMMENT '执行结果说明',
+ `state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
+ `create_by` VARCHAR(64) NULL COMMENT '创建人',
+ `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_by` VARCHAR(64) NULL COMMENT '更新人',
+ `update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_steady_checksquare_task_no` (`task_no`),
+ KEY `idx_steady_checksquare_task_line_time` (`line_id`, `time_start`, `time_end`),
+ KEY `idx_steady_checksquare_task_status` (`task_status`),
+ KEY `idx_steady_checksquare_task_indicator_text` (`indicator_codes_text`(255)),
+ KEY `idx_steady_checksquare_task_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验任务表';
+
+CREATE TABLE IF NOT EXISTS `steady_checksquare_item` (
+ `id` VARCHAR(64) NOT NULL COMMENT '主键',
+ `task_id` VARCHAR(64) NOT NULL COMMENT '检测任务ID',
+ `item_key` VARCHAR(255) NOT NULL COMMENT '检测项唯一键',
+ `indicator_code` VARCHAR(64) NOT NULL COMMENT '指标编码',
+ `indicator_name` VARCHAR(255) NULL COMMENT '指标名称',
+ `harmonic_order` INT NULL COMMENT '谐波次数;聚合项为空',
+ `interval_minutes` INT NULL COMMENT '当前检测项统计间隔,单位分钟',
+ `has_data` TINYINT NOT NULL DEFAULT 0 COMMENT '是否存在任意数据:0-否,1-是',
+ `expected_point_count` INT NOT NULL DEFAULT 0 COMMENT '期望点数',
+ `actual_point_count` INT NOT NULL DEFAULT 0 COMMENT '实际点数',
+ `missing_point_count` INT NOT NULL DEFAULT 0 COMMENT '缺失点数',
+ `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性',
+ `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本',
+ `abnormal` TINYINT NOT NULL DEFAULT 0 COMMENT '指标值大小关系是否异常',
+ `abnormal_point_count` INT NOT NULL DEFAULT 0 COMMENT '大小关系异常点数',
+ `harmonic_parity_abnormal` TINYINT NOT NULL DEFAULT 0 COMMENT '谐波奇偶关系是否异常',
+ `harmonic_parity_abnormal_point_count` INT NOT NULL DEFAULT 0 COMMENT '谐波奇偶关系异常点数',
+ `state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
+ `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_steady_checksquare_item` (`task_id`, `item_key`),
+ KEY `idx_steady_checksquare_item_indicator` (`indicator_code`),
+ KEY `idx_steady_checksquare_item_abnormal` (`abnormal`, `harmonic_parity_abnormal`),
+ KEY `idx_steady_checksquare_item_data_integrity` (`data_integrity`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验检测项表';
+
+CREATE TABLE IF NOT EXISTS `steady_checksquare_stat_summary` (
+ `id` VARCHAR(64) NOT NULL COMMENT '主键',
+ `item_id` VARCHAR(64) NOT NULL COMMENT '检测项ID',
+ `stat_type` VARCHAR(16) NOT NULL COMMENT '统计类型:AVG/MAX/MIN/CP95',
+ `supported` TINYINT NOT NULL DEFAULT 1 COMMENT '是否支持',
+ `has_data` TINYINT NOT NULL DEFAULT 0 COMMENT '是否存在数据',
+ `expected_point_count` INT NOT NULL DEFAULT 0 COMMENT '期望点数',
+ `actual_point_count` INT NOT NULL DEFAULT 0 COMMENT '实际点数',
+ `missing_point_count` INT NOT NULL DEFAULT 0 COMMENT '缺失点数',
+ `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性',
+ `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本',
+ `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_steady_checksquare_stat` (`item_id`, `stat_type`),
+ KEY `idx_steady_checksquare_stat_type` (`stat_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验统计摘要表';
+
+CREATE TABLE IF NOT EXISTS `steady_checksquare_detail` (
+ `id` VARCHAR(64) NOT NULL COMMENT '主键',
+ `item_id` VARCHAR(64) NOT NULL COMMENT '检测项ID',
+ `detail_type` VARCHAR(32) NOT NULL COMMENT '明细类型:SEGMENT/VALUE_ORDER/HARMONIC_PARITY',
+ `stat_type` VARCHAR(16) NULL COMMENT '统计类型',
+ `start_time` DATETIME NULL COMMENT '区间开始时间',
+ `end_time` DATETIME NULL COMMENT '区间结束时间',
+ `point_time` DATETIME NULL COMMENT '异常点时间',
+ `segment_status` VARCHAR(16) NULL COMMENT '区间状态:NORMAL/MISSING',
+ `missing_point_count` INT NULL COMMENT '缺失点数',
+ `duration_minutes` INT NULL COMMENT '持续时长,单位分钟',
+ `phase` VARCHAR(16) NULL COMMENT '相别',
+ `harmonic_order` INT NULL COMMENT '谐波次数',
+ `max_value` DECIMAL(24,8) NULL COMMENT '最大值',
+ `min_value` DECIMAL(24,8) NULL COMMENT '最小值',
+ `avg_value` DECIMAL(24,8) NULL COMMENT '平均值',
+ `cp95_value` DECIMAL(24,8) NULL COMMENT 'CP95值',
+ `even_harmonic_order` INT NULL COMMENT '偶次谐波次数',
+ `even_value` DECIMAL(24,8) NULL COMMENT '偶次谐波值',
+ `odd_harmonic_orders_json` JSON NULL COMMENT '参与比较的奇次谐波次数',
+ `odd_values_json` JSON NULL COMMENT '参与比较的奇次谐波值',
+ `odd_median_value` DECIMAL(24,8) NULL COMMENT '奇次谐波中位数',
+ `threshold_multiplier` DECIMAL(12,6) NULL COMMENT '异常阈值倍数',
+ `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_steady_checksquare_detail_item_type` (`item_id`, `detail_type`),
+ KEY `idx_steady_checksquare_detail_point_time` (`point_time`),
+ KEY `idx_steady_checksquare_detail_segment` (`start_time`, `end_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验明细表';
diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql
new file mode 100644
index 0000000..46f5604
--- /dev/null
+++ b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `steady_checksquare_task`
+ CHANGE COLUMN `max_missing_rate` `min_data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '最低数据完整性';
+
+ALTER TABLE `steady_checksquare_item`
+ DROP INDEX `idx_steady_checksquare_item_missing_rate`,
+ CHANGE COLUMN `missing_rate` `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性',
+ CHANGE COLUMN `missing_rate_text` `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本',
+ DROP COLUMN `max_continuous_missing_minutes`,
+ ADD KEY `idx_steady_checksquare_item_data_integrity` (`data_integrity`);
+
+ALTER TABLE `steady_checksquare_stat_summary`
+ CHANGE COLUMN `missing_rate` `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性',
+ CHANGE COLUMN `missing_rate_text` `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本',
+ DROP COLUMN `max_continuous_missing_minutes`;
diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java
index d68542f..37eda9d 100644
--- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java
+++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java
@@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
+import java.util.List;
import java.lang.reflect.Method;
/**
@@ -34,5 +35,9 @@ class SteadyChecksquareControllerTest {
String.class, String.class, String.class, Integer.class, Integer.class);
GetMapping itemDetailMapping = itemDetailMethod.getAnnotation(GetMapping.class);
Assertions.assertArrayEquals(new String[]{"/item-detail"}, itemDetailMapping.value());
+
+ Method deleteMethod = SteadyChecksquareController.class.getDeclaredMethod("delete", List.class);
+ PostMapping deleteMapping = deleteMethod.getAnnotation(PostMapping.class);
+ Assertions.assertArrayEquals(new String[]{"/delete"}, deleteMapping.value());
}
}
diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java
index 421a010..36a6a72 100644
--- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java
+++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java
@@ -25,6 +25,7 @@ import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService;
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService;
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService;
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
+import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator;
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
@@ -437,6 +438,34 @@ class SteadyChecksquareServiceImplTest {
verify(detailService).page(any(Page.class), any());
}
+ @Test
+ void shouldDeleteTasksAndItemsLogically() {
+ SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
+ SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
+ LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class);
+ SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
+ task.setId("task-001");
+ task.setState(1);
+ when(taskService.lambdaQuery()).thenReturn(taskQuery);
+ when(taskQuery.in(any(), any(List.class))).thenReturn(taskQuery);
+ when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
+ when(taskQuery.list()).thenReturn(Collections.singletonList(task));
+ when(taskService.update(any())).thenReturn(true);
+ when(itemService.update(any())).thenReturn(true);
+ SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
+ mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
+ mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
+ new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
+ itemService, mock(SteadyChecksquareStatSummaryService.class),
+ mock(SteadyChecksquareDetailService.class), new ObjectMapper());
+
+ boolean result = service.delete(Collections.singletonList("task-001"));
+
+ Assertions.assertTrue(result);
+ verify(taskService).update(any());
+ verify(itemService).update(any());
+ }
+
@Test
void shouldSaveChecksquareResultsInBatch() {
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
@@ -465,9 +494,8 @@ class SteadyChecksquareServiceImplTest {
item.setExpectedPointCount(2);
item.setActualPointCount(2);
item.setMissingPointCount(0);
- item.setMissingRate(BigDecimal.ZERO.setScale(6));
- item.setMissingRateText("0.00%");
- item.setMaxContinuousMissingMinutes(0);
+ item.setDataIntegrity(BigDecimal.ONE.setScale(6));
+ item.setDataIntegrityText("100.00%");
item.setAbnormal(false);
item.setAbnormalPointCount(0);
item.setHarmonicParityAbnormal(false);
@@ -479,9 +507,8 @@ class SteadyChecksquareServiceImplTest {
summary.setExpectedPointCount(2);
summary.setActualPointCount(2);
summary.setMissingPointCount(0);
- summary.setMissingRate(BigDecimal.ZERO.setScale(6));
- summary.setMissingRateText("0.00%");
- summary.setMaxContinuousMissingMinutes(0);
+ summary.setDataIntegrity(BigDecimal.ONE.setScale(6));
+ summary.setDataIntegrityText("100.00%");
item.getStatSummaries().add(summary);
result.getItems().add(item);
@@ -492,12 +519,87 @@ class SteadyChecksquareServiceImplTest {
verify(statSummaryService).saveBatch(any());
}
+ @Test
+ void shouldCountNoDataItemAsAbnormalWhenSavingTask() {
+ SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
+ SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
+ mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
+ mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
+ new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
+ mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
+ mock(SteadyChecksquareDetailService.class), new ObjectMapper());
+ SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
+ param.setIndicatorCodes(Collections.singletonList("V_RMS"));
+ SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
+ result.setLineId("line-001");
+ result.setLineName("进线一");
+ result.setTimeStart("2026-05-01 00:00:00");
+ result.setTimeEnd("2026-05-01 00:01:00");
+ result.setIntervalMinutes(1);
+ SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
+ item.setItemKey("line-001|V_RMS");
+ item.setIndicatorCode("V_RMS");
+ item.setHasData(false);
+ item.setExpectedPointCount(2);
+ item.setActualPointCount(0);
+ item.setMissingPointCount(2);
+ item.setDataIntegrity(BigDecimal.ZERO.setScale(6));
+ item.setDataIntegrityText("0.00%");
+ item.setAbnormal(false);
+ item.setHarmonicParityAbnormal(false);
+ result.getItems().add(item);
+
+ saveResult(service, param, result);
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(SteadyChecksquareTaskPO.class);
+ verify(taskService).save(captor.capture());
+ Assertions.assertEquals(Integer.valueOf(1), captor.getValue().getAbnormalItemCount());
+ Assertions.assertEquals(BigDecimal.ZERO.setScale(6), captor.getValue().getMinDataIntegrity());
+ }
+
+ @Test
+ void shouldMarkAggregateHarmonicItemNoDataWhenDataIntegrityIsZero() {
+ SteadyChecksquareServiceImpl service = newService();
+ SteadyTrendIndicatorDefinitionBO indicator = buildHarmonicIndicator();
+ SteadyChecksquareItemVO orderItem = buildOrderItem(true, BigDecimal.ZERO.setScale(6));
+ orderItem.getStatSummaries().add(buildSummaryVO(true, BigDecimal.ZERO.setScale(6)));
+
+ SteadyChecksquareItemVO result = aggregateHarmonicItems(service, indicator, Collections.singletonList(orderItem));
+
+ Assertions.assertEquals(BigDecimal.ZERO.setScale(6), result.getDataIntegrity());
+ Assertions.assertEquals(Boolean.FALSE, result.getHasData());
+ Assertions.assertEquals(Boolean.FALSE, result.getStatSummaries().get(0).getHasData());
+ }
+
+ @Test
+ void shouldMarkAggregateHarmonicItemHasDataWhenDataIntegrityIsGreaterThanZero() {
+ SteadyChecksquareServiceImpl service = newService();
+ SteadyTrendIndicatorDefinitionBO indicator = buildHarmonicIndicator();
+ SteadyChecksquareItemVO orderItem = buildOrderItem(false, new BigDecimal("0.500000"));
+ orderItem.getStatSummaries().add(buildSummaryVO(false, new BigDecimal("0.500000")));
+
+ SteadyChecksquareItemVO result = aggregateHarmonicItems(service, indicator, Collections.singletonList(orderItem));
+
+ Assertions.assertEquals(new BigDecimal("0.500000"), result.getDataIntegrity());
+ Assertions.assertEquals(Boolean.TRUE, result.getHasData());
+ Assertions.assertEquals(Boolean.TRUE, result.getStatSummaries().get(0).getHasData());
+ }
+
private void assertItemInterval(SteadyChecksquareItemVO item, String indicatorCode, int intervalMinutes, int expectedPointCount) {
Assertions.assertEquals(indicatorCode, item.getIndicatorCode());
Assertions.assertEquals(Integer.valueOf(intervalMinutes), item.getIntervalMinutes());
Assertions.assertEquals(Integer.valueOf(expectedPointCount), item.getExpectedPointCount());
}
+ private SteadyChecksquareServiceImpl newService() {
+ return new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
+ mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
+ mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
+ new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class),
+ mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
+ mock(SteadyChecksquareDetailService.class), new ObjectMapper());
+ }
+
private SteadyChecksquareQueryVO calculate(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param) {
try {
Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("calculate", SteadyChecksquareQueryParam.class);
@@ -519,6 +621,58 @@ class SteadyChecksquareServiceImplTest {
}
}
+ private SteadyChecksquareItemVO aggregateHarmonicItems(SteadyChecksquareServiceImpl service,
+ SteadyTrendIndicatorDefinitionBO indicator,
+ List orderItems) {
+ try {
+ Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("aggregateHarmonicItems",
+ String.class, SteadyTrendIndicatorDefinitionBO.class, List.class, int.class);
+ method.setAccessible(true);
+ return (SteadyChecksquareItemVO) method.invoke(service, "line-001", indicator, orderItems, 1);
+ } catch (Exception exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ private SteadyTrendIndicatorDefinitionBO buildHarmonicIndicator() {
+ SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorDefinitionBO();
+ indicator.setIndicatorCode("V_HARMONIC");
+ indicator.setName("V_HARMONIC");
+ indicator.setHarmonic(true);
+ return indicator;
+ }
+
+ private SteadyChecksquareItemVO buildOrderItem(boolean hasData, BigDecimal dataIntegrity) {
+ SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
+ item.setItemKey("line-001|V_HARMONIC|2");
+ item.setIndicatorCode("V_HARMONIC");
+ item.setIndicatorName("V_HARMONIC");
+ item.setHarmonicOrder(2);
+ item.setIntervalMinutes(1);
+ item.setHasData(hasData);
+ item.setExpectedPointCount(2);
+ item.setActualPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0);
+ item.setMissingPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2);
+ item.setDataIntegrity(dataIntegrity);
+ item.setAbnormal(false);
+ item.setAbnormalPointCount(0);
+ item.setHarmonicParityAbnormal(false);
+ item.setHarmonicParityAbnormalPointCount(0);
+ return item;
+ }
+
+ private SteadyChecksquareStatSummaryVO buildSummaryVO(boolean hasData, BigDecimal dataIntegrity) {
+ SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
+ summary.setStatType("AVG");
+ summary.setSupported(true);
+ summary.setHasData(hasData);
+ summary.setExpectedPointCount(2);
+ summary.setActualPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0);
+ summary.setMissingPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2);
+ summary.setDataIntegrity(dataIntegrity);
+ return summary;
+ }
+
private SteadyChecksquareItemPO buildItemPO(String itemId, String indicatorCode) {
SteadyChecksquareItemPO item = new SteadyChecksquareItemPO();
item.setId(itemId);
@@ -529,8 +683,7 @@ class SteadyChecksquareServiceImplTest {
item.setExpectedPointCount(1);
item.setActualPointCount(1);
item.setMissingPointCount(0);
- item.setMissingRate(BigDecimal.ZERO.setScale(6));
- item.setMaxContinuousMissingMinutes(0);
+ item.setDataIntegrity(BigDecimal.ONE.setScale(6));
item.setAbnormal(0);
item.setAbnormalPointCount(0);
item.setHarmonicParityAbnormal(0);
@@ -547,8 +700,7 @@ class SteadyChecksquareServiceImplTest {
summary.setExpectedPointCount(1);
summary.setActualPointCount(1);
summary.setMissingPointCount(0);
- summary.setMissingRate(BigDecimal.ZERO.setScale(6));
- summary.setMaxContinuousMissingMinutes(0);
+ summary.setDataIntegrity(BigDecimal.ONE.setScale(6));
return summary;
}
diff --git a/steady/steady-DataView/steady-checksquare-api-debug_20260610.md b/steady/steady-DataView/steady-checksquare-api-debug_20260610.md
new file mode 100644
index 0000000..20a0a31
--- /dev/null
+++ b/steady/steady-DataView/steady-checksquare-api-debug_20260610.md
@@ -0,0 +1,522 @@
+# 数据校验 API 调试文档
+
+## 1. 基础信息
+
+- 模块:`steady/steady-DataView`
+- 控制器:`com.njcn.gather.steady.checksquare.controller.SteadyChecksquareController`
+- 接口前缀:`/steady/data-view/checksquare`
+- 本地默认地址:`http://localhost:18192`
+- Content-Type:`application/json`
+- 认证:除登录和 Swagger 资源外,请求需要携带登录后的 `Authorization` 请求头。
+- 数据库结果表:`steady_checksquare_task`、`steady_checksquare_item`、`steady_checksquare_stat_summary`、`steady_checksquare_detail`
+
+通用请求头:
+
+```http
+Authorization: Bearer
+Content-Type: application/json
+```
+
+通用返回结构:
+
+```json
+{
+ "code": 200,
+ "message": "success",
+ "data": {}
+}
+```
+
+> 实际 `code`、`message` 字段以项目公共 `HttpResult` 封装为准,下面示例重点展示 `data` 内容。
+
+## 2. 查询数据校验历史记录
+
+- 方法:`POST`
+- 路径:`/steady/data-view/checksquare/query`
+- 返回:`HttpResult>`
+- 说明:分页查询已落库的数据校验任务,按创建时间倒序返回。
+
+请求示例:
+
+```json
+{
+ "pageNum": 1,
+ "pageSize": 10,
+ "lineId": "line-001",
+ "indicatorCode": "V_RMS",
+ "timeStart": "2026-05-01 00:00:00",
+ "timeEnd": "2026-05-01 23:59:59",
+ "hasAbnormal": true
+}
+```
+
+请求字段:
+
+| 字段 | 必填 | 说明 |
+| --- | --- | --- |
+| `pageNum` | 否 | 当前页码,继承自 `BaseParam` |
+| `pageSize` | 否 | 每页数量,继承自 `BaseParam` |
+| `lineId` | 否 | 监测点 ID,精确匹配 |
+| `indicatorCode` | 否 | 指标编码,按任务指标集合匹配 |
+| `timeStart` | 否 | 检测开始时间下限,格式 `yyyy-MM-dd HH:mm:ss` |
+| `timeEnd` | 否 | 检测结束时间上限,格式 `yyyy-MM-dd HH:mm:ss` |
+| `hasAbnormal` | 否 | 是否只查询存在异常项的任务;`true` 表示 `abnormalItemCount > 0` |
+
+返回示例:
+
+```json
+{
+ "records": [
+ {
+ "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
+ "taskNo": "CS202606101630001",
+ "lineId": "line-001",
+ "lineName": "进线一",
+ "timeStart": "2026-05-01 00:00:00",
+ "timeEnd": "2026-05-01 23:59:59",
+ "intervalMinutes": 1,
+ "taskStatus": "SUCCESS",
+ "itemCount": 2,
+ "abnormalItemCount": 1,
+ "minDataIntegrity": 0.999306,
+ "createTime": "2026-06-10 16:30:00"
+ }
+ ],
+ "total": 1,
+ "size": 10,
+ "current": 1,
+ "pages": 1
+}
+```
+
+cURL 示例:
+
+```bash
+curl -X POST "http://localhost:18192/steady/data-view/checksquare/query" \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"pageNum":1,"pageSize":10,"lineId":"line-001","indicatorCode":"V_RMS","hasAbnormal":true}'
+```
+
+## 3. 新增数据校验记录
+
+- 方法:`POST`
+- 路径:`/steady/data-view/checksquare/create`
+- 返回:`HttpResult`
+- 说明:按监测点、指标和时间范围实时查询 InfluxDB,执行缺数校验、指标值大小关系校验、谐波奇偶关系校验,并将任务、检测项、统计摘要和明细写入 MySQL。
+
+请求示例:
+
+```json
+{
+ "lineId": "line-001",
+ "indicatorCodes": ["V_RMS", "FREQ", "V_HARMONIC"],
+ "timeStart": "2026-05-01 00:00:00",
+ "timeEnd": "2026-05-01 23:59:59"
+}
+```
+
+请求字段:
+
+| 字段 | 必填 | 说明 |
+| --- | --- | --- |
+| `lineId` | 是 | 监测点 ID,需要能在台账中找到且处于可用状态 |
+| `indicatorCodes` | 是 | 指标编码列表,来自 `/steady/data-view/indicator-tree` |
+| `timeStart` | 是 | 检测开始时间,格式 `yyyy-MM-dd HH:mm:ss` |
+| `timeEnd` | 是 | 检测结束时间,格式 `yyyy-MM-dd HH:mm:ss` |
+
+返回示例:
+
+```json
+{
+ "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
+ "taskNo": "CS202606101630001",
+ "lineId": "line-001",
+ "lineName": "进线一",
+ "timeStart": "2026-05-01 00:00:00",
+ "timeEnd": "2026-05-01 23:59:59",
+ "intervalMinutes": 1,
+ "itemCount": 3,
+ "abnormalItemCount": 1
+}
+```
+
+cURL 示例:
+
+```bash
+curl -X POST "http://localhost:18192/steady/data-view/checksquare/create" \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"lineId":"line-001","indicatorCodes":["V_RMS","FREQ","V_HARMONIC"],"timeStart":"2026-05-01 00:00:00","timeEnd":"2026-05-01 23:59:59"}'
+```
+
+调试建议:
+
+- 新增接口会实际写入 MySQL,重复调用会生成新的任务记录。
+- 时间范围越大、指标越多,InfluxDB 查询和明细落库耗时越高。
+- 谐波类指标固定按 2-50 次聚合检测,不需要在请求体传 `harmonicOrders`。
+
+## 4. 查询数据校验任务详情
+
+- 方法:`GET`
+- 路径:`/steady/data-view/checksquare/detail`
+- 返回:`HttpResult`
+- 说明:按任务 ID 查询任务基础信息、检测项列表和各检测项统计摘要。检测项的明细列表通过 `/item-detail` 按需查询。
+
+请求参数:
+
+| 参数 | 必填 | 说明 |
+| --- | --- | --- |
+| `taskId` | 是 | 数据校验任务 ID,即 `/query` 或 `/create` 返回的 `taskId` |
+
+请求示例:
+
+```http
+GET /steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001
+```
+
+返回示例:
+
+```json
+{
+ "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
+ "taskNo": "CS202606101630001",
+ "lineId": "line-001",
+ "lineName": "进线一",
+ "timeStart": "2026-05-01 00:00:00",
+ "timeEnd": "2026-05-01 23:59:59",
+ "intervalMinutes": 1,
+ "items": [
+ {
+ "itemId": "0f4b6d6c3e9d400a902a2df101d10001",
+ "itemKey": "line-001|V_RMS",
+ "indicatorCode": "V_RMS",
+ "indicatorName": "相电压有效值",
+ "harmonicOrder": null,
+ "intervalMinutes": 1,
+ "hasData": true,
+ "expectedPointCount": 5760,
+ "actualPointCount": 5756,
+ "missingPointCount": 4,
+ "dataIntegrity": 0.999306,
+ "dataIntegrityText": "99.93%",
+ "abnormal": true,
+ "abnormalPointCount": 2,
+ "harmonicParityAbnormal": false,
+ "harmonicParityAbnormalPointCount": 0,
+ "statSummaries": [
+ {
+ "statType": "AVG",
+ "supported": true,
+ "hasData": true,
+ "expectedPointCount": 1440,
+ "actualPointCount": 1439,
+ "missingPointCount": 1,
+ "dataIntegrity": 0.999306,
+ "dataIntegrityText": "99.93%",
+ }
+ ],
+ "statDetails": []
+ }
+ ]
+}
+```
+
+cURL 示例:
+
+```bash
+curl -X GET "http://localhost:18192/steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001" \
+ -H "Authorization: Bearer "
+```
+
+## 5. 查询检测项明细
+
+- 方法:`GET`
+- 路径:`/steady/data-view/checksquare/item-detail`
+- 返回:`HttpResult`
+- 说明:按检测项 ID、明细类型查询缺数连续区间、指标值大小关系异常点或谐波奇偶关系异常点。`pageNum` 和 `pageSize` 未同时传入时保持全量返回;两者同时传入且均大于 0 时返回当前页明细,并在结果中带回分页元数据。
+
+请求参数:
+
+| 参数 | 必填 | 说明 |
+| --- | --- | --- |
+| `itemId` | 是 | 检测项 ID,即任务详情中每个 `items[].itemId` |
+| `detailType` | 是 | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` |
+| `statType` | 否 | 统计类型:`AVG`、`MAX`、`MIN`、`CP95`;查询缺数区间时建议传入 |
+| `pageNum` | 否 | 明细分页页码;与 `pageSize` 同时传入且大于 0 时启用分页 |
+| `pageSize` | 否 | 明细分页条数;与 `pageNum` 同时传入且大于 0 时启用分页 |
+
+### 5.1 查询缺数连续区间
+
+请求示例:
+
+```http
+GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=SEGMENT&statType=AVG
+```
+
+返回示例:
+
+```json
+{
+ "itemId": "0f4b6d6c3e9d400a902a2df101d10001",
+ "detailType": "SEGMENT",
+ "statType": "AVG",
+ "pageNum": null,
+ "pageSize": null,
+ "total": null,
+ "segments": [
+ {
+ "startTime": "2026-05-01 00:00:00",
+ "endTime": "2026-05-01 00:09:00",
+ "status": "NORMAL",
+ "harmonicOrder": null,
+ "missingPointCount": 0,
+ "durationMinutes": 10
+ },
+ {
+ "startTime": "2026-05-01 00:10:00",
+ "endTime": "2026-05-01 00:10:00",
+ "status": "MISSING",
+ "harmonicOrder": null,
+ "missingPointCount": 1,
+ "durationMinutes": 1
+ }
+ ],
+ "valueOrderDetails": [],
+ "harmonicParityDetails": []
+}
+```
+
+### 5.2 查询指标值大小关系异常明细
+
+请求示例:
+
+```http
+GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20
+```
+
+返回示例:
+
+```json
+{
+ "itemId": "0f4b6d6c3e9d400a902a2df101d10001",
+ "detailType": "VALUE_ORDER",
+ "statType": null,
+ "pageNum": 1,
+ "pageSize": 20,
+ "total": 1,
+ "segments": [],
+ "valueOrderDetails": [
+ {
+ "time": "2026-05-01 00:10:00",
+ "phase": "A",
+ "harmonicOrder": null,
+ "maxValue": 219.8,
+ "minValue": 218.1,
+ "avgValue": 219.0,
+ "cp95Value": 219.8
+ }
+ ],
+ "harmonicParityDetails": []
+}
+```
+
+### 5.3 查询谐波奇偶关系异常明细
+
+请求示例:
+
+```http
+GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10002&detailType=HARMONIC_PARITY&pageNum=1&pageSize=20
+```
+
+返回示例:
+
+```json
+{
+ "itemId": "0f4b6d6c3e9d400a902a2df101d10002",
+ "detailType": "HARMONIC_PARITY",
+ "statType": null,
+ "pageNum": 1,
+ "pageSize": 20,
+ "total": 1,
+ "segments": [],
+ "valueOrderDetails": [],
+ "harmonicParityDetails": [
+ {
+ "time": "2026-05-01 00:10:00",
+ "phase": "A",
+ "statType": "AVG",
+ "evenHarmonicOrder": 4,
+ "evenValue": 0.32,
+ "oddHarmonicOrders": [3, 5],
+ "oddValues": [0.08, 0.09],
+ "oddMedianValue": 0.085,
+ "thresholdMultiplier": 3.0
+ }
+ ]
+}
+```
+
+cURL 示例:
+
+```bash
+curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER" \
+ -H "Authorization: Bearer "
+```
+
+分页 cURL 示例:
+
+```bash
+curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20" \
+ -H "Authorization: Bearer "
+```
+
+## 6. 删除数据校验任务
+
+- 方法:`POST`
+- 路径:`/steady/data-view/checksquare/delete`
+- 返回:`HttpResult`
+- 说明:按任务 ID 批量逻辑删除数据校验任务,并同步将任务下检测项置为删除态。删除后历史查询不再返回该任务,详情和检测项明细会按不存在处理。
+
+请求示例:
+
+```json
+[
+ "8f7a4d6d1f3145a88b6f9d7a8e6c1001"
+]
+```
+
+请求字段:
+
+| 字段 | 必填 | 说明 |
+| --- | --- | --- |
+| 请求体 | 是 | 数据校验任务 ID 数组 |
+
+返回示例:
+
+```json
+true
+```
+
+cURL 示例:
+
+```bash
+curl -X POST "http://localhost:18192/steady/data-view/checksquare/delete" \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '["8f7a4d6d1f3145a88b6f9d7a8e6c1001"]'
+```
+
+## 7. 校验规则说明
+
+### 7.1 缺数校验
+
+- 按检测项支持的统计类型分别查询实际存在的时间点。
+- 返回期望点数、实际点数、缺失点数、数据完整性和最大连续缺失时长。
+- `SEGMENT` 明细按连续区间返回,`status` 可取 `NORMAL`、`MISSING`。
+- `FLUC`、`PST` 固定按 10 分钟间隔校验,`PLT` 固定按 120 分钟间隔校验,其余指标按监测点统计间隔校验。
+
+### 7.2 指标值大小关系校验
+
+- 同一时间点、同一指标、同一相别需要满足 `MAX >= CP95 >= AVG >= MIN`。
+- 缺少 `MAX`、`CP95`、`AVG`、`MIN` 任一值的时间点不计入大小关系异常,由缺数校验体现。
+- 异常点写入 `VALUE_ORDER` 明细,前端可通过 `item-detail` 拉取后弹窗展示。
+
+### 7.3 谐波奇偶关系校验
+
+- 谐波指标固定聚合 2-50 次检测。
+- 偶次谐波值需要大于 `0.1` 才继续参与奇偶关系检测。
+- 异常点写入 `HARMONIC_PARITY` 明细,包含偶次谐波值、参与比较的奇次谐波值、中位数和阈值倍数。
+
+## 8. 字段说明
+
+### 8.1 任务字段
+
+| 字段 | 说明 |
+| --- | --- |
+| `taskId` | 数据校验任务 ID |
+| `taskNo` | 数据校验任务编号 |
+| `lineId` | 监测点 ID |
+| `lineName` | 监测点名称 |
+| `timeStart` | 检测开始时间 |
+| `timeEnd` | 检测结束时间 |
+| `intervalMinutes` | 监测点统计间隔,单位分钟 |
+| `taskStatus` | 任务状态,当前成功任务为 `SUCCESS` |
+| `itemCount` | 检测项数量 |
+| `abnormalItemCount` | 存在异常的检测项数量 |
+| `minDataIntegrity` | 检测项中的最低数据完整性 |
+| `createTime` | 任务创建时间 |
+
+### 8.2 检测项字段
+
+| 字段 | 说明 |
+| --- | --- |
+| `itemId` | 检测项 ID |
+| `itemKey` | 检测项唯一键 |
+| `indicatorCode` | 指标编码 |
+| `indicatorName` | 指标名称 |
+| `harmonicOrder` | 谐波次数;聚合检测项为空 |
+| `hasData` | 当前检测项是否存在任意数据 |
+| `expectedPointCount` | 期望点数 |
+| `actualPointCount` | 实际点数 |
+| `missingPointCount` | 缺失点数 |
+| `dataIntegrity` | 数据完整性数值 |
+| `dataIntegrityText` | 数据完整性文本 |
+| `abnormal` | 指标值大小关系是否异常 |
+| `abnormalPointCount` | 指标值大小关系异常点数 |
+| `harmonicParityAbnormal` | 谐波奇偶关系是否异常 |
+| `harmonicParityAbnormalPointCount` | 谐波奇偶关系异常点数 |
+| `statSummaries` | 各统计类型缺数摘要 |
+| `statDetails` | 兼容字段;明细建议通过 `/item-detail` 按需查询 |
+
+### 8.3 检测项明细字段
+
+| 字段 | 说明 |
+| --- | --- |
+| `itemId` | 检测项 ID |
+| `detailType` | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` |
+| `statType` | 统计类型;未传入或当前明细类型不按统计类型过滤时为空 |
+| `pageNum` | 当前页码;未启用分页时为空 |
+| `pageSize` | 每页条数;未启用分页时为空 |
+| `total` | 当前查询条件下的总明细数;未启用分页时为空 |
+| `segments` | 缺数连续区间,仅 `SEGMENT` 查询返回数据 |
+| `valueOrderDetails` | 指标值大小关系异常点,仅 `VALUE_ORDER` 查询返回数据 |
+| `harmonicParityDetails` | 谐波奇偶关系异常点,仅 `HARMONIC_PARITY` 查询返回数据 |
+
+## 9. 常见错误场景
+
+| 场景 | 后端提示 |
+| --- | --- |
+| 新增时 `lineId` 为空 | `监测点 ID 不能为空` |
+| 新增时 `indicatorCodes` 为空 | `指标不能为空` |
+| 新增时开始时间为空 | `开始时间不能为空` |
+| 新增时结束时间为空 | `结束时间不能为空` |
+| 开始时间大于结束时间 | `开始时间不能大于结束时间` |
+| 时间格式不正确 | `时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss` |
+| 监测点不存在或不可用 | `监测点不存在或不可用` |
+| 指标编码不支持 | `稳态指标不支持:xxx` |
+| 任务不存在或已删除 | `数据校验任务不存在` |
+| 检测项不存在或已删除 | `数据校验检测项不存在` |
+| 删除时任务 ID 为空 | `数据校验任务 ID 不能为空` |
+| 删除时任务不存在或已删除 | `数据校验任务不存在或已删除` |
+| 明细类型为空 | `明细类型不能为空` |
+| 明细类型不支持 | `明细类型不支持:xxx` |
+
+## 10. 调试流程建议
+
+1. 调用 `/steady/data-view/ledger-tree` 获取可用监测点,取叶子节点 `lineId`。
+2. 调用 `/steady/data-view/indicator-tree` 获取可用指标编码。
+3. 调用 `/steady/data-view/checksquare/create` 新增校验任务。
+4. 使用返回的 `taskId` 调用 `/steady/data-view/checksquare/detail` 查看任务详情和检测项列表。
+5. 使用 `items[].itemId` 调用 `/steady/data-view/checksquare/item-detail` 查看缺数区间或异常点明细。
+6. 调用 `/steady/data-view/checksquare/query` 验证任务已落库,并按监测点、指标、时间和异常状态筛选历史记录。
+7. 调用 `/steady/data-view/checksquare/delete` 删除任务后,再调用 `/query`、`/detail` 验证任务已不可见。
+
+## 11. 环境依赖
+
+- MySQL:需要已初始化 4 张 `steady_checksquare_*` 结果表。
+- InfluxDB:新增校验任务时需要可访问 `steady.influxdb` 配置的时序库。
+- 台账:新增校验任务时需要 `lineId` 能在台账中解析到监测点路径和统计间隔。
+- 指标目录:`indicatorCodes` 必须来自后端趋势指标目录。
+
+
diff --git a/tools/README.md b/tools/README.md
index f0ad21e..715aaeb 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -10,9 +10,10 @@
- `add-data`
- `add-ledger`
- `mms-mapping`
+- `parse-pqdif`
- `wave-tool`
-因此,`tools` 现阶段仍然是聚合模块,但当前已实际承载激活工具、电能质量数据补录工具、数据台账工具空模块、ICD/MMS 映射工具和波形查看工具五个子模块。
+因此,`tools` 现阶段仍然是聚合模块,但当前已实际承载激活工具、电能质量数据补录工具、数据台账工具空模块、ICD/MMS 映射工具、PQDIF 解析预留工具和波形查看工具六个子模块。
## 当前结构
@@ -22,6 +23,7 @@ tools/
├── add-data/
├── add-ledger/
├── mms-mapping/
+├── parse-pqdif/
└── wave-tool/
```
@@ -78,6 +80,16 @@ tools/
从接口层看,当前主要围绕 `/api/mms-mapping` 路径提供能力。
+## parse-pqdif 的职责
+
+`parse-pqdif` 当前仅作为 PQDIF 文件解析工具的预留骨架,参照 `mms-mapping` 的 Controller、Service、ServiceImpl 和 VO 分层组织。
+
+- 预留 PQDIF 文件上传解析入口
+- 预留解析服务接口与实现
+- 当前不包含真实 PQDIF 解析、持久化或数据转换逻辑
+
+从接口层看,当前预留路径为 `/api/parse-pqdif/parse`。
+
## mms-mapping 配置
`mms-mapping` 当前支持以下配置项:
@@ -98,7 +110,7 @@ tools/
## 依赖关系
-`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/mms-mapping` 与 `tools/wave-tool` 当前主要依赖:
+`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/mms-mapping`、`tools/parse-pqdif` 与 `tools/wave-tool` 当前主要依赖:
- `com.njcn:njcn-common`
- `com.njcn:spingboot2.3.12`
diff --git a/tools/mms-mapping/pom.xml b/tools/mms-mapping/pom.xml
index f61b169..292a63d 100644
--- a/tools/mms-mapping/pom.xml
+++ b/tools/mms-mapping/pom.xml
@@ -20,6 +20,12 @@
0.0.1
+
+ com.njcn
+ mybatis-plus
+ 0.0.1
+
+
com.njcn
spingboot2.3.12
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java
new file mode 100644
index 0000000..b5f3fd9
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java
@@ -0,0 +1,72 @@
+package com.njcn.gather.icd.mapping.controller;
+
+import com.njcn.common.pojo.annotation.OperateInfo;
+import com.njcn.common.pojo.enums.common.LogEnum;
+import com.njcn.common.pojo.enums.response.CommonResponseEnum;
+import com.njcn.common.pojo.response.HttpResult;
+import com.njcn.common.utils.LogUtil;
+import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam;
+import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO;
+import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO;
+import com.njcn.gather.icd.mapping.service.MmsDeviceTypeService;
+import com.njcn.web.controller.BaseController;
+import com.njcn.web.utils.HttpResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 设备类型校验入口。
+ */
+@Slf4j
+@Api(tags = "设备类型ICD校验")
+@RestController
+@RequestMapping("/api/mms-mapping/dev-types")
+@RequiredArgsConstructor
+public class MmsDeviceTypeController extends BaseController {
+
+ private final MmsDeviceTypeService mmsDeviceTypeService;
+
+ @OperateInfo(info = LogEnum.BUSINESS_COMMON)
+ @ApiOperation("查询设备类型校验列表")
+ @GetMapping
+ public HttpResult> listDeviceTypes() {
+ String methodDescribe = getMethodDescribe("listDeviceTypes");
+ LogUtil.njcnDebug(log, "{},开始查询设备类型校验列表", methodDescribe);
+ List result = mmsDeviceTypeService.listDeviceTypes();
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
+ }
+
+ @OperateInfo(info = LogEnum.BUSINESS_COMMON)
+ @ApiOperation("保存设备类型ICD唯一性校验结果")
+ @ApiImplicitParam(name = "id", value = "设备类型ID", required = true)
+ @PostMapping("/{id}/icd-check-result")
+ public HttpResult saveIcdCheckResult(@PathVariable("id") String id,
+ @RequestBody IcdCheckResultSaveParam param) {
+ String methodDescribe = getMethodDescribe("saveIcdCheckResult");
+ LogUtil.njcnDebug(log, "{},开始保存设备类型ICD校验结果,devTypeId={}", methodDescribe, id);
+ boolean result = mmsDeviceTypeService.saveIcdCheckResult(id, param);
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
+ }
+
+ @OperateInfo(info = LogEnum.BUSINESS_COMMON)
+ @ApiOperation("设备类型PQDIF校验")
+ @ApiImplicitParam(name = "id", value = "设备类型ID", required = true)
+ @PostMapping("/{id}/pqdif-check")
+ public HttpResult pqdifCheck(@PathVariable("id") String id) {
+ String methodDescribe = getMethodDescribe("pqdifCheck");
+ LogUtil.njcnDebug(log, "{},设备类型PQDIF校验预留入口,devTypeId={}", methodDescribe, id);
+ PqdifCheckPlaceholderVO result = mmsDeviceTypeService.pqdifCheck(id);
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
+ }
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java
new file mode 100644
index 0000000..9102fa1
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java
@@ -0,0 +1,15 @@
+package com.njcn.gather.icd.mapping.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.njcn.gather.icd.mapping.pojo.po.CsDevTypePO;
+import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO;
+
+import java.util.List;
+
+/**
+ * 设备类型 Mapper。
+ */
+public interface CsDevTypeMapper extends BaseMapper {
+
+ List selectDeviceTypeCheckList();
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java
new file mode 100644
index 0000000..501f058
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java
@@ -0,0 +1,10 @@
+package com.njcn.gather.icd.mapping.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO;
+
+/**
+ * ICD 路径 Mapper。
+ */
+public interface CsIcdPathMapper extends BaseMapper {
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml
new file mode 100644
index 0000000..60d386a
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java
new file mode 100644
index 0000000..b2e3a9c
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java
@@ -0,0 +1,25 @@
+package com.njcn.gather.icd.mapping.pojo.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * ICD 校验结果保存参数。
+ */
+@Data
+@ApiModel("ICD校验结果保存参数")
+public class IcdCheckResultSaveParam {
+
+ @ApiModelProperty("MMS映射JSON")
+ private String mappingJson;
+
+ @ApiModelProperty("MMS映射XML")
+ private String xml;
+
+ @ApiModelProperty("校验结论:0-否,1-是")
+ private Integer result;
+
+ @ApiModelProperty("校验结论描述")
+ private String msg;
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java
new file mode 100644
index 0000000..50be80f
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java
@@ -0,0 +1,61 @@
+package com.njcn.gather.icd.mapping.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 设备类型表。
+ */
+@Data
+@TableName("cs_dev_type")
+public class CsDevTypePO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId("id")
+ private String id;
+
+ @TableField("name")
+ private String name;
+
+ @TableField("icd")
+ private String icd;
+
+ @TableField("power")
+ private String power;
+
+ @TableField("Dev_Volt")
+ private Float devVolt;
+
+ @TableField("Dev_Curr")
+ private Float devCurr;
+
+ @TableField("Dev_Chns")
+ private Integer devChns;
+
+ @TableField("Wave_Cmd")
+ private String waveCmd;
+
+ @TableField("State")
+ private Integer state;
+
+ @TableField("Create_By")
+ private String createBy;
+
+ @TableField("Create_Time")
+ private LocalDateTime createTime;
+
+ @TableField("Update_By")
+ private String updateBy;
+
+ @TableField("Update_Time")
+ private LocalDateTime updateTime;
+
+ @TableField("report_name")
+ private String reportName;
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java
new file mode 100644
index 0000000..268a7e8
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java
@@ -0,0 +1,67 @@
+package com.njcn.gather.icd.mapping.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * ICD 存储记录。
+ */
+@Data
+@TableName("cs_icd_path")
+public class CsIcdPathPO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId("ID")
+ private String id;
+
+ @TableField("Name")
+ private String name;
+
+ @TableField("Path")
+ private String path;
+
+ @TableField("Angle")
+ private Integer angle;
+
+ @TableField("Use_Phase_Index")
+ private Integer usePhaseIndex;
+
+ @TableField("State")
+ private Integer state;
+
+ @TableField("Create_By")
+ private String createBy;
+
+ @TableField("Create_Time")
+ private LocalDateTime createTime;
+
+ @TableField("Update_By")
+ private String updateBy;
+
+ @TableField("Update_Time")
+ private LocalDateTime updateTime;
+
+ @TableField("Json_Str")
+ private String jsonStr;
+
+ @TableField("Xml_Str")
+ private String xmlStr;
+
+ @TableField("Result")
+ private Integer result;
+
+ @TableField("Msg")
+ private String msg;
+
+ @TableField("Type")
+ private Integer type;
+
+ @TableField("Reference_Icd_Id")
+ private String referenceIcdId;
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java
new file mode 100644
index 0000000..fe4ff16
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java
@@ -0,0 +1,43 @@
+package com.njcn.gather.icd.mapping.pojo.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 设备类型校验列表项。
+ */
+@Data
+@ApiModel("设备类型校验列表项")
+public class MmsDeviceTypeVO {
+
+ @ApiModelProperty("设备类型ID")
+ private String id;
+
+ @ApiModelProperty("设备类型名称")
+ private String name;
+
+ @ApiModelProperty("关联ICD ID")
+ private String icdId;
+
+ @ApiModelProperty("ICD名称")
+ private String icdName;
+
+ @ApiModelProperty("ICD路径")
+ private String icdPath;
+
+ @ApiModelProperty("ICD校验结论:0-否,1-是")
+ private Integer icdResult;
+
+ @ApiModelProperty("ICD校验结论描述")
+ private String icdMsg;
+
+ @ApiModelProperty("报告模板名称")
+ private String reportName;
+
+ @ApiModelProperty("是否可执行ICD唯一性校验")
+ private Boolean canCheckIcd;
+
+ @ApiModelProperty("是否可执行PQDIF校验")
+ private Boolean canCheckPqdif;
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java
new file mode 100644
index 0000000..8f86b69
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java
@@ -0,0 +1,19 @@
+package com.njcn.gather.icd.mapping.pojo.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * PQDIF 校验预留响应。
+ */
+@Data
+@ApiModel("PQDIF校验预留响应")
+public class PqdifCheckPlaceholderVO {
+
+ @ApiModelProperty("状态")
+ private String status;
+
+ @ApiModelProperty("提示信息")
+ private String message;
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java
new file mode 100644
index 0000000..67d9de4
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java
@@ -0,0 +1,19 @@
+package com.njcn.gather.icd.mapping.service;
+
+import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam;
+import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO;
+import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO;
+
+import java.util.List;
+
+/**
+ * 设备类型校验服务。
+ */
+public interface MmsDeviceTypeService {
+
+ List listDeviceTypes();
+
+ boolean saveIcdCheckResult(String devTypeId, IcdCheckResultSaveParam param);
+
+ PqdifCheckPlaceholderVO pqdifCheck(String devTypeId);
+}
diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java
new file mode 100644
index 0000000..94cc71b
--- /dev/null
+++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java
@@ -0,0 +1,141 @@
+package com.njcn.gather.icd.mapping.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.njcn.gather.icd.mapping.mapper.CsDevTypeMapper;
+import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper;
+import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam;
+import com.njcn.gather.icd.mapping.pojo.po.CsDevTypePO;
+import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO;
+import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO;
+import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO;
+import com.njcn.gather.icd.mapping.service.MmsDeviceTypeService;
+import com.njcn.web.utils.RequestUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 设备类型校验服务实现。
+ */
+@Service
+@RequiredArgsConstructor
+public class MmsDeviceTypeServiceImpl implements MmsDeviceTypeService {
+
+ private static final int STATE_NORMAL = 1;
+
+ private static final int ICD_TYPE_STANDARD = 1;
+
+ private static final String PQDIF_NOT_SUPPORTED = "NOT_SUPPORTED";
+
+ private final CsDevTypeMapper csDevTypeMapper;
+
+ private final CsIcdPathMapper csIcdPathMapper;
+
+ @Override
+ public List listDeviceTypes() {
+ return csDevTypeMapper.selectDeviceTypeCheckList();
+ }
+
+ @Override
+ @Transactional
+ public boolean saveIcdCheckResult(String devTypeId, IcdCheckResultSaveParam param) {
+ if (param == null) {
+ throw new IllegalArgumentException("ICD校验结果不能为空");
+ }
+ CsDevTypePO devType = requireDevType(devTypeId);
+ if (isBlank(devType.getIcd())) {
+ throw new IllegalArgumentException("设备类型未关联ICD,不能保存校验结果");
+ }
+
+ CsIcdPathPO referenceIcd = requireUniqueReferenceIcd();
+ CsIcdPathPO icdPath = csIcdPathMapper.selectById(devType.getIcd());
+ if (icdPath == null || !Integer.valueOf(STATE_NORMAL).equals(icdPath.getState())) {
+ throw new IllegalArgumentException("设备类型关联的ICD不存在或已删除");
+ }
+
+ icdPath.setJsonStr(trimToNull(param.getMappingJson()));
+ icdPath.setXmlStr(trimToNull(param.getXml()));
+ icdPath.setResult(normalizeResult(param.getResult()));
+ icdPath.setMsg(trimToNull(param.getMsg()));
+ icdPath.setReferenceIcdId(referenceIcd.getId());
+ icdPath.setUpdateBy(currentUserId());
+ icdPath.setUpdateTime(LocalDateTime.now());
+ return csIcdPathMapper.updateById(icdPath) > 0;
+ }
+
+ @Override
+ public PqdifCheckPlaceholderVO pqdifCheck(String devTypeId) {
+ requireDevType(devTypeId);
+ PqdifCheckPlaceholderVO result = new PqdifCheckPlaceholderVO();
+ result.setStatus(PQDIF_NOT_SUPPORTED);
+ result.setMessage("PQDIF校验功能待实现");
+ return result;
+ }
+
+ private CsDevTypePO requireDevType(String devTypeId) {
+ String id = requireText(devTypeId, "设备类型ID不能为空");
+ CsDevTypePO devType = csDevTypeMapper.selectById(id);
+ if (devType == null || !Integer.valueOf(STATE_NORMAL).equals(devType.getState())) {
+ throw new IllegalArgumentException("设备类型不存在或已删除");
+ }
+ return devType;
+ }
+
+ /**
+ * 全系统只允许一个正常状态的标准 ICD 作为唯一参照。
+ */
+ private CsIcdPathPO requireUniqueReferenceIcd() {
+ List referenceIcdList = csIcdPathMapper.selectList(new LambdaQueryWrapper()
+ .eq(CsIcdPathPO::getState, STATE_NORMAL)
+ .eq(CsIcdPathPO::getType, ICD_TYPE_STANDARD));
+ if (referenceIcdList == null || referenceIcdList.isEmpty()) {
+ throw new IllegalArgumentException("未配置标准ICD,无法执行唯一性校验");
+ }
+ if (referenceIcdList.size() > 1) {
+ throw new IllegalArgumentException("存在多个标准ICD,无法确定唯一参照");
+ }
+ return referenceIcdList.get(0);
+ }
+
+ private Integer normalizeResult(Integer result) {
+ if (result == null) {
+ throw new IllegalArgumentException("校验结论不能为空");
+ }
+ if (result != 0 && result != 1) {
+ throw new IllegalArgumentException("校验结论只能是0或1");
+ }
+ return result;
+ }
+
+ private String requireText(String value, String message) {
+ String text = trimToNull(value);
+ if (text == null) {
+ throw new IllegalArgumentException(message);
+ }
+ return text;
+ }
+
+ private String trimToNull(String value) {
+ if (value == null) {
+ return null;
+ }
+ String text = value.trim();
+ return text.isEmpty() ? null : text;
+ }
+
+ private boolean isBlank(String value) {
+ return trimToNull(value) == null;
+ }
+
+ private String currentUserId() {
+ try {
+ String userId = RequestUtil.getUserId();
+ return isBlank(userId) ? "未知用户" : userId;
+ } catch (Exception ex) {
+ return "未知用户";
+ }
+ }
+}
diff --git a/tools/parse-pqdif/pom.xml b/tools/parse-pqdif/pom.xml
new file mode 100644
index 0000000..9a6d660
--- /dev/null
+++ b/tools/parse-pqdif/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+
+ com.njcn.gather
+ tools
+ 1.0.0
+
+
+ parse-pqdif
+ jar
+
+
+
+ com.njcn
+ njcn-common
+ 0.0.1
+
+
+
+ com.njcn
+ spingboot2.3.12
+ 2.3.12
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ parse-pqdif
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+
+
diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java
new file mode 100644
index 0000000..58f7320
--- /dev/null
+++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java
@@ -0,0 +1,46 @@
+package com.njcn.gather.tool.parsepqdif.controller;
+
+import com.njcn.common.pojo.annotation.OperateInfo;
+import com.njcn.common.pojo.enums.common.LogEnum;
+import com.njcn.common.pojo.enums.response.CommonResponseEnum;
+import com.njcn.common.pojo.response.HttpResult;
+import com.njcn.common.utils.LogUtil;
+import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
+import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService;
+import com.njcn.web.controller.BaseController;
+import com.njcn.web.utils.HttpResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * PQDIF 解析接口入口。
+ */
+@Slf4j
+@Api(tags = "PQDIF解析")
+@RestController
+@RequestMapping("/api/parse-pqdif")
+@RequiredArgsConstructor
+public class ParsePqdifController extends BaseController {
+
+ private final ParsePqdifService parsePqdifService;
+
+ @OperateInfo(info = LogEnum.BUSINESS_COMMON)
+ @ApiOperation("上传 PQDIF 文件并解析")
+ @ApiImplicitParam(name = "pqdifFile", value = "PQDIF文件", required = true, dataType = "__file", paramType = "form")
+ @PostMapping(value = "/parse", consumes = {"multipart/form-data"})
+ public HttpResult parse(@RequestPart("pqdifFile") MultipartFile pqdifFile) {
+ String methodDescribe = getMethodDescribe("parse");
+ LogUtil.njcnDebug(log, "{},PQDIF解析预留入口,fileName={}",
+ methodDescribe, pqdifFile == null ? null : pqdifFile.getOriginalFilename());
+ PqdifParseResponse result = parsePqdifService.parse(pqdifFile);
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
+ }
+}
diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java
new file mode 100644
index 0000000..313f56a
--- /dev/null
+++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java
@@ -0,0 +1,22 @@
+package com.njcn.gather.tool.parsepqdif.pojo.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * PQDIF 解析占位响应。
+ */
+@Data
+@ApiModel("PQDIF解析响应")
+public class PqdifParseResponse {
+
+ @ApiModelProperty("状态")
+ private String status;
+
+ @ApiModelProperty("提示信息")
+ private String message;
+
+ @ApiModelProperty("文件名")
+ private String fileName;
+}
diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java
new file mode 100644
index 0000000..c74bc3a
--- /dev/null
+++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java
@@ -0,0 +1,12 @@
+package com.njcn.gather.tool.parsepqdif.service;
+
+import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * PQDIF 解析服务。
+ */
+public interface ParsePqdifService {
+
+ PqdifParseResponse parse(MultipartFile pqdifFile);
+}
diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java
new file mode 100644
index 0000000..78b9fe4
--- /dev/null
+++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java
@@ -0,0 +1,24 @@
+package com.njcn.gather.tool.parsepqdif.service.impl;
+
+import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
+import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * PQDIF 解析服务实现。
+ */
+@Service
+public class ParsePqdifServiceImpl implements ParsePqdifService {
+
+ private static final String STATUS_NOT_SUPPORTED = "NOT_SUPPORTED";
+
+ @Override
+ public PqdifParseResponse parse(MultipartFile pqdifFile) {
+ PqdifParseResponse response = new PqdifParseResponse();
+ response.setStatus(STATUS_NOT_SUPPORTED);
+ response.setMessage("PQDIF解析功能待实现");
+ response.setFileName(pqdifFile == null ? null : pqdifFile.getOriginalFilename());
+ return response;
+ }
+}
diff --git a/tools/parse-pqdif/src/main/resources/.gitkeep b/tools/parse-pqdif/src/main/resources/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/parse-pqdif/src/main/resources/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/tools/pom.xml b/tools/pom.xml
index c1d5dcf..369df55 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -23,6 +23,7 @@
wave-tool
add-data
add-ledger
+ parse-pqdif