From 32f56a87102eccacca68774288ae46c652854db8 Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Thu, 7 May 2026 16:36:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/ExperimentDataService.cs | 2 +- .../Services/NpoiReportExportService.cs | 116 ++++++++++++++---- 2 files changed, 91 insertions(+), 27 deletions(-) diff --git a/ConeCalorimeter/Services/ExperimentDataService.cs b/ConeCalorimeter/Services/ExperimentDataService.cs index 8394dc5..3caf0e1 100644 --- a/ConeCalorimeter/Services/ExperimentDataService.cs +++ b/ConeCalorimeter/Services/ExperimentDataService.cs @@ -114,7 +114,7 @@ public sealed class ExperimentDataService : IExperimentDataService { if (double.IsFinite(snapshot.HeatReleaseRate)) { - _accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds; + _accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds / 1000; } if (double.IsFinite(snapshot.SmokeProduction)) diff --git a/ConeCalorimeter/Services/NpoiReportExportService.cs b/ConeCalorimeter/Services/NpoiReportExportService.cs index 6b79957..7433797 100644 --- a/ConeCalorimeter/Services/NpoiReportExportService.cs +++ b/ConeCalorimeter/Services/NpoiReportExportService.cs @@ -44,12 +44,14 @@ public sealed class NpoiReportExportService : IReportExportService public void Export(string outputPath, ReportInput input, IReadOnlyList records) { - var exportRecords = records.Where(IsValidExperimentRecord).ToList(); + var exportRecords = PrepareExportRecords(records); if (exportRecords.Count == 0) { throw new InvalidOperationException("请先点击“测试开始”,产生有效实验数据后再导出报表。"); } + ValidateExportRecords(exportRecords); + var templatePath = FindTemplatePath(); using var templateStream = File.OpenRead(templatePath); var workbook = new HSSFWorkbook(templateStream); @@ -57,11 +59,21 @@ public sealed class NpoiReportExportService : IReportExportService FillReportSheet(workbook.GetSheet("Result_ISO"), "Result_ISO", input, exportRecords); FillReportSheet(workbook.GetSheet("Result_FTP"), "Result_FTP", input, exportRecords); FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords); + ValidateWorkbookDataSheet(workbook, exportRecords.Count); using var outputStream = File.Create(outputPath); workbook.Write(outputStream); } + private static List PrepareExportRecords(IReadOnlyList records) + { + return records + .Where(IsValidExperimentRecord) + .OrderBy(record => record.TestSeconds) + .ThenBy(record => record.Timestamp) + .ToList(); + } + private static void FillReportSheet( ISheet? sheet, string sheetName, @@ -147,13 +159,12 @@ public sealed class NpoiReportExportService : IReportExportService headerRow.CreateCell(i).SetCellValue(DataHeaders[i]); } - var figra = CalculatePeakGrowthIndex(records, record => record.HeatReleaseRate, 1000); - var smogra = CalculatePeakGrowthIndex(records, record => record.SmokeProduction, 1); - for (var i = 0; i < records.Count; i++) { var row = sheet.CreateRow(i + 1); var record = records[i]; + var figra = CalculateGrowthIndex(record.TestSeconds, record.HeatReleaseRate, 1000); + var smogra = CalculateGrowthIndex(record.TestSeconds, record.SmokeProduction, 1); // Keep columns A-G fixed; the Excel template charts reference these positions. SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN); SetNumeric(row, 1, record.HeatReleaseRate); @@ -191,32 +202,85 @@ public sealed class NpoiReportExportService : IReportExportService } } - private static double CalculatePeakGrowthIndex( - IReadOnlyList records, - Func selector, - double multiplier) + private static double CalculateGrowthIndex(int testSeconds, double value, double multiplier) { - double? peak = null; - var peakSeconds = 0; - - foreach (var record in records) + if (testSeconds <= 0 || !double.IsFinite(value) || value < 0) { - var value = selector(record); - if (record.TestSeconds <= 0 || !double.IsFinite(value) || value < 0) - { - continue; - } - - if (!peak.HasValue || value > peak.Value) - { - peak = value; - peakSeconds = record.TestSeconds; - } + return double.NaN; } - return peak.HasValue && peakSeconds > 0 - ? peak.Value * multiplier / peakSeconds - : double.NaN; + return value * multiplier / testSeconds; + } + + private static void ValidateExportRecords(IReadOnlyList records) + { + var previousSeconds = -1; + foreach (var record in records) + { + if (record.TestSeconds < 0) + { + throw new InvalidOperationException("导出数据时间列包含负数,无法生成准确曲线。"); + } + + if (record.TestSeconds < previousSeconds) + { + throw new InvalidOperationException("导出数据时间列存在倒退,无法生成准确曲线。"); + } + + previousSeconds = record.TestSeconds; + } + + RequireAnyFiniteCurveValue(records, "热释放", record => record.HeatReleaseRate); + RequireAnyFiniteCurveValue(records, "产烟率", record => record.SmokeProduction); + RequireAnyFiniteCurveValue(records, "总热释放", record => record.TotalHeatRelease); + RequireAnyFiniteCurveValue(records, "总产烟量", record => record.TotalSmoke); + } + + private static void RequireAnyFiniteCurveValue( + IReadOnlyList records, + string label, + Func selector) + { + if (!records.Any(record => double.IsFinite(selector(record)))) + { + throw new InvalidOperationException($"导出数据缺少有效曲线列:{label}。请确认实验数据采集正常。"); + } + } + + private static void ValidateWorkbookDataSheet(IWorkbook workbook, int expectedDataRows) + { + var sheet = workbook.GetSheet("Data") + ?? throw new InvalidOperationException("导出报表缺少 Data 工作表,曲线无法显示。"); + + if (sheet.LastRowNum < expectedDataRows) + { + throw new InvalidOperationException("导出报表 Data 工作表行数不足,曲线数据不完整。"); + } + + for (var columnIndex = 0; columnIndex <= 6; columnIndex++) + { + var header = sheet.GetRow(0)?.GetCell(columnIndex); + if (CellText(header) != DataHeaders[columnIndex]) + { + throw new InvalidOperationException($"导出报表 Data 工作表缺少曲线列:{DataHeaders[columnIndex]}。"); + } + + var hasNumericValue = false; + for (var rowIndex = 1; rowIndex <= expectedDataRows; rowIndex++) + { + var cell = sheet.GetRow(rowIndex)?.GetCell(columnIndex); + if (cell?.CellType == CellType.Numeric && double.IsFinite(cell.NumericCellValue)) + { + hasNumericValue = true; + break; + } + } + + if (!hasNumericValue) + { + throw new InvalidOperationException($"导出报表 Data 工作表曲线列没有有效数值:{DataHeaders[columnIndex]}。"); + } + } } private static bool SetValueBesideLabel(ISheet sheet, string label, string value)