更新
This commit is contained in:
@@ -114,7 +114,7 @@ public sealed class ExperimentDataService : IExperimentDataService
|
|||||||
{
|
{
|
||||||
if (double.IsFinite(snapshot.HeatReleaseRate))
|
if (double.IsFinite(snapshot.HeatReleaseRate))
|
||||||
{
|
{
|
||||||
_accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds;
|
_accumulatedTotalHeatRelease += snapshot.HeatReleaseRate * deltaSeconds / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (double.IsFinite(snapshot.SmokeProduction))
|
if (double.IsFinite(snapshot.SmokeProduction))
|
||||||
|
|||||||
@@ -44,12 +44,14 @@ public sealed class NpoiReportExportService : IReportExportService
|
|||||||
|
|
||||||
public void Export(string outputPath, ReportInput input, IReadOnlyList<RealtimeDataRecord> records)
|
public void Export(string outputPath, ReportInput input, IReadOnlyList<RealtimeDataRecord> records)
|
||||||
{
|
{
|
||||||
var exportRecords = records.Where(IsValidExperimentRecord).ToList();
|
var exportRecords = PrepareExportRecords(records);
|
||||||
if (exportRecords.Count == 0)
|
if (exportRecords.Count == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("请先点击“测试开始”,产生有效实验数据后再导出报表。");
|
throw new InvalidOperationException("请先点击“测试开始”,产生有效实验数据后再导出报表。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValidateExportRecords(exportRecords);
|
||||||
|
|
||||||
var templatePath = FindTemplatePath();
|
var templatePath = FindTemplatePath();
|
||||||
using var templateStream = File.OpenRead(templatePath);
|
using var templateStream = File.OpenRead(templatePath);
|
||||||
var workbook = new HSSFWorkbook(templateStream);
|
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_ISO"), "Result_ISO", input, exportRecords);
|
||||||
FillReportSheet(workbook.GetSheet("Result_FTP"), "Result_FTP", input, exportRecords);
|
FillReportSheet(workbook.GetSheet("Result_FTP"), "Result_FTP", input, exportRecords);
|
||||||
FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords);
|
FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords);
|
||||||
|
ValidateWorkbookDataSheet(workbook, exportRecords.Count);
|
||||||
|
|
||||||
using var outputStream = File.Create(outputPath);
|
using var outputStream = File.Create(outputPath);
|
||||||
workbook.Write(outputStream);
|
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(
|
private static void FillReportSheet(
|
||||||
ISheet? sheet,
|
ISheet? sheet,
|
||||||
string sheetName,
|
string sheetName,
|
||||||
@@ -147,13 +159,12 @@ public sealed class NpoiReportExportService : IReportExportService
|
|||||||
headerRow.CreateCell(i).SetCellValue(DataHeaders[i]);
|
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++)
|
for (var i = 0; i < records.Count; i++)
|
||||||
{
|
{
|
||||||
var row = sheet.CreateRow(i + 1);
|
var row = sheet.CreateRow(i + 1);
|
||||||
var record = records[i];
|
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.
|
// Keep columns A-G fixed; the Excel template charts reference these positions.
|
||||||
SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
|
SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
|
||||||
SetNumeric(row, 1, record.HeatReleaseRate);
|
SetNumeric(row, 1, record.HeatReleaseRate);
|
||||||
@@ -191,32 +202,85 @@ public sealed class NpoiReportExportService : IReportExportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double CalculatePeakGrowthIndex(
|
private static double CalculateGrowthIndex(int testSeconds, double value, double multiplier)
|
||||||
IReadOnlyList<RealtimeDataRecord> records,
|
|
||||||
Func<RealtimeDataRecord, double> selector,
|
|
||||||
double multiplier)
|
|
||||||
{
|
{
|
||||||
double? peak = null;
|
if (testSeconds <= 0 || !double.IsFinite(value) || value < 0)
|
||||||
var peakSeconds = 0;
|
|
||||||
|
|
||||||
foreach (var record in records)
|
|
||||||
{
|
{
|
||||||
var value = selector(record);
|
return double.NaN;
|
||||||
if (record.TestSeconds <= 0 || !double.IsFinite(value) || value < 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!peak.HasValue || value > peak.Value)
|
|
||||||
{
|
|
||||||
peak = value;
|
|
||||||
peakSeconds = record.TestSeconds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return peak.HasValue && peakSeconds > 0
|
return value * multiplier / testSeconds;
|
||||||
? peak.Value * multiplier / peakSeconds
|
}
|
||||||
: double.NaN;
|
|
||||||
|
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)
|
private static bool SetValueBesideLabel(ISheet sheet, string label, string value)
|
||||||
|
|||||||
Reference in New Issue
Block a user