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