Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
abb5866
RWA-978 - Initial steps in pushing to MPI
mseaton Feb 21, 2025
bca50da
RWA-978 - Submit post data as json body
mseaton Feb 21, 2025
817ef7a
RWA-978 - Ready for initial testing
mseaton Feb 25, 2025
cbd4a9f
RWA-978 - Add additional checks
mseaton Feb 25, 2025
46379d4
RWA-978 - Rename from MPI to HIE
mseaton Feb 25, 2025
812a578
Fix typos
mseaton Feb 26, 2025
fe4002f
Expose more fine-grained messages and errors to front-end when connec…
mseaton Feb 26, 2025
43115f5
Merge branch 'RWA-978' of https://github.com/PIH/openmrs-module-rwand…
patieru12 Feb 27, 2025
6325154
Merge branch 'master' into RWA-978
mseaton Mar 4, 2025
3ca7e90
RWA-978 - Generate UPID using tempid; update attributes in HIE; updat…
mseaton Mar 4, 2025
be61ab0
Merge branch 'RWA-978' of https://github.com/PIH/openmrs-module-rwand…
patieru12 Mar 5, 2025
99f0b65
enable shr records to view on the clinician side
patieru12 Mar 27, 2025
ae1c51c
README.md
patieru12 Mar 27, 2025
586d22d
resolve conflict used to reduce the log size
patieru12 Apr 14, 2025
3f87c50
change fragment view to summarize visit in location first and later e…
patieru12 Apr 24, 2025
0667704
complete push encounter and Obs to SHR
patieru12 Jul 9, 2025
35feb63
RWA-1035 - Insurance eligibility checking
mseaton Aug 12, 2025
1eb3e8d
RWA-1035 - Insurance eligibility checking
mseaton Aug 12, 2025
219f34f
RWA-1035 - Insurance eligibility checking
mseaton Aug 12, 2025
c0cfb0b
RWA-1035 - Add dialog for choosing eligible member
mseaton Aug 14, 2025
9bc966d
Resolve conflict
patieru12 Aug 15, 2025
75514e9
Merge branch 'master' into patieru12
mseaton Oct 29, 2025
867c99f
Updating to reflect changes to master
mseaton Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.openmrs.module.rwandaemr.config;

import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.api.context.Context;
import org.openmrs.event.Event;
import org.openmrs.module.rwandaemr.event.CreateInsurancePatientListener;
import org.openmrs.module.rwandaemr.event.HieEventListener;
import org.openmrs.module.rwandaemr.integration.UpdateClientRegistryPatientListener;
import org.openmrs.module.rwandaemr.integration.UpdateShrEncounterListener;
import org.openmrs.module.rwandaemr.integration.UpdateShrObsListener;
import org.openmrs.module.rwandaemr.radiology.RadiologyOrderEventListener;

/**
Expand All @@ -16,13 +21,23 @@ public static void setup() {
Event.subscribe(Patient.class, Event.Action.CREATED.name(), getCreateInsurancePatientListener());
Event.subscribe(Patient.class, Event.Action.CREATED.name(), getUpdateClientRegistryPatientListener());
Event.subscribe(Patient.class, Event.Action.UPDATED.name(), getUpdateClientRegistryPatientListener());
/**
* Event for EMR to HIE encounter synchronization
*/
Event.subscribe(Encounter.class, Event.Action.CREATED.name(), getUpdateShrEncounterEventListener());
/**
* Event for EMR to HIE OBS synchronization
*/
Event.subscribe(Obs.class, Event.Action.CREATED.name(), getUpdateShrObservationEventListener());
getRadiologyOrderEventListener().setup();
}

public static void teardown() {
Event.unsubscribe(Patient.class, Event.Action.CREATED, getCreateInsurancePatientListener());
Event.unsubscribe(Patient.class, Event.Action.CREATED, getUpdateClientRegistryPatientListener());
Event.unsubscribe(Patient.class, Event.Action.UPDATED, getUpdateClientRegistryPatientListener());
Event.unsubscribe(Encounter.class, Event.Action.CREATED, getUpdateShrEncounterEventListener());
Event.unsubscribe(Obs.class, Event.Action.CREATED, getUpdateShrObservationEventListener());
getRadiologyOrderEventListener().teardown();
}

