Skip to content
Closed
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
81 changes: 63 additions & 18 deletions src/main/java/ch/bfh/ti/i4mi/mag/pmir/PMIRRequestConverter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package ch.bfh.ti.i4mi.mag.pmir;

import net.ihe.gazelle.hl7v3.datatypes.*;
import net.ihe.gazelle.hl7v3.mccimt000100UV01.MCCIMT000100UV01Device;
import net.ihe.gazelle.hl7v3.mccimt000100UV01.MCCIMT000100UV01Receiver;
import net.ihe.gazelle.hl7v3.mccimt000100UV01.MCCIMT000100UV01Sender;
import net.ihe.gazelle.hl7v3.prpain201305UV02.PRPAIN201305UV02QUQIMT021001UV01ControlActProcess;
import net.ihe.gazelle.hl7v3.prpain201305UV02.PRPAIN201305UV02Type;
import net.ihe.gazelle.hl7v3.prpamt201306UV02.PRPAMT201306UV02ParameterList;
import net.ihe.gazelle.hl7v3.prpamt201306UV02.PRPAMT201306UV02QueryByParameter;
import net.ihe.gazelle.hl7v3.voc.*;
import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
Expand All @@ -13,24 +22,9 @@
import org.hl7.fhir.r4.model.StringType;

import ch.bfh.ti.i4mi.mag.BaseRequestConverter;
import net.ihe.gazelle.hl7v3.datatypes.AD;
import net.ihe.gazelle.hl7v3.datatypes.AdxpCity;
import net.ihe.gazelle.hl7v3.datatypes.AdxpCountry;
import net.ihe.gazelle.hl7v3.datatypes.AdxpCounty;
import net.ihe.gazelle.hl7v3.datatypes.AdxpPostalCode;
import net.ihe.gazelle.hl7v3.datatypes.AdxpState;
import net.ihe.gazelle.hl7v3.datatypes.AdxpStreetAddressLine;
import net.ihe.gazelle.hl7v3.datatypes.CE;
import net.ihe.gazelle.hl7v3.datatypes.ENXP;
import net.ihe.gazelle.hl7v3.datatypes.EnFamily;
import net.ihe.gazelle.hl7v3.datatypes.EnGiven;
import net.ihe.gazelle.hl7v3.datatypes.EnPrefix;
import net.ihe.gazelle.hl7v3.datatypes.EnSuffix;
import net.ihe.gazelle.hl7v3.datatypes.IVLTS;
import net.ihe.gazelle.hl7v3.datatypes.IVXBTS;
import net.ihe.gazelle.hl7v3.datatypes.PN;
import net.ihe.gazelle.hl7v3.datatypes.TEL;
import net.ihe.gazelle.hl7v3.datatypes.TS;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.Timestamp;

import java.util.Collections;

