zaaReloaded2/zaaReloaded2/Importer/ZaaImporter/LaurisItem.cs

234 lines
9.3 KiB
C#
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 "[&lt;= 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
}
}