|
|
@@ -135,7 +135,10 @@ const duplicateLocationCover = mapAnomalyCoverFields({
|
|
|
provinceName: "大唐新疆公司",
|
|
|
cityName: "大唐新疆公司",
|
|
|
},
|
|
|
- modelList: [{ machineTypeCode: "GW82-1500" }, { machineTypeCode: "GW82-1500" }],
|
|
|
+ modelList: [
|
|
|
+ { machineTypeCode: "GW82-1500" },
|
|
|
+ { machineTypeCode: "GW82-1500" },
|
|
|
+ ],
|
|
|
});
|
|
|
assert(
|
|
|
duplicateLocationCover.Overview_of_the_Wind_Farm ===
|
|
|
@@ -247,10 +250,9 @@ if (fs.existsSync(templateSource)) {
|
|
|
assert(tocHas42, "目录应保留 4.2 按功能诊断项统计 条目");
|
|
|
assert(!tocHasFigure, "目录中不应出现图3/图4 图题");
|
|
|
assert(!tocHasModuleTag, "目录中不应插入模块条件标签");
|
|
|
- const detectorLoopOpen =
|
|
|
- patchedXml.indexOf(
|
|
|
- "{#zn-techcn-replace-tags-data_detector_anomaly-generalFiles}",
|
|
|
- );
|
|
|
+ const detectorLoopOpen = patchedXml.indexOf(
|
|
|
+ "{#zn-techcn-replace-tags-data_detector_anomaly-generalFiles}",
|
|
|
+ );
|
|
|
const detectorCaption = patchedXml.indexOf(
|
|
|
"{figure_caption}",
|
|
|
detectorLoopOpen,
|
|
|
@@ -480,10 +482,7 @@ assert(
|
|
|
emphasisAnomaly.itemStyle?.color === "#FF1E1E",
|
|
|
"报告全场散点异常点应为亮红",
|
|
|
);
|
|
|
-assert(
|
|
|
- !emphasisAnomaly.itemStyle?.borderWidth,
|
|
|
- "报告全场散点异常点不描白边",
|
|
|
-);
|
|
|
+assert(!emphasisAnomaly.itemStyle?.borderWidth, "报告全场散点异常点不描白边");
|
|
|
assert(farmScatterOption.grid3D, "风功率散点应输出 3D 图表");
|
|
|
|
|
|
const singleScatterOption = buildDetectorPlotOption(
|
|
|
@@ -516,7 +515,8 @@ assert(curveSeries?.type === "line", "近90日功率曲线应渲染为折线而
|
|
|
assert(curveSeries?.showSymbol === false, "近90日功率曲线折线不应显示散点符号");
|
|
|
|
|
|
assert(
|
|
|
- diagnosticSeriesColor({ label: "正常运行 (271)" }) === DIAGNOSTIC_COLORS.normal &&
|
|
|
+ diagnosticSeriesColor({ label: "正常运行 (271)" }) ===
|
|
|
+ DIAGNOSTIC_COLORS.normal &&
|
|
|
diagnosticSeriesColor({ label: "异常点" }) === DIAGNOSTIC_COLORS.anomaly &&
|
|
|
diagnosticSeriesColor({ label: "降载" }) === DIAGNOSTIC_COLORS.deload &&
|
|
|
diagnosticSeriesColor({ label: "停机" }) === DIAGNOSTIC_COLORS.shutdown,
|
|
|
@@ -632,11 +632,13 @@ assert(
|
|
|
);
|
|
|
assert(
|
|
|
Number(
|
|
|
- (singleStatusScatter.series || []).find((item) => item.name === "合同功率曲线")
|
|
|
- ?.z,
|
|
|
+ (singleStatusScatter.series || []).find(
|
|
|
+ (item) => item.name === "合同功率曲线",
|
|
|
+ )?.z,
|
|
|
) <
|
|
|
Number(
|
|
|
- (singleStatusScatter.series || []).find((item) => item.name === "正常")?.z,
|
|
|
+ (singleStatusScatter.series || []).find((item) => item.name === "正常")
|
|
|
+ ?.z,
|
|
|
),
|
|
|
"单机散点图合同功率曲线应在散点下方",
|
|
|
);
|
|
|
@@ -1106,6 +1108,44 @@ assert(
|
|
|
.length >= 2,
|
|
|
"阈值线应拉满时间轴",
|
|
|
);
|
|
|
+const loneRateOption = listDetectorPlotOptions(
|
|
|
+ {
|
|
|
+ panelTitle: "累计降载率随时间变化",
|
|
|
+ xaxis: "时间",
|
|
|
+ yaxis: "累计降载率 (%)",
|
|
|
+ yrange: [0, 100],
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ label: "累计降载率",
|
|
|
+ xData: ["2026-10-13 07:05:00"],
|
|
|
+ yData: [0],
|
|
|
+ mode: "lines",
|
|
|
+ timeData: ["2026-10-13 07:05:00"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "正常上限 5%",
|
|
|
+ xData: ["2026-10-13 07:05:00", "2026-10-13 07:05:00"],
|
|
|
+ yData: [5, 5],
|
|
|
+ mode: "lines",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "频繁阈值 20%",
|
|
|
+ xData: ["2026-10-13 07:05:00", "2026-10-13 07:05:00"],
|
|
|
+ yData: [20, 20],
|
|
|
+ mode: "lines",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ "002 降载判定分析",
|
|
|
+ "anomalyDeloadPO",
|
|
|
+ { templateKey: "ctrl_deload" },
|
|
|
+);
|
|
|
+assert(loneRateOption.length === 1, "单时刻累计降载率应出图");
|
|
|
+loneRateOption[0].series.forEach((item) => {
|
|
|
+ assert(item.showSymbol === true, `${item.name} 只有一个时刻时应画点`);
|
|
|
+ assert(item.symbol === "circle", `${item.name} 单点符号应为圆点`);
|
|
|
+ assert((item.data || []).length === 1, `${item.name} 重合时刻应收成一个点`);
|
|
|
+});
|
|
|
|
|
|
const opFarm = buildFarmPlotJsons(
|
|
|
[
|
|
|
@@ -1438,7 +1478,8 @@ assert(
|
|
|
);
|
|
|
assert(
|
|
|
farmWithContract[0].data.some(
|
|
|
- (row) => row.__shared && row.label === "合同功率曲线" && row.mode === "lines",
|
|
|
+ (row) =>
|
|
|
+ row.__shared && row.label === "合同功率曲线" && row.mode === "lines",
|
|
|
),
|
|
|
"总图应绘制 referenceCurve 合同功率曲线",
|
|
|
);
|
|
|
@@ -1473,6 +1514,80 @@ assert(
|
|
|
);
|
|
|
assert(contractSeries.lineStyle?.type === "solid", "合同功率曲线应为实线");
|
|
|
|
|
|
+const multiModelFarmJson = {
|
|
|
+ detector: "wind_power_scatter",
|
|
|
+ chartType: "series",
|
|
|
+ xaxis: "风速 (m/s)",
|
|
|
+ yaxis: "有功功率 (kW)",
|
|
|
+ turbines: [
|
|
|
+ {
|
|
|
+ turbine: "C07",
|
|
|
+ model: "S48-750",
|
|
|
+ anomaly: true,
|
|
|
+ xType: "number",
|
|
|
+ xData: [3.5, 7.5],
|
|
|
+ yData: [110, 710],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ referenceCurves: [
|
|
|
+ {
|
|
|
+ label: "合同功率曲线(EN156-3300)",
|
|
|
+ model: "EN156-3300",
|
|
|
+ xData: [3, 12],
|
|
|
+ yData: [51.6, 900],
|
|
|
+ mode: "lines",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "合同功率曲线(S48-750)",
|
|
|
+ model: "S48-750",
|
|
|
+ xData: [3, 12],
|
|
|
+ yData: [15.2, 750],
|
|
|
+ mode: "lines",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+};
|
|
|
+const multiModelPanels = farmAggregateToPanels(multiModelFarmJson);
|
|
|
+assert(
|
|
|
+ multiModelPanels[0].data.filter((row) => row.label === "合同功率曲线")
|
|
|
+ .length === 2,
|
|
|
+ "不同机型的合同功率曲线都应保留",
|
|
|
+);
|
|
|
+const multiModelOption = buildDetectorPlotOption(
|
|
|
+ multiModelFarmJson,
|
|
|
+ "全场风功率散点分析",
|
|
|
+ "anomalyScatterPO",
|
|
|
+);
|
|
|
+const modelContracts = (multiModelOption.series || []).filter(
|
|
|
+ (item) => item.name === "合同功率曲线",
|
|
|
+);
|
|
|
+assert(modelContracts.length === 2, "总图应画出两条机型合同功率曲线");
|
|
|
+const s48Contract = modelContracts.find((item) => item.__model === "S48-750");
|
|
|
+assert(s48Contract, "合同功率曲线应带上机型");
|
|
|
+const contractHover = multiModelOption.tooltip.formatter({
|
|
|
+ seriesIndex: multiModelOption.series.indexOf(s48Contract),
|
|
|
+ seriesName: s48Contract.name,
|
|
|
+ value: s48Contract.data[0],
|
|
|
+ marker: "",
|
|
|
+});
|
|
|
+assert(
|
|
|
+ String(contractHover).includes("机型: S48-750"),
|
|
|
+ "合同功率曲线划过提示应显示机型",
|
|
|
+);
|
|
|
+const s48Turbine = (multiModelOption.series || []).find(
|
|
|
+ (item) => item.__model === "S48-750" && item.name !== "合同功率曲线",
|
|
|
+);
|
|
|
+assert(s48Turbine, "风机数据应带上机型");
|
|
|
+const turbineHover = multiModelOption.tooltip.formatter({
|
|
|
+ seriesIndex: multiModelOption.series.indexOf(s48Turbine),
|
|
|
+ seriesName: s48Turbine.name,
|
|
|
+ value: s48Turbine.data[0],
|
|
|
+ marker: "",
|
|
|
+});
|
|
|
+assert(
|
|
|
+ String(turbineHover).includes("机型: S48-750"),
|
|
|
+ "风机数据划过提示应显示机型",
|
|
|
+);
|
|
|
+
|
|
|
const misplacedX = buildDetectorPlotOption(
|
|
|
{
|
|
|
title: "DT01 功率曲线分析",
|
|
|
@@ -1597,8 +1712,8 @@ const rangedPitch = listDetectorPlotOptions(
|
|
|
{ templateKey: "pitch_coord" },
|
|
|
);
|
|
|
assert(
|
|
|
- rangedPitch[0].xAxis.min === 1000 && rangedPitch[0].xAxis.max == null,
|
|
|
- "变桨协调分图横轴应从 1000 起,上限自适应",
|
|
|
+ rangedPitch[0].xAxis.min === 5 && rangedPitch[0].xAxis.max === 18,
|
|
|
+ "变桨协调分图横轴应按 xrange",
|
|
|
);
|
|
|
assert(
|
|
|
rangedPitch[0].yAxis.min === 0 && rangedPitch[0].yAxis.max === 1500,
|
|
|
@@ -1939,7 +2054,8 @@ assert(
|
|
|
"运行状态转速-功率分图纵轴应自适应",
|
|
|
);
|
|
|
assert(
|
|
|
- opStateOptions[0].xAxis.scale === true && opStateOptions[0].yAxis.scale === true,
|
|
|
+ opStateOptions[0].xAxis.scale === true &&
|
|
|
+ opStateOptions[0].yAxis.scale === true,
|
|
|
"运行状态分图应按数据自适应缩放",
|
|
|
);
|
|
|
assert(
|
|
|
@@ -1947,7 +2063,8 @@ assert(
|
|
|
"运行状态没有 xrange/yrange 的分图应自适应",
|
|
|
);
|
|
|
assert(
|
|
|
- opStateOptions[1].xAxis.scale === true && opStateOptions[1].yAxis.scale === true,
|
|
|
+ opStateOptions[1].xAxis.scale === true &&
|
|
|
+ opStateOptions[1].yAxis.scale === true,
|
|
|
"运行状态缺省轴范围时应自适应缩放",
|
|
|
);
|
|
|
assert(
|
|
|
@@ -2003,9 +2120,10 @@ assert(
|
|
|
"风场功率曲线总图应画出正常机组",
|
|
|
);
|
|
|
assert(
|
|
|
- !(Array.isArray(farmCurveOptions[0].legend)
|
|
|
- ? farmCurveOptions[0].legend
|
|
|
- : [farmCurveOptions[0].legend]
|
|
|
+ !(
|
|
|
+ Array.isArray(farmCurveOptions[0].legend)
|
|
|
+ ? farmCurveOptions[0].legend
|
|
|
+ : [farmCurveOptions[0].legend]
|
|
|
).some((item) => (item?.data || []).some((name) => /01号/.test(name))),
|
|
|
"风场功率曲线总图图例不展示正常机组",
|
|
|
);
|
|
|
@@ -2158,8 +2276,8 @@ assert(
|
|
|
"偏航次数总图图例应列出正常色和全部异常机组",
|
|
|
);
|
|
|
assert(
|
|
|
- farmYawCountOptions[0].series.find((item) => item.name === "正常机组")?.data?.[1]
|
|
|
- ?.itemStyle?.color === DIAGNOSTIC_COLORS.normal,
|
|
|
+ farmYawCountOptions[0].series.find((item) => item.name === "正常机组")
|
|
|
+ ?.data?.[1]?.itemStyle?.color === DIAGNOSTIC_COLORS.normal,
|
|
|
"偏航次数正常机组应统一蓝色",
|
|
|
);
|
|
|
assert(
|
|
|
@@ -2627,10 +2745,7 @@ assert(
|
|
|
formatSensorTypeText({ sensorAnomalyType: "1,5" }) === "功率异常、扭矩异常",
|
|
|
"多个数据感知类型编码应按顿号拼接",
|
|
|
);
|
|
|
-assert(
|
|
|
- formatSensorTypeText({}) === "暂无异常",
|
|
|
- "无数据感知异常时应写暂无异常",
|
|
|
-);
|
|
|
+assert(formatSensorTypeText({}) === "暂无异常", "无数据感知异常时应写暂无异常");
|
|
|
|
|
|
const sensorHeat = buildSensorHeatmapData([
|
|
|
{
|
|
|
@@ -2651,8 +2766,8 @@ assert(
|
|
|
);
|
|
|
assert(sensorHeat.yLabels[0] === "功率异常", "数据感知热力图首行应为功率异常");
|
|
|
assert(
|
|
|
- sensorHeat.yLabels.includes("偏航角、扭缆角、偏航误差等偏航"),
|
|
|
- "数据感知热力图应含偏航角、扭缆角、偏航误差等偏航",
|
|
|
+ sensorHeat.yLabels.includes("偏航扭缆异常"),
|
|
|
+ "数据感知热力图应含偏航扭缆异常",
|
|
|
);
|
|
|
assert(
|
|
|
sensorHeat.yLabels.includes("大部件温度异常"),
|
|
|
@@ -2705,7 +2820,9 @@ const heatRamp = (label) => {
|
|
|
(item.data || []).some((cell) => cell.value[1] === y),
|
|
|
);
|
|
|
const maps = [].concat(detectorHeatOption.visualMap || []);
|
|
|
- return (maps.find((item) => item.seriesIndex === seriesIndex)?.inRange?.color || []).join(",");
|
|
|
+ return (
|
|
|
+ maps.find((item) => item.seriesIndex === seriesIndex)?.inRange?.color || []
|
|
|
+ ).join(",");
|
|
|
};
|
|
|
assert(
|
|
|
Array.isArray(detectorHeatOption.visualMap) &&
|
|
|
@@ -2758,10 +2875,25 @@ const summaryMetricRows = buildAnomalySummaryRows([
|
|
|
{
|
|
|
turbineName: "009",
|
|
|
detectorHits: [
|
|
|
- { title: "静态偏航误差分析", templateKey: "yaw_error", points: 4, rate: 8 },
|
|
|
+ {
|
|
|
+ title: "静态偏航误差分析",
|
|
|
+ templateKey: "yaw_error",
|
|
|
+ points: 4,
|
|
|
+ rate: 8,
|
|
|
+ },
|
|
|
{ title: "偏航次数分析", templateKey: "yaw_count", points: 2, rate: 110 },
|
|
|
- { title: "最小桨距角分析", templateKey: "pitch_min", points: 1, rate: 1.5 },
|
|
|
- { title: "变桨一致性分析", templateKey: "pitch_regulation", points: 3, rate: 0.021 },
|
|
|
+ {
|
|
|
+ title: "最小桨距角分析",
|
|
|
+ templateKey: "pitch_min",
|
|
|
+ points: 1,
|
|
|
+ rate: 1.5,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "变桨一致性分析",
|
|
|
+ templateKey: "pitch_regulation",
|
|
|
+ points: 3,
|
|
|
+ rate: 0.021,
|
|
|
+ },
|
|
|
],
|
|
|
},
|
|
|
]);
|
|
|
@@ -3168,17 +3300,11 @@ assert(
|
|
|
smokeDocText.includes("数据感知异常类型"),
|
|
|
"报告中检测器明细表应包含数据感知异常类型列",
|
|
|
);
|
|
|
-assert(
|
|
|
- smokeDocText.includes("大部件温度异常"),
|
|
|
- "报告应输出大部件温度异常",
|
|
|
-);
|
|
|
+assert(smokeDocText.includes("大部件温度异常"), "报告应输出大部件温度异常");
|
|
|
assert(
|
|
|
smokeDocText.includes("大部件温度(齿轮箱油温、发电机轴承"),
|
|
|
"表3-1 应包含大部件温度关联测点说明",
|
|
|
);
|
|
|
-assert(
|
|
|
- !/\{[A-Za-z_]/.test(smokeDocText),
|
|
|
- "报告中不应残留模板占位符",
|
|
|
-);
|
|
|
+assert(!/\{[A-Za-z_]/.test(smokeDocText), "报告中不应残留模板占位符");
|
|
|
|
|
|
await shutdownChartService();
|