use of org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality in project openmrs-module-pihcore by PIH.
the class PihPatientSearchAlgorithmTest method shouldDisregardAccentMarksWhenMakingMatch.
@Test
public void shouldDisregardAccentMarksWhenMakingMatch() {
Patient patient = new Patient();
PersonName name = new PersonName();
patient.addName(name);
// this is an exact name match against the database
name.setGivenName("Jarus");
name.setFamilyName("Rapondi");
patient.setBirthdate(new Date());
patient.setGender("M");
List<PatientAndMatchQuality> results = searchAlgorithm.findSimilarPatients(patient, null, null, 10);
double score = results.get(0).getScore();
// now we add some accent marks
name.setGivenName("Járùs");
name.setFamilyName("Rápóndi");
// if we do a search again, the score should be the same, assuming the accent marks were ignored
results = searchAlgorithm.findSimilarPatients(patient, null, null, 10);
assertThat(results.get(0).getScore(), is(score));
}
use of org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality in project openmrs-module-pihcore by PIH.
the class PihPatientSearchAlgorithmTest method shouldReturnEmptyListIfNoNameBirthdateOrGender.
@Test
public void shouldReturnEmptyListIfNoNameBirthdateOrGender() {
Patient patient = new Patient();
List<PatientAndMatchQuality> results = searchAlgorithm.findSimilarPatients(patient, null, 2.0, 10);
assertThat(results.size(), is(0));
}
use of org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality in project openmrs-module-pihcore by PIH.
the class PihPatientSearchAlgorithmTest method shouldMatchPhoneNumberOnNumericsOnly.
@Test
public void shouldMatchPhoneNumberOnNumericsOnly() {
// first get the base score for this patient
Patient patient = new Patient();
PersonName name = new PersonName();
patient.addName(name);
name.setGivenName("Jarusz");
name.setFamilyName("Rapondee");
patient.setBirthdate(new Date());
patient.setGender("M");
List<PatientAndMatchQuality> results = searchAlgorithm.findSimilarPatients(patient, null, null, 10);
double scoreWithoutAddress = results.get(0).getScore();
// now add in address and recalculate
PersonAttribute attribute = new PersonAttribute();
attribute.setAttributeType(Metadata.getPhoneNumberAttributeType());
attribute.setValue("6178675309");
patient.addAttribute(attribute);
results = searchAlgorithm.findSimilarPatients(patient, null, null, 10);
double scoreWithAddress = results.get(0).getScore();
// in our test config we've weighed telephone at 3 points
assertThat(scoreWithAddress - scoreWithoutAddress, is(3.0));
assertTrue(results.get(0).getMatchedFields().contains("attributes.Telephone Number"));
}
use of org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality in project openmrs-module-pihcore by PIH.
the class PihPatientSearchAlgorithm method findSimilarPatients.
// TODO what about highlighting/showing the reasons for the match on the screen?
// TODO what about exact patient search algo? do we want to still use that, or just use this with a higher cutoff?
// TODO phonetics on attributes?
// TODO what about max results on initial query? needed?
// TODO more tests? make sure matchedfields are properly included?
// TODO address hierarchy field changes don't trigger a re-search
@Override
public List<PatientAndMatchQuality> findSimilarPatients(Patient patient, Map<String, Object> otherDataPoints, Double cutoff, Integer maxResults) {
// only do search if we have family name, given name, gender, and birthdate--return empty list otherwise
if (!hasName(patient) || !hasBirthdate(patient, otherDataPoints) || patient.getGender() == null) {
return new ArrayList<PatientAndMatchQuality>();
}
// our initial search to find a "base cohort"; hits will only occur if there is a phonetic match on both given name and family name
List<Patient> patients = getPatientsByPhonetics(patient.getGivenName(), patient.getFamilyName());
List<PatientAndMatchQuality> matches = new ArrayList<PatientAndMatchQuality>();
for (Patient match : patients) {
List<String> matchedFields = new ArrayList<String>();
double score = 0;
// these are matched by the fact that we made it through the name phonetics match
matchedFields.add("names.givenName");
matchedFields.add("names.familyName");
// one point if gender matches
if (patient.getGender() != null && match.getGender() != null) {
if (patient.getGender().equals(match.getGender())) {
score += 1;
matchedFields.add("gender");
} else {
score += -8;
}
}
// exact birthdate match = 10 pts; otherwise, if years between < 5, assign points based on 5-years between
if (patient.getBirthdate() != null && match.getBirthdate() != null) {
if (!match.getBirthdateEstimated() && new DateTime(patient.getBirthdate()).withTimeAtStartOfDay().equals(new DateTime(match.getBirthdate()).withTimeAtStartOfDay())) {
score += 10;
matchedFields.add("birthdate");
} else {
int yearsBetween = Math.abs(Years.yearsBetween(new DateTime(patient.getBirthdate()), new DateTime(match.getBirthdate())).getYears());
if (yearsBetween <= 5) {
score += (6 - yearsBetween);
matchedFields.add("birthdate");
} else {
score += -8;
}
}
} else // try estimated birthdate
{
Integer patientEstimatedAge = null;
if (otherDataPoints != null && otherDataPoints.containsKey("birthdateYears") && otherDataPoints.get("birthdateYears") != null) {
patientEstimatedAge = (Integer) otherDataPoints.get("birthdateYears");
} else if (otherDataPoints != null && otherDataPoints.containsKey("birthdateMonths") && otherDataPoints.get("birthdateMonths") != null) {
patientEstimatedAge = 0;
}
if (patientEstimatedAge != null) {
int yearsBetween = Math.abs(patientEstimatedAge - match.getAge());
if (yearsBetween < 5) {
score += (6 - yearsBetween);
matchedFields.add("birthdate");
}
}
}
// check for *exact* name matches
boolean familyNameMatch = false;
boolean givenNameMatch = false;
boolean middleNameMatch = false;
familyNameMatch = nameExactMatch(patient.getFamilyName(), match.getFamilyName());
givenNameMatch = nameExactMatch(patient.getGivenName(), match.getGivenName());
middleNameMatch = nameExactMatch(patient.getMiddleName(), match.getMiddleName());
if (familyNameMatch && givenNameMatch && middleNameMatch) {
score += 4;
} else if (familyNameMatch && givenNameMatch) {
score += 2;
} else if (familyNameMatch || givenNameMatch || middleNameMatch) {
score += 0.5;
}
// check for address matches
if (config.getRegistrationConfig() != null && config.getRegistrationConfig().getSimilarPatientsSearch() != null && config.getRegistrationConfig().getSimilarPatientsSearch().containsKey("addressFields")) {
Map<String, String> addressFields = (Map<String, String>) config.getRegistrationConfig().getSimilarPatientsSearch().get("addressFields");
PersonAddress patientAddress = patient.getPersonAddress();
PersonAddress matchAddress = match.getPersonAddress();
if (addressFields != null && patientAddress != null && matchAddress != null) {
for (String addressField : addressFields.keySet()) {
try {
String patientField = (String) PropertyUtils.getProperty(patientAddress, addressField);
String matchField = (String) PropertyUtils.getProperty(matchAddress, addressField);
if (StringUtils.isNotBlank(patientField) && StringUtils.isNotBlank(matchField) && stripAccentMarks(patientField).equalsIgnoreCase(stripAccentMarks(matchField))) {
score += new Double(addressFields.get(addressField));
matchedFields.add("addresses." + addressField);
}
} catch (Exception e) {
log.error("Unable to access " + addressField + " on patient during similar patient search");
}
}
}
}
// check person attribute matches
if (config.getRegistrationConfig() != null && config.getRegistrationConfig().getSimilarPatientsSearch() != null && config.getRegistrationConfig().getSimilarPatientsSearch().containsKey("personAttributeTypes")) {
Map<String, String> personAttributeTypes = (Map<String, String>) config.getRegistrationConfig().getSimilarPatientsSearch().get("personAttributeTypes");
for (String personAttributeType : personAttributeTypes.keySet()) {
String patientAttribute = patient.getAttribute(personAttributeType) != null ? patient.getAttribute(personAttributeType).getValue() : null;
String matchAttribute = match.getAttribute(personAttributeType) != null ? match.getAttribute(personAttributeType).getValue() : null;
if (StringUtils.isNotBlank(patientAttribute) && StringUtils.isNotBlank(matchAttribute)) {
// special case telephone number: strip all non-numerics
if (personAttributeType.equalsIgnoreCase(Metadata.getPhoneNumberAttributeType().getName())) {
patientAttribute = patientAttribute.replaceAll("[^0-9]", "");
matchAttribute = matchAttribute.replaceAll("[^0-9]", "");
}
// special case First Name of Mother: convert to name phonetics
if (personAttributeType.equalsIgnoreCase(Metadata.getMothersFirstNameAttributeType().getName())) {
patientAttribute = NamePhoneticsUtil.encodeString(patientAttribute, adminService.getGlobalProperty("namephonetics.givenNameStringEncoder"));
matchAttribute = NamePhoneticsUtil.encodeString(matchAttribute, adminService.getGlobalProperty("namephonetics.givenNameStringEncoder"));
}
if (stripAccentMarks(patientAttribute).equalsIgnoreCase(stripAccentMarks(matchAttribute))) {
score += new Double(personAttributeTypes.get(personAttributeType));
matchedFields.add("attributes." + personAttributeType);
}
}
}
}
// only take matches that make the cut
if (cutoff == null) {
matches.add(new PatientAndMatchQuality(match, score, matchedFields));
} else {
if (score >= cutoff) {
matches.add(new PatientAndMatchQuality(match, score, matchedFields));
}
}
}
Collections.sort(matches);
if (maxResults != null && matches.size() > maxResults) {
return matches.subList(0, maxResults);
} else {
return matches;
}
}
Aggregations