更新20260529

This commit is contained in:
GukSang.Jin
2026-05-29 16:18:10 +08:00
parent 57b5b09a05
commit 3701c3cb02
4 changed files with 162 additions and 52 deletions

View File

@@ -2,7 +2,7 @@ namespace ConeCalorimeter.Models;
public sealed class ReportInput public sealed class ReportInput
{ {
public string LaboratoryName { get; set; } = "检测中心"; public string LaboratoryName { get; set; } = string.Empty;
public string Operator { get; set; } = string.Empty; public string Operator { get; set; } = string.Empty;
@@ -16,19 +16,19 @@ public sealed class ReportInput
public string SampleName { get; set; } = string.Empty; public string SampleName { get; set; } = string.Empty;
public string Thickness { get; set; } = "3 mm"; public string Thickness { get; set; } = string.Empty;
public string InitialMass { get; set; } = string.Empty; public string InitialMass { get; set; } = string.Empty;
public string IrradiatedArea { get; set; } = "100 c㎡"; public string IrradiatedArea { get; set; } = string.Empty;
public string Irradiance { get; set; } = "35 kW/㎡"; public string Irradiance { get; set; } = string.Empty;
public string IrradianceDistance { get; set; } = "25 mm"; public string IrradianceDistance { get; set; } = string.Empty;
public string SampleOrientation { get; set; } = "Vertical"; public string SampleOrientation { get; set; } = string.Empty;
public string SampleCount { get; set; } = "1"; public string SampleCount { get; set; } = string.Empty;
public string RequiredExhaustFlow { get; set; } = string.Empty; public string RequiredExhaustFlow { get; set; } = string.Empty;
@@ -46,7 +46,7 @@ public sealed class ReportInput
public string TestCondition { get; set; } = string.Empty; public string TestCondition { get; set; } = string.Empty;
public string TestStandard { get; set; } = "ISO 5660-1"; public string TestStandard { get; set; } = string.Empty;
public DateTime TestDate { get; set; } = DateTime.Today; public DateTime TestDate { get; set; } = DateTime.Today;
@@ -88,5 +88,5 @@ public sealed class ReportInput
public string BaselineCO2Oxygen { get; set; } = string.Empty; public string BaselineCO2Oxygen { get; set; } = string.Empty;
public string EquivalentHeatValue { get; set; } = "13.1 MJ/kg"; public string EquivalentHeatValue { get; set; } = string.Empty;
} }

View File

