Skip to content

Commit

Permalink
EA-190 Improve API for retrieving inpatient visits (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
mseaton authored Jul 23, 2024
1 parent ae76c84 commit 238b854
Show file tree
Hide file tree
Showing 13 changed files with 754 additions and 159 deletions.
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ Higher-level APIs to support building EMR functionality in OpenMRS, to supplemen

## Required modules

* reporting (+ serialization.xstream, htmlwidgets, calculation)
* providermanagement
* metadatamapping
* event
* metadatamapping
* webservices.rest

## Aware of modules

* fhir2
* metadatasharing
* providermanagement
* reporting

# Configuration

Expand All @@ -38,6 +38,9 @@ Higher-level APIs to support building EMR functionality in OpenMRS, to supplemen
* Sets up the patient viewed event listener subscription
* Ensures the person image folder exists

# REST API
See https://github.com/openmrs/openmrs-contrib-rest-api-docs

# API

## account
Expand All @@ -58,10 +61,6 @@ Higher-level APIs to support building EMR functionality in OpenMRS, to supplemen
* Provides implementations for merging patients and merging visits
* Provides APIs for creating checkin encounters, adt encounters, and identify and get inpatient visits

* (reporting module) definitions and evaluators for getting:
* AwaitingAdmissionVisitQuery - any Visits that are in the state of "awaiting admission"
* MostRecentAdmissionRequestVisitDataDefinition - data from a Visit relevant for "admission request"

## concept

* Provides additional service methods for getting and searching for concepts
Expand Down Expand Up @@ -141,7 +140,3 @@ Higher-level APIs to support building EMR functionality in OpenMRS, to supplemen
* Service and parameter objects to find visits and return associated EncounterTransactions in that visit
* Service methods to get diagnoses obs and patients with particular diagnoses
* VisitDomainWrapper - convenience wrapper to get info around active / open status, encounters contained, diagnoses, dispositions, ADT status

# condition-list

This provides an implementation of condition list functionality, but condition was added to core in 2.2, so this is likely no longer needed
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ Encounter checkInPatient(Patient patient, Location where, Provider checkInClerk,
* @param visitLocation
* @return
*/
@Deprecated
List<VisitDomainWrapper> getInpatientVisits(Location visitLocation, Location ward);

/**
Expand Down Expand Up @@ -353,4 +354,11 @@ VisitDomainWrapper createRetrospectiveVisit(Patient patient, Location location,
* @return List<InpatientRequest> of the matching InpatientRequests that match the criteria
*/
List<InpatientRequest> getInpatientRequests(InpatientRequestSearchCriteria criteria);

/**
* Returns all List of InpatientAdmission that match the given search criteria
* @param criteria - represents the criteria by which inpatient admissions are searched and returned
* @return List<InpatientAdmission> of the matching InpatientAdmissions that match the criteria
*/
List<InpatientAdmission> getInpatientAdmissions(InpatientAdmissionSearchCriteria criteria);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -988,7 +989,7 @@ public List<InpatientRequest> getInpatientRequests(InpatientRequestSearchCriteri
parameters.put("visitIds", criteria.getVisitIds());
parameters.put("limitByVisit", criteria.getVisitIds() != null);

List<?> reqs = emrApiDAO.executeHqlFromResource("hql/inpatient_request_dispositions.hql", parameters, List.class);
List<?> reqs = emrApiDAO.executeHqlFromResource("hql/inpatient_requests.hql", parameters, List.class);
List<InpatientRequest> ret = new ArrayList<>();
for (Object req : reqs) {
Object[] o = (Object[]) req;
Expand All @@ -1010,4 +1011,64 @@ public List<InpatientRequest> getInpatientRequests(InpatientRequestSearchCriteri
}
return ret;
}

@Override
@Transactional(readOnly = true)
public List<InpatientAdmission> getInpatientAdmissions(InpatientAdmissionSearchCriteria criteria) {

// Determine whether to filter visits at a particular location
Location visitLocation = null ;
if (criteria.getVisitLocation() != null ) {
visitLocation = getLocationThatSupportsVisits(criteria.getVisitLocation());
}

EncounterType admissionEncounterType = emrApiProperties.getAdmissionEncounterType();
EncounterType transferEncounterType = emrApiProperties.getTransferWithinHospitalEncounterType();
EncounterType dischargeEncounterType = emrApiProperties.getExitFromInpatientEncounterType();

Map<String, Object> parameters = new HashMap<>();
parameters.put("visitLocation", visitLocation);
parameters.put("admissionEncounterType", emrApiProperties.getAdmissionEncounterType());
parameters.put("transferEncounterType", emrApiProperties.getTransferWithinHospitalEncounterType());
parameters.put("dischargeEncounterType", emrApiProperties.getExitFromInpatientEncounterType());
parameters.put("patientIds", criteria.getPatientIds());
parameters.put("limitByPatient", criteria.getPatientIds() != null);
parameters.put("visitIds", criteria.getVisitIds());
parameters.put("limitByVisit", criteria.getVisitIds() != null);

List<?> l = emrApiDAO.executeHqlFromResource("hql/inpatient_admissions.hql", parameters, List.class);
Map<Visit, InpatientAdmission> m = new LinkedHashMap<>();
for (Object req : l) {
Object[] o = (Object[]) req;
Visit visit = (Visit)o[0];
Patient patient = (Patient)o[1];
Encounter encounter = (Encounter)o[2];
InpatientAdmission admission = m.get(visit);
if (admission == null) {
admission = new InpatientAdmission();
admission.setVisit(visit);
admission.setPatient(patient);
m.put(visit, admission);
}
if (encounter.getEncounterType().equals(admissionEncounterType)) {
admission.getAdmissionEncounters().add(encounter);
}
else if (encounter.getEncounterType().equals(transferEncounterType)) {
admission.getTransferEncounters().add(encounter);
}
else if (encounter.getEncounterType().equals(dischargeEncounterType)) {
admission.getDischargeEncounters().add(encounter);
}
}

List<InpatientAdmission> ret = new ArrayList<>();
for (InpatientAdmission admission : m.values()) {
if (criteria.getCurrentInpatientLocations() == null || criteria.getCurrentInpatientLocations().contains(admission.getCurrentInpatientLocation())) {
if (criteria.isIncludeDischarged() || !admission.isDischarged()) {
ret.add(admission);
}
}
}
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.openmrs.module.emrapi.adt;

import lombok.Data;
import org.openmrs.Encounter;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.Visit;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
* Represents a hospital Admission
*/
@Data
public class InpatientAdmission {

private Visit visit;
private Patient patient;
private Set<Encounter> admissionEncounters = new TreeSet<>(getEncounterComparator());
private Set<Encounter> transferEncounters = new TreeSet<>(getEncounterComparator());
private Set<Encounter> dischargeEncounters = new TreeSet<>(getEncounterComparator());

public List<Encounter> getAdmissionAndTransferEncounters() {
List<Encounter> encounters = new ArrayList<>();
encounters.addAll(admissionEncounters);
encounters.addAll(transferEncounters);
encounters.sort(getEncounterComparator());
return Collections.unmodifiableList(encounters);
}

public List<Encounter> getAdtEncounters() {
List<Encounter> encounters = new ArrayList<>();
encounters.addAll(admissionEncounters);
encounters.addAll(transferEncounters);
encounters.addAll(dischargeEncounters);
encounters.sort(getEncounterComparator());
return Collections.unmodifiableList(encounters);
}

public Encounter getFirstAdmissionOrTransferEncounter() {
List<Encounter> encounters = getAdmissionAndTransferEncounters();
return encounters.isEmpty() ? null : encounters.get(0);
}

public Encounter getLatestAdmissionOrTransferEncounter() {
List<Encounter> encounters = getAdmissionAndTransferEncounters();
return encounters.isEmpty() ? null : encounters.get(encounters.size() - 1);
}

public Encounter getLatestAdtEncounter() {
List<Encounter> encounters = getAdtEncounters();
return encounters.isEmpty() ? null : encounters.get(encounters.size() - 1);
}

public Location getCurrentInpatientLocation() {
if (isDischarged()) {
return null;
}
Encounter encounter = getLatestAdmissionOrTransferEncounter();
return encounter == null ? null : encounter.getLocation();
}

public Encounter getEncounterAssigningToCurrentInpatientLocation() {
Location location = getCurrentInpatientLocation();
if (location == null) {
return null;
}
List<Encounter> encounters = getAdmissionAndTransferEncounters();
if (encounters.isEmpty()) {
return null;
}
Encounter ret = encounters.get(encounters.size() - 1);
if (!ret.getLocation().equals(location)) { // Sanity check, this should not happen
return null;
}
for (int i=encounters.size()-2; i>=0; i--) {
Encounter e = encounters.get(i);
if (e.getLocation().equals(location)) {
ret = e;
}
else {
return ret;
}
}
return ret;
}

public boolean isDischarged() {
if (dischargeEncounters.isEmpty()) {
return false;
}
return dischargeEncounters.contains(getLatestAdtEncounter());
}

private Comparator<Encounter> getEncounterComparator() {
return Comparator.comparing(Encounter::getEncounterDatetime)
.thenComparing(Encounter::getDateCreated)
.thenComparing(Encounter::getEncounterId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.openmrs.module.emrapi.adt;

import lombok.Data;
import org.openmrs.Location;

import java.util.ArrayList;
import java.util.List;

/**
* Represents criteria for searching for InpatientAdmissions
*/
@Data
public class InpatientAdmissionSearchCriteria {

private Location visitLocation;
private List<Location> currentInpatientLocations;
private boolean includeDischarged = false;
private List<Integer> patientIds;
private List<Integer> visitIds;

public void addCurrentInpatientLocation(Location location) {
if (currentInpatientLocations == null) {
currentInpatientLocations = new ArrayList<>();
}
currentInpatientLocations.add(location);
}

public void addPatientId(Integer patientId) {
if (patientIds == null) {
patientIds = new ArrayList<>();
}
patientIds.add(patientId);
}

public void addVisitId(Integer visitId) {
if (visitIds == null) {
visitIds = new ArrayList<>();
}
visitIds.add(visitId);
}
}
17 changes: 17 additions & 0 deletions api/src/main/resources/hql/inpatient_admissions.hql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
select
visit,
patient,
encounter
from
Encounter as encounter
inner join encounter.visit as visit
inner join encounter.patient as patient
where encounter.voided = false
and visit.voided = false
and patient.voided = false
and (:visitLocation is null or visit.location = :visitLocation)
and visit.stopDatetime is null
and encounter.encounterType in (:admissionEncounterType, :transferEncounterType, :dischargeEncounterType)
and (:limitByPatient is false or patient.patientId in (:patientIds))
and (:limitByVisit is false or visit.visitId in (:visitIds))
order by visit.visitId, encounter.encounterDatetime, encounter.encounterId
Loading

0 comments on commit 238b854

Please sign in to comment.