更新20260529
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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延迟时间"),
|
||||||
|
|||||||
Reference in New Issue
Block a user