-
Notifications
You must be signed in to change notification settings - Fork 10
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
Update PID 3.4 #1095
Update PID 3.4 #1095
Changes from all commits
09e60a7
bc570b2
a8b407e
507ea8b
14307ea
b93cc1b
b795841
98e9121
fea405e
59ccab8
0ae1509
2729fed
79e8371
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.custom; | ||
|
||
import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; | ||
import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleExecutionException; | ||
import gov.hhs.cdc.trustedintermediary.etor.ruleengine.transformation.CustomFhirTransformation; | ||
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiHelper; | ||
import java.util.Map; | ||
import org.hl7.fhir.r4.model.Bundle; | ||
|
||
public class UpdatePatientIdentifierListAssigningAuthority implements CustomFhirTransformation { | ||
|
||
@Override | ||
public void transform(FhirResource<?> resource, Map<String, String> args) | ||
throws RuleExecutionException { | ||
|
||
if (!(resource.getUnderlyingResource() instanceof Bundle)) { | ||
throw new RuleExecutionException("Resource provided is not a Bundle"); | ||
} | ||
|
||
try { | ||
Bundle bundle = (Bundle) resource.getUnderlyingResource(); | ||
String newValue = args.get("newValue"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should not be a new value for this transformation. This is the description as it's in the spreadsheet: "Strip content from Patient Identifier (PID-3). Remove PID-3.4 and PID-3.5" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the value can be: newValue = " " |
||
|
||
HapiHelper.updateOrganizationIdentifierValue(bundle, newValue); | ||
|
||
} catch (Exception e) { | ||
throw new RuleExecutionException("Unexpected error during transformation", e); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,27 @@ | ||
package gov.hhs.cdc.trustedintermediary.external.hapi; | ||
|
||
import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; | ||
import gov.hhs.cdc.trustedintermediary.wrappers.Logger; | ||
import java.util.AbstractMap; | ||
import java.util.Optional; | ||
import java.util.stream.Stream; | ||
import org.hl7.fhir.r4.model.Bundle; | ||
import org.hl7.fhir.r4.model.Coding; | ||
import org.hl7.fhir.r4.model.Identifier; | ||
import org.hl7.fhir.r4.model.MessageHeader; | ||
import org.hl7.fhir.r4.model.Meta; | ||
import org.hl7.fhir.r4.model.Organization; | ||
import org.hl7.fhir.r4.model.Patient; | ||
import org.hl7.fhir.r4.model.Reference; | ||
import org.hl7.fhir.r4.model.Resource; | ||
|
||
/** Helper class that works on HapiFHIR constructs. */ | ||
public class HapiHelper { | ||
|
||
private HapiHelper() {} | ||
|
||
private static final Logger LOGGER = ApplicationContext.getImplementation(Logger.class); | ||
|
||
public static final Coding OML_CODING = | ||
new Coding( | ||
"http://terminology.hl7.org/CodeSystem/v2-0003", | ||
|
@@ -65,4 +75,106 @@ public static MessageHeader findOrInitializeMessageHeader(Bundle bundle) { | |
} | ||
return (MessageHeader) messageHeader; | ||
} | ||
|
||
/** | ||
* Updates the value of an identifier for an Organization within a FHIR Bundle based on specific | ||
* criteria. This method processes each Patient resource within the bundle, identifies linked | ||
* Organizations via the assigner field of Patient identifiers, and checks each Organization's | ||
* identifier for specific extension criteria. If the criteria are met (specifically an | ||
* extension URL and a valueString that match predefined values), the identifier's value is | ||
* updated to a new specified value. | ||
* | ||
* <p>The following HL7 segment is changed: PID 3.4 - Assigning Authority | ||
* | ||
* @param bundle the FHIR Bundle containing Patient resources whose identifiers need to be | ||
* updated. | ||
* @param newValue the new value to which the identifier type code should be set. | ||
*/ | ||
public static void updateOrganizationIdentifierValue(Bundle bundle, String newValue) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I will end up moving this logic into the transform method of the transformation class. This will only leave helper methods in the HapiHelper class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will use FHIR path for checking, if it exists, then I will proceed to update the value |
||
bundle.getEntry().stream() | ||
.map(Bundle.BundleEntryComponent::getResource) | ||
.filter(Patient.class::isInstance) | ||
.map(Patient.class::cast) | ||
.flatMap(patient -> patient.getIdentifier().stream()) | ||
.map( | ||
identifier -> | ||
new AbstractMap.SimpleEntry<>( | ||
identifier, | ||
getOrganizationFromAssigner( | ||
bundle, identifier.getAssigner()))) | ||
.filter(entry -> entry.getValue().isPresent()) | ||
.flatMap( | ||
entry -> | ||
entry.getValue().get().getIdentifier().stream() | ||
.filter( | ||
orgIdentifier -> | ||
hasRequiredExtension( | ||
orgIdentifier, | ||
"https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", | ||
"HD.1")) | ||
.peek( | ||
orgIdentifier -> | ||
LOGGER.logInfo( | ||
"Updating Organization identifier from: " | ||
+ orgIdentifier.getValue())) | ||
.map(orgIdentifier -> orgIdentifier.setValue(newValue))) | ||
.forEach( | ||
orgIdentifier -> | ||
LOGGER.logInfo( | ||
"Updated Organization identifier to: " | ||
+ orgIdentifier.getValue())); | ||
} | ||
|
||
/** | ||
* Fetches the Organization referenced by a Patient identifier assigner. | ||
* | ||
* @param bundle The FHIR Bundle to search. | ||
* @param assigner The assigner reference. | ||
* @return Optional<Organization> if found, otherwise Optionale.empty(). | ||
*/ | ||
private static Optional<Organization> getOrganizationFromAssigner( | ||
Bundle bundle, Reference assigner) { | ||
if (assigner == null || assigner.getReference() == null) { | ||
LOGGER.logInfo("Assigner or assigner reference is null."); | ||
return Optional.empty(); | ||
} | ||
|
||
LOGGER.logInfo( | ||
"Starting search for Organization with reference: " + assigner.getReference()); | ||
|
||
return bundle.getEntry().stream() | ||
.map(Bundle.BundleEntryComponent::getResource) | ||
.filter(Organization.class::isInstance) | ||
.map(Organization.class::cast) | ||
.peek(org -> LOGGER.logInfo("Checking organization with ID: " + org.getId())) | ||
.filter( | ||
org -> { | ||
boolean matches = | ||
("Organization/" + org.getId()).equals(assigner.getReference()); | ||
LOGGER.logInfo( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried using LOGGER.logDebug() here but it was not working |
||
"Checking organization with ID: " | ||
+ org.getId() | ||
+ " for match: " | ||
+ matches); | ||
return matches; | ||
}) | ||
.findFirst(); | ||
} | ||
|
||
/** | ||
* Checks if an identifier has the required extension with specific url and valueString. | ||
* | ||
* @param identifier The identifier to check. | ||
* @param url The extension url to match. | ||
* @param valueString The extension valueString to match. | ||
* @return true if the extension exists and matches, false otherwise. | ||
*/ | ||
private static boolean hasRequiredExtension( | ||
Identifier identifier, String url, String valueString) { | ||
return identifier.getExtension().stream() | ||
.anyMatch( | ||
extension -> | ||
url.equals(extension.getUrl()) | ||
&& valueString.equals(extension.getValue().toString())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,29 @@ | ||
package gov.hhs.cdc.trustedintermediary.external.hapi | ||
|
||
import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext | ||
import org.hl7.fhir.r4.model.Bundle | ||
import org.hl7.fhir.r4.model.Organization | ||
import org.hl7.fhir.r4.model.Coding | ||
import org.hl7.fhir.r4.model.Identifier | ||
import org.hl7.fhir.r4.model.Extension | ||
import org.hl7.fhir.r4.model.MessageHeader | ||
import org.hl7.fhir.r4.model.Meta | ||
import org.hl7.fhir.r4.model.Patient | ||
import org.hl7.fhir.r4.model.Provenance | ||
import org.hl7.fhir.r4.model.Reference | ||
import org.hl7.fhir.r4.model.ResourceType | ||
import org.hl7.fhir.r4.model.ServiceRequest | ||
import org.hl7.fhir.r4.model.StringType | ||
import spock.lang.Specification | ||
|
||
class HapiHelperTest extends Specification { | ||
|
||
def setup() { | ||
TestApplicationContext.reset() | ||
TestApplicationContext.init() | ||
TestApplicationContext.injectRegisteredImplementations() | ||
} | ||
|
||
def "resourcesInBundle return a stream of a specific type of resources in a FHIR Bundle"() { | ||
given: | ||
def patients = [ | ||
|
@@ -181,4 +193,76 @@ class HapiHelperTest extends Specification { | |
convertedMessageHeader.getEventCoding().getCode() == expectedCode | ||
convertedMessageHeader.getEventCoding().getDisplay() == expectedDisplay | ||
} | ||
|
||
def "updateOrganizationIdentifierValue updates the correct identifier value"() { | ||
given: | ||
def id = "1" | ||
def presentValue = "initial Assigning Authority" | ||
def newValue = "Updated Assigning Authority" | ||
def identifierExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.1")) | ||
|
||
def organization = new Organization() | ||
organization.setId(id) | ||
organization.addIdentifier(new Identifier().setValue(presentValue).addExtension(identifierExtension) as Identifier) | ||
|
||
def patient = new Patient() | ||
patient.addIdentifier(new Identifier().setAssigner(new Reference("Organization/" + id) as Reference)) | ||
|
||
def bundle = new Bundle() | ||
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(patient)) | ||
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(organization).setFullUrl("Organization/1")) | ||
|
||
when: | ||
HapiHelper.updateOrganizationIdentifierValue(bundle, newValue) | ||
|
||
then: | ||
organization.getIdentifier().any { | ||
it.hasExtension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field") && | ||
it.getExtensionByUrl("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field").getValue().toString() == "HD.1" && | ||
it.getValue() == newValue | ||
} | ||
} | ||
|
||
def "handle empty bundle gracefully"() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test will change once exceptions are thrown |
||
given: | ||
Bundle emptyBundle = new Bundle() | ||
|
||
when: | ||
HapiHelper.updateOrganizationIdentifierValue(emptyBundle, "newValue") | ||
|
||
then: | ||
noExceptionThrown() | ||
} | ||
|
||
def "should not perform updates if no organizations are present in the bundle"() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with this unit test, it will change once exceptions are thrown |
||
given: | ||
def id = "1" | ||
def patient = new Patient() | ||
patient.addIdentifier(new Identifier().setAssigner(new Reference("Organization/" + id) as Reference)) | ||
|
||
def bundle = new Bundle() | ||
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(patient)) | ||
Bundle bundleWithNoOrganizations = bundle | ||
|
||
when: | ||
HapiHelper.updateOrganizationIdentifierValue(bundleWithNoOrganizations, "newValue") | ||
|
||
then: | ||
noExceptionThrown() | ||
} | ||
|
||
def "should handle patients without assigners"() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. It will change once exceptions are thrown |
||
given: | ||
def patient = new Patient() | ||
patient.addIdentifier(new Identifier()) | ||
|
||
def bundle = new Bundle() | ||
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(patient)) | ||
|
||
when: | ||
HapiHelper.updateOrganizationIdentifierValue(bundle, "newValue") | ||
|
||
then: | ||
noExceptionThrown() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be the only check that would trigger an exception as the
HapiHelper::updatePatientIdentifierValue
does not throw any exceptions. So, I will add the ability to throw exceptions if the bundle is missing the patient resource or the organization/id can't be found.