Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

person to FHIR patient #4842

Merged
merged 56 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a4e5c3f
add fhir structure dependency
BobanL Nov 23, 2022
85a378e
WIP patient
BobanL Nov 23, 2022
0814e4a
write lock for fhir
BobanL Nov 23, 2022
411520e
add to fhir method
BobanL Nov 23, 2022
bcc32a8
add to fhir method with mobile support
BobanL Nov 23, 2022
521bd73
add landline support
BobanL Nov 23, 2022
d1f528a
add invalid test and reformat
BobanL Nov 23, 2022
344a4ba
rename assert function and make it static
BobanL Nov 23, 2022
6148456
add toFhir method
BobanL Nov 23, 2022
c164331
add a null test to street toFhir
BobanL Nov 23, 2022
e03eaad
use assertThat instead of assertEquals
BobanL Nov 23, 2022
f237181
add basic test for person toFhir
BobanL Nov 23, 2022
ced62f9
add null check to toFhir method
BobanL Nov 23, 2022
b506371
add additional gender checks
BobanL Nov 23, 2022
6a1833e
remove comments
BobanL Dec 5, 2022
a70a6ba
delete unused method
BobanL Dec 5, 2022
eefd82c
refactor to chain methods
BobanL Dec 5, 2022
e8f1c86
rename method toFhir method to match other places
BobanL Dec 5, 2022
b3458dc
refactor test to check identifier alone.
BobanL Dec 5, 2022
943b447
refactor toFhir to own methods.
BobanL Dec 5, 2022
8bb602b
add race extension
BobanL Dec 9, 2022
1cf43d2
add additional race extension tests
BobanL Dec 9, 2022
e0b5134
refactor tests
BobanL Dec 9, 2022
4b7103f
fix word
BobanL Dec 12, 2022
35e3856
fix
BobanL Dec 12, 2022
0fbce0c
refactor to replace filter with get extension by url
BobanL Dec 12, 2022
16c1acb
add ethnicity extension
BobanL Dec 12, 2022
cf23833
add tribal affiliation extension
BobanL Dec 12, 2022
00fa282
do a full comparison of a patient object
BobanL Dec 12, 2022
d699105
add json ignore on all person methods
BobanL Dec 12, 2022
29574d5
refactor static util methods and constants into own class
BobanL Dec 12, 2022
7af6e3c
make unknowns use the null code system
BobanL Dec 12, 2022
9a29ff5
Merge branch 'main' into boban/eventToFHIR
BobanL Dec 12, 2022
5fec43d
set nullCodeSystem as transiet
BobanL Dec 12, 2022
fd5e3f0
fix human name for null values
BobanL Dec 13, 2022
781ab6d
update build wiremock to make individual runs of TestResultUploadServ…
BobanL Dec 13, 2022
fecd87b
fix birthdate check on person test
BobanL Dec 13, 2022
f5c88c9
fix code smells
BobanL Dec 13, 2022
6650403
add additional test coverage
BobanL Dec 13, 2022
78c0d7d
move patient into resource folder
BobanL Dec 14, 2022
a17df02
rename fhir utils to mapping constants
BobanL Dec 14, 2022
5be4528
refactor name & identifier to fhir
BobanL Dec 14, 2022
17cf882
refactor PersonName to HumanName
BobanL Dec 14, 2022
559df68
refactor number to fhir
BobanL Dec 14, 2022
b3eea27
refactor contact point
BobanL Dec 14, 2022
8732055
refactor administrative gender
BobanL Dec 14, 2022
e613d68
refactor date
BobanL Dec 14, 2022
64c4fb2
add convertToAddress
BobanL Dec 16, 2022
d80be10
move race extension to fhir converter
BobanL Dec 16, 2022
2de7e8c
refactor to remove all remaining toFhir methods
BobanL Dec 16, 2022
db51a9c
move ethnicity to fhir converter
BobanL Dec 16, 2022
b847bb4
refactor gender tests
BobanL Dec 16, 2022
3df53d3
convert tribal affiliation to convert
BobanL Dec 16, 2022
517903a
move constants to fhir constants
BobanL Dec 16, 2022
756a963
add a list for phone number and email converters
BobanL Dec 19, 2022
8619417
fix code smells
BobanL Dec 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,15 @@ dependencies {
testAnnotationProcessor 'org.projectlombok:lombok'

// WireMock (only used for testing)
testImplementation "com.github.tomakehurst:wiremock-jre8:2.31.0"
testImplementation "com.github.tomakehurst:wiremock-jre8:2.35.0"

runtimeOnly "io.zipkin.brave:brave-instrumentation-http:5.13.2"
runtimeOnly "io.zipkin.brave:brave:5.13.2"
runtimeOnly "io.zipkin.reporter2:zipkin-reporter-brave:2.16.3"
runtimeOnly "io.zipkin.reporter2:zipkin-reporter:2.16.1"
runtimeOnly "io.zipkin.zipkin2:zipkin:2.23.0"

implementation 'ca.uhn.hapi.fhir:hapi-fhir-structures-r4:6.2.1'
}

