Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EA-198: Add support for configuring Mother Child relationships within… #237

Merged
merged 8 commits into from
Aug 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,6 @@ public class EmrApiConstants {
));

public static final String GP_USE_LEGACY_DIAGNOSIS_SERVICE = "emrapi.useLegacyDiagnosisService";

public static final String METADATA_MAPPING_MOTHER_CHILD_RELATIONSHIP_TYPE = "emrapi.motherChildRelationshipType";
mseaton marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.openmrs.PatientIdentifierType;
import org.openmrs.PersonAttributeType;
import org.openmrs.Provider;
import org.openmrs.RelationshipType;
import org.openmrs.Role;
import org.openmrs.VisitType;
import org.openmrs.module.emrapi.diagnosis.DiagnosisMetadata;
Expand Down Expand Up @@ -347,4 +348,8 @@ public List<Disposition> getDispositions() {
public DispositionDescriptor getDispositionDescriptor() {
return dispositionService.getDispositionDescriptor();
}

public RelationshipType getMotherChildRelationshipType() {
return getEmrApiMetadataByCode(RelationshipType.class, EmrApiConstants.METADATA_MAPPING_MOTHER_CHILD_RELATIONSHIP_TYPE, false);
}
}
12 changes: 12 additions & 0 deletions api/src/main/java/org/openmrs/module/emrapi/maternal/Child.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.openmrs.module.emrapi.maternal;

import lombok.Data;
import org.openmrs.Patient;
import org.openmrs.module.emrapi.adt.InpatientAdmission;

