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

Map syphilis history to FHIR #8151

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import static gov.cdc.usds.simplereport.api.model.TestEventExport.DEFAULT_LOCATION_NAME;
import static gov.cdc.usds.simplereport.api.model.TestEventExport.FALLBACK_DEFAULT_TEST_MINUTES;
import static gov.cdc.usds.simplereport.api.model.TestEventExport.UNKNOWN_ADDRESS_INDICATOR;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.NO_SYPHILIS_HISTORY_SNOMED;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.YES_SYPHILIS_HISTORY_SNOMED;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.genderIdentityDisplaySet;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.genderIdentitySnomedSet;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.getResidenceTypeMap;
Expand Down Expand Up @@ -865,6 +867,20 @@ public Observation convertToAOEPregnancyObservation(String pregnancyStatusSnomed
pregnancyStatusValueCode);
}

public Observation convertToAOESyphilisHistoryObservation(String syphilisHistory) {
CodeableConcept observationCode =
createSNOMEDConcept(
YES_SYPHILIS_HISTORY_SNOMED, "History of syphilis", "History of syphilis");
Boolean hasHistory = null;
if (syphilisHistory.equalsIgnoreCase(YES_SYPHILIS_HISTORY_SNOMED)) {
hasHistory = true;
} else if (syphilisHistory.equalsIgnoreCase(NO_SYPHILIS_HISTORY_SNOMED)) {
hasHistory = false;
}
return createAOEObservation(
uuidGenerator.randomUUID().toString(), observationCode, createYesNoUnkConcept(hasHistory));
}

public Observation convertToAOEYesNoUnkObservation(
Boolean isObserved, String observationLoinc, String observationDisplayText) {
CodeableConcept observationCode =
Expand Down Expand Up @@ -991,6 +1007,10 @@ public Set<Observation> convertToAOEObservations(
observations.addAll(convertToAOEGenderOfSexualPartnersObservation(sexualPartners));
}

if (surveyData.getSyphilisHistory() != null) {
observations.add(convertToAOESyphilisHistoryObservation(surveyData.getSyphilisHistory()));
}

return observations;
}

Expand Down Expand Up @@ -1050,7 +1070,17 @@ private CodeableConcept createYesNoUnkConcept(Boolean val) {
}

private CodeableConcept createSNOMEDConcept(String resultCode, String resultDisplay) {
return createSNOMEDConcept(resultCode, resultDisplay, null);
}

private CodeableConcept createSNOMEDConcept(
String resultCode, String resultDisplay, String resultText) {
CodeableConcept concept = new CodeableConcept();

if (StringUtils.isNotBlank(resultText)) {
concept.setText(resultText);
}

Coding coding = concept.addCoding();
coding.setSystem(SNOMED_CODE_SYSTEM);
coding.setCode(resultCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ public List<FeedbackMessage> validateIndividualValues() {
testResult, DiseaseService.HIV_NAME, List.of(gendersOfSexualPartners, pregnant)));
}

errors.addAll(validateYesNoAnswer(syphilisHistory));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ added validation here for the syphilis_history column

if (isSyphilisResult()) {
errors.addAll(
validateRequiredFieldsForPositiveResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,4 +855,13 @@ public static Map<String, String> getGenderIdentityAbbreviationMap() {

return genderMap;
}

public static final Map<String, String> syphilisHistorySnomedMap =
Map.of(
"YES".toLowerCase(), YES_SYPHILIS_HISTORY_SNOMED,
"Y".toLowerCase(), YES_SYPHILIS_HISTORY_SNOMED,
"NO".toLowerCase(), NO_SYPHILIS_HISTORY_SNOMED,
"N".toLowerCase(), NO_SYPHILIS_HISTORY_SNOMED,
"UNK".toLowerCase(), UNKNOWN_SYPHILIS_HISTORY_SNOMED,
"U".toLowerCase(), UNKNOWN_SYPHILIS_HISTORY_SNOMED);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static gov.cdc.usds.simplereport.api.model.filerow.TestResultRow.diseaseSpecificLoincMap;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.getGenderIdentityAbbreviationMap;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.getResidenceTypeMap;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.syphilisHistorySnomedMap;
import static gov.cdc.usds.simplereport.utils.DateTimeUtils.DATE_TIME_FORMATTER;
import static gov.cdc.usds.simplereport.utils.DateTimeUtils.convertToZonedDateTime;
import static gov.cdc.usds.simplereport.utils.ResultUtils.mapTestResultStatusToSRValue;
Expand Down Expand Up @@ -475,6 +476,13 @@ private Bundle convertRowToFhirBundle(TestResultRow row, UUID orgId) {
AOE_EMPLOYED_IN_HEALTHCARE_DISPLAY));
}