dependencyManagement {
Expand Down
16 changes: 13 additions & 3 deletions backend/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
antlr:antlr:2.7.7=compileClasspath,runtimeClasspath
ca.uhn.hapi.fhir:hapi-fhir-base:6.2.1=compileClasspath,runtimeClasspath
ca.uhn.hapi.fhir:hapi-fhir-structures-r4:6.2.1=compileClasspath,runtimeClasspath
ca.uhn.hapi.fhir:org.hl7.fhir.r4:5.6.68=compileClasspath,runtimeClasspath
ca.uhn.hapi.fhir:org.hl7.fhir.utilities:5.6.68=compileClasspath,runtimeClasspath
ch.qos.logback:logback-classic:1.2.11=compileClasspath,runtimeClasspath
ch.qos.logback:logback-core:1.2.11=compileClasspath,runtimeClasspath
com.azure:azure-core-http-netty:1.11.9=compileClasspath,runtimeClasspath
Expand All @@ -22,6 +26,11 @@ com.fasterxml.woodstox:woodstox-core:6.4.0=compileClasspath,runtimeClasspath
com.fasterxml:classmate:1.5.1=compileClasspath,runtimeClasspath
com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,runtimeClasspath
com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath
com.google.errorprone:error_prone_annotations:2.7.1=compileClasspath,runtimeClasspath
com.google.guava:failureaccess:1.0.1=compileClasspath,runtimeClasspath
com.google.guava:guava:31.0.1-jre=compileClasspath,runtimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=compileClasspath,runtimeClasspath
com.google.j2objc:j2objc-annotations:1.3=compileClasspath,runtimeClasspath
com.googlecode.libphonenumber:libphonenumber:8.12.57=compileClasspath,runtimeClasspath
com.graphql-java:graphql-java-extended-scalars:18.1=compileClasspath,runtimeClasspath
com.graphql-java:graphql-java-extended-validation:18.1-hibernate-validator-6.2.0.Final=compileClasspath,runtimeClasspath
Expand Down Expand Up @@ -56,7 +65,7 @@ com.vladmihalcea:hibernate-types-55:2.19.2=compileClasspath,runtimeClasspath
com.zaxxer:HikariCP:4.0.3=compileClasspath,runtimeClasspath
commons-codec:commons-codec:1.15=compileClasspath,runtimeClasspath
commons-fileupload:commons-fileupload:1.4=compileClasspath,runtimeClasspath
commons-io:commons-io:2.7=compileClasspath,runtimeClasspath
commons-io:commons-io:2.11.0=compileClasspath,runtimeClasspath
io.github.openfeign.form:feign-form-spring:3.8.0=compileClasspath,runtimeClasspath
io.github.openfeign.form:feign-form:3.8.0=compileClasspath,runtimeClasspath
io.github.openfeign:feign-core:11.8=compileClasspath,runtimeClasspath
Expand Down Expand Up @@ -108,6 +117,7 @@ net.minidev:accessors-smart:2.4.8=compileClasspath,runtimeClasspath
net.minidev:json-smart:2.4.8=compileClasspath,runtimeClasspath
org.antlr:antlr4-runtime:4.9.3=runtimeClasspath
org.apache.commons:commons-lang3:3.12.0=compileClasspath,runtimeClasspath
org.apache.commons:commons-text:1.10.0=compileClasspath,runtimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=compileClasspath,runtimeClasspath
org.apache.httpcomponents:httpcore:4.4.15=compileClasspath,runtimeClasspath
org.apache.httpcomponents:httpmime:4.5.13=runtimeClasspath
Expand All @@ -123,7 +133,7 @@ org.bouncycastle:bcpkix-jdk15on:1.70=runtimeClasspath
org.bouncycastle:bcprov-jdk15on:1.68=compileClasspath
org.bouncycastle:bcprov-jdk15on:1.70=runtimeClasspath
org.bouncycastle:bcutil-jdk15on:1.70=runtimeClasspath
org.checkerframework:checker-qual:3.5.0=runtimeClasspath
org.checkerframework:checker-qual:3.12.0=compileClasspath,runtimeClasspath
org.codehaus.woodstox:stax2-api:4.2.1=compileClasspath,runtimeClasspath
org.glassfish.jaxb:jaxb-runtime:2.3.7=compileClasspath,runtimeClasspath
org.glassfish.jaxb:txw2:2.3.7=compileClasspath,runtimeClasspath
Expand All @@ -143,7 +153,7 @@ org.ow2.asm:asm:9.1=compileClasspath,runtimeClasspath
org.postgresql:postgresql:42.4.1=runtimeClasspath
org.projectlombok:lombok:1.18.24=compileClasspath
org.reactivestreams:reactive-streams:1.0.4=compileClasspath,runtimeClasspath
org.slf4j:jcl-over-slf4j:1.7.36=runtimeClasspath
org.slf4j:jcl-over-slf4j:1.7.36=compileClasspath,runtimeClasspath
org.slf4j:jul-to-slf4j:1.7.36=compileClasspath,runtimeClasspath
org.slf4j:slf4j-api:1.7.36=compileClasspath,runtimeClasspath
org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.6=compileClasspath,runtimeClasspath
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gov.cdc.usds.simplereport.api;

public class MappingConstants {
private MappingConstants() {
throw new IllegalStateException("Utility class");
}

public static final String UNK_CODE = "UNK";
public static final String UNKNOWN_STRING = "unknown";
public static final String U_CODE = "U";
public static final String ASKED_BUT_UNKNOWN_CODE = "ASKU";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gov.cdc.usds.simplereport.api.converter;

public class FhirConstants {
private FhirConstants() {
throw new IllegalStateException("Utility class");
}

public static final String NULL_CODE_SYSTEM =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend adding _URL to the name to keep it consistent with the others

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the reason why these don't have _URL at the end is because the are used as the system and the _URL are added to the URL, but if that's confusing I can totally change it to describe what the contents of that variable is like recommended.

"http://terminology.hl7.org/CodeSystem/v3-NullFlavor";
public static final String RACE_EXTENSION_URL =
"http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd";
public static final String RACE_CODING_SYSTEM = "http://terminology.hl7.org/CodeSystem/v3-Race";
public static final String ETHNICITY_EXTENSION_URL =
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity";
public static final String ETHNICITY_CODE_SYSTEM = "urn:oid:2.16.840.1.113883.6.238";
public static final String TRIBAL_AFFILIATION_EXTENSION_URL =
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-tribal-affiliation";
public static final String TRIBAL_AFFILIATION_STRING = "tribalAffiliation";
public static final String TRIBAL_AFFILIATION_CODE_SYSTEM =
"http://terminology.hl7.org/CodeSystem/v3-TribalEntityUS";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
package gov.cdc.usds.simplereport.api.converter;

import static gov.cdc.usds.simplereport.api.converter.FhirConstants.ETHNICITY_CODE_SYSTEM;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.ETHNICITY_EXTENSION_URL;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.NULL_CODE_SYSTEM;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.RACE_CODING_SYSTEM;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.RACE_EXTENSION_URL;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.TRIBAL_AFFILIATION_CODE_SYSTEM;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.TRIBAL_AFFILIATION_EXTENSION_URL;
import static gov.cdc.usds.simplereport.api.converter.FhirConstants.TRIBAL_AFFILIATION_STRING;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import gov.cdc.usds.simplereport.api.MappingConstants;
import gov.cdc.usds.simplereport.db.model.PersonUtils;
import gov.cdc.usds.simplereport.db.model.PhoneNumber;
import gov.cdc.usds.simplereport.db.model.auxiliary.PersonName;
import gov.cdc.usds.simplereport.db.model.auxiliary.PhoneType;
import gov.cdc.usds.simplereport.db.model.auxiliary.StreetAddress;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ContactPoint;
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.r4.model.ContactPoint.ContactPointUse;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Identifier.IdentifierUse;
import org.hl7.fhir.r4.model.StringType;

@Slf4j
public class FhirConverter {
private FhirConverter() {
throw new IllegalStateException("Utility class");
}

public static Identifier convertToIdentifier(UUID id) {
if (id != null) {
return convertToIdentifier(id.toString());
}
return null;
}

public static Identifier convertToIdentifier(String id) {
if (id != null) {
return new Identifier().setValue(id).setUse(IdentifierUse.USUAL);
}
return null;
}

public static HumanName convertToHumanName(PersonName personName) {
if (personName != null) {
return convertToHumanName(
personName.getFirstName(),
personName.getMiddleName(),
personName.getLastName(),
personName.getSuffix());
}
return null;
}

public static HumanName convertToHumanName(
String first, String middle, String last, String suffix) {
var humanName = new HumanName();
if (StringUtils.isNotBlank(first)) {
humanName.addGiven(first);
}
if (StringUtils.isNotBlank(middle)) {
humanName.addGiven(middle);
}
if (StringUtils.isNotBlank(last)) {
humanName.setFamily(last);
}
if (StringUtils.isNotBlank(suffix)) {
humanName.addSuffix(suffix);
}
return humanName;
}

public static List<ContactPoint> phoneNumberToContactPoint(List<PhoneNumber> phoneNumber) {
if (phoneNumber != null && !phoneNumber.isEmpty()) {
return phoneNumber.stream()
.map(FhirConverter::phoneNumberToContactPoint)
.collect(Collectors.toList());
}
return Collections.emptyList();
}

public static ContactPoint phoneNumberToContactPoint(PhoneNumber phoneNumber) {
if (phoneNumber != null) {
var contactPointUse = ContactPointUse.HOME;
if (PhoneType.MOBILE.equals(phoneNumber.getType())) {
contactPointUse = ContactPointUse.MOBILE;
}

return phoneNumberToContactPoint(contactPointUse, phoneNumber.getNumber());
}
return null;
}

public static ContactPoint phoneNumberToContactPoint(
ContactPointUse contactPointUse, String number) {
// converting string to phone format as recommended by the fhir format.
// https://www.hl7.org/fhir/datatypes.html#ContactPoint
try {
var phoneUtil = PhoneNumberUtil.getInstance();
var parsedNumber = phoneUtil.parse(number, "US");
var formattedWithDash = phoneUtil.format(parsedNumber, PhoneNumberFormat.NATIONAL);

number = formattedWithDash.replace("-", " ");
} catch (NumberParseException exception) {
log.debug("Error parsing number: " + exception);
}

return convertToContactPoint(contactPointUse, ContactPointSystem.PHONE, number);
}

public static List<ContactPoint> emailToContactPoint(List<String> emails) {
if (emails != null) {
return emails.stream().map(FhirConverter::emailToContactPoint).collect(Collectors.toList());
}
return Collections.emptyList();
}

public static ContactPoint emailToContactPoint(String email) {
if (email != null) {
return convertToContactPoint(null, ContactPointSystem.EMAIL, email);
}
return null;
}

public static ContactPoint convertToContactPoint(
ContactPointUse use, ContactPointSystem system, String value) {
if (value != null) {
return new ContactPoint().setUse(use).setSystem(system).setValue(value);
}
return null;
}

public static AdministrativeGender convertToAdministrativeGender(String gender) {
if ("male".equalsIgnoreCase(gender) || "m".equalsIgnoreCase(gender)) {
return AdministrativeGender.MALE;
} else if ("female".equalsIgnoreCase(gender) || "f".equalsIgnoreCase(gender)) {
return AdministrativeGender.FEMALE;
} else {
return AdministrativeGender.UNKNOWN;
}
}

public static Date convertToDate(LocalDate date) {
if (date != null) {
return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
}
return null;
}

public static Address convertToAddress(StreetAddress address) {
if (address != null) {
return convertToAddress(
address.getStreet(),
address.getCity(),
address.getCounty(),
address.getState(),
address.getPostalCode());
}
return null;
}

public static Address convertToAddress(
List<String> street, String city, String county, String state, String postalCode) {
var address =
new Address().setCity(city).setDistrict(county).setState(state).setPostalCode(postalCode);
if (street != null) {
street.forEach(address::addLine);
}
return address;
}

public static Extension convertToRaceExtension(String race) {
if (StringUtils.isNotBlank(race)) {
var ext = new Extension();
ext.setUrl(RACE_EXTENSION_URL);
var codeable = new CodeableConcept();
var coding = codeable.addCoding();
if (PersonUtils.raceMap.containsKey(race)) {
if (MappingConstants.UNKNOWN_STRING.equalsIgnoreCase(race)
|| "refused".equalsIgnoreCase(race)) {
coding.setSystem(NULL_CODE_SYSTEM);
} else {
coding.setSystem(RACE_CODING_SYSTEM);
}
coding.setCode(PersonUtils.raceMap.get(race));
codeable.setText(race);
} else {
coding.setSystem(NULL_CODE_SYSTEM);
coding.setCode(MappingConstants.UNK_CODE);
codeable.setText(MappingConstants.UNKNOWN_STRING);
}
ext.setValue(codeable);
return ext;
}
return null;
}

public static Extension convertToEthnicityExtension(String ethnicity) {
if (StringUtils.isNotBlank(ethnicity)) {
var ext = new Extension();
ext.setUrl(ETHNICITY_EXTENSION_URL);
var ombExtension = ext.addExtension();
ombExtension.setUrl("ombCategory");
var ombCoding = new Coding();
if (PersonUtils.ETHNICITY_MAP.containsKey(ethnicity)) {
if ("refused".equalsIgnoreCase(ethnicity)) {
ombCoding.setSystem(NULL_CODE_SYSTEM);
} else {
ombCoding.setSystem(ETHNICITY_CODE_SYSTEM);
}
ombCoding.setCode(PersonUtils.ETHNICITY_MAP.get(ethnicity).get(0));
ombCoding.setDisplay(PersonUtils.ETHNICITY_MAP.get(ethnicity).get(1));

var text = ext.addExtension();
text.setUrl("text");
text.setValue(new StringType(PersonUtils.ETHNICITY_MAP.get(ethnicity).get(1)));
} else {
ombCoding.setSystem(NULL_CODE_SYSTEM);
ombCoding.setCode(MappingConstants.UNK_CODE);
ombCoding.setDisplay(MappingConstants.UNKNOWN_STRING);

var text = ext.addExtension();
text.setUrl("text");
text.setValue(new StringType(MappingConstants.UNKNOWN_STRING));
}
ombExtension.setValue(ombCoding);
return ext;
}
return null;
}

public static Extension convertToTribalAffiliationExtension(List<String> tribalAffiliations) {
if (tribalAffiliations != null && !tribalAffiliations.isEmpty()) {
return convertToTribalAffiliationExtension(tribalAffiliations.get(0));
}
return null;
}

public static Extension convertToTribalAffiliationExtension(String tribalAffiliation) {
if (StringUtils.isNotBlank(tribalAffiliation)
&& PersonUtils.tribalMap().containsKey(tribalAffiliation)) {
var ext = new Extension();
ext.setUrl(TRIBAL_AFFILIATION_EXTENSION_URL);
var tribeExtension = ext.addExtension();
tribeExtension.setUrl(TRIBAL_AFFILIATION_STRING);
var tribeCodeableConcept = new CodeableConcept();
var tribeCoding = tribeCodeableConcept.addCoding();
tribeCoding.setSystem(TRIBAL_AFFILIATION_CODE_SYSTEM);
tribeCoding.setCode(tribalAffiliation);
tribeCoding.setDisplay(PersonUtils.tribalMap().get(tribalAffiliation));
tribeCodeableConcept.setText(PersonUtils.tribalMap().get(tribalAffiliation));
tribeExtension.setValue(tribeCodeableConcept);
return ext;
}
return null;
}
}
Loading