diff --git a/Tests/LabItemTest.cs b/Tests/LabItemTest.cs index 0012bcb..a270dd6 100755 --- a/Tests/LabItemTest.cs +++ b/Tests/LabItemTest.cs @@ -20,11 +20,111 @@ using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using zaaReloaded2.Models; namespace Tests { [TestFixture] class LabItemTest { + [Test] + [TestCase("Natrium: 139 [135 - 145] mmol/l", "Natrium", 139, "mmol/l", 135, 145, true)] + [TestCase("Kalium: 5.2 [3.5 - 5] mmol/l", "Kalium", 5.2, "mmol/l", 3.5, 5, false)] + public void ParseLaurisWithBothLimits( + string laurisString, string name, double value, + string unit, double lowerLimit, double upperLimit, bool isNormal) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(unit, i.Unit, "Unit"); + Assert.IsTrue(i.IsNumerical, "IsNumerical"); + Assert.AreEqual(value, i.NumericalValue, "NumericalValue"); + Assert.AreEqual(lowerLimit, i.LowerLimit, "Lower limit"); + Assert.AreEqual(upperLimit, i.UpperLimit, "Upper limit"); + Assert.AreEqual(isNormal, i.IsNormal, "IsNormal"); + Assert.IsTrue(i.HasLimits, "HasLimits"); + Assert.IsTrue(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsTrue(i.HasUpperLimit, "HasUpperLimit"); + } + + [TestCase("HDL - Cholesterin: 45 [>= 35] mg/dl", "HDL - Cholesterin", 45, "mg/dl", 35, true)] + public void ParseLaurisWithLowerLimit( + string laurisString, string name, double value, + string unit, double lowerLimit, bool isNormal) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(unit, i.Unit, "Unit"); + Assert.IsTrue(i.IsNumerical, "IsNumerical"); + Assert.AreEqual(value, i.NumericalValue, "NumericalValue"); + Assert.AreEqual(lowerLimit, i.LowerLimit, "Lower limit"); + Assert.AreEqual(isNormal, i.IsNormal, "IsNormal"); + Assert.IsTrue(i.HasLimits, "HasLimits"); + Assert.IsTrue(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsFalse(i.HasUpperLimit, "HasUpperLimit"); + } + + [TestCase("GOT (ASAT): 303.0 [<= 50] U/l; ", "GOT (ASAT)", 303, "U/l", 50, false)] + public void ParseLaurisWithUpperLimit( + string laurisString, string name, double value, + string unit, double upperLimit, bool isNormal) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(unit, i.Unit, "Unit"); + Assert.IsTrue(i.IsNumerical, "IsNumerical"); + Assert.AreEqual(value, i.NumericalValue, "NumericalValue"); + Assert.AreEqual(upperLimit, i.UpperLimit, "Upper limit"); + Assert.AreEqual(isNormal, i.IsNormal, "IsNormal"); + Assert.IsTrue(i.HasLimits, "HasLimits"); + Assert.IsFalse(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsTrue(i.HasUpperLimit, "HasUpperLimit"); + } + + [TestCase("Niedermol. Heparin (Anti-Xa): 0.99 U/ml;", "Niedermol. Heparin (Anti-Xa)", 0.99, "U/ml")] + [TestCase("glomerul. Filtrationsr. CKD-EP: 42 ml/min /1,73qm;", "glomerul. Filtrationsr. CKD-EP", 42, "ml/min /1,73qm")] + public void ParseLaurisWithoutLimits( + string laurisString, string name, double value, + string unit) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(unit, i.Unit, "Unit"); + Assert.IsTrue(i.IsNumerical, "IsNumerical"); + Assert.AreEqual(value, i.NumericalValue, "NumericalValue"); + Assert.IsFalse(i.HasLimits, "HasLimits"); + Assert.IsFalse(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsFalse(i.HasUpperLimit, "HasUpperLimit"); + } + + [TestCase("HBs-Antigen: neg. ;", "HBs-Antigen", "neg.")] + public void ParseLaurisNonNumericNoNormal( + string laurisString, string name, string value) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(value, i.Value, "Value"); + Assert.IsTrue(String.IsNullOrEmpty(i.Unit), "Unit should be a null string"); + Assert.IsFalse(i.HasLimits, "HasLimits"); + Assert.IsFalse(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsFalse(i.HasUpperLimit, "HasUpperLimit"); + // TODO: Define the behavior of LabItem.IsNormal if no normal value known + } + + [TestCase("Erythrozyten (U): + [negativ]", "Erythrozyten (U)", "+", "negativ", false)] + [TestCase("Bilirubin (U): negativ [negativ]", "Bilirubin (U)", "negativ", "negativ", true)] + public void ParseLaurisNonNumericWithNormal( + string laurisString, string name, string value, string normal, bool isNormal) + { + LabItem i = new LabItem(laurisString); + Assert.AreEqual(name, i.Name, "Name"); + Assert.AreEqual(value, i.Value, "Value"); + Assert.AreEqual(normal, i.Normal, "Normal"); + Assert.AreEqual(isNormal, i.IsNormal, "IsNormal"); + Assert.IsFalse(i.HasLimits, "HasLimits"); + Assert.IsFalse(i.HasLowerLimit, "HasLowerLimit"); + Assert.IsFalse(i.HasUpperLimit, "HasUpperLimit"); + // TODO: Define the behavior of LabItem.IsNormal if no normal value known + } } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 3a13cc7..83fb960 100755 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -64,6 +64,12 @@ + + + {0478f1b0-17f2-4151-8f93-1cb6eb9732c5} + zaaReloaded2 + + diff --git a/zaaReloaded2/Models/LabItem.cs b/zaaReloaded2/Models/LabItem.cs index 1f8ccde..f7bc238 100755 --- a/zaaReloaded2/Models/LabItem.cs +++ b/zaaReloaded2/Models/LabItem.cs @@ -16,9 +16,8 @@ * limitations under the License. */ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; namespace zaaReloaded2.Models { @@ -29,12 +28,162 @@ namespace zaaReloaded2.Models { #region Properties + /// + /// Gets or sets the original name of the item (as known by Lauris). + /// public string Name { get; set; } + + /// + /// Gets or sets the unit of the item (as known by Lauris). + /// public string Unit { get; set; } - public double Value { get; set; } - public double LowerLimit { get; set; } - public double UpperLimit { get; set; } - public string Lauris { get; set; } + + /// + /// Gets or sets the value of the item. This may be a number or a string. + /// + public string Value { get; set; } + + public double NumericalValue + { + get + { + if (IsNumerical) + { + return _numericalValue; + } + else + { + throw new InvalidOperationException( + String.Format("Value '{0}' is not numerical.", Value)); + } + } + private set + { + _numericalValue = value; + } + } + + public bool IsNumerical + { + get + { + return (Double.TryParse(Value, + NumberStyles.Any, + CultureInfo.InvariantCulture, + out _numericalValue)); + } + } + + /// + /// Gets or sets the normal value of the item. Need not be set. This is + /// used for items with nominal values (as opposed to numbers). + /// + public string Normal { get; set; } + + /// + /// Gets or sets the lower limit of normal. + /// + public double LowerLimit + { + get + { + return _lowerLimit; + } + set + { + _lowerLimit = value; + HasLowerLimit = true; + } + } + + /// + /// Is true if the item has a lower limit of normal. + /// + public bool HasLowerLimit { get; private set; } + + /// + /// Gets or sets the upper limit of normal. + /// + public double UpperLimit + { + get + { + return _upperLimit; + } + set + { + _upperLimit = value; + HasUpperLimit = true; + } + } + + /// + /// Is true if the item has an upper limit of normal. + /// + public bool HasUpperLimit { get; private set; } + + /// + /// Is true if is normal. + /// + public bool IsNormal + { + get + { + if (HasLimits) + { + if (HasLowerLimit && HasUpperLimit) + { + return (LowerLimit <= NumericalValue && + NumericalValue <= UpperLimit); + } + else + { + if (HasLowerLimit) + { + return (LowerLimit <= NumericalValue); + } + return (NumericalValue <= UpperLimit); + } + } + else + { + return (Value == Normal); + } + } + } + + /// + /// Is true if the item has lower and/or upper limits. Is false if the + /// item does not have limits but a value. + /// + public bool HasLimits + { + get { return (HasLowerLimit || HasUpperLimit); } + } + + /// + /// The original Lauris string from which this lab item was created. + /// + public string Lauris { get; private set; } + + /// + /// The canonical name of this item, or the Lauris + /// of the item if no canonical name is known. + /// + public string CanonicalName + { + get + { + if (!string.IsNullOrEmpty(_canonicalName)) + { + return _canonicalName; + } + else + { + return Name; + } + } + } #endregion @@ -66,9 +215,85 @@ namespace zaaReloaded2.Models /// private void ParseLauris() { - + // Examples of Lauris output strings: + // "Natrium: 139 [135 - 145] mmol/l" + // "HDL - Cholesterin: 45 [>= 35] mg/dl" + // "GOT (ASAT): 303.0 [<= 50] U/l; " + // "Niedermol. Heparin (Anti-Xa): 0.99 U/ml;" + // "HBs-Antigen: neg. ;" + // "Erythrozyten (U): + [negativ]" + Match match; + Regex numericalRegex = new Regex( + @"(?[^:]+):\s*(?[\d.]+)\s*(?\[[^\]]+])?\s*(?[^;]+)?"); + Regex categoricalRegex = new Regex( + @"(?[^:]+):\s*(?[^[;]+)\s*(\[(?[^\]]+)])?"); + if (numericalRegex.IsMatch(Lauris)) + { + match = numericalRegex.Match(Lauris); + ParseLimits(match); + } + else + { + match = categoricalRegex.Match(Lauris); + Normal = match.Groups["normal"].Value.Trim(); + } + if (match != null) + { + Name = match.Groups["name"].Value.Trim(); + Value = match.Groups["value"].Value.Trim(); + Unit = match.Groups["unit"].Value.Trim(); + } } + /// + /// Parses a string containing value limits. The string must be like + /// "[3.5 - 5]", "[>= 50]", or "[<= 100]". + /// + /// Match object that should contain a group "limits". + void ParseLimits(Match match) + { + if (match.Groups["limits"].Success) + { + Regex limitRegex = new Regex(@"\[(?[\d.]+)?\s*(?\S+)\s*(?[\d.]+)?]"); + Match limitMatch = limitRegex.Match(match.Groups["limits"].Value); + if (limitMatch.Groups["limit1"].Success && limitMatch.Groups["limit2"].Success) + { + // Use InvariantCulture because Lauris always outputs dots as decimal separator + LowerLimit = Double.Parse(limitMatch.Groups["limit1"].Value, + CultureInfo.InvariantCulture); + UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value, + CultureInfo.InvariantCulture); + } + else + { + switch (limitMatch.Groups["operator"].Value.Trim()) + { + case "<=": + UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value, + CultureInfo.InvariantCulture); + break; + case ">=": + LowerLimit = Double.Parse(limitMatch.Groups["limit2"].Value, + CultureInfo.InvariantCulture); + break; + default: + throw new InvalidOperationException( + String.Format("Unknown operator in {0}", + match.Groups["limits"].Value)); + } + } + } + } + + #endregion + + #region Fields + + string _canonicalName; + double _numericalValue; + double _lowerLimit; + double _upperLimit; + #endregion } } diff --git a/zaaReloaded2/zaaReloaded2.csproj b/zaaReloaded2/zaaReloaded2.csproj index 036132a..7989fa7 100755 --- a/zaaReloaded2/zaaReloaded2.csproj +++ b/zaaReloaded2/zaaReloaded2.csproj @@ -192,11 +192,21 @@ + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + true + + + zaaReloaded2_TemporaryKey.pfx + + + 0EDB0CD8E3605AC48387A527A80943403E568BD9 +