Skip to content

Commit

Permalink
chore(#142): sonar fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
witash committed Oct 22, 2024
1 parent 86bdc31 commit 1a5be52
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 81 deletions.
68 changes: 47 additions & 21 deletions mediator/src/utils/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ export async function createChtFollowUpRecord(patientId: string) {
}
}

export async function getLocationFromOpenMRSPatient(fhirPatient: fhir4.Patient) {
/*
Get the address field from an OpenMRS Patient
Assuming it is stored at a specific path in the fhir Patient
*/
function getAddressFromOpenMRSPatient(fhirPatient: fhir4.Patient) {
// first, extract address value; is fchv area available?
const addresses = fhirPatient.address?.[0]?.extension?.[0]?.extension;
let addressKey = "http://fhir.openmrs.org/ext/address#address4"
Expand All @@ -51,10 +55,47 @@ export async function getLocationFromOpenMRSPatient(fhirPatient: fhir4.Patient)
addressKey = "http://fhir.openmrs.org/ext/address#address5"
addressValue = addresses?.find((ext: any) => ext.url === addressKey)?.valueString;

// still no... return nothing
if (!addressValue) {
return '';
}
}
return addressValue;
}

/*
* Query CouchDB to get a place_id from a name
* This is a workaround for patients not having an place_id
* in the address field (as described above)
* Because it relies on names matching excatly, and qurying a
* CHT couchdb directly, it is not intended for general use
*/
async function getPlaceIdFromCouch(addressValue: string) {
const query: CouchDBQuery = {
selector: {
type: "contact",
name: addressValue
},
fields: ['place_id']
}
const location = await queryCht(query);

// edge cases can result in more than one location, get first matching
// if not found by name, no more we can do, give up
if (!location.data?.docs || location.data.docs.length == 0){
return '';
} else {
return location.data.docs[0].place_id;
}
}

/*
* get a CHT place_id from an OpenMRS patient
* assumes that either the patient has an address containing the palce id
* (see above), or the name matches the contact name in CHT
* It is to support a specific workflow and is not intended for general use.
*/
export async function getLocationFromOpenMRSPatient(fhirPatient: fhir4.Patient) {
// if no address found, return empty string
const addressValue = getAddressFromOpenMRSPatient(fhirPatient);
if (!addressValue) {
return '';
}

// does the name have a place id included?
Expand All @@ -66,22 +107,7 @@ export async function getLocationFromOpenMRSPatient(fhirPatient: fhir4.Patient)
return match[1];
} else {
// if not, query by name
const query: CouchDBQuery = {
selector: {
type: "contact",
name: addressValue
},
fields: ['place_id']
}
const location = await queryCht(query);

// edge cases can result in more than one location, get first matching
// if not found by name, no more we can do, give up
if (!location.data?.docs || location.data.docs.length == 0){
return '';
} else {
return location.data.docs[0].place_id;
}
return getPlaceIdFromCouch(addressValue);
}
}

Expand Down
46 changes: 35 additions & 11 deletions mediator/src/utils/fhir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,43 @@ export async function getFhirResourcesSince(lastUpdated: Date, resourceType: str
return getResourcesSince(FHIR.url, lastUpdated, resourceType);
}

/*
* get the "next" url from a fhir paginated response and a base url
*/
function getNextUrl(url: string, pagination: any) {
let nextUrl = '';
const nextLink = pagination.link && pagination.link.find((link: any) => link.relation === 'next');
if (nextLink?.url) {
const qs = nextLink.url.split('?')[1];
nextUrl = `${url}/?${qs}`;
}
return nextUrl;
}

/*
* Gets the full url for a resource type, given base url
* For some resource types, it is usefult o get related resources
* This function returns the full url including include clauses
* currently it is only for encounters, to include observations
* and the subject patient
*/
function getResourceUrl(baseUrl: string, lastUpdated: Date, resourceType: string) {
let url = `${baseUrl}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`;
// for encounters, include related resources
if (resourceType === 'Encounter') {
url = url + '&_revinclude=Observation:encounter&_include=Encounter:patient';
}
return url
}