String syphilisHistory = row.getSyphilisHistory().getValue();
if (StringUtils.isNotBlank(syphilisHistory)) {
String syphilisHistorySnomed = getSyphilisHistorySnomed(syphilisHistory);
aoeObservations.add(
fhirConverter.convertToAOESyphilisHistoryObservation(syphilisHistorySnomed));
}

String hospitalizedValue = row.getHospitalized().getValue();
if (StringUtils.isNotBlank(hospitalizedValue)) {
Boolean hospitalized = yesNoToBooleanMap.get(hospitalizedValue.toLowerCase());
Expand Down Expand Up @@ -601,6 +609,13 @@ private String getPregnancyStatusSnomed(String input) {
return null;
}

private String getSyphilisHistorySnomed(String input) {
if (input != null && input.matches(ALPHABET_REGEX)) {
return syphilisHistorySnomedMap.get(input.toLowerCase());
}
return null;
}

private String getResidenceTypeSnomed(String input) {
if (input != null && input.matches(ALPHABET_REGEX)) {
return getResidenceTypeMap().get(input.toLowerCase());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import static gov.cdc.usds.simplereport.api.model.TestEventExport.DEFAULT_LOCATION_CODE;
import static gov.cdc.usds.simplereport.api.model.TestEventExport.DEFAULT_LOCATION_NAME;
import static gov.cdc.usds.simplereport.api.model.TestEventExport.UNKNOWN_ADDRESS_INDICATOR;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.NO_SYPHILIS_HISTORY_SNOMED;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.PREGNANT_UNKNOWN_SNOMED;
import static gov.cdc.usds.simplereport.db.model.PersonUtils.YES_SYPHILIS_HISTORY_SNOMED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.from;
import static org.junit.jupiter.params.provider.Arguments.arguments;
Expand Down Expand Up @@ -1238,22 +1240,53 @@ void convertToAoeObservation_genderOfSexualPartners_matchesJson() throws IOExcep
JSONAssert.assertEquals(expectedSerialized, actualSerialized, true);
}

@Test
void convertToAoeObservation_syphilisHistory_matchesJson() throws IOException {
AskOnEntrySurvey answers =
AskOnEntrySurvey.builder()
.pregnancy(null)
.syphilisHistory(YES_SYPHILIS_HISTORY_SNOMED)
.symptoms(null)
.genderOfSexualPartners(null)
.symptomOnsetDate(null)
.noSymptoms(false)
.build();

String testId = "fakeId";

Set<Observation> actual =
fhirConverter.convertToAOEObservations(
testId, answers, new Person("first", "last", "middle", "suffix", null));

String actualSerialized =
actual.stream().map(parser::encodeResourceToString).collect(Collectors.toSet()).toString();
String expectedSerialized =
IOUtils.toString(
Objects.requireNonNull(
getClass()
.getClassLoader()
.getResourceAsStream("fhir/observationSyphilisHistory.json")),
StandardCharsets.UTF_8);
JSONAssert.assertEquals(expectedSerialized, actualSerialized, true);
}

@Test
void convertToAoeObservation_allAOE_matchesJson() throws IOException {
List<String> sexualPartners = List.of("male", "female");
AskOnEntrySurvey answers =
AskOnEntrySurvey.builder()
.pregnancy(PREGNANT_UNKNOWN_SNOMED)
.syphilisHistory(null)
.syphilisHistory(NO_SYPHILIS_HISTORY_SNOMED)
.symptoms(Map.of("fake", true))
.symptomOnsetDate(LocalDate.of(2023, 3, 4))
.genderOfSexualPartners(null)
.genderOfSexualPartners(sexualPartners)
.noSymptoms(false)
.build();

String testId = "fakeId";

var birthDate = LocalDate.of(2022, 12, 13);
var person =
LocalDate birthDate = LocalDate.of(2022, 12, 13);
Person person =
new Person(
null,
null,
Expand All @@ -1277,11 +1310,11 @@ void convertToAoeObservation_allAOE_matchesJson() throws IOException {
"English",
null);

var actual = fhirConverter.convertToAOEObservations(testId, answers, person);
Set<Observation> actual = fhirConverter.convertToAOEObservations(testId, answers, person);

String actualSerialized =
actual.stream().map(parser::encodeResourceToString).collect(Collectors.toSet()).toString();
var expectedSerialized =
String expectedSerialized =
IOUtils.toString(
Objects.requireNonNull(
getClass().getClassLoader().getResourceAsStream("fhir/observationAllAoe.json")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,22 @@ public void beforeEach() {
when(resultsUploaderCachingService.getSpecimenTypeNameToSNOMEDMap())
.thenReturn(
Map.of(
"Nasal swab".toLowerCase(), "445297001",
"Anterior nares swab".toLowerCase(), "697989009"));
"Nasal swab".toLowerCase(),
"445297001",
"Anterior nares swab".toLowerCase(),
"697989009",
"Venous blood specimen".toLowerCase(),
"122555007"));

when(resultsUploaderCachingService.getSNOMEDToSpecimenTypeNameMap())
.thenReturn(
Map.of(
"445297001", "Nasal swab",
"697989009", "Anterior nares swab"));
"445297001",
"Nasal swab",
"697989009",
"Anterior nares swab",
"122555007",
"Venous blood specimen"));

when(resultsUploaderCachingService.getZoneIdByAddress(any()))
.thenReturn(ZoneId.of("US/Central"));
Expand Down Expand Up @@ -650,4 +658,29 @@ void convertExistingCsv_validHivPositive_withAOEdataColumns() {
assertThat(gendersOfSexualPartnersObservations).hasSize(5);
assertThat(codeableConceptValues).containsExactlyInAnyOrderElementsOf(expectedGenders);
}

@Test
void convertExistingCsv_validSyphilisPositive_withAOEdataColumns() {
InputStream input = loadCsv("testResultUpload/test-results-upload-valid-syphilis-only.csv");

FHIRBundleRecord bundleRecord = sut.convertToFhirBundles(input, UUID.randomUUID());
List<String> serializedBundles = bundleRecord.serializedBundle();
String first = serializedBundles.get(0);
Bundle deserializedBundle = (Bundle) parser.parseResource(first);

List<Observation> syphilisHistoryObservations =
deserializedBundle.getEntry().stream()
.filter(entry -> entry.getFullUrl().contains("Observation/"))
.map(x -> (Observation) x.getResource())
.filter(x -> Objects.equals(x.getCode().getText(), "History of syphilis"))
.collect(Collectors.toList());

List<String> codeableConceptValues =
syphilisHistoryObservations.stream()
.map(x -> x.getValueCodeableConcept().getCoding().get(0).getDisplay())
.collect(Collectors.toList());

assertThat(syphilisHistoryObservations).hasSize(1);
assertThat(codeableConceptValues).isEqualTo(List.of("unknown"));
}
}
114 changes: 114 additions & 0 deletions backend/src/test/resources/fhir/observationAllAoe.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
[
{
"resourceType": "Observation",
"id": "8526bd3b-42e4-3a6f-88ff-3fc43b69dc20",
"identifier": [
{
"use": "official",
"type": {
"coding": [
{
"system": "http://loinc.org",
"code": "81959-9",
"display": "Public health laboratory ask at order entry panel"
}
]
}
}
],
"status": "final",
"code": {
"coding": [
{
"system": "http://simplereport.gov/",
"code": "SR0001",
"display": "What is the gender of their sexual partners"
}
],
"text": "What is the gender of their sexual partners"
},
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "446151000124109",
"display": "Male gender identity"
}
]
}
},
{
"resourceType": "Observation",
"id": "5f3026ea-845b-3649-8b62-d3dbfd3e7b62",
Expand Down Expand Up @@ -37,6 +75,82 @@
]
}
},
{
"resourceType": "Observation",
"id": "8526bd3b-42e4-3a6f-88ff-3fc43b69dc20",
"identifier": [
{
"use": "official",
"type": {
"coding": [
{
"system": "http://loinc.org",
"code": "81959-9",
"display": "Public health laboratory ask at order entry panel"
}
]
}
}
],
"status": "final",
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "1087151000119108",
"display": "History of syphilis"
}
],
"text": "History of syphilis"
},
"valueCodeableConcept": {
"coding": [
{
"system": "http://terminology.hl7.org/ValueSet/v2-0136",
"code": "N",
"display": "No"
}
]
}
},
{
"resourceType": "Observation",
"id": "8526bd3b-42e4-3a6f-88ff-3fc43b69dc20",
"identifier": [
{
"use": "official",
"type": {
"coding": [
{
"system": "http://loinc.org",
"code": "81959-9",
"display": "Public health laboratory ask at order entry panel"
}
]
}
}
],
"status": "final",
"code": {
"coding": [
{
"system": "http://simplereport.gov/",
"code": "SR0001",
"display": "What is the gender of their sexual partners"
}
],
"text": "What is the gender of their sexual partners"
},
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "446141000124107",
"display": "Female gender identity"
}
]
}
},
{
"resourceType": "Observation",
"id": "07099f95-8754-3317-8e87-e1c4717c1af1",
Expand Down
Loading
Loading