Search in sources :

Example 6 with PatientAndMatchQuality

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));
}
Also used : PersonName(org.openmrs.PersonName) PatientAndMatchQuality(org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality) Patient(org.openmrs.Patient) Date(java.util.Date) Test(org.junit.Test) PihCoreContextSensitiveTest(org.openmrs.module.pihcore.PihCoreContextSensitiveTest)

Example 7 with PatientAndMatchQuality

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));
}
Also used : PatientAndMatchQuality(org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality) Patient(org.openmrs.Patient) Test(org.junit.Test) PihCoreContextSensitiveTest(org.openmrs.module.pihcore.PihCoreContextSensitiveTest)

Example 8 with PatientAndMatchQuality

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"));
}
Also used : PersonName(org.openmrs.PersonName) PatientAndMatchQuality(org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality) Patient(org.openmrs.Patient) Date(java.util.Date) PersonAttribute(org.openmrs.PersonAttribute) Test(org.junit.Test) PihCoreContextSensitiveTest(org.openmrs.module.pihcore.PihCoreContextSensitiveTest)

Example 9 with PatientAndMatchQuality

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;
    }
}
Also used : PersonAddress(org.openmrs.PersonAddress) ArrayList(java.util.ArrayList) PatientAndMatchQuality(org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality) Patient(org.openmrs.Patient) DateTime(org.joda.time.DateTime) Map(java.util.Map)

Aggregations

Patient (org.openmrs.Patient)9 PatientAndMatchQuality (org.openmrs.module.registrationcore.api.search.PatientAndMatchQuality)9 Test (org.junit.Test)8 PihCoreContextSensitiveTest (org.openmrs.module.pihcore.PihCoreContextSensitiveTest)8 Date (java.util.Date)7 PersonName (org.openmrs.PersonName)7 PersonAttribute (org.openmrs.PersonAttribute)3 PersonAddress (org.openmrs.PersonAddress)2 ArrayList (java.util.ArrayList)1 Map (java.util.Map)1 DateTime (org.joda.time.DateTime)1