Skip to content

Commit

Permalink
Martina, Roni, Eyal, Klaus | BAH-460 | add api to search patients wit…
Browse files Browse the repository at this point in the history
…h similar name, gender and birthdate/age
  • Loading branch information
mduemcke committed Aug 20, 2018
1 parent 89fc7a5 commit 117df8f
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
import org.apache.commons.lang.StringUtils;
import org.openmrs.module.webservices.rest.web.RequestContext;

import java.sql.Timestamp;
import java.util.Date;
import java.util.Map;

public class PatientSearchParameters {
private final String MIDNIGHT = "00:00:00";

private Boolean filterPatientsByLocation;
private String identifier;
private String name;
private String gender;
private Date birthdate;
private String addressFieldName;
private String addressFieldValue;
private Integer start;
Expand Down Expand Up @@ -43,6 +49,8 @@ public PatientSearchParameters(RequestContext context) {
} else {
this.setAddressFieldName("city_village");
}
this.setGender(context.getParameter("gender"));
this.setBirthdate(context.getParameter("birthdate"));
this.setAddressFieldValue(context.getParameter("addressFieldValue"));
Map parameterMap = context.getRequest().getParameterMap();
this.setAddressSearchResultFields((String[]) parameterMap.get("addressSearchResultsConfig"));
Expand Down Expand Up @@ -71,6 +79,26 @@ public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public void setBirthdate(String birthdate) {
if(StringUtils.isEmpty(birthdate)) {
return;
}

this.birthdate = Timestamp.valueOf(setToMidnight(birthdate));
}

public Date getBirthdate() {
return birthdate;
}

public String getAddressFieldName() {
return addressFieldName;
}
Expand Down Expand Up @@ -174,4 +202,12 @@ public void setFilterOnAllIdentifiers(Boolean filterOnAllIdentifiers) {
public Boolean getFilterOnAllIdentifiers() {
return filterOnAllIdentifiers;
}

private String setToMidnight(String birthdate) {
if(StringUtils.isNotEmpty(birthdate) && birthdate.matches("\\d{4}-\\d{2}-\\d{2}")) {
return birthdate + " " + MIDNIGHT;
} else {
return birthdate;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.bahmni.module.bahmnicore.contract.patient.mapper;

import java.util.Objects;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.bahmni.module.bahmnicore.contract.patient.response.PatientResponse;
Expand All @@ -21,11 +20,13 @@

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;

public class PatientResponseMapper {
private PatientResponse patientResponse;
private VisitService visitService;
Expand All @@ -38,8 +39,8 @@ public PatientResponseMapper(VisitService visitService, BahmniVisitLocationServi
}

public PatientResponse map(Patient patient, String loginLocationUuid, String[] searchResultFields, String[] addressResultFields, Object programAttributeValue) {
List<String> patientSearchResultFields = searchResultFields != null ? Arrays.asList(searchResultFields) : new ArrayList<>();
List<String> addressSearchResultFields = addressResultFields != null ? Arrays.asList(addressResultFields) : new ArrayList<>();
List<String> patientSearchResultFields = searchResultFields != null ? asList(searchResultFields) : new ArrayList<>();
List<String> addressSearchResultFields = addressResultFields != null ? asList(addressResultFields) : new ArrayList<>();

Integer visitLocationId = bahmniVisitLocationService.getVisitLocation(loginLocationUuid).getLocationId();
List<Visit> activeVisitsByPatient = visitService.getActiveVisitsByPatient(patient);
Expand All @@ -55,7 +56,9 @@ public PatientResponse map(Patient patient, String loginLocationUuid, String[] s
patientResponse.setFamilyName(patient.getFamilyName());
patientResponse.setGender(patient.getGender());
PatientIdentifier primaryIdentifier = patient.getPatientIdentifier();
patientResponse.setIdentifier(primaryIdentifier.getIdentifier());
if(primaryIdentifier != null) {
patientResponse.setIdentifier(primaryIdentifier.getIdentifier());
}
patientResponse.setPatientProgramAttributeValue(programAttributeValue);

mapExtraIdentifiers(patient, primaryIdentifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.openmrs.Patient;
import org.openmrs.RelationshipType;

import java.util.Date;
import java.util.List;

public interface PatientDao {
Expand All @@ -19,6 +20,8 @@ List<PatientResponse> getPatientsUsingLuceneSearch(String identifier, String nam
String programAttributeFieldName, String[] addressSearchResultFields,
String[] patientSearchResultFields, String loginLocationUuid, Boolean filterPatientsByLocation, Boolean filterOnAllIdentifiers);

List<PatientResponse> getSimilarPatientsUsingLuceneSearch(String name, String gender, Date birthdate, String loginLocationUuid, Integer length);

public Patient getPatient(String identifier);

public List<Patient> getPatients(String partialIdentifier, boolean shouldMatchExactPatientId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,26 @@
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.Person;
import org.openmrs.PersonName;
import org.openmrs.RelationshipType;
import org.openmrs.api.context.Context;
import org.openmrs.api.db.hibernate.HibernatePatientDAO;
import org.openmrs.api.db.hibernate.PersonLuceneQuery;
import org.openmrs.api.db.hibernate.search.LuceneQuery;
import org.openmrs.module.bahmniemrapi.visitlocation.BahmniVisitLocationServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static java.util.stream.Collectors.reducing;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;

@Repository
Expand Down Expand Up @@ -88,18 +94,74 @@ public List<PatientResponse> getPatientsUsingLuceneSearch(String identifier, Str
List<PatientResponse> patientResponses = patientIdentifiers.stream()
.map(patientIdentifier -> {
Patient patient = patientIdentifier.getPatient();
if(!uniquePatientIds.contains(patient.getPatientId())) {
PatientResponse patientResponse = patientResponseMapper.map(patient, loginLocationUuid, patientSearchResultFields, addressSearchResultFields,
programAttributes.get(patient.getPatientId()));
uniquePatientIds.add(patient.getPatientId());
return patientResponse;
}else
return null;
return toPatientResponse(patientResponseMapper, patient, loginLocationUuid, addressSearchResultFields, patientSearchResultFields, programAttributes, uniquePatientIds);
}).filter(Objects::nonNull)
.collect(toList());
return patientResponses;
}

@Override
public List<PatientResponse> getSimilarPatientsUsingLuceneSearch(String name, String gender, Date birthdate, String loginLocationUuid, Integer length) {
PatientResponseMapper patientResponseMapper = new PatientResponseMapper(Context.getVisitService(),new BahmniVisitLocationServiceImpl(Context.getLocationService()));
List<Patient> patients = getPatientsByNameGenderAndBirthdate(name, gender, birthdate, length);
List<PatientResponse> patientResponses = patients.stream()
.map(patient -> {return patientResponseMapper.map(patient, loginLocationUuid, null, null,null);}).filter(Objects::nonNull)
.collect(toList());
return patientResponses;
}

private PatientResponse toPatientResponse(PatientResponseMapper patientResponseMapper, Patient patient, String loginLocationUuid, String[] addressSearchResultFields, String[] patientSearchResultFields, Map<Object, Object> programAttributes, Set<Integer> uniquePatientIds) {
if(!uniquePatientIds.contains(patient.getPatientId())) {
PatientResponse patientResponse = patientResponseMapper.map(patient, loginLocationUuid, patientSearchResultFields, addressSearchResultFields,
programAttributes.get(patient.getPatientId()));
uniquePatientIds.add(patient.getPatientId());
return patientResponse;
} else {
return null;
}
}

private List<Patient> getPatientsByNameGenderAndBirthdate(String name, String gender, Date birthdate, Integer length) {
if(isAllNullOrEmpty(name, gender, birthdate)) {
return new ArrayList<>();
}

HibernatePatientDAO patientDAO = new HibernatePatientDAO();
patientDAO.setSessionFactory(sessionFactory);
String query = LuceneQuery.escapeQuery(name);
PersonLuceneQuery personLuceneQuery = new PersonLuceneQuery(sessionFactory);
LuceneQuery<PersonName> nameQuery = personLuceneQuery.getPatientNameQuery(query, false);
List<Patient> patients = nameQuery.list().stream()
.filter(patient -> patient.getPreferred() && checkGender(patient.getPerson(), gender)
&& checkBirthdate(patient.getPerson(), birthdate)
)
.limit(length)
.map(patient -> new Patient(patient.getPerson()))
.collect(toList());
return patients;
}

private Boolean isAllNullOrEmpty(String name, String gender, Date birthdate) {
return (name == null || name.trim().isEmpty()) && (gender == null || gender.isEmpty()) && birthdate == null;
}


private Boolean checkGender(Person person, String gender) {
if(gender != null && !gender.isEmpty()){
return gender.equals(person.getGender());
} else {
return true;
}
}

private Boolean checkBirthdate(Person person, Date birthdate) {
if(birthdate != null) {
return birthdate.equals(person.getBirthdate());
} else {
return true;
}
}

private List<PatientIdentifier> getPatientIdentifiers(String identifier, Boolean filterOnAllIdentifiers, Integer offset, Integer length) {
FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(PatientIdentifier.class).get();
Expand All @@ -108,7 +170,7 @@ private List<PatientIdentifier> getPatientIdentifiers(String identifier, Boolean
.wildcard().onField("identifierAnywhere").matching("*" + identifier.toLowerCase() + "*").createQuery();
org.apache.lucene.search.Query nonVoidedIdentifiers = queryBuilder.keyword().onField("voided").matching(false).createQuery();
org.apache.lucene.search.Query nonVoidedPatients = queryBuilder.keyword().onField("patient.voided").matching(false).createQuery();

List<String> identifierTypeNames = getIdentifierTypeNames(filterOnAllIdentifiers);

BooleanJunction identifierTypeShouldJunction = queryBuilder.bool();
Expand All @@ -132,7 +194,7 @@ private List<PatientIdentifier> getPatientIdentifiers(String identifier, Boolean
fullTextQuery.setMaxResults(length);
return (List<PatientIdentifier>) fullTextQuery.list();
}

private List<String> getIdentifierTypeNames(Boolean filterOnAllIdentifiers) {
List<String> identifierTypeNames = new ArrayList<>();
addIdentifierTypeName(identifierTypeNames,"bahmni.primaryIdentifierType");
Expand Down Expand Up @@ -179,7 +241,7 @@ private boolean isValidAddressField(String addressFieldName) {
"LOWER (TABLE_NAME) ='person_address' and LOWER(COLUMN_NAME) IN " +
"( :personAddressField)";
Query queryToGetAddressFields = sessionFactory.getCurrentSession().createSQLQuery(query);
queryToGetAddressFields.setParameterList("personAddressField", Arrays.asList(addressFieldName.toLowerCase()));
queryToGetAddressFields.setParameterList("personAddressField", asList(addressFieldName.toLowerCase()));
List list = queryToGetAddressFields.list();
return list.size() > 0;
}
Expand All @@ -201,7 +263,7 @@ private List<Integer> getPersonAttributeIds(String[] patientAttributes) {
String query = "select person_attribute_type_id from person_attribute_type where name in " +
"( :personAttributeTypeNames)";
Query queryToGetAttributeIds = sessionFactory.getCurrentSession().createSQLQuery(query);
queryToGetAttributeIds.setParameterList("personAttributeTypeNames", Arrays.asList(patientAttributes));
queryToGetAttributeIds.setParameterList("personAttributeTypeNames", asList(patientAttributes));
List list = queryToGetAttributeIds.list();
return (List<Integer>) list;
}
Expand Down Expand Up @@ -229,7 +291,7 @@ public List<Patient> getPatients(String patientIdentifier, boolean shouldMatchEx
}

Patient patient = getPatient(patientIdentifier);
List<Patient> result = (patient == null ? new ArrayList<Patient>() : Arrays.asList(patient));
List<Patient> result = (patient == null ? new ArrayList<Patient>() : asList(patient));
return result;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.bahmni.module.bahmnicore.service;

import org.bahmni.module.bahmnicore.contract.patient.PatientSearchParameters;
import org.bahmni.module.bahmnicore.contract.patient.mapper.PatientResponseMapper;
import org.bahmni.module.bahmnicore.contract.patient.response.PatientConfigResponse;
import org.bahmni.module.bahmnicore.contract.patient.response.PatientResponse;
import org.openmrs.Patient;
import org.openmrs.Person;
import org.openmrs.RelationshipType;

import java.util.List;
Expand All @@ -15,6 +17,8 @@ public interface BahmniPatientService {

List<PatientResponse> luceneSearch(PatientSearchParameters searchParameters);

List<PatientResponse> searchSimilarPatients(PatientSearchParameters searchParameters);

public List<Patient> get(String partialIdentifier, boolean shouldMatchExactPatientId);

public List<RelationshipType> getByAIsToB(String aIsToB);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
@Service
@Lazy //to get rid of cyclic dependencies
public class BahmniPatientServiceImpl implements BahmniPatientService {
private static final int SIMILAR_PATIENT_RESULT_LENGTH = 5;
private PersonService personService;
private ConceptService conceptService;
private PatientDao patientDao;
Expand Down Expand Up @@ -83,6 +84,15 @@ public List<PatientResponse> luceneSearch(PatientSearchParameters searchParamete
searchParameters.getFilterPatientsByLocation(), searchParameters.getFilterOnAllIdentifiers());
}

@Override
public List<PatientResponse> searchSimilarPatients(PatientSearchParameters searchParameters) {
return patientDao.getSimilarPatientsUsingLuceneSearch(searchParameters.getName(),
searchParameters.getGender(),
searchParameters.getBirthdate(),
searchParameters.getLoginLocationUuid(),
SIMILAR_PATIENT_RESULT_LENGTH);
}

@Override
public List<Patient> get(String partialIdentifier, boolean shouldMatchExactPatientId) {
return patientDao.getPatients(partialIdentifier, shouldMatchExactPatientId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.bahmni.module.bahmnicore.contract.patient;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.openmrs.module.webservices.rest.web.RequestContext;

import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;


public class PatientSearchParametersTest {

@Mock
RequestContext requestContext;

@Mock
HttpServletRequest request;

@Before
public void setup() {
initMocks(this);
when(requestContext.getRequest()).thenReturn(request);
}

@Test
public void shouldIgnoreEmptyBirthdate () {
when(requestContext.getParameter("birthdate")).thenReturn("");
PatientSearchParameters patientSearchParameters = new PatientSearchParameters(requestContext);

assertNull(patientSearchParameters.getBirthdate());
}

@Test
public void shouldParseBirthdateFromStringAndSetToMidnight () {
when(requestContext.getParameter("birthdate")).thenReturn("1983-01-30");
PatientSearchParameters patientSearchParameters = new PatientSearchParameters(requestContext);

assertEquals(Timestamp.valueOf("1983-01-30 00:00:00"), patientSearchParameters.getBirthdate());
}
}
Loading

0 comments on commit 117df8f

Please sign in to comment.