Skip to content

Commit

Permalink
feat(#114): get observations and patients together with encounters
Browse files Browse the repository at this point in the history
  • Loading branch information
witash committed May 31, 2024
1 parent 6d03a4f commit aba7c95
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 138 deletions.
2 changes: 1 addition & 1 deletion mediator/src/controllers/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function createEncounter(chtReport: any) {
for (const entry of chtReport.observations) {
if (entry.valueCode || entry.valueString || entry.valueDateTime) {
const observation = buildFhirObservationFromCht(chtReport.patient_uuid, fhirEncounter, entry);
updateFhirResource(observation);
createFhirResource(observation);
}
}

Expand Down
24 changes: 24 additions & 0 deletions mediator/src/mappers/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,27 @@ export function buildFhirObservationFromCht(patient_id: string, encounter: fhir4

return observation;
}

export async function buildChtRecordFromObservations(patient: fhir4.Patient, observations: fhir4.Observation[]) {
const patientId = getIdType(patient, chtDocumentIdentifierType);

const record: any = {
_meta: {
form: "openmrs_anc"
},
patient_id: patientId
}

observations.forEach((observation: fhir4.Observation) => {
if ( observation?.code?.coding && observation.code.coding.length > 0){
const code = observation.code.coding[0].code?.toLowerCase() || '';
if (observation.valueCodeableConcept) {
record[code] = observation.valueCodeableConcept.text;
} else if (observation.valueDateTime) {
record[code] = observation.valueDateTime.split('T')[0];
}
}
});

return record;
}
1 change: 1 addition & 0 deletions mediator/src/middlewares/schemas/encounter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import joi from 'joi';

export const EncounterSchema = joi.object({
resourceType: joi.string(),
id: joi.string().uuid(),
identifier: joi
.array()
Expand Down
1 change: 1 addition & 0 deletions mediator/src/middlewares/schemas/patient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import joi from 'joi';

export const PatientSchema = joi.object({
resourceType: joi.string(),
id: joi.string().uuid(),
identifier: joi
.array()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ export const HumanNameFactory = Factory.define('humanName')
.attr('given', ['John']);

export const PatientFactory = Factory.define('patient')
.attr('resourceType', 'Patient')
.attr('id', randomUUID())
.attr('identifier', identifier)
.attr('name', () => [HumanNameFactory.build()])
.attr('gender', 'male')
.attr('birthDate', '2000-01-01');

export const EncounterFactory = Factory.define('encounter')
.attr('resourceType', 'Encounter')
.attr('id', randomUUID())
.attr('identifier', identifier)
.attr('status', 'planned')
Expand Down
10 changes: 7 additions & 3 deletions mediator/src/routes/cht.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Router } from 'express';
import { requestHandler } from '../utils/request';
import { createPatient, updatePatientIds, createEncounter } from '../controllers/cht'
import { syncPatients, syncEncountersAndObservations} from '../utils/openmrs_sync'
import { syncPatients, syncEncounters } from '../utils/openmrs_sync'

const router = Router();

Expand All @@ -25,8 +25,12 @@ router.post(
router.post(
'/sync',
requestHandler(async (req) => {
await syncPatients();
syncEncountersAndObservations();
async function syncAll() {
await syncPatients();
await syncEncounters();
}
// dont await, return immediately
syncAll();
return { status: 200, data: {}};
})
);
Expand Down
62 changes: 32 additions & 30 deletions mediator/src/utils/cht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { CHT } from '../../config';
import { generateBasicAuthUrl } from './url';
import https from 'https';
import path from 'path';
import { buildChtPatientFromFhir } from '../mappers/cht';
import { buildChtPatientFromFhir, buildChtRecordFromObservations } from '../mappers/cht';
import { logger } from '../../logger';

type CouchDBQuery = {
selector: Record<string, any>;
Expand All @@ -29,7 +30,13 @@ export async function createChtRecord(patientId: string) {

const chtApiUrl = generateChtRecordsApiUrl(CHT.url, CHT.username, CHT.password);

return await axios.post(chtApiUrl, record, getOptions());
try {
const res = await axios.post(chtApiUrl, record, getOptions());
return { status: res?.status, data: res?.data };
} catch (error: any) {
logger.error(error);
return { status: error.status, data: error.data };
}
}

async function getLocation(fhirPatient: fhir4.Patient) {
Expand Down Expand Up @@ -105,47 +112,42 @@ export async function createChtPatient(fhirPatient: fhir4.Patient) {
return chtRecordsApi(cht_patient);
}

export async function chtRecordFromObservations(patient_id: string, observations: any) {
const record: any = {
_meta: {
form: "openmrs_anc"
},
patient_id: patient_id
}

for (const entry of observations.entry) {
if (entry.resource.resourceType == 'Observation') {
const code:string = entry.resource.code.coding[0].code;
if (entry.resource.valueCodeableConcept) {
record[code.toLowerCase()] = entry.resource.valueCodeableConcept.text;
} else if (entry.resource.valueDateTime) {
record[code.toLowerCase()] = entry.resource.valueDateTime.split('T')[0];
}
}
}

export async function chtRecordFromObservations(patient: fhir4.Patient, observations: fhir4.Observation[]) {
const record = buildChtRecordFromObservations(patient, observations);
return chtRecordsApi(record);
}

export async function updateChtDocument(doc: any, update_object: any) {
const chtApiUrl = generateChtDBUrl(CHT.url, CHT.username, CHT.password);
const updated_doc = { ...doc, ...update_object }
return await axios.put(path.join(chtApiUrl, doc._id), updated_doc, getOptions());
}

export async function chtRecordsApi(doc: any) {
const chtApiUrl = generateChtRecordsApiUrl(CHT.url, CHT.username, CHT.password);
return await axios.post(chtApiUrl, doc, getOptions());
try {
const res = await axios.post(chtApiUrl, doc, getOptions());
return { status: res?.status, data: res?.data };
} catch (error: any) {
logger.error(error);
return { status: error.status, data: error.data };
}
}

export async function getChtDocumentById(doc_id: string) {
const chtApiUrl = generateChtDBUrl(CHT.url, CHT.username, CHT.password);
return await axios.get(path.join(chtApiUrl, doc_id), getOptions());
try {
const res = await axios.get(path.join(chtApiUrl, doc_id), getOptions());
return { status: res?.status, data: res?.data };
} catch (error: any) {
logger.error(error);
return { status: error.status, data: error.data };
}
}

export async function queryCht(query: any) {
const chtApiUrl = generateChtDBUrl(CHT.url, CHT.username, CHT.password);
return await axios.post(path.join(chtApiUrl, '_find'), query, getOptions());
try {
const res = await axios.post(path.join(chtApiUrl, '_find'), query, getOptions());
return { status: res?.status, data: res?.data };
} catch (error: any) {
logger.error(error);
return { status: error.status, data: error.data };
}
}

export const generateChtRecordsApiUrl = (chtUrl: string, username: string, password: string) => {
Expand Down
41 changes: 20 additions & 21 deletions mediator/src/utils/fhir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,21 @@ export async function getFHIRPatients(lastUpdated: Date) {
);
}

export function copyIdToNamedIdentifier(fromResource: any, toResource: fhir4.Patient | fhir4.Encounter, fromIDType: fhir4.CodeableConcept){
export function copyIdToNamedIdentifier(fromResource: any, toResource: fhir4.Patient | fhir4.Encounter, fromIdType: fhir4.CodeableConcept){
const identifier: fhir4.Identifier = {
type: fromIDType,
type: fromIdType,
value: fromResource.id,
use: "secondary"
};
const sameIdType = (id: any) => (id.type.text === fromIDType.text)
const sameIdType = (id: any) => (id.type.text === fromIdType.text)
if (!toResource.identifier?.some(sameIdType)) {
toResource.identifier?.push(identifier);
}
return toResource;
}

export function getIdType(resource: fhir4.Patient | fhir4.Encounter, idType: fhir4.CodeableConcept): string{
return resource.identifier?.find((id: any) => id?.type.text == idType.text)?.value || '';
return resource?.identifier?.find((id: any) => id?.type.text == idType.text)?.value || '';
}

export function addId(resource: fhir4.Patient | fhir4.Encounter, idType: fhir4.CodeableConcept, value: string){
Expand All @@ -140,6 +140,14 @@ export function addId(resource: fhir4.Patient | fhir4.Encounter, idType: fhir4.C
return resource;
}

export function replaceReference(resource: any, referenceKey: string, referred: fhir4.Resource) {
const newReference: fhir4.Reference = {
reference: `${referred.resourceType}/${referred.id}`,
type: referred.resourceType
}
resource[referenceKey] = newReference;
}

export async function getFHIRLocation(locationId: string) {
return await axios.get(
`${FHIR.url}/Patient/?identifier=${locationId}`,
Expand All @@ -152,13 +160,7 @@ export async function deleteFhirSubscription(id?: string) {

export async function createFhirResource(doc: fhir4.Resource) {
try {
const res = await axios.post(`${FHIR.url}/${doc.resourceType}`, doc, {
auth: {
username: FHIR.username,
password: FHIR.password,
},
});

const res = await axios.post(`${FHIR.url}/${doc.resourceType}`, doc, axiosOptions);
return { status: res.status, data: res.data };
} catch (error: any) {
logger.error(error);
Expand All @@ -168,12 +170,7 @@ export async function createFhirResource(doc: fhir4.Resource) {

export async function updateFhirResource(doc: fhir4.Resource) {
try {
const res = await axios.put(`${FHIR.url}/${doc.resourceType}/${doc.id}`, doc, {
auth: {
username: FHIR.username,
password: FHIR.password,
},
});
const res = await axios.put(`${FHIR.url}/${doc.resourceType}/${doc.id}`, doc, axiosOptions);

return { status: res?.status, data: res?.data };
} catch (error: any) {
Expand All @@ -184,10 +181,12 @@ export async function updateFhirResource(doc: fhir4.Resource) {

export async function getFhirResourcesSince(lastUpdated: Date, resourceType: string) {
try {
const res = await axios.get(
`${FHIR.url}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`,
axiosOptions
);
let url = `${FHIR.url}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`;
// for encounters, include related resources
if (resourceType === 'Encounter') {
url = url + '&_revinclude=Observation:encounter&_include=Encounter:patient';
}
const res = await axios.get(url, axiosOptions);
return { status: res?.status, data: res?.data };
} catch (error: any) {
logger.error(error);
Expand Down
26 changes: 8 additions & 18 deletions mediator/src/utils/openmrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ export async function getOpenMRSPatientResource(patientId: string) {

export async function getOpenMRSResourcesSince(lastUpdated: Date, resourceType: string) {
try {
const res = await axios.get(
`${OPENMRS.url}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`,
axiosOptions
);
let url = `${OPENMRS.url}/${resourceType}/?_lastUpdated=gt${lastUpdated.toISOString()}`;
// for encounters, include related resources
if (resourceType === 'Encounter') {
url = url + '&_revinclude=Observation:encounter&_include=Encounter:patient';
}
const res = await axios.get(url, axiosOptions);
return { status: res.status, data: res.data };
} catch (error: any) {
logger.error(error);
Expand All @@ -31,13 +33,7 @@ export async function getOpenMRSResourcesSince(lastUpdated: Date, resourceType:

export async function createOpenMRSResource(doc: fhir4.Resource) {
try {
const res = await axios.post(`${OPENMRS.url}/${doc.resourceType}`, doc, {
auth: {
username: OPENMRS.username,
password: OPENMRS.password,
},
});

const res = await axios.post(`${OPENMRS.url}/${doc.resourceType}`, doc, axiosOptions);
return { status: res.status, data: res.data };
} catch (error: any) {
logger.error(error);
Expand All @@ -47,13 +43,7 @@ export async function createOpenMRSResource(doc: fhir4.Resource) {

export async function updateOpenMRSResource(doc: fhir4.Resource) {
try {
const res = await axios.put(`${OPENMRS.url}/${doc.resourceType}/${doc.id}`, doc, {
auth: {
username: OPENMRS.username,
password: OPENMRS.password,
},
});

const res = await axios.post(`${OPENMRS.url}/${doc.resourceType}/${doc.id}`, doc, axiosOptions);
return { status: res.status, data: res.data };
} catch (error: any) {
logger.error(error);
Expand Down
Loading

0 comments on commit aba7c95

Please sign in to comment.