Merge branch 'import-from-clinic' into develop

This commit is contained in:
daniel 2017-09-17 13:28:43 +02:00
commit ba485ac560
18 changed files with 1091 additions and 104 deletions

View File

@ -0,0 +1,55 @@
/* ClinicImporterTest.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using zaa = zaaReloaded2.Importer.ClinicImporter;
using NUnit.Framework;
namespace Tests.Importer.ClinicImporter
{
[TestFixture]
class ClinicImporterTest
{
/// <summary>
/// Parses the demo-output.txt file that is built into the assembly.
/// Note that this demo text contains seven time points, only six of
/// which are distinct. Thus, the two time points with identical time
/// stamps should be merged in the <see cref="Laboratory"/>.
/// </summary>
[Test]
public void ParseTimePoints()
{
zaa.ClinicImporter importer = TestHelpers.ClinicImporterFromResource();
// Only 6 distinct time points (see method documentation above).
Assert.AreEqual(5, importer.Laboratory.TimePoints.Count);
}
[Test]
public void ParseInvalidInput()
{
zaa.ClinicImporter importer = new zaa.ClinicImporter();
importer.Import("some arbitrary text\r\nthat does not represent\r\na valid lab");
Assert.IsFalse(importer.Success);
importer.Import("(03.03.1930 13:30:00)\r\n\tNatrium 135 [135 - 145] mmol/l;");
Assert.IsTrue(importer.Success);
}
}
}

View File

@ -0,0 +1,56 @@
/* LaurisItemTest.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using zaaReloaded2.LabModel;
using zaaReloaded2.Importer.ZaaImporter;
namespace Tests.Importer.ClinicImporter
{
/// <summary>
/// The ClinicItemTest really just tests the zaaReloaded2.Importer.ZaaImporter.LaurisItem
/// class with text formatted by the outpatient clinic system to make sure LaurisItem works
/// with clinic-formatted lab data as well.
/// </summary>
[TestFixture]
class ClinicItemTest
{
[Test]
[TestCase(" Natrium: 137 [135 - 145] mmol/l", "Natrium", 137, "mmol/l", 135, 145, true)]
public void ParseClinicWithBothLimits(
string laurisString, string name, double value,
string unit, double lowerLimit, double upperLimit, bool isNormal)
{
LaurisItem i = new LaurisItem(laurisString);
Assert.AreEqual(name, i.Name, "Name");
Assert.AreEqual(unit, i.Unit, "Unit");
Assert.IsFalse(i.IsExtreme, "IsExtreme");
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");
}
}
}

View File

@ -0,0 +1,45 @@
/* ClinicLineTest.cs
* part of zaaReloaded2
*
* Copyright 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 NUnit.Framework;
using zaaReloaded2.LabModel;
using zaaReloaded2.Importer.ClinicImporter;
namespace Tests.Importer.ClinicImporter
{
[TestFixture]
class ClinicLineTest
{
[Test]
public void ParseLine()
{
string demo = "\tNatrium:\t137\t[135 - 145]\tmmol/l";
ClinicLine line = new ClinicLine(demo);
Assert.IsTrue(line.IsClinicLine);
Assert.AreEqual(137, line.Item.NumericalValue);
}
[Test]
public void ParseInvalidLine()
{
// Missing leading tab
string demo = "Natrium:\t137\t[135 - 145]\tmmol/l";
ClinicLine line = new ClinicLine(demo);
Assert.IsFalse(line.IsClinicLine);
}
}
}

View File

@ -0,0 +1,67 @@
/* TimePointTest.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using zaaReloaded2.LabModel;
using zaaReloaded2.Importer.ClinicImporter;
namespace Tests.Importer.ClinicImporter
{
[TestFixture]
class ClinicTimePointTest
{
[Test]
public void ParseValidClinicTimePoint()
{
ClinicTimePoint tp = new ClinicTimePoint(
"(06.09.2017 09:45:00)" + Environment.NewLine +
"\tKalium:\t4.6\t[3.5 - 5]\tmmol/l");
Assert.IsTrue(tp.IsValidTimePoint);
}
[Test]
public void ParseInvalidClinicTimePoints()
{
ClinicTimePoint tp = new ClinicTimePoint("Mit freundlichen Grüßen");
Assert.IsFalse(tp.IsValidTimePoint,
"Bogus paragraph should be invalid LaurisTimePoint");
tp.ClinicText = "(22.10.2013 12:30:00)";
Assert.IsFalse(tp.IsValidTimePoint,
"LaurisTimePoint should be invalid if it consists of time stamp only.");
}
[Test]
public void ParseClinicTimePointWithDuplicateItems()
{
ClinicTimePoint tp = new ClinicTimePoint(
"(22.10.2013 12:30:00)" + Environment.NewLine +
"\tNatrium:\t137\t[135 - 145]\tmmol/l" + Environment.NewLine +
"\tNatrium:\t140\t[135 - 145]\tmmol/l"
);
Assert.IsTrue(tp.Items.ContainsKey("Natrium"),
"LaurisTimePoint should contain 'Natrium' item.");
Assert.AreEqual(140, tp.Items["Natrium"].NumericalValue,
"LaurisTimePoint does not use last occurrence of 'Natrium'.");
}
}
}

View File

@ -21,6 +21,7 @@ using System.IO;
using System.Linq;
using System.Text;
using zaaReloaded2.Importer.ZaaImporter;
using zaaReloaded2.Importer.ClinicImporter;
namespace Tests
{
@ -38,5 +39,18 @@ namespace Tests
importer.Import(r.ReadToEnd());
return importer;
}
/// <summary>
/// Creates a ClinicImporter object and imports demo-output-clinic.txt.
/// </summary>
/// <returns></returns>
public static ClinicImporter ClinicImporterFromResource()
{
Stream s = typeof(TestHelpers).Assembly.GetManifestResourceStream("Tests.demo-output-clinic.txt");
StreamReader r = new StreamReader(s);
ClinicImporter importer = new ClinicImporter();
importer.Import(r.ReadToEnd());
return importer;
}
}
}

View File

@ -91,6 +91,10 @@
<Compile Include="Controller\Comments\CommentPoolTest.cs" />
<Compile Include="Controller\Comments\ItemCommentTest.cs" />
<Compile Include="Controller\Elements\CloneTest.cs" />
<Compile Include="Importer\ClinicImporter\ClinicItemTest.cs" />
<Compile Include="Importer\ClinicImporter\ClinicLineTest.cs" />
<Compile Include="Importer\ClinicImporter\TimePointTest.cs" />
<Compile Include="Importer\ClinicImporter\ClinicImporterTest.cs" />
<Compile Include="Medication\ImporterTest.cs" />
<Compile Include="Medication\PrescriptionTest.cs" />
<Compile Include="SerializationTest.cs" />
@ -144,6 +148,10 @@
<ItemGroup>
<EmbeddedResource Include="Formatter\FormatterTest-all.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="demo-output-clinic.txt" />
</ItemGroup>
<ItemGroup />
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>

90
Tests/demo-output-clinic.txt Executable file
View File

@ -0,0 +1,90 @@
# This file contains 5 distinct time points.
# This leading text should be ignored.
# DO NOT CHANGE THE FOLLOWING TEXT, LEST THE TESTS WILL FAIL!
(06.09.2017 09:54:00)
Gesamt-Eiweiss (PU): 54 [<= 120] mg/l
Gesamt-Eiweiss/Creatinin (PU): 59 [<= 70] mg/g Crea
Albumin (PU): 4 [<= 30] mg/l
Albumin/Creatinin (PU): 4.4 [<= 30] mg/g Crea
Creatinin (PU): 91.1 [29 - 226] mg/dl
(06.09.2017 09:54:00)
Erythrozyten (U): negativ [negativ]
Leukozyten (U): + [negativ]
Nitrit (U): negativ [negativ]
Protein (U): negativ [negativ]
Glucose (U): negativ [negativ]
Ketonkörper (U): + [negativ]
Bilirubin (U): negativ [negativ]
Urobilinogen (U): negativ [negativ]
pH (U): 6.500 [4.8 - 7.4]
spezifisches Gewicht (U): 1.015 [1.00 - 1.04] g/ml
Erythrozyten (U): 3 [<= 25] Ery/µl
Leukozyten (U): 4 [<= 20] Leu/µl
Bakterien (U): 193 Bak/µl
Plattenepithelien (U): 25 Epi/µl
Übergangsepithelien (U): 0 Uge/µl
hyaline Zylinder (U): 0 Zyh/µl
(06.09.2017 09:52:00)
Gesamt-Eiweiss (SU): < 40 [<= 120] mg/l
Albumin (SU): < 3 mg/l
a1-Microglobulin (SU): < 5 mg/l
Immunglobulin G (SU): < 4 mg/l
Sammelzeit (U): 24 h
Sammelmenge (U): 3200 ml
Calcium (SU): 0.26 mmol/l
Calcium (SU)/die: 0.83 [2.5 - 8] mmol/d
Creatinin (SU): 30.2 mg/dl
Harnstoff (SU): 674 [900 - 3000] mg/dl
Harnstoff (SU)/die: 21.6 [<= 35] g/d
Kalium (SU): 45.4 [20 - 80] mmol/l
Kalium (SU)/die: 145.28 [25 - 125] mmol/d
Natrium (SU): 28.9 [54 - 150] mmol/l
Natrium (SU)/die: 92.5 [40 - 220] mmol/d
(06.09.2017 09:50:00)
Cystatin C (Latex Gen. 2): 1.04 [0.61 - 0.95] mg/l
(06.09.2017 09:45:00)
Natrium: 137 [135 - 145] mmol/l
Kalium: 4.6 [3.5 - 5] mmol/l
Calcium: 2.4 [2.0 - 2.7] mmol/l
anorg. Phosphat: 1.29 [0.87 - 1.45] mmol/l
Calcium-Phosphat-Produkt: 3.10 [<= 4.4] mmol²/l²
glomerul. Filtrationsr. CKD-EP: 62 ml/min /1,73qm
glomerul. Filtrationsr. (MDRD): 59 ml/min /1,73qm
Creatinin: 1.06 [0 - 0.95] mg/dl
Harnstoff: 52.1 [10 - 50] mg/dl
Lactat Dehydrogenase: 175 [<= 250] U/l
Cholesterin: 180 [130 - 220] mg/dl
Triglyceride: 51 [74 - 172] mg/dl
LDL - Cholesterin: 91 [0 - 150] mg/dl
HDL - Cholesterin: 79 [>= 35] mg/dl
Albumin: 4.5 [3.5 - 5.5] g/dl
Leukozyten: 4.7 [5 - 10] n*1000/µl
Erythrozyten: 4.09 [4 - 5] n*10E6/µl
Hämoglobin: 11.9 [12 - 16] g/dl
Hämatokrit: 36.6 [35 - 47] %
MCV: 89.5 [82 - 96] fl
MCH (HbE): 29.1 [27 - 33] pg
MCHC: 32.5 [32 - 36] g/dl
Thrombozyten: 302 [150 - 450] n*1000/µl
Mittleres Plättchenvolumen: 11.3 [9.6 - 12.0] fl
Neutrophile: 2.60 [1.8 - 7.2] n*1000/µl
Lymphozyten: 1.35 [1 - 4.05] n*1000/µl
Monozyten: 0.47 [0.08 - 0.8] n*1000/µl
Eosinophile: 0.2 [0.04 - 0.36] n*1000/µl
Basophile: 0.1 [0 - 0.08] n*1000/µl
% Neutrophile: 55.1 [41 - 70] %
% Lymphozyten: 28.7 [25 - 40] %
% Monozyten: 10.0 [2 - 8] %
% Eosinophile: 4.7 [0.8 - 6.2] %
% Basophile: 1.3 [0 - 1] %
Unreife Granulozyten: 0.01 n*1000/µl
% Unreife Granulozyten: 0.2 %
C-reaktives Protein: 0.02 [0 - 0.5] mg/dl
(06.09.2017 09:15:00)
TSH: 1.30 [0.3 - 4.0] mIU/l
PTH intakt: 106.0 [12 - 65] ng/l

View File

@ -208,7 +208,8 @@ namespace zaaReloaded2
{
Logger.Info("DoFormat: Attempting to auto-detect");
Word.Document doc = activeWindow.Document;
if (!AutoDetect.Detect(doc))
Importer.AutoDetector autoDetector = new Importer.AutoDetector();
if (!autoDetector.Detect(doc))
{
Logger.Info("DoFormat: Automatic detection failed");
NotificationAction a = new NotificationAction();

Binary file not shown.

View File

@ -20,11 +20,15 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using zaaReloaded2.Importer.ZaaImporter;
using zaaReloaded2.Importer.ClinicImporter;
namespace zaaReloaded2.Importer.ZaaImporter
namespace zaaReloaded2.Importer
{
static class AutoDetect
class AutoDetector
{
#region Public methods
/// <summary>
/// Attempts to automatically detect laboratory data in the Word
/// document.
@ -34,7 +38,7 @@ namespace zaaReloaded2.Importer.ZaaImporter
/// <returns>True if laboratory data was detected, false if not.</returns>
/// <exception cref="ArgumentNullException">if <paramref name="document"/>
/// is null.</exception>
public static bool Detect(Document document)
public bool Detect(Document document)
{
if (document == null)
{
@ -89,17 +93,68 @@ namespace zaaReloaded2.Importer.ZaaImporter
return false;
}
#endregion
#region Private methods
/// <summary>
/// Returns true if a paragraph is a time stamp line.
/// </summary>
private bool IsTimeStampParagraph(Paragraph paragraph)
{
string text = paragraph.Range.Text;
bool isCinicTimePoint = ClinicTimePoint.IsTimeStampLine(text);
bool isZaaTimePoint = LaurisTimePoint.IsTimeStampLine(text);
// If the line is a ZAA time point, but not a clinic timepoint, we can deduct that
// the lab mode *must* be ZAA, because it will be a line in the form
// "(17.09.2015-201710:44:00) Cyclosporin-A vor Gabe: 130 µg/l;" which does not
// occur in the clinic format.
if ((_mode == Mode.Undefined) && isZaaTimePoint && !isCinicTimePoint)
{
_mode = Mode.Zaa;
}
return isCinicTimePoint || isZaaTimePoint;
}
/// <summary>
/// Returns true if a paragraph is either a time stamp line
/// or a paragraph with laboratory items.
/// </summary>
/// <remarks>
/// This method determines the mode: either ZAA-generated output or clinic system-generated
/// output. ZAA is given priority over clinic. Once a mode is detected, it will stick to
/// that mode.
/// </remarks>
/// <param name="paragraph"></param>
/// <returns></returns>
private static bool IsLabParagraph(Paragraph paragraph)
private bool IsLabParagraph(Paragraph paragraph)
{
string text = paragraph.Range.Text;
return (LaurisParagraph.ResemblesLaurisParagraph(text)
|| LaurisTimePoint.IsTimeStampLine(text));
bool isLabParagraph = false;
switch (_mode)
{
case Mode.Undefined:
if (LaurisParagraph.ResemblesLaurisParagraph(text) || LaurisTimePoint.IsTimeStampLine(text))
{
_mode = Mode.Zaa;
isLabParagraph = true;
}
else if (ClinicLine.ResemblesClinicLine(text) || ClinicTimePoint.IsTimeStampLine(text))
{
_mode = Mode.Clinic;
isLabParagraph = true;
}
break;
case Mode.Zaa:
isLabParagraph = LaurisParagraph.ResemblesLaurisParagraph(text) || LaurisTimePoint.IsTimeStampLine(text);
break;
case Mode.Clinic:
isLabParagraph = ClinicLine.ResemblesClinicLine(text) || ClinicTimePoint.IsTimeStampLine(text);
break;
default:
break;
}
return isLabParagraph;
}
/// <summary>
@ -110,9 +165,24 @@ namespace zaaReloaded2.Importer.ZaaImporter
/// </remarks>
/// <param name="paragraph">Paragraph whose index to return.</param>
/// <returns>Index of the paragraph.</returns>
private static int GetParagraphIndex(Document document, Paragraph paragraph)
private int GetParagraphIndex(Document document, Paragraph paragraph)
{
return document.Range(0, paragraph.Range.Start).Paragraphs.Count;
}
#endregion
#region Fields
private enum Mode
{
Undefined,
Zaa,
Clinic
}
private Mode _mode;
#endregion
}
}

View File

@ -0,0 +1,170 @@
/* ZaaImporter.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Bovender.Extensions;
using zaaReloaded2.LabModel;
using zaaReloaded2.Thesaurus;
namespace zaaReloaded2.Importer
{
/// <summary>
/// Base class for certain importers such as ZaaImporter, ClinicImporter.
/// </summary>
public class BaseImporter : IImporter
{
#region IImporter implementation
public Laboratory Laboratory
{
[DebuggerStepThrough]
get
{
if (_laboratory == null)
{
_laboratory = new Laboratory();
}
return _laboratory;
}
[DebuggerStepThrough]
set
{
_laboratory = value;
}
}
public bool Success
{
get
{
return Laboratory.TimePoints.Count > 0;
}
}
/// <summary>
/// Splits the <paramref name="text"/> into individual time points
/// and creates <see cref="TimePoint"/> objects from them.
/// </summary>
public void Import(string text)
{
Logger.Info("Import: \"{0}\"", text.TruncateWithEllipsis(120));
string[] paragraphs = Helpers.SplitParagraphs(text);
Logger.Info("Import: {0} paragraph(s)", paragraphs.Length);
TimePoint timePoint = null;
foreach (string paragraph in paragraphs)
{
Logger.Info("Import: \"{0}\"", paragraph.TruncateWithEllipsis(40));
// If the current paragraph looks like a time stamp,
// create a new time point.
if (IsTimeStamp(paragraph))
{
Logger.Info("Import: Time stamp detected", paragraph);
timePoint = CreateTimePoint(paragraph, _parameters, _units);
// Add the time point to the laboratory only if none
// with the same time stamp exists yet.
TimePoint existing = null;
if (Laboratory.TryGetTimePoint(timePoint.TimeStamp, ref existing))
{
timePoint = existing;
}
else
{
Laboratory.AddTimePoint(timePoint);
}
}
// If the current paragraph looks like a paragraph with
// laboratory items, add it to the current time point;
// if no time point exists yet, create one.
else if (IsItemsParagraph(paragraph))
{
Logger.Info("Import: Paragraph with lab items detected");
if (timePoint == null)
{
timePoint = CreateTimePoint(paragraph, _parameters, _units);
Laboratory.AddTimePoint(timePoint);
}
else
{
timePoint.Parse(paragraph);
}
}
else
{
Logger.Debug("Import: Neither time stamp, nor Lauris paragraph");
}
}
}
#endregion
#region Constructor
public BaseImporter()
{
_parameters = Parameters.Default;
_units = Units.Default;
}
#endregion
#region Virtual methods
protected virtual TimePoint CreateTimePoint(string paragraph, Parameters parameters, Units units)
{
NotImplementedException e = new NotImplementedException("Cannot create TimePoint in base class - derived class must override CreateTimePoint");
Logger.Fatal(e);
throw e;
}
protected virtual bool IsTimeStamp(string paragraph)
{
NotImplementedException e = new NotImplementedException("Cannot test time stamp in base class - derived class must override IsTimeStamp");
Logger.Fatal(e);
throw e;
}
protected virtual bool IsItemsParagraph(string paragraph)
{
NotImplementedException e = new NotImplementedException("Cannot test items in base class - derived class must override IsItemsParagraph");
Logger.Fatal(e);
throw e;
}
#endregion
#region Fields
Laboratory _laboratory;
Parameters _parameters;
Units _units;
#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
}
}

View File

@ -0,0 +1,68 @@
/* ZaaImporter.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Bovender.Extensions;
using zaaReloaded2.LabModel;
using zaaReloaded2.Thesaurus;
namespace zaaReloaded2.Importer.ClinicImporter
{
/// <summary>
/// Imports laboratory items by parsing the Lauris data from a
/// physician's letter.
/// </summary>
public class ClinicImporter : BaseImporter
{
#region Constructor
public ClinicImporter() : base() { }
#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
#region Implementation of BaseImporter
protected override TimePoint CreateTimePoint(string paragraph, Parameters parameters, Units units)
{
return new ClinicTimePoint(paragraph, parameters, units);
}
protected override bool IsTimeStamp(string paragraph)
{
return ClinicTimePoint.IsTimeStampLine(paragraph);
}
protected override bool IsItemsParagraph(string paragraph)
{
return ClinicLine.ResemblesClinicLine(paragraph);
}
#endregion
}
}

View File

@ -0,0 +1,138 @@
/* ClinicLine.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using zaaReloaded2.Thesaurus;
using zaaReloaded2.LabModel;
using zaaReloaded2.Importer.ZaaImporter;
using Bovender.Extensions;
namespace zaaReloaded2.Importer.ClinicImporter
{
/// <summary>
/// Parses a line in a lab section produced by the outpatients clinic system,
/// and creates a list of <see cref="zaaReloaded2.Importer.BaseImporter.LabItem"/>s.
/// </summary>
public class ClinicLine
{
#region Static methods
/// <summary>
/// Investigates a paragraph and determines whether it looks
/// like a clinic laboratory items line.
/// </summary>
public static bool ResemblesClinicLine(string line)
{
return _expectedFormat.IsMatch(line);
}
#endregion
#region Public properties
public LaurisItem Item { get; private set; }
/// <summary>
/// Gets the original line that this object was constructed from.
/// </summary>
public string OriginalLine{ get; private set; }
/// <summary>
/// Is true if the <see cref="OriginalLine"/> matches the expected
/// format and contains <see cref="LabItem"/>s.
/// </summary>
public bool IsClinicLine{ get; private set; }
#endregion
#region Constructor
public ClinicLine(string line)
{
OriginalLine = line;
Parse();
}
/// <summary>
/// Constructs a <see cref="ClinicLine"/> object from a given line,
/// using a <paramref name="parameterDictionary"/> and a
/// <paramref name="unitDictionary"/> to translate the individual
/// items' properties.
/// </summary>
/// <param name="line">line 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 ClinicLine(string line,
Thesaurus.Parameters parameterDictionary,
Thesaurus.Units unitDictionary)
{
OriginalLine = line;
_parameterDictionary = parameterDictionary;
_unitDictionary = unitDictionary;
Parse();
}
#endregion
#region Private methods
/// <summary>
/// Attempts to parse a line.
/// </summary>
void Parse()
{
Logger.Info("Parse: \"{0}\"", OriginalLine.TruncateWithEllipsis(40).Replace("\t", " "));
Match m = _expectedFormat.Match(OriginalLine);
if (m.Success)
{
Logger.Info("Parse: Matches clinic line format");
Item = new LaurisItem(m.Groups["item"].Value, _parameterDictionary, _unitDictionary);
IsClinicLine = true;
}
else
{
Logger.Info("Parse: Does not match clinic line format");
IsClinicLine = false;
}
}
#endregion
#region Fields
static readonly Regex _expectedFormat = new Regex(@"\t(?<item>[^:]+:(\t[^\t]+){3})");
Thesaurus.Parameters _parameterDictionary;
Thesaurus.Units _unitDictionary;
#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
}
}

View File

@ -0,0 +1,258 @@
/* ClinicTimePoint.cs
* part of zaaReloaded2
*
* Copyright 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using zaaReloaded2.Thesaurus;
using zaaReloaded2.LabModel;
namespace zaaReloaded2.Importer.ClinicImporter
{
/// <summary>
/// Holds all laboratory items for a given time point.
/// </summary>
class ClinicTimePoint : TimePoint
{
#region Static methods
/// <summary>
/// Examines a string and returns true if it resembles
/// a time stamp line in the clinic output.
/// </summary>
/// <param name="line">Line to examine.</param>
/// <returns>True if line resembles a time stamp line
/// in the clinic output.</returns>
static public bool IsTimeStampLine(string line)
{
return _timeStampRegex.IsMatch(line);
}
/// <summary>
/// Gets a Regex object that matches a clinic time stamp
/// line.
/// </summary>
static public Regex TimeStampRegex
{
get
{
return _timeStampRegex;
}
}
#endregion
#region Properties
/// <summary>
/// Gets an array of lines in this ClinicText.
/// </summary>
public IList<String> Lines
{
[DebuggerStepThrough]
get
{
if (_lines == null)
{
_lines = new List<String>();
}
return _lines;
}
set
{
_lines = value;
ParseLines();
}
}
/// <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
{
return Items.Count > 0;
}
}
/// <summary>
/// Gets or sets the original Lauris text for this timepoint.
/// </summary>
public string ClinicText
{
[DebuggerStepThrough]
get
{
return String.Join(Environment.NewLine, Lines);
}
set
{
if (!String.IsNullOrEmpty(value))
{
Lines = value.Split(
new string[] { Environment.NewLine },
StringSplitOptions.None).ToList();
}
}
}
#endregion
#region Constructors
public ClinicTimePoint() { }
public ClinicTimePoint(
string clinicText,
Parameters parameterDictionary,
Units unitDictionary)
: this()
{
_parameterDictionary = parameterDictionary;
_unitDictionary = unitDictionary;
ClinicText = clinicText;
}
public ClinicTimePoint(string clinicText)
: this(clinicText, null, null)
{ }
public ClinicTimePoint(
IList<String> lines,
Parameters parameterDictionary,
Units unitDictionary)
: this(parameterDictionary, unitDictionary)
{
Lines = lines;
}
public ClinicTimePoint(IList<String> lines)
: this(lines, null, null)
{
}
public ClinicTimePoint(
Parameters parameterDictionary,
Units unitDictionary)
: this()
{
_parameterDictionary = parameterDictionary;
_unitDictionary = unitDictionary;
}
#endregion
#region Public methods
/// <summary>
/// Adds a new line to this time point by parsing
/// the line for a laboratory item.
/// </summary>
/// <param name="paragraph">Line to add.</param>
public override void Parse(string paragraph)
{
Lines.Add(paragraph);
ParseLine(paragraph);
}
#endregion
#region Private methods
/// <summary>
/// Analyzes each clinic line in this time point, sets the date
/// and time, and collects LabItem data.
/// </summary>
void ParseLines()
{
if (Lines != null)
{
foreach (string paragraph in Lines)
{
ParseLine(paragraph);
}
}
}
void ParseLine(string line)
{
Match m = _timeStampRegex.Match(line);
if (m.Success)
{
Logger.Info("ParseLine: Matches time stamp");
DateTime dt;
if (DateTime.TryParseExact(
m.Groups["datetime"].Value,
"dd.MM.yyyy HH:mm",
CultureInfo.InvariantCulture,
DateTimeStyles.AllowWhiteSpaces,
out dt))
{
TimeStamp = dt;
}
}
else
{
Logger.Info("ParseLine: Not a time stamp");
ClinicLine clinicLine = new ClinicLine(
line,
_parameterDictionary,
_unitDictionary);
if (clinicLine.IsClinicLine)
{
Logger.Debug("ParseParagraph: Merging Lauris paragraph");
Items[clinicLine.Item.QualifiedName] = clinicLine.Item;
}
}
}
void AddItems(IItemDictionary items)
{
Items.Merge(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 _timeStampRegex = new Regex(
@"^(Labor:?)?\s*\(?\s*(?<datetime>\d\d\.\d\d\.\d\d\d\d\s+\d\d:\d\d)(:00)?\)\s*$");
IList<String> _lines;
Parameters _parameterDictionary;
Units _unitDictionary;
#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
}
}

View File

@ -169,7 +169,7 @@ namespace zaaReloaded2.Importer.ZaaImporter
/// the paragraph for laboratory items.
/// </summary>
/// <param name="paragraph">Paragraph to add.</param>
public void AddParagraph(string paragraph)
public override void Parse(string paragraph)
{
Paragraphs.Add(paragraph);
ParseParagraph(paragraph);

View File

@ -30,106 +30,30 @@ namespace zaaReloaded2.Importer.ZaaImporter
/// Imports laboratory items by parsing the Lauris data from a
/// physician's letter.
/// </summary>
public class ZaaImporter : IImporter
public class ZaaImporter : BaseImporter
{
#region IImporter implementation
public Laboratory Laboratory
{
[DebuggerStepThrough]
get
{
if (_laboratory == null)
{
_laboratory = new Laboratory();
}
return _laboratory;
}
[DebuggerStepThrough]
set
{
_laboratory = value;
}
}
public bool Success
{
get
{
return Laboratory.TimePoints.Count > 0;
}
}
/// <summary>
/// Splits the <paramref name="text"/> into individual time points
/// and creates <see cref="LaurisTimePoint"/> objects from them.
/// </summary>
/// <param name="text">ZAA-formatted Lauris output to import.</param>
public void Import(string text)
{
Logger.Info("Import: \"{0}\"", text.TruncateWithEllipsis(120));
string[] paragraphs = Helpers.SplitParagraphs(text);
Logger.Info("Import: {0} paragraph(s)", paragraphs.Length);
LaurisTimePoint timePoint = null;
foreach (string paragraph in paragraphs)
{
Logger.Info("Import: \"{0}\"", paragraph.TruncateWithEllipsis(40));
// If the current paragraph looks like a Lauris time stamp,
// create a new time point.
if (LaurisTimePoint.IsTimeStampLine(paragraph))
{
Logger.Info("Import: Time stamp detected", paragraph);
timePoint = new LaurisTimePoint(paragraph, _parameters, _units);
// Add the time point to the laboratory only if none
// with the same time stamp exists yet.
TimePoint existing = null;
if (Laboratory.TryGetTimePoint(timePoint.TimeStamp, ref existing))
{
timePoint = existing as LaurisTimePoint;
}
else
{
Laboratory.AddTimePoint(timePoint);
}
}
// If the current paragraph looks like a paragraph with
// laboratory items, add it to the current time point;
// if no time point exists yet, create one.
else if (LaurisParagraph.ResemblesLaurisParagraph(paragraph))
{
Logger.Info("Import: Lauris paragraph detected");
if (timePoint == null)
{
timePoint = new LaurisTimePoint(_parameters, _units);
Laboratory.AddTimePoint(timePoint);
}
timePoint.AddParagraph(paragraph);
}
else
{
Logger.Debug("Import: Neither time stamp, nor Lauris paragraph");
}
}
}
#endregion
#region Constructor
public ZaaImporter()
{
_parameters = Parameters.Default;
_units = Units.Default;
}
public ZaaImporter() : base() { }
#endregion
#region Fields
#region Implementation of BaseImporter
Laboratory _laboratory;
Parameters _parameters;
Units _units;
protected override TimePoint CreateTimePoint(string paragraph, Parameters parameters, Units units)
{
return new LaurisTimePoint(paragraph, parameters, units);
}
protected override bool IsTimeStamp(string paragraph)
{
return LaurisTimePoint.IsTimeStampLine(paragraph);
}
protected override bool IsItemsParagraph(string paragraph)
{
return LaurisParagraph.ResemblesLaurisParagraph(paragraph);
}
#endregion

View File

@ -68,6 +68,7 @@ namespace zaaReloaded2.LabModel
{
if (String.IsNullOrEmpty(item.QualifiedName))
{
Logger.Fatal("Cannot add item without QualifiedName!");
throw new ArgumentException("Cannot add item that has no qualified name.");
}
Items.Add(item.QualifiedName, item);
@ -84,8 +85,10 @@ namespace zaaReloaded2.LabModel
public void MergeItems(TimePoint otherTimePoint)
{
if (otherTimePoint == null)
{
Logger.Fatal("Cannot merge null!");
throw new ArgumentNullException("otherTimePoint");
}
Items.Merge(otherTimePoint.Items);
}
@ -101,6 +104,21 @@ namespace zaaReloaded2.LabModel
return Items.ContainsKey(item);
}
public virtual void Parse(string paragraph)
{
NotImplementedException e = new NotImplementedException("Cannot parse paragraph in base class - derived class must override Parse");
Logger.Fatal(e);
throw e;
}
#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
}
}

View File

@ -204,7 +204,11 @@
<ItemGroup>
<Compile Include="Commands.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="Importer\ZaaImporter\AutoDetect.cs" />
<Compile Include="Importer\ClinicImporter\ClinicLine.cs" />
<Compile Include="Importer\ClinicImporter\ClinicTimePoint.cs" />
<Compile Include="Importer\ClinicImporter\ClinicImporter.cs" />
<Compile Include="Importer\BaseImporter.cs" />
<Compile Include="Importer\AutoDetector.cs" />
<Compile Include="Controller\Comments\CommentPool.cs" />
<Compile Include="Controller\Elements\ControlElementBase.cs" />
<Compile Include="Controller\Elements\FormatElementBase.cs" />
@ -466,6 +470,7 @@
<ItemGroup>
<Resource Include="Icons\mm.png" />
</ItemGroup>
<ItemGroup />
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>