diff --git a/.github/workflows/emap-tests.yaml b/.github/workflows/emap-tests.yaml index 2a003b64b..64fedaa7b 100644 --- a/.github/workflows/emap-tests.yaml +++ b/.github/workflows/emap-tests.yaml @@ -3,7 +3,7 @@ on: push: branches: [ main, develop ] pull_request: - branches: [ main, develop, sk/waveform-dev ] + branches: [ main, develop, sk/waveform-dev, test_branch_zella ] repository_dispatch: permissions: contents: read diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/InformDbOperations.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/InformDbOperations.java index 04d53b854..32f6d28ba 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/InformDbOperations.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/InformDbOperations.java @@ -35,6 +35,7 @@ import uk.ac.ucl.rits.inform.interchange.adt.PendingDischarge; import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; import uk.ac.ucl.rits.inform.interchange.adt.SwapLocations; +import uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality; import uk.ac.ucl.rits.inform.interchange.form.FormMetadataMsg; import uk.ac.ucl.rits.inform.interchange.form.FormMsg; import uk.ac.ucl.rits.inform.interchange.form.FormQuestionMetadataMsg; @@ -361,5 +362,16 @@ public void processMessage(FormQuestionMetadataMsg msg) { formProcessor.processQuestionMetadataMessage(msg, storedFrom); } + /** + * Process an Update Sub Speciality message. + * @param updateSubSpeciality the message + * @throws EmapOperationMessageProcessingException if message could not be processed + */ + @Override + @Transactional + public void processMessage(UpdateSubSpeciality updateSubSpeciality) throws EmapOperationMessageProcessingException { + Instant storedFrom = Instant.now(); + adtProcessor.processUpdateSubSpeciality(updateSubSpeciality, storedFrom); + } } diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/LocationController.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/LocationController.java index 482bf5a46..742847ad0 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/LocationController.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/LocationController.java @@ -199,7 +199,7 @@ class DepartmentController { } /** - * Get or create minomal department entity. + * Get or create minimal department entity. * @param msg minimal department message * @return saved department entity */ @@ -257,7 +257,10 @@ private static boolean updateFieldIfNull(String currentData, String newdata, Con void processDepartmentStates(DepartmentMetadata msg, Department department, Instant storedFrom) throws IncompatibleDatabaseStateException { Instant validFrom = msg.getSpecialityUpdate() == null ? msg.getDepartmentContactDate() : msg.getSpecialityUpdate(); DepartmentState currentState = new DepartmentState( - department, msg.getDepartmentRecordStatus().toString(), msg.getDepartmentSpeciality(), validFrom, storedFrom); + department, msg.getDepartmentRecordStatus().toString(), + msg.getDepartmentSpeciality(), msg.getDepartmentType(), + msg.getIsWardOrFlowArea(), msg.getIsCoreInpatientArea(), + msg.getSiteLocation(), validFrom, storedFrom); if (departmentStateRepo.existsByDepartmentIdAndSpecialityAndValidFrom(department, msg.getDepartmentSpeciality(), validFrom)) { logger.debug("Department State already exists in the database, no need to process further"); @@ -274,9 +277,11 @@ void processDepartmentStates(DepartmentMetadata msg, Department department, Inst invalidatePreviousStateIfChanged(msg.getPreviousDepartmentSpeciality(), currentState, possiblePreviousState.get()); } else if (msg.getPreviousDepartmentSpeciality() != null) { // if the previous department speciality is not in the database - DepartmentState previousState = new DepartmentState( - department, msg.getDepartmentRecordStatus().toString(), msg.getPreviousDepartmentSpeciality(), - msg.getDepartmentContactDate(), storedFrom); + DepartmentState previousState = new DepartmentState(department, + msg.getDepartmentRecordStatus().toString(), + msg.getPreviousDepartmentSpeciality(), msg.getDepartmentType(), + msg.getIsWardOrFlowArea(), msg.getIsCoreInpatientArea(), + msg.getSiteLocation(), msg.getDepartmentContactDate(), storedFrom); previousState.setStoredUntil(currentState.getStoredFrom()); previousState.setValidUntil(currentState.getValidFrom()); departmentStateRepo.saveAll(List.of(previousState, currentState)); diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/PendingAdtController.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/PendingAdtController.java index d5a39430e..8b866acf9 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/PendingAdtController.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/PendingAdtController.java @@ -10,13 +10,18 @@ import uk.ac.ucl.rits.inform.informdb.movement.Location; import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovement; import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovementAudit; +import uk.ac.ucl.rits.inform.interchange.adt.AdmitPatient; +import uk.ac.ucl.rits.inform.interchange.adt.AdtCancellation; +import uk.ac.ucl.rits.inform.interchange.adt.AdtMessage; import uk.ac.ucl.rits.inform.interchange.adt.CancelPendingTransfer; +import uk.ac.ucl.rits.inform.interchange.adt.HospitalService; import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; import uk.ac.ucl.rits.inform.interchange.adt.CancelPendingDischarge; import uk.ac.ucl.rits.inform.interchange.adt.PendingDischarge; import java.time.Instant; import java.util.List; +import java.util.Objects; import java.util.Optional; @@ -174,6 +179,96 @@ public void processMsg(HospitalVisit visit, CancelPendingTransfer msg, Instant v plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo); } + /** + * Process a real ADT-triggered change of hospital service/subspeciality, as a fallback for when + * a matching pending transfer request was never sent (e.g. Z99 edits, or a real admission/update + * that carries a hospital service different to the one currently recorded). + *

+ * The Hl7 feed will eventually be changed so that we have an identifier per pending transfer, until then we guarantee the order of cancellations. + * If we get messages out of order and have several cancellation messages before we receive any requests, + * then the first request message for the location and encounter will add the eventDatetime to the earliest cancellation. + * Subsequent requests will add the eventDatetime to the earliest cancellation with no eventDatetime, or create a new request if none exist + * after the pending request eventDatetime. + * @param visit associated visit + * @param msg the ADT message, for the fields shared by all ADT messages + * @param serviceMsg the same message, as its HospitalService view + * @param validFrom time in the hospital when the message was created + * @param storedFrom time that emap core started processing the message + */ + public void processHospitalServiceFallback(HospitalVisit visit, AdtMessage msg, HospitalService serviceMsg, + Instant validFrom, Instant storedFrom) { + if (serviceMsg.getHospitalService().isUnknown()) { + return; + } + + Location fullLocation = null; + if (msg.getFullLocationString().isSave()) { + fullLocation = locationController.getOrCreateLocation(msg.getFullLocationString().get()); + } + + Instant eventDateTime = msg.getEventOccurredDateTime(); + + List movements = plannedMovementRepo.findMatchingMovementsForHospitalServiceFallback(visit, fullLocation, eventDateTime); + if (!movements.isEmpty()) { + + int mostRecentMoveIndex = movements.size() - 1; + String currentService = movements.get(mostRecentMoveIndex).getHospitalService(); + String editedService = serviceMsg.getHospitalService().get(); + + if (!Objects.equals(currentService, editedService)) { + Long matchedMovementId = movements.get(mostRecentMoveIndex).getPlannedMovementId(); + RowState plannedState = getOrCreate( + allFromRequest, visit, fullLocation, "EDIT/HOSPITAL_SERVICE_CHANGE", eventDateTime, validFrom, storedFrom + ); + PlannedMovement movement = plannedState.getEntity(); + // not sure why but event date time isn't being set. Add it here. + plannedState.assignIfDifferent(eventDateTime, movement.getEventDatetime(), movement::setEventDatetime); + plannedState.assignInterchangeValue(serviceMsg.getHospitalService(), movement.getHospitalService(), movement::setHospitalService); + plannedState.assignIfDifferent(matchedMovementId, movement.getMatchedMovementId(), movement::setMatchedMovementId); + plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo); + } + } + } + + /** + * Record a real admission as its own planned movement. + *

+ * Unlike {@link #processHospitalServiceFallback}, this always inserts (or idempotently reuses) an + * ADMISSION row, regardless of whether a matching planned movement already exists or has the same + * hospital service. Any matched prior movement (e.g. a TRANSFER row from a pending transfer request) + * is only referenced via matchedMovementId - it is never itself modified. + * @param visit associated visit + * @param msg the admission message + * @param validFrom time in the hospital when the message was created + * @param storedFrom time that emap core started processing the message + */ + public void processAdmission(HospitalVisit visit, AdmitPatient msg, Instant validFrom, Instant storedFrom) { + if (msg.getHospitalService().isUnknown()) { + return; + } + + Location fullLocation = null; + if (msg.getFullLocationString().isSave()) { + fullLocation = locationController.getOrCreateLocation(msg.getFullLocationString().get()); + } + Instant eventDateTime = msg.getEventOccurredDateTime(); + + List priorMovements = plannedMovementRepo.findMatchingMovementsForHospitalServiceFallback( + visit, fullLocation, eventDateTime); + Long matchedMovementId = priorMovements.isEmpty() ? null : priorMovements.get(priorMovements.size() - 1).getPlannedMovementId(); + + RowState plannedState = getOrCreate( + allFromRequest, visit, fullLocation, "ADMISSION", eventDateTime, validFrom, storedFrom + ); + PlannedMovement movement = plannedState.getEntity(); + plannedState.assignIfDifferent(eventDateTime, movement.getEventDatetime(), movement::setEventDatetime); + plannedState.assignInterchangeValue(msg.getHospitalService(), movement.getHospitalService(), movement::setHospitalService); + if (matchedMovementId != null) { + plannedState.assignIfDifferent(matchedMovementId, movement.getMatchedMovementId(), movement::setMatchedMovementId); + } + plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo); + } + /** * Process pending ADT cancellation. *

@@ -203,6 +298,42 @@ public void processMsg(HospitalVisit visit, CancelPendingDischarge msg, Instant } + /** + * Cancel the matching ADMISSION row for a cancelled admission. + *

+ * Only rows created by {@link #processAdmission} (event type ADMISSION) are ever cancelled here. + * A row originating from a real pending transfer/discharge request is left alone, since it + * represents a separately-tracked plan that may still be valid regardless of this one ADT message. + * @param visit associated visit + * @param msg the ADT message, for the fields shared by all ADT messages + * @param cancellation the same message, as its AdtCancellation view + * @param validFrom time in the hospital when the message was created + * @param storedFrom time that emap core started processing the message + */ + public void processAdmissionCancellation(HospitalVisit visit, AdtMessage msg, AdtCancellation cancellation, + Instant validFrom, Instant storedFrom) { + Location fullLocation = null; + if (msg.getFullLocationString().isSave()) { + fullLocation = locationController.getOrCreateLocation(msg.getFullLocationString().get()); + } + + List movements = plannedMovementRepo.findMatchingMovementsForHospitalServiceFallback( + visit, fullLocation, cancellation.getCancelledDateTime()); + if (movements.isEmpty()) { + return; + } + + PlannedMovement movement = movements.get(movements.size() - 1); + if (!"ADMISSION".equals(movement.getEventType()) || movement.getCancelledDatetime() != null) { + return; + } + + RowState plannedState = new RowState<>(movement, validFrom, storedFrom, false); + plannedState.assignIfDifferent(cancellation.getCancelledDateTime(), movement.getCancelledDatetime(), movement::setCancelledDatetime); + plannedState.assignIfDifferent(true, movement.getCancelled(), movement::setCancelled); + plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo); + } + /** * Delete planned movements from a delete patient information message. * @param visit Hospital visit that should have their planned movements deleted diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/VisitController.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/VisitController.java index 3469f99dc..c29a74287 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/VisitController.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/controllers/VisitController.java @@ -69,6 +69,18 @@ public HospitalVisit getOrCreateMinimalHospitalVisit( return visit.getEntity(); } + /** + * Look up an existing hospital visit without creating one. + * @param encounter encounter number + * @return the existing visit, or null if none exists for this encounter + */ + public HospitalVisit getHospitalVisitIfExists(final String encounter) { + if (encounter == null || encounter.isEmpty()) { + return null; + } + return hospitalVisitRepo.findByEncounter(encounter).orElse(null); + } + /** * Get or create minimal hospital visit, and update whether it was created. * @param encounter encounter number @@ -138,7 +150,7 @@ public HospitalVisit updateOrCreateHospitalVisit( Instant validFrom = msg.bestGuessAtValidFrom(); RowState visitState = getOrCreateHospitalVisit( msg.getVisitNumber(), mrn, msg.getSourceSystem(), validFrom, storedFrom); - + addAdmissionType(msg, visitState); if (visitShouldBeUpdated(validFrom, msg.getSourceSystem(), visitState)) { updateGenericData(msg, visitState); @@ -219,6 +231,17 @@ private void addAdmissionDateTime(final AdmissionDateTime msg, RowState visitState) { + HospitalVisit visit = visitState.getEntity(); + visitState.assignInterchangeValue(msg.getAdmissionType(), visit.getAdmissionType(), visit::setAdmissionType); + } + + /** * Delete admission specific information. * @param msg cancellation message diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/dataprocessors/AdtProcessor.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/dataprocessors/AdtProcessor.java index d17274410..91bf73fa3 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/dataprocessors/AdtProcessor.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/dataprocessors/AdtProcessor.java @@ -13,16 +13,20 @@ import uk.ac.ucl.rits.inform.informdb.identity.HospitalVisit; import uk.ac.ucl.rits.inform.informdb.identity.Mrn; import uk.ac.ucl.rits.inform.interchange.EmapOperationMessageProcessingException; +import uk.ac.ucl.rits.inform.interchange.adt.AdmitPatient; import uk.ac.ucl.rits.inform.interchange.adt.AdtMessage; +import uk.ac.ucl.rits.inform.interchange.adt.CancelAdmitPatient; import uk.ac.ucl.rits.inform.interchange.adt.CancelPendingDischarge; import uk.ac.ucl.rits.inform.interchange.adt.CancelPendingTransfer; import uk.ac.ucl.rits.inform.interchange.adt.ChangePatientIdentifiers; import uk.ac.ucl.rits.inform.interchange.adt.DeletePersonInformation; +import uk.ac.ucl.rits.inform.interchange.adt.HospitalService; import uk.ac.ucl.rits.inform.interchange.adt.MergePatient; import uk.ac.ucl.rits.inform.interchange.adt.MoveVisitInformation; import uk.ac.ucl.rits.inform.interchange.adt.PendingDischarge; import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; import uk.ac.ucl.rits.inform.interchange.adt.SwapLocations; +import uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality; import java.time.Instant; import java.util.List; @@ -42,11 +46,11 @@ public class AdtProcessor { /** * Implicitly wired spring beans. - * @param personController person interactions. - * @param visitController encounter interactions. - * @param patientLocationController location interactions. - * @param pendingAdtController pending ADT interactions. - * @param deletionController cascading deletes for hospital visits. + * @param personController person interactions. + * @param visitController encounter interactions. + * @param patientLocationController location interactions. + * @param pendingAdtController pending ADT interactions. + * @param deletionController cascading deletes for hospital visits. */ public AdtProcessor(PersonController personController, VisitController visitController, PatientLocationController patientLocationController, PendingAdtController pendingAdtController, @@ -70,6 +74,18 @@ public void processMessage(final AdtMessage msg, final Instant storedFrom) throw Instant messageDateTime = msg.bestGuessAtValidFrom(); HospitalVisit visit = processPersonAndVisit(msg, storedFrom, messageDateTime); patientLocationController.processVisitLocation(visit, msg, storedFrom); + if (msg instanceof AdmitPatient) { + pendingAdtController.processAdmission(visit, (AdmitPatient) msg, messageDateTime, storedFrom); + } else if (msg instanceof HospitalService) { + // UpdatePatientInfo doesn't create a visit of its own, so look up an existing one for the fallback. + HospitalVisit visitForFallback = (visit != null) ? visit : visitController.getHospitalVisitIfExists(msg.getVisitNumber()); + if (visitForFallback != null) { + pendingAdtController.processHospitalServiceFallback(visitForFallback, msg, (HospitalService) msg, messageDateTime, storedFrom); + } + } + if (msg instanceof CancelAdmitPatient) { + pendingAdtController.processAdmissionCancellation(visit, msg, (CancelAdmitPatient) msg, messageDateTime, storedFrom); + } } private HospitalVisit processPersonAndVisit(AdtMessage msg, Instant storedFrom, Instant validFrom) throws RequiredDataMissingException { @@ -244,4 +260,21 @@ public void processPendingAdt(CancelPendingDischarge msg, Instant storedFrom) th pendingAdtController.processMsg(visit, msg, validFrom, storedFrom); } + + /** + * Process an update subspeciality message. + *

+ * Updates the sub speciality in the hospital visit table + * @param msg change sub speciality adt message + * @param storedFrom time that emap core started processing the message + * @throws RequiredDataMissingException if the visit number is missing + */ + @Transactional + public void processUpdateSubSpeciality(UpdateSubSpeciality msg, Instant storedFrom) throws RequiredDataMissingException { + Instant validFrom = msg.bestGuessAtValidFrom(); + HospitalVisit visit = processPersonAndVisit(msg, storedFrom, validFrom); + // patientLocationController.processVisitLocation(visit, msg, storedFrom); + pendingAdtController.processHospitalServiceFallback(visit, msg, msg, validFrom, storedFrom); + } + } diff --git a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/repos/PlannedMovementRepository.java b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/repos/PlannedMovementRepository.java index 44af22f6f..ce2a08bb6 100644 --- a/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/repos/PlannedMovementRepository.java +++ b/core/src/main/java/uk/ac/ucl/rits/inform/datasinks/emapstar/repos/PlannedMovementRepository.java @@ -39,6 +39,28 @@ List findMatchingMovementsFromRequest( String eventType, HospitalVisit hospitalVisitId, Location plannedLocation, Instant eventDatetime ); + /** + * Try and find a matching planned movement for the ADT-triggered hospital service fallback + * (e.g. Z99 edit sub speciality, or a real admission/update carrying a hospital service). + *

+ * Always find planned location and hospital visit Id, then: + * - Messages before the the same event date time + * @param hospitalVisitId hospital visit associated with the movement + * @param plannedLocation planned location for the movement + * @param eventDatetime the datetime that event was created + * @return planned movement entities + */ + @Query("from PlannedMovement " + + "where hospitalVisitId = :hospitalVisitId " + + "and (locationId = :plannedLocation or (:plannedLocation is null and locationId is null)) " + + "and (eventDatetime <= :eventDatetime) " + + "order by eventDatetime " + ) + List findMatchingMovementsForHospitalServiceFallback( + HospitalVisit hospitalVisitId, Location plannedLocation, Instant eventDatetime + ); + + /** * Try and find a matching planned movement from a pending adt cancellation message. *

diff --git a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdmitPatientHospitalServiceFallback.java b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdmitPatientHospitalServiceFallback.java new file mode 100644 index 000000000..00d04e827 --- /dev/null +++ b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdmitPatientHospitalServiceFallback.java @@ -0,0 +1,131 @@ +package uk.ac.ucl.rits.inform.datasinks.emapstar.adt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import uk.ac.ucl.rits.inform.datasinks.emapstar.MessageProcessingBase; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.CoreDemographicRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.HospitalVisitRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.MrnRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.PlannedMovementRepository; +import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovement; +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; +import uk.ac.ucl.rits.inform.interchange.adt.AdmitPatient; +import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * A real admission (ADT^A01) always records its own ADMISSION row in planned_movement, + * per #166. Unlike the Z99/A08 + * hospital-service fallback, this never edits a matched row in place - it only links to it + * via matchedMovementId. + */ +class TestAdmitPatientHospitalServiceFallback extends MessageProcessingBase { + @Autowired + private MrnRepository mrnRepository; + @Autowired + private CoreDemographicRepository coreDemographicRepository; + @Autowired + private HospitalVisitRepository hospitalVisitRepository; + @Autowired + private PlannedMovementRepository plannedMovementRepository; + + private AdmitPatient admitPatient; + private PendingTransfer pendingTransfer; + + private static final String VISIT_NUMBER = "123412341234"; + private static final String LOCATION_STRING = "1020100166^SDEC BY02^11 SDEC"; + private static final Instant ADMISSION_EVENT_TIME = Instant.parse("2022-04-22T00:00:00Z"); + + @BeforeEach + void setup() throws IOException { + admitPatient = messageFactory.getAdtMessage("generic/A01.yaml"); + admitPatient.setFullLocationString(InterchangeValue.buildFromHl7(LOCATION_STRING)); + admitPatient.setEventOccurredDateTime(ADMISSION_EVENT_TIME); + + pendingTransfer = messageFactory.getAdtMessage("pending/A15.yaml"); + } + + /** + * Given that no planned movement exists at all (e.g. a direct A&E admission with no + * preceding pending transfer request), an admission still inserts its own ADMISSION row, + * with no matchedMovementId since there was nothing to fulfil. + */ + @Test + void testAdmissionInsertsOwnRowWithNoPriorMovement() throws Exception { + dbOps.processMessage(admitPatient); + + assertEquals(1, mrnRepository.count()); + assertEquals(1, coreDemographicRepository.count()); + assertEquals(1, hospitalVisitRepository.count()); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("ADMISSION", movements.get(0).getEventType()); + assertEquals(ADMISSION_EVENT_TIME, movements.get(0).getEventDatetime()); + assertNull(movements.get(0).getMatchedMovementId()); + } + + /** + * A prior pending transfer creates a TRANSFER row. An admission with the SAME hospital + * service still inserts its own ADMISSION row (not a no-op), linking back to the TRANSFER + * row via matchedMovementId - the TRANSFER row itself is left untouched. + */ + @Test + void testAdmissionInsertsOwnRowWhenHospitalServicesAreTheSame() throws Exception { + dbOps.processMessage(pendingTransfer); + admitPatient.setHospitalService(pendingTransfer.getHospitalService()); + dbOps.processMessage(admitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(2, movements.size()); + + PlannedMovement transferRow = movements.get(0); + assertEquals("TRANSFER", transferRow.getEventType()); + + PlannedMovement admissionRow = movements.get(1); + assertEquals("ADMISSION", admissionRow.getEventType()); + assertEquals(transferRow.getPlannedMovementId(), admissionRow.getMatchedMovementId()); + assertEquals(admitPatient.getHospitalService().get(), admissionRow.getHospitalService()); + } + + /** + * Same as above, but with a differing hospital service - still just one new ADMISSION row, + * not a separate EDIT/HOSPITAL_SERVICE_CHANGE row. The TRANSFER row is still untouched. + */ + @Test + void testAdmissionInsertsOwnRowWhenHospitalServicesDiffer() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(admitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(2, movements.size()); + + PlannedMovement transferRow = movements.get(0); + assertEquals("TRANSFER", transferRow.getEventType()); + + PlannedMovement admissionRow = movements.get(1); + assertEquals("ADMISSION", admissionRow.getEventType()); + assertEquals(transferRow.getPlannedMovementId(), admissionRow.getMatchedMovementId()); + assertEquals(admitPatient.getHospitalService().get(), admissionRow.getHospitalService()); + } + + /** + * Reprocessing the identical admission message must not duplicate the ADMISSION row. + */ + @Test + void testReprocessingSameAdmissionIsIdempotent() throws Exception { + dbOps.processMessage(admitPatient); + dbOps.processMessage(admitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("ADMISSION", movements.get(0).getEventType()); + } +} diff --git a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdtProcessingVisit.java b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdtProcessingVisit.java index 50157c238..0ffee6c96 100644 --- a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdtProcessingVisit.java +++ b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestAdtProcessingVisit.java @@ -60,6 +60,7 @@ public void testCreateNewAdmit() throws Exception { HospitalVisit visit = visits.get(0); assertNotNull(visit.getAdmissionDatetime()); + assertNotNull(visit.getAdmissionType()); assertNull(visit.getPresentationDatetime()); // no audit log should be added assertTrue(getAllAuditHospitalVisits().isEmpty()); diff --git a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestCancelAdmitPatientHospitalServiceFallback.java b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestCancelAdmitPatientHospitalServiceFallback.java new file mode 100644 index 000000000..96cc39beb --- /dev/null +++ b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestCancelAdmitPatientHospitalServiceFallback.java @@ -0,0 +1,119 @@ +package uk.ac.ucl.rits.inform.datasinks.emapstar.adt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import uk.ac.ucl.rits.inform.datasinks.emapstar.MessageProcessingBase; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.PlannedMovementRepository; +import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovement; +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; +import uk.ac.ucl.rits.inform.interchange.adt.AdmitPatient; +import uk.ac.ucl.rits.inform.interchange.adt.CancelAdmitPatient; +import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Cancelling an admission (ADT^A11) should cancel the matching ADMISSION row created by + * {@link uk.ac.ucl.rits.inform.datasinks.emapstar.controllers.PendingAdtController#processAdmission}, + * but must never touch a planned_movement row that originated from a real pending transfer/discharge + * request. + */ +class TestCancelAdmitPatientHospitalServiceFallback extends MessageProcessingBase { + @Autowired + private PlannedMovementRepository plannedMovementRepository; + + private PendingTransfer pendingTransfer; + private AdmitPatient admitPatient; + private CancelAdmitPatient cancelAdmitPatient; + + private static final String VISIT_NUMBER = "123412341234"; + private static final String LOCATION_STRING = "1020100166^SDEC BY02^11 SDEC"; + private static final Instant ADMISSION_EVENT_TIME = Instant.parse("2022-04-22T00:00:00Z"); + + @BeforeEach + void setup() throws IOException { + pendingTransfer = messageFactory.getAdtMessage("pending/A15.yaml"); + + admitPatient = messageFactory.getAdtMessage("generic/A01.yaml"); + admitPatient.setFullLocationString(InterchangeValue.buildFromHl7(LOCATION_STRING)); + admitPatient.setEventOccurredDateTime(ADMISSION_EVENT_TIME); + + cancelAdmitPatient = messageFactory.getAdtMessage("generic/A11.yaml"); + cancelAdmitPatient.setFullLocationString(InterchangeValue.buildFromHl7(LOCATION_STRING)); + cancelAdmitPatient.setCancelledDateTime(ADMISSION_EVENT_TIME); + } + + /** + * If no planned movement exists at all, cancelling the admission is a no-op. + */ + @Test + void testNoPlannedMovementsIsNoOp() throws Exception { + dbOps.processMessage(cancelAdmitPatient); + + assertEquals(0, plannedMovementRepository.count()); + } + + /** + * A pending transfer creates a TRANSFER row, then an admission creates its own ADMISSION row. + * Cancelling that admission should cancel only the ADMISSION row. + */ + @Test + void testCancelsOnlyTheAdmissionRow() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(admitPatient); + dbOps.processMessage(cancelAdmitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(2, movements.size()); + + PlannedMovement transferRow = movements.get(0); + assertEquals("TRANSFER", transferRow.getEventType()); + assertFalse(transferRow.getCancelled()); + assertNull(transferRow.getCancelledDatetime()); + + PlannedMovement admissionRow = movements.get(1); + assertEquals("ADMISSION", admissionRow.getEventType()); + assertTrue(admissionRow.getCancelled()); + assertEquals(ADMISSION_EVENT_TIME, admissionRow.getCancelledDatetime()); + } + + /** + * If no admission was ever processed, cancelling the admission must not touch the pre-existing + * TRANSFER row, even though it's the most recent match at that location. + */ + @Test + void testDoesNotCancelTransferRow() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(cancelAdmitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("TRANSFER", movements.get(0).getEventType()); + assertFalse(movements.get(0).getCancelled()); + } + + /** + * Cancelling the same admission twice should be idempotent: no error, and the cancelledDatetime + * set by the first call is not overwritten by the second. + */ + @Test + void testCancellingTwiceIsIdempotent() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(admitPatient); + dbOps.processMessage(cancelAdmitPatient); + dbOps.processMessage(cancelAdmitPatient); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + PlannedMovement admissionRow = movements.get(1); + assertTrue(admissionRow.getCancelled()); + assertEquals(ADMISSION_EVENT_TIME, admissionRow.getCancelledDatetime()); + } +} diff --git a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdatePatientInfoHospitalServiceFallback.java b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdatePatientInfoHospitalServiceFallback.java new file mode 100644 index 000000000..f4626660a --- /dev/null +++ b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdatePatientInfoHospitalServiceFallback.java @@ -0,0 +1,155 @@ +package uk.ac.ucl.rits.inform.datasinks.emapstar.adt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import uk.ac.ucl.rits.inform.datasinks.emapstar.MessageProcessingBase; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.CoreDemographicRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.HospitalVisitRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.MrnRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.PlannedMovementRepository; +import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovement; +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; +import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; +import uk.ac.ucl.rits.inform.interchange.adt.UpdatePatientInfo; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * A patient info update (ADT^A08) should feed the same ADT-triggered hospital service fallback as Z99, + * per #166. Also covers the issue's explicit + * deduplication requirement: repeated A08s carrying the same, already-recorded hospital service must not + * flood planned_movement with duplicate rows. + */ +class TestUpdatePatientInfoHospitalServiceFallback extends MessageProcessingBase { + @Autowired + private MrnRepository mrnRepository; + @Autowired + private CoreDemographicRepository coreDemographicRepository; + @Autowired + private HospitalVisitRepository hospitalVisitRepository; + @Autowired + private PlannedMovementRepository plannedMovementRepository; + + private UpdatePatientInfo updatePatientInfo; + private PendingTransfer pendingTransfer; + private PendingTransfer pendingTransferLater; + private PendingTransfer pendingTransferAfter; + + private static final String VISIT_NUMBER = "123412341234"; + private static final String LOCATION_STRING = "1020100166^SDEC BY02^11 SDEC"; + private static final Instant UPDATE_EVENT_TIME = Instant.parse("2022-04-22T00:00:00Z"); + + private PlannedMovement getPlannedMovementOrThrow(String visitNumber, String location) { + return plannedMovementRepository + .findByHospitalVisitIdEncounterAndLocationIdLocationString(visitNumber, location).orElseThrow(); + } + + @BeforeEach + void setup() throws IOException { + updatePatientInfo = messageFactory.getAdtMessage("generic/A08_v1.yaml"); + updatePatientInfo.setFullLocationString(InterchangeValue.buildFromHl7(LOCATION_STRING)); + updatePatientInfo.setEventOccurredDateTime(UPDATE_EVENT_TIME); + + pendingTransfer = messageFactory.getAdtMessage("pending/A15.yaml"); + pendingTransferLater = messageFactory.getAdtMessage("pending/A15.yaml"); + pendingTransferAfter = messageFactory.getAdtMessage("pending/A15.yaml"); + + Instant laterTime = pendingTransferLater.getEventOccurredDateTime().plus(1, ChronoUnit.MINUTES); + pendingTransferLater.setEventOccurredDateTime(laterTime); + + Instant afterTime = pendingTransferAfter.getEventOccurredDateTime().plus(1, ChronoUnit.HOURS); + pendingTransferAfter.setEventOccurredDateTime(afterTime); + } + + /** + * Given that no entities exist in the database + * When an update is processed + * Mrn and core demographics entities should be created (UpdatePatientInfo does not create its own + * hospital visit), and no planned movement fallback row should be created as there is nothing to + * match against. + */ + @Test + void testUpdateCreatesOtherEntitiesNoFallback() throws Exception { + dbOps.processMessage(updatePatientInfo); + + assertEquals(1, mrnRepository.count()); + assertEquals(1, coreDemographicRepository.count()); + assertEquals(0, hospitalVisitRepository.count()); + + assertThrows(NoSuchElementException.class, () -> getPlannedMovementOrThrow(VISIT_NUMBER, LOCATION_STRING)); + } + + /** + * If more than one pending transfer exists find the most recent one and if the update + * has a different hospital service insert the edit into the planned movement table. + */ + @Test + void testUpdateInsertsEditIfHospitalServicesAreDifferent() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(pendingTransferLater); + dbOps.processMessage(pendingTransferAfter); + dbOps.processMessage(updatePatientInfo); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(4, movements.size()); + assertEquals("EDIT/HOSPITAL_SERVICE_CHANGE", movements.get(3).getEventType()); + assertEquals(Instant.parse("2022-04-22T00:00:00Z"), movements.get(3).getEventDatetime()); + } + + /** + * Find the most recent matching planned movement, but don't add to the table + * if the update has the same hospital service as it. + */ + @Test + void testUpdateDoesNotInsertIfHospitalServicesAreTheSame() throws Exception { + dbOps.processMessage(pendingTransfer); + updatePatientInfo.setHospitalService(pendingTransfer.getHospitalService()); + dbOps.processMessage(updatePatientInfo); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("TRANSFER", movements.get(0).getEventType()); + } + + /** + * If pending transfers only exist after the update event, don't add the fallback edit. + */ + @Test + void testUpdateDoesNotInsertIfTransfersAreAfter() throws Exception { + dbOps.processMessage(pendingTransferAfter); + dbOps.processMessage(updatePatientInfo); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("TRANSFER", movements.get(0).getEventType()); + } + + /** + * Real A08 traffic reconfirms the same subspeciality repeatedly for hours after admission. + * A second A08 carrying the same hospital service as the fallback row just inserted by the + * first one must not insert a duplicate row. + */ + @Test + void testRepeatedUpdatesWithSameHospitalServiceAreDeduplicated() throws Exception { + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(updatePatientInfo); + + UpdatePatientInfo secondUpdate = messageFactory.getAdtMessage("generic/A08_v1.yaml"); + secondUpdate.setFullLocationString(InterchangeValue.buildFromHl7(LOCATION_STRING)); + secondUpdate.setEventOccurredDateTime(UPDATE_EVENT_TIME.plus(1, ChronoUnit.HOURS)); + secondUpdate.setHospitalService(updatePatientInfo.getHospitalService()); + dbOps.processMessage(secondUpdate); + + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(2, movements.size()); + assertEquals("EDIT/HOSPITAL_SERVICE_CHANGE", movements.get(1).getEventType()); + } +} diff --git a/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdateSubSpeciality.java b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdateSubSpeciality.java new file mode 100644 index 000000000..ddda5fbf7 --- /dev/null +++ b/core/src/test/java/uk/ac/ucl/rits/inform/datasinks/emapstar/adt/TestUpdateSubSpeciality.java @@ -0,0 +1,139 @@ +package uk.ac.ucl.rits.inform.datasinks.emapstar.adt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import uk.ac.ucl.rits.inform.datasinks.emapstar.MessageProcessingBase; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.CoreDemographicRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.HospitalVisitRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.MrnRepository; +import uk.ac.ucl.rits.inform.datasinks.emapstar.repos.PlannedMovementRepository; +import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovement; +import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; +import uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.NoSuchElementException; + + +import static org.junit.jupiter.api.Assertions.*; + +class TestUpdateSubSpeciality extends MessageProcessingBase { + private static final Logger logger = LoggerFactory.getLogger(TestPendingAdt.class); + @Autowired + private MrnRepository mrnRepository; + @Autowired + private CoreDemographicRepository coreDemographicRepository; + @Autowired + private HospitalVisitRepository hospitalVisitRepository; + @Autowired + private PlannedMovementRepository plannedMovementRepository; + + // end to end messages + private UpdateSubSpeciality updateSubSpeciality; + private PendingTransfer pendingTransfer; + private PendingTransfer pendingTransferLater; + private PendingTransfer pendingTransferAfter; + + private static final String VISIT_NUMBER = "123412341234"; + private static final String LOCATION_STRING = "1020100166^SDEC BY02^11 SDEC"; + + + private PlannedMovement getPlannedMovementOrThrow(String visitNumber, String location) { + return plannedMovementRepository + .findByHospitalVisitIdEncounterAndLocationIdLocationString(visitNumber, location).orElseThrow(); + } + + @BeforeEach + void setup() throws IOException { + updateSubSpeciality = messageFactory.getAdtMessage("Location/Moves/09_Z99.yaml"); + + pendingTransfer = messageFactory.getAdtMessage("pending/A15.yaml"); + pendingTransferLater = messageFactory.getAdtMessage("pending/A15.yaml"); + pendingTransferAfter = messageFactory.getAdtMessage("pending/A15.yaml"); + + Instant laterTime = pendingTransferLater.getEventOccurredDateTime().plus(1, ChronoUnit.MINUTES); + pendingTransferLater.setEventOccurredDateTime(laterTime); + + Instant afterTime = pendingTransferAfter.getEventOccurredDateTime().plus(1, ChronoUnit.HOURS); + pendingTransferAfter.setEventOccurredDateTime(afterTime); + } + + /** + * Given that no entities exist in the database + * When a Z99 Message is created + * Mrn, core demographics and hospital visit entities should be created. + * A planned movement should not be created as there are no matching planned moves in the + * planned movement table + * @throws Exception shouldn't happen + */ + @Test + void testUpdateCreatesOtherEntities() throws Exception { + dbOps.processMessage(updateSubSpeciality); + + assertEquals(1, mrnRepository.count()); + assertEquals(1, coreDemographicRepository.count()); + assertEquals(1, hospitalVisitRepository.count()); + + assertThrows(NoSuchElementException.class, () -> getPlannedMovementOrThrow(VISIT_NUMBER, LOCATION_STRING)); + } + + /** + * If more than one pending transfer exists find the most recent one and if it + * has a different hospital service insert the edit into the planned movement table. + */ + @Test + void testEditMessageInsertedIfHospitalServicesAreDifferent() throws Exception { + + dbOps.processMessage(pendingTransfer); + dbOps.processMessage(pendingTransferLater); + dbOps.processMessage(pendingTransferAfter); + dbOps.processMessage(updateSubSpeciality); + + // and entry should have been added to the planned movement table with the correct matched planned movement id + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(4, movements.size()); + assertEquals("EDIT/HOSPITAL_SERVICE_CHANGE", movements.get(3).getEventType()); + assertEquals(7, movements.get(3).getMatchedMovementId()); + assertEquals(Instant.parse("2022-04-22T00:00:00Z"), movements.get(3).getEventDatetime()); + } + + /** + * Find the most recent one, but don't add to table if it has the same hospital service as the edit message + */ + @Test + void testEditMessageNotInsertedIfHospitalServicesAreTheSame() throws Exception { + dbOps.processMessage(pendingTransfer); + updateSubSpeciality.setHospitalService(pendingTransfer.getHospitalService()); + dbOps.processMessage(updateSubSpeciality); + + // and entry should have been added to the planned movement table with the correct matched planned movement id + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("TRANSFER", movements.get(0).getEventType()); + } + + /** + * If pending transfers only exist after the edit event, don't add edit message. + */ + @Test + void testEditMessageNotInsertedIfTransfersAreAfter() throws Exception { + dbOps.processMessage(pendingTransferAfter); + dbOps.processMessage(updateSubSpeciality); + + assertEquals(1, mrnRepository.count()); + assertEquals(1, coreDemographicRepository.count()); + assertEquals(1, hospitalVisitRepository.count()); + + // one entry should have been added to the planned movement table with the correct matched planned movement id + List movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER); + assertEquals(1, movements.size()); + assertEquals("TRANSFER", movements.get(0).getEventType()); + //assertThrows(NoSuchElementException.class, () -> getPlannedMovementOrThrow(VISIT_NUMBER, LOCATION_STRING)); + } +} diff --git a/emap-checker.xml b/emap-checker.xml index 1e27d302e..a526209c9 100644 --- a/emap-checker.xml +++ b/emap-checker.xml @@ -107,8 +107,10 @@ - - + + + + diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/EmapOperationMessageProcessor.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/EmapOperationMessageProcessor.java index 539fd840d..b44f5b842 100644 --- a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/EmapOperationMessageProcessor.java +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/EmapOperationMessageProcessor.java @@ -10,6 +10,7 @@ import uk.ac.ucl.rits.inform.interchange.adt.PendingDischarge; import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer; import uk.ac.ucl.rits.inform.interchange.adt.SwapLocations; +import uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality; import uk.ac.ucl.rits.inform.interchange.form.FormMetadataMsg; import uk.ac.ucl.rits.inform.interchange.form.FormMsg; import uk.ac.ucl.rits.inform.interchange.form.FormQuestionMetadataMsg; @@ -191,4 +192,11 @@ public interface EmapOperationMessageProcessor { * @throws EmapOperationMessageProcessingException if message cannot be processed */ void processMessage(WaveformMessage msg) throws EmapOperationMessageProcessingException; + + /** + * @param msg the UpdateSubSpeciality message to process + * @throws EmapOperationMessageProcessingException if message cannot be processed + */ + void processMessage(UpdateSubSpeciality msg) throws EmapOperationMessageProcessingException; + } diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdmitPatient.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdmitPatient.java index 832d82cd6..2b8b46ddb 100644 --- a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdmitPatient.java +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdmitPatient.java @@ -16,8 +16,10 @@ @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class AdmitPatient extends AdtMessage implements AdmissionDateTime { +public class AdmitPatient extends AdtMessage implements AdmissionDateTime, HospitalService { private InterchangeValue admissionDateTime = InterchangeValue.unknown(); + private InterchangeValue admissionType = InterchangeValue.unknown(); + private InterchangeValue hospitalService = InterchangeValue.unknown(); @Override diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdtMessage.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdtMessage.java index 79835a44f..1dff53726 100644 --- a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdtMessage.java +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/AdtMessage.java @@ -27,6 +27,7 @@ public abstract class AdtMessage extends EmapOperationMessage { private String nhsNumber; private String visitNumber; private InterchangeValue modeOfArrival = InterchangeValue.unknown(); + private InterchangeValue ethnicGroup = InterchangeValue.unknown(); private InterchangeValue fullLocationString = InterchangeValue.unknown(); private InterchangeValue previousLocationString = InterchangeValue.unknown(); @@ -43,6 +44,9 @@ public abstract class AdtMessage extends EmapOperationMessage { private InterchangeValue patientSex = InterchangeValue.unknown(); private InterchangeValue patientTitle = InterchangeValue.unknown(); private InterchangeValue patientZipOrPostalCode = InterchangeValue.unknown(); + private InterchangeValue matchedMovementId = InterchangeValue.unknown(); + + private InterchangeValue admissionType = InterchangeValue.unknown(); /** diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/HospitalService.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/HospitalService.java new file mode 100644 index 000000000..fc5233600 --- /dev/null +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/HospitalService.java @@ -0,0 +1,16 @@ +package uk.ac.ucl.rits.inform.interchange.adt; + +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; + + +/** + * Ensuring admission date time is used in a class. + *

+ * Used in specific circumstances because where the sub speciality is changed without a movement + * via a ADT Z99 message + */ +public interface HospitalService { + InterchangeValue getHospitalService(); + + void setHospitalService(InterchangeValue hospitalService); +} diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdatePatientInfo.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdatePatientInfo.java index 9543c2e2a..af4e7213d 100644 --- a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdatePatientInfo.java +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdatePatientInfo.java @@ -5,6 +5,7 @@ import lombok.ToString; import uk.ac.ucl.rits.inform.interchange.EmapOperationMessageProcessingException; import uk.ac.ucl.rits.inform.interchange.EmapOperationMessageProcessor; +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; /** * Change patient demographics, can contain visit information. @@ -13,7 +14,9 @@ @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class UpdatePatientInfo extends AdtMessage { +public class UpdatePatientInfo extends AdtMessage implements HospitalService { + private InterchangeValue hospitalService = InterchangeValue.unknown(); + @Override public void processMessage(EmapOperationMessageProcessor processor) throws EmapOperationMessageProcessingException { processor.processMessage(this); diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdateSubSpeciality.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdateSubSpeciality.java new file mode 100644 index 000000000..194b86b99 --- /dev/null +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/adt/UpdateSubSpeciality.java @@ -0,0 +1,27 @@ +package uk.ac.ucl.rits.inform.interchange.adt; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import uk.ac.ucl.rits.inform.interchange.EmapOperationMessageProcessingException; +import uk.ac.ucl.rits.inform.interchange.EmapOperationMessageProcessor; +import uk.ac.ucl.rits.inform.interchange.InterchangeValue; + + +/** + * Change the Sub Speciality. + * HL7 messages: Z99 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class UpdateSubSpeciality extends AdtMessage implements HospitalService { + private InterchangeValue hospitalService = InterchangeValue.unknown(); + private InterchangeValue matchedMovementId = InterchangeValue.unknown(); + + @Override + public void processMessage(EmapOperationMessageProcessor processor) throws EmapOperationMessageProcessingException { + processor.processMessage(this); + } + +} diff --git a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/location/DepartmentMetadata.java b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/location/DepartmentMetadata.java index ad6fb9c72..7a06350ca 100644 --- a/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/location/DepartmentMetadata.java +++ b/emap-interchange/src/main/java/uk/ac/ucl/rits/inform/interchange/location/DepartmentMetadata.java @@ -42,6 +42,15 @@ public class DepartmentMetadata extends EmapOperationMessage implements MinimalD private String departmentSpeciality; private Instant specialityUpdate; private String previousDepartmentSpeciality; + + /** + * Variables added to make location processing more robust. + */ + private String departmentType; + private Boolean isWardOrFlowArea; + private Boolean isCoreInpatientArea; + private String siteLocation; + /** * Not used in processing, but useful to debugging ordering. */ diff --git a/emap-interchange/src/test/resources/AdtMessages/DoubleA01WithA13/A08.yaml b/emap-interchange/src/test/resources/AdtMessages/DoubleA01WithA13/A08.yaml index 38de8c119..b34ed461a 100644 --- a/emap-interchange/src/test/resources/AdtMessages/DoubleA01WithA13/A08.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/DoubleA01WithA13/A08.yaml @@ -36,3 +36,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/01_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/01_A01.yaml index 51795bd72..376f9d0dd 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/01_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/01_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/03_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/03_A01.yaml index 6e1ca3be8..dd0f0a707 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/03_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/03_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/05_A03.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/05_A03.yaml index df2d72247..0ba608258 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/05_A03.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelAdmit/05_A03.yaml @@ -13,6 +13,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T13:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/01_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/01_A01.yaml index 67dd481ca..79e641a53 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/01_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/01_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/03_A03.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/03_A03.yaml index 4dcc5ae8c..422ee1854 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/03_A03.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/03_A03.yaml @@ -13,6 +13,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T11:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/05_A03.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/05_A03.yaml index 12eba4d40..4fd265f81 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/05_A03.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelDischarge/05_A03.yaml @@ -13,6 +13,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T11:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/CancelTransfer/01_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/CancelTransfer/01_A01.yaml index 67dd481ca..79e641a53 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/CancelTransfer/01_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/CancelTransfer/01_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/02_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/02_A01.yaml index 2556795b5..3798606a8 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/02_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/02_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/04_A03.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/04_A03.yaml index 23c76ea56..f562e0e2c 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/04_A03.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/DuplicateSimple/04_A03.yaml @@ -13,6 +13,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T12:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/Moves/02_A01.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/02_A01.yaml index 67dd481ca..79e641a53 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/Moves/02_A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/02_A01.yaml @@ -38,3 +38,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/Moves/08_A03.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/08_A03.yaml index bfddb8b38..2c68f418b 100644 --- a/emap-interchange/src/test/resources/AdtMessages/Location/Moves/08_A03.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/08_A03.yaml @@ -13,6 +13,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T10:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/Location/Moves/09_Z99.yaml b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/09_Z99.yaml new file mode 100644 index 000000000..02e2bfb0b --- /dev/null +++ b/emap-interchange/src/test/resources/AdtMessages/Location/Moves/09_Z99.yaml @@ -0,0 +1,36 @@ +"@class": "uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality" +recordedDateTime: "2013-02-11T18:00:52Z" +eventOccurredDateTime: "2022-04-22T00:00:00Z" +eventReasonCode: "ADT_EVENT" +visitNumber: "123412341234" +nhsNumber: "9999999999" +sourceSystem: "EPIC" +mrn: "40800000" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Anaes" +fullLocationString: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "1020100166^SDEC BY02^11 SDEC" +patientClass: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "INPATIENT" +patientIsAlive: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: true +patientFamilyName: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "ORANGE" +patientGivenName: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "BLOOD" +patientSex: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "F" diff --git a/emap-interchange/src/test/resources/AdtMessages/generic/A01.yaml b/emap-interchange/src/test/resources/AdtMessages/generic/A01.yaml index 88da640e3..4464b3d92 100644 --- a/emap-interchange/src/test/resources/AdtMessages/generic/A01.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/generic/A01.yaml @@ -18,6 +18,10 @@ fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" value: "T42E^T42E BY02^BY02-17" status: "SAVE" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Matern Ante" + status: "SAVE" patientBirthDate: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" @@ -50,3 +54,7 @@ ethnicGroup: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "Refused to Give" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/generic/A02.yaml b/emap-interchange/src/test/resources/AdtMessages/generic/A02.yaml index 43b1b8af5..6c6198edf 100644 --- a/emap-interchange/src/test/resources/AdtMessages/generic/A02.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/generic/A02.yaml @@ -10,6 +10,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2012-10-17T11:24:53Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Emerg Other" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/generic/A04.yaml b/emap-interchange/src/test/resources/AdtMessages/generic/A04.yaml index 0b406bc03..95495d3e3 100644 --- a/emap-interchange/src/test/resources/AdtMessages/generic/A04.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/generic/A04.yaml @@ -10,6 +10,10 @@ presentationDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2013-02-11T10:00:52Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Matern Ante" fullLocationString: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/generic/A06.yaml b/emap-interchange/src/test/resources/AdtMessages/generic/A06.yaml index 2f13fc145..48a25e3d4 100644 --- a/emap-interchange/src/test/resources/AdtMessages/generic/A06.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/generic/A06.yaml @@ -10,6 +10,10 @@ admissionDateTime: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "2020-04-01T18:30:00Z" +admissionType: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + status: "SAVE" + value: "Emergency" modeOfArrival: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" diff --git a/emap-interchange/src/test/resources/AdtMessages/generic/A08_v1.yaml b/emap-interchange/src/test/resources/AdtMessages/generic/A08_v1.yaml index 09e1d7365..124d9b744 100644 --- a/emap-interchange/src/test/resources/AdtMessages/generic/A08_v1.yaml +++ b/emap-interchange/src/test/resources/AdtMessages/generic/A08_v1.yaml @@ -37,3 +37,7 @@ patientSex: "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" status: "SAVE" value: "F" +hospitalService: + "@class": "uk.ac.ucl.rits.inform.interchange.InterchangeValue" + value: "Anaes" + status: "SAVE" diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/acun_dept.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/acun_dept.yaml index ff889578d..b0e854559 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/acun_dept.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/acun_dept.yaml @@ -8,4 +8,8 @@ departmentRecordStatus: "ACTIVE" previousDepartmentSpeciality: "Dental - Oral Medicine" departmentContactDate: "2012-02-09T00:00:00Z" specialityUpdate: "2022-02-09T00:00:20Z" -combinedUpdate: "2022-02-09T00:00:20Z" \ No newline at end of file +departmentType: "2" +isWardOrFlowArea: true +isCoreInpatientArea: true +siteLocation: "EMH" +combinedUpdate: "2022-02-09T00:00:20Z" diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/dental_department_only.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/dental_department_only.yaml index f74c366b6..35c7eb22a 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/dental_department_only.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/dental_department_only.yaml @@ -8,4 +8,9 @@ departmentRecordStatus: "ACTIVE" departmentContactDate: "2018-08-22T00:00:00Z" departmentSpeciality: "Dental - Oral Medicine" specialityUpdate: "2020-09-22T13:47:56Z" +departmentType: "2" +isWardOrFlowArea: true +isCoreInpatientArea: true +siteLocation: "UCLH" combinedUpdate: "2020-09-22T13:47:56Z" + diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/medsurg_active_pool_bed.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/medsurg_active_pool_bed.yaml index 41cf2670a..99c5cbbfb 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/medsurg_active_pool_bed.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/medsurg_active_pool_bed.yaml @@ -7,5 +7,9 @@ departmentId: "1" departmentSpeciality: null departmentRecordStatus: "DELETED_AND_HIDDEN" departmentContactDate: "2005-12-08T00:00:00Z" +departmentType: "2" +isWardOrFlowArea: true +isCoreInpatientArea: true +siteLocation: "UCLH" combinedUpdate: "2021-04-23T09:00:00Z" diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/no_department_history.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/no_department_history.yaml index bbe538032..d7b8c0436 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/no_department_history.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/no_department_history.yaml @@ -7,4 +7,8 @@ departmentId: "1020100002" departmentSpeciality: null departmentRecordStatus: "ACTIVE" departmentContactDate: "2022-12-08T00:00:00Z" +departmentType: "12" +isWardOrFlowArea: true +isCoreInpatientArea: false +siteLocation: "UCLH" combinedUpdate: "2022-12-08T00:00:00Z" diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_1.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_1.yaml index b396e601f..a7f1faa24 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_1.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_1.yaml @@ -9,4 +9,9 @@ departmentRecordStatus: "ACTIVE" departmentContactDate: "2021-08-25T23:00:00Z" # missing previous department will be null specialityUpdate: "2021-08-26T11:28:51Z" -combinedUpdate: "2021-08-26T11:28:51Z" \ No newline at end of file +departmentType: "12" +isWardOrFlowArea: true +isCoreInpatientArea: false +siteLocation: "UCLH" +combinedUpdate: "2021-08-26T11:28:51Z" + diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_2.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_2.yaml index 7872eae2e..351787ec3 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_2.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_2.yaml @@ -9,4 +9,8 @@ previousDepartmentSpeciality: "Paediatric - Respiratory Medicine" departmentRecordStatus: "ACTIVE" departmentContactDate: "2021-08-25T23:00:00Z" specialityUpdate: "2021-09-06T06:42:26Z" -combinedUpdate: "2021-09-06T06:42:26Z" \ No newline at end of file +departmentType: "12" +isWardOrFlowArea: true +isCoreInpatientArea: false +siteLocation: "UCLH" +combinedUpdate: "2021-09-06T06:42:26Z" diff --git a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_3.yaml b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_3.yaml index 1834085f7..0cd332acf 100644 --- a/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_3.yaml +++ b/emap-interchange/src/test/resources/DepartmentMetadata/local-only/paediatric_department_only_3.yaml @@ -9,4 +9,8 @@ previousDepartmentSpeciality: "Paediatric - Infectious Diseases" departmentRecordStatus: "ACTIVE" departmentContactDate: "2021-08-25T23:00:00Z" specialityUpdate: "2021-10-08T23:00:20Z" -combinedUpdate: "2021-10-08T23:00:20Z" \ No newline at end of file +departmentType: "12" +isWardOrFlowArea: true +isCoreInpatientArea: false +siteLocation: "UCLH" +combinedUpdate: "2021-10-08T23:00:20Z" diff --git a/emap-star/emap-star/pom.xml b/emap-star/emap-star/pom.xml index a84891117..5936a7375 100644 --- a/emap-star/emap-star/pom.xml +++ b/emap-star/emap-star/pom.xml @@ -25,7 +25,7 @@ jdt_apt 2.13.1 - 1.18.22 + 1.18.30 diff --git a/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/identity/HospitalVisit.java b/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/identity/HospitalVisit.java index 38c79a24d..34beccd0e 100644 --- a/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/identity/HospitalVisit.java +++ b/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/identity/HospitalVisit.java @@ -85,6 +85,11 @@ public class HospitalVisit extends TemporalCore { * \brief Current speciality of the department. */ private String speciality; + + /** + * \brief Report Group Nine (departmentType) value associated with this department. + */ + private String departmentType; + + /** + * \brief Predicate determining whether the Department is a ward or flow area. + */ + private Boolean isWardOrFlowArea; + + /** + * \brief Predicate determining whether the Department is a core inpatient area. + */ + private Boolean isCoreInpatientArea; + + /** + * \brief hospital location where this department is situated. + */ + private String siteLocation; + /** * Create valid department state. * @param department parent department @@ -61,11 +82,21 @@ public class DepartmentState extends AuditCore { * @param validFrom time that the message was valid from * @param storedFrom time that emap core stared processing the message * @param speciality name of the current speciality of this department + * @param departmentType Report Group Nine value associated with this department + * @param isWardOrFlowArea is this department a ward or flow area + * @param isCoreInpatientArea is this department a core inpatient area + * @param siteLocation hospital location where this department is situated. */ - public DepartmentState(Department department, String status, String speciality, Instant validFrom, Instant storedFrom) { + public DepartmentState(Department department, String status, + String speciality, String departmentType, Boolean isWardOrFlowArea, + Boolean isCoreInpatientArea, String siteLocation, Instant validFrom, Instant storedFrom) { departmentId = department; this.status = status; this.speciality = speciality; + this.departmentType = departmentType; + this.isWardOrFlowArea = isWardOrFlowArea; + this.isCoreInpatientArea = isCoreInpatientArea; + this.siteLocation = siteLocation; setValidFrom(validFrom); setStoredFrom(storedFrom); } @@ -78,6 +109,10 @@ private DepartmentState(DepartmentState other) { departmentId = other.departmentId; status = other.status; speciality = other.speciality; + departmentType = other.departmentType; + isWardOrFlowArea = other.isWardOrFlowArea; + isCoreInpatientArea = other.isCoreInpatientArea; + siteLocation = other.siteLocation; } @Override diff --git a/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/movement/PlannedMovement.java b/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/movement/PlannedMovement.java index 6ab3a91d0..fa9ca9ca0 100644 --- a/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/movement/PlannedMovement.java +++ b/emap-star/emap-star/src/main/java/uk/ac/ucl/rits/inform/informdb/movement/PlannedMovement.java @@ -63,10 +63,15 @@ public class PlannedMovement extends TemporalCore getReferringDoctors() throws HL7Exception { * @throws HL7Exception if HAPI does */ default String getHospitalService() throws HL7Exception { + if (!pv1SegmentExists()) { + return null; + } return getPV1().getHospitalService().getValue(); } diff --git a/hl7-reader/src/main/java/uk/ac/ucl/rits/inform/datasources/ids/labs/LabOrderBuilder.java b/hl7-reader/src/main/java/uk/ac/ucl/rits/inform/datasources/ids/labs/LabOrderBuilder.java index 87175ec79..7dea0d15a 100644 --- a/hl7-reader/src/main/java/uk/ac/ucl/rits/inform/datasources/ids/labs/LabOrderBuilder.java +++ b/hl7-reader/src/main/java/uk/ac/ucl/rits/inform/datasources/ids/labs/LabOrderBuilder.java @@ -11,9 +11,9 @@ import ca.uhn.hl7v2.model.v26.segment.NTE; import ca.uhn.hl7v2.model.v26.segment.OBR; import ca.uhn.hl7v2.model.v26.segment.ORC; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import uk.ac.ucl.rits.inform.datasources.ids.HL7Utils; import uk.ac.ucl.rits.inform.datasources.ids.exceptions.Hl7InconsistencyException; import uk.ac.ucl.rits.inform.datasources.ids.hl7.parser.NotesParser; import uk.ac.ucl.rits.inform.datasources.ids.hl7.parser.PatientInfoHl7; @@ -23,11 +23,11 @@ import java.time.Instant; import java.util.Collection; -import java.util.List; import java.util.Set; import java.util.StringJoiner; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import static uk.ac.ucl.rits.inform.datasources.ids.HL7Utils.interpretLocalTime; @@ -39,6 +39,7 @@ abstract class LabOrderBuilder { private String epicCareOrderNumberObr; private final OrderCodingSystem codingSystem; + @Getter private final LabOrderMsg msg = new LabOrderMsg(); /** @@ -50,13 +51,6 @@ abstract class LabOrderBuilder { this.codingSystem = codingSystem; } - /** - * @return Lab Order Msg. - */ - public LabOrderMsg getMsg() { - return msg; - } - /** * @return order coding system. */ @@ -87,6 +81,9 @@ String getEpicCareOrderNumberObr() { */ void populateOrderInformation(ORC orc, OBR obr) throws DataTypeException, Hl7InconsistencyException { // NA/NW/CA/CR/OC/XO + if (orc.getOrc1_OrderControl().getValue() == null) { + throw new Hl7InconsistencyException("ORC 1 Order Control value is null"); + } msg.setOrderControlId(orc.getOrc1_OrderControl().getValue()); epicCareOrderNumberOrc = orc.getOrc2_PlacerOrderNumber().getEi1_EntityIdentifier().getValueOrEmpty(); setLabSpecimenNumber(orc); @@ -216,9 +213,9 @@ void populateObrFields(OBR obr, boolean requiredCollectionTime) throws DataTypeE epicCareOrderNumberObr = obr.getObr2_PlacerOrderNumber().getEi1_EntityIdentifier().getValueOrEmpty(); - msg.setStatusChangeTime(HL7Utils.interpretLocalTime(obr.getObr22_ResultsRptStatusChngDateTime())); + msg.setStatusChangeTime(interpretLocalTime(obr.getObr22_ResultsRptStatusChngDateTime())); - String reasonForStudy = List.of(obr.getObr31_ReasonForStudy()).stream() + String reasonForStudy = Stream.of(obr.getObr31_ReasonForStudy()) .map(CWE::getCwe2_Text) .map(ST::getValueOrEmpty) .collect(Collectors.joining("\n")) @@ -241,7 +238,7 @@ void populateObrFields(OBR obr, boolean requiredCollectionTime) throws DataTypeE } - protected void addMsgIfAllowedOcId(String idsUnid, List orders) { + void addMsgIfAllowedOcId(String idsUnid, Collection orders) { if (msg.getOrderControlId() != null && allowedOcIds.contains(msg.getOrderControlId())) { orders.add(msg); } else { diff --git a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/TestHL7ParsingMatchesInterchangeFactoryOutput.java b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/TestHL7ParsingMatchesInterchangeFactoryOutput.java index e1bb5381e..c4708648e 100644 --- a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/TestHL7ParsingMatchesInterchangeFactoryOutput.java +++ b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/TestHL7ParsingMatchesInterchangeFactoryOutput.java @@ -189,7 +189,7 @@ public void testDoubleA01WithA13() throws Exception { @Test void testAdtPermutationMoves() throws Exception { - String[] fileNames = {"02_A01", "03_A02", "04_A02", "05_A02", "06_A02", "07_A06", "08_A03"}; + String[] fileNames = {"02_A01", "03_A02", "04_A02", "05_A02", "06_A02", "07_A06", "08_A03", "09_Z99"}; builtAndAssertAdtMessages("Adt", "Location/Moves", fileNames); } diff --git a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestAdtFields.java b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestAdtFields.java index 1890ac992..258132136 100644 --- a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestAdtFields.java +++ b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestAdtFields.java @@ -165,4 +165,9 @@ public void testDeathIndicator() { void testEthnicGroup() { assertEquals(InterchangeValue.buildFromHl7("Not Yet Asked"), msg.getEthnicGroup()); } + + @Test + void testAdmissionType() { + assertEquals(InterchangeValue.buildFromHl7("Elective"), msg.getAdmissionType()); + } } diff --git a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestMiscAdt.java b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestMiscAdt.java index 9059fbe3f..b867d455f 100644 --- a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestMiscAdt.java +++ b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/adt/TestMiscAdt.java @@ -5,8 +5,10 @@ import uk.ac.ucl.rits.inform.interchange.InterchangeValue; import uk.ac.ucl.rits.inform.interchange.adt.AdmitPatient; import uk.ac.ucl.rits.inform.interchange.adt.AdtMessage; +import uk.ac.ucl.rits.inform.interchange.adt.HospitalService; import uk.ac.ucl.rits.inform.interchange.adt.PatientClass; import uk.ac.ucl.rits.inform.interchange.adt.RegisterPatient; +import uk.ac.ucl.rits.inform.interchange.adt.UpdatePatientInfo; import java.time.Instant; import java.time.LocalDate; @@ -55,6 +57,37 @@ public void testInpatientAdmission() throws Exception { assertEquals(AdmitPatient.class.getName(), msg.getMessageType()); } + /** + * A01 carries PV1-10, so should populate hospital service on the interchange message. + * @throws Exception shouldn't happen + */ + @Test + void testAdmissionHospitalService() throws Exception { + AdmitPatient msg = (AdmitPatient) processSingleAdtMessage("Adt/generic/A01.txt"); + assertEquals(InterchangeValue.buildFromHl7("Anaes"), msg.getHospitalService()); + } + + /** + * A08 carries PV1-10, so should populate hospital service on the interchange message. + * @throws Exception shouldn't happen + */ + @Test + void testUpdateInfoHospitalService() throws Exception { + UpdatePatientInfo msg = (UpdatePatientInfo) processSingleAdtMessage("Adt/generic/A08_v1.txt"); + assertEquals(InterchangeValue.buildFromHl7("Anaes"), msg.getHospitalService()); + } + + /** + * A08 can legitimately have no PV1 segment at all (a demographics-only update). + * Hospital service should resolve to unknown rather than the message failing to parse. + * @throws Exception shouldn't happen + */ + @Test + void testUpdateInfoNoPv1HospitalServiceUnknown() throws Exception { + HospitalService msg = (HospitalService) processSingleAdtMessage("Adt/generic/A08_no_pv1.txt"); + assertTrue(msg.getHospitalService().isUnknown()); + } + /** * When a birth datetime is purely a date, should keep the original day, even when BST. * @throws Exception shouldn't happen diff --git a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/labs/TestWinPathOrders.java b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/labs/TestWinPathOrders.java index be8bc143e..452344d8e 100644 --- a/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/labs/TestWinPathOrders.java +++ b/hl7-reader/src/test/java/uk/ac/ucl/rits/inform/datasources/ids/labs/TestWinPathOrders.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import uk.ac.ucl.rits.inform.datasources.ids.exceptions.Hl7InconsistencyException; import uk.ac.ucl.rits.inform.interchange.InterchangeValue; import uk.ac.ucl.rits.inform.interchange.OrderCodingSystem; import uk.ac.ucl.rits.inform.interchange.lab.LabOrderMsg; @@ -12,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -208,4 +210,12 @@ void testOrmO01Notes() throws Exception { // separated by :- assertEquals("?stroke", order.getQuestions().get("Clinical Details")); } + + /** + * Null OrderControlId should throw an HL7InconsistencyException + */ + @Test + void testNullOrderControlId() { + assertThrows(Hl7InconsistencyException.class, () -> labReader.getFirstOrder(FILE_TEMPLATE, "null_order_control_id")); + } } diff --git a/hl7-reader/src/test/resources/Adt/Location/Moves/09_Z99.txt b/hl7-reader/src/test/resources/Adt/Location/Moves/09_Z99.txt new file mode 100644 index 000000000..74a1fa777 --- /dev/null +++ b/hl7-reader/src/test/resources/Adt/Location/Moves/09_Z99.txt @@ -0,0 +1,6 @@ +MSH|^~\&|EPIC|UCLH|||20220421230000|IPUC|ADT^Z99|31644ZZZ|T|2.4|||AL|NE +EVN|Z99|20130211180052||ADT_EVENT|IPUC^COORDINATOR^INPATIENT^UNIT^^^^^UCLHS^^^^^UCHC|20220422010000 +PID|1||40800000^^^MRENTR^MEDREC~9999999999^^^NHS^HN||ORANGE^BLOOD^^^^^D|||F|||^^^^^ENGLAND^L|||||||||||||||||||N +PV1|1|I|1020100166^SDEC BY02^11 SDEC|Matern Ante|||HB^BADGER^HONEY^^^^^^CONS^^^^CONS~C1111111^BADGER^HONEY^^^^^^CDS^^^^CDS~752^BADGER^HONEY^^^^^^PROVID^^^^PROVID|||Anaes||||Usual|||||123412341234|SELF||||||||||||||||Feeling great|Home||||Adm*Conf|||20130211100052|20220422000000 +PV2||||||||20130211||||Hospital Encounter||||||||||N||||||||||N +OBX|1|TX|AdminCode^ADMINISTRATIVE CATEGORY CODE|1|1|||||||||20130211 diff --git a/hl7-reader/src/test/resources/Adt/TestForJunit.txt b/hl7-reader/src/test/resources/Adt/TestForJunit.txt index 71f24eb69..475be92e5 100644 --- a/hl7-reader/src/test/resources/Adt/TestForJunit.txt +++ b/hl7-reader/src/test/resources/Adt/TestForJunit.txt @@ -3,7 +3,7 @@ EVN|A01|201209211843|hello|ADM|U439966^WYYYY^CLLLL|201201011200\r PID|||50032556^^^MRENTR^MEDREC~this is a test NHS number||INTERFACES^Amendadmission^Longforenamesecondfn^^LADY||194001010000|F|||42 Rooo Fooo^^Blorp^^ZZ1 0AB^^^^West Yorkshire|||||||||||Z1||||||||N PD1|||HH|397982^LA^M^^^DR^^^^^^^^G9010000~B8^4 L H L^B^^^^^ZZ1 0AB^^01222 222222^^^^B8&1~404278^ME^AH^^^^^^^^^^^D2444444~V244444^DENTAL SURGERY^9 W^B^WEST YORKSHIRE^^^BD1 1AA^^^^^^V22222&3||14\r NK1|1|NIECEOFTHEINTERFACETESTPATIENTDDDDD^Firstnameoftheneiceofthetestpatient|N|1 LINE OF VERY LONG ADDRESSWITHINFO&2 LINE OF VERY LONG ADDRESSWITHALLF^3 LINE OF VERYLONG ADDRESSWITH3DETA&4 LINE OF VERYLONGADDRESSWITH4DETAI^5 LINE FOR CITY OR TOWN ENTEREDINTO^^Z1 1AA^^^^6 LINE OF VERYLONGADDRESS FOR COUNT|02033333333|02033333333X888888|N\r -PV1||I|H2HH^H203^H203-11|1~I|||399999^IS^DAV^^^PROF|397982^LA^M^^^DR~399999^IS^DAV^J^^PROF||41008||||19|||||1234TESTVISITNUM|||PLS-1|||||||||||||||||HH||2|||201209211840\r +PV1||I|H2HH^H203^H203-11|Elective|||399999^IS^DAV^^^PROF|397982^LA^M^^^DR~399999^IS^DAV^J^^PROF||41008||||19|||||1234TESTVISITNUM|||PLS-1|||||||||||||||||HH||2|||201209211840\r PV2||W|DETAILS FOR a08 AMEND aDMISSION MESSAGE||||||||||B\r OBX|1|ST|^^^ABC^Assign Benefits^PLW-HL7||||||||N|||20120921\r OBX|2|ST|^^^LRRF^Reg Required Flags^PLW-HL7||~~~~14~1||||||N\r diff --git a/hl7-reader/src/test/resources/Adt/generic/A08_no_pv1.txt b/hl7-reader/src/test/resources/Adt/generic/A08_no_pv1.txt new file mode 100644 index 000000000..0b40c6a2f --- /dev/null +++ b/hl7-reader/src/test/resources/Adt/generic/A08_no_pv1.txt @@ -0,0 +1,3 @@ +MSH|^~\&|EPIC|UCLH|||20130311100052|IPUC|ADT^A08|31644ZZZ|T|2.4|||AL|NE +EVN|A08|20130311100052||ADT_EVENT|IPUC^COORDINATOR^INPATIENT^UNIT^^^^^UCLHS^^^^^UCHC +PID|1||40800000^^^MRENTR^MEDREC~9999999999^^^NHS^HN||ORANG^BLOD^^^^^D||19890909|F|||^^^^^ENGLAND^L|||||||||||||||||||N diff --git a/hl7-reader/src/test/resources/LabOrders/winpath/null_order_control_id.txt b/hl7-reader/src/test/resources/LabOrders/winpath/null_order_control_id.txt new file mode 100644 index 000000000..982f39f08 --- /dev/null +++ b/hl7-reader/src/test/resources/LabOrders/winpath/null_order_control_id.txt @@ -0,0 +1,5 @@ +MSH|^~\&|WinPath|UCH|EPIC|UCH|20210101120000||ORM^O01|1828yhf9872-1234-1234-1234-123983hdi|P|2.3 +PID|||Test^^^MRENTR^MEDREC~||TEST^TEST||19000101|U +PV1|||UNKNOWN^UNKNOWN||||||UNKNOWN^Unknown +ORC|||11U123456 +OBR|1||11U123456|H.SIG1^^WinPath|N|20210101120000|20210101120000|||||||20210101120000||||||||20210101120000||IMM|I