Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import uk.ac.ucl.rits.inform.interchange.adt.MoveVisitInformation;
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;
Expand Down Expand Up @@ -338,5 +339,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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import uk.ac.ucl.rits.inform.informdb.movement.PlannedMovementAudit;
import uk.ac.ucl.rits.inform.interchange.adt.CancelPendingTransfer;
import uk.ac.ucl.rits.inform.interchange.adt.PendingTransfer;
import uk.ac.ucl.rits.inform.interchange.adt.UpdateSubSpeciality;

import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
Expand Down Expand Up @@ -139,6 +141,54 @@ public void processMsg(HospitalVisit visit, CancelPendingTransfer msg, Instant v
plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo);
}

/**
* Process an Update subspeciality (Z99) request.
* <p>
* 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 update sub speciality
* @param validFrom time in the hospital when the message was created
* @param storedFrom time that emap core started processing the message
*/
public void processMsg(HospitalVisit visit, UpdateSubSpeciality msg, Instant validFrom, Instant storedFrom) {
Location fullLocation = null;

if (msg.getFullLocationString().isSave()) {
fullLocation = locationController.getOrCreateLocation(msg.getFullLocationString().get());
}
// pseudo from issue
// match pending adt by hospital_visit and location
// if if a match is found, add in new row to the planned_movement table, event_type = EDIT/HOSPTIAL_SERVICE_CHANGE
// look for matching entry here

Instant eventDateTime = msg.getEventOccurredDateTime();

List<PlannedMovement> movements = plannedMovementRepo.findMatchingMovementsFromZ99(visit, fullLocation, eventDateTime);
if (!movements.isEmpty()) {

int mostRecentMoveIndex = movements.size() - 1;
String currentService = movements.get(mostRecentMoveIndex).getHospitalService();
String editedService = msg.getHospitalService().get();

if (!Objects.equals(currentService, editedService)) {
Long matchedMovementId = movements.get(mostRecentMoveIndex).getPlannedMovementId();
RowState<PlannedMovement, PlannedMovementAudit> 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(msg.getHospitalService(), movement.getHospitalService(), movement::setHospitalService);
plannedState.assignIfDifferent(matchedMovementId, movement.getMatchedMovementId(), movement::setMatchedMovementId);
plannedState.saveEntityOrAuditLogIfRequired(plannedMovementRepo, plannedMovementAuditRepo);
}
}
}

/**
* Delete planned movements from a delete patient information message.
* @param visit Hospital visit that should have their planned movements deleted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import uk.ac.ucl.rits.inform.interchange.adt.MoveVisitInformation;
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;
Expand All @@ -40,11 +41,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,
Expand Down Expand Up @@ -211,4 +212,21 @@ public void processPendingAdt(CancelPendingTransfer msg, Instant storedFrom) thr
HospitalVisit visit = processPersonAndVisit(msg, storedFrom, validFrom);
pendingAdtController.processMsg(visit, msg, validFrom, storedFrom);
}

/**
* Process an update subspeciality message.
* <p>
* 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.processMsg(visit, msg, validFrom, storedFrom);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ List<PlannedMovement> findMatchingMovementsFromRequest(
String eventType, HospitalVisit hospitalVisitId, Location plannedLocation, Instant eventDatetime
);

/**
* Try and find a matching planned movement from a Z99 edit sub speciality message.
* <p>
* 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<PlannedMovement> findMatchingMovementsFromZ99(
HospitalVisit hospitalVisitId, Location plannedLocation, Instant eventDatetime
);


/**
* Try and find a matching planned movement from a pending adt cancellation message.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PlannedMovement> 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<PlannedMovement> 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<PlannedMovement> movements = plannedMovementRepository.findAllByHospitalVisitIdEncounter(VISIT_NUMBER);
assertEquals(1, movements.size());
assertEquals("TRANSFER", movements.get(0).getEventType());
//assertThrows(NoSuchElementException.class, () -> getPlannedMovementOrThrow(VISIT_NUMBER, LOCATION_STRING));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import uk.ac.ucl.rits.inform.interchange.adt.MoveVisitInformation;
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;
Expand Down Expand Up @@ -176,4 +177,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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public abstract class AdtMessage extends EmapOperationMessage {
private InterchangeValue<String> patientSex = InterchangeValue.unknown();
private InterchangeValue<String> patientTitle = InterchangeValue.unknown();
private InterchangeValue<String> patientZipOrPostalCode = InterchangeValue.unknown();

private InterchangeValue<Long> matchedMovementId = InterchangeValue.unknown();

/**
* Ideally the time the event occurred, but uses the message date time as a backup.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* Used in specific circumstances because where the sub speciality is changed without a movement
* via a ADT Z99 message
*/
public interface HospitalService {
InterchangeValue<String> getHospitalService();

void setHospitalService(InterchangeValue<String> hospitalService);
}
Original file line number Diff line number Diff line change
@@ -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<String> hospitalService = InterchangeValue.unknown();
private InterchangeValue<Long> matchedMovementId = InterchangeValue.unknown();

@Override
public void processMessage(EmapOperationMessageProcessor processor) throws EmapOperationMessageProcessingException {
processor.processMessage(this);
}

}
Loading