Skip to content

Commit 4558912

Browse files
committed
Support experimental SSN4 matching
1 parent bed18da commit 4558912

File tree

6 files changed

+149
-11
lines changed

6 files changed

+149
-11
lines changed

src/main/java/ca/uhn/fhir/jpa/starter/operations/IdentityMatching.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ public class IdentityMatching {
4545
private final String IDI_Patient_L1_FhirPath = "((identifier.type.coding.exists(code = 'PPN' or code = 'DL' or code = 'STID') or identifier.exists(system='http://hl7.org/fhir/us/identity-matching/ns/HL7Identifier')) and identifier.value.exists()).toInteger()*10 + iif(((address.exists(use = 'home') and address.line.exists() and (address.postalCode.exists() or (address.state.exists() and address.city.exists()))).toInteger() + (identifier.type.coding.exists(code != 'PPN' and code != 'DL' and code != 'STID') and identifier.value.exists()).toInteger() + (telecom.exists(system = 'email') and telecom.value.exists()).toInteger() + (telecom.exists(system = 'phone') and telecom.value.exists()).toInteger() + (photo.exists()).toInteger()) =1,4,iif(((address.exists(use = 'home') and address.line.exists() and (address.postalCode.exists() or (address.state.exists() and address.city.exists()))).toInteger() + (identifier.type.coding.exists(code != 'PPN' and code != 'DL' and code != 'STID') and identifier.value.exists()).toInteger() + (telecom.exists(system = 'email') and telecom.value.exists()).toInteger() + (telecom.exists(system = 'phone') and telecom.value.exists()).toInteger() + (photo.exists()).toInteger()) >1,5,0)) + (name.family.exists() and name.given.exists()).toInteger()*3 + (birthDate.exists().toInteger()*2) >= 10";
4646

4747
private final String BIRTH_SEX_EXTENSION = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex";
48-
private final String HL7_IDENTIFIER_SYSTEM = "http://terminology.hl7.org/CodeSystem/v2-0203";
49-
private final String IDENTITY_IDENTIFIER_SYSTEM = "http://hl7.org/fhir/us/identity-matching/CodeSystem/Identity-Identifier-cs";
48+
private final String IDENTIFIER_SSN_SYSTEM = "http://hl7.org/fhir/sid/us-ssn";
49+
private final String IDENTITY_IDENTIFIER_SSN4_SYSTEM = "http://hl7.org/fhir/us/identity-matching/CodeSystem/Identity-Identifier-cs";
50+
private final String IDENTITY_IDENTIFIER_SSN4_CODE = "SSN4";
51+
private final String IDENTITY_IDENTIFIER_SSN4_PARAMETER = "ssn4";
5052

5153

5254
private AppProperties appProperties;
@@ -284,17 +286,24 @@ protected Resource doMatch(
284286
identifiers.stream().forEach(x -> {
285287
// System.out.println("x: " + (x.hasType() ? x.getType().getCoding().stream().findFirst().get().getSystem() : "") + " -- " + x.getSystem() + " " + x.getValue());
286288

289+
if (!x.hasValue()) {
290+
return;
291+
}
292+
293+
// primary check for a full identifier match
287294
if (
288295
// identifier value must match
289296
x.getValue().equals(id.getIdentifierValue())
290297

298+
// ... and
291299
&& (
292300
// identifier type system + code need to match...
293301
(x.hasType() && x.getType().hasCoding() && x.getType().getCoding().stream().anyMatch(
294-
coding -> coding.getSystem().equals(id.getIdentifierTypeSystem()) && coding.getCode().equals(id.getIdentifierTypeCode())
302+
coding -> coding.hasSystem() && coding.getSystem().equals(id.getIdentifierTypeSystem())
303+
&& coding.hasCode() && coding.getCode().equals(id.getIdentifierTypeCode())
295304
))
296305
// ...or identifier system has to match (if no type exists)
297-
|| x.getSystem().equals(id.getIdentifierSystem())
306+
|| (x.hasSystem() && x.getSystem().equals(id.getIdentifierSystem()))
298307
)
299308

300309
) {
@@ -312,10 +321,23 @@ protected Resource doMatch(
312321
case("DL"): { scorer.setDriversLicenseMatch(true); } break;
313322
case("PPN"): { scorer.setPassportMatch(true); } break;
314323
case("SB"): { scorer.setSSNMatch(true); } break;
324+
case("SSN4"): { scorer.setSSNLast4Match(true); } break;
315325
}
316326

317327
}
318328

329+
330+
// Additional SSN4 match case:
331+
// Patient may be matched based on the custom SSN4 search parameter but patient resource has a full SSN (http://terminology.hl7.org/CodeSystem/v2-0203|SB type)
332+
// But, the search was only for last four digits, so we cannot weigh it as a full SSN match
333+
else if (
334+
id.hasIdentifierSystem() && id.hasIdentifierTypeCode()
335+
&& id.getIdentifierTypeSystem().equals(IDENTITY_IDENTIFIER_SSN4_SYSTEM) && id.getIdentifierTypeCode().equals(IDENTITY_IDENTIFIER_SSN4_CODE)
336+
&& x.getSystem().equals(IDENTIFIER_SSN_SYSTEM)
337+
&& x.getValue().substring(Math.max(x.getValue().length() - 4, 0)).equals(id.getIdentifierValue())
338+
) {
339+
scorer.setSSNLast4Match(true);
340+
}
319341
});
320342
}
321343
}
@@ -414,7 +436,7 @@ else if(com.hasSystem() && com.getSystem().toCode().equals(ContactPoint.ContactP
414436
//create bundle search component element
415437
Bundle.BundleEntrySearchComponent searchComp = new Bundle.BundleEntrySearchComponent();
416438
searchComp.setMode(Bundle.SearchEntryMode.MATCH);
417-
searchComp.setScore(scorer.scoreMatch());
439+
// searchComp.setScore(scorer.scoreMatch());
418440
// for(String msg : scorer.getMatchMessages()) {
419441
// System.out.println(msg);
420442
// }
@@ -877,13 +899,25 @@ private Bundle getPatientMatch(Patient refPatient, RequestDetails theRequestDeta
877899
continue;
878900
}
879901

902+
// custom search parameter for SSN4 matching
903+
// if (coding.getSystem().equals(IDENTITY_IDENTIFIER_SYSTEM) && coding.getCode().equals(IDENTITY_IDENTIFIER_SSN4_CODE)) {
904+
if (
905+
identifier.getType().getCoding().stream().anyMatch(
906+
coding -> coding.getSystem().equals(IDENTITY_IDENTIFIER_SSN4_SYSTEM) && coding.getCode().equals(IDENTITY_IDENTIFIER_SSN4_CODE)
907+
)
908+
) {
909+
searchMap.add(IDENTITY_IDENTIFIER_SSN4_PARAMETER, new TokenParam().setValue(identifier.getValue()));
910+
continue;
911+
}
912+
913+
880914
// search params for identifier type if present (":of-type" modifier)
881915
if (identifier.hasType() && identifier.getType().hasCoding()) {
882916
for (Coding coding : identifier.getType().getCoding()) {
883917
if (coding.hasSystem() && coding.hasCode()) {
884918
identifierParam.addAnd(new TokenParam()
885919
.setSystem(coding.getSystem())
886-
.setValue(coding.getCode() + "|" + identifier.getValue())
920+
.setValue(coding.getCode() + "|*" + identifier.getValue())
887921
.setModifier(TokenParamModifier.OF_TYPE));
888922
}
889923
}

src/main/java/ca/uhn/fhir/jpa/starter/operations/models/IdentifierQueryParams.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ public IdentifierQueryParams(String system, String value, String typeCode, Strin
1919

2020
public String getIdentifierSystem() { return _identifierSystem; }
2121
public void setIdentifierSystem(String system) { this._identifierSystem = system; }
22+
public boolean hasIdentifierSystem() { return _identifierSystem != null && !_identifierSystem.isEmpty(); }
2223

2324
public String getIdentifierValue() { return _identifierValue; }
2425
public void setIdentifierValue(String value) { this._identifierValue = value; }
26+
public boolean hasIdentifierValue() { return _identifierValue != null && !_identifierValue.isEmpty(); }
2527

2628
public String getIdentifierTypeCode() { return _identifierTypeCode; }
2729
public void setIdentifierTypeCode(String code) { this._identifierTypeCode = code; }
30+
public boolean hasIdentifierTypeCode() { return _identifierTypeCode != null && !_identifierTypeCode.isEmpty(); }
2831

2932
public String getIdentifierTypeSystem() { return _identifierTypeSystem; }
3033
public void setIdentifierTypeSystem(String system) { this._identifierTypeSystem = system; }
34+
public boolean hasIdentifierTypeSystem() { return _identifierTypeSystem != null && !_identifierTypeSystem.isEmpty(); }
3135

3236
}

src/main/resources/application.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ spring:
9898
hapi:
9999
fhir:
100100
initialdata:
101+
- artifacts
101102
- ig-examples
102103
- sample-data
103104
match-validation-header: X-Match-Validation
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"resourceType": "SearchParameter",
3+
"id": "patient-ssn4",
4+
"url": "http://hl7.org/fhir/us/identity-matching/SearchParameter/patient-ssn4",
5+
"name": "PatientSSN4SearchParameter",
6+
"status": "active",
7+
"experimental": true,
8+
"description": "Search by last four digits of a patient's social security number",
9+
"code": "ssn4",
10+
"base": ["Patient"],
11+
"type": "token",
12+
"expression": "Patient.identifier.where(system='http://hl7.org/fhir/sid/us-ssn' and value.length()>3).select(value.substring(length()-4))"
13+
}

src/main/resources/sample-data/Patient-PatientFullDemographics.json

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
"resourceType": "Patient",
33
"id": "PatientFullDemographics",
44
"meta": {
5-
"versionId": "1",
6-
"lastUpdated": "2024-04-11T14:41:08.548+00:00",
75
"profile": [
86
"http://hl7.org/fhir/us/identity-matching/StructureDefinition/IDI-Patient"
97
]
@@ -74,7 +72,7 @@
7472
]
7573
},
7674
"system": "http://hl7.org/fhir/sid/us-ssn",
77-
"value": "390-16-5329"
75+
"value": "390165329"
7876
},
7977
{
8078
"type": {
@@ -89,14 +87,60 @@
8987
"value": "3902-16532901"
9088
}
9189
],
90+
"active": true,
9291
"name": [
9392
{
9493
"family": "Patient",
9594
"given": [
9695
"Max",
9796
"Demographics"
98-
]
97+
],
98+
"use": "usual"
99+
}
100+
],
101+
"telecom": [
102+
{
103+
"system": "phone",
104+
"value": "726-555-2900",
105+
"use": "mobile"
106+
},
107+
{
108+
"system": "email",
109+
"value": "max@patient.me",
110+
"use": "home"
111+
}
112+
],
113+
"gender": "male",
114+
"birthDate": "1992-05-17",
115+
"address": [
116+
{
117+
"type": "physical",
118+
"line": [
119+
"418 Teapot Lane",
120+
"Unit 2"
121+
],
122+
"city": "Raleigh",
123+
"state": "NC",
124+
"postalCode": "27513-1234"
99125
}
100126
],
101-
"birthDate": "1992-05-17"
127+
"maritalStatus": {
128+
"coding": [
129+
{
130+
"system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor",
131+
"code": "UNK"
132+
}
133+
]
134+
},
135+
"contact": [
136+
{
137+
"telecom": [
138+
{
139+
"system": "fax",
140+
"value": "726-555-1094",
141+
"use": "home"
142+
}
143+
]
144+
}
145+
]
102146
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"resourceType": "Patient",
3+
"id": "PatientSSN4",
4+
"meta": {
5+
"profile": [
6+
"http://hl7.org/fhir/us/identity-matching/StructureDefinition/IDI-Patient"
7+
]
8+
},
9+
"language": "en-US",
10+
"extension": [
11+
{
12+
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex",
13+
"valueCode": "M"
14+
}
15+
],
16+
"identifier": [
17+
{
18+
"type": {
19+
"coding": [
20+
{
21+
"system": "http://hl7.org/fhir/us/identity-matching/CodeSystem/Identity-Identifier-cs",
22+
"code": "SSN4"
23+
}
24+
]
25+
},
26+
"system": "http://hl7.org/fhir/sid/us-ssn",
27+
"value": "4321"
28+
}
29+
],
30+
"active": true,
31+
"name": [
32+
{
33+
"family": "Privacy",
34+
"given": [
35+
"Patrick"
36+
],
37+
"use": "usual"
38+
}
39+
],
40+
"gender": "male",
41+
"birthDate": "1992-01-04"
42+
}

0 commit comments

Comments
 (0)