234 lines
9.3 KiB
C#
Executable File
234 lines
9.3 KiB
C#
Executable File
/* 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
|
||
{
|
||
/// <summary>
|
||
/// Represents a single laboratory item (e.g., sodium or creatinine).
|
||
/// </summary>
|
||
public class LaurisItem : LabItem
|
||
{
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// The original Lauris string from which this lab item was created.
|
||
/// </summary>
|
||
public string LaurisText { get; private set; }
|
||
|
||
/// <summary>
|
||
/// The original name of this item as known by Lauris
|
||
/// </summary>
|
||
public string OriginalName { get; private set; }
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// Creates an empty LabItem object.
|
||
/// </summary>
|
||
public LaurisItem() : base() { }
|
||
|
||
public LaurisItem(string laurisString)
|
||
: this()
|
||
{
|
||
LaurisText = laurisString;
|
||
ParseLauris();
|
||
DetectMaterial();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a LabItem object from a given Lauris output, using
|
||
/// a <see cref="ParameterDictionary"/> too look up additional
|
||
/// properties (canonical name, material type, whether or not
|
||
/// to always print the reference interval).
|
||
/// </summary>
|
||
/// <param name="laurisString">Lauris output to parse.</param>
|
||
/// <param name="parameterDictionary">ParameterDictionary that is used
|
||
/// to look up the canonical name, material type, and whether or
|
||
/// not to always print the reference interval</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// Parses the original Lauris string contained in
|
||
/// <see cref="LaurisText"/>.
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parses a string containing value limits. The string must be like
|
||
/// "[3.5 - 5]", "[>= 50]", or "[<= 100]".
|
||
/// </summary>
|
||
/// <param name="match">Match object that should contain a group "limits".</param>
|
||
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));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.)
|
||
/// </summary>
|
||
/// <example>
|
||
/// Gesamt-Eiweiss (SU), Albumin (SU)/die, Gesamt-Eiweiss (PU)
|
||
/// </example>
|
||
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(
|
||
@"(?<name>[^:]+):\s*(?<value>([\<\>]\s)?[\d,.]+)\s*(?<limits>\[[^\]]+])?\s*(?<unit>[^;]+)?");
|
||
static readonly Regex _categoricalRegex = new Regex(
|
||
@"(?<name>[^:]+):\s*(?<value>[^[;]+)\s*(\[(?<normal>[^\]]+)])?");
|
||
static readonly Regex _limitRegex = new Regex(@"\[(?<limit1>[-\d,.]+)?\s*(?<operator>\S+)\s*(?<limit2>[-\d,.]+)?]");
|
||
static readonly Regex _materialRegex = new Regex(@"\((?<material>(SU|PU))\)");
|
||
|
||
#endregion
|
||
|
||
#region Class logger
|
||
|
||
private static NLog.Logger Logger { get { return _logger.Value; } }
|
||
|
||
private static readonly Lazy<NLog.Logger> _logger = new Lazy<NLog.Logger>(() => NLog.LogManager.GetCurrentClassLogger());
|
||
|
||
#endregion
|
||
}
|
||
}
|