From 86240d52995ae935998dcbf0ef582e757727cc74 Mon Sep 17 00:00:00 2001 From: Clement Hennequin Date: Tue, 19 Nov 2024 12:57:00 -0500 Subject: [PATCH] Improve Advanced ACK and extracted methods to Hl7Util --- .../kernal/logic/IIncomingMessageHandler.java | 2 +- .../kernal/logic/IncomingMessageHandler.java | 99 ++++++++++++------- .../logic/IncomingMessageHandlerR4.java | 5 +- .../logic/IncomingMessageHandlerR5.java | 4 +- .../iis/kernal/logic/ack/IisAckBuilder.java | 96 ++---------------- .../iis/kernal/logic/ack/IisHL7Util.java | 92 +++++++++++++++++ 6 files changed, 171 insertions(+), 127 deletions(-) diff --git a/src/main/java/org/immregistries/iis/kernal/logic/IIncomingMessageHandler.java b/src/main/java/org/immregistries/iis/kernal/logic/IIncomingMessageHandler.java index 488fce0..01c980c 100644 --- a/src/main/java/org/immregistries/iis/kernal/logic/IIncomingMessageHandler.java +++ b/src/main/java/org/immregistries/iis/kernal/logic/IIncomingMessageHandler.java @@ -10,7 +10,7 @@ import java.util.Random; import java.util.Set; -interface IIncomingMessageHandler { +public interface IIncomingMessageHandler { double MINIMAL_MATCHING_SCORE = 0.9; String PATIENT_MIDDLE_NAME_MULTI = "Multi"; diff --git a/src/main/java/org/immregistries/iis/kernal/logic/IncomingMessageHandler.java b/src/main/java/org/immregistries/iis/kernal/logic/IncomingMessageHandler.java index 35d5ef6..e9e18d5 100644 --- a/src/main/java/org/immregistries/iis/kernal/logic/IncomingMessageHandler.java +++ b/src/main/java/org/immregistries/iis/kernal/logic/IncomingMessageHandler.java @@ -61,6 +61,11 @@ public abstract class IncomingMessageHandler implements IIncomingMessageHandler { public static final int NAME_SIZE_LIMIT = 15; + public static final String RSP_K_11_RSP_K_11 = "RSP^K11^RSP_K11"; + public static final String MATCH = "Match"; + public static final String NO_MATCH = "No Match"; + public static final String POSSIBLE_MATCH = "Possible Match"; + public static final String TOO_MANY_MATCHES = "Too Many Matches"; protected final Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class); /** * DYNAMIC VALUE SETS for validation @@ -109,9 +114,9 @@ public void verifyNoErrors(List processingExceptionList) th } } - public boolean hasErrors(List processingExceptionList) { - for (ProcessingException pe : processingExceptionList) { - if (pe.isError()) { + public boolean hasErrors(List reportables) { + for (IisReportable pe : reportables) { + if (pe.getSeverity().equals(IisReportableSeverity.ERROR)) { return true; } } @@ -317,7 +322,19 @@ public String buildAckMqe(HL7Reader reader, MqeMessageServiceResponse mqeMessage return ackBuilder.buildAckFrom(data); } - public Date parseDateWarn(String dateString, String errorMessage, String segmentId, int segmentRepeat, int fieldPosition, boolean strict, List processingExceptionList) { + public Date parseDateWarn(String dateString, String errorMessage, String segmentId, int segmentRepeat, int fieldPosition, boolean strict, List processingExceptionList) { + try { + return parseDateInternal(dateString, strict); + } catch (ParseException e) { + if (errorMessage != null) { + ProcessingException pe = new ProcessingException(errorMessage + ": " + e.getMessage(), segmentId, segmentRepeat, fieldPosition).setWarning(); + processingExceptionList.add(new IisReportable(pe)); + } + } + return null; + } + + public Date parseDateWarnOld(String dateString, String errorMessage, String segmentId, int segmentRepeat, int fieldPosition, boolean strict, List processingExceptionList) { try { return parseDateInternal(dateString, strict); } catch (ParseException e) { @@ -360,9 +377,12 @@ public Date parseDateInternal(String dateString, boolean strict) throws ParseExc return date; } - public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived) { + public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived) throws Exception { + MqeMessageServiceResponse mqeMessageServiceResponse = mqeMessageService.processMessage(messageReceived); + List reportables = nistValidation(messageReceived); + PatientMaster patientMasterForMatchQuery = new PatientMaster(); - List processingExceptionList = new ArrayList<>(); +// List processingExceptionList = new ArrayList<>(); if (reader.advanceToSegment("QPD")) { String mrn = ""; { @@ -384,7 +404,7 @@ public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived String patientNameMiddle = reader.getValue(4, 3); boolean strictDate = false; - Date patientBirthDate = parseDateWarn(reader.getValue(6), "Invalid patient birth date", "QPD", 1, 6, strictDate, processingExceptionList); + Date patientBirthDate = parseDateWarn(reader.getValue(6), "Invalid patient birth date", "QPD", 1, 6, strictDate, reportables); String patientSex = reader.getValue(7); if (patientNameLast.equals("")) { @@ -398,7 +418,8 @@ public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived fieldPosition = 6; } if (StringUtils.isNotBlank(problem)) { - processingExceptionList.add(new ProcessingException(problem, "QPD", 1, fieldPosition)); + reportables.add(new IisReportable(new ProcessingException(problem, "QPD", 1, fieldPosition))); +// processingExceptionList.add(new ProcessingException(problem, "QPD", 1, fieldPosition)); } else { PatientName patientName = new PatientName(patientNameLast, patientNameFirst, patientNameMiddle, ""); @@ -406,7 +427,8 @@ public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived patientMasterForMatchQuery.setBirthDate(patientBirthDate); } } else { - processingExceptionList.add(new ProcessingException("QPD segment not found", null, 0, 0)); + reportables.add(new IisReportable(new ProcessingException("QPD segment not found", null, 0, 0))); +// processingExceptionList.add(new ProcessingException("QPD segment not found", null, 0, 0)); } Set processingFlavorSet = tenant.getProcessingFlavorSet(); @@ -446,11 +468,14 @@ public String processQBP(Tenant tenant, HL7Reader reader, String messageReceived singleMatch = fhirRequester.matchPatient(multipleMatches, patientMasterForMatchQuery, cutoff); - return buildRSP(reader, messageReceived, singleMatch, tenant, multipleMatches, processingExceptionList); + return buildRSP(reader, messageReceived, singleMatch, tenant, multipleMatches, reportables); } @SuppressWarnings("unchecked") - public String buildRSP(HL7Reader reader, String messageReceived, PatientMaster patientMaster, Tenant tenant, List patientReportedPossibleList, List processingExceptionList) { + public String buildRSP(HL7Reader reader, String messageReceived, PatientMaster patientMaster, Tenant tenant, List patientReportedPossibleList, List iisReportables) { + + MqeMessageServiceResponse mqeMessageServiceResponse = mqeMessageService.processMessage(messageReceived); + IGenericClient fhirClient = repositoryClientFactory.getFhirClient(); reader.resetPostion(); reader.advanceToSegment("MSH"); @@ -459,7 +484,7 @@ public String buildRSP(HL7Reader reader, String messageReceived, PatientMaster p StringBuilder sb = new StringBuilder(); String profileIdSubmitted = reader.getValue(21); CodeMap codeMap = CodeMapManager.getCodeMap(); - String categoryResponse = "No Match"; + String categoryResponse = NO_MATCH; String profileId = RSP_Z33_NO_MATCH; boolean sendBackForecast = true; if (processingFlavorSet.contains(ProcessingFlavor.COCONUT)) { @@ -486,58 +511,62 @@ public String buildRSP(HL7Reader reader, String messageReceived, PatientMaster p } String queryResponse = QUERY_OK; { - String messageType = "RSP^K11^RSP_K11"; if (patientMaster == null) { queryResponse = QUERY_NOT_FOUND; profileId = RSP_Z33_NO_MATCH; - categoryResponse = "No Match"; + categoryResponse = NO_MATCH; if (patientReportedPossibleList.size() > 0) { if (profileIdSubmitted.equals(QBP_Z34)) { if (patientReportedPossibleList.size() > maxCount) { queryResponse = QUERY_TOO_MANY; profileId = RSP_Z33_NO_MATCH; - categoryResponse = "Too Many Matches"; + categoryResponse = TOO_MANY_MATCHES; } else { queryResponse = QUERY_OK; profileId = RSP_Z31_MULTIPLE_MATCH; - categoryResponse = "Possible Match"; + categoryResponse = POSSIBLE_MATCH; } } else if (profileIdSubmitted.equals("Z44")) { queryResponse = QUERY_NOT_FOUND; profileId = RSP_Z33_NO_MATCH; - categoryResponse = "No Match"; + categoryResponse = NO_MATCH; } } - if (hasErrors(processingExceptionList)) { + if (hasErrors(iisReportables)) { queryResponse = QUERY_APPLICATION_ERROR; } } else if (profileIdSubmitted.equals(QBP_Z34)) { profileId = RSP_Z32_MATCH; - categoryResponse = "Match"; + categoryResponse = MATCH; } else if (profileIdSubmitted.equals(QBP_Z44)) { if (processingFlavorSet.contains(ProcessingFlavor.ORANGE)) { profileId = RSP_Z32_MATCH; - categoryResponse = "Match"; + categoryResponse = MATCH; } else { sendBackForecast = true; profileId = RSP_Z42_MATCH_WITH_FORECAST; - categoryResponse = "Match"; + categoryResponse = MATCH; } } else { - processingExceptionList.add(new ProcessingException("Unrecognized profile id '" + profileIdSubmitted + "'", "MSH", 1, 21)); + iisReportables.add(new IisReportable(new ProcessingException("Unrecognized profile id '" + profileIdSubmitted + "'", "MSH", 1, 21))); } - hl7MessageWriter.createMSH(messageType, profileId, reader, sb, processingFlavorSet); + // TODO remove notices ? + hl7MessageWriter.createMSH(RSP_K_11_RSP_K_11, profileId, reader, sb, processingFlavorSet); } { String sendersUniqueId = reader.getValue(10); - if (hasErrors(processingExceptionList)) { - sb.append("MSA|AE|").append(sendersUniqueId).append("\r"); - } else { - sb.append("MSA|AA|").append(sendersUniqueId).append("\r"); - } - if (processingExceptionList.size() > 0) { - sb.append(IisHL7Util.makeERRSegment(new IisReportable(processingExceptionList.get(processingExceptionList.size() - 1)), false)); - } + IisHL7Util.makeMsaAndErr(sb, sendersUniqueId, profileId, profileId, iisReportables); +// if (hasErrors(iisReportables)) { +// sb.append("MSA|AE|").append(sendersUniqueId).append("\r"); +// } else { +// sb.append("MSA|AA|").append(sendersUniqueId).append("\r"); +// } +// if (iisReportables.size() > 0) { +// for (IisReportable iisReportable: iisReportables) { +// sb.append(IisHL7Util.makeERRSegment(iisReportable, false)); +// } +//// sb.append(IisHL7Util.makeERRSegment(new IisReportable(processingExceptionList.get(processingExceptionList.size() - 1)), false)); +// } } String profileName = "Request a Complete Immunization History"; if (profileIdSubmitted.equals("")) { @@ -1163,7 +1192,7 @@ public PatientReported processPatientFhirAgnostic(HL7Reader reader, List processingFlavorSet = null; @@ -298,7 +297,7 @@ public String processVXU(Tenant tenant, HL7Reader reader, String message, Organi vaccinationReported.setAdministeredAmount(reader.getValue(6)); vaccinationReported.setInformationSource(reader.getValue(9)); vaccinationReported.setLotnumber(reader.getValue(15)); - vaccinationReported.setExpirationDate(parseDateWarn(reader.getValue(16), "Invalid vaccination expiration date", "RXA", rxaCount, 16, strictDate, processingExceptionList)); + vaccinationReported.setExpirationDate(parseDateWarnOld(reader.getValue(16), "Invalid vaccination expiration date", "RXA", rxaCount, 16, strictDate, processingExceptionList)); vaccinationReported.setVaccineMvxCode(reader.getValue(17)); vaccinationReported.setRefusalReasonCode(reader.getValue(18)); vaccinationReported.setCompletionStatus(reader.getValue(20)); @@ -505,7 +504,7 @@ public ObservationReported readObservations(HL7Reader reader, List reportables = ackDataIn.getReportables(); + + IisHL7Util.makeMsaAndErr(ack, controlId, processingId, profileExtension, reportables); return ack.toString(); } -// public static void makeERRSegment(StringBuilder ack, String severity, String hl7ErrorCode, String textMessage, Reportable reportable) -// { -// -// if (severity.equals("E") && StringUtils.isBlank(hl7ErrorCode)) -// { -// hl7ErrorCode = "102"; -// } -// ack.append("ERR||"); -// // 2 Error Location -// ack.append("|" + reportable.getHl7LocationList()); -// // 3 HL7 Error Code -// IisHL7Util.appendErrorCode(ack, reportable.getHl7ErrorCode()); -// ack.append("|"); -// // 4 Severity -// ack.append(severity); -// ack.append("|"); -// // 5 Application Error Code -// IisHL7Util.appendAppErrorCode(ack, reportable); -// ack.append("|"); -// // 6 Application Error Parameter -// ack.append("|"); -// // 7 Diagnostic Information -// ack.append("|"); -// // 8 User Message -// ack.append(IisHL7Util.escapeHL7Chars(reportable.getReportedMessage())); -// ack.append("|\r"); -// -// } - private static boolean hasErrorSeverityType(IisAckData ackDataIn, String severityCode) { - for (IisReportable reportable : ackDataIn.getReportables()) { - if (reportable.getSeverity().getCode().equals(severityCode)) { - return true; - } - } - return false; - } + public static void makeHeader(StringBuilder ack, IisAckData ackDataIn, String profileId, String responseType) { @@ -140,12 +67,9 @@ public static void makeHeader(StringBuilder ack, IisAckData ackDataIn, String pr if (responseType == null) { responseType = "ACK^V04^ACK"; } - ack.append("|" + responseType); // MSH-9 - // Message - // Type - ack.append("|" + messageDate + "." + getNextAckCount()); // MSH-10 Message - // Control ID - ack.append("|P"); // MSH-11 Processing ID + ack.append("|" + responseType); // MSH-9 Message Type + ack.append("|" + messageDate + "." + getNextAckCount()); // MSH-10 Message Control ID + ack.append("|T"); // MSH-11 Processing ID TODO figure wether to remove or not ack.append("|2.5.1"); // MSH-12 Version ID ack.append("|"); if (profileId != null) { diff --git a/src/main/java/org/immregistries/iis/kernal/logic/ack/IisHL7Util.java b/src/main/java/org/immregistries/iis/kernal/logic/ack/IisHL7Util.java index 5d49918..f274929 100644 --- a/src/main/java/org/immregistries/iis/kernal/logic/ack/IisHL7Util.java +++ b/src/main/java/org/immregistries/iis/kernal/logic/ack/IisHL7Util.java @@ -3,12 +3,17 @@ import org.apache.commons.lang3.StringUtils; import org.immregistries.mqe.hl7util.ReportableSource; import org.immregistries.mqe.hl7util.builder.AckERRCode; +import org.immregistries.mqe.hl7util.builder.AckResult; import org.immregistries.mqe.hl7util.model.CodedWithExceptions; import org.immregistries.mqe.hl7util.model.Hl7Location; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import static org.immregistries.iis.kernal.logic.IIncomingMessageHandler.ADVANCED_ACK; +import static org.immregistries.iis.kernal.logic.ack.IisAckBuilder.PROCESSING_ID_DEBUG; import static org.immregistries.mqe.vxu.parse.HL7ParsingUtil.escapeHL7Chars; public class IisHL7Util { @@ -314,4 +319,91 @@ public static String makeERRSegment(String severity, String textMessage, IisRepo ack.append("|\r"); return ack.toString(); } + + public static void makeMsaAndErr(StringBuilder sb, String controlId, String processingId, String profileExtension, List reportables) { + String ackCode = getAckCode(profileExtension, reportables); + sb.append("MSA|").append(ackCode).append("|").append(controlId).append("|\r"); + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.ERROR) { + sb.append(IisHL7Util.makeERRSegment(r, PROCESSING_ID_DEBUG.equals(processingId))); + } + } + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.WARN) { + sb.append(IisHL7Util.makeERRSegment(r, PROCESSING_ID_DEBUG.equals(processingId))); + } + } + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.INFO) { + sb.append(IisHL7Util.makeERRSegment(r, PROCESSING_ID_DEBUG.equals(processingId))); + } + } + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.NOTICE) { + sb.append(IisHL7Util.makeERRSegment(r, PROCESSING_ID_DEBUG.equals(processingId))); + } + } + if (PROCESSING_ID_DEBUG.equals(processingId)) { + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.ACCEPT) { + sb.append(IisHL7Util.makeERRSegment(r, PROCESSING_ID_DEBUG.equals(processingId))); + } + } + } + } + + private static boolean hasErrorSeverityType(List reportables, String severityCode) { + for (IisReportable reportable : reportables) { + if (reportable.getSeverity().getCode().equals(severityCode)) { + return true; + } + } + return false; + } + + private static String getAckCode(String profileExtension, List reportables) { + String ackCode; + String hl7ErrorCode; + if (ADVANCED_ACK.equals(profileExtension)) { // If extended ACK profile + if (hasErrorSeverityType(reportables, IisReportableSeverity.ERROR.getCode())) { + ackCode = AckResult.APP_ERROR.getCode(); + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.ERROR && r.getHl7ErrorCode() != null + && r.getHl7ErrorCode().getIdentifier() != null) { + hl7ErrorCode = r.getHl7ErrorCode().getIdentifier(); + if (hl7ErrorCode != null && hl7ErrorCode.startsWith("2")) { + ackCode = AckResult.APP_REJECT.getCode(); + break; + } + } + } + } else if (hasErrorSeverityType(reportables, IisReportableSeverity.WARN.getCode())) { + ackCode = "AW"; + } else if (hasErrorSeverityType(reportables, "N")) { + ackCode = "AN"; + } else { + ackCode = AckResult.APP_ACCEPT.getCode(); + } + } else { + List list = reportables.stream().filter(iisReportable -> iisReportable.getSeverity().equals(IisReportableSeverity.NOTICE)).collect(Collectors.toList()); + reportables = list; + if (hasErrorSeverityType(reportables, IisReportableSeverity.ERROR.getCode()) || hasErrorSeverityType(reportables, IisReportableSeverity.WARN.getCode())) { + ackCode = AckResult.APP_ERROR.getCode(); + for (IisReportable r : reportables) { + if (r.getSeverity() == IisReportableSeverity.ERROR && r.getHl7ErrorCode() != null + && r.getHl7ErrorCode().getIdentifier() != null) { + hl7ErrorCode = r.getHl7ErrorCode().getIdentifier(); + if (hl7ErrorCode != null && hl7ErrorCode.startsWith("2")) { + ackCode = AckResult.APP_REJECT.getCode(); + break; + } + } + } + } else { + ackCode = AckResult.APP_ACCEPT.getCode(); + } + } + return ackCode; + } + }