feat(wave-tool): 添加波形解析结果统计信息并优化数值精度

- 在 WaveComtradeResultVO 中新增 totalChannels、phaseCount 和 unit 字段用于展示统计信息
- 实现波形时间和幅值的小数位统一保留 3 位精度处理
- 添加了对 COMTRADE 配置文件中的变比进行优先使用的逻辑
- 实现了波形单位识别逻辑,支持 kV/A、kV、A 等单位显示
- 更新了文档中关于 PT/CT 变比的说明,明确优先使用 cfg 模拟量通道的变比
- 添加 spring-boot-starter-test 依赖以支持测试功能
This commit is contained in:
2026-05-11 16:32:25 +08:00
parent 6f5d8dc45a
commit b56116264c
6 changed files with 136 additions and 12 deletions

View File

@@ -111,9 +111,9 @@ wave-tool/
- `1`: 三角
- `2`: 开口三角
- `pt`
- PT 变比
- PT 变比,优先使用 `cfg` 模拟量通道的一次/二次变比;`cfg` 无有效变比时使用该参数
- `ct`
- CT 变比
- CT 变比,优先使用 `cfg` 模拟量通道的一次/二次变比;`cfg` 无有效变比时使用该参数
- `monitorName`
- 测点名称
- `calculateRms`

View File

@@ -24,5 +24,11 @@
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.util.*;
@@ -543,13 +545,13 @@ public class WaveFileComponent {
if (!blxValue && j == 0) {
xValueAll = (float) (currentDataIndex * 20) / tmpRateDTO.getNOneSample() - comtradeCfgDTO.getNPush();
blxValue = true;
tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100);
tmpWaveData.add(roundWaveTime(xValueAll));
} else if (j == 0) {
xValueAll += (float) dfValue / nWaveSpan;
tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100);
tmpWaveData.add(roundWaveTime(xValueAll));
}
tmpWaveData.add((float) (Math.round(fValue * 100)) / 100);
tmpWaveData.add(roundWaveAmplitude(fValue));
}
listWaveData.add(tmpWaveData);
}
@@ -598,13 +600,13 @@ public class WaveFileComponent {
if (!blxValue && j == 0) {
xValueAll = (float) (i * 20) / tmpRateDTO.getNOneSample() - comtradeCfgDTO.getNPush();
blxValue = true;
tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100);
tmpWaveData.add(roundWaveTime(xValueAll));
} else if (j == 0) {
xValueAll += (float) nWaveSpan * dfValue;
tmpWaveData.add((float) (Math.round(xValueAll * 100)) / 100);
tmpWaveData.add(roundWaveTime(xValueAll));
}
tmpWaveData.add((float) (Math.round(fValue * 100)) / 100);
tmpWaveData.add(roundWaveAmplitude(fValue));
}
listWaveData.add(tmpWaveData);
}
@@ -632,6 +634,20 @@ public class WaveFileComponent {
return listWaveData;
}
/**
* 波形时间统一保留 3 位小数。
*/
static Float roundWaveTime(float value) {
return BigDecimal.valueOf(value).setScale(3, RoundingMode.HALF_UP).floatValue();
}
/**
* 波形幅值统一保留 3 位小数。
*/
static Float roundWaveAmplitude(float value) {
return BigDecimal.valueOf(value).setScale(3, RoundingMode.HALF_UP).floatValue();
}
/**
* 组装 DAT 解析上下文,便于日志直接定位 cfg/dat 是否匹配。
*/

View File

