diff --git a/Tests/Importer/ClinicImporter/ClinicImporterTest.cs b/Tests/Importer/ClinicImporter/ClinicImporterTest.cs new file mode 100755 index 0000000..4ad5609 --- /dev/null +++ b/Tests/Importer/ClinicImporter/ClinicImporterTest.cs @@ -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 + { + /// + /// 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 . + /// + [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); + } + } +} diff --git a/Tests/Importer/ClinicImporter/ClinicItemTest.cs b/Tests/Importer/ClinicImporter/ClinicItemTest.cs new file mode 100755 index 0000000..40a06c9 --- /dev/null +++ b/Tests/Importer/ClinicImporter/ClinicItemTest.cs @@ -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 +{ + /// + /// 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. + /// + [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"); + } + } +} diff --git a/Tests/Importer/ClinicImporter/ClinicLineTest.cs b/Tests/Importer/ClinicImporter/ClinicLineTest.cs new file mode 100755 index 0000000..f0bbdd1 --- /dev/null +++ b/Tests/Importer/ClinicImporter/ClinicLineTest.cs @@ -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); + } + } +} diff --git a/Tests/Importer/ClinicImporter/TimePointTest.cs b/Tests/Importer/ClinicImporter/TimePointTest.cs new file mode 100755 index 0000000..3569617 --- /dev/null +++ b/Tests/Importer/ClinicImporter/TimePointTest.cs @@ -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'."); + } + } +} diff --git a/Tests/TestHelpers.cs b/Tests/TestHelpers.cs index a95db83..fa8fff0 100755 --- a/Tests/TestHelpers.cs +++ b/Tests/TestHelpers.cs @@ -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; } + + /// + /// Creates a ClinicImporter object and imports demo-output-clinic.txt. + /// + /// + 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; + } } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 4c7e441..5ac530b 100755 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -91,6 +91,10 @@ + + + + @@ -144,6 +148,10 @@ + + + + diff --git a/Tests/demo-output-clinic.txt b/Tests/demo-output-clinic.txt new file mode 100755 index 0000000..c36f1da --- /dev/null +++ b/Tests/demo-output-clinic.txt @@ -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 diff --git a/zaaReloaded2/Commands.cs b/zaaReloaded2/Commands.cs index c6b34d1..c40a37d 100755 --- a/zaaReloaded2/Commands.cs +++ b/zaaReloaded2/Commands.cs @@ -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(); diff --git a/zaaReloaded2/Demo/Demo.docx b/zaaReloaded2/Demo/Demo.docx index 6206475..a81f68b 100755 Binary files a/zaaReloaded2/Demo/Demo.docx and b/zaaReloaded2/Demo/Demo.docx differ diff --git a/zaaReloaded2/Importer/ZaaImporter/AutoDetect.cs b/zaaReloaded2/Importer/AutoDetector.cs similarity index 56% rename from zaaReloaded2/Importer/ZaaImporter/AutoDetect.cs rename to zaaReloaded2/Importer/AutoDetector.cs index 3bf9905..b1798eb 100755 --- a/zaaReloaded2/Importer/ZaaImporter/AutoDetect.cs +++ b/zaaReloaded2/Importer/AutoDetector.cs @@ -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 + /// /// Attempts to automatically detect laboratory data in the Word /// document. @@ -34,7 +38,7 @@ namespace zaaReloaded2.Importer.ZaaImporter /// True if laboratory data was detected, false if not. /// if /// is null. - 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 + + /// + /// Returns true if a paragraph is a time stamp line. + /// + 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; + } + /// /// Returns true if a paragraph is either a time stamp line /// or a paragraph with laboratory items. /// + /// + /// 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. + /// /// /// - 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; } /// @@ -110,9 +165,24 @@ namespace zaaReloaded2.Importer.ZaaImporter /// /// Paragraph whose index to return. /// Index of the paragraph. - 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 } } diff --git a/zaaReloaded2/Importer/BaseImporter.cs b/zaaReloaded2/Importer/BaseImporter.cs new file mode 100755 index 0000000..374085f --- /dev/null +++ b/zaaReloaded2/Importer/BaseImporter.cs @@ -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 +{ + /// + /// Base class for certain importers such as ZaaImporter, ClinicImporter. + /// + 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; + } + } + + /// + /// Splits the into individual time points + /// and creates objects from them. + /// + 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 _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion + } +} diff --git a/zaaReloaded2/Importer/ClinicImporter/ClinicImporter.cs b/zaaReloaded2/Importer/ClinicImporter/ClinicImporter.cs new file mode 100755 index 0000000..024985e --- /dev/null +++ b/zaaReloaded2/Importer/ClinicImporter/ClinicImporter.cs @@ -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 +{ + /// + /// Imports laboratory items by parsing the Lauris data from a + /// physician's letter. + /// + 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 _logger = new Lazy(() => 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 + } +} diff --git a/zaaReloaded2/Importer/ClinicImporter/ClinicLine.cs b/zaaReloaded2/Importer/ClinicImporter/ClinicLine.cs new file mode 100755 index 0000000..43459c9 --- /dev/null +++ b/zaaReloaded2/Importer/ClinicImporter/ClinicLine.cs @@ -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 +{ + /// + /// Parses a line in a lab section produced by the outpatients clinic system, + /// and creates a list of s. + /// + public class ClinicLine + { + #region Static methods + + /// + /// Investigates a paragraph and determines whether it looks + /// like a clinic laboratory items line. + /// + public static bool ResemblesClinicLine(string line) + { + return _expectedFormat.IsMatch(line); + } + + #endregion + + #region Public properties + + public LaurisItem Item { get; private set; } + + /// + /// Gets the original line that this object was constructed from. + /// + public string OriginalLine{ get; private set; } + + /// + /// Is true if the matches the expected + /// format and contains s. + /// + public bool IsClinicLine{ get; private set; } + + #endregion + + #region Constructor + + public ClinicLine(string line) + { + OriginalLine = line; + Parse(); + } + + /// + /// Constructs a object from a given line, + /// using a and a + /// to translate the individual + /// items' properties. + /// + /// line to parse. + /// ParameterDictionary that contains + /// canonical names and material types. + /// Unit dictionary that contains canonical + /// unit names. + public ClinicLine(string line, + Thesaurus.Parameters parameterDictionary, + Thesaurus.Units unitDictionary) + { + OriginalLine = line; + _parameterDictionary = parameterDictionary; + _unitDictionary = unitDictionary; + Parse(); + } + + #endregion + + #region Private methods + + /// + /// Attempts to parse a line. + /// + 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(?[^:]+:(\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 _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion + } +} diff --git a/zaaReloaded2/Importer/ClinicImporter/ClinicTimePoint.cs b/zaaReloaded2/Importer/ClinicImporter/ClinicTimePoint.cs new file mode 100755 index 0000000..8ca5aeb --- /dev/null +++ b/zaaReloaded2/Importer/ClinicImporter/ClinicTimePoint.cs @@ -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 +{ + /// + /// Holds all laboratory items for a given time point. + /// + class ClinicTimePoint : TimePoint + { + #region Static methods + + /// + /// Examines a string and returns true if it resembles + /// a time stamp line in the clinic output. + /// + /// Line to examine. + /// True if line resembles a time stamp line + /// in the clinic output. + static public bool IsTimeStampLine(string line) + { + return _timeStampRegex.IsMatch(line); + } + + /// + /// Gets a Regex object that matches a clinic time stamp + /// line. + /// + static public Regex TimeStampRegex + { + get + { + return _timeStampRegex; + } + } + + #endregion + + #region Properties + + /// + /// Gets an array of lines in this ClinicText. + /// + public IList Lines + { + [DebuggerStepThrough] + get + { + if (_lines == null) + { + _lines = new List(); + } + return _lines; + } + set + { + _lines = value; + ParseLines(); + } + } + + /// + /// Is true if the LaurisText has time stamp in the first + /// paragraph and s in the others. + /// + public bool IsValidTimePoint + { + get + { + return Items.Count > 0; + } + } + + /// + /// Gets or sets the original Lauris text for this timepoint. + /// + 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 lines, + Parameters parameterDictionary, + Units unitDictionary) + : this(parameterDictionary, unitDictionary) + { + Lines = lines; + } + + public ClinicTimePoint(IList lines) + : this(lines, null, null) + { + } + + public ClinicTimePoint( + Parameters parameterDictionary, + Units unitDictionary) + : this() + { + _parameterDictionary = parameterDictionary; + _unitDictionary = unitDictionary; + } + + #endregion + + #region Public methods + + /// + /// Adds a new line to this time point by parsing + /// the line for a laboratory item. + /// + /// Line to add. + public override void Parse(string paragraph) + { + Lines.Add(paragraph); + ParseLine(paragraph); + } + + #endregion + + #region Private methods + + /// + /// Analyzes each clinic line in this time point, sets the date + /// and time, and collects LabItem data. + /// + 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 + + /// + /// A regular expression that matches the time stamp in the first + /// paragraph of a LaurisText. + /// + static readonly Regex _timeStampRegex = new Regex( + @"^(Labor:?)?\s*\(?\s*(?\d\d\.\d\d\.\d\d\d\d\s+\d\d:\d\d)(:00)?\)\s*$"); + IList _lines; + Parameters _parameterDictionary; + Units _unitDictionary; + + #endregion + + #region Class logger + + private static NLog.Logger Logger { get { return _logger.Value; } } + + private static readonly Lazy _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion + } +} diff --git a/zaaReloaded2/Importer/ZaaImporter/LaurisTimePoint.cs b/zaaReloaded2/Importer/ZaaImporter/LaurisTimePoint.cs index 3d5d1ef..27978bd 100755 --- a/zaaReloaded2/Importer/ZaaImporter/LaurisTimePoint.cs +++ b/zaaReloaded2/Importer/ZaaImporter/LaurisTimePoint.cs @@ -169,7 +169,7 @@ namespace zaaReloaded2.Importer.ZaaImporter /// the paragraph for laboratory items. /// /// Paragraph to add. - public void AddParagraph(string paragraph) + public override void Parse(string paragraph) { Paragraphs.Add(paragraph); ParseParagraph(paragraph); diff --git a/zaaReloaded2/Importer/ZaaImporter/ZaaImporter.cs b/zaaReloaded2/Importer/ZaaImporter/ZaaImporter.cs index fdc869f..48f490c 100755 --- a/zaaReloaded2/Importer/ZaaImporter/ZaaImporter.cs +++ b/zaaReloaded2/Importer/ZaaImporter/ZaaImporter.cs @@ -30,106 +30,30 @@ namespace zaaReloaded2.Importer.ZaaImporter /// Imports laboratory items by parsing the Lauris data from a /// physician's letter. /// - 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; - } - } - - /// - /// Splits the into individual time points - /// and creates objects from them. - /// - /// ZAA-formatted Lauris output to import. - 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 diff --git a/zaaReloaded2/LabModel/TimePoint.cs b/zaaReloaded2/LabModel/TimePoint.cs index a96ffb7..f5c65fe 100755 --- a/zaaReloaded2/LabModel/TimePoint.cs +++ b/zaaReloaded2/LabModel/TimePoint.cs @@ -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 _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + #endregion } } diff --git a/zaaReloaded2/zaaReloaded2.csproj b/zaaReloaded2/zaaReloaded2.csproj index fd33e25..26438d8 100755 --- a/zaaReloaded2/zaaReloaded2.csproj +++ b/zaaReloaded2/zaaReloaded2.csproj @@ -204,7 +204,11 @@ - + + + + + @@ -466,6 +470,7 @@ + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)