From 8cdbe2375f10fd36025eb7fe975780f65ccbe550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannick=20B=C3=B6rner?= Date: Wed, 24 Mar 2021 10:47:04 +0100 Subject: [PATCH] Transfer project --- .github/workflows/dotnet.yml | 25 + .../AnonymizerTests.cs | 73 +++ .../Healex.HL7v2Anonymizer.Tests.csproj | 35 + .../TestData/TestAdt1.hl7 | 8 + .../TestData/TestOru1.hl7 | 13 + Healex.HL7v2Anonymizer.sln | 31 + Healex.HL7v2Anonymizer/Anonymizer.cs | 59 ++ .../Healex.HL7v2Anonymizer.csproj | 21 + Healex.HL7v2Anonymizer/Program.cs | 102 +++ Healex.HL7v2Anonymizer/ReplacementOptions.cs | 19 + Healex.HL7v2Anonymizer/appsettings.json | 602 ++++++++++++++++++ README.md | 58 +- images/healex-icon-cropped.png | Bin 0 -> 31068 bytes 13 files changed, 1045 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/dotnet.yml create mode 100644 Healex.HL7v2Anonymizer.Tests/AnonymizerTests.cs create mode 100644 Healex.HL7v2Anonymizer.Tests/Healex.HL7v2Anonymizer.Tests.csproj create mode 100644 Healex.HL7v2Anonymizer.Tests/TestData/TestAdt1.hl7 create mode 100644 Healex.HL7v2Anonymizer.Tests/TestData/TestOru1.hl7 create mode 100644 Healex.HL7v2Anonymizer.sln create mode 100644 Healex.HL7v2Anonymizer/Anonymizer.cs create mode 100644 Healex.HL7v2Anonymizer/Healex.HL7v2Anonymizer.csproj create mode 100644 Healex.HL7v2Anonymizer/Program.cs create mode 100644 Healex.HL7v2Anonymizer/ReplacementOptions.cs create mode 100644 Healex.HL7v2Anonymizer/appsettings.json create mode 100644 images/healex-icon-cropped.png diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..e4301fe --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,25 @@ +name: .NET + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/Healex.HL7v2Anonymizer.Tests/AnonymizerTests.cs b/Healex.HL7v2Anonymizer.Tests/AnonymizerTests.cs new file mode 100644 index 0000000..ee2a48e --- /dev/null +++ b/Healex.HL7v2Anonymizer.Tests/AnonymizerTests.cs @@ -0,0 +1,73 @@ +using HL7.Dotnetcore; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using static Healex.HL7v2Anonymizer.ReplacementOptions; + +namespace Healex.HL7v2Anonymizer.Tests +{ + [TestClass] + public class AnonymizerTests + { + [TestMethod] + [DeploymentItem("Healex.HL7v2Anonymizer.Tests/TestData", "TestData")] + public void AnonymizerTestAdt() + { + TestAnonymization(File.ReadAllText(@"./TestData/TestAdt1.hl7")); + } + + [TestMethod] + [DeploymentItem("Healex.HL7v2Anonymizer.Tests/TestData", "TestData")] + public void AnonymizerTestOru() + { + TestAnonymization(File.ReadAllText(@"./TestData/TestOru1.hl7")); + } + + public void TestAnonymization(string messageContent) + { + var message = new Message(messageContent); + + // Setup + var replacementOptions = getReplacementOptions(); + message.ParseMessage(); + + // Method under test + var anonymizer = new Anonymizer(replacementOptions); + anonymizer.Anonymize(message); + + // Assert + foreach (SegmentReplacement segment in replacementOptions.Segments) + { + foreach (Replacement replacement in segment.Replacements) + { + try + { + Assert.AreEqual(message.GetValue(replacement.Path), replacement.Value); + } + catch (HL7Exception) + { + // Throws if segment is not present + continue; + } + } + } + + // Output for manual inspection + var messageAsString = message.SerializeMessage(true); + Console.WriteLine(messageAsString); + } + + private static ReplacementOptions getReplacementOptions() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false); + + IConfiguration config = builder.Build(); + + var replacementOptions = config.GetSection("ReplacementOptions").Get(); + return replacementOptions; + } + } +} diff --git a/Healex.HL7v2Anonymizer.Tests/Healex.HL7v2Anonymizer.Tests.csproj b/Healex.HL7v2Anonymizer.Tests/Healex.HL7v2Anonymizer.Tests.csproj new file mode 100644 index 0000000..5d55f9c --- /dev/null +++ b/Healex.HL7v2Anonymizer.Tests/Healex.HL7v2Anonymizer.Tests.csproj @@ -0,0 +1,35 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/Healex.HL7v2Anonymizer.Tests/TestData/TestAdt1.hl7 b/Healex.HL7v2Anonymizer.Tests/TestData/TestAdt1.hl7 new file mode 100644 index 0000000..88203bc --- /dev/null +++ b/Healex.HL7v2Anonymizer.Tests/TestData/TestAdt1.hl7 @@ -0,0 +1,8 @@ +MSH|^~\&|NES|NINTENDO|TESTSYSTEM|TESTFACILITY|20010101000000||ADT^A04|Q123456789T123456789X123456|P|2.3 +EVN|A04|20010101000000|||^KOOPA^BOWSER^^^^^^^CURRENT +PID|1||123456789^^^^MR||BROS^MARIO^^^^||19850101000000|M|||123 FAKE STREET^MARIO LUIGI BROS PLACE^TOADSTOOL KINGDOM^NES^A1B2C3^JP^HOME^^1234|1234|(555)555-0123^HOME^JP:1234567^^1^555^5550123~(555)555-0124^HOME^JP:1234568^^1^555^5550124|||S|MSH|12345678|||||||0|||||N +NK1|1|PEACH^PRINCESS^^^^|SO|ANOTHER CASTLE^^TOADSTOOL KINGDOM^NES^^JP|(123)555-1234|(123)555-2345|NOK||||||||||||| +NK1|2|TOADSTOOL^PRINCESS^^^^|SO|YET ANOTHER CASTLE^^TOADSTOOL KINGDOM^NES^^JP|(123)555-3456|(123)555-4567|EMC||||||||||||| +PV1|1|O|ABCD^EFGH^|||^^|123456^DINO^YOSHI^^^^^^MSRM^CURRENT^^^NEIGHBOURHOOD DR NBR^|^DOG^DUCKHUNT^^^^^^^CURRENT||CRD|||||||123456^DINO^YOSHI^^^^^^MSRM^CURRENT^^^NEIGHBOURHOOD DR NBR^|AO|0123456789|1|||||||||||||||||||MSH||A|||20010101000000 +IN1|1|PAR^PARENT||||LUIGI +IN1|2|FRI^FRIEND||||PRINCESS \ No newline at end of file diff --git a/Healex.HL7v2Anonymizer.Tests/TestData/TestOru1.hl7 b/Healex.HL7v2Anonymizer.Tests/TestData/TestOru1.hl7 new file mode 100644 index 0000000..232d962 --- /dev/null +++ b/Healex.HL7v2Anonymizer.Tests/TestData/TestOru1.hl7 @@ -0,0 +1,13 @@ +MSH|^~\&|LCS|LCA|LIS|TEST9999|199807311532||ORU^R01|3629|P|2.2 +PID|2|2161348462|20809880170|1614614|20809880170^TESTPAT||19760924|M|||^^^^ +ORC|NW|8642753100012^LIS|20809880170^LCS||||||19980727000000|||HAVILAND +OBR|1|8642753100012^LIS|20809880170^LCS|008342^UPPER RESPIRATORY +OBX|1|ST|008342^UPPER RESPIRATORY CULTURE^L||FINALREPORT|||||N|F||| 19980729160500|BN +ORC|NW|8642753100012^LIS|20809880170^LCS||||||19980727000000|||HAVILAND +OBR|2|8642753100012^LIS|20809880170^LCS|997602^.^L|||19980727175800||||G||| +OBX|2|CE|997231^RESULT 1^L||M415|||||N|F|||19980729160500|BN +NTE|1|L|MORAXELLA (BRANHAMELLA) CATARRHALIS +NTE|2|L| HEAVY GROWTH +NTE|3|L| BETA LACTAMASE POSITIVE +OBX|3|CE|997232^RESULT 2^L||MR105|||||N|F|||19980729160500|BN +NTE|1|L|ROUTINE RESPIRATORY FLORA \ No newline at end of file diff --git a/Healex.HL7v2Anonymizer.sln b/Healex.HL7v2Anonymizer.sln new file mode 100644 index 0000000..aa47054 --- /dev/null +++ b/Healex.HL7v2Anonymizer.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31005.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Healex.HL7v2Anonymizer", "Healex.HL7v2Anonymizer\Healex.HL7v2Anonymizer.csproj", "{9A79221A-7684-4BD6-9721-199934853893}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Healex.HL7v2Anonymizer.Tests", "Healex.HL7v2Anonymizer.Tests\Healex.HL7v2Anonymizer.Tests.csproj", "{2B44D800-3F01-4288-AFD1-1DF7D956B661}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9A79221A-7684-4BD6-9721-199934853893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A79221A-7684-4BD6-9721-199934853893}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A79221A-7684-4BD6-9721-199934853893}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A79221A-7684-4BD6-9721-199934853893}.Release|Any CPU.Build.0 = Release|Any CPU + {2B44D800-3F01-4288-AFD1-1DF7D956B661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B44D800-3F01-4288-AFD1-1DF7D956B661}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B44D800-3F01-4288-AFD1-1DF7D956B661}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B44D800-3F01-4288-AFD1-1DF7D956B661}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {971ACC78-6922-482C-8AC1-693787A35B0B} + EndGlobalSection +EndGlobal diff --git a/Healex.HL7v2Anonymizer/Anonymizer.cs b/Healex.HL7v2Anonymizer/Anonymizer.cs new file mode 100644 index 0000000..181702f --- /dev/null +++ b/Healex.HL7v2Anonymizer/Anonymizer.cs @@ -0,0 +1,59 @@ +using HL7.Dotnetcore; +using System; +using static Healex.HL7v2Anonymizer.ReplacementOptions; + +namespace Healex.HL7v2Anonymizer +{ + public class Anonymizer + { + private ReplacementOptions _replacementOptions; + + public Anonymizer(ReplacementOptions replacementOptions) + { + _replacementOptions = replacementOptions; + } + + public bool Anonymize(Message message) + { + var isSuccess = true; + foreach (SegmentReplacement segmentReplacement in _replacementOptions.Segments) + { + var segments = message.Segments(segmentReplacement.Segment); + foreach (Segment segment in segments) + { + // Create new temporary message for each repeating segment + // because we can't set values in all repeating segments at once + // We use references so this overwrites the original segments + var tempMessage = new Message(); + tempMessage.AddNewSegment(segment); + + foreach (Replacement replacement in segmentReplacement.Replacements) + { + tryReplaceValue(replacement, tempMessage); + } + } + } + return isSuccess; + } + + private bool tryReplaceValue(Replacement replacement, Message message) + { + try + { + message.SetValue(replacement.Path, replacement.Value); + } + catch (HL7Exception) + { + // Throws if segment is not present + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return false; + } + return true; + } + } + + +} diff --git a/Healex.HL7v2Anonymizer/Healex.HL7v2Anonymizer.csproj b/Healex.HL7v2Anonymizer/Healex.HL7v2Anonymizer.csproj new file mode 100644 index 0000000..6e5ef97 --- /dev/null +++ b/Healex.HL7v2Anonymizer/Healex.HL7v2Anonymizer.csproj @@ -0,0 +1,21 @@ + + + + Exe + net5.0 + + + + + + + + + + + + Always + + + + diff --git a/Healex.HL7v2Anonymizer/Program.cs b/Healex.HL7v2Anonymizer/Program.cs new file mode 100644 index 0000000..a1756cd --- /dev/null +++ b/Healex.HL7v2Anonymizer/Program.cs @@ -0,0 +1,102 @@ +using HL7.Dotnetcore; +using Microsoft.Extensions.Configuration; +using System; +using System.IO; + +namespace Healex.HL7v2Anonymizer +{ + class Program + { + static void Main(string[] args) + { + waitForInput(); + } + + private static void waitForInput() + { + Console.WriteLine("Welcome to the Healex HL7v2 anonymizer!"); + Console.WriteLine("---------------------------------------------------------------------------------------"); + Console.WriteLine("WARNING: THIS APPLICATION OVERWRITES THE ORIGINAL v2 MESSAGES. BACKUP YOUR FILES FIRST!"); + Console.WriteLine("---------------------------------------------------------------------------------------"); + Console.WriteLine("Enter the directory to your v2 messages and press enter:"); + var directory = Console.ReadLine(); + Console.WriteLine(); + tryAnonymizeMessages(directory); + Console.WriteLine(); + waitForInput(); + } + + private static void tryAnonymizeMessages(string directory) + { + var pathsToV2Messages = getPathsToV2Messages(directory); + var anonymizer = new Anonymizer(getReplacementOptions()); + + if (pathsToV2Messages is not null) + { + if (pathsToV2Messages.Length == 0) + Console.WriteLine($"=> No v2 messages found in {directory}"); + + foreach (string path in pathsToV2Messages) + { + var message = readAndParseMessage(path); + var success = anonymizer.Anonymize(message); + serializeAndWriteMessageOrLogError(success, message, path); + } + } + } + + private static void serializeAndWriteMessageOrLogError(bool success, Message message, string path) + { + if (success) + { + Console.WriteLine($"=> Anonymization successful: {path}"); + var serializedMessage = message.SerializeMessage(true); + File.WriteAllText(path, serializedMessage); + } + else + { + Console.WriteLine($"=> Anonymization fail: {path}"); + } + } + + private static string[] getPathsToV2Messages(string directory) + { + try + { + var v2Messages = Directory.GetFiles(directory, "*.hl7"); + return v2Messages; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return null; + } + } + + private static Message readAndParseMessage(string path) + { + try + { + var message = new Message(File.ReadAllText(path)); + message.ParseMessage(); + return message; + } catch (Exception e) + { + Console.Write(e.Message); + return null; + } + } + + private static ReplacementOptions getReplacementOptions() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false); + + IConfiguration config = builder.Build(); + + var replacementOptions = config.GetSection("ReplacementOptions").Get(); + return replacementOptions; + } + } +} diff --git a/Healex.HL7v2Anonymizer/ReplacementOptions.cs b/Healex.HL7v2Anonymizer/ReplacementOptions.cs new file mode 100644 index 0000000..a2e1d06 --- /dev/null +++ b/Healex.HL7v2Anonymizer/ReplacementOptions.cs @@ -0,0 +1,19 @@ +namespace Healex.HL7v2Anonymizer +{ + public class ReplacementOptions + { + public SegmentReplacement[] Segments { get; set; } + + public class SegmentReplacement + { + public string Segment { get; set; } + public Replacement[] Replacements { get; set; } + } + + public class Replacement + { + public string Path { get; set; } + public string Value { get; set; } + } + } +} diff --git a/Healex.HL7v2Anonymizer/appsettings.json b/Healex.HL7v2Anonymizer/appsettings.json new file mode 100644 index 0000000..48a68e9 --- /dev/null +++ b/Healex.HL7v2Anonymizer/appsettings.json @@ -0,0 +1,602 @@ +{ + "ReplacementOptions": { + "Segments": [ + { + "Segment": "PID", + "Replacements": [ + { + "Path": "PID.2.1", + "Value": "Id" + }, + { + "Path": "PID.3.1", + "Value": "Id" + }, + { + "Path": "PID.4.1", + "Value": "Id" + }, + { + "Path": "PID.5.1", + "Value": "Family name" + }, + { + "Path": "PID.5.2", + "Value": "Given name" + }, + { + "Path": "PID.6.1", + "Value": "Family name" + }, + { + "Path": "PID.6.2", + "Value": "Given name" + }, + { + "Path": "PID.6.3", + "Value": "Second name" + }, + { + "Path": "PID.9.1", + "Value": "Family name" + }, + { + "Path": "PID.9.2", + "Value": "Given name" + }, + { + "Path": "PID.9.3", + "Value": "Second name" + }, + { + "Path": "PID.7", + "Value": "Birthdate" + }, + { + "Path": "PID.11.1", + "Value": "Street" + }, + { + "Path": "PID.11.2", + "Value": "Other" + }, + { + "Path": "PID.11.3", + "Value": "City" + }, + { + "Path": "PID.11.4", + "Value": "State" + }, + { + "Path": "PID.11.5", + "Value": "Zip code" + }, + { + "Path": "PID.11.6", + "Value": "Country" + }, + { + "Path": "PID.13.1", + "Value": "Telephone number" + }, + { + "Path": "PID.13.4", + "Value": "Email address" + }, + { + "Path": "PID.13.5", + "Value": "Country code" + }, + { + "Path": "PID.13.6", + "Value": "Area/City code" + }, + { + "Path": "PID.13.7", + "Value": "Phone number" + }, + { + "Path": "PID.13.11", + "Value": "Speeddial" + }, + { + "Path": "PID.13.12", + "Value": "Phone number" + }, + { + "Path": "PID.14.1", + "Value": "Telephone number" + }, + { + "Path": "PID.14.4", + "Value": "Email address" + }, + { + "Path": "PID.14.5", + "Value": "Country code" + }, + { + "Path": "PID.14.6", + "Value": "Area/City code" + }, + { + "Path": "PID.14.7", + "Value": "Phone number" + }, + { + "Path": "PID.14.12", + "Value": "Phone number" + }, + { + "Path": "PID.18.1", + "Value": "MRN" + }, + { + "Path": "PID.20.1", + "Value": "Driver's license number" + }, + { + "Path": "PID.29.1", + "Value": "DeathDate" + } + ] + }, + { + "Segment": "NK1", + "Replacements": [ + { + "Path": "NK1.1.1", + "Value": "Id" + }, + { + "Path": "NK1.2.1", + "Value": "Familiy Name" + }, + { + "Path": "NK1.2.2", + "Value": "Given Name" + }, + { + "Path": "NK1.2.3", + "Value": "Second Name" + }, + { + "Path": "NK1.4.1", + "Value": "Street Address" + }, + { + "Path": "NK1.4.2", + "Value": "Other Designation" + }, + { + "Path": "NK1.4.3", + "Value": "City" + }, + { + "Path": "NK1.4.4", + "Value": "State or Province" + }, + { + "Path": "NK1.4.5", + "Value": "Zip or postal code" + }, + { + "Path": "NK1.4.6", + "Value": "Country" + }, + { + "Path": "NK1.5.1", + "Value": "Telephone Number" + }, + { + "Path": "NK1.5.4", + "Value": "Email Adress" + }, + { + "Path": "NK1.5.5", + "Value": "Country Code" + }, + { + "Path": "NK1.5.6", + "Value": "Area Code" + }, + { + "Path": "NK1.5.7", + "Value": "Phone Number" + }, + { + "Path": "NK1.6.1", + "Value": "Telephone Number" + }, + { + "Path": "NK1.6.4", + "Value": "Email Adress" + }, + { + "Path": "NK1.6.5", + "Value": "Country Code" + }, + { + "Path": "NK1.6.6", + "Value": "Area Code" + }, + { + "Path": "NK1.6.7", + "Value": "Phone Number" + }, + { + "Path": "NK1.10", + "Value": "Job" + }, + { + "Path": "NK1.12", + "Value": "Employee Number" + }, + { + "Path": "NK1.13", + "Value": "Organization Name" + }, + { + "Path": "NK1.16", + "Value": "Birthdate" + }, + { + "Path": "NK1.26.1", + "Value": "Family Name" + }, + { + "Path": "NK1.26.2", + "Value": "Given Name" + }, + { + "Path": "NK1.26.3", + "Value": "Second Name" + }, + { + "Path": "NK1.30.1", + "Value": "Family Name" + }, + { + "Path": "NK1.30.2", + "Value": "Given Name" + }, + { + "Path": "NK1.30.3", + "Value": "Second Name" + }, + { + "Path": "NK1.31.1", + "Value": "Telephone Number" + }, + { + "Path": "NK1.31.4", + "Value": "Email Adress" + }, + { + "Path": "NK1.31.5", + "Value": "Country Code" + }, + { + "Path": "NK1.31.6", + "Value": "Area Code" + }, + { + "Path": "NK1.31.7", + "Value": "Phone Number" + }, + { + "Path": "NK1.32.1", + "Value": "Street Address" + }, + { + "Path": "NK1.32.2", + "Value": "Other Designation" + }, + { + "Path": "NK1.32.3", + "Value": "City" + }, + { + "Path": "NK1.32.4", + "Value": "State or Province" + }, + { + "Path": "NK1.32.5", + "Value": "Zip or postal code" + }, + { + "Path": "NK1.32.6", + "Value": "Country" + }, + { + "Path": "NK1.37", + "Value": "Socal Security Number" + } + ] + }, + { + "Segment": "IN1", + "Replacements": [ + { + "Path": "IN1.6.1", + "Value": "Family Name" + }, + { + "Path": "IN1.6.2", + "Value": "Given Name" + }, + { + "Path": "IN1.6.3", + "Value": "Second name" + }, + { + "Path": "IN1.7", + "Value": "Id" + }, + { + "Path": "IN1.11.1", + "Value": "Name" + }, + { + "Path": "IN1.11.3", + "Value": "1" + }, + { + "Path": "IN1.11.4", + "Value": "Check digit" + }, + { + "Path": "IN1.16.1", + "Value": "Familiy Name" + }, + { + "Path": "IN1.16.2", + "Value": "Given Name" + }, + { + "Path": "IN1.16.3", + "Value": "Second Name" + }, + { + "Path": "IN1.19.1", + "Value": "Street Address" + }, + { + "Path": "IN1.19.2", + "Value": "Other Designation" + }, + { + "Path": "IN1.19.3", + "Value": "City" + }, + { + "Path": "IN1.19.4", + "Value": "State or Province" + }, + { + "Path": "IN1.19.5", + "Value": "Zip or postal code" + }, + { + "Path": "IN1.19.6", + "Value": "Country" + }, + { + "Path": "IN1.44.1", + "Value": "Street Address" + }, + { + "Path": "IN1.44.2", + "Value": "Other Designation" + }, + { + "Path": "IN1.44.3", + "Value": "City" + }, + { + "Path": "IN1.44.4", + "Value": "State or Province" + }, + { + "Path": "IN1.44.5", + "Value": "Zip or postal code" + }, + { + "Path": "IN1.44.6", + "Value": "Country" + }, + { + "Path": "IN1.18", + "Value": "Birthdate" + } + ] + }, + { + "Segment": "IN2", + "Replacements": [ + { + "Path": "IN2.1.1", + "Value": "Id" + }, + { + "Path": "IN2.2", + "Value": "Social Security" + }, + { + "Path": "IN2.3.1", + "Value": "Id Number" + }, + { + "Path": "IN2.3.2", + "Value": "Family Name" + }, + { + "Path": "IN2.3.3", + "Value": "Given Name" + }, + { + "Path": "IN2.3.3", + "Value": "Given Name" + }, + { + "Path": "IN2.3.4", + "Value": "Second Name" + }, + { + "Path": "IN2.6", + "Value": "Card Number" + }, + { + "Path": "IN2.7.1", + "Value": "Family Name" + }, + { + "Path": "IN2.7.2", + "Value": "Given Name" + }, + { + "Path": "IN2.7.3", + "Value": "Second Name" + }, + { + "Path": "IN2.8", + "Value": "Medicaid case number" + }, + { + "Path": "IN2.10", + "Value": "Military ID number" + }, + { + "Path": "IN2.25.1", + "Value": "ID" + }, + { + "Path": "IN2.26.1", + "Value": "ID" + }, + { + "Path": "IN2.40.1", + "Value": "Family Name" + }, + { + "Path": "IN2.40.2", + "Value": "Given Name" + }, + { + "Path": "IN2.40.3", + "Value": "Second Name" + }, + { + "Path": "IN2.46", + "Value": "Job title" + }, + { + "Path": "IN2.49.1", + "Value": "Family Name" + }, + { + "Path": "IN2.49.2", + "Value": "Given Name" + }, + { + "Path": "IN2.49.3", + "Value": "Second Name" + }, + { + "Path": "IN2.50.1", + "Value": "Telephone number" + }, + { + "Path": "IN2.50.4", + "Value": "Email address" + }, + { + "Path": "IN2.50.5", + "Value": "Country code" + }, + { + "Path": "IN2.50.6", + "Value": "Area/City code" + }, + { + "Path": "IN2.50.7", + "Value": "Phone number" + }, + { + "Path": "IN2.52.1", + "Value": "Family Name" + }, + { + "Path": "IN2.52.2", + "Value": "Given Name" + }, + { + "Path": "IN2.52.3", + "Value": "Second Name" + }, + { + "Path": "IN2.53.1", + "Value": "Telephone number" + }, + { + "Path": "IN2.53.4", + "Value": "Email address" + }, + { + "Path": "IN2.53.5", + "Value": "Country code" + }, + { + "Path": "IN2.53.6", + "Value": "Area/City code" + }, + { + "Path": "IN2.53.7", + "Value": "Phone number" + }, + { + "Path": "IN2.63.1", + "Value": "Telephone number" + }, + { + "Path": "IN2.63.4", + "Value": "Email address" + }, + { + "Path": "IN2.63.5", + "Value": "Country code" + }, + { + "Path": "IN2.63.6", + "Value": "Area/City code" + }, + { + "Path": "IN2.63.7", + "Value": "Phone number" + }, + { + "Path": "IN2.64.1", + "Value": "Telephone number" + }, + { + "Path": "IN2.64.4", + "Value": "Email address" + }, + { + "Path": "IN2.64.5", + "Value": "Country code" + }, + { + "Path": "IN2.64.6", + "Value": "Area/City code" + }, + { + "Path": "IN2.64.7", + "Value": "Phone number" + } + ] + } + ] + } +} diff --git a/README.md b/README.md index 3a09403..0248c70 100644 --- a/README.md +++ b/README.md @@ -1 +1,57 @@ -# Healex.HL7-V2-Anonymizer \ No newline at end of file +![Healex](images/healex-icon-cropped.png) + +# Healex.HL7v2Anonymizer + +This console application allows you to anonymize HL7v2 messages. + +## Motivation + +The project was build to enable data stewards and scientists to share HL7v2 sample messages without identifiable data. + +## How to use? + +**Warning: This application overwrites the original message so make sure you are working on a copy.** + +1. Download the latest release to a location of your choice. +2. Unzip it. +3. Run the application and enter the path to your v2 messages. Make sure to back them up prior as this application will overwrite the original messages. + +This application will use the `appsettings.json` to read the values that are to be replaced for each segments and their corresponding subsegments. + +A segment is recognized by its `"Segment"` property. Each segment contains an array of replacements. A segment's subsegment can be identified by its `"Path"` property inside the replacements array. Subsegments will also have a value property that contains the value by which a value inside a HL7v2 message is to be replaced. + +Say for instance, you want to replace the value that is currently assigned for the given name of an `NK1` segment. Navigate to `appsettings.json`, find the `NK1` segment and replace the value for path `"Path": "NK1.2.2"` like this: + +```json + { + "Segment": "NK1", + "Replacements": [ + // ommited + { + "Path": "NK1.2.2", + "Value": "Given name" <---- replace this value + }, + // omitted + ] + } +``` + +Adding additional segments works in a similar manner. Simply add a new segment to the `appsettings.json` after `"Segment": "IN2"`. Make sure to add a comma to the closing brace of this segment so the JSON file remains valid, then use this template to add a new segment. + +```json + { + "Segment": "SEGMENT_ID", + "Replacements": [ + { + "Path": "Path_to_sub_segment1", + "Value": "Replacement_value1" + }, + { + "Path": "Path_to_sub_segment2", + "Value": "Replacement_value2" + } + ] + } +``` + +Save and restart application for the changes to take effect. diff --git a/images/healex-icon-cropped.png b/images/healex-icon-cropped.png new file mode 100644 index 0000000000000000000000000000000000000000..73b37ee6118e68b98ec739cd3d4214db60e96ed4 GIT binary patch literal 31068 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 BVz&SQ literal 0 HcmV?d00001