SSPC-Tester/SSPCTester.Logic/Report/ProfessionalReportGenerator.cs
2026-07-08 04:40:30 +08:00

535 lines
26 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SSPCTester.Logic.Models;
namespace SSPCTester.Logic.Report;
/// <summary>专业版固态功率控制器动态参数检测报告。</summary>
public sealed class ReportGenerator
{
private const string Navy = "#17324D";
private const string Blue = "#245B82";
private const string PaleBlue = "#EAF1F6";
private const string Line = "#CBD5DF";
private const string Muted = "#667585";
private const string Text = "#1E2933";
private const string Success = "#287A4B";
private const string SuccessBg = "#E8F3EC";
private const string Danger = "#A53232";
private const string DangerBg = "#F8EAEA";
private const string Warning = "#98651A";
private const string WarningBg = "#FAF1DE";
private const string NeutralBg = "#F1F3F5";
static ReportGenerator() => QuestPDF.Settings.License = LicenseType.Community;
public Dictionary<int, CurveReportImages> CurveImages { get; set; } = new();
public void Generate(Project project, string outputPath)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
string reportNo = BuildReportNumber(project);
Document.Create(document =>
{
if (project.Report.Cover)
document.Page(page => ComposeCover(page, project, reportNo));
document.Page(page => ComposeBody(page, project, reportNo));
}).GeneratePdf(outputPath);
}
private static void ComposeCover(PageDescriptor page, Project p, string reportNo)
{
page.Size(PageSizes.A4);
page.MarginHorizontal(2.1f, Unit.Centimetre);
page.MarginVertical(1.8f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Microsoft YaHei").FontSize(10).FontColor(Text));
page.Header().AlignRight().Text("SPC-DynaTest 检测系统")
.FontSize(9).FontColor(Muted).LetterSpacing(0.05f);
page.Content().Column(column =>
{
//column.Item().Height(34).Background(Navy);
column.Item().PaddingTop(54).AlignCenter().Text("固态功率控制器")
.FontSize(22).Bold().FontColor(Navy);
column.Item().PaddingTop(8).AlignCenter().Text("动态参数检测报告")
.FontSize(30).Bold().FontColor(Navy).LetterSpacing(0.08f);
// column.Item().PaddingTop(16).AlignCenter().Width(110)
// .LineHorizontal(2).LineColor(Blue);
column.Item().PaddingTop(45).Element(container => CoverIdentity(container, p, reportNo));
column.Item().PaddingTop(34).Element(container => OverallPanel(container, p));
column.Item().ExtendVertical();
});
page.Footer().Element(container => CoverFooter(container, p));
}
private static void CoverFooter(IContainer container, Project p)
{
container.Column(column =>
{
column.Item().BorderTop(1).BorderColor(Line).PaddingTop(14).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
CoverSignCell(table, "编制", p.Site.Tester);
CoverSignCell(table, "审核", "");
CoverSignCell(table, "批准", "");
});
column.Item().PaddingTop(12).Row(row =>
{
row.RelativeItem().Text("本报告仅对所检样品及记录数据负责").FontSize(8).FontColor(Muted);
row.ConstantItem(180).AlignRight().Text($"生成时间:{DateTime.Now:yyyy-MM-dd HH:mm}")
.FontSize(8).FontColor(Muted);
});
});
}
private static void CoverIdentity(IContainer container, Project p, string reportNo)
{
container.Border(1).BorderColor(Line).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(105);
columns.RelativeColumn();
});
IdentityRow(table, "报告编号", reportNo, true);
IdentityRow(table, "项目名称", ValueOrDash(p.Name));
IdentityRow(table, "设备编号", ValueOrDash(p.Dut.SerialNumber));
IdentityRow(table, "设备名称", ValueOrDash(p.Dut.ProductName));
IdentityRow(table, "设备型号", ValueOrDash(p.Dut.Model));
IdentityRow(table, "委托/生产单位", ValueOrDash(p.Dut.Manufacturer));
IdentityRow(table, "检测日期", p.Site.TestDate.ToString("yyyy年MM月dd日"));
});
}
private static void IdentityRow(TableDescriptor table, string label, string value, bool mono = false)
{
table.Cell().BorderBottom(1).BorderColor(Line).Background(PaleBlue)
.PaddingVertical(8).PaddingHorizontal(10).Text(label).SemiBold().FontColor(Navy);
var text = table.Cell().BorderBottom(1).BorderColor(Line)
.PaddingVertical(8).PaddingHorizontal(12).Text(value);
if (mono) text.FontFamily("Consolas");
}
private static void CoverSignCell(TableDescriptor table, string label, string value)
{
table.Cell().PaddingRight(14).Column(column =>
{
column.Item().Text(label).FontSize(9).FontColor(Muted);
column.Item().PaddingTop(16).BorderBottom(1).BorderColor(Line)
.PaddingBottom(4).Text(ValueOrDash(value)).FontSize(10);
});
}
private void ComposeBody(PageDescriptor page, Project p, string reportNo)
{
page.Size(PageSizes.A4);
page.MarginHorizontal(1.5f, Unit.Centimetre);
page.MarginVertical(1.3f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Microsoft YaHei").FontSize(8.7f).FontColor(Text));
page.Header().Element(container => BodyHeader(container, p, reportNo));
page.Content().PaddingVertical(10).Element(container => BodyContent(container, p));
page.Footer().Element(container => Footer(container, reportNo));
}
private static void BodyHeader(IContainer container, Project p, string reportNo)
{
container.BorderBottom(1).BorderColor(Navy).PaddingBottom(7).Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text("固态功率控制器动态参数检测报告")
.FontSize(12).Bold().FontColor(Navy);
column.Item().Text(ValueOrDash(p.Name)).FontSize(8).FontColor(Muted);
});
row.ConstantItem(175).AlignRight().Column(column =>
{
column.Item().AlignRight().Text($"报告编号:{reportNo}")
.FontFamily("Consolas").FontSize(8).FontColor(Muted);
column.Item().AlignRight().Text($"样品编号:{ValueOrDash(p.Dut.SerialNumber)}")
.FontSize(8).FontColor(Muted);
});
});
}
private static void Footer(IContainer container, string reportNo)
{
container.BorderTop(0.7f).BorderColor(Line).PaddingTop(6).Row(row =>
{
row.RelativeItem().Text($"SPC-DynaTest · {reportNo}").FontSize(7.5f).FontColor(Muted);
row.RelativeItem().AlignCenter().Text("未经授权不得部分复制本报告")
.FontSize(7.5f).FontColor(Muted);
row.RelativeItem().AlignRight()
.DefaultTextStyle(style => style.FontSize(7.5f).FontColor(Muted))
.Text(text =>
{
text.Span("第 ");
text.CurrentPageNumber();
text.Span(" 页 / 共 ");
text.TotalPages();
text.Span(" 页");
});
});
}
private void BodyContent(IContainer container, Project p)
{
container.Column(column =>
{
column.Spacing(12);
int section = 1;
if (p.Report.Cover)
{
Section(column, section++, "检测概况", "TEST OVERVIEW");
column.Item().Element(x => Overview(x, p));
Section(column, section++, "结果摘要", "RESULT SUMMARY");
column.Item().Element(x => ResultSummary(x, p));
}
if (p.Report.Basic && (p.BasicResults.Count > 0 || HasNote(p.TestFunction?.Basic)))
{
Section(column, section++, "基础测试", "BASIC FUNCTION TEST");
if (p.BasicResults.Count > 0)
{
column.Item().Element(x => ModuleSummary(x, "通道开合、电压、电流与关后残压", p.BasicResults.Select(r => r.Status)));
column.Item().Element(x => BasicTable(x, p));
}
ManualNote(column, p.TestFunction?.Basic);
}
if (p.Report.Curve && (p.CurveResults.Count > 0 || HasNote(p.TestFunction?.Curve)))
{
Section(column, section++, "开启与关断曲线", "SWITCHING CHARACTERISTICS");
if (p.CurveResults.Count > 0)
{
string criteria = $"判据:开启时间≤{p.CurveThresholds.MaxOnTimeMs:F1}ms上升时间≤{p.CurveThresholds.MaxRiseTimeMs:F1}ms关断时间≤{p.CurveThresholds.MaxOffTimeMs:F1}ms下降时间≤{p.CurveThresholds.MaxFallTimeMs:F1}ms";
column.Item().Element(x => ModuleSummary(x, criteria, p.CurveResults.Select(r => r.Status)));
column.Item().Element(x => CurveTable(x, p));
}
ManualNote(column, p.TestFunction?.Curve);
foreach (var (channel, images) in CurveImages.OrderBy(x => x.Key))
{
AddCurveImage(column, channel, "ON", images.Rising);
AddCurveImage(column, channel, "OFF", images.Falling);
}
}
if (p.Report.Power && (p.PowerResults.Count > 0 || HasNote(p.TestFunction?.Power)))
{
Section(column, section++, "功率损耗", "POWER LOSS");
if (p.PowerResults.Count > 0)
{
column.Item().Element(x => ModuleSummary(x, "采集输入/输出两端电压与电流,计算功率损耗与效率", p.PowerResults.Select(r => r.Status)));
column.Item().Element(x => PowerTable(x, p));
}
ManualNote(column, p.TestFunction?.Power);
}
if (p.Report.Protect && (p.ProtectResults.Count > 0 || HasNote(p.TestFunction?.Protect)))
{
Section(column, section++, "保护功能", "PROTECTION FUNCTION");
if (p.ProtectResults.Count > 0)
{
column.Item().Element(x => ModuleSummary(x, "跳闸、短路、过温、过压、欠压及限流保护", p.ProtectResults.Select(r => r.Status)));
column.Item().Element(x => ProtectTable(x, p));
}
ManualNote(column, p.TestFunction?.Protect);
}
column.Item().PaddingTop(12).Element(x => Conclusion(x, p));
});
}
private static void Section(ColumnDescriptor column, int number, string title, string english)
{
column.Item().PaddingTop(4).BorderBottom(1.3f).BorderColor(Navy).PaddingBottom(5).Row(row =>
{
row.ConstantItem(34).Height(25).Background(Navy).AlignCenter().AlignMiddle()
.Text(number.ToString("00")).Bold().FontColor(Colors.White).FontFamily("Consolas");
row.RelativeItem().PaddingLeft(10).AlignMiddle().Text(title)
.FontSize(13).Bold().FontColor(Navy);
row.ConstantItem(180).AlignRight().AlignMiddle().Text(english)
.FontSize(7.5f).FontColor(Muted).LetterSpacing(0.05f);
});
}
private static void AddCurveImage(ColumnDescriptor column, int channel, string phaseLabel, byte[]? png)
{
if (png == null || png.Length == 0) return;
column.Item().ShowEntire().Border(1).BorderColor(Line).Padding(8).Column(imageColumn =>
{
imageColumn.Item().Text($"CH{channel:00} {phaseLabel} Curve").SemiBold().FontColor(Navy);
imageColumn.Item().PaddingTop(6).Image(png).FitWidth();
});
}
private static void Overview(IContainer container, Project p)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(82); columns.RelativeColumn();
columns.ConstantColumn(82); columns.RelativeColumn();
});
OverviewCell(table, "设备编号", p.Dut.SerialNumber);
OverviewCell(table, "设备名称", p.Dut.ProductName);
OverviewCell(table, "设备型号", p.Dut.Model);
OverviewCell(table, "生产厂家", p.Dut.Manufacturer);
OverviewCell(table, "测试人员", p.Site.Tester);
OverviewCell(table, "测试日期", p.Site.TestDate.ToString("yyyy-MM-dd"));
OverviewCell(table, "环境温度", $"{p.Site.TemperatureC:F1} ℃");
OverviewCell(table, "相对湿度", $"{p.Site.HumidityPct:F1} %RH");
OverviewCell(table, "环境气压", $"{p.Site.PressureKpa:F1} kPa");
OverviewCell(table, "项目创建", p.CreatedAt.ToString("yyyy-MM-dd HH:mm"));
table.Cell().Border(0.6f).BorderColor(Line).Background(PaleBlue).Padding(6)
.Text("测试备注").SemiBold().FontColor(Navy);
table.Cell().ColumnSpan(3).Border(0.6f).BorderColor(Line).Padding(6)
.Text(ValueOrDash(p.Site.Remark));
});
}
private static void OverviewCell(TableDescriptor table, string label, string? value)
{
table.Cell().Border(0.6f).BorderColor(Line).Background(PaleBlue)
.Padding(6).Text(label).SemiBold().FontColor(Navy);
table.Cell().Border(0.6f).BorderColor(Line).Padding(6).Text(ValueOrDash(value));
}
private static void ResultSummary(IContainer container, Project p)
{
var statuses = p.BasicResults.Select(x => x.Status)
.Concat(p.CurveResults.Select(x => x.Status))
.Concat(p.PowerResults.Select(x => x.Status))
.Concat(p.ProtectResults.Select(x => x.Status)).ToArray();
container.Row(row =>
{
row.RelativeItem().Element(x => SummaryCard(x, "检测项目", statuses.Length.ToString(), Navy, PaleBlue));
row.RelativeItem().PaddingLeft(7).Element(x => SummaryCard(x, "合格", statuses.Count(s => s == TestStatus.Pass).ToString(), Success, SuccessBg));
row.RelativeItem().PaddingLeft(7).Element(x => SummaryCard(x, "不合格", statuses.Count(s => s == TestStatus.Fail).ToString(), Danger, DangerBg));
row.RelativeItem().PaddingLeft(7).Element(x => SummaryCard(x, "已测", statuses.Count(s => s == TestStatus.Measured).ToString(), Blue, PaleBlue));
row.RelativeItem().PaddingLeft(7).Element(x => SummaryCard(x, "未测试", statuses.Count(s => s == TestStatus.Untested).ToString(), Muted, NeutralBg));
row.RelativeItem().PaddingLeft(7).Element(x => SummaryCard(x, "综合判定", StatusText(p.OverallStatus), StatusColor(p.OverallStatus), StatusBackground(p.OverallStatus)));
});
}
private static void SummaryCard(IContainer container, string title, string value, string color, string background)
{
container.MinHeight(58).Border(0.8f).BorderColor(color).Background(background)
.Padding(8).Column(column =>
{
column.Item().Text(title).FontSize(7.5f).FontColor(Muted);
column.Item().PaddingTop(4).Text(value).FontSize(13).Bold().FontColor(color);
});
}
private static void ModuleSummary(IContainer container, string description, IEnumerable<TestStatus> source)
{
var statuses = source.ToArray();
container.Background(PaleBlue).BorderLeft(3).BorderColor(Blue).Padding(7).Row(row =>
{
row.RelativeItem().Text(description).FontSize(8).FontColor(Muted);
row.ConstantItem(190).AlignRight()
.DefaultTextStyle(style => style.FontSize(8))
.Text(text =>
{
text.Span($"合格 {statuses.Count(x => x == TestStatus.Pass)}").FontColor(Success).SemiBold();
text.Span(" ");
text.Span($"不合格 {statuses.Count(x => x == TestStatus.Fail)}").FontColor(Danger).SemiBold();
text.Span(" ");
text.Span($"已测 {statuses.Count(x => x == TestStatus.Measured)}").FontColor(Blue).SemiBold();
text.Span(" ");
text.Span($"未测 {statuses.Count(x => x == TestStatus.Untested)}").FontColor(Muted);
});
});
}
private static void BasicTable(IContainer container, Project p) =>
DataTable(container, new[] { 0.65f, 1f, 1f, 1.15f, 1.15f, 0.8f },
new[] { "通道", "导通电压\nV", "导通电流\nA", "关后电压\nV", "关后电流\nA", "判定" },
p.BasicResults.Select(r => new ReportRow(r.Status, new[]
{
$"CH{r.Channel:00}", Format(r.VoltageOn, "F2"), Format(r.CurrentOn, "F3"),
Format(r.VoltageOff, "F2"), Format(r.CurrentOff, "F3"), StatusText(r.Status)
})));
private static void CurveTable(IContainer container, Project p) =>
DataTable(container, new[] { 0.62f, 1f, 1f, 1f, 1f, 0.76f },
new[] { "通道", "开启时间\nms", "上升时间\nms", "关断时间\nms", "下降时间\nms", "判定" },
p.CurveResults.Select(r => new ReportRow(r.Status, new[]
{
$"CH{r.Channel:00}", Format(r.OnTimeMs, "F3"), Format(r.RiseTimeMs, "F3"),
Format(r.OffTimeMs, "F3"), Format(r.FallTimeMs, "F3"), StatusText(r.Status)
})));
private static void PowerTable(IContainer container, Project p) =>
DataTable(container, new[] { 0.58f, 0.78f, 0.78f, 0.86f, 0.78f, 0.78f, 0.86f, 0.78f, 0.86f, 0.72f, 0.68f },
new[] { "通道", "输入电压\nV", "输入电流\nA", "输入功率\nW", "输出电压\nV", "输出电流\nA", "输出功率\nW", "电压降\nV", "功率损耗\nW", "效率\n%", "状态" },
p.PowerResults.Select(r => new ReportRow(r.Status, new[]
{
$"CH{r.Channel:00}", Format(r.Vin, "F3"), Format(r.Iin, "F3"),
Format(r.PowerIn, "F3"), Format(r.Vout, "F3"), Format(r.Iout, "F3"),
Format(r.PowerOut, "F3"), Format(r.VoltageDrop, "F3"), Format(r.PowerLossW, "F3"),
Format(r.EfficiencyPct, "F1"), StatusText(r.Status)
})));
private static void ProtectTable(IContainer container, Project p) =>
DataTable(container, new[] { 0.55f, 1.1f, 1.45f, 1f, 0.95f, 0.75f },
new[] { "序号", "保护类型", "触发条件", "实测值", "判别模式", "判别结果" },
p.ProtectResults.Select(r => new ReportRow(r.Status, new[]
{
r.Index > 0 ? r.Index.ToString() : "—",
ValueOrDash(r.ProtectType),
ValueOrDash(r.Trigger),
ValueOrDash(r.MeasuredValue),
ValueOrDash(r.JudgeMode),
ProtectStatusText(r.Status)
})));
private static void DataTable(IContainer container, IReadOnlyList<float> widths,
IReadOnlyList<string> headers, IEnumerable<ReportRow> source)
{
var rows = source.ToArray();
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
foreach (float width in widths) columns.RelativeColumn(width);
});
table.Header(header =>
{
foreach (string text in headers)
header.Cell().Background(Navy).BorderRight(0.5f).BorderColor(Colors.White)
.PaddingVertical(6).PaddingHorizontal(4).AlignCenter().AlignMiddle()
.Text(text).FontSize(7.6f).SemiBold().FontColor(Colors.White);
});
for (int rowIndex = 0; rowIndex < rows.Length; rowIndex++)
{
var row = rows[rowIndex];
string background = row.Status == TestStatus.Fail ? DangerBg
: rowIndex % 2 == 0 ? Colors.White : "#F7F9FB";
for (int columnIndex = 0; columnIndex < row.Values.Length; columnIndex++)
{
var text = table.Cell().Background(background).BorderBottom(0.55f).BorderColor(Line)
.PaddingVertical(4.5f).PaddingHorizontal(4).AlignCenter().AlignMiddle()
.Text(row.Values[columnIndex]).FontSize(7.8f);
if (columnIndex == row.Values.Length - 1)
text.SemiBold().FontColor(StatusColor(row.Status));
else if (columnIndex == 0)
text.FontFamily("Consolas").SemiBold().FontColor(Navy);
}
}
});
}
private static void ManualNote(ColumnDescriptor column, string? note)
{
if (!HasNote(note)) return;
column.Item().ShowEntire().Border(0.8f).BorderColor(Line).Background("#F8FAFC")
.Padding(8).Column(noteColumn =>
{
noteColumn.Item().Text("测试说明 / 现场记录").FontSize(8).SemiBold().FontColor(Navy);
noteColumn.Item().PaddingTop(5).Text(note!.Trim()).FontSize(8.2f).LineHeight(1.35f);
});
}
private static void Conclusion(IContainer container, Project p)
{
container.ShowEntire().Border(1.2f).BorderColor(StatusColor(p.OverallStatus))
.Background(StatusBackground(p.OverallStatus)).Padding(12).Column(column =>
{
column.Item().Row(row =>
{
row.RelativeItem().Text("检测结论").FontSize(11).Bold().FontColor(Navy);
row.ConstantItem(120).AlignRight().Text(StatusText(p.OverallStatus))
.FontSize(13).Bold().FontColor(StatusColor(p.OverallStatus));
});
column.Item().PaddingTop(8).Text(ConclusionText(p.OverallStatus))
.FontSize(8.5f).FontColor(Text).LineHeight(1.4f);
column.Item().PaddingTop(18).Row(row =>
{
row.RelativeItem().Text($"检测:{ValueOrDash(p.Site.Tester)}").FontSize(8).FontColor(Muted);
row.RelativeItem().Text("复核________________").FontSize(8).FontColor(Muted);
row.RelativeItem().AlignRight().Text("日期____年__月__日").FontSize(8).FontColor(Muted);
});
});
}
private static void OverallPanel(IContainer container, Project p)
{
container.Border(1.2f).BorderColor(StatusColor(p.OverallStatus))
.Background(StatusBackground(p.OverallStatus)).Padding(12).Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().Text("综合检测结论").FontSize(9).FontColor(Muted);
column.Item().PaddingTop(4).Text(ConclusionText(p.OverallStatus)).FontSize(9);
});
row.ConstantItem(72).PaddingLeft(10).AlignRight().AlignMiddle().Text(StatusText(p.OverallStatus))
.FontSize(18).Bold().FontColor(StatusColor(p.OverallStatus));
});
}
private static string BuildReportNumber(Project p)
{
string serial = new(p.Dut.SerialNumber.Where(char.IsLetterOrDigit).ToArray());
if (string.IsNullOrWhiteSpace(serial)) serial = "UNKNOWN";
if (serial.Length > 14) serial = serial[^14..];
return $"SPC-{p.Site.TestDate:yyyyMMdd}-{serial.ToUpperInvariant()}";
}
private static string ConclusionText(TestStatus status) => status switch
{
TestStatus.Pass => "本次所选检测项目均满足当前判定要求,综合结论为合格。",
TestStatus.Fail => "本次检测存在不合格项目,综合结论为不合格;请结合异常通道记录进行复核与处置。",
TestStatus.Running => "检测尚未结束,当前结果仅供过程参考,不作为最终判定依据。",
TestStatus.Measured => "功率损耗已完成测量,报告基于输入/输出两端数据计算损耗与效率。",
_ => "检测数据尚不完整,暂不具备出具最终合格判定的条件。"
};
private static string Format(double value, string format) =>
double.IsNaN(value) || double.IsInfinity(value) ? "—" : value.ToString(format);
private static bool HasNote(string? value) => !string.IsNullOrWhiteSpace(value);
private static string ValueOrDash(string? value) => string.IsNullOrWhiteSpace(value) ? "—" : value.Trim();
private static string StatusText(TestStatus status) => status switch
{
TestStatus.Pass => "合格", TestStatus.Fail => "不合格",
TestStatus.Running => "测试中", TestStatus.Measured => "已测",
_ => "未测试"
};
private static string ProtectStatusText(TestStatus status) => status switch
{
TestStatus.Pass => "合格",
TestStatus.Fail => "不合格",
TestStatus.Running => "测试中",
_ => "未判定"
};
private static string StatusColor(TestStatus status) => status switch
{
TestStatus.Pass => Success, TestStatus.Fail => Danger,
TestStatus.Running => Warning, TestStatus.Measured => Blue,
_ => Muted
};
private static string StatusBackground(TestStatus status) => status switch
{
TestStatus.Pass => SuccessBg, TestStatus.Fail => DangerBg,
TestStatus.Running => WarningBg, TestStatus.Measured => PaleBlue,
_ => NeutralBg
};
private sealed record ReportRow(TestStatus Status, string[] Values);
}