import fs from "fs"; import PizZip from "pizzip"; import { registerImageBuffer, renderDocxReport, } from "../src/server/reportService/docxReportBuilder.js"; import { mapHealthReportContext } from "../src/server/reportService/healthReportMapper.js"; import { buildFarmCategoryBarOption, buildFarmLevelPieOption, buildTurbineTrendChartOptions, } from "../src/server/reportService/healthChartBuilder.js"; import { renderEchartsOption } from "../src/server/reportService/echartsRenderer.js"; import { initChartService, shutdownChartService, } from "../src/server/utils/chartService/index.js"; function assert(condition, message) { if (!condition) throw new Error(message); } const mapped = mapHealthReportContext( { healthscoresWindVO: { fieldName: "鄂能风电场", fieldCode: "SVLgVZfi", sourceDatetime: "2026-09-01", totalCount: 1, overallScore: 80, }, healthOverviewListVOList: [], }, { fieldName: "新疆十三师风电场", fieldMeta: { fieldName: "新疆十三师风电场", provinceName: "大唐新疆能源有限公司", machineTypeCode: "GW", }, }, ); assert( mapped.Province === "新疆" && mapped.Wind_farm === "十三师风电场" && mapped.Company === "大唐新疆能源有限公司", `健康报告公司和风场应取选中风场,实际 Province=${mapped.Province} Wind_farm=${mapped.Wind_farm} Company=${mapped.Company}`, ); assert( buildFarmLevelPieOption({ overallScore: -1, excellentCount: 0, goodCount: 0, fairCount: 0, poorCount: 0, }) === null, "风场未评估时不应生成综合评分饼图", ); assert( buildFarmCategoryBarOption({ overallScore: -1, systemScore: -1, componentScore: -1, structureScore: -1, }) === null, "风场分类评分全未评估时不应生成柱状图", ); const partialBar = buildFarmCategoryBarOption({ systemScore: 75, componentScore: 70, structureScore: -1, }); assert(partialBar, "有有效分类评分时应生成柱状图"); assert( JSON.stringify(partialBar.xAxis.data) === JSON.stringify(["系统健康", "部件健康"]), `未评估分类不应出现在柱状图中,实际为 ${JSON.stringify(partialBar.xAxis.data)}`, ); const emptyStructuralPoints = [ { birthday: "2026-08-01", overallScore: 80, rotorScore: -1, towerScore: -1, yawSystemScore: 72, pitchSystemScore: 71, hydraulicSystemScore: 70, controlSystemScore: 69, generatorScore: 68, gearboxScore: 67, mainShaftScore: 66, converterScore: 65, }, ]; const emptyStructuralCharts = buildTurbineTrendChartOptions( emptyStructuralPoints, emptyStructuralPoints[0], ); assert( !emptyStructuralCharts.some((item) => item.key === "structural"), "结构未评估时不应生成结构健康趋势图", ); assert( emptyStructuralCharts.some((item) => item.key === "system"), "系统有数据时应保留系统健康趋势图", ); await initChartService(); const map = {}; registerImageBuffer( map, "health_fig1", await renderEchartsOption( buildFarmLevelPieOption({ overallScore: 80, excellentCount: 2, goodCount: 1, fairCount: 1, poorCount: 0, }), { width: 760, height: 420 }, ), ); registerImageBuffer( map, "health_fig2", await renderEchartsOption( buildFarmCategoryBarOption({ overallScore: 80, systemScore: 75, componentScore: 70, structureScore: 72, }), { width: 760, height: 360 }, ), ); const trendPoints = [ { birthday: "2026-08-01", overallScore: 80, rotorScore: 70, towerScore: 75, yawSystemScore: 72, pitchSystemScore: 71, hydraulicSystemScore: 70, controlSystemScore: 69, generatorScore: 68, gearboxScore: 67, mainShaftScore: 66, converterScore: 65, }, { birthday: "2026-08-02", overallScore: 82, rotorScore: 71, towerScore: 75, yawSystemScore: 73, pitchSystemScore: 72, hydraulicSystemScore: 71, controlSystemScore: 70, generatorScore: 69, gearboxScore: 68, mainShaftScore: 67, converterScore: 66, }, ]; const trendCharts = buildTurbineTrendChartOptions(trendPoints, trendPoints[1]); assert(trendCharts.length === 4, "完整趋势数据应生成 4 张趋势图"); const renderedCharts = []; for (const chart of trendCharts) { const imageKey = `health_trend_WT01_${chart.key}`; registerImageBuffer( map, imageKey, await renderEchartsOption(chart.option, { width: 760, height: chart.key === "overall" ? 260 : 300, }), ); renderedCharts.push({ chart_title: chart.title, trend_image: imageKey, }); } const out = await renderDocxReport({ templateName: "health-report-template.docx", skipPatch: false, imageBufferMap: map, renderData: { 风场名称: "测试风场", farm_name: "十三师风电场", Province: "新疆", Wind_farm: "十三师风电场", Year_now: "2026", Month_now: "08", machineTypeCode: "TEST", Company: "大唐新疆能源有限公司", field_id: "F1", create_time: "2026-08-13", source_datetime: "2026-08-12", eval_start_date: "2026-07-14", eval_end_date: "2026-08-12", eval_date_range: "2026-07-14至2026-08-12(滚动30天)", turbine_count: "1", valid_turbine_count: "1", turbine_types: "TEST", overall_score: "80", system_score: "75", component_score: "70", "structure_score/未评估": "72", excellent_count: "2", good_count: "1", fair_count: "1", poor_count: "0", overall_level_text: "良", main_level: "良", level_distribution_text: "优2台", overall_summary_text: "测试", focus_engine_list: "WT01", chart_fig1: "health_fig1", chart_fig2: "health_fig2", show_chart_fig1: [{}], show_chart_fig2: [{}], turbine_trends: [ { engine_title: "WT01 风机健康趋势图", charts: renderedCharts }, ], }, }); fs.writeFileSync("templates/_smoke_health_report.docx", out); const zip = new PizZip(out); const headerTexts = Object.keys(zip.files) .filter((name) => /^word\/header\d+\.xml$/.test(name)) .sort() .map((name) => { const xml = zip.file(name).asText(); const texts = []; xml.replace(/]*)?>([\s\S]*?)<\/w:t>/g, (_, text) => { texts.push(text); return _; }); return { name, plain: texts.join("").trim() }; }); const filledHeaders = headerTexts.filter((item) => item.plain); assert(filledHeaders.length > 0, "健康报告应有非空页眉"); filledHeaders.forEach((item) => { assert( item.plain === "大唐新疆能源有限公司十三师风电场风机健康评估报告", `${item.name} 页眉应为完整公司+风场标题,实际为「${item.plain}」`, ); }); const footerTexts = Object.keys(zip.files) .filter((name) => /^word\/footer\d+\.xml$/.test(name)) .sort() .map((name) => { const xml = zip.file(name).asText(); const texts = []; xml.replace(/]*)?>([\s\S]*?)<\/w:t>/g, (_, text) => { texts.push(text); return _; }); return { name, plain: texts.join(""), hasPage: /PAGE/.test(xml), xml }; }); footerTexts.forEach((item) => { assert(!item.hasPage, `${item.name} 不应含页码域`); assert(!item.plain.trim(), `${item.name} 页脚应为空,实际为「${item.plain}」`); }); const coverXml = zip.file("word/document.xml").asText(); assert( coverXml.includes("新疆十三师风电场") && !coverXml.includes("新疆十三师风电场风电场"), "封面风场名不应重复「风电场」", ); console.log("smoke ok, size=", out.length); await shutdownChartService(); process.exit(0);