Refactor: Separate concerns.

This commit is contained in:
Daniel Kraus
2015-07-06 15:48:43 +02:00
parent 2bd9c0e696
commit 2cf31914dd
19 changed files with 492 additions and 259 deletions

View File

@ -0,0 +1,39 @@
/* IImporter.cs
* part of zaaReloaded2
*
* Copyright 2015 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.Collections.Generic;
using System.Linq;
using System.Text;
using zaaReloaded2.LabModel;
namespace zaaReloaded2.Importer
{
public interface IImporter
{
/// <summary>
/// Gets the Laboratory resulting from the data import.
/// </summary>
Laboratory Laboratory { get; set; }
/// <summary>
/// Imports laboratory data contained in a string.
/// </summary>
/// <param name="text">String with laboratory data.</param>
void Import(string text);
}
}

View File

@ -0,0 +1,203 @@
/* LabItem.cs
* part of zaaReloaded2
*
* Copyright 2015 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,
Dictionaries.ParameterDictionary parameterDictionary,
Dictionaries.UnitDictionary unitDictionary)
: this(laurisString)
{
if (parameterDictionary != null)
{
Name = parameterDictionary.GetCanonicalName(OriginalName);
AlwaysPrintLimits = parameterDictionary.GetForceReferenceDisplay(Name);
}
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;
Regex numericalRegex = new Regex(
@"(?<name>[^:]+):\s*(?<value>[\d.]+)\s*(?<limits>\[[^\]]+])?\s*(?<unit>[^;]+)?");
Regex categoricalRegex = new Regex(
@"(?<name>[^:]+):\s*(?<value>[^[;]+)\s*(\[(?<normal>[^\]]+)])?");
if (numericalRegex.IsMatch(LaurisText))
{
match = numericalRegex.Match(LaurisText);
ParseLimits(match);
}
else
{
match = categoricalRegex.Match(LaurisText);
Normal = match.Groups["normal"].Value.Trim();
}
if (match != null)
{
OriginalName = match.Groups["name"].Value.Trim();
Name = OriginalName;
Value = match.Groups["value"].Value.Trim();
Unit = match.Groups["unit"].Value.Trim();
}
}
/// <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)
{
Regex limitRegex = new Regex(@"\[(?<limit1>[\d.]+)?\s*(?<operator>\S+)\s*(?<limit2>[\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));
}
}
}
}
/// <summary>
/// Analyses the Lauris name for a material abbreviation.
/// If the parameter does not refer to blood (serum, whole
/// blood, etc.), Lauris appends an abbreviation in parentheses
/// to the parameter name.
/// </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 _materialRegex = new Regex(@"\((?<material>(SU|PU))\)");
#endregion
}
}

View File

@ -0,0 +1,131 @@
/* LaurisParagraph.cs
* part of zaaReloaded2
*
* Copyright 2015 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using zaaReloaded2.Dictionaries;
using zaaReloaded2.LabModel;
namespace zaaReloaded2.Importer.ZaaImporter
{
/// <summary>
/// Parses an entire Lauris paragraph (such as "Klinische Chemie: ...")
/// and creates a list of <see cref="LabItem"/>s.
/// </summary>
public class LaurisParagraph
{
#region Public properties
/// <summary>
/// Gets a collection of <see cref="LabItem"/>s found in this paragraph.
/// </summary>
public IItemDictionary Items { get; private set; }
/// <summary>
/// Gets the caption that was extracted from the <see cref="OriginalParagraph"/>,
/// e.g. "Klin. Chemie" in "Klin. Chemie: Natrium ...".
/// </summary>
public string Caption { get; private set; }
/// <summary>
/// Gets the original paragraph that this object was constructed from.
/// </summary>
public string OriginalParagraph { get; private set; }
/// <summary>
/// Is true if the <see cref="OriginalParagraph"/> matches the expected
/// format and contains <see cref="LabItem"/>s.
/// </summary>
public bool IsLaurisParagraph { get; private set; }
#endregion
#region Constructor
public LaurisParagraph(string paragraph)
{
OriginalParagraph = paragraph;
Parse();
}
/// <summary>
/// Constructs a <see cref="LaurisParagraph"/> object from a given
/// Lauris paragraph, using a <paramref name="parameterDictionary"/>
/// and a <paramref name="unitDictionary"/> to translate the individual
/// items' properties.
/// </summary>
/// <param name="paragraph">lauris paragraph to parse.</param>
/// <param name="parameterDictionary">ParameterDictionary that contains
/// canonical names and material types.</param>
/// <param name="unitDictionary">Unit dictionary that contains canonical
/// unit names.</param>
public LaurisParagraph(string paragraph,
Dictionaries.ParameterDictionary parameterDictionary,
Dictionaries.UnitDictionary unitDictionary)
{
OriginalParagraph = paragraph;
_parameterDictionary = parameterDictionary;
_unitDictionary = unitDictionary;
Parse();
}
#endregion
#region Private methods
/// <summary>
/// Attempts to parse a Lauris paragraph.
/// </summary>
void Parse()
{
Match m = _expectedFormat.Match(OriginalParagraph);
if (m.Success)
{
Items = new ItemDictionary();
if (m.Groups["caption"].Success)
{
Caption = m.Groups["caption"].Value.Trim(new char[] {' ', ':'});
}
foreach (Capture itemCapture in m.Groups["items"].Captures)
{
LaurisItem i = new LaurisItem(itemCapture.Value, _parameterDictionary, _unitDictionary);
Items.Add(i.QualifiedName, i);
}
IsLaurisParagraph = Items.Count > 0;
}
else
{
IsLaurisParagraph = false;
}
}
#endregion
#region Fields
static readonly Regex _expectedFormat = new Regex(@"(?<caption>[^:]+:\s*)?(?<items>[^:]+:\s*[^;]+;)*");
Dictionaries.ParameterDictionary _parameterDictionary;
Dictionaries.UnitDictionary _unitDictionary;
#endregion
}
}

View File

@ -0,0 +1,179 @@
/* LaurisTimePoint.cs
* part of zaaReloaded2
*
* Copyright 2015 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using zaaReloaded2.Dictionaries;
using zaaReloaded2.LabModel;
namespace zaaReloaded2.Importer.ZaaImporter
{
/// <summary>
/// Holds all laboratory items for a given time point.
/// </summary>
class LaurisTimePoint : TimePoint
{
#region Properties
/// <summary>
/// Gets an array of paragraphs in this LaurisText.
/// </summary>
public string[] Paragraphs { get; private set; }
/// <summary>
/// Is true if the LaurisText has time stamp in the first
/// paragraph and <see cref="LabItem"/>s in the others.
/// </summary>
public bool IsValidTimePoint { get; private set; }
/// <summary>
/// Gets or sets the original Lauris text for this timepoint.
/// </summary>
public string LaurisText
{
[DebuggerStepThrough]
get
{
return String.Join(Environment.NewLine, Paragraphs);
}
set
{
if (!String.IsNullOrEmpty(value))
{
Paragraphs = value.Split(
new string[] { Environment.NewLine },
StringSplitOptions.None);
ParseParagraphs();
}
}
}
#endregion
#region Constructors
public LaurisTimePoint() { }
public LaurisTimePoint(string laurisTest)
: this()
{
_parameterDictionary = null;
_unitDictionary = null;
LaurisText = laurisTest;
}
public LaurisTimePoint(
string laurisTest,
ParameterDictionary parameterDictionary,
UnitDictionary unitDictionary)
: this()
{
_parameterDictionary = parameterDictionary;
_unitDictionary = unitDictionary;
LaurisText = laurisTest;
}
#endregion
#region Private methods
/// <summary>
/// Analyzes each Lauris paragraph in this time point, sets the date
/// and time, and collects LabItem data.
/// </summary>
/// <returns>True if the LaurisText has time stamp in the first paragraphs
/// and contains <see cref="LabItem"/>s in the others.</returns>
bool ParseParagraphs()
{
Items = new ItemDictionary();
if (Paragraphs.Length > 0)
{
if (!ParseTimeStamp()) return false;
LaurisParagraph lp;
if (IsValidTimePoint)
{
for (int i = 1; i < Paragraphs.Length; i++)
{
lp = new LaurisParagraph(
Paragraphs[i],
_parameterDictionary,
_unitDictionary);
if (lp.IsLaurisParagraph)
{
Items.Merge(lp.Items);
}
}
}
IsValidTimePoint = Items.Count > 0;
}
return true;
}
/// <summary>
/// Analyzes the date and time information that is expected to be
/// in the first paragraph.
/// </summary>
/// <returns>True if the LaurisText contains a time stamp in the
/// first paragraph.</returns>
bool ParseTimeStamp()
{
if (Paragraphs.Length == 0)
throw new InvalidOperationException("The time point has no paragraphs.");
Match m = _dateStampRegex.Match(Paragraphs[0]);
bool success = false;
if (m.Success)
{
DateTime dt;
success = DateTime.TryParseExact(
m.Groups["datetime"].Value,
"dd.MM.yyyy HH:mm",
CultureInfo.InvariantCulture,
DateTimeStyles.AllowWhiteSpaces,
out dt);
TimeStamp = dt;
}
IsValidTimePoint = success;
return success;
}
void AddItems(IItemDictionary items)
{
}
#endregion
#region Private fields
/// <summary>
/// A regular expression that matches the time stamp in the first
/// paragraph of a LaurisText.
/// </summary>
static readonly Regex _dateStampRegex = new Regex(
@"^\s*\[?\s*(?<datetime>\d\d\.\d\d\.\d\d\d\d\s+\d\d:\d\d)");
ParameterDictionary _parameterDictionary;
UnitDictionary _unitDictionary;
#endregion
}
}

View File

@ -0,0 +1,54 @@
/* ZaaImporter.cs
* part of zaaReloaded2
*
* Copyright 2015 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using zaaReloaded2.LabModel;
namespace zaaReloaded2.Importer.ZaaImporter
{
/// <summary>
/// Imports laboratory items by parsing the Lauris data from a
/// physician's letter.
/// </summary>
public class ZaaImporter : IImporter
{
#region IImporter implementation
public Laboratory Laboratory
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void Import(string text)
{
throw new NotImplementedException();
}
#endregion
}
}