Expand All @@ -34,6 +49,13 @@ public static UpdateClientRegistryPatientListener getUpdateClientRegistryPatient
return Context.getRegisteredComponents(UpdateClientRegistryPatientListener.class).get(0);
}

public static HieEventListener getUpdateShrEncounterEventListener(){
return Context.getRegisteredComponents(UpdateShrEncounterListener.class).get(0);
}

public static HieEventListener getUpdateShrObservationEventListener(){
return Context.getRegisteredComponents(UpdateShrObsListener.class).get(0);
}
public static RadiologyOrderEventListener getRadiologyOrderEventListener() {
return Context.getRegisteredComponents(RadiologyOrderEventListener.class).get(0);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.openmrs.module.rwandaemr.event;

import javax.jms.MapMessage;
import javax.jms.Message;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Daemon;
import org.openmrs.event.EventListener;
import org.openmrs.module.DaemonToken;

import lombok.Setter;

public abstract class HieEventListener implements EventListener{
protected final Log log = LogFactory.getLog(getClass());
@Setter private static DaemonToken daemonToken;

@Override
public void onMessage(Message message){
try {
if(!(message instanceof MapMessage)){
throw new IllegalArgumentException("Message is not a MapMessage object");
}

MapMessage mapMessage = (MapMessage) message;
String uuid = mapMessage.getString("uuid");
if(StringUtils.isBlank(uuid)){
throw new IllegalArgumentException("Unable to retrieve encounter uuid from message: " + message);
}

Daemon.runInDaemonThread(() -> handle(uuid, mapMessage), daemonToken);
} catch(Exception e){
handleException(e);
}
}

public abstract void handle(String encounterUuid, MapMessage mapMessage);
public abstract void handleException(Exception e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.openmrs.module.rwandaemr.integration;

import java.util.Date;
import javax.validation.constraints.NotNull;
import org.hl7.fhir.r4.model.Encounter;

import lombok.Data;

@Data
public class ShrEncounter {

private final Encounter encounter;

public ShrEncounter(@NotNull Encounter encounter){
this.encounter = encounter;
}

public String getEncounterUuid(){
if(encounter.hasId()){
return encounter.getIdBase();
}
return null;
}

public Encounter.EncounterLocationComponent getEncounterLocation(){
if(encounter.hasLocation()){
return encounter.getLocationFirstRep();
}
return null;
}

public String getEncounterTypeString(){
if(encounter.hasType()){
return encounter.getTypeFirstRep().getCodingFirstRep().getCode();
}
return null;
}

public String getEncounterTypeDisplay(){
if(encounter.hasType()){
return encounter.getTypeFirstRep().getCodingFirstRep().getDisplay();
}
return null;
}

public Encounter.EncounterParticipantComponent getEncounterSubject(){
if(encounter.hasSubject()){
return encounter.getParticipantFirstRep();
}
return null;
}

public Date getEncounterDate(){
if(encounter.hasPeriod()){
return encounter.getPeriod().getStart();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package org.openmrs.module.rwandaemr.integration;

import ca.uhn.fhir.context.FhirContext;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Encounter;
import org.openmrs.PatientIdentifier;
import org.openmrs.module.rwandaemr.RwandaEmrConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("shrEncounterProvider")
public class ShrEncounterProvider {

protected Log log = LogFactory.getLog(getClass());

private final FhirContext fhirContext;
private final IntegrationConfig integrationConfig;
private final RwandaEmrConfig rwandaEmrConfig;
private final ShrEncounterTranslator shrEncounterTranslator;

public ShrEncounterProvider(
@Autowired @Qualifier("fhirR4") FhirContext fhirContext,
@Autowired IntegrationConfig integrationConfig,
@Autowired RwandaEmrConfig rwandaEmrConfig,
@Autowired ShrEncounterTranslator shrEncounterTranslator
){
this.fhirContext = fhirContext;
this.integrationConfig = integrationConfig;
this.rwandaEmrConfig = rwandaEmrConfig;
this.shrEncounterTranslator = shrEncounterTranslator;
}

public List<ShrEncounter> fetchEncounterFromShr(String upid){
if(!integrationConfig.isHieEnabled()){
throw new IllegalStateException("The HIE connection is not enabled on this server");
}

try(CloseableHttpClient httpClient = HttpUtils.getHieClient()){
String url = integrationConfig.getHieEndpointUrl("/shr/Encounter", "searchSet", "ANY","value", upid, "page", "1", "size", "50");
HttpGet httpGet = new HttpGet(url);
//log.debug("Getting Encounters for " + upid + " from " + url);

try(CloseableHttpResponse response = httpClient.execute(httpGet)){
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode != 200){
throw new IllegalStateException("HIE responded with " + statusCode + ", Please contact HIE support team");
}
HttpEntity httpEntity = response.getEntity();
String data = "";
try{
data = EntityUtils.toString(httpEntity);
} catch(Exception e){
//Here we ignored the string as it can't be catched
}

//Process the Bundle information
Bundle bundle = fhirContext.newJsonParser().parseResource(Bundle.class, data);

List<ShrEncounter> encounters = new ArrayList<>();
if(bundle != null && bundle.hasEntry()){
for(Bundle.BundleEntryComponent entryComponent: bundle.getEntry()){
Encounter fhirEncounter = (Encounter) entryComponent.getResource();
if(fhirEncounter != null){
ShrEncounter shrEncounter = new ShrEncounter(fhirEncounter);
encounters.add(shrEncounter);
}
}

//From here we can return the list of SHR encounters to be presanted to dashboard later
return encounters;
}
}
} catch (Exception e) {
log.debug("Unable to get information from SHR registry");
}
return null;
}

public ShrEncounter fetchEncounterFromShrByUuid(String uuid){
log.info("Please make sure to implement fetch by UUID: " + uuid);
return null;
}

public void updateEncounterInShr(org.openmrs.Encounter encounter) throws Exception {
//check if the HIE is enabled
if(!integrationConfig.isHieEnabled()){
log.debug("Incomplete credentials supplied to connect to shr, skipping");
return;
}

//make sure the patient record has UPID cause if no UPID in the subject the record will not be accepted
// Patient patient = ;
PatientIdentifier upid = encounter.getPatient().getPatientIdentifier(rwandaEmrConfig.getUPID());

if(upid == null){
log.debug("The patient under this encounter does not have UPID, so records can't be pushed to SHR");
return;
}
//log.info("Updating in SHR but first check if the encounter exist.");
//check if the SHR had record first
ShrEncounter shrEncounter = fetchEncounterFromShrByUuid(encounter.getUuid());

//if the record is new make sure to create it
if(shrEncounter == null) {
shrEncounter = new ShrEncounter(new Encounter());
shrEncounter.getEncounter().setId(encounter.getUuid());
}
log.info("Here we need the translator in order to have FHIR Object");
//make sure to update the registry with the current data
shrEncounterTranslator.updateShrEncounter(shrEncounter, encounter);

String endPoint = "/shr/Encounter";
String postBody = fhirContext.newJsonParser().encodeResourceToString(shrEncounter.getEncounter());
// log.debug("End Point: " + endPoint);
// log.debug("Data: " + postBody);

//Now we are sending the request to HIE server
try(CloseableHttpClient httpClient = HttpUtils.getHieClient()){

HttpPost httpPost = new HttpPost(integrationConfig.getHieEndpointUrl(endPoint));
httpPost.setEntity(new StringEntity(postBody));
httpPost.setHeader("Content-Type", "application/json");

//sent the request and wait for the response
try(CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
String data = "";
try{
data = EntityUtils.toString(entity);
} catch(Exception ignored){
log.info(ignored.getMessage());
}
if(statusCode != 201){
throw new IllegalStateException("Http Status code: " + statusCode + "; Response was: " + data);
}
log.debug("Encounter request submitted successfuly");
}
}
}
}
Loading