更新
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -44,12 +44,14 @@ public sealed class NpoiReportExportService : IReportExportService
|
||||
|
||||
public void Export(string outputPath, ReportInput input, IReadOnlyList<RealtimeDataRecord> 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<RealtimeDataRecord> PrepareExportRecords(IReadOnlyList<RealtimeDataRecord> 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<RealtimeDataRecord> records,
|
||||
Func<RealtimeDataRecord, double> 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<RealtimeDataRecord> 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<RealtimeDataRecord> records,
|
||||
string label,
|
||||
Func<RealtimeDataRecord, double> 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)
|
||||
|
||||
Reference in New Issue
Block a user