initial commit
This commit is contained in:
425
Models/TestDefinition.cs
Normal file
425
Models/TestDefinition.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace HC_APTBS.Models
|
||||
{
|
||||
// ── Test type constants ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Well-known test type identifiers stored in <see cref="TestDefinition.Name"/>.</summary>
|
||||
public static class TestType
|
||||
{
|
||||
public const string Dfi = "DFI";
|
||||
public const string F = "F";
|
||||
public const string Wl = "WL";
|
||||
public const string Up = "UP";
|
||||
public const string Pfp = "PFP";
|
||||
public const string Svme = "SVME";
|
||||
public const string Other = "OTHER";
|
||||
}
|
||||
|
||||
// ── TestDefinition ────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Defines a complete test procedure. A test consists of one or more <see cref="PhaseDefinition"/>
|
||||
/// objects that are executed in order. The test is considered successful only if all phases pass.
|
||||
/// </summary>
|
||||
public class TestDefinition
|
||||
{
|
||||
// ── XML element name constants ─────────────────────────────────────────
|
||||
|
||||
internal const string XmlTest = "Test";
|
||||
internal const string XmlPhase = "Fase";
|
||||
internal const string XmlSends = "Sends";
|
||||
internal const string XmlReady = "Ready";
|
||||
internal const string XmlReceives = "Receives";
|
||||
internal const string XmlResult = "Result";
|
||||
internal const string XmlMeasure = "Measure";
|
||||
|
||||
public const string TimestampFormat = "yyyy/MM/dd/ HH:mm:ss";
|
||||
|
||||
// ── Properties ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Test type identifier (see <see cref="TestType"/>).</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Conditioning time in seconds: bench must hold setpoint for this long before measurement.</summary>
|
||||
public int ConditioningTimeSec { get; set; }
|
||||
|
||||
/// <summary>Measurement window duration in seconds.</summary>
|
||||
public int MeasurementTimeSec { get; set; }
|
||||
|
||||
/// <summary>Number of measurements taken per second during the measurement window.</summary>
|
||||
public double MeasurementsPerSecond { get; set; }
|
||||
|
||||
/// <summary>Execution order index within the pump's test list.</summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>Phase definitions, executed sequentially.</summary>
|
||||
public List<PhaseDefinition> Phases { get; set; } = new();
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Returns true if at least one phase in this test is enabled.</summary>
|
||||
public bool HasActivePhase() => Phases.Any(p => p.Enabled);
|
||||
|
||||
/// <summary>Indicates whether this test collects result measurements (i.e. should appear in the report).</summary>
|
||||
public bool HasResults() => Phases.Any(p => p.Receives?.Count > 0);
|
||||
|
||||
/// <summary>Returns the total estimated wall-clock time for this test in seconds.</summary>
|
||||
public int EstimatedTotalSeconds()
|
||||
{
|
||||
int total = 0;
|
||||
foreach (var p in Phases)
|
||||
{
|
||||
if (p.Enabled)
|
||||
total += ConditioningTimeSec + MeasurementTimeSec + 5; // 5 s bench ramp-up estimate
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// ── XML serialisation ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Serialises this test definition to XML.</summary>
|
||||
public XElement ToXml()
|
||||
{
|
||||
var node = new XElement(XmlTest,
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("Tacond", ConditioningTimeSec),
|
||||
new XAttribute("Tmeasur", MeasurementTimeSec),
|
||||
new XAttribute("MeasurePerSecond", MeasurementsPerSecond),
|
||||
new XAttribute("orden", Order));
|
||||
|
||||
foreach (var phase in Phases)
|
||||
node.Add(phase.ToXml());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>Deserialises a test definition from XML.</summary>
|
||||
public static TestDefinition FromXml(XElement element)
|
||||
{
|
||||
var t = new TestDefinition
|
||||
{
|
||||
Name = element.Attribute("name")!.Value,
|
||||
ConditioningTimeSec = int.Parse(element.Attribute("Tacond")!.Value),
|
||||
MeasurementTimeSec = int.Parse(element.Attribute("Tmeasur")!.Value),
|
||||
MeasurementsPerSecond = double.Parse(
|
||||
element.Attribute("MeasurePerSecond")!.Value, CultureInfo.InvariantCulture),
|
||||
Order = int.Parse(element.Attribute("orden")!.Value)
|
||||
};
|
||||
|
||||
foreach (var phaseEl in element.Elements(XmlPhase))
|
||||
t.Phases.Add(PhaseDefinition.FromXml(phaseEl));
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// ── PhaseDefinition ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single phase within a <see cref="TestDefinition"/>.
|
||||
/// A phase specifies an RPM setpoint, temperature setpoint, parameter sends,
|
||||
/// and expected measurement results.
|
||||
/// </summary>
|
||||
public class PhaseDefinition
|
||||
{
|
||||
/// <summary>Display name (e.g. "1000 RPM 40°C").</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>When true, a test failure in this phase immediately halts the entire test sequence.</summary>
|
||||
public bool IsCritical { get; set; }
|
||||
|
||||
/// <summary>Execution order within the parent test.</summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>Whether this phase is included in the test run.</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Whether this phase passed all result criteria.</summary>
|
||||
public bool Success { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Bench readiness conditions (temperature setpoint).
|
||||
/// Typically a single entry giving the required oil temperature.
|
||||
/// </summary>
|
||||
public List<TestParameter> Readies { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to write to the pump before measurement
|
||||
/// (RPM setpoint, DFI angle, etc.).
|
||||
/// </summary>
|
||||
public List<TestParameter> Sends { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to read and compare against target values with tolerances.
|
||||
/// Results are stored in <see cref="TestParameter.Result"/>.
|
||||
/// </summary>
|
||||
public List<TestParameter> Receives { get; set; } = new();
|
||||
|
||||
/// <summary>Bit positions from the Alarms CAN parameter that fired during this phase.</summary>
|
||||
public List<int> ErrorBits { get; set; } = new();
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Returns the RPM setpoint TestParameter from the Sends list.</summary>
|
||||
public TestParameter? GetRpmSetpoint()
|
||||
=> Sends.FirstOrDefault(tp => tp.Name == BenchParameterNames.Rpm);
|
||||
|
||||
/// <summary>Records an alarm bit error, notifying listeners.</summary>
|
||||
public void RecordErrorBit(int bit)
|
||||
{
|
||||
if (!ErrorBits.Contains(bit))
|
||||
ErrorBits.Add(bit);
|
||||
}
|
||||
|
||||
/// <summary>Clears all accumulated result data for a fresh run.</summary>
|
||||
public void ClearResults()
|
||||
{
|
||||
foreach (var tp in Receives)
|
||||
tp.Result = null;
|
||||
}
|
||||
|
||||
internal bool ReceiveResults() => Receives?.Count > 0;
|
||||
|
||||
// ── XML ───────────────────────────────────────────────────────────────
|
||||
|
||||
internal XElement ToXml()
|
||||
{
|
||||
var node = new XElement(TestDefinition.XmlPhase,
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("critica", IsCritical),
|
||||
new XAttribute("orden", Order));
|
||||
|
||||
if (Readies.Count > 0)
|
||||
{
|
||||
var readyNode = new XElement(TestDefinition.XmlReady);
|
||||
foreach (var tp in Readies) readyNode.Add(tp.ToXml());
|
||||
node.Add(readyNode);
|
||||
}
|
||||
|
||||
if (Sends.Count > 0)
|
||||
{
|
||||
var sendsNode = new XElement(TestDefinition.XmlSends);
|
||||
foreach (var tp in Sends) sendsNode.Add(tp.ToXml());
|
||||
node.Add(sendsNode);
|
||||
}
|
||||
|
||||
if (Receives.Count > 0)
|
||||
{
|
||||
var recvNode = new XElement(TestDefinition.XmlReceives);
|
||||
foreach (var tp in Receives) if (tp != null) recvNode.Add(tp.ToXml());
|
||||
node.Add(recvNode);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
internal static PhaseDefinition FromXml(XElement element)
|
||||
{
|
||||
var p = new PhaseDefinition
|
||||
{
|
||||
Name = element.Attribute("name")!.Value,
|
||||
IsCritical = bool.Parse(element.Attribute("critica")!.Value),
|
||||
Order = int.Parse(element.Attribute("orden")!.Value)
|
||||
};
|
||||
|
||||
foreach (var group in element.Elements())
|
||||
{
|
||||
if (group.Name.LocalName == TestDefinition.XmlReady)
|
||||
foreach (var xtp in group.Elements()) p.Readies.Add(TestParameter.FromXml(xtp));
|
||||
else if (group.Name.LocalName == TestDefinition.XmlSends)
|
||||
foreach (var xtp in group.Elements()) p.Sends.Add(TestParameter.FromXml(xtp));
|
||||
else if (group.Name.LocalName == TestDefinition.XmlReceives)
|
||||
foreach (var xtp in group.Elements()) p.Receives.Add(TestParameter.FromXml(xtp));
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// ── TestParameter ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// A single measurement point within a <see cref="PhaseDefinition"/>: a named CAN
|
||||
/// parameter with an expected value and tolerance window.
|
||||
/// </summary>
|
||||
public class TestParameter
|
||||
{
|
||||
/// <summary>CAN parameter name (see <see cref="BenchParameterNames"/>).</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Target setpoint or expected measurement value.</summary>
|
||||
public double Value { get; set; }
|
||||
|
||||
/// <summary>Acceptable deviation from <see cref="Value"/> for a pass result.</summary>
|
||||
public double Tolerance { get; set; }
|
||||
|
||||
/// <summary>Accumulated measurement result, populated during phase execution.</summary>
|
||||
public TestResult? Result { get; set; }
|
||||
|
||||
public TestParameter() { }
|
||||
|
||||
/// <param name="name">Parameter name.</param>
|
||||
/// <param name="value">Target value.</param>
|
||||
/// <param name="tolerance">Acceptable deviation.</param>
|
||||
public TestParameter(string name, double value, double tolerance)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
Tolerance = tolerance;
|
||||
}
|
||||
|
||||
/// <summary>Returns a shallow clone without Result or listener.</summary>
|
||||
public TestParameter Clone() => new(Name, Value, Tolerance);
|
||||
|
||||
// ── XML ───────────────────────────────────────────────────────────────
|
||||
|
||||
internal XElement ToXml()
|
||||
{
|
||||
var node = new XElement(Name,
|
||||
new XAttribute("value", Value),
|
||||
new XAttribute("tolerance", Tolerance));
|
||||
|
||||
if (Result != null)
|
||||
node.Add(Result.ToXml());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
internal static TestParameter FromXml(XElement element)
|
||||
{
|
||||
var tp = new TestParameter(
|
||||
element.Name.LocalName,
|
||||
double.Parse(element.Attribute("value")!.Value, CultureInfo.InvariantCulture),
|
||||
double.Parse(element.Attribute("tolerance")!.Value, CultureInfo.InvariantCulture));
|
||||
|
||||
var resultEl = element.Element(TestDefinition.XmlResult);
|
||||
if (resultEl != null)
|
||||
tp.Result = TestResult.FromXml(resultEl);
|
||||
|
||||
return tp;
|
||||
}
|
||||
}
|
||||
|
||||
// ── TestResult ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Accumulated measurement result for a single <see cref="TestParameter"/> over
|
||||
/// one phase execution. Stores all individual samples and computes the average.
|
||||
/// </summary>
|
||||
public class TestResult
|
||||
{
|
||||
/// <summary>True if the average fell within the tolerance window.</summary>
|
||||
public bool Passed { get; set; }
|
||||
|
||||
/// <summary>Average of all collected samples.</summary>
|
||||
public double Average { get; set; }
|
||||
|
||||
/// <summary>Test type used to select the tolerance extension rules.</summary>
|
||||
public string TestType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>All individual measurement samples collected during the phase.</summary>
|
||||
public List<MeasurementSample> Samples { get; set; } = new();
|
||||
|
||||
/// <summary>Appends a new sample to the collection.</summary>
|
||||
public void AddSample(MeasurementSample sample) => Samples.Add(sample);
|
||||
|
||||
/// <summary>Clears all accumulated samples.</summary>
|
||||
public void Clear() => Samples.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Computes the average of all samples and evaluates pass/fail against the
|
||||
/// tolerance window. UP and PFP test types apply a configurable extension factor
|
||||
/// supplied by <paramref name="upExtension"/> / <paramref name="pfpExtension"/>.
|
||||
/// </summary>
|
||||
/// <param name="expected">Target value.</param>
|
||||
/// <param name="tolerance">Base tolerance.</param>
|
||||
/// <param name="upExtension">Extension factor for UP tests (0 = no extension).</param>
|
||||
/// <param name="pfpExtension">Extension factor for PFP tests (0 = no extension).</param>
|
||||
public void Evaluate(
|
||||
double expected,
|
||||
double tolerance,
|
||||
double upExtension = 0,
|
||||
double pfpExtension = 0)
|
||||
{
|
||||
if (Samples.Count == 0) { Passed = false; return; }
|
||||
|
||||
double sum = 0;
|
||||
foreach (var s in Samples) sum += s.Value;
|
||||
Average = Math.Round(sum / Samples.Count, 2);
|
||||
|
||||
if (TestType == global::HC_APTBS.Models.TestType.Up)
|
||||
{
|
||||
double hi = (expected + tolerance) * (1 + upExtension);
|
||||
double lo = (expected - tolerance) * (1 - upExtension);
|
||||
Passed = Average >= lo && Average <= hi;
|
||||
}
|
||||
else if (TestType == global::HC_APTBS.Models.TestType.Pfp)
|
||||
{
|
||||
double hi = (expected + tolerance) * (1 + pfpExtension);
|
||||
double lo = (expected - tolerance) * (1 - pfpExtension);
|
||||
Passed = Average >= lo && Average <= hi;
|
||||
}
|
||||
else
|
||||
{
|
||||
Passed = Math.Abs(Average - expected) <= tolerance;
|
||||
}
|
||||
}
|
||||
|
||||
// ── XML ───────────────────────────────────────────────────────────────
|
||||
|
||||
internal XElement ToXml()
|
||||
{
|
||||
var node = new XElement(TestDefinition.XmlResult,
|
||||
new XAttribute("average", Average),
|
||||
new XAttribute("result", Passed),
|
||||
new XAttribute("testtype", TestType));
|
||||
|
||||
foreach (var s in Samples)
|
||||
node.Add(s.ToXml());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
internal static TestResult FromXml(XElement element)
|
||||
{
|
||||
var tr = new TestResult
|
||||
{
|
||||
Passed = bool.Parse(element.Attribute("result")!.Value),
|
||||
Average = double.Parse(element.Attribute("average")!.Value, CultureInfo.InvariantCulture),
|
||||
TestType = element.Attribute("testtype")?.Value ?? string.Empty
|
||||
};
|
||||
foreach (var measureEl in element.Elements())
|
||||
tr.Samples.Add(MeasurementSample.FromXml(measureEl));
|
||||
return tr;
|
||||
}
|
||||
}
|
||||
|
||||
// ── MeasurementSample ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>A single time-stamped measurement value collected during a phase.</summary>
|
||||
public class MeasurementSample
|
||||
{
|
||||
/// <summary>Engineering-unit measurement value.</summary>
|
||||
public double Value { get; set; }
|
||||
|
||||
/// <summary>Timestamp when the sample was acquired (format <see cref="TestDefinition.TimestampFormat"/>).</summary>
|
||||
public string Timestamp { get; set; } = string.Empty;
|
||||
|
||||
internal XElement ToXml()
|
||||
=> new XElement(TestDefinition.XmlMeasure,
|
||||
new XAttribute("value", Value),
|
||||
new XAttribute("time", Timestamp));
|
||||
|
||||
internal static MeasurementSample FromXml(XElement element)
|
||||
=> new MeasurementSample
|
||||
{
|
||||
Value = double.Parse(element.Attribute("value")!.Value, CultureInfo.InvariantCulture),
|
||||
Timestamp = element.Attribute("time")?.Value ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user