@@ -20,11 +20,11 @@ public class WaveComtradeParseParam {
private Integer ptType = 0;
/** PT 变比。 */
@ApiModelProperty(value = "PT 变比,默认 1")
@ApiModelProperty(value = "PT 变比,cfg 无有效变比时使用,默认 1")
private Double pt = 1D;
/** CT 变比。 */
@ApiModelProperty(value = "CT 变比,默认 1")
@ApiModelProperty(value = "CT 变比,cfg 无有效变比时使用,默认 1")
private Double ct = 1D;
/** 测点名称。 */

View File

@@ -16,6 +16,18 @@ import java.util.List;
@ApiModel("COMTRADE 波形解析结果")
public class WaveComtradeResultVO {
/** 通道总数。 */
@ApiModelProperty("通道总数")
private Integer totalChannels;
/** 相别数量。 */
@ApiModelProperty("相别数量")
private Integer phaseCount;
/** 波形单位,电压/电流混合时返回 kV/A。 */
@ApiModelProperty("波形单位")
private String unit;
/** 波形基础数据。 */
@ApiModelProperty("波形基础数据")
private WaveDataDTO waveData;

View File

@@ -6,6 +6,8 @@ import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.tool.wave.component.WaveFileComponent;
import com.njcn.gather.tool.wave.component.WaveVectorComponent;
import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail;
import com.njcn.gather.tool.wave.pojo.dto.AnalogDTO;
import com.njcn.gather.tool.wave.pojo.dto.ComtradeCfgDTO;
import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO;
@@ -124,6 +126,7 @@ public class WaveServiceImpl implements WaveService {
}
WaveComtradeResultVO result = new WaveComtradeResultVO();
fillComtradeSummary(result, waveDataDTO);
result.setWaveData(waveDataDTO);
if (buildDetails) {
List<WaveDataDetail> waveDataDetails = WaveUtil.filterWaveData(waveDataDTO);
@@ -145,6 +148,66 @@ public class WaveServiceImpl implements WaveService {
}
}
private void fillComtradeSummary(WaveComtradeResultVO result, WaveDataDTO waveDataDTO) {
if (result == null || waveDataDTO == null) {
return;
}
ComtradeCfgDTO cfgDTO = waveDataDTO.getComtradeCfgDTO();
if (cfgDTO != null) {
result.setTotalChannels(cfgDTO.getNChannelNum());
}
result.setPhaseCount(resolvePhaseCount(waveDataDTO, cfgDTO));
result.setUnit(resolveDisplayUnit(waveDataDTO));
}
private Integer resolvePhaseCount(WaveDataDTO waveDataDTO, ComtradeCfgDTO cfgDTO) {
if (waveDataDTO.getIPhasic() != null) {
return waveDataDTO.getIPhasic();
}
return cfgDTO == null ? null : cfgDTO.getNPhasic();
}
private String resolveDisplayUnit(WaveDataDTO waveDataDTO) {
boolean hasVoltage = false;
boolean hasCurrent = false;
ComtradeCfgDTO cfgDTO = waveDataDTO.getComtradeCfgDTO();
if (cfgDTO != null && cfgDTO.getLstAnalogDTO() != null) {
for (AnalogDTO analogDTO : cfgDTO.getLstAnalogDTO()) {
if (analogDTO == null || StrUtil.isBlank(analogDTO.getSzUnitName())) {
continue;
}
if (isVoltageChannel(analogDTO)) {
hasVoltage = true;
} else {
hasCurrent = true;
}
}
}
if (!hasVoltage && !hasCurrent && waveDataDTO.getWaveTitle() != null) {
for (String title : waveDataDTO.getWaveTitle()) {
if (StrUtil.isBlank(title) || "x".equalsIgnoreCase(title)) {
continue;
}
if (title.substring(0, 1).equalsIgnoreCase("U")) {
hasVoltage = true;
} else {
hasCurrent = true;
}
}
}
if (hasVoltage && hasCurrent) {
return "kV/A";
}
if (hasVoltage) {
return "kV";
}
if (hasCurrent) {
return "A";
}
return null;
}
@Override
public WaveComtradeVectorResultVO parseComtradeVector(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) {
if (cfgStream == null || datStream == null) {
@@ -180,12 +243,39 @@ public class WaveServiceImpl implements WaveService {
}
private void applyWaveMetadata(WaveDataDTO waveDataDTO, WaveComtradeParseParam param) {
waveDataDTO.setPt(defaultIfNull(param.getPt(), DEFAULT_RATIO));
waveDataDTO.setCt(defaultIfNull(param.getCt(), DEFAULT_RATIO));
waveDataDTO.setPt(resolveCfgRatio(waveDataDTO, true, defaultIfNull(param.getPt(), DEFAULT_RATIO)));
waveDataDTO.setCt(resolveCfgRatio(waveDataDTO, false, defaultIfNull(param.getCt(), DEFAULT_RATIO)));
waveDataDTO.setPtType(param.getPtType() == null ? DEFAULT_PT_TYPE : param.getPtType());
waveDataDTO.setMonitorName(StrUtil.blankToDefault(param.getMonitorName(), "未命名测点"));
}
private double resolveCfgRatio(WaveDataDTO waveDataDTO, boolean voltage, double fallbackRatio) {
if (waveDataDTO == null || waveDataDTO.getComtradeCfgDTO() == null
|| waveDataDTO.getComtradeCfgDTO().getLstAnalogDTO() == null) {
return fallbackRatio;
}
for (AnalogDTO analogDTO : waveDataDTO.getComtradeCfgDTO().getLstAnalogDTO()) {
if (!hasValidCfgRatio(analogDTO) || isVoltageChannel(analogDTO) != voltage) {
continue;
}
// cfg 模拟量通道的 primary/secondary 是变比权威来源。
return analogDTO.getFPrimary() / analogDTO.getFSecondary();
}
return fallbackRatio;
}
private boolean hasValidCfgRatio(AnalogDTO analogDTO) {
return analogDTO != null
&& analogDTO.getFPrimary() != null
&& analogDTO.getFSecondary() != null
&& analogDTO.getFPrimary() > 0
&& analogDTO.getFSecondary() > 0;
}
private boolean isVoltageChannel(AnalogDTO analogDTO) {
return !StrUtil.equalsIgnoreCase(analogDTO.getSzUnitName(), "A");
}
private boolean shouldCalculateRms(WaveComtradeParseParam param) {
return param.getCalculateRms() == null || param.getCalculateRms();
}