@Data
public class Child {
private Patient child;
private Patient mother;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts about making this to be private Mother mother;?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep the REST response as simple as possible. You mean have:

public class Mother {
    private Patient mother;
    private Child child;
    private InpatientAdmission motherAdmission;
}


public class Child {
    private Patient child;
    private Mother mother;
    private InpatientAdmission childAdmission;
}

Then on a child you've have to child.mother.mother to get the mother?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I think given what you have in place it would make sense to just have one object:

public class MotherAndChild {
    private Patient mother;
    private Child child;
    private InpatientAdmission motherAdmission;
    private InpatientAdmission childAdmission;
}

And then one service method to getMothersAndChildren(MothersAndChildrenSearchCriteria) that returns a List of these.

That list would contain all children of interest if you pass it a set of child uuids, and would contain all mothers of interest if you pass it was set of mother uuids, and everyone of interest if you just pass it criteria about the visit constraints expected...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking of that, though I thought there was a reason it wasn't going to work, will check again.

private InpatientAdmission childAdmission;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.openmrs.module.emrapi.maternal;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChildrenByMothersSearchCriteria {
private List<String> motherUuids; // restrict to children of these mothers
@Accessors(fluent = true) private boolean requireMotherHasActiveVisit = false; // restrict to children of mothers who have an active visit
@Accessors(fluent = true) private boolean requireChildHasActiveVisit = false; // restrict to children who have an active visit
@Accessors(fluent = true) private boolean requireChildBornDuringMothersActiveVisit = false; // restrict to children who were born during their mother's active visit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this fluent = true? Don't we want these to act and behave like standard Javabean properties?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the getter/setter "requireMotherHasActiveVisit" instead of "isRequireMotherHasActiveVisit"... "is" is the default for "boolean" types and that read horribly. https://projectlombok.org/features/experimental/Accessors

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea why it's called fluent. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what it does, but nothing else that works with JavaBean objects will be able to deal with this. We should make it standard, even if it reads poorly, or try to change the variable name so it reads better with an "is" or "get" prefix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah... let's just stick with the ugly name then, will change.

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.openmrs.module.emrapi.maternal;

import java.util.List;

import org.openmrs.api.OpenmrsService;

public interface MaternalService extends OpenmrsService {
mseaton marked this conversation as resolved.
Show resolved Hide resolved

/**
* Fetches patients who are "children" (personB) of a Mother-Child relationship
* @param criteria search criteria (see class for details)
* @return a list of children, with their linked mothers (note this returns a "Child" per mother-child pair, so a child with multiple mothers will appear multiple times)
*/
List<Child> getChildrenByMothers(ChildrenByMothersSearchCriteria criteria);

/**
* Fetches patients who are "mothers" (personA) of a Mother-Child relationship
* @param criteria search criteria (see class for details)
* @return a list of mothers, with their linked children (note this returns a "Mother" per mother-child pair, so a mother with multiple children will appear multiple times)
*/
List<Mother> getMothersByChildren(MothersByChildrenSearchCriteria criteria);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding these method names, see my suggestion above. These aren't "ByMothers" or "ByChildren" at all. They are really lists of MotherAndChild objects. They aren't "by" anything.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package org.openmrs.module.emrapi.maternal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.openmrs.Patient;
import org.openmrs.RelationshipType;
import org.openmrs.api.APIException;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.module.emrapi.EmrApiProperties;
import org.openmrs.module.emrapi.adt.AdtService;
import org.openmrs.module.emrapi.adt.InpatientAdmission;
import org.openmrs.module.emrapi.adt.InpatientAdmissionSearchCriteria;
import org.openmrs.module.emrapi.db.EmrApiDAO;

public class MaternalServiceImpl extends BaseOpenmrsService implements MaternalService {

private EmrApiProperties emrApiProperties;

private AdtService adtService;

private EmrApiDAO emrApiDAO;

public void setEmrApiProperties(EmrApiProperties emrApiProperties) {
this.emrApiProperties = emrApiProperties;
}

public void setEmrApiDAO(EmrApiDAO emrApiDAO) {
this.emrApiDAO = emrApiDAO;
}

public void setAdtService(AdtService adtService) {
this.adtService = adtService;
}

public List<Child> getChildrenByMothers(ChildrenByMothersSearchCriteria criteria) {

RelationshipType motherChildRelationshipType = emrApiProperties.getMotherChildRelationshipType();

if (motherChildRelationshipType == null) {
throw new APIException("Mother-Child relationship type has not been configured");
}

Map<String, Object> parameters = new HashMap<>();
parameters.put("motherUuids", criteria.getMotherUuids());
parameters.put("childUuids", null);
parameters.put("motherChildRelationshipType", motherChildRelationshipType);
parameters.put("requireMotherHasActiveVisit", criteria.requireMotherHasActiveVisit());
parameters.put("requireChildHasActiveVisit", criteria.requireChildHasActiveVisit());
parameters.put("requireChildBornDuringMothersActiveVisit", criteria.requireChildBornDuringMothersActiveVisit());

List<?> l = emrApiDAO.executeHqlFromResource("hql/mother_child.hql", parameters, List.class);

List<Child> ret = new ArrayList<>();

for (Object req : l) {
Object[] row = (Object[]) req;
Child child = new Child();
child.setMother((Patient) row[0]);
child.setChild((Patient) row[1]);
ret.add(child);
}

// now fetch all the admissions for children in the result set
InpatientAdmissionSearchCriteria inpatientAdmissionSearchCriteria = new InpatientAdmissionSearchCriteria();
inpatientAdmissionSearchCriteria.setPatientIds(new ArrayList<>(ret.stream().map(Child::getChild).map(Patient::getId).collect(Collectors.toSet())));
List<InpatientAdmission> admissions = adtService.getInpatientAdmissions(inpatientAdmissionSearchCriteria);
Map<Patient, InpatientAdmission> admissionsByPatient = new HashMap<>();
for (InpatientAdmission admission : admissions) {
admissionsByPatient.put(admission.getVisit().getPatient(), admission);
}
for (Child child : ret) {
child.setChildAdmission(admissionsByPatient.get(child.getChild()));
}

return ret;
}

public List<Mother> getMothersByChildren(MothersByChildrenSearchCriteria criteria) {
RelationshipType motherChildRelationshipType = emrApiProperties.getMotherChildRelationshipType();

if (motherChildRelationshipType == null) {
throw new APIException("Mother-Child relationship type has not been configured");
}

Map<String, Object> parameters = new HashMap<>();
parameters.put("motherUuids", null);
parameters.put("childUuids", criteria.getChildUuids());
parameters.put("motherChildRelationshipType", motherChildRelationshipType);
parameters.put("requireMotherHasActiveVisit", criteria.requireMotherHasActiveVisit());
parameters.put("requireChildHasActiveVisit", criteria.requireChildHasActiveVisit());
parameters.put("requireChildBornDuringMothersActiveVisit", criteria.requireChildBornDuringMothersActiveVisit());

List<?> l = emrApiDAO.executeHqlFromResource("hql/mother_child.hql", parameters, List.class);

List<Mother> ret = new ArrayList<>();

for (Object req : l) {
Object[] row = (Object[]) req;
Mother mother = new Mother();
mother.setMother((Patient) row[0]);
mother.setChild((Patient) row[1]);
ret.add(mother);
}

// now fetch all the admissions for mothers in the result set
InpatientAdmissionSearchCriteria inpatientAdmissionSearchCriteria = new InpatientAdmissionSearchCriteria();
inpatientAdmissionSearchCriteria.setPatientIds(new ArrayList<>(ret.stream().map(Mother::getMother).map(Patient::getId).collect(Collectors.toSet())));
List<InpatientAdmission> admissions = adtService.getInpatientAdmissions(inpatientAdmissionSearchCriteria);
Map<Patient, InpatientAdmission> admissionsByPatient = new HashMap<>();
for (InpatientAdmission admission : admissions) {
admissionsByPatient.put(admission.getVisit().getPatient(), admission);
}
for (Mother mother : ret) {
mother.setMotherAdmission(admissionsByPatient.get(mother.getMother()));
}


return ret;
}
}
13 changes: 13 additions & 0 deletions api/src/main/java/org/openmrs/module/emrapi/maternal/Mother.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.openmrs.module.emrapi.maternal;

import lombok.Data;
import org.openmrs.Patient;
import org.openmrs.module.emrapi.adt.InpatientAdmission;


@Data
public class Mother {
private Patient mother;
private Patient child;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts about making this private Child child;?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above again.

private InpatientAdmission motherAdmission;
mseaton marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.openmrs.module.emrapi.maternal;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MothersByChildrenSearchCriteria {
private List<String> childUuids; // restrict to mothers of these children
@Accessors(fluent = true) private Boolean requireMotherHasActiveVisit = false; // restrict to mothers who have an active visit
@Accessors(fluent = true) private Boolean requireChildHasActiveVisit = false; // restrict to mothers of children who have an active visit
@Accessors(fluent = true) private boolean requireChildBornDuringMothersActiveVisit = false; // restrict to mothers who had a child born during their active visit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto


}
18 changes: 18 additions & 0 deletions api/src/main/resources/hql/mother_child.hql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
select
mother,
child
from
Relationship as motherChildRelationship
inner join motherChildRelationship.personA as mother
inner join motherChildRelationship.personB as child
Comment on lines +6 to +7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are relationships always uni-directional like this, i.e., with a fixed A/B? (The flexibility around this is one of the things I've always disliked about relationships).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are flexible, but we are saying this is based around have a standardized uni-directional "Mother to Child" relationship type

where
((:motherUuids) is null or mother.uuid in (:motherUuids))
and ((:childUuids) is null or child.uuid in (:childUuids))
and motherChildRelationship.relationshipType = :motherChildRelationshipType
and (:requireMotherHasActiveVisit = false or (select count(motherVisit) from Visit as motherVisit where motherVisit.patient = mother and motherVisit.stopDatetime is null and motherVisit.voided = false) > 0)
and (:requireChildHasActiveVisit = false or (select count(childVisit) from Visit as childVisit where childVisit.patient = child and childVisit.stopDatetime is null and childVisit.voided = false) > 0)
and (:requireChildBornDuringMothersActiveVisit = false or (select count(motherVisit) from Visit as motherVisit where motherVisit.patient = mother and motherVisit.stopDatetime is null and motherVisit.voided = false
and year(child.birthdate) >= year(motherVisit.startDatetime)
and month(child.birthdate) >= month(motherVisit.startDatetime)
and day(child.birthdate) >= day(motherVisit.startDatetime)) > 0)
and mother.voided = false and child.voided = false and motherChildRelationship.voided = false
28 changes: 28 additions & 0 deletions api/src/main/resources/moduleApplicationContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@
</property>
</bean>

<bean id="maternalService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="target">
<bean class="org.openmrs.module.emrapi.maternal.MaternalServiceImpl">
<property name="adtService" ref="adtService"/>
<property name="emrApiProperties" ref="emrApiProperties"/>
<property name="emrApiDAO" ref="emrApiDAOImpl"/>
</bean>
</property>
<property name="preInterceptors">
<ref bean="serviceInterceptors"/>
</property>
<property name="transactionAttributeSource">
<ref bean="transactionAttributeSource"/>
</property>
</bean>

<bean parent="serviceContext">
<property name="moduleService">
<list merge="true">
<value>org.openmrs.module.emrapi.maternal.MaternalService</value>
<ref bean="maternalService"/>
</list>
</property>
</bean>

<bean id="exitFromCareService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/>
Expand Down
Loading
Loading