/*
* get resources of a given type from url, where lastUpdated is > the given data
* if results are paginated, goes through all pages
*/
export async function getResourcesSince(url: string, lastUpdated: Date, resourceType: string) {
try {
let nextUrl = `${url}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`;
let results: fhir4.Resource[] = [];
// for encounters, include related resources
if (resourceType === 'Encounter') {
nextUrl = nextUrl + '&_revinclude=Observation:encounter&_include=Encounter:patient';
}
let nextUrl = getResourceUrl(url, lastUpdated, resourceType);

while (nextUrl) {
const res = await axios.get(nextUrl, axiosOptions);
Expand All @@ -198,12 +227,7 @@ export async function getResourcesSince(url: string, lastUpdated: Date, resource
results = results.concat(res.data.entry.map((entry: any) => entry.resource));
}

const nextLink = res.data.link && res.data.link.find((link: any) => link.relation === 'next');
nextUrl = nextLink ? nextLink.url : null;
if (nextUrl) {
const qs = nextUrl.split('?')[1];
nextUrl = `${url}/?${qs}`;
}
nextUrl = getNextUrl(url, res.data);
}
return { status: 200, data: results };
} catch (error: any) {
Expand Down
117 changes: 72 additions & 45 deletions mediator/src/utils/openmrs_sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,27 +211,39 @@ export async function sendEncounterToOpenMRS(
}

logger.info(`Sending Encounter ${encounter.id} to OpenMRS`);

const patient = getPatient(encounter, references);
const observations = getObservations(encounter, references);
const patientId = getIdType(patient, openMRSIdentifierType);
const openMRSVisit = buildOpenMRSVisit(patientId, encounter);

const visitResponse = await createOpenMRSResource(openMRSVisit[0]);
if (visitResponse.status == 200 || visitResponse.status == 201) {
const visitNoteResponse = await createOpenMRSResource(openMRSVisit[1]);
if (visitNoteResponse.status == 200 || visitNoteResponse.status == 201) {
const visitNote = visitNoteResponse.data as fhir4.Encounter;
// save openmrs id on orignal encounter
logger.info(`Updating Encounter ${encounter.id} with openMRSId ${visitNote.id}`);
copyIdToNamedIdentifier(visitNote, encounter, openMRSIdentifierType);
addSourceMeta(visitNote, chtSource);
await updateFhirResource(encounter);
observations.forEach((observation) => {
logger.info(`Sending Observation ${observation.code!.coding![0]!.code} to OpenMRS`);
const openMRSObservation = buildOpenMRSObservation(observation, patientId, visitNote.id || '');
createOpenMRSResource(openMRSObservation);
});
}
if (visitResponse.status != 201) {
logger.error(`Error saving visit to OpenMRS ${encounter.id}: ${visitResponse.status}`);
return
}

const visitNoteResponse = await createOpenMRSResource(openMRSVisit[1]);
if (visitNoteResponse.status != 201) {
logger.error(`Error saving visit note to OpenMRS ${encounter.id}: ${visitNoteResponse.status}`);
return
}

const visitNote = visitNoteResponse.data as fhir4.Encounter;

logger.info(`Updating Encounter ${encounter.id} with openMRSId ${visitNote.id}`);

// save openmrs id on orignal encounter
copyIdToNamedIdentifier(visitNote, encounter, openMRSIdentifierType);
addSourceMeta(visitNote, chtSource);

await updateFhirResource(encounter);

observations.forEach((observation) => {
logger.info(`Sending Observation ${observation.code!.coding![0]!.code} to OpenMRS`);
const openMRSObservation = buildOpenMRSObservation(observation, patientId, visitNote.id || '');
createOpenMRSResource(openMRSObservation);
});
}

/*
Expand Down Expand Up @@ -259,44 +271,59 @@ export async function sendEncounterToFhir(
logger.error(`Not re-sending encounter from cht ${encounter.id}`);
return
}

if (!encounter.period?.end) {
logger.error(`Not sending encounter which is incomplete ${encounter.id}`);
return
}

logger.info(`Sending Encounter ${encounter.id} to FHIR`);
const patient = getPatient(encounter, references);

const observations = getObservations(encounter, references);
if (patient && patient.id) {
// get patient from FHIR to resolve reference
const patientResponse = await getFHIRPatientResource(patient.id);
if (patientResponse.status == 200 || patientResponse.status == 201) {
const existingPatient = patientResponse.data?.entry[0].resource;
copyIdToNamedIdentifier(encounter, encounter, openMRSIdentifierType);
addSourceMeta(encounter, openMRSSource);

logger.info(`Replacing ${encounter.subject!.reference} with ${patient.id} for ${encounter.id}`);
replaceReference(encounter, 'subject', existingPatient);

// remove unused references
delete encounter.participant;
delete encounter.location;

const response = await updateFhirResource(encounter);
if (response.status == 200 || response.status == 201) {
observations.forEach(o => sendObservationToFhir(o, existingPatient));

logger.info(`Sending Encounter ${encounter.id} to CHT`);
const chtResponse = await chtRecordFromObservations(existingPatient.id, observations);
if (chtResponse.status == 200) {
const chtId = chtResponse.data.id;
addId(encounter, chtDocumentIdentifierType, chtId)
await updateFhirResource(encounter);
}
}
}
} else {

const patient = getPatient(encounter, references);
if (!patient?.id) {
logger.error(`Patient ${encounter.subject!.reference} not found for ${encounter.id}`);
return
}

// get patient from FHIR to resolve reference
const patientResponse = await getFHIRPatientResource(patient.id);
if (patientResponse.status != 200) {
logger.error(`Error getting Patient ${patient.id}: ${patientResponse.status}`);
return
}

const existingPatient = patientResponse.data?.entry[0].resource;
copyIdToNamedIdentifier(encounter, encounter, openMRSIdentifierType);
addSourceMeta(encounter, openMRSSource);

logger.info(`Replacing ${encounter.subject!.reference} with ${patient.id} for ${encounter.id}`);
replaceReference(encounter, 'subject', existingPatient);

// remove unused references
delete encounter.participant;
delete encounter.location;

const response = await updateFhirResource(encounter);
if (response.status != 201) {
logger.error(`Error saving encounter to fhir ${encounter.id}: ${response.status}`);
return
}

observations.forEach(o => sendObservationToFhir(o, existingPatient));

logger.info(`Sending Encounter ${encounter.id} to CHT`);
const chtResponse = await chtRecordFromObservations(existingPatient.id, observations);
if (chtResponse.status != 200) {
logger.error(`Error saving encounter to cht ${encounter.id}: ${chtResponse.status}`);
return
}

const chtId = chtResponse.data.id;
addId(encounter, chtDocumentIdentifierType, chtId)

await updateFhirResource(encounter);
}

/*
Expand Down
8 changes: 4 additions & 4 deletions mediator/src/utils/tests/openmrs_sync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('OpenMRS Sync', () => {
});
jest.spyOn(fhir, 'updateFhirResource').mockResolvedValueOnce({
data: openMRSPatient,
status: 200
status: 201
});
jest.spyOn(cht, 'createChtPatient')

Expand Down Expand Up @@ -142,7 +142,7 @@ describe('OpenMRS Sync', () => {
});
jest.spyOn(openmrs, 'createOpenMRSResource').mockResolvedValueOnce({
data: fhirPatient,
status: 200
status: 201
});
jest.spyOn(fhir, 'updateFhirResource')

Expand Down Expand Up @@ -190,7 +190,7 @@ describe('OpenMRS Sync', () => {

jest.spyOn(fhir, 'updateFhirResource').mockResolvedValue({
data: [],
status: 200,
status: 201,
});

jest.spyOn(fhir, 'createFhirResource')
Expand Down Expand Up @@ -232,7 +232,7 @@ describe('OpenMRS Sync', () => {

jest.spyOn(openmrs, 'createOpenMRSResource').mockResolvedValue({
data: [],
status: 200,
status: 201,
});

const startTime = new Date();
Expand Down

0 comments on commit 1a5be52

Please sign in to comment.