/* LabItem.cs * part of zaaReloaded2 * * Copyright 2015-2017 Daniel Kraus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Globalization; using System.Text.RegularExpressions; using zaaReloaded2.LabModel; namespace zaaReloaded2.Importer.ZaaImporter { /// /// Represents a single laboratory item (e.g., sodium or creatinine). /// public class LaurisItem : LabItem { #region Properties /// /// The original Lauris string from which this lab item was created. /// public string LaurisText { get; private set; } /// /// The original name of this item as known by Lauris /// public string OriginalName { get; private set; } #endregion #region Constructors /// /// Creates an empty LabItem object. /// public LaurisItem() : base() { } public LaurisItem(string laurisString) : this() { LaurisText = laurisString; ParseLauris(); DetectMaterial(); } /// /// Creates a LabItem object from a given Lauris output, using /// a too look up additional /// properties (canonical name, material type, whether or not /// to always print the reference interval). /// /// Lauris output to parse. /// ParameterDictionary that is used /// to look up the canonical name, material type, and whether or /// not to always print the reference interval public LaurisItem(string laurisString, Thesaurus.Parameters parameterDictionary, Thesaurus.Units unitDictionary) : this(laurisString) { if (parameterDictionary != null) { Name = parameterDictionary.GetCanonicalName(OriginalName); AlwaysPrintLimits = parameterDictionary.GetForceReferenceDisplay(OriginalName); IsBlacklisted = parameterDictionary.GetIsBlacklisted(OriginalName); Material = parameterDictionary.GetMaterial(OriginalName, Material); PreferredPrecision = parameterDictionary.GetPrecision(OriginalName); } if (unitDictionary != null) { Unit = unitDictionary.TranslateLaurisUnit(Unit); } } #endregion #region Private methods /// /// Parses the original Lauris string contained in /// . /// 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; Logger.Debug("ParseLauris: {0}", LaurisText); if (_numericalRegex.IsMatch(LaurisText)) { Logger.Debug("ParseLauris: Numerical match"); match = _numericalRegex.Match(LaurisText); ParseLimits(match); Value = match.Groups["value"].Value.Trim().Replace(',', '.'); } else { Logger.Debug("ParseLauris: Not a numerical match"); match = _categoricalRegex.Match(LaurisText); Normal = match.Groups["normal"].Value.Trim(); Value = match.Groups["value"].Value.Trim(); } if (match != null) { OriginalName = match.Groups["name"].Value.Trim(); Name = OriginalName; Unit = match.Groups["unit"].Value.Trim(); Logger.Debug("ParseLauris: Match: {0}, {1}", Name, Unit); } else { Logger.Debug("ParseLauris: No match: \"{0}\"", LaurisText); } } /// /// 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) { Logger.Debug("ParseLimits: Has limits: {0}", match.Groups["limits"].Value); Match limitMatch = _limitRegex.Match(match.Groups["limits"].Value); if (limitMatch.Groups["limit1"].Success && limitMatch.Groups["limit2"].Success) { Logger.Debug("ParseLimits: Upper and lower limit detected"); // Use InvariantCulture because Lauris always outputs dots as decimal separator // Only in rare cases, a comma sneaks in... LowerLimit = Double.Parse(limitMatch.Groups["limit1"].Value.Replace(',', '.'), CultureInfo.InvariantCulture); UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value.Replace(',', '.'), CultureInfo.InvariantCulture); } else { Logger.Debug("ParseLimits: Single limit detected"); switch (limitMatch.Groups["operator"].Value.Trim()) { case "<=": UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value.Replace(',', '.'), CultureInfo.InvariantCulture); break; case ">=": LowerLimit = Double.Parse(limitMatch.Groups["limit2"].Value.Replace(',', '.'), CultureInfo.InvariantCulture); break; case "": // NOOP for special cases such as "[s. Bem.]" which occurs in NT-proBNP // Fixes exception ID 65ca8575. break; default: string unknown = match.Groups["limits"].Value; Logger.Fatal("ParseLimits: Unknown operator \"{0}\"", unknown); throw new InvalidOperationException(String.Format("Unknown operator in {0}",unknown)); } } } } /// /// Parses the original Lauris name for a material abbreviation. /// This may be misleading in certain cases, e.g. "Sammelmenge (U)" /// appears to be spot urine ("U"), but is collected urine instead /// ("SU"). Therefore, in the constructor that takes the thesaurus /// parameters, the material is looked up in the Parameters thesaurus. /// ("Sammelmenge (U)" is contained in the Parameters thesaurus.) /// /// /// Gesamt-Eiweiss (SU), Albumin (SU)/die, Gesamt-Eiweiss (PU) /// void DetectMaterial() { // The material is encoded in the original name of the item // that was produced by Lauris (eg. "Natrium (PU)" for spot // urine). Match m = _materialRegex.Match(OriginalName); if (m.Success) { switch (m.Groups["material"].Value.ToUpper()) { case "SU": Material = LabModel.Material.SU; break; case "PU": Material = LabModel.Material.U; break; } } } #endregion #region Fields static readonly Regex _numericalRegex = new Regex( @"(?[^:]+):\s*(?([\<\>]\s)?[\d,.]+)\s*(?\[[^\]]+])?\s*(?[^;]+)?"); static readonly Regex _categoricalRegex = new Regex( @"(?[^:]+):\s*(?[^[;]+)\s*(\[(?[^\]]+)])?"); static readonly Regex _limitRegex = new Regex(@"\[(?[-\d,.]+)?\s*(?\S+)\s*(?[-\d,.]+)?]"); static readonly Regex _materialRegex = new Regex(@"\((?(SU|PU))\)"); #endregion #region Class logger private static NLog.Logger Logger { get { return _logger.Value; } } private static readonly Lazy _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); #endregion } }