From 1c979e248a4649503aa781e8fd1181162ed9242f Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Thu, 11 Jun 2026 11:09:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(steady-checksquare):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A0=A1=E9=AA=8C=E5=8A=9F=E8=83=BD=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加数据校验历史记录查询接口 - 实现数据校验任务创建功能 - 新增数据校验详情查询接口 - 添加谐波奇偶关系异常检测规则 - 实现数据校验明细数据结构 - 添加数据校验编号生成工具 - 优化InfluxDB查询组件并增加缓存机制 - 添加数据校验常量定义 - 实现数据校验值生成器中的派生字段处理逻辑 - 新增数据校验相关的VO、PO、DTO类 - 添加数据校验组件单元测试 --- ...hecksquareHarmonicParityRuleComponent.java | 193 ++++ ...SteadyChecksquareInfluxQueryComponent.java | 183 +++- ...adyChecksquareValueOrderRuleComponent.java | 16 +- .../SteadyChecksquareController.java | 48 +- .../mapper/SteadyChecksquareDetailMapper.java | 10 + .../mapper/SteadyChecksquareItemMapper.java | 10 + .../SteadyChecksquareStatSummaryMapper.java | 10 + .../mapper/SteadyChecksquareTaskMapper.java | 10 + .../pojo/constant/SteadyChecksquareConst.java | 18 + .../SteadyChecksquareHistoryQueryParam.java | 31 + .../param/SteadyChecksquareQueryParam.java | 7 +- .../pojo/po/SteadyChecksquareDetailPO.java | 67 ++ .../pojo/po/SteadyChecksquareItemPO.java | 63 ++ .../po/SteadyChecksquareStatSummaryPO.java | 45 + .../pojo/po/SteadyChecksquareTaskPO.java | 59 ++ .../pojo/vo/SteadyChecksquareCreateVO.java | 44 + ...eadyChecksquareHarmonicParityDetailVO.java | 47 + ...SteadyChecksquareHarmonicParityRuleVO.java | 23 + .../vo/SteadyChecksquareItemDetailVO.java | 48 + .../pojo/vo/SteadyChecksquareItemVO.java | 13 + .../pojo/vo/SteadyChecksquareQueryVO.java | 6 + .../pojo/vo/SteadyChecksquareSegmentVO.java | 3 + .../pojo/vo/SteadyChecksquareTaskVO.java | 54 ++ .../SteadyChecksquareValueOrderDetailVO.java | 3 + .../SteadyChecksquareDetailService.java | 10 + .../service/SteadyChecksquareItemService.java | 10 + .../service/SteadyChecksquareService.java | 16 +- .../SteadyChecksquareStatSummaryService.java | 10 + .../service/SteadyChecksquareTaskService.java | 10 + .../SteadyChecksquareDetailServiceImpl.java | 15 + .../SteadyChecksquareItemServiceImpl.java | 15 + .../impl/SteadyChecksquareServiceImpl.java | 896 +++++++++++++++++- ...eadyChecksquareStatSummaryServiceImpl.java | 16 + .../SteadyChecksquareTaskServiceImpl.java | 15 + .../util/SteadyChecksquareIdUtil.java | 24 + ...squareHarmonicParityRuleComponentTest.java | 147 +++ ...dyChecksquareInfluxQueryComponentTest.java | 96 ++ ...hecksquareValueOrderRuleComponentTest.java | 74 +- .../SteadyChecksquareControllerTest.java | 20 +- .../SteadyChecksquareQueryParamTest.java | 2 +- .../SteadyChecksquareServiceImplTest.java | 408 +++++++- .../component/AddDataValueGenerator.java | 13 +- .../component/AddDataValueGeneratorTest.java | 20 + 43 files changed, 2716 insertions(+), 112 deletions(-) create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java create mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java create mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java new file mode 100644 index 0000000..d1c55d5 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java @@ -0,0 +1,193 @@ +package com.njcn.gather.steady.checksquare.component; + +import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO; +import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO; +import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 谐波偶次与局部奇次基线关系规则。 + */ +@Component +@RequiredArgsConstructor +public class SteadyChecksquareHarmonicParityRuleComponent { + + private static final DateTimeFormatter OUTPUT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final BigDecimal THRESHOLD_MULTIPLIER = new BigDecimal("2"); + private static final BigDecimal EVEN_HARMONIC_DEADBAND_VALUE = new BigDecimal("0.1"); + private static final int MIN_ODD_REFERENCE_COUNT = 2; + + private final SteadyChecksquareInfluxQueryComponent influxQueryComponent; + + public SteadyChecksquareHarmonicParityRuleVO check(String lineId, SteadyTrendIndicatorDefinitionBO indicator, + LocalDateTime startTime, LocalDateTime endTime, + int intervalMinutes) { + SteadyChecksquareHarmonicParityRuleVO result = new SteadyChecksquareHarmonicParityRuleVO(); + if (!supportHarmonicParityRule(indicator)) { + return result; + } + for (String statType : indicator.getSupportStats()) { + for (String phase : indicator.getPhaseCodes()) { + Map> valueMap = queryOrderValueMap(lineId, indicator, phase, + statType, startTime, endTime, intervalMinutes); + appendAbnormalDetails(result, phase, statType, indicator, valueMap); + } + } + result.setAbnormalPointCount(result.getAbnormalDetails().size()); + result.setAbnormal(result.getAbnormalPointCount() > 0); + return result; + } + + private boolean supportHarmonicParityRule(SteadyTrendIndicatorDefinitionBO indicator) { + return indicator != null && Boolean.TRUE.equals(indicator.getHarmonic()) + && indicator.getHarmonicOrderStart() != null && indicator.getHarmonicOrderEnd() != null; + } + + private Map> queryOrderValueMap(String lineId, + SteadyTrendIndicatorDefinitionBO indicator, + String phase, String statType, + LocalDateTime startTime, + LocalDateTime endTime, + int intervalMinutes) { + Map> result = new LinkedHashMap>(); + List fields = new ArrayList(); + for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) { + fields.add(buildResolvedField(lineId, indicator, order, phase, statType)); + } + Map> fieldValueMap = + influxQueryComponent.queryValuePointMap(fields, startTime, endTime, intervalMinutes); + if (fieldValueMap == null) { + fieldValueMap = Collections.emptyMap(); + } + for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) { + result.put(order, toValueMap(fieldValueMap.get(indicator.getHarmonicFieldPrefix() + "_" + order))); + } + return result; + } + + private void appendAbnormalDetails(SteadyChecksquareHarmonicParityRuleVO result, String phase, String statType, + SteadyTrendIndicatorDefinitionBO indicator, + Map> valueMap) { + for (int order = firstEvenOrder(indicator.getHarmonicOrderStart()); order <= indicator.getHarmonicOrderEnd(); order += 2) { + Map evenValues = valueMap.get(order); + if (evenValues == null || evenValues.isEmpty()) { + continue; + } + for (Map.Entry entry : evenValues.entrySet()) { + appendAbnormalDetailIfNecessary(result, phase, statType, order, entry.getKey(), entry.getValue(), valueMap); + } + } + } + + private void appendAbnormalDetailIfNecessary(SteadyChecksquareHarmonicParityRuleVO result, String phase, + String statType, int evenOrder, LocalDateTime time, + BigDecimal evenValue, + Map> valueMap) { + if (evenValue == null || evenValue.compareTo(EVEN_HARMONIC_DEADBAND_VALUE) <= 0) { + return; + } + List oddOrders = buildOddReferenceOrders(evenOrder); + List oddValues = new ArrayList(); + List effectiveOddOrders = new ArrayList(); + for (Integer oddOrder : oddOrders) { + Map values = valueMap.get(oddOrder); + BigDecimal oddValue = values == null ? null : values.get(time); + if (oddValue != null) { + effectiveOddOrders.add(oddOrder); + oddValues.add(oddValue); + } + } + if (oddValues.size() < MIN_ODD_REFERENCE_COUNT) { + return; + } + BigDecimal median = calculateMedian(oddValues); + if (median == null || evenValue.compareTo(median.multiply(THRESHOLD_MULTIPLIER)) <= 0) { + return; + } + result.getAbnormalDetails().add(buildDetail(time, phase, statType, evenOrder, evenValue, + effectiveOddOrders, oddValues, median)); + } + + private SteadyChecksquareHarmonicParityDetailVO buildDetail(LocalDateTime time, String phase, String statType, + Integer evenOrder, BigDecimal evenValue, + List oddOrders, List oddValues, + BigDecimal median) { + SteadyChecksquareHarmonicParityDetailVO detail = new SteadyChecksquareHarmonicParityDetailVO(); + detail.setTime(OUTPUT_TIME_FORMATTER.format(time)); + detail.setPhase(phase); + detail.setStatType(statType); + detail.setEvenHarmonicOrder(evenOrder); + detail.setEvenValue(evenValue); + detail.setOddHarmonicOrders(new ArrayList(oddOrders)); + detail.setOddValues(new ArrayList(oddValues)); + detail.setOddMedianValue(median); + detail.setThresholdMultiplier(THRESHOLD_MULTIPLIER); + return detail; + } + + private List buildOddReferenceOrders(int evenOrder) { + List result = new ArrayList(); + result.add(evenOrder - 3); + result.add(evenOrder - 1); + result.add(evenOrder + 1); + result.add(evenOrder + 3); + return result; + } + + private BigDecimal calculateMedian(List values) { + if (values == null || values.isEmpty()) { + return null; + } + List sorted = new ArrayList(values); + Collections.sort(sorted, Comparator.naturalOrder()); + int middleIndex = sorted.size() / 2; + if (sorted.size() % 2 == 1) { + return sorted.get(middleIndex); + } + return sorted.get(middleIndex - 1).add(sorted.get(middleIndex)).divide(new BigDecimal("2")); + } + + private int firstEvenOrder(int startOrder) { + return startOrder % 2 == 0 ? startOrder : startOrder + 1; + } + + private Map toValueMap(List points) { + Map result = new LinkedHashMap(); + if (points == null || points.isEmpty()) { + return result; + } + for (SteadyChecksquareValuePointBO point : points) { + if (point != null && point.getTime() != null && point.getValue() != null) { + result.put(point.getTime(), point.getValue()); + } + } + return result; + } + + private SteadyTrendResolvedFieldBO buildResolvedField(String lineId, SteadyTrendIndicatorDefinitionBO indicator, + Integer harmonicOrder, String phase, String statType) { + SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO(); + field.setMeasurement(indicator.getTableName()); + field.setField(indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder); + field.setLineId(lineId); + field.setIndicatorCode(indicator.getIndicatorCode()); + field.setIndicatorName(indicator.getName()); + field.setPhase(phase); + field.setStatType(statType); + field.setUnit(indicator.getUnit()); + return field; + } +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java index b7a1d20..c3841d1 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java @@ -11,11 +11,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.math.BigDecimal; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URLEncoder; import java.net.URL; @@ -26,7 +26,9 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -39,37 +41,49 @@ public class SteadyChecksquareInfluxQueryComponent { private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ThreadLocal>> REQUEST_VALUE_CACHE = + new ThreadLocal>>(); private final SteadyInfluxDbProperties properties; + public void enableRequestCache() { + REQUEST_VALUE_CACHE.set(new LinkedHashMap>()); + } + + public void clearRequestCache() { + REQUEST_VALUE_CACHE.remove(); + } + public Set queryExistingSlots(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime, int intervalMinutes) { - validateConfig(); - String query = buildChecksquareQuery(field, startTime, endTime); - long startMillis = System.currentTimeMillis(); - log.info("数据校验 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}", - field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query); - try { - String body = executeQuery(query); - Set slots = parseExistingSlots(body, intervalMinutes); - log.info("数据校验 InfluxDB 查询结束,slotCount={},costMs={}", slots.size(), System.currentTimeMillis() - startMillis); - return slots; - } catch (RuntimeException ex) { - log.warn("数据校验 InfluxDB 查询异常,costMs={},error={}", System.currentTimeMillis() - startMillis, ex.getMessage()); - throw ex; + List points = queryValuePoints(field, startTime, endTime, intervalMinutes); + Set result = new HashSet(); + for (SteadyChecksquareValuePointBO point : points) { + if (point != null && point.getTime() != null) { + result.add(point.getTime()); + } } + return result; } public List queryValuePoints(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime, int intervalMinutes) { validateConfig(); String query = buildValuePointQuery(field, startTime, endTime); + String cacheKey = buildCacheKey(query, intervalMinutes); + Map> cache = REQUEST_VALUE_CACHE.get(); + if (cache != null && cache.containsKey(cacheKey)) { + return new ArrayList(cache.get(cacheKey)); + } long startMillis = System.currentTimeMillis(); log.info("数据校验指标值 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}", field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query); try { String body = executeQuery(query); List points = parseValuePoints(body, intervalMinutes); + if (cache != null) { + cache.put(cacheKey, new ArrayList(points)); + } log.info("数据校验指标值 InfluxDB 查询结束,pointCount={},costMs={}", points.size(), System.currentTimeMillis() - startMillis); return points; } catch (RuntimeException ex) { @@ -78,10 +92,69 @@ public class SteadyChecksquareInfluxQueryComponent { } } + public Map> queryValuePointMap(List fields, + LocalDateTime startTime, + LocalDateTime endTime, + int intervalMinutes) { + Map> result = + new LinkedHashMap>(); + if (fields == null || fields.isEmpty()) { + return result; + } + if (fields.size() == 1) { + SteadyTrendResolvedFieldBO field = fields.get(0); + result.put(field.getField(), queryValuePoints(field, startTime, endTime, intervalMinutes)); + return result; + } + validateConfig(); + Map> cache = REQUEST_VALUE_CACHE.get(); + List missingFields = new ArrayList(); + for (SteadyTrendResolvedFieldBO field : fields) { + String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes); + if (cache != null && cache.containsKey(cacheKey)) { + result.put(field.getField(), new ArrayList(cache.get(cacheKey))); + } else { + missingFields.add(field); + } + } + if (!missingFields.isEmpty()) { + String query = buildBatchValuePointQuery(missingFields, startTime, endTime); + long startMillis = System.currentTimeMillis(); + SteadyTrendResolvedFieldBO first = missingFields.get(0); + log.info("数据校验指标值 InfluxDB 批量查询开始,measurement={},fieldCount={},lineId={},phase={},statType={},query={}", + first.getMeasurement(), missingFields.size(), first.getLineId(), first.getPhase(), first.getStatType(), query); + try { + Map> queried = parseBatchValuePoints(executeQuery(query), intervalMinutes); + for (SteadyTrendResolvedFieldBO field : missingFields) { + List points = queried.get(field.getField()); + if (points == null) { + points = new ArrayList(); + } + result.put(field.getField(), points); + if (cache != null) { + String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes); + cache.put(cacheKey, new ArrayList(points)); + } + } + log.info("数据校验指标值 InfluxDB 批量查询结束,fieldCount={},costMs={}", + missingFields.size(), System.currentTimeMillis() - startMillis); + } catch (RuntimeException ex) { + log.warn("数据校验指标值 InfluxDB 批量查询异常,fieldCount={},costMs={},error={}", + missingFields.size(), System.currentTimeMillis() - startMillis, ex.getMessage()); + throw ex; + } + } + return result; + } + public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) { return buildValuePointQuery(field, startTime, endTime); } + private String buildCacheKey(String query, int intervalMinutes) { + return query + "|intervalMinutes=" + intervalMinutes; + } + public String buildValuePointQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) { StringBuilder sql = new StringBuilder(); sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\""); @@ -97,27 +170,26 @@ public class SteadyChecksquareInfluxQueryComponent { return sql.toString(); } - private Set parseExistingSlots(String body, int intervalMinutes) { - try { - JsonNode root = OBJECT_MAPPER.readTree(body); - JsonNode values = root.path("results").path(0).path("series").path(0).path("values"); - Set result = new HashSet(); - if (!values.isArray()) { - return result; + public String buildBatchValuePointQuery(List fields, LocalDateTime startTime, LocalDateTime endTime) { + SteadyTrendResolvedFieldBO first = fields.get(0); + StringBuilder sql = new StringBuilder("SELECT "); + for (int i = 0; i < fields.size(); i++) { + SteadyTrendResolvedFieldBO field = fields.get(i); + if (i > 0) { + sql.append(", "); } - for (JsonNode value : values) { - if (value.size() < 2 || value.get(1).isNull()) { - continue; - } - LocalDateTime time = parseInfluxTime(value.get(0).asText()); - if (time != null) { - result.add(alignToPreviousSlot(time, intervalMinutes)); - } - } - return result; - } catch (IOException ex) { - throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage()); + sql.append("\"").append(field.getField()).append("\" AS \"").append(field.getField()).append("\""); } + sql.append(" FROM \"").append(first.getMeasurement()).append("\""); + sql.append(" WHERE time >= '").append(INFLUX_TIME_FORMATTER.format(startTime)).append("'"); + sql.append(" AND time <= '").append(INFLUX_TIME_FORMATTER.format(endTime)).append("'"); + sql.append(" AND \"line_id\" = '").append(escapeTagValue(first.getLineId())).append("'"); + sql.append(" AND \"phasic_type\" = '").append(escapeTagValue(first.getPhase())).append("'"); + if (hasValueTypeTag(first.getMeasurement())) { + sql.append(" AND \"value_type\" = '").append(resolveValueType(first.getStatType())).append("'"); + } + sql.append(" ORDER BY time ASC"); + return sql.toString(); } private List parseValuePoints(String body, int intervalMinutes) { @@ -149,6 +221,51 @@ public class SteadyChecksquareInfluxQueryComponent { } } + private Map> parseBatchValuePoints(String body, int intervalMinutes) { + try { + JsonNode root = OBJECT_MAPPER.readTree(body); + JsonNode series = root.path("results").path(0).path("series").path(0); + JsonNode columns = series.path("columns"); + JsonNode values = series.path("values"); + Map columnMap = new LinkedHashMap(); + Map> result = + new LinkedHashMap>(); + if (!columns.isArray() || !values.isArray()) { + return result; + } + for (int i = 1; i < columns.size(); i++) { + String fieldName = columns.get(i).asText(); + columnMap.put(i, fieldName); + result.put(fieldName, new ArrayList()); + } + for (JsonNode row : values) { + if (row.size() < 2) { + continue; + } + LocalDateTime time = parseInfluxTime(row.get(0).asText()); + if (time == null) { + continue; + } + LocalDateTime slot = alignToPreviousSlot(time, intervalMinutes); + for (Map.Entry entry : columnMap.entrySet()) { + JsonNode value = row.get(entry.getKey()); + if (value == null || value.isNull()) { + continue; + } + SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO(); + point.setTime(slot); + point.setValue(new BigDecimal(value.asText())); + result.get(entry.getValue()).add(point); + } + } + return result; + } catch (IOException ex) { + throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage()); + } catch (NumberFormatException ex) { + throw fail("InfluxDB 返回指标值格式不正确:" + ex.getMessage()); + } + } + private LocalDateTime alignToPreviousSlot(LocalDateTime time, int intervalMinutes) { LocalDateTime minuteFloor = time.withSecond(0).withNano(0); int minuteOfDay = minuteFloor.getHour() * 60 + minuteFloor.getMinute(); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java index a70ccd6..1555a59 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java @@ -41,7 +41,7 @@ public class SteadyChecksquareValueOrderRuleComponent { for (String phase : indicator.getPhaseCodes()) { Map> statValueMap = queryStatValueMap(lineId, indicator, harmonicOrder, phase, startTime, endTime, intervalMinutes); - appendAbnormalDetails(result, phase, statValueMap); + appendAbnormalDetails(result, phase, harmonicOrder, statValueMap); } result.setAbnormalPointCount(result.getAbnormalDetails().size()); result.setAbnormal(result.getAbnormalPointCount() > ABNORMAL_THRESHOLD); @@ -65,8 +65,8 @@ public class SteadyChecksquareValueOrderRuleComponent { return result; } - private void appendAbnormalDetails(SteadyChecksquareValueOrderRuleVO result, String phase, - Map> statValueMap) { + private void appendAbnormalDetails(SteadyChecksquareValueOrderRuleVO result, String phase, Integer harmonicOrder, + Map> statValueMap) { Map maxValues = statValueMap.get("MAX"); Map cp95Values = statValueMap.get("CP95"); Map avgValues = statValueMap.get("AVG"); @@ -84,18 +84,20 @@ public class SteadyChecksquareValueOrderRuleComponent { if (maxValue == null || cp95Value == null || avgValue == null || minValue == null) { continue; } - if (maxValue.compareTo(cp95Value) > 0 && cp95Value.compareTo(avgValue) > 0 && avgValue.compareTo(minValue) > 0) { + if (maxValue.compareTo(cp95Value) >= 0 && cp95Value.compareTo(avgValue) >= 0 && avgValue.compareTo(minValue) >= 0) { continue; } - result.getAbnormalDetails().add(buildDetail(time, phase, maxValue, minValue, avgValue, cp95Value)); + result.getAbnormalDetails().add(buildDetail(time, phase, harmonicOrder, maxValue, minValue, avgValue, cp95Value)); } } - private SteadyChecksquareValueOrderDetailVO buildDetail(LocalDateTime time, String phase, BigDecimal maxValue, - BigDecimal minValue, BigDecimal avgValue, BigDecimal cp95Value) { + private SteadyChecksquareValueOrderDetailVO buildDetail(LocalDateTime time, String phase, Integer harmonicOrder, + BigDecimal maxValue, BigDecimal minValue, + BigDecimal avgValue, BigDecimal cp95Value) { SteadyChecksquareValueOrderDetailVO detail = new SteadyChecksquareValueOrderDetailVO(); detail.setTime(OUTPUT_TIME_FORMATTER.format(time)); detail.setPhase(phase); + detail.setHarmonicOrder(harmonicOrder); detail.setMaxValue(maxValue); detail.setMinValue(minValue); detail.setAvgValue(avgValue); 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 f3d35f5..c0bfa9a 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 @@ -1,12 +1,18 @@ package com.njcn.gather.steady.checksquare.controller; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; 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.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; +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 com.njcn.gather.steady.checksquare.service.SteadyChecksquareService; import com.njcn.web.controller.BaseController; import com.njcn.web.utils.HttpResultUtil; @@ -14,9 +20,12 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -32,12 +41,43 @@ public class SteadyChecksquareController extends BaseController { private final SteadyChecksquareService checksquareService; @OperateInfo(info = LogEnum.BUSINESS_COMMON) - @ApiOperation("查询数据校验结果") + @ApiOperation("查询数据校验历史记录") @PostMapping("/query") - public HttpResult query(@RequestBody SteadyChecksquareQueryParam param) { + public HttpResult> query(@RequestBody @Validated SteadyChecksquareHistoryQueryParam param) { String methodDescribe = getMethodDescribe("query"); - LogUtil.njcnDebug(log, "{},开始查询数据校验结果,param={}", methodDescribe, param); - SteadyChecksquareQueryVO result = checksquareService.query(param); + LogUtil.njcnDebug(log, "{},开始查询数据校验历史记录,param={}", methodDescribe, param); + Page result = checksquareService.query(param); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD) + @ApiOperation("新增数据校验记录") + @PostMapping("/create") + public HttpResult create(@RequestBody @Validated SteadyChecksquareQueryParam param) { + String methodDescribe = getMethodDescribe("create"); + LogUtil.njcnDebug(log, "{},开始新增数据校验记录,param={}", methodDescribe, param); + SteadyChecksquareCreateVO result = checksquareService.create(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询数据校验任务详情") + @GetMapping("/detail") + public HttpResult detail(@RequestParam("taskId") String taskId) { + String methodDescribe = getMethodDescribe("detail"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, checksquareService.detail(taskId), methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询数据校验检测项明细") + @GetMapping("/item-detail") + public HttpResult itemDetail(@RequestParam("itemId") String itemId, + @RequestParam("detailType") String detailType, + @RequestParam(value = "statType", required = false) String statType, + @RequestParam(value = "pageNum", required = false) Integer pageNum, + @RequestParam(value = "pageSize", required = false) Integer pageSize) { + String methodDescribe = getMethodDescribe("itemDetail"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, + checksquareService.itemDetail(itemId, detailType, statType, pageNum, pageSize), methodDescribe); + } } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java new file mode 100644 index 0000000..cc428ae --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; + +/** + * 数据校验明细 Mapper。 + */ +public interface SteadyChecksquareDetailMapper extends BaseMapper { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java new file mode 100644 index 0000000..f37fb1c --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; + +/** + * 数据校验检测项 Mapper。 + */ +public interface SteadyChecksquareItemMapper extends BaseMapper { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java new file mode 100644 index 0000000..5a01345 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; + +/** + * 数据校验统计摘要 Mapper。 + */ +public interface SteadyChecksquareStatSummaryMapper extends BaseMapper { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java new file mode 100644 index 0000000..53c6007 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; + +/** + * 数据校验任务 Mapper。 + */ +public interface SteadyChecksquareTaskMapper extends BaseMapper { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java new file mode 100644 index 0000000..a588aa0 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java @@ -0,0 +1,18 @@ +package com.njcn.gather.steady.checksquare.pojo.constant; + +/** + * 数据校验常量。 + */ +public final class SteadyChecksquareConst { + + public static final int STATE_DELETED = 0; + public static final int STATE_ENABLED = 1; + + public static final String TASK_STATUS_SUCCESS = "SUCCESS"; + public static final String DETAIL_TYPE_SEGMENT = "SEGMENT"; + public static final String DETAIL_TYPE_VALUE_ORDER = "VALUE_ORDER"; + public static final String DETAIL_TYPE_HARMONIC_PARITY = "HARMONIC_PARITY"; + + private SteadyChecksquareConst() { + } +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java new file mode 100644 index 0000000..4a21acd --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java @@ -0,0 +1,31 @@ +package com.njcn.gather.steady.checksquare.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据校验历史查询参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("数据校验历史查询参数") +public class SteadyChecksquareHistoryQueryParam extends BaseParam { + + @ApiModelProperty("监测点 ID") + private String lineId; + + @ApiModelProperty("指标编码") + private String indicatorCode; + + @ApiModelProperty("检测开始时间,格式 yyyy-MM-dd HH:mm:ss") + private String timeStart; + + @ApiModelProperty("检测结束时间,格式 yyyy-MM-dd HH:mm:ss") + private String timeEnd; + + @ApiModelProperty("是否存在异常") + private Boolean hasAbnormal; +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java index 680bd04..611ac57 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java @@ -8,10 +8,10 @@ import java.io.Serializable; import java.util.List; /** - * 数据校验查询参数。 + * 数据校验新增检测参数。 */ @Data -@ApiModel("数据校验查询参数") +@ApiModel("数据校验新增检测参数") public class SteadyChecksquareQueryParam implements Serializable { private static final long serialVersionUID = 1L; @@ -27,7 +27,4 @@ public class SteadyChecksquareQueryParam implements Serializable { @ApiModelProperty("结束时间,格式 yyyy-MM-dd HH:mm:ss") private String timeEnd; - - @ApiModelProperty("谐波次数,谐波指标按请求次数查询") - private List harmonicOrders; } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java new file mode 100644 index 0000000..1be1729 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java @@ -0,0 +1,67 @@ +package com.njcn.gather.steady.checksquare.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.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 数据校验明细。 + */ +@Data +@TableName("steady_checksquare_detail") +public class SteadyChecksquareDetailPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + private String id; + @TableField("item_id") + private String itemId; + @TableField("detail_type") + private String detailType; + @TableField("stat_type") + private String statType; + @TableField("start_time") + private LocalDateTime startTime; + @TableField("end_time") + private LocalDateTime endTime; + @TableField("point_time") + private LocalDateTime pointTime; + @TableField("segment_status") + private String segmentStatus; + @TableField("missing_point_count") + private Integer missingPointCount; + @TableField("duration_minutes") + private Integer durationMinutes; + @TableField("phase") + private String phase; + @TableField("harmonic_order") + private Integer harmonicOrder; + @TableField("max_value") + private BigDecimal maxValue; + @TableField("min_value") + private BigDecimal minValue; + @TableField("avg_value") + private BigDecimal avgValue; + @TableField("cp95_value") + private BigDecimal cp95Value; + @TableField("even_harmonic_order") + private Integer evenHarmonicOrder; + @TableField("even_value") + private BigDecimal evenValue; + @TableField("odd_harmonic_orders_json") + private String oddHarmonicOrdersJson; + @TableField("odd_values_json") + private String oddValuesJson; + @TableField("odd_median_value") + private BigDecimal oddMedianValue; + @TableField("threshold_multiplier") + private BigDecimal thresholdMultiplier; + @TableField("create_time") + private LocalDateTime createTime; +} 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 new file mode 100644 index 0000000..6a56ea7 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java @@ -0,0 +1,63 @@ +package com.njcn.gather.steady.checksquare.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.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 数据校验检测项。 + */ +@Data +@TableName("steady_checksquare_item") +public class SteadyChecksquareItemPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + private String id; + @TableField("task_id") + private String taskId; + @TableField("item_key") + private String itemKey; + @TableField("indicator_code") + private String indicatorCode; + @TableField("indicator_name") + private String indicatorName; + @TableField("harmonic_order") + private Integer harmonicOrder; + @TableField("interval_minutes") + private Integer intervalMinutes; + @TableField("has_data") + private Integer hasData; + @TableField("expected_point_count") + private Integer expectedPointCount; + @TableField("actual_point_count") + 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("abnormal") + private Integer abnormal; + @TableField("abnormal_point_count") + private Integer abnormalPointCount; + @TableField("harmonic_parity_abnormal") + private Integer harmonicParityAbnormal; + @TableField("harmonic_parity_abnormal_point_count") + private Integer harmonicParityAbnormalPointCount; + @TableField("state") + private Integer state; + @TableField("create_time") + private LocalDateTime createTime; + @TableField("update_time") + private LocalDateTime updateTime; +} 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 new file mode 100644 index 0000000..5f4697c --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java @@ -0,0 +1,45 @@ +package com.njcn.gather.steady.checksquare.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.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 数据校验统计摘要。 + */ +@Data +@TableName("steady_checksquare_stat_summary") +public class SteadyChecksquareStatSummaryPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + private String id; + @TableField("item_id") + private String itemId; + @TableField("stat_type") + private String statType; + @TableField("supported") + private Integer supported; + @TableField("has_data") + private Integer hasData; + @TableField("expected_point_count") + private Integer expectedPointCount; + @TableField("actual_point_count") + 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("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 new file mode 100644 index 0000000..bcc75b2 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java @@ -0,0 +1,59 @@ +package com.njcn.gather.steady.checksquare.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.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 数据校验任务。 + */ +@Data +@TableName("steady_checksquare_task") +public class SteadyChecksquareTaskPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + private String id; + @TableField("task_no") + private String taskNo; + @TableField("line_id") + private String lineId; + @TableField("line_name") + private String lineName; + @TableField("time_start") + private LocalDateTime timeStart; + @TableField("time_end") + private LocalDateTime timeEnd; + @TableField("interval_minutes") + private Integer intervalMinutes; + @TableField("indicator_codes_json") + private String indicatorCodesJson; + @TableField("indicator_codes_text") + private String indicatorCodesText; + @TableField("task_status") + private String taskStatus; + @TableField("item_count") + private Integer itemCount; + @TableField("abnormal_item_count") + private Integer abnormalItemCount; + @TableField("max_missing_rate") + private BigDecimal maxMissingRate; + @TableField("result_message") + private String resultMessage; + @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; +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java new file mode 100644 index 0000000..a974484 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java @@ -0,0 +1,44 @@ +package com.njcn.gather.steady.checksquare.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 新增数据校验结果。 + */ +@Data +@ApiModel("新增数据校验结果") +public class SteadyChecksquareCreateVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("任务 ID") + private String taskId; + + @ApiModelProperty("任务编号") + private String taskNo; + + @ApiModelProperty("监测点 ID") + private String lineId; + + @ApiModelProperty("监测点名称") + private String lineName; + + @ApiModelProperty("开始时间") + private String timeStart; + + @ApiModelProperty("结束时间") + private String timeEnd; + + @ApiModelProperty("统计间隔,单位分钟") + private Integer intervalMinutes; + + @ApiModelProperty("检测项数量") + private Integer itemCount; + + @ApiModelProperty("异常检测项数量") + private Integer abnormalItemCount; +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java new file mode 100644 index 0000000..0e16a06 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java @@ -0,0 +1,47 @@ +package com.njcn.gather.steady.checksquare.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * 谐波奇偶关系异常明细。 + */ +@Data +@ApiModel("谐波奇偶关系异常明细") +public class SteadyChecksquareHarmonicParityDetailVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("时间") + private String time; + + @ApiModelProperty("相别") + private String phase; + + @ApiModelProperty("统计类型") + private String statType; + + @ApiModelProperty("偶次谐波次数") + private Integer evenHarmonicOrder; + + @ApiModelProperty("偶次谐波值") + private BigDecimal evenValue; + + @ApiModelProperty("参与比较的奇次谐波次数") + private List oddHarmonicOrders = new ArrayList(); + + @ApiModelProperty("参与比较的奇次谐波值") + private List oddValues = new ArrayList(); + + @ApiModelProperty("奇次谐波中位数") + private BigDecimal oddMedianValue; + + @ApiModelProperty("异常阈值倍数") + private BigDecimal thresholdMultiplier; +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java new file mode 100644 index 0000000..cef4a62 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java @@ -0,0 +1,23 @@ +package com.njcn.gather.steady.checksquare.pojo.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 谐波奇偶关系规则结果。 + */ +@Data +public class SteadyChecksquareHarmonicParityRuleVO implements Serializable { + + private static final long serialVersionUID = 1L; + + private Boolean abnormal = false; + + private Integer abnormalPointCount = 0; + + private List abnormalDetails = + new ArrayList(); +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java new file mode 100644 index 0000000..ee86da2 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java @@ -0,0 +1,48 @@ +package com.njcn.gather.steady.checksquare.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 数据校验检测项明细。 + */ +@Data +@ApiModel("数据校验检测项明细") +public class SteadyChecksquareItemDetailVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("检测项 ID") + private String itemId; + + @ApiModelProperty("明细类型") + private String detailType; + + @ApiModelProperty("统计类型") + private String statType; + + @ApiModelProperty("当前页码;未分页查询时为空") + private Integer pageNum; + + @ApiModelProperty("每页条数;未分页查询时为空") + private Integer pageSize; + + @ApiModelProperty("总记录数;未分页查询时为空") + private Long total; + + @ApiModelProperty("缺失区间") + private List segments = new ArrayList(); + + @ApiModelProperty("大小关系异常明细") + private List valueOrderDetails = + new ArrayList(); + + @ApiModelProperty("谐波奇偶关系异常明细") + private List harmonicParityDetails = + new ArrayList(); +} 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 1108794..400e37a 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 @@ -18,6 +18,9 @@ public class SteadyChecksquareItemVO implements Serializable { private static final long serialVersionUID = 1L; + @ApiModelProperty("检测项 ID") + private String itemId; + @ApiModelProperty("校验项唯一键") private String itemKey; @@ -63,6 +66,16 @@ public class SteadyChecksquareItemVO implements Serializable { @ApiModelProperty("指标值大小关系异常明细") private List abnormalDetails = new ArrayList(); + @ApiModelProperty("谐波奇偶关系是否异常") + private Boolean harmonicParityAbnormal; + + @ApiModelProperty("谐波奇偶关系异常累计值") + private Integer harmonicParityAbnormalPointCount; + + @ApiModelProperty("谐波奇偶关系异常明细") + private List harmonicParityAbnormalDetails = + new ArrayList(); + @ApiModelProperty("统计类型摘要") private List statSummaries = new ArrayList(); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java index 7ba697b..b6cac48 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java @@ -17,6 +17,12 @@ public class SteadyChecksquareQueryVO implements Serializable { private static final long serialVersionUID = 1L; + @ApiModelProperty("任务 ID") + private String taskId; + + @ApiModelProperty("任务编号") + private String taskNo; + @ApiModelProperty("监测点 ID") private String lineId; diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java index b455860..4b80e06 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java @@ -24,6 +24,9 @@ public class SteadyChecksquareSegmentVO implements Serializable { @ApiModelProperty("状态,NORMAL/MISSING") private String status; + @ApiModelProperty("谐波次数") + private Integer harmonicOrder; + @ApiModelProperty("缺失点数") private Integer missingPointCount; 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 new file mode 100644 index 0000000..c8904ec --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java @@ -0,0 +1,54 @@ +package com.njcn.gather.steady.checksquare.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 数据校验历史任务。 + */ +@Data +@ApiModel("数据校验历史任务") +public class SteadyChecksquareTaskVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("任务 ID") + private String taskId; + + @ApiModelProperty("任务编号") + private String taskNo; + + @ApiModelProperty("监测点 ID") + private String lineId; + + @ApiModelProperty("监测点名称") + private String lineName; + + @ApiModelProperty("开始时间") + private String timeStart; + + @ApiModelProperty("结束时间") + private String timeEnd; + + @ApiModelProperty("统计间隔,单位分钟") + private Integer intervalMinutes; + + @ApiModelProperty("任务状态") + private String taskStatus; + + @ApiModelProperty("检测项数量") + private Integer itemCount; + + @ApiModelProperty("异常检测项数量") + private Integer abnormalItemCount; + + @ApiModelProperty("最大缺失率") + private BigDecimal maxMissingRate; + + @ApiModelProperty("创建时间") + private String createTime; +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java index 1a807ba..0fdb0e4 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java @@ -22,6 +22,9 @@ public class SteadyChecksquareValueOrderDetailVO implements Serializable { @ApiModelProperty("相别") private String phase; + @ApiModelProperty("谐波次数") + private Integer harmonicOrder; + @ApiModelProperty("最大值") private BigDecimal maxValue; diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java new file mode 100644 index 0000000..6f17e74 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; + +/** + * 数据校验明细服务。 + */ +public interface SteadyChecksquareDetailService extends IService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java new file mode 100644 index 0000000..683a0da --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; + +/** + * 数据校验检测项服务。 + */ +public interface SteadyChecksquareItemService extends IService { +} 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 622af24..f5a34d6 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 @@ -1,12 +1,26 @@ package com.njcn.gather.steady.checksquare.service; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; +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; /** * 数据校验服务。 */ public interface SteadyChecksquareService { - SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param); + Page query(SteadyChecksquareHistoryQueryParam param); + + SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param); + + SteadyChecksquareQueryVO detail(String taskId); + + SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType); + + SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType, + Integer pageNum, Integer pageSize); } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java new file mode 100644 index 0000000..f96906b --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; + +/** + * 数据校验统计摘要服务。 + */ +public interface SteadyChecksquareStatSummaryService extends IService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java new file mode 100644 index 0000000..afe5edb --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java @@ -0,0 +1,10 @@ +package com.njcn.gather.steady.checksquare.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; + +/** + * 数据校验任务服务。 + */ +public interface SteadyChecksquareTaskService extends IService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java new file mode 100644 index 0000000..1f69d89 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java @@ -0,0 +1,15 @@ +package com.njcn.gather.steady.checksquare.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareDetailMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService; +import org.springframework.stereotype.Service; + +/** + * 数据校验明细服务实现。 + */ +@Service +public class SteadyChecksquareDetailServiceImpl extends ServiceImpl + implements SteadyChecksquareDetailService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java new file mode 100644 index 0000000..e29cfe5 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java @@ -0,0 +1,15 @@ +package com.njcn.gather.steady.checksquare.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareItemMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService; +import org.springframework.stereotype.Service; + +/** + * 数据校验检测项服务实现。 + */ +@Service +public class SteadyChecksquareItemServiceImpl extends ServiceImpl + implements SteadyChecksquareItemService { +} 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 a4bceea..c62f42c 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,18 +1,39 @@ package com.njcn.gather.steady.checksquare.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.ObjectMapper; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator; +import com.njcn.gather.steady.checksquare.component.SteadyChecksquareHarmonicParityRuleComponent; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareValueOrderRuleComponent; +import com.njcn.gather.steady.checksquare.pojo.constant.SteadyChecksquareConst; +import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareSegmentVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService; import com.njcn.gather.steady.checksquare.service.SteadyChecksquareService; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService; +import com.njcn.gather.steady.checksquare.util.SteadyChecksquareIdUtil; 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; @@ -21,9 +42,12 @@ import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator; import com.njcn.gather.tool.addledger.pojo.constant.AddLedgerConst; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; import com.njcn.gather.tool.addledger.service.AddLedgerService; +import com.njcn.web.factory.PageFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; import java.math.BigDecimal; import java.math.RoundingMode; @@ -31,12 +55,15 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * 数据校验服务实现。 @@ -50,16 +77,128 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { private static final String EMPTY_TEXT = "-"; private static final int FLICKER_SHORT_INTERVAL_MINUTES = 10; private static final int FLICKER_LONG_INTERVAL_MINUTES = 120; + private static final int HARMONIC_AGGREGATE_ORDER_START = 2; + private static final int HARMONIC_AGGREGATE_ORDER_END = 50; private final SteadyTrendIndicatorCatalog indicatorCatalog; private final SteadyChecksquareInfluxQueryComponent influxQueryComponent; private final SteadyChecksquareCalculator calculator; private final SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent; + private final SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent; private final AddDataTimeSlotCalculator timeSlotCalculator; private final AddLedgerService addLedgerService; + private final SteadyChecksquareTaskService taskService; + private final SteadyChecksquareItemService itemService; + private final SteadyChecksquareStatSummaryService statSummaryService; + private final SteadyChecksquareDetailService detailService; + private final ObjectMapper objectMapper; + + @Autowired(required = false) + private TransactionTemplate transactionTemplate; @Override - public SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param) { + public Page query(SteadyChecksquareHistoryQueryParam param) { + SteadyChecksquareHistoryQueryParam query = param == null ? new SteadyChecksquareHistoryQueryParam() : param; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED) + .eq(trimToNull(query.getLineId()) != null, SteadyChecksquareTaskPO::getLineId, trimToNull(query.getLineId())) + .like(trimToNull(query.getIndicatorCode()) != null, SteadyChecksquareTaskPO::getIndicatorCodesText, "|" + trimToNull(query.getIndicatorCode()) + "|") + .ge(trimToNull(query.getTimeStart()) != null, SteadyChecksquareTaskPO::getTimeStart, parseOptionalTime(query.getTimeStart())) + .le(trimToNull(query.getTimeEnd()) != null, SteadyChecksquareTaskPO::getTimeEnd, parseOptionalTime(query.getTimeEnd())) + .gt(Boolean.TRUE.equals(query.getHasAbnormal()), SteadyChecksquareTaskPO::getAbnormalItemCount, 0) + .orderByDesc(SteadyChecksquareTaskPO::getCreateTime); + Page page = taskService.page(new Page( + PageFactory.getPageNum(query), PageFactory.getPageSize(query)), wrapper); + Page result = new Page(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(page.getRecords().stream().map(this::toTaskVO).collect(Collectors.toList())); + return result; + } + + @Override + public SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param) { + influxQueryComponent.enableRequestCache(); + SteadyChecksquareQueryVO result; + try { + result = calculate(param); + } finally { + influxQueryComponent.clearRequestCache(); + } + SteadyChecksquareTaskPO task = saveResultInTransaction(param, result); + return toCreateVO(task); + } + + @Override + public SteadyChecksquareQueryVO detail(String taskId) { + SteadyChecksquareTaskPO task = requireTask(taskId); + List items = itemService.lambdaQuery() + .eq(SteadyChecksquareItemPO::getTaskId, task.getId()) + .eq(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_ENABLED) + .list(); + SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); + result.setTaskId(task.getId()); + result.setTaskNo(task.getTaskNo()); + result.setLineId(task.getLineId()); + result.setLineName(task.getLineName()); + result.setTimeStart(formatTime(task.getTimeStart())); + result.setTimeEnd(formatTime(task.getTimeEnd())); + result.setIntervalMinutes(task.getIntervalMinutes()); + Map> summaryMap = loadSummaryMap(items); + for (SteadyChecksquareItemPO item : items) { + SteadyChecksquareItemVO itemVO = toItemVO(item); + List summaries = summaryMap.get(item.getId()); + itemVO.setStatSummaries(summaries == null ? new ArrayList() : summaries); + result.getItems().add(itemVO); + } + return result; + } + + @Override + public SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType) { + return itemDetail(itemId, detailType, statType, null, null); + } + + @Override + public SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType, + Integer pageNum, Integer pageSize) { + SteadyChecksquareItemPO item = requireItem(itemId); + String type = trimToNull(detailType); + if (type == null) { + throw fail("明细类型不能为空"); + } + if (!isSupportedDetailType(type)) { + throw fail("明细类型不支持:" + type); + } + SteadyChecksquareItemDetailVO result = new SteadyChecksquareItemDetailVO(); + result.setItemId(item.getId()); + result.setDetailType(type); + result.setStatType(statType); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SteadyChecksquareDetailPO::getItemId, item.getId()) + .eq(SteadyChecksquareDetailPO::getDetailType, type) + .eq(trimToNull(statType) != null, SteadyChecksquareDetailPO::getStatType, trimToNull(statType)) + .orderByAsc(SteadyChecksquareDetailPO::getStartTime) + .orderByAsc(SteadyChecksquareDetailPO::getPointTime); + List details; + if (isPaged(pageNum, pageSize)) { + Page page = detailService.page( + new Page(pageNum, pageSize), wrapper); + result.setPageNum((int) page.getCurrent()); + result.setPageSize((int) page.getSize()); + result.setTotal(page.getTotal()); + details = page.getRecords() == null ? new ArrayList() : page.getRecords(); + } else { + details = detailService.list(wrapper); + if (details == null) { + details = new ArrayList(); + } + } + for (SteadyChecksquareDetailPO detail : details) { + fillItemDetail(result, detail); + } + return result; + } + + private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) { validateParam(param); String lineId = trimToNull(param.getLineId()); LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); @@ -78,34 +217,324 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { long startMillis = System.currentTimeMillis(); List indicatorCodes = normalizeTextList(param.getIndicatorCodes()); - List harmonicOrders = normalizeHarmonicOrders(param.getHarmonicOrders()); - log.info("数据校验查询开始,lineId={},indicatorCount={},timeStart={},timeEnd={},intervalMinutes={}", - lineId, indicatorCodes.size(), startTime, endTime, intervalMinutes); + List indicators = new ArrayList(); for (String indicatorCode : indicatorCodes) { - SteadyTrendIndicatorDefinitionBO indicator = requireIndicator(indicatorCode); + indicators.add(requireIndicator(indicatorCode)); + } + log.info("数据校验新增检测开始,lineId={},indicatorCount={},timeStart={},timeEnd={},intervalMinutes={}", + lineId, indicatorCodes.size(), startTime, endTime, intervalMinutes); + prefetchNormalIndicatorPoints(lineId, indicators, startTime, endTime, intervalMinutes); + for (SteadyTrendIndicatorDefinitionBO indicator : indicators) { int itemIntervalMinutes = resolveIndicatorIntervalMinutes(indicator, intervalMinutes); List itemSlots = timeSlotCalculator.buildTimeSlots(startTime, endTime, itemIntervalMinutes); - result.getItems().addAll(buildIndicatorItems(lineId, indicator, harmonicOrders, startTime, endTime, itemSlots, itemIntervalMinutes)); + result.getItems().addAll(buildIndicatorItems(lineId, indicator, startTime, endTime, itemSlots, itemIntervalMinutes)); } - log.info("数据校验查询结束,lineId={},itemCount={},costMs={}", lineId, result.getItems().size(), System.currentTimeMillis() - startMillis); + log.info("数据校验新增检测结束,lineId={},itemCount={},costMs={}", lineId, result.getItems().size(), System.currentTimeMillis() - startMillis); return result; } + private SteadyChecksquareTaskPO saveResultInTransaction(SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { + if (transactionTemplate == null) { + return saveResult(param, result); + } + return transactionTemplate.execute(status -> saveResult(param, result)); + } + + private SteadyChecksquareTaskPO saveResult(SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { + LocalDateTime now = LocalDateTime.now(); + SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); + task.setId(SteadyChecksquareIdUtil.uuid()); + task.setTaskNo(SteadyChecksquareIdUtil.taskNo()); + task.setLineId(result.getLineId()); + task.setLineName(result.getLineName()); + task.setTimeStart(parseRequiredTime(result.getTimeStart(), "开始时间不能为空")); + task.setTimeEnd(parseRequiredTime(result.getTimeEnd(), "结束时间不能为空")); + task.setIntervalMinutes(result.getIntervalMinutes()); + List indicatorCodes = normalizeTextList(param.getIndicatorCodes()); + task.setIndicatorCodesJson(writeJson(indicatorCodes)); + task.setIndicatorCodesText(buildIndicatorCodesText(indicatorCodes)); + task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_SUCCESS); + task.setItemCount(result.getItems().size()); + task.setAbnormalItemCount(countAbnormalItems(result.getItems())); + task.setMaxMissingRate(maxMissingRate(result.getItems())); + task.setResultMessage("数据校验完成"); + task.setState(SteadyChecksquareConst.STATE_ENABLED); + task.setCreateTime(now); + task.setUpdateTime(now); + taskService.save(task); + + List itemPOs = new ArrayList(); + List summaryPOs = new ArrayList(); + List detailPOs = new ArrayList(); + for (SteadyChecksquareItemVO item : result.getItems()) { + SteadyChecksquareItemPO itemPO = buildItemPO(task.getId(), item, now); + item.setItemId(itemPO.getId()); + itemPOs.add(itemPO); + summaryPOs.addAll(buildSummaryPOs(itemPO.getId(), item, now)); + detailPOs.addAll(buildDetailPOs(itemPO.getId(), item, now)); + } + if (!itemPOs.isEmpty()) { + itemService.saveBatch(itemPOs); + } + if (!summaryPOs.isEmpty()) { + statSummaryService.saveBatch(summaryPOs); + } + if (!detailPOs.isEmpty()) { + detailService.saveBatch(detailPOs); + } + return task; + } + + private SteadyChecksquareItemPO buildItemPO(String taskId, SteadyChecksquareItemVO item, LocalDateTime now) { + SteadyChecksquareItemPO po = new SteadyChecksquareItemPO(); + po.setId(SteadyChecksquareIdUtil.uuid()); + po.setTaskId(taskId); + po.setItemKey(item.getItemKey()); + po.setIndicatorCode(item.getIndicatorCode()); + po.setIndicatorName(item.getIndicatorName()); + po.setHarmonicOrder(item.getHarmonicOrder()); + po.setIntervalMinutes(item.getIntervalMinutes()); + po.setHasData(toFlag(item.getHasData())); + 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.setAbnormal(toFlag(item.getAbnormal())); + po.setAbnormalPointCount(nullToZero(item.getAbnormalPointCount())); + po.setHarmonicParityAbnormal(toFlag(item.getHarmonicParityAbnormal())); + po.setHarmonicParityAbnormalPointCount(nullToZero(item.getHarmonicParityAbnormalPointCount())); + po.setState(SteadyChecksquareConst.STATE_ENABLED); + po.setCreateTime(now); + po.setUpdateTime(now); + return po; + } + + private List buildSummaryPOs(String itemId, SteadyChecksquareItemVO item, LocalDateTime now) { + List result = new ArrayList(); + for (SteadyChecksquareStatSummaryVO summary : item.getStatSummaries()) { + SteadyChecksquareStatSummaryPO po = new SteadyChecksquareStatSummaryPO(); + po.setId(SteadyChecksquareIdUtil.uuid()); + po.setItemId(itemId); + po.setStatType(summary.getStatType()); + po.setSupported(toFlag(summary.getSupported())); + po.setHasData(toFlag(summary.getHasData())); + 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.setCreateTime(now); + result.add(po); + } + return result; + } + + private List buildDetailPOs(String itemId, SteadyChecksquareItemVO item, LocalDateTime now) { + List result = new ArrayList(); + for (SteadyChecksquareStatDetailVO detail : item.getStatDetails()) { + for (SteadyChecksquareSegmentVO segment : detail.getSegments()) { + SteadyChecksquareDetailPO po = new SteadyChecksquareDetailPO(); + po.setId(SteadyChecksquareIdUtil.uuid()); + po.setItemId(itemId); + po.setDetailType(SteadyChecksquareConst.DETAIL_TYPE_SEGMENT); + po.setStatType(detail.getStatType()); + po.setHarmonicOrder(segment.getHarmonicOrder()); + po.setStartTime(parseOptionalTime(segment.getStartTime())); + po.setEndTime(parseOptionalTime(segment.getEndTime())); + po.setSegmentStatus(segment.getStatus()); + po.setMissingPointCount(segment.getMissingPointCount()); + po.setDurationMinutes(segment.getDurationMinutes()); + po.setCreateTime(now); + result.add(po); + } + } + for (SteadyChecksquareValueOrderDetailVO detail : item.getAbnormalDetails()) { + SteadyChecksquareDetailPO po = new SteadyChecksquareDetailPO(); + po.setId(SteadyChecksquareIdUtil.uuid()); + po.setItemId(itemId); + po.setDetailType(SteadyChecksquareConst.DETAIL_TYPE_VALUE_ORDER); + po.setPointTime(parseOptionalTime(detail.getTime())); + po.setPhase(detail.getPhase()); + po.setHarmonicOrder(detail.getHarmonicOrder()); + po.setMaxValue(detail.getMaxValue()); + po.setMinValue(detail.getMinValue()); + po.setAvgValue(detail.getAvgValue()); + po.setCp95Value(detail.getCp95Value()); + po.setCreateTime(now); + result.add(po); + } + for (SteadyChecksquareHarmonicParityDetailVO detail : item.getHarmonicParityAbnormalDetails()) { + SteadyChecksquareDetailPO po = new SteadyChecksquareDetailPO(); + po.setId(SteadyChecksquareIdUtil.uuid()); + po.setItemId(itemId); + po.setDetailType(SteadyChecksquareConst.DETAIL_TYPE_HARMONIC_PARITY); + po.setPointTime(parseOptionalTime(detail.getTime())); + po.setPhase(detail.getPhase()); + po.setStatType(detail.getStatType()); + po.setEvenHarmonicOrder(detail.getEvenHarmonicOrder()); + po.setEvenValue(detail.getEvenValue()); + po.setOddHarmonicOrdersJson(writeJson(detail.getOddHarmonicOrders())); + po.setOddValuesJson(writeJson(detail.getOddValues())); + po.setOddMedianValue(detail.getOddMedianValue()); + po.setThresholdMultiplier(detail.getThresholdMultiplier()); + po.setCreateTime(now); + result.add(po); + } + return result; + } + + private void prefetchNormalIndicatorPoints(String lineId, List indicators, + LocalDateTime startTime, LocalDateTime endTime, int lineIntervalMinutes) { + Map> fieldMap = + new LinkedHashMap>(); + Map intervalMap = new LinkedHashMap(); + for (SteadyTrendIndicatorDefinitionBO indicator : indicators) { + if (Boolean.TRUE.equals(indicator.getHarmonic())) { + continue; + } + int intervalMinutes = resolveIndicatorIntervalMinutes(indicator, lineIntervalMinutes); + for (String statType : indicator.getSupportStats()) { + for (String phase : indicator.getPhaseCodes()) { + SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, null, phase, statType); + String key = buildPrefetchKey(field, intervalMinutes); + List fields = fieldMap.get(key); + if (fields == null) { + fields = new ArrayList(); + fieldMap.put(key, fields); + intervalMap.put(key, intervalMinutes); + } + fields.add(field); + } + } + } + for (Map.Entry> entry : fieldMap.entrySet()) { + // 预取只依赖请求级缓存;后续缺数和规则校验复用同一批 Influx 结果。 + influxQueryComponent.queryValuePointMap(entry.getValue(), startTime, endTime, intervalMap.get(entry.getKey())); + } + } + + private String buildPrefetchKey(SteadyTrendResolvedFieldBO field, int intervalMinutes) { + return field.getMeasurement() + "|" + field.getLineId() + "|" + field.getPhase() + "|" + + field.getStatType() + "|" + intervalMinutes; + } + private List buildIndicatorItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator, - List harmonicOrders, LocalDateTime startTime, LocalDateTime endTime, List slots, int intervalMinutes) { List result = new ArrayList(); if (Boolean.TRUE.equals(indicator.getHarmonic())) { - for (Integer order : requireValidHarmonicOrders(indicator, harmonicOrders)) { + List harmonicOrders = buildAggregateHarmonicOrders(indicator); + prefetchHarmonicIndicatorPoints(lineId, indicator, harmonicOrders, startTime, endTime, intervalMinutes); + for (Integer order : harmonicOrders) { result.add(buildItem(lineId, indicator, order, startTime, endTime, slots, intervalMinutes)); } - return result; + fillHarmonicParityRuleResult(result, lineId, indicator, startTime, endTime, intervalMinutes); + return Collections.singletonList(aggregateHarmonicItems(lineId, indicator, result, intervalMinutes)); } result.add(buildItem(lineId, indicator, null, startTime, endTime, slots, intervalMinutes)); return result; } + private void prefetchHarmonicIndicatorPoints(String lineId, SteadyTrendIndicatorDefinitionBO indicator, + List harmonicOrders, LocalDateTime startTime, + LocalDateTime endTime, int intervalMinutes) { + if (harmonicOrders == null || harmonicOrders.isEmpty()) { + return; + } + for (String statType : indicator.getSupportStats()) { + for (String phase : indicator.getPhaseCodes()) { + List fields = new ArrayList(); + for (Integer order : harmonicOrders) { + fields.add(buildResolvedField(lineId, indicator, order, phase, statType)); + } + influxQueryComponent.queryValuePointMap(fields, startTime, endTime, intervalMinutes); + } + } + } + + private SteadyChecksquareItemVO aggregateHarmonicItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator, + List orderItems, int intervalMinutes) { + SteadyChecksquareItemVO result = new SteadyChecksquareItemVO(); + result.setItemKey(buildItemKey(lineId, indicator, null)); + result.setIndicatorCode(indicator.getIndicatorCode()); + 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.setAbnormal(anyAbnormal(orderItems)); + result.setAbnormalPointCount(averageAbnormalCount(orderItems, "abnormalPointCount", result.getAbnormal())); + result.setHarmonicParityAbnormal(anyHarmonicParityAbnormal(orderItems)); + result.setHarmonicParityAbnormalPointCount(averageAbnormalCount(orderItems, + "harmonicParityAbnormalPointCount", result.getHarmonicParityAbnormal())); + for (SteadyChecksquareItemVO orderItem : orderItems) { + result.getAbnormalDetails().addAll(orderItem.getAbnormalDetails()); + result.getHarmonicParityAbnormalDetails().addAll(orderItem.getHarmonicParityAbnormalDetails()); + } + result.setStatSummaries(aggregateStatSummaries(orderItems)); + result.setStatDetails(aggregateStatDetails(orderItems)); + return result; + } + + private List aggregateStatSummaries(List orderItems) { + Map> summaryMap = new LinkedHashMap>(); + for (SteadyChecksquareItemVO item : orderItems) { + for (SteadyChecksquareStatSummaryVO summary : item.getStatSummaries()) { + List summaries = summaryMap.get(summary.getStatType()); + if (summaries == null) { + summaries = new ArrayList(); + summaryMap.put(summary.getStatType(), summaries); + } + summaries.add(summary); + } + } + List result = new ArrayList(); + for (Map.Entry> entry : summaryMap.entrySet()) { + 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")); + result.add(summary); + } + return result; + } + + private List aggregateStatDetails(List orderItems) { + Map detailMap = new LinkedHashMap(); + for (SteadyChecksquareItemVO item : orderItems) { + for (SteadyChecksquareStatDetailVO detail : item.getStatDetails()) { + SteadyChecksquareStatDetailVO aggregateDetail = detailMap.get(detail.getStatType()); + if (aggregateDetail == null) { + aggregateDetail = new SteadyChecksquareStatDetailVO(); + aggregateDetail.setStatType(detail.getStatType()); + aggregateDetail.setSupported(true); + detailMap.put(detail.getStatType(), aggregateDetail); + } + if (item.getHarmonicOrder() != null) { + for (SteadyChecksquareSegmentVO segment : detail.getSegments()) { + segment.setHarmonicOrder(item.getHarmonicOrder()); + } + } + aggregateDetail.getSegments().addAll(detail.getSegments()); + } + } + return new ArrayList(detailMap.values()); + } + private SteadyChecksquareItemVO buildItem(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder, LocalDateTime startTime, LocalDateTime endTime, List slots, int intervalMinutes) { @@ -115,6 +544,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { item.setIndicatorName(indicator.getName()); item.setHarmonicOrder(harmonicOrder); item.setIntervalMinutes(intervalMinutes); + item.setHarmonicParityAbnormal(false); + item.setHarmonicParityAbnormalPointCount(0); int totalExpected = 0; int totalActual = 0; @@ -145,6 +576,42 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return item; } + private void fillHarmonicParityRuleResult(List items, String lineId, + SteadyTrendIndicatorDefinitionBO indicator, + LocalDateTime startTime, LocalDateTime endTime, + int intervalMinutes) { + SteadyChecksquareHarmonicParityRuleVO ruleResult = harmonicParityRuleComponent.check(lineId, indicator, + startTime, endTime, intervalMinutes); + if (ruleResult == null || ruleResult.getAbnormalDetails() == null || ruleResult.getAbnormalDetails().isEmpty()) { + for (SteadyChecksquareItemVO item : items) { + item.setHarmonicParityAbnormal(false); + item.setHarmonicParityAbnormalPointCount(0); + } + return; + } + for (SteadyChecksquareItemVO item : items) { + List matchedDetails = + filterHarmonicParityDetails(ruleResult, item.getHarmonicOrder()); + item.setHarmonicParityAbnormal(!matchedDetails.isEmpty()); + item.setHarmonicParityAbnormalPointCount(matchedDetails.size()); + item.setHarmonicParityAbnormalDetails(matchedDetails); + } + } + + private List filterHarmonicParityDetails( + SteadyChecksquareHarmonicParityRuleVO ruleResult, Integer harmonicOrder) { + List result = new ArrayList(); + if (ruleResult == null || ruleResult.getAbnormalDetails() == null || harmonicOrder == null) { + return result; + } + for (SteadyChecksquareHarmonicParityDetailVO detail : ruleResult.getAbnormalDetails()) { + if (detail != null && harmonicOrder.equals(detail.getEvenHarmonicOrder())) { + result.add(detail); + } + } + return result; + } + private void fillValueOrderRuleResult(SteadyChecksquareItemVO item, String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder, LocalDateTime startTime, LocalDateTime endTime, int intervalMinutes) { @@ -260,6 +727,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { } } + private LocalDateTime parseOptionalTime(String time) { + String text = trimToNull(time); + if (text == null) { + return null; + } + return parseRequiredTime(text, "时间不能为空"); + } + private AddLedgerLinePathVO requireLinePath(String lineId) { Map linePathMap = addLedgerService.listLinePathByLineIds(Collections.singletonList(lineId)); AddLedgerLinePathVO linePath = linePathMap.get(lineId); @@ -296,6 +771,324 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return indicator; } + private List buildAggregateHarmonicOrders(SteadyTrendIndicatorDefinitionBO indicator) { + int start = Math.max(HARMONIC_AGGREGATE_ORDER_START, indicator.getHarmonicOrderStart()); + int end = Math.min(HARMONIC_AGGREGATE_ORDER_END, indicator.getHarmonicOrderEnd()); + if (start > end) { + throw fail("谐波次数只能在 " + HARMONIC_AGGREGATE_ORDER_START + " 到 " + HARMONIC_AGGREGATE_ORDER_END + " 之间"); + } + List result = new ArrayList(); + for (int order = start; order <= end; order++) { + result.add(order); + } + return result; + } + + private Map> loadSummaryMap(List items) { + Map> result = + new LinkedHashMap>(); + if (items == null || items.isEmpty()) { + return result; + } + List itemIds = new ArrayList(); + for (SteadyChecksquareItemPO item : items) { + itemIds.add(item.getId()); + result.put(item.getId(), new ArrayList()); + } + List summaries = statSummaryService.lambdaQuery() + .in(SteadyChecksquareStatSummaryPO::getItemId, itemIds) + .list(); + for (SteadyChecksquareStatSummaryPO summary : summaries) { + List itemSummaries = result.get(summary.getItemId()); + if (itemSummaries != null) { + itemSummaries.add(toSummaryVO(summary)); + } + } + return result; + } + + private void fillItemDetail(SteadyChecksquareItemDetailVO result, SteadyChecksquareDetailPO detail) { + if (SteadyChecksquareConst.DETAIL_TYPE_SEGMENT.equals(detail.getDetailType())) { + SteadyChecksquareSegmentVO segment = new SteadyChecksquareSegmentVO(); + segment.setStartTime(formatTime(detail.getStartTime())); + segment.setEndTime(formatTime(detail.getEndTime())); + segment.setStatus(detail.getSegmentStatus()); + segment.setHarmonicOrder(detail.getHarmonicOrder()); + segment.setMissingPointCount(detail.getMissingPointCount()); + segment.setDurationMinutes(detail.getDurationMinutes()); + result.getSegments().add(segment); + return; + } + if (SteadyChecksquareConst.DETAIL_TYPE_VALUE_ORDER.equals(detail.getDetailType())) { + SteadyChecksquareValueOrderDetailVO valueDetail = new SteadyChecksquareValueOrderDetailVO(); + valueDetail.setTime(formatTime(detail.getPointTime())); + valueDetail.setPhase(detail.getPhase()); + valueDetail.setHarmonicOrder(detail.getHarmonicOrder()); + valueDetail.setMaxValue(detail.getMaxValue()); + valueDetail.setMinValue(detail.getMinValue()); + valueDetail.setAvgValue(detail.getAvgValue()); + valueDetail.setCp95Value(detail.getCp95Value()); + result.getValueOrderDetails().add(valueDetail); + return; + } + if (SteadyChecksquareConst.DETAIL_TYPE_HARMONIC_PARITY.equals(detail.getDetailType())) { + SteadyChecksquareHarmonicParityDetailVO parityDetail = new SteadyChecksquareHarmonicParityDetailVO(); + parityDetail.setTime(formatTime(detail.getPointTime())); + parityDetail.setPhase(detail.getPhase()); + parityDetail.setStatType(detail.getStatType()); + parityDetail.setEvenHarmonicOrder(detail.getEvenHarmonicOrder()); + parityDetail.setEvenValue(detail.getEvenValue()); + parityDetail.setOddHarmonicOrders(readIntegerList(detail.getOddHarmonicOrdersJson())); + parityDetail.setOddValues(readBigDecimalList(detail.getOddValuesJson())); + parityDetail.setOddMedianValue(detail.getOddMedianValue()); + parityDetail.setThresholdMultiplier(detail.getThresholdMultiplier()); + result.getHarmonicParityDetails().add(parityDetail); + } + } + + private SteadyChecksquareTaskPO requireTask(String taskId) { + SteadyChecksquareTaskPO task = taskService.getById(taskId); + if (task == null || !Integer.valueOf(SteadyChecksquareConst.STATE_ENABLED).equals(task.getState())) { + throw fail("数据校验任务不存在或已删除"); + } + return task; + } + + private SteadyChecksquareItemPO requireItem(String itemId) { + SteadyChecksquareItemPO item = itemService.getById(itemId); + if (item == null || !Integer.valueOf(SteadyChecksquareConst.STATE_ENABLED).equals(item.getState())) { + throw fail("数据校验检测项不存在或已删除"); + } + return item; + } + + private boolean isSupportedDetailType(String detailType) { + return SteadyChecksquareConst.DETAIL_TYPE_SEGMENT.equals(detailType) + || SteadyChecksquareConst.DETAIL_TYPE_VALUE_ORDER.equals(detailType) + || SteadyChecksquareConst.DETAIL_TYPE_HARMONIC_PARITY.equals(detailType); + } + + private boolean isPaged(Integer pageNum, Integer pageSize) { + return pageNum != null && pageNum > 0 && pageSize != null && pageSize > 0; + } + + private SteadyChecksquareTaskVO toTaskVO(SteadyChecksquareTaskPO task) { + SteadyChecksquareTaskVO vo = new SteadyChecksquareTaskVO(); + vo.setTaskId(task.getId()); + vo.setTaskNo(task.getTaskNo()); + vo.setLineId(task.getLineId()); + vo.setLineName(task.getLineName()); + vo.setTimeStart(formatTime(task.getTimeStart())); + vo.setTimeEnd(formatTime(task.getTimeEnd())); + vo.setIntervalMinutes(task.getIntervalMinutes()); + vo.setTaskStatus(task.getTaskStatus()); + vo.setItemCount(task.getItemCount()); + vo.setAbnormalItemCount(task.getAbnormalItemCount()); + vo.setMaxMissingRate(task.getMaxMissingRate()); + vo.setCreateTime(formatTime(task.getCreateTime())); + return vo; + } + + private SteadyChecksquareCreateVO toCreateVO(SteadyChecksquareTaskPO task) { + SteadyChecksquareCreateVO vo = new SteadyChecksquareCreateVO(); + vo.setTaskId(task.getId()); + vo.setTaskNo(task.getTaskNo()); + vo.setLineId(task.getLineId()); + vo.setLineName(task.getLineName()); + vo.setTimeStart(formatTime(task.getTimeStart())); + vo.setTimeEnd(formatTime(task.getTimeEnd())); + vo.setIntervalMinutes(task.getIntervalMinutes()); + vo.setItemCount(task.getItemCount()); + vo.setAbnormalItemCount(task.getAbnormalItemCount()); + return vo; + } + + private SteadyChecksquareItemVO toItemVO(SteadyChecksquareItemPO item) { + SteadyChecksquareItemVO vo = new SteadyChecksquareItemVO(); + vo.setItemId(item.getId()); + vo.setItemKey(item.getItemKey()); + vo.setIndicatorCode(item.getIndicatorCode()); + vo.setIndicatorName(item.getIndicatorName()); + vo.setHarmonicOrder(item.getHarmonicOrder()); + vo.setIntervalMinutes(item.getIntervalMinutes()); + vo.setHasData(toBoolean(item.getHasData())); + 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.setAbnormal(toBoolean(item.getAbnormal())); + vo.setAbnormalPointCount(item.getAbnormalPointCount()); + vo.setHarmonicParityAbnormal(toBoolean(item.getHarmonicParityAbnormal())); + vo.setHarmonicParityAbnormalPointCount(item.getHarmonicParityAbnormalPointCount()); + return vo; + } + + private SteadyChecksquareStatSummaryVO toSummaryVO(SteadyChecksquareStatSummaryPO summary) { + SteadyChecksquareStatSummaryVO vo = new SteadyChecksquareStatSummaryVO(); + vo.setStatType(summary.getStatType()); + vo.setSupported(toBoolean(summary.getSupported())); + vo.setHasData(toBoolean(summary.getHasData())); + vo.setExpectedPointCount(summary.getExpectedPointCount()); + vo.setActualPointCount(summary.getActualPointCount()); + vo.setMissingPointCount(summary.getMissingPointCount()); + vo.setMissingRate(summary.getMissingRate()); + vo.setMissingRateText(summary.getMissingRateText()); + vo.setMaxContinuousMissingMinutes(summary.getMaxContinuousMissingMinutes()); + 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())) { + count++; + } + } + return count; + } + + private BigDecimal maxMissingRate(List items) { + BigDecimal max = BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); + for (SteadyChecksquareItemVO item : items) { + if (item.getMissingRate() != null && item.getMissingRate().compareTo(max) > 0) { + max = item.getMissingRate(); + } + } + return max; + } + + private Boolean anyHasData(List items) { + for (SteadyChecksquareItemVO item : items) { + if (Boolean.TRUE.equals(item.getHasData())) { + return true; + } + } + return false; + } + + private Boolean anyAbnormal(List items) { + for (SteadyChecksquareItemVO item : items) { + if (Boolean.TRUE.equals(item.getAbnormal())) { + return true; + } + } + return false; + } + + private Boolean anyHarmonicParityAbnormal(List items) { + for (SteadyChecksquareItemVO item : items) { + if (Boolean.TRUE.equals(item.getHarmonicParityAbnormal())) { + return true; + } + } + 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; + } + int total = 0; + for (SteadyChecksquareItemVO item : items) { + total += valueOfItemInteger(item, fieldName); + } + return new BigDecimal(total).divide(new BigDecimal(items.size()), 0, RoundingMode.HALF_UP).intValue(); + } + + private Integer averageAbnormalCount(List items, String fieldName, Boolean abnormal) { + Integer average = averageInteger(items, fieldName); + if (Boolean.TRUE.equals(abnormal) && average <= 0) { + return 1; + } + return average; + } + + private Integer averageSummaryInteger(List summaries, String fieldName) { + if (summaries == null || summaries.isEmpty()) { + return 0; + } + int total = 0; + for (SteadyChecksquareStatSummaryVO summary : summaries) { + total += valueOfSummaryInteger(summary, fieldName); + } + return new BigDecimal(total).divide(new BigDecimal(summaries.size()), 0, RoundingMode.HALF_UP).intValue(); + } + + private BigDecimal averageRate(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()); + } + return total.divide(new BigDecimal(items.size()), 6, RoundingMode.HALF_UP); + } + + private BigDecimal averageSummaryRate(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()); + } + return total.divide(new BigDecimal(summaries.size()), 6, RoundingMode.HALF_UP); + } + + private int valueOfItemInteger(SteadyChecksquareItemVO item, String fieldName) { + if ("expectedPointCount".equals(fieldName)) { + return nullToZero(item.getExpectedPointCount()); + } + if ("actualPointCount".equals(fieldName)) { + return nullToZero(item.getActualPointCount()); + } + if ("missingPointCount".equals(fieldName)) { + return nullToZero(item.getMissingPointCount()); + } + if ("maxContinuousMissingMinutes".equals(fieldName)) { + return nullToZero(item.getMaxContinuousMissingMinutes()); + } + if ("abnormalPointCount".equals(fieldName)) { + return nullToZero(item.getAbnormalPointCount()); + } + if ("harmonicParityAbnormalPointCount".equals(fieldName)) { + return nullToZero(item.getHarmonicParityAbnormalPointCount()); + } + return 0; + } + + private int valueOfSummaryInteger(SteadyChecksquareStatSummaryVO summary, String fieldName) { + if ("expectedPointCount".equals(fieldName)) { + return nullToZero(summary.getExpectedPointCount()); + } + if ("actualPointCount".equals(fieldName)) { + return nullToZero(summary.getActualPointCount()); + } + if ("missingPointCount".equals(fieldName)) { + return nullToZero(summary.getMissingPointCount()); + } + if ("maxContinuousMissingMinutes".equals(fieldName)) { + return nullToZero(summary.getMaxContinuousMissingMinutes()); + } + return 0; + } + + private int nullToZero(Integer value) { + return value == null ? 0 : value; + } + private BigDecimal calculateRate(int missingCount, int expectedCount) { if (expectedCount <= 0) { return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); @@ -310,6 +1103,21 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return rate.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP).toPlainString() + "%"; } + private String formatTime(LocalDateTime time) { + if (time == null) { + return null; + } + return time.format(TIME_FORMATTER); + } + + private int toFlag(Boolean value) { + return Boolean.TRUE.equals(value) ? 1 : 0; + } + + private Boolean toBoolean(Integer value) { + return value != null && value == 1; + } + private List normalizeTextList(List values) { if (values == null || values.isEmpty()) { return new ArrayList(); @@ -324,31 +1132,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return new ArrayList(result); } - private List normalizeHarmonicOrders(List values) { - if (values == null || values.isEmpty()) { - return new ArrayList(); - } - List result = new ArrayList(); - for (Integer value : values) { - if (value != null && !result.contains(value)) { - result.add(value); - } - } - return result; - } - - private List requireValidHarmonicOrders(SteadyTrendIndicatorDefinitionBO indicator, List harmonicOrders) { - if (harmonicOrders == null || harmonicOrders.isEmpty()) { - throw fail("谐波次数不能为空"); - } - for (Integer order : harmonicOrders) { - if (order < indicator.getHarmonicOrderStart() || order > indicator.getHarmonicOrderEnd()) { - throw fail("谐波次数只能在 " + indicator.getHarmonicOrderStart() + " 到 " + indicator.getHarmonicOrderEnd() + " 之间"); - } - } - return harmonicOrders; - } - private String trimToNull(String value) { if (value == null) { return null; @@ -357,6 +1140,49 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return trimmed.isEmpty() ? null : trimmed; } + private String writeJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage()); + } + } + + private String buildIndicatorCodesText(List indicatorCodes) { + if (indicatorCodes == null || indicatorCodes.isEmpty()) { + return null; + } + StringBuilder builder = new StringBuilder("|"); + for (String indicatorCode : indicatorCodes) { + builder.append(indicatorCode).append("|"); + } + return builder.toString(); + } + + private List readIntegerList(String json) { + if (trimToNull(json) == null) { + return new ArrayList(); + } + try { + Integer[] values = objectMapper.readValue(json, Integer[].class); + return new ArrayList(Arrays.asList(values)); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage()); + } + } + + private List readBigDecimalList(String json) { + if (trimToNull(json) == null) { + return new ArrayList(); + } + try { + BigDecimal[] values = objectMapper.readValue(json, BigDecimal[].class); + return new ArrayList(Arrays.asList(values)); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage()); + } + } + private BusinessException fail(String message) { return new BusinessException(CommonResponseEnum.FAIL, message); } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java new file mode 100644 index 0000000..374a5d6 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java @@ -0,0 +1,16 @@ +package com.njcn.gather.steady.checksquare.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareStatSummaryMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService; +import org.springframework.stereotype.Service; + +/** + * 数据校验统计摘要服务实现。 + */ +@Service +public class SteadyChecksquareStatSummaryServiceImpl + extends ServiceImpl + implements SteadyChecksquareStatSummaryService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java new file mode 100644 index 0000000..91c417c --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java @@ -0,0 +1,15 @@ +package com.njcn.gather.steady.checksquare.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareTaskMapper; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService; +import org.springframework.stereotype.Service; + +/** + * 数据校验任务服务实现。 + */ +@Service +public class SteadyChecksquareTaskServiceImpl extends ServiceImpl + implements SteadyChecksquareTaskService { +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java new file mode 100644 index 0000000..f2d0a92 --- /dev/null +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java @@ -0,0 +1,24 @@ +package com.njcn.gather.steady.checksquare.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +/** + * 数据校验编号工具。 + */ +public final class SteadyChecksquareIdUtil { + + private static final DateTimeFormatter TASK_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + + private SteadyChecksquareIdUtil() { + } + + public static String uuid() { + return UUID.randomUUID().toString().replace("-", ""); + } + + public static String taskNo() { + return "CS" + LocalDateTime.now().format(TASK_FORMATTER); + } +} diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java new file mode 100644 index 0000000..a35ca1b --- /dev/null +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java @@ -0,0 +1,147 @@ +package com.njcn.gather.steady.checksquare.component; + +import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO; +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 org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * 谐波奇偶关系规则测试。 + */ +class SteadyChecksquareHarmonicParityRuleComponentTest { + + @Test + void shouldRecordAbnormalWhenEvenHarmonicExceedsOddMedianThreshold() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent); + LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); + when(influxQueryComponent.queryValuePointMap(any(), + any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenAnswer(invocation -> { + Map> values = emptyBatchResult(invocation.getArgument(0)); + putPoint(values, "v_3", time, "10"); + putPoint(values, "v_4", time, "31"); + putPoint(values, "v_5", time, "12"); + putPoint(values, "v_7", time, "14"); + return values; + }); + SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC"); + + SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator, + time, time, 1); + + Assertions.assertEquals(Boolean.TRUE, result.getAbnormal()); + Assertions.assertEquals(Integer.valueOf(1), result.getAbnormalPointCount()); + SteadyChecksquareHarmonicParityDetailVO detail = result.getAbnormalDetails().get(0); + Assertions.assertEquals("2026-05-01 00:00:00", detail.getTime()); + Assertions.assertEquals("A", detail.getPhase()); + Assertions.assertEquals("AVG", detail.getStatType()); + Assertions.assertEquals(Integer.valueOf(4), detail.getEvenHarmonicOrder()); + Assertions.assertEquals(new BigDecimal("31"), detail.getEvenValue()); + Assertions.assertEquals(Arrays.asList(3, 5, 7), detail.getOddHarmonicOrders()); + Assertions.assertEquals(new BigDecimal("12"), detail.getOddMedianValue()); + Assertions.assertEquals(new BigDecimal("2"), detail.getThresholdMultiplier()); + } + + @Test + void shouldSkipWhenOddReferenceCountLessThanTwo() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent); + LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); + when(influxQueryComponent.queryValuePointMap(any(), + any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenAnswer(invocation -> { + Map> values = emptyBatchResult(invocation.getArgument(0)); + putPoint(values, "v_2", time, "50"); + putPoint(values, "v_3", time, "10"); + return values; + }); + SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC"); + + SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator, + time, time, 1); + + Assertions.assertEquals(Boolean.FALSE, result.getAbnormal()); + Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount()); + Assertions.assertTrue(result.getAbnormalDetails().isEmpty()); + } + + @Test + void shouldSkipEvenHarmonicWhenValueNotGreaterThanDeadband() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent); + LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); + when(influxQueryComponent.queryValuePointMap(any(), + any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenAnswer(invocation -> { + Map> values = emptyBatchResult(invocation.getArgument(0)); + putPoint(values, "v_3", time, "0.01"); + putPoint(values, "v_4", time, "0.10"); + putPoint(values, "v_5", time, "0.02"); + return values; + }); + SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC"); + + SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator, + time, time, 1); + + Assertions.assertEquals(Boolean.FALSE, result.getAbnormal()); + Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount()); + Assertions.assertTrue(result.getAbnormalDetails().isEmpty()); + } + + @Test + void shouldSkipNonHarmonicIndicator() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent); + SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_RMS"); + + SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator, + LocalDateTime.of(2026, 5, 1, 0, 0), + LocalDateTime.of(2026, 5, 1, 0, 1), 1); + + Assertions.assertEquals(Boolean.FALSE, result.getAbnormal()); + Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount()); + Assertions.assertTrue(result.getAbnormalDetails().isEmpty()); + } + + private SteadyChecksquareValuePointBO point(LocalDateTime time, String value) { + SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO(); + point.setTime(time); + point.setValue(new BigDecimal(value)); + return point; + } + + private Map> emptyBatchResult(List fields) { + Map> result = + new LinkedHashMap>(); + for (SteadyTrendResolvedFieldBO field : fields) { + result.put(field.getField(), Collections.emptyList()); + } + return result; + } + + private void putPoint(Map> values, String field, + LocalDateTime time, String value) { + if (values.containsKey(field)) { + values.put(field, Collections.singletonList(point(time, value))); + } + } +} diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java index 473325f..867adda 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java @@ -2,10 +2,16 @@ package com.njcn.gather.steady.checksquare.component; import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties; import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO; +import com.sun.net.httpserver.HttpServer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** * 数据校验 InfluxQL 构造契约测试。 @@ -54,4 +60,94 @@ class SteadyChecksquareInfluxQueryComponentTest { Assertions.assertTrue(query.contains("\"value_type\" = 'CP95'")); Assertions.assertTrue(query.endsWith("ORDER BY time ASC")); } + + @Test + void shouldReuseValuePointQueryWithinRequestCache() throws Exception { + AtomicInteger requestCount = new AtomicInteger(); + HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/query", exchange -> { + requestCount.incrementAndGet(); + byte[] body = ("{\"results\":[{\"series\":[{\"values\":[" + + "[\"2026-05-01T00:00:00Z\",1.23]," + + "[\"2026-05-01T00:01:00Z\",2.34]" + + "]}]}]}").getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + }); + server.start(); + try { + SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties(); + properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort()); + properties.setDatabase("steady"); + SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties); + SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO(); + field.setMeasurement("data_v"); + field.setField("rms"); + field.setLineId("line-001"); + field.setPhase("A"); + field.setStatType("AVG"); + + component.enableRequestCache(); + component.queryExistingSlots(field, + LocalDateTime.of(2026, 5, 1, 0, 0, 0), + LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1); + component.queryValuePoints(field, + LocalDateTime.of(2026, 5, 1, 0, 0, 0), + LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1); + component.clearRequestCache(); + + Assertions.assertEquals(1, requestCount.get()); + } finally { + server.stop(0); + } + } + + @Test + void shouldQueryMultipleValueFieldsOnce() throws Exception { + AtomicInteger requestCount = new AtomicInteger(); + HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/query", exchange -> { + requestCount.incrementAndGet(); + byte[] body = ("{\"results\":[{\"series\":[{\"columns\":[\"time\",\"h_2\",\"h_3\"],\"values\":[" + + "[\"2026-05-01T00:00:00Z\",1.23,2.34]," + + "[\"2026-05-01T00:01:00Z\",3.45,null]" + + "]}]}]}").getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + }); + server.start(); + try { + SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties(); + properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort()); + properties.setDatabase("steady"); + SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties); + SteadyTrendResolvedFieldBO h2 = buildField("h_2"); + SteadyTrendResolvedFieldBO h3 = buildField("h_3"); + + component.enableRequestCache(); + Map> result = + component.queryValuePointMap(Arrays.asList(h2, h3), + LocalDateTime.of(2026, 5, 1, 0, 0, 0), + LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1); + component.clearRequestCache(); + + Assertions.assertEquals(1, requestCount.get()); + Assertions.assertEquals(2, result.get("h_2").size()); + Assertions.assertEquals(1, result.get("h_3").size()); + } finally { + server.stop(0); + } + } + + private SteadyTrendResolvedFieldBO buildField(String fieldName) { + SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO(); + field.setMeasurement("data_harmonic"); + field.setField(fieldName); + field.setLineId("line-001"); + field.setPhase("A"); + field.setStatType("AVG"); + return field; + } } diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java index e2c44fa..748c4e4 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java @@ -35,13 +35,13 @@ class SteadyChecksquareValueOrderRuleComponentTest { return Arrays.asList(point(firstTime, "8"), point(secondTime, "9")); } if ("CP95".equals(statType)) { - return Arrays.asList(point(firstTime, "8"), point(secondTime, "10")); + return Arrays.asList(point(firstTime, "9"), point(secondTime, "10")); } if ("AVG".equals(statType)) { return Arrays.asList(point(firstTime, "7"), point(secondTime, "8")); } if ("MIN".equals(statType)) { - return Arrays.asList(point(firstTime, "1"), point(secondTime, "8")); + return Arrays.asList(point(firstTime, "1"), point(secondTime, "9")); } return Collections.emptyList(); }); @@ -57,7 +57,38 @@ class SteadyChecksquareValueOrderRuleComponentTest { Assertions.assertEquals(new BigDecimal("8"), result.getAbnormalDetails().get(0).getMaxValue()); Assertions.assertEquals(new BigDecimal("1"), result.getAbnormalDetails().get(0).getMinValue()); Assertions.assertEquals(new BigDecimal("7"), result.getAbnormalDetails().get(0).getAvgValue()); - Assertions.assertEquals(new BigDecimal("8"), result.getAbnormalDetails().get(0).getCp95Value()); + Assertions.assertEquals(new BigDecimal("9"), result.getAbnormalDetails().get(0).getCp95Value()); + } + + @Test + void shouldTreatEqualAdjacentStatValuesAsNormal() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); + LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); + when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) + .thenAnswer(invocation -> { + String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); + if ("MAX".equals(statType)) { + return Collections.singletonList(point(time, "10")); + } + if ("CP95".equals(statType)) { + return Collections.singletonList(point(time, "10")); + } + if ("AVG".equals(statType)) { + return Collections.singletonList(point(time, "8")); + } + if ("MIN".equals(statType)) { + return Collections.singletonList(point(time, "8")); + } + return Collections.emptyList(); + }); + + SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null, + LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1); + + Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount()); + Assertions.assertEquals(Boolean.FALSE, result.getAbnormal()); + Assertions.assertTrue(result.getAbnormalDetails().isEmpty()); } @Test @@ -69,10 +100,10 @@ class SteadyChecksquareValueOrderRuleComponentTest { .thenAnswer(invocation -> { String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); if ("MAX".equals(statType)) { - return Collections.singletonList(point(time, "10")); + return Collections.singletonList(point(time, "8")); } if ("CP95".equals(statType)) { - return Collections.singletonList(point(time, "8")); + return Collections.singletonList(point(time, "10")); } if ("AVG".equals(statType)) { return Collections.singletonList(point(time, "8")); @@ -91,6 +122,39 @@ class SteadyChecksquareValueOrderRuleComponentTest { Assertions.assertEquals(1, result.getAbnormalDetails().size()); } + @Test + void shouldFillHarmonicOrderInAbnormalDetailForHarmonicIndicator() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); + LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); + when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) + .thenAnswer(invocation -> { + String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); + if ("MAX".equals(statType)) { + return Collections.singletonList(point(time, "8")); + } + if ("CP95".equals(statType)) { + return Collections.singletonList(point(time, "10")); + } + if ("AVG".equals(statType)) { + return Collections.singletonList(point(time, "8")); + } + if ("MIN".equals(statType)) { + return Collections.singletonList(point(time, "1")); + } + return Collections.emptyList(); + }); + SteadyTrendIndicatorDefinitionBO indicator = indicator(); + indicator.setHarmonic(true); + indicator.setHarmonicFieldPrefix("v"); + + SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator, 5, + LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1); + + Assertions.assertEquals(1, result.getAbnormalDetails().size()); + Assertions.assertEquals(Integer.valueOf(5), result.getAbnormalDetails().get(0).getHarmonicOrder()); + } + @Test void shouldSkipPointWhenAnyRequiredStatValueMissing() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); 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 0d1c597..d68542f 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 @@ -2,6 +2,7 @@ package com.njcn.gather.steady.checksquare.controller; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,8 +18,21 @@ class SteadyChecksquareControllerTest { RequestMapping requestMapping = SteadyChecksquareController.class.getAnnotation(RequestMapping.class); Assertions.assertArrayEquals(new String[]{"/steady/data-view/checksquare"}, requestMapping.value()); - Method method = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class); - PostMapping postMapping = method.getAnnotation(PostMapping.class); - Assertions.assertArrayEquals(new String[]{"/query"}, postMapping.value()); + Method queryMethod = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam.class); + PostMapping queryMapping = queryMethod.getAnnotation(PostMapping.class); + Assertions.assertArrayEquals(new String[]{"/query"}, queryMapping.value()); + + Method createMethod = SteadyChecksquareController.class.getDeclaredMethod("create", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class); + PostMapping createMapping = createMethod.getAnnotation(PostMapping.class); + Assertions.assertArrayEquals(new String[]{"/create"}, createMapping.value()); + + Method detailMethod = SteadyChecksquareController.class.getDeclaredMethod("detail", String.class); + GetMapping detailMapping = detailMethod.getAnnotation(GetMapping.class); + Assertions.assertArrayEquals(new String[]{"/detail"}, detailMapping.value()); + + Method itemDetailMethod = SteadyChecksquareController.class.getDeclaredMethod("itemDetail", + String.class, String.class, String.class, Integer.class, Integer.class); + GetMapping itemDetailMapping = itemDetailMethod.getAnnotation(GetMapping.class); + Assertions.assertArrayEquals(new String[]{"/item-detail"}, itemDetailMapping.value()); } } diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java index d97ff41..eae9492 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java @@ -19,7 +19,7 @@ class SteadyChecksquareQueryParamTest { Assertions.assertNull(field("qualityFlag")); Assertions.assertNull(field("statTypes")); Assertions.assertNull(field("phases")); - Assertions.assertNotNull(field("harmonicOrders")); + Assertions.assertNull(field("harmonicOrders")); } private Field field(String name) { 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 f51387e..421a010 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 @@ -1,21 +1,43 @@ package com.njcn.gather.steady.checksquare.service.impl; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator; +import com.njcn.gather.steady.checksquare.component.SteadyChecksquareHarmonicParityRuleComponent; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent; import com.njcn.gather.steady.checksquare.component.SteadyChecksquareValueOrderRuleComponent; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; +import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO; +import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService; +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.SteadyTrendResolvedFieldBO; import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; import com.njcn.gather.tool.addledger.service.AddLedgerService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.transaction.annotation.Transactional; +import java.lang.reflect.Method; +import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -25,6 +47,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -32,15 +56,28 @@ import static org.mockito.Mockito.when; */ class SteadyChecksquareServiceImplTest { + @Test + void shouldNotOpenTransactionAroundCreateCalculation() throws Exception { + Method createMethod = SteadyChecksquareServiceImpl.class.getMethod("create", SteadyChecksquareQueryParam.class); + + Assertions.assertNull(createMethod.getAnnotation(Transactional.class)); + } + @Test void shouldUseFixedFlickerIntervalsPerIndicator() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); AddLedgerService addLedgerService = mock(AddLedgerService.class); SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), - influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, new AddDataTimeSlotCalculator(), addLedgerService); + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); linePath.setLineName("进线一"); @@ -61,7 +98,7 @@ class SteadyChecksquareServiceImplTest { param.setTimeStart("2026-05-01 00:00:00"); param.setTimeEnd("2026-05-01 02:00:00"); - SteadyChecksquareQueryVO result = service.query(param); + SteadyChecksquareQueryVO result = calculate(service, param); Assertions.assertEquals(Integer.valueOf(1), result.getIntervalMinutes()); Assertions.assertEquals(3, result.getItems().size()); @@ -71,14 +108,20 @@ class SteadyChecksquareServiceImplTest { } @Test - void shouldOnlyQueryRequestedHarmonicOrders() { + void shouldAggregateAllHarmonicOrdersIntoIndicatorItem() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); AddLedgerService addLedgerService = mock(AddLedgerService.class); SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), - influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, new AddDataTimeSlotCalculator(), addLedgerService); + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); linePath.setLineName("进线一"); @@ -92,25 +135,32 @@ class SteadyChecksquareServiceImplTest { SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); param.setLineId("line-001"); param.setIndicatorCodes(Collections.singletonList("V_HARMONIC")); - param.setHarmonicOrders(Collections.singletonList(5)); param.setTimeStart("2026-05-01 00:00:00"); param.setTimeEnd("2026-05-01 00:01:00"); - SteadyChecksquareQueryVO result = service.query(param); + SteadyChecksquareQueryVO result = calculate(service, param); Assertions.assertEquals(1, result.getItems().size()); - Assertions.assertEquals(Integer.valueOf(5), result.getItems().get(0).getHarmonicOrder()); + Assertions.assertEquals("line-001|V_HARMONIC", result.getItems().get(0).getItemKey()); + Assertions.assertNull(result.getItems().get(0).getHarmonicOrder()); + Assertions.assertEquals(Integer.valueOf(2), result.getItems().get(0).getStatDetails().get(0).getSegments().get(0).getHarmonicOrder()); } @Test - void shouldKeepRequestedHarmonicOrdersDistinctAndOrdered() { + void shouldAverageHarmonicOrderResultsAndMarkAbnormalWhenAnyOrderAbnormal() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); AddLedgerService addLedgerService = mock(AddLedgerService.class); SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), - influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, new AddDataTimeSlotCalculator(), addLedgerService); + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); linePath.setLineName("进线一"); @@ -118,30 +168,53 @@ class SteadyChecksquareServiceImplTest { when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) - .thenReturn(new HashSet()); + .thenReturn(new HashSet(Collections.singletonList( + LocalDateTime.of(2026, 5, 1, 0, 0)))); + SteadyChecksquareValueOrderRuleVO normalRuleResult = new SteadyChecksquareValueOrderRuleVO(); + SteadyChecksquareValueOrderRuleVO abnormalRuleResult = new SteadyChecksquareValueOrderRuleVO(); + SteadyChecksquareValueOrderDetailVO abnormalDetail = new SteadyChecksquareValueOrderDetailVO(); + abnormalDetail.setTime("2026-05-01 00:00:00"); + abnormalDetail.setPhase("A"); + abnormalDetail.setHarmonicOrder(2); + abnormalRuleResult.setAbnormal(true); + abnormalRuleResult.setAbnormalPointCount(4); + abnormalRuleResult.setAbnormalDetails(Collections.singletonList(abnormalDetail)); + when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(normalRuleResult); + when(valueOrderRuleComponent.check(any(), any(), eq(2), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(abnormalRuleResult); + when(valueOrderRuleComponent.check(any(), any(), eq(3), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(normalRuleResult); SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); param.setLineId("line-001"); param.setIndicatorCodes(Collections.singletonList("V_HARMONIC")); - param.setHarmonicOrders(Arrays.asList(7, 5, 7)); param.setTimeStart("2026-05-01 00:00:00"); param.setTimeEnd("2026-05-01 00:01:00"); - SteadyChecksquareQueryVO result = service.query(param); + SteadyChecksquareQueryVO result = calculate(service, param); List items = result.getItems(); - Assertions.assertEquals(2, items.size()); - Assertions.assertEquals(Integer.valueOf(7), items.get(0).getHarmonicOrder()); - Assertions.assertEquals(Integer.valueOf(5), items.get(1).getHarmonicOrder()); + Assertions.assertEquals(1, items.size()); + Assertions.assertEquals(Boolean.TRUE, items.get(0).getAbnormal()); + Assertions.assertEquals(Integer.valueOf(1), items.get(0).getAbnormalPointCount()); + Assertions.assertEquals(1, items.get(0).getAbnormalDetails().size()); + Assertions.assertEquals(Integer.valueOf(2), items.get(0).getAbnormalDetails().get(0).getHarmonicOrder()); + Assertions.assertEquals(Integer.valueOf(8), items.get(0).getExpectedPointCount()); + Assertions.assertEquals(Integer.valueOf(4), items.get(0).getActualPointCount()); } @Test void shouldAssembleValueOrderRuleResultIntoItem() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); AddLedgerService addLedgerService = mock(AddLedgerService.class); SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), - influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, new AddDataTimeSlotCalculator(), addLedgerService); + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); linePath.setLineName("进线一"); @@ -167,7 +240,7 @@ class SteadyChecksquareServiceImplTest { param.setTimeStart("2026-05-01 00:00:00"); param.setTimeEnd("2026-05-01 00:01:00"); - SteadyChecksquareQueryVO result = service.query(param); + SteadyChecksquareQueryVO result = calculate(service, param); SteadyChecksquareItemVO item = result.getItems().get(0); Assertions.assertEquals(Boolean.TRUE, item.getAbnormal()); @@ -176,13 +249,314 @@ class SteadyChecksquareServiceImplTest { Assertions.assertEquals("A", item.getAbnormalDetails().get(0).getPhase()); } + @Test + void shouldPrefetchNormalIndicatorFieldsByMeasurementPhaseAndStat() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); + AddLedgerService addLedgerService = mock(AddLedgerService.class); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); + AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); + linePath.setLineId("line-001"); + linePath.setLineName("进线一"); + linePath.setLineInterval(1); + when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) + .thenReturn(Collections.singletonMap("line-001", linePath)); + when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) + .thenReturn(new HashSet(Collections.singletonList( + LocalDateTime.of(2026, 5, 1, 0, 0)))); + + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setLineId("line-001"); + param.setIndicatorCodes(Arrays.asList("V_RMS", "V_LINE_RMS")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-01 00:01:00"); + + calculate(service, param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(influxQueryComponent, times(12)).queryValuePointMap(captor.capture(), + any(LocalDateTime.class), any(LocalDateTime.class), eq(1)); + boolean foundBatch = false; + for (List fields : captor.getAllValues()) { + if (fields.size() == 2) { + List fieldNames = new ArrayList(); + for (Object field : fields) { + fieldNames.add(((SteadyTrendResolvedFieldBO) field).getField()); + } + foundBatch = fieldNames.contains("rms") && fieldNames.contains("rms_lvr"); + } + } + Assertions.assertTrue(foundBatch); + } + + @Test + void shouldAssembleHarmonicParityRuleResultIntoAggregateItem() { + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); + AddLedgerService addLedgerService = mock(AddLedgerService.class); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyRuleResult()); + AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); + linePath.setLineId("line-001"); + linePath.setLineName("进线一"); + linePath.setLineInterval(1); + when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) + .thenReturn(Collections.singletonMap("line-001", linePath)); + when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) + .thenReturn(new HashSet(Collections.singletonList( + LocalDateTime.of(2026, 5, 1, 0, 0)))); + SteadyChecksquareHarmonicParityRuleVO ruleResult = new SteadyChecksquareHarmonicParityRuleVO(); + SteadyChecksquareHarmonicParityDetailVO detail = new SteadyChecksquareHarmonicParityDetailVO(); + detail.setTime("2026-05-01 00:00:00"); + detail.setPhase("A"); + detail.setStatType("AVG"); + detail.setEvenHarmonicOrder(4); + ruleResult.setAbnormal(true); + ruleResult.setAbnormalPointCount(1); + ruleResult.setAbnormalDetails(Collections.singletonList(detail)); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) + .thenReturn(ruleResult); + + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setLineId("line-001"); + param.setIndicatorCodes(Collections.singletonList("V_HARMONIC")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-01 00:01:00"); + + SteadyChecksquareQueryVO result = calculate(service, param); + + SteadyChecksquareItemVO item = result.getItems().get(0); + Assertions.assertNull(item.getHarmonicOrder()); + Assertions.assertEquals(Boolean.TRUE, item.getHarmonicParityAbnormal()); + Assertions.assertEquals(Integer.valueOf(1), item.getHarmonicParityAbnormalPointCount()); + Assertions.assertEquals("AVG", item.getHarmonicParityAbnormalDetails().get(0).getStatType()); + } + + @Test + void shouldRejectUnsupportedItemDetailType() { + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareItemPO item = new SteadyChecksquareItemPO(); + item.setId("item-001"); + item.setState(1); + when(itemService.getById("item-001")).thenReturn(item); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class), + itemService, mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + + Assertions.assertThrows(RuntimeException.class, () -> service.itemDetail("item-001", "UNKNOWN", null)); + } + + @Test + void shouldLoadDetailSummariesInSingleBatch() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class); + LambdaQueryChainWrapper itemQuery = mock(LambdaQueryChainWrapper.class); + LambdaQueryChainWrapper summaryQuery = mock(LambdaQueryChainWrapper.class); + SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); + task.setId("task-001"); + task.setState(1); + task.setLineId("line-001"); + task.setLineName("进线一"); + task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0)); + task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1)); + task.setIntervalMinutes(1); + SteadyChecksquareItemPO item1 = buildItemPO("item-001", "V_RMS"); + SteadyChecksquareItemPO item2 = buildItemPO("item-002", "FREQ"); + SteadyChecksquareStatSummaryPO summary1 = buildSummaryPO("item-001", "AVG"); + SteadyChecksquareStatSummaryPO summary2 = buildSummaryPO("item-002", "AVG"); + when(taskService.getById("task-001")).thenReturn(task); + when(itemService.lambdaQuery()).thenReturn(itemQuery); + when(itemQuery.eq(any(), any())).thenReturn(itemQuery); + when(itemQuery.list()).thenReturn(Arrays.asList(item1, item2)); + when(statSummaryService.lambdaQuery()).thenReturn(summaryQuery); + when(summaryQuery.in(any(), any(List.class))).thenReturn(summaryQuery); + when(summaryQuery.list()).thenReturn(Arrays.asList(summary1, summary2)); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService, + itemService, statSummaryService, mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + + SteadyChecksquareQueryVO result = service.detail("task-001"); + + Assertions.assertEquals(2, result.getItems().size()); + Assertions.assertEquals(1, result.getItems().get(0).getStatSummaries().size()); + Assertions.assertEquals(1, result.getItems().get(1).getStatSummaries().size()); + verify(statSummaryService, times(1)).lambdaQuery(); + } + + @Test + void shouldPageItemDetailWhenPageArgumentsPresent() { + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); + SteadyChecksquareItemPO item = new SteadyChecksquareItemPO(); + item.setId("item-001"); + item.setState(1); + SteadyChecksquareDetailPO detail = new SteadyChecksquareDetailPO(); + detail.setItemId("item-001"); + detail.setDetailType("VALUE_ORDER"); + detail.setPointTime(LocalDateTime.of(2026, 5, 1, 0, 0)); + detail.setPhase("A"); + Page page = new Page(2, 1); + page.setTotal(1); + page.setRecords(Collections.singletonList(detail)); + when(itemService.getById("item-001")).thenReturn(item); + when(detailService.page(any(Page.class), any())).thenReturn(page); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class), + itemService, mock(SteadyChecksquareStatSummaryService.class), + detailService, new ObjectMapper()); + + SteadyChecksquareItemDetailVO result = service.itemDetail("item-001", "VALUE_ORDER", null, 2, 1); + + Assertions.assertEquals(Integer.valueOf(2), result.getPageNum()); + Assertions.assertEquals(Integer.valueOf(1), result.getPageSize()); + Assertions.assertEquals(Long.valueOf(1L), result.getTotal()); + Assertions.assertEquals(1, result.getValueOrderDetails().size()); + verify(detailService).page(any(Page.class), any()); + } + + @Test + void shouldSaveChecksquareResultsInBatch() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class); + SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService, + itemService, statSummaryService, detailService, 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.setIndicatorName("相电压有效值"); + item.setIntervalMinutes(1); + item.setHasData(true); + item.setExpectedPointCount(2); + item.setActualPointCount(2); + item.setMissingPointCount(0); + item.setMissingRate(BigDecimal.ZERO.setScale(6)); + item.setMissingRateText("0.00%"); + item.setMaxContinuousMissingMinutes(0); + item.setAbnormal(false); + item.setAbnormalPointCount(0); + item.setHarmonicParityAbnormal(false); + item.setHarmonicParityAbnormalPointCount(0); + SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO(); + summary.setStatType("AVG"); + summary.setSupported(true); + summary.setHasData(true); + summary.setExpectedPointCount(2); + summary.setActualPointCount(2); + summary.setMissingPointCount(0); + summary.setMissingRate(BigDecimal.ZERO.setScale(6)); + summary.setMissingRateText("0.00%"); + summary.setMaxContinuousMissingMinutes(0); + item.getStatSummaries().add(summary); + result.getItems().add(item); + + saveResult(service, param, result); + + verify(taskService).save(any()); + verify(itemService).saveBatch(any()); + verify(statSummaryService).saveBatch(any()); + } + 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 SteadyChecksquareQueryVO calculate(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param) { + try { + Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("calculate", SteadyChecksquareQueryParam.class); + method.setAccessible(true); + return (SteadyChecksquareQueryVO) method.invoke(service, param); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private void saveResult(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { + try { + Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("saveResult", + SteadyChecksquareQueryParam.class, SteadyChecksquareQueryVO.class); + method.setAccessible(true); + method.invoke(service, param, result); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private SteadyChecksquareItemPO buildItemPO(String itemId, String indicatorCode) { + SteadyChecksquareItemPO item = new SteadyChecksquareItemPO(); + item.setId(itemId); + item.setIndicatorCode(indicatorCode); + item.setIndicatorName(indicatorCode); + item.setState(1); + item.setHasData(1); + item.setExpectedPointCount(1); + item.setActualPointCount(1); + item.setMissingPointCount(0); + item.setMissingRate(BigDecimal.ZERO.setScale(6)); + item.setMaxContinuousMissingMinutes(0); + item.setAbnormal(0); + item.setAbnormalPointCount(0); + item.setHarmonicParityAbnormal(0); + item.setHarmonicParityAbnormalPointCount(0); + return item; + } + + private SteadyChecksquareStatSummaryPO buildSummaryPO(String itemId, String statType) { + SteadyChecksquareStatSummaryPO summary = new SteadyChecksquareStatSummaryPO(); + summary.setItemId(itemId); + summary.setStatType(statType); + summary.setSupported(1); + summary.setHasData(1); + summary.setExpectedPointCount(1); + summary.setActualPointCount(1); + summary.setMissingPointCount(0); + summary.setMissingRate(BigDecimal.ZERO.setScale(6)); + summary.setMaxContinuousMissingMinutes(0); + return summary; + } + private SteadyChecksquareValueOrderRuleVO emptyRuleResult() { return new SteadyChecksquareValueOrderRuleVO(); } + + private SteadyChecksquareHarmonicParityRuleVO emptyHarmonicParityRuleResult() { + return new SteadyChecksquareHarmonicParityRuleVO(); + } } diff --git a/tools/add-data/src/main/java/com/njcn/gather/tool/adddata/component/AddDataValueGenerator.java b/tools/add-data/src/main/java/com/njcn/gather/tool/adddata/component/AddDataValueGenerator.java index e51324d..784e8ef 100644 --- a/tools/add-data/src/main/java/com/njcn/gather/tool/adddata/component/AddDataValueGenerator.java +++ b/tools/add-data/src/main/java/com/njcn/gather/tool/adddata/component/AddDataValueGenerator.java @@ -223,7 +223,8 @@ public class AddDataValueGenerator { if (baseValue == null) { throw new IllegalStateException("派生字段缺少主值:" + column); } - double factor = noise(state.sharedSeed + column.hashCode(), 0.01D, 0.05D); + String baseColumn = resolveDerivedBaseColumn(column, metricType); + double factor = noise(state.sharedSeed + baseColumn.hashCode(), 0.01D, 0.05D); double delta = Math.max(Math.abs(baseValue) * factor, 0.005D); double value; if (MetricType.MAX.equals(metricType)) { @@ -239,6 +240,16 @@ public class AddDataValueGenerator { return round(value, 4); } + private String resolveDerivedBaseColumn(String column, MetricType metricType) { + if (MetricType.MAX.equals(metricType)) { + return removeSuffix(column, SUFFIX_MAX); + } + if (MetricType.MIN.equals(metricType)) { + return removeSuffix(column, SUFFIX_MIN); + } + return removeSuffix(column, SUFFIX_CP95); + } + /** * 构建同源基础状态。 * diff --git a/tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataValueGeneratorTest.java b/tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataValueGeneratorTest.java index cab4ddd..99e5f5c 100644 --- a/tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataValueGeneratorTest.java +++ b/tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataValueGeneratorTest.java @@ -25,4 +25,24 @@ class AddDataValueGeneratorTest { Assertions.assertEquals(0, row.get(3)); } + + @Test + void shouldKeepStatValuesOrderedForSameTimeAndMetric() { + AddDataValueGenerator generator = new AddDataValueGenerator(); + AddDataTableDefinition definition = new AddDataTableDefinition("data_v", + Arrays.asList("TIMEID", "LINEID", "PHASIC_TYPE", "QUALITYFLAG", + "RMS", "RMS_MAX", "RMS_CP95", "RMS_MIN"), + Arrays.asList("A"), 100, AddDataTableDefinition.TimeAxisType.REQUEST_INTERVAL); + + List row = generator.generateRow(definition, "line-001", + LocalDateTime.of(2026, 5, 18, 10, 0, 0), "A"); + + double avg = ((Number) row.get(4)).doubleValue(); + double max = ((Number) row.get(5)).doubleValue(); + double cp95 = ((Number) row.get(6)).doubleValue(); + double min = ((Number) row.get(7)).doubleValue(); + Assertions.assertTrue(max >= cp95, "MAX should be greater than or equal to CP95"); + Assertions.assertTrue(cp95 >= avg, "CP95 should be greater than or equal to AVG"); + Assertions.assertTrue(avg >= min, "AVG should be greater than or equal to MIN"); + } }