Skip to content

Commit

Permalink
Martina, Roni, Eyal | BAH-460 | add api to search patients with simil…
Browse files Browse the repository at this point in the history
…ar name and gender
  • Loading branch information
mduemcke committed Jun 8, 2018
1 parent 140a6db commit a88cb84
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class PatientSearchParameters {
private Boolean filterPatientsByLocation;
private String identifier;
private String name;
private String gender;
private String addressFieldName;
private String addressFieldValue;
private Integer start;
Expand Down Expand Up @@ -43,6 +44,7 @@ public PatientSearchParameters(RequestContext context) {
} else {
this.setAddressFieldName("city_village");
}
this.setGender(context.getParameter("gender"));
this.setAddressFieldValue(context.getParameter("addressFieldValue"));
Map parameterMap = context.getRequest().getParameterMap();
this.setAddressSearchResultFields((String[]) parameterMap.get("addressSearchResultsConfig"));
Expand Down Expand Up @@ -71,6 +73,14 @@ public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

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

public String getAddressFieldName() {
return addressFieldName;
}
Expand Down
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 @@ -17,11 +16,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 @@ -34,8 +35,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 @@ -51,7 +52,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 @@ -19,6 +19,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, 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.HashMap;
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,68 @@ 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, String loginLocationUuid, Integer length) {
PatientResponseMapper patientResponseMapper = new PatientResponseMapper(Context.getVisitService(),new BahmniVisitLocationServiceImpl(Context.getLocationService()));
List<Patient> patients = getPatientsByNameAndGender(name, gender, 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> getPatientsByNameAndGender(String name, String gender, Integer length) {
if(isNullOrEmpty(name, gender)) {
return new ArrayList<>();
}

HibernatePatientDAO patientDAO = new HibernatePatientDAO();
patientDAO.setSessionFactory(sessionFactory);
List<Patient> patients = new ArrayList<Patient>();
String query = LuceneQuery.escapeQuery(name);
PersonLuceneQuery personLuceneQuery = new PersonLuceneQuery(sessionFactory);
LuceneQuery<PersonName> nameQuery = personLuceneQuery.getPatientNameQueryWithOrParser(query, false);
List<PersonName> persons = nameQuery.list().stream()
.filter(
personName ->
personName.getPreferred()
&& checkGender(personName.getPerson(), gender)
).collect(toList());
persons = persons.subList(0, Math.min(length, persons.size()));
persons.forEach(person -> patients.add(new Patient(person.getPerson())));
return patients;
}

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


private Boolean checkGender(Person person, String gender) {
if(gender != null && !gender.isEmpty()){
return gender.equals(person.getGender());
} 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 Down Expand Up @@ -179,7 +235,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 +257,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 +285,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,14 @@ 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.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
Expand Up @@ -7,7 +7,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.openmrs.Patient;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
Expand Down Expand Up @@ -213,4 +212,54 @@ public void shouldNotReturnDuplicatePatientsEvenIfTwoIdentifiersMatches() {
assertTrue(patient.getExtraIdentifiers().contains("200006"));
}

@Test
public void shouldSearchSimilarPatientByPatientName() {
List<PatientResponse> patients = patientDao.getSimilarPatientsUsingLuceneSearch("Peet", "", "c36006e5-9fbb-4f20-866b-0ece245615a1", 5);
assertEquals(2, patients.size());

PatientResponse patient1 = patients.get(0);
PatientResponse patient2 = patients.get(1);
assertEquals(patient1.getGivenName(), "Horatio");
assertEquals(patient1.getMiddleName(), "Peeter");
assertEquals(patient1.getFamilyName(), "Sinha");
assertEquals(patient2.getGivenName(), "John");
assertEquals(patient2.getMiddleName(), "Peeter");
assertEquals(patient2.getFamilyName(), "Sinha");
}

@Test
public void shouldSearchSimilarPatientByPatientNameAndUseLimitResult() {
List<PatientResponse> patients = patientDao.getSimilarPatientsUsingLuceneSearch("Peet", "", "c36006e5-9fbb-4f20-866b-0ece245615a1", 1);
assertEquals("Should limit number of results",1, patients.size());
PatientResponse patient1 = patients.get(0);

assertEquals(patient1.getGivenName(), "Horatio");
assertEquals(patient1.getMiddleName(), "Peeter");
assertEquals(patient1.getFamilyName(), "Sinha");
}

@Test
public void shouldSearchSimilarPatientByPatientNameAndGender() {
List<PatientResponse> patients = patientDao.getSimilarPatientsUsingLuceneSearch("Peet", "F", "c36006e5-9fbb-4f20-866b-0ece245615a1", 5);
PatientResponse patient1 = patients.get(0);

assertEquals(1, patients.size());
assertEquals(patient1.getGivenName(), "John");
assertEquals(patient1.getMiddleName(), "Peeter");
assertEquals(patient1.getFamilyName(), "Sinha");
}

@Test
public void shouldReturnEmptyListIfAllSearchTermsAreEmpty() {
List<PatientResponse> patients = patientDao.getSimilarPatientsUsingLuceneSearch("", "", "c36006e5-9fbb-4f20-866b-0ece245615a1", 5);

assertEquals(0, patients.size());
}

@Test
public void shouldReturnResultsIfOneFieldIsSet() {
List<PatientResponse> patients = patientDao.getSimilarPatientsUsingLuceneSearch("", "F", "c36006e5-9fbb-4f20-866b-0ece245615a1", 5);

assertEquals(5, patients.size());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.bahmni.module.bahmnicore.service.impl;

import org.bahmni.module.bahmnicore.contract.patient.PatientSearchParameters;
import org.bahmni.module.bahmnicore.contract.patient.response.PatientConfigResponse;
import org.bahmni.module.bahmnicore.dao.PatientDao;
import org.junit.Before;
Expand All @@ -9,12 +10,16 @@
import org.openmrs.PersonAttributeType;
import org.openmrs.api.ConceptService;
import org.openmrs.api.PersonService;
import org.openmrs.module.webservices.rest.web.RequestContext;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
Expand All @@ -25,6 +30,8 @@ public class BahmniPatientServiceImplTest {
@Mock
private ConceptService conceptService;
@Mock
RequestContext requestContext;
@Mock
private PatientDao patientDao;

private BahmniPatientServiceImpl bahmniPatientService;
Expand Down Expand Up @@ -67,4 +74,18 @@ public void shouldGetPatientByPartialIdentifier() throws Exception {
bahmniPatientService.get("partial_identifier", shouldMatchExactPatientId);
verify(patientDao).getPatients("partial_identifier", shouldMatchExactPatientId);
}

@Test
public void shouldCallGetSimilarPatientsUsingLuceneSearch() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(requestContext.getRequest()).thenReturn(request);
when(request.getParameterMap()).thenReturn(new HashMap<>());
PatientSearchParameters patientSearchParameters = new PatientSearchParameters(requestContext);
patientSearchParameters.setName("John");
patientSearchParameters.setGender("M");
patientSearchParameters.setLoginLocationUuid("someUUid");

bahmniPatientService.searchSimilarPatients(patientSearchParameters);
verify(patientDao).getSimilarPatientsUsingLuceneSearch("John", "M", "someUUid", 5);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,19 @@ public ResponseEntity<AlreadyPaged<PatientResponse>> luceneSearch(HttpServletReq
return new ResponseEntity(RestUtil.wrapErrorResponse(e, e.getMessage()), HttpStatus.BAD_REQUEST);
}
}

@RequestMapping(value="similar", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<AlreadyPaged<PatientResponse>> searchSimilarPerson(HttpServletRequest request,
HttpServletResponse response) throws ResponseException{
RequestContext requestContext = RestUtil.getRequestContext(request, response);
PatientSearchParameters searchParameters = new PatientSearchParameters(requestContext);
try {
List<PatientResponse> patients = bahmniPatientService.searchSimilarPatients(searchParameters);
AlreadyPaged alreadyPaged = new AlreadyPaged(requestContext, patients, false);
return new ResponseEntity(alreadyPaged, HttpStatus.OK);
}catch (IllegalArgumentException e){
return new ResponseEntity(RestUtil.wrapErrorResponse(e, e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
}
Loading

0 comments on commit a88cb84

Please sign in to comment.