refactor(steady): 重构数据校验功能并新增PQDIF解析预留模块

- 将数据校验中的缺失率相关字段替换为数据完整性字段
- 新增数据校验任务删除功能及相应测试
- 在tools模块中添加parse-pqdif子模块作为PQDIF文件解析预留
- 更新README文档以反映新的模块结构和依赖关系
- 优化数据校验统计汇总逻辑和测试覆盖
- 在entrance模块中集成parse-pqdif依赖
- 重构数据校验服务层实现和数据对象映射
This commit is contained in:
2026-06-12 08:41:11 +08:00
parent f7154db93d
commit 212b69060c
37 changed files with 1606 additions and 126 deletions

View File

@@ -45,19 +45,6 @@ public class SteadyChecksquareCalculator {
return result;
}
public int maxContinuousMissingMinutes(List<SteadyChecksquareSegmentVO> 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();

View File

@@ -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<Boolean> delete(@RequestBody List<String> 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")

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<String> taskIds);
SteadyChecksquareQueryVO detail(String taskId);
SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType);

View File

@@ -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<String> taskIds) {
List<String> ids = normalizeTextList(taskIds);
if (ids.isEmpty()) {
throw fail("数据校验任务 ID 不能为空");
}
List<SteadyChecksquareTaskPO> 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<String> taskIds) {
LambdaUpdateWrapper<SteadyChecksquareTaskPO> taskWrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>()
.set(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_DELETED)
.in(SteadyChecksquareTaskPO::getId, taskIds)
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
boolean taskResult = taskService.update(taskWrapper);
// 检测项同步置为删除态,避免已删任务下的 item-detail 被继续访问。
LambdaUpdateWrapper<SteadyChecksquareItemPO> itemWrapper = new LambdaUpdateWrapper<SteadyChecksquareItemPO>()
.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<LocalDateTime> actualSlots = queryMergedActualSlots(lineId, indicator, harmonicOrder, statType, startTime, endTime, intervalMinutes);
Set<LocalDateTime> 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<SteadyChecksquareItemVO> 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<SteadyChecksquareItemVO> items) {
BigDecimal max = BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
private BigDecimal minDataIntegrity(List<SteadyChecksquareItemVO> 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<SteadyChecksquareItemVO> items) {
for (SteadyChecksquareItemVO item : items) {
if (Boolean.TRUE.equals(item.getHasData())) {
return true;
}
}
return false;
return min;
}
private Boolean anyAbnormal(List<SteadyChecksquareItemVO> items) {
@@ -986,15 +1005,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return false;
}
private Boolean anySummaryHasData(List<SteadyChecksquareStatSummaryVO> summaries) {
for (SteadyChecksquareStatSummaryVO summary : summaries) {
if (Boolean.TRUE.equals(summary.getHasData())) {
return true;
}
}
return false;
}
private Integer averageInteger(List<SteadyChecksquareItemVO> 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<SteadyChecksquareItemVO> items) {
private BigDecimal averageDataIntegrity(List<SteadyChecksquareItemVO> 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<SteadyChecksquareStatSummaryVO> summaries) {
private BigDecimal averageSummaryDataIntegrity(List<SteadyChecksquareStatSummaryVO> 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) {

View File

@@ -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='稳态数据校验明细表';

View File

@@ -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`;

View File

@@ -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());
}
}

View File

@@ -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<SteadyChecksquareTaskPO> 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<SteadyChecksquareTaskPO> 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<SteadyChecksquareItemVO> 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;
}