@@ -58,7 +58,6 @@ public sealed class ExperimentDataService : IExperimentDataService
_initialMass = CurrentSnapshot.CurrentMass; _initialMass = CurrentSnapshot.CurrentMass;
} }
RecordElapsedSnapshots();
QueueImmediateDevicePoll(); QueueImmediateDevicePoll();
Log.Information( Log.Information(
@@ -69,11 +68,6 @@ public sealed class ExperimentDataService : IExperimentDataService
public void StopTest() public void StopTest()
{ {
if (_isTestRunning)
{
RecordElapsedSnapshots();
}
_isTestRunning = false; _isTestRunning = false;
_testClock.Stop(); _testClock.Stop();
PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot)); PublishSnapshot(BuildIdleSnapshot(CurrentSnapshot));
@@ -98,11 +92,6 @@ public sealed class ExperimentDataService : IExperimentDataService
private async Task TickAsync() private async Task TickAsync()
{ {
if (_isTestRunning)
{
RecordElapsedSnapshots();
}
await RefreshDeviceSnapshotAsync(); await RefreshDeviceSnapshotAsync();
} }
@@ -123,7 +112,14 @@ public sealed class ExperimentDataService : IExperimentDataService
{ {
var elapsed = _isTestRunning ? _testClock.Elapsed : TimeSpan.Zero; var elapsed = _isTestRunning ? _testClock.Elapsed : TimeSpan.Zero;
var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed)); var snapshot = await Task.Run(() => _realtimeDataService.GetCurrentSnapshot(elapsed));
PublishSnapshot(BuildDisplaySnapshot(snapshot)); if (_isTestRunning)
{
RecordDeviceSnapshot(snapshot);
}
else
{
PublishSnapshot(BuildIdleSnapshot(snapshot));
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -154,29 +150,30 @@ public sealed class ExperimentDataService : IExperimentDataService
return true; return true;
} }
private void RecordElapsedSnapshots() private void RecordDeviceSnapshot(RealtimeSnapshot snapshot)
{ {
if (!_isTestRunning) if (!_isTestRunning)
{ {
return; return;
} }
var currentSecond = GetElapsedTestSeconds(); var currentSecond = Math.Max(0, snapshot.TestSeconds);
var nextSecond = _lastRecordedSeconds.HasValue ? _lastRecordedSeconds.Value + 1 : 0; if (_lastRecordedSeconds.HasValue && currentSecond <= _lastRecordedSeconds.Value)
{
PublishSnapshot(BuildDisplaySnapshot(snapshot));
return;
}
for (var second = nextSecond; second <= currentSecond; second++) var timedSnapshot = snapshot with
{ {
var timedSnapshot = CurrentSnapshot with TestSeconds = currentSecond
{
TestSeconds = second
}; };
var massSnapshot = ApplyMassLoss(timedSnapshot); var massSnapshot = ApplyMassLoss(timedSnapshot);
var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot); var accumulatedSnapshot = ApplyAccumulatedTotals(massSnapshot);
PublishSnapshot(accumulatedSnapshot); PublishSnapshot(accumulatedSnapshot);
Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot)); Records.Add(RealtimeDataRecord.FromSnapshot(accumulatedSnapshot));
_lastRecordedSeconds = second; _lastRecordedSeconds = currentSecond;
}
} }
private RealtimeSnapshot BuildDisplaySnapshot(RealtimeSnapshot snapshot) private RealtimeSnapshot BuildDisplaySnapshot(RealtimeSnapshot snapshot)

View File