/**
* base class for PMIR/PIX request converters
Expand Down Expand Up @@ -151,4 +145,55 @@ public static CE transform(CodeableConcept cc) {
Coding coding = cc.getCodingFirstRep();
return new CE(coding.getCode(), coding.getDisplay(), coding.getSystem());
}

protected PRPAIN201305UV02Type initiateIti47Request(final String senderOid,
final String receiverOid) {
final var request = new PRPAIN201305UV02Type();
request.setITSVersion("XML_1.0");

request.setId(new II(senderOid, uniqueId()));
request.setCreationTime(new TS(Timestamp.now().toHL7())); // Now
request.setProcessingCode(new CS("T", null ,null));
request.setProcessingModeCode(new CS("T", null, null));
request.setInteractionId(new II("2.16.840.1.113883.1.6", "PRPA_IN201305UV02"));
request.setAcceptAckCode(new CS("AL", null, null));

final var receiver = new MCCIMT000100UV01Receiver();
request.addReceiver(receiver);
receiver.setTypeCode(CommunicationFunctionType.RCV);

final var receiverDevice = new MCCIMT000100UV01Device();
receiver.setDevice(receiverDevice );
receiverDevice.setClassCode(EntityClassDevice.DEV);
receiverDevice.setDeterminerCode(EntityDeterminer.INSTANCE);
receiverDevice.setId(Collections.singletonList(new II(receiverOid, null)));

final var sender = new MCCIMT000100UV01Sender();
request.setSender(sender);
sender.setTypeCode(CommunicationFunctionType.SND);

final var senderDevice = new MCCIMT000100UV01Device();
sender.setDevice(senderDevice);
senderDevice.setClassCode(EntityClassDevice.DEV);
senderDevice.setDeterminerCode(EntityDeterminer.INSTANCE);
senderDevice.setId(Collections.singletonList(new II(senderOid, null)));

final var controlActProcess = new PRPAIN201305UV02QUQIMT021001UV01ControlActProcess();
request.setControlActProcess(controlActProcess );
controlActProcess.setClassCode(ActClassControlAct.CACT);
controlActProcess.setMoodCode(XActMoodIntentEvent.EVN);
controlActProcess.setCode(new CD("PRPA_TE201305UV02","2.16.840.1.113883.1.6", null));

final var queryByParameter = new PRPAMT201306UV02QueryByParameter();
controlActProcess.setQueryByParameter(queryByParameter );
queryByParameter.setQueryId(new II(senderOid, uniqueId()));
queryByParameter.setStatusCode(new CS("new", null, null));
queryByParameter.setResponsePriorityCode(new CS("I", null, null));
queryByParameter.setResponseModalityCode(new CS("R", null, null));

final var parameterList = new PRPAMT201306UV02ParameterList();
queryByParameter.setParameterList(parameterList);

return request;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ch.bfh.ti.i4mi.mag.pmir.iti119;

import ch.bfh.ti.i4mi.mag.Config;
import ch.bfh.ti.i4mi.mag.pmir.iti78.Iti78RequestConverter;
import net.ihe.gazelle.hl7v3.datatypes.II;
import net.ihe.gazelle.hl7v3.datatypes.IVLTS;
import net.ihe.gazelle.hl7v3.prpain201305UV02.PRPAIN201305UV02Type;
import net.ihe.gazelle.hl7v3.prpamt201306UV02.PRPAMT201306UV02LivingSubjectId;
import org.apache.camel.Body;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.openehealth.ipf.commons.ihe.fhir.iti119.PdqmMatchInputPatient;

import javax.xml.bind.JAXBException;
import java.util.Optional;

public class Iti119RequestConverter extends Iti78RequestConverter {

public Iti119RequestConverter(final Config config) {
this.config = config;
}

public PRPAIN201305UV02Type convert(@Body final Parameters parameters) throws JAXBException {
// Extract the Patient resource from the Parameters
final var patient = Optional.ofNullable(parameters.getParameter("resource"))
.map(Parameters.ParametersParameterComponent::getResource)
.filter(Patient.class::isInstance)
.map(Patient.class::cast)
.orElseThrow(() -> new IllegalArgumentException("Parameters must contain a Patient resource"));

// Create an ITI-47 request
final PRPAIN201305UV02Type request = initiateIti47Request(config.getPixMySenderOid(),
config.getPixReceiverOid());
final var parameterList = request.getControlActProcess().getQueryByParameter().getParameterList();

// Map the search parameters from the Patient resource to the request

// 1. Identifiers
for (final var identifier : patient.getIdentifier()) {
final var id = new PRPAMT201306UV02LivingSubjectId();
id.addValue(new II(identifier.getSystem(), identifier.getValue()));
id.setSemanticsText(ST("LivingSubject.id"));
parameterList.addLivingSubjectId(id);
}

// 2. Name & Birth name
for (final var name : patient.getName()) {
parameterList.addLivingSubjectName(createLivingSubjectName(transform(name)));
}

// 3. Gender
if (patient.hasGender()) {
parameterList.addLivingSubjectAdministrativeGender(createAdministrativeGender(patient.getGender().toCode()));
}

// 4. Birth date
if (patient.hasBirthDate()) {
final var birthDate = new IVLTS();
birthDate.setValue(patient.getBirthDateElement().getValueAsString().replace("-",""));

Copilot AI Aug 22, 2025

Copy link

Choose a reason for hiding this comment

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

The date formatting logic using simple string replacement is fragile and may not handle all date formats correctly. Consider using a proper date formatter or the existing transform methods for consistent date handling.

Suggested change
birthDate.setValue(patient.getBirthDateElement().getValueAsString().replace("-",""));
// Use robust date formatting
final var birthDateValue = patient.getBirthDateElement().getValue();
if (birthDateValue != null) {
LocalDate localDate = birthDateValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
birthDate.setValue(localDate.format(formatter));
}

Copilot uses AI. Check for mistakes.
parameterList.addLivingSubjectBirthTime(createBirthTime(birthDate));
}

// 5. Address
for (final var address : patient.getAddress()) {
parameterList.addPatientAddress(createAddress(transform(address)));
}

// 6. Maiden name (not required)
if (patient.hasExtension(PdqmMatchInputPatient.MOTHERS_MAIDEN_NAME_EXT)) {
final var ext = patient.getExtensionByUrl(PdqmMatchInputPatient.MOTHERS_MAIDEN_NAME_EXT);
if (ext.getValue() instanceof StringType) {
final var value = (StringType) ext.getValue();
parameterList.addMothersMaidenName(createMothersMaidenName(value.getValue()));

Copilot AI Aug 22, 2025

Copy link

Choose a reason for hiding this comment

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

The extension handling assumes StringType but the PdqmMatchInputPatient class defines mothersMaidenName as HumanName type. This type mismatch could cause the extension to be ignored. The extension should be processed as HumanName to match the class definition.

Suggested change
parameterList.addMothersMaidenName(createMothersMaidenName(value.getValue()));
if (ext.getValue() instanceof HumanName) {
final var humanName = (HumanName) ext.getValue();
// Use the display name if available, otherwise concatenate given and family
String maidenName = humanName.getText();
if (maidenName == null || maidenName.isEmpty()) {
maidenName = String.join(" ", humanName.getGiven().stream().map(g -> g.getValue()).toArray(String[]::new));
if (humanName.hasFamily()) {
maidenName = maidenName.isEmpty() ? humanName.getFamily() : maidenName + " " + humanName.getFamily();
}
}
parameterList.addMothersMaidenName(createMothersMaidenName(maidenName));

Copilot uses AI. Check for mistakes.
}
}

return request;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ch.bfh.ti.i4mi.mag.pmir.iti119;

import ch.bfh.ti.i4mi.mag.pmir.iti78.Iti78ResponseConverter;
import org.springframework.stereotype.Component;

@Component
public class Iti119ResponseConverter extends Iti78ResponseConverter {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package ch.bfh.ti.i4mi.mag.pmir.iti119;

import ch.bfh.ti.i4mi.mag.Config;
import ch.bfh.ti.i4mi.mag.common.RequestHeadersForwarder;
import ch.bfh.ti.i4mi.mag.common.TraceparentHandler;
import ch.bfh.ti.i4mi.mag.mhd.BaseResponseConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import javax.xml.ws.soap.SOAPFaultException;

import static org.openehealth.ipf.platform.camel.ihe.fhir.core.FhirCamelTranslators.translateToFhir;

/**
* IHE PDQm: ITI-119 Patient Demographics Match
*/
@Slf4j
@Component
@ConditionalOnProperty("mag.pix.iti-47.url")
public class Iti119RouteBuilder extends RouteBuilder {

private final Config config;
private final Iti119ResponseConverter responseConverter;

public Iti119RouteBuilder(final Config config,
final Iti119ResponseConverter responseConverter) {
super();
this.config = config;
this.responseConverter = responseConverter;
log.debug("Iti119RouteBuilder initialized");
}

@Override
public void configure() throws Exception {
log.debug("Iti119RouteBuilder configure");

final String xds47Endpoint = String.format(
"pdqv3-iti47://%s?secure=%s", this.config.getIti47HostUrl(), this.config.isPixHttps() ? "true" : "false"
) +
//"&sslContextParameters=#pixContext" +
"&audit=true" +
"&auditContext=#myAuditContext" +
"&inInterceptors=#soapResponseLogger" +
"&inFaultInterceptors=#soapResponseLogger"+
"&outInterceptors=#soapRequestLogger" +
"&outFaultInterceptors=#soapRequestLogger";

from("pdqm-iti119:translation?audit=true&auditContext=#myAuditContext").routeId("pdqm-adapter")
// pass back errors to the endpoint
.errorHandler(noErrorHandler())
.process(RequestHeadersForwarder.checkAuthorization(config.isChPdqmConstraints()))
.process(RequestHeadersForwarder.forward())
.bean(Iti119RequestConverter.class, "convert")
.doTry()
.to(xds47Endpoint)
.process(TraceparentHandler.updateHeaderForFhir())
.process(translateToFhir(responseConverter , byte[].class))
.doCatch(SOAPFaultException.class)
.setBody(simple("${exception}"))
.bean(BaseResponseConverter.class, "errorFromException")
.end();
}
}
Loading
Loading