@@ -56,6 +56,8 @@ 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);
FillGraphSheet(workbook.GetSheet("Graph"), input);
FillBaseSheet(workbook.GetSheet("BASE"), exportRecords);
FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords); FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords);
ValidateWorkbookDataSheet(workbook, exportRecords.Count); ValidateWorkbookDataSheet(workbook, exportRecords.Count);
@@ -198,6 +200,48 @@ public sealed class NpoiReportExportService : IReportExportService
} }
} }
private static void FillGraphSheet(ISheet? sheet, ReportInput input)
{
if (sheet is null)
{
return;
}
SetGraphValuesBesideLabel(sheet, "实验室名称", input.LaboratoryName);
SetGraphValuesBesideLabel(sheet, "实验员", input.Operator);
SetGraphValuesBesideLabel(sheet, "文件名", input.FileName);
SetGraphValuesBesideLabel(sheet, "报告名", input.ReportName);
SetGraphValuesBesideLabel(sheet, "样品描述", input.SampleDescription);
SetGraphValuesBesideLabel(sheet, "材料", input.Material);
}
private static void FillBaseSheet(ISheet? sheet, IReadOnlyList<RealtimeDataRecord> records)
{
if (sheet is null)
{
return;
}
ClearRowsBelowHeader(sheet);
for (var i = 0; i < records.Count; i++)
{
var row = sheet.GetRow(i + 1) ?? sheet.CreateRow(i + 1);
var record = records[i];
SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
SetNumeric(row, 1, record.HeatReleaseRate);
SetNumeric(row, 2, record.Oxygen);
SetNumeric(row, 3, record.CarbonMonoxide);
SetNumeric(row, 4, record.CarbonDioxide);
SetNumeric(row, 5, record.CurrentMass);
SetNumeric(row, 6, record.TotalHeatRelease);
SetNumeric(row, 7, record.SmokeProduction);
SetNumeric(row, 8, record.TotalSmoke);
SetNumeric(row, 13, record.OrificePressure);
}
}
private static double CalculateGrowthIndex(int testSeconds, double value, double multiplier) private static double CalculateGrowthIndex(int testSeconds, double value, double multiplier)
{ {
if (testSeconds <= 0 || !double.IsFinite(value) || value < 0) if (testSeconds <= 0 || !double.IsFinite(value) || value < 0)
@@ -281,11 +325,6 @@ public sealed class NpoiReportExportService : IReportExportService
private static bool SetValueBesideLabel(ISheet sheet, string label, string value) private static bool SetValueBesideLabel(ISheet sheet, string label, string value)
{ {
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++) for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
{ {
var row = sheet.GetRow(rowIndex); var row = sheet.GetRow(rowIndex);
@@ -302,14 +341,14 @@ public sealed class NpoiReportExportService : IReportExportService
} }
var cell = row.GetCell(columnIndex); var cell = row.GetCell(columnIndex);
if (!CellText(cell).Contains(label, StringComparison.CurrentCultureIgnoreCase)) if (!IsMatchingLabel(CellText(cell), label))
{ {
continue; continue;
} }
var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex); var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex);
var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn); var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn);
target.SetCellValue(value); SetTextOrBlank(target, value);
return true; return true;
} }
} }
@@ -317,6 +356,59 @@ public sealed class NpoiReportExportService : IReportExportService
return false; return false;
} }
private static void SetGraphValuesBesideLabel(ISheet sheet, string label, string value)
{
for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
{
var row = sheet.GetRow(rowIndex);
if (row is null)
{
continue;
}
for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
{
if (columnIndex < 0)
{
continue;
}
var cell = row.GetCell(columnIndex);
if (!IsMatchingLabel(CellText(cell), label))
{
continue;
}
var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex);
var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn);
SetTextOrBlank(target, value);
}
}
}
private static bool IsMatchingLabel(string cellText, string label)
{
var normalizedCell = NormalizeLabel(cellText);
var normalizedLabel = NormalizeLabel(label);
return normalizedCell.Equals(normalizedLabel, StringComparison.CurrentCultureIgnoreCase)
|| normalizedCell.StartsWith($"{normalizedLabel}(", StringComparison.CurrentCultureIgnoreCase)
|| normalizedCell.StartsWith($"{normalizedLabel}", StringComparison.CurrentCultureIgnoreCase);
}
private static string NormalizeLabel(string value)
{
var chars = value
.Where(ch => !char.IsWhiteSpace(ch) && ch is not ':' and not '' and not '?' and not '')
.ToArray();
return new string(chars);
}
private static void SetTextOrBlank(ICell cell, string value)
{
cell.SetCellValue(string.IsNullOrWhiteSpace(value) ? string.Empty : value);
}
private static void SetRequiredValueBesideLabel(ISheet sheet, string label, string value) private static void SetRequiredValueBesideLabel(ISheet sheet, string label, string value)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
@@ -410,6 +502,27 @@ public sealed class NpoiReportExportService : IReportExportService
} }
} }
private static void ClearRowsBelowHeader(ISheet sheet)
{
for (var rowIndex = Math.Max(sheet.FirstRowNum + 1, 1); rowIndex <= sheet.LastRowNum; rowIndex++)
{
var row = sheet.GetRow(rowIndex);
if (row is null)
{
continue;
}
for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
{
var cell = columnIndex >= 0 ? row.GetCell(columnIndex) : null;
if (cell is not null)
{
row.RemoveCell(cell);
}
}
}
}
private static void SetNumeric(IRow row, int columnIndex, double value) private static void SetNumeric(IRow row, int columnIndex, double value)
{ {
if (double.IsFinite(value)) if (double.IsFinite(value))

View File

@@ -60,7 +60,7 @@ public sealed class ReportPageViewModel : PageViewModel
[ [
new ReportSectionViewModel("报告信息", new ReportSectionViewModel("报告信息",
[ [
new ReportFieldViewModel("LaboratoryName", "实验室名称", "检测中心"), new ReportFieldViewModel("LaboratoryName", "实验室名称"),
new ReportFieldViewModel("Operator", "实验员"), new ReportFieldViewModel("Operator", "实验员"),
new ReportFieldViewModel("FileName", "文件名"), new ReportFieldViewModel("FileName", "文件名"),
new ReportFieldViewModel("ReportName", "报告名") new ReportFieldViewModel("ReportName", "报告名")
@@ -70,17 +70,17 @@ public sealed class ReportPageViewModel : PageViewModel
new ReportFieldViewModel("SampleDescription", "样品描述"), new ReportFieldViewModel("SampleDescription", "样品描述"),
new ReportFieldViewModel("Material", "材料"), new ReportFieldViewModel("Material", "材料"),
new ReportFieldViewModel("SampleName", "样品"), new ReportFieldViewModel("SampleName", "样品"),
new ReportFieldViewModel("Thickness", "厚度", "3 mm"), new ReportFieldViewModel("Thickness", "厚度"),
new ReportFieldViewModel("InitialMass", "初始质量"), new ReportFieldViewModel("InitialMass", "初始质量"),
new ReportFieldViewModel("Manufacturer", "制造商") new ReportFieldViewModel("Manufacturer", "制造商")
]), ]),
new ReportSectionViewModel("测试条件", new ReportSectionViewModel("测试条件",
[ [
new ReportFieldViewModel("IrradiatedArea", "辐射面积", "100 c㎡"), new ReportFieldViewModel("IrradiatedArea", "辐射面积"),
new ReportFieldViewModel("Irradiance", "热辐射值", "35 kW/㎡"), new ReportFieldViewModel("Irradiance", "热辐射值"),
new ReportFieldViewModel("IrradianceDistance", "辐射距离", "25 mm"), new ReportFieldViewModel("IrradianceDistance", "辐射距离"),
new ReportFieldViewModel("SampleOrientation", "试样方向", "Vertical"), new ReportFieldViewModel("SampleOrientation", "试样方向"),
new ReportFieldViewModel("SampleCount", "试样数目", "1"), new ReportFieldViewModel("SampleCount", "试样数目"),
new ReportFieldViewModel("RequiredExhaustFlow", "要求烟气流量"), new ReportFieldViewModel("RequiredExhaustFlow", "要求烟气流量"),
new ReportFieldViewModel("FrameSelected", "边框选用?"), new ReportFieldViewModel("FrameSelected", "边框选用?"),
new ReportFieldViewModel("GridSelected", "栅格选用?"), new ReportFieldViewModel("GridSelected", "栅格选用?"),
@@ -88,7 +88,7 @@ public sealed class ReportPageViewModel : PageViewModel
new ReportFieldViewModel("AmbientTemperature", "环境温度"), new ReportFieldViewModel("AmbientTemperature", "环境温度"),
new ReportFieldViewModel("AmbientHumidity", "环境湿度"), new ReportFieldViewModel("AmbientHumidity", "环境湿度"),
new ReportFieldViewModel("TestCondition", "测试条件"), new ReportFieldViewModel("TestCondition", "测试条件"),
new ReportFieldViewModel("TestStandard", "符合标准", "ISO 5660-1"), new ReportFieldViewModel("TestStandard", "符合标准"),
new ReportFieldViewModel("TestDate", "测试日期", DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.CurrentCulture)), new ReportFieldViewModel("TestDate", "测试日期", DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.CurrentCulture)),
new ReportFieldViewModel("ForecastCondition", "预测条件") new ReportFieldViewModel("ForecastCondition", "预测条件")
]), ]),
@@ -105,7 +105,7 @@ public sealed class ReportPageViewModel : PageViewModel
]), ]),
new ReportSectionViewModel("设备参数", new ReportSectionViewModel("设备参数",
[ [
new ReportFieldViewModel("EquivalentHeatValue", "E等价热值", "13.1 MJ/kg"), new ReportFieldViewModel("EquivalentHeatValue", "E等价热值"),
new ReportFieldViewModel("CFactor", "C-系数"), new ReportFieldViewModel("CFactor", "C-系数"),
new ReportFieldViewModel("LightPath", "光程"), new ReportFieldViewModel("LightPath", "光程"),
new ReportFieldViewModel("O2DelayTime", "O2延迟时间"), new ReportFieldViewModel("O2DelayTime", "O2延迟时间"),