const axios = require('axios'); const createError = require('http-errors'); const config = require('../config'); const { getBusToken, createBusRequest } = require('../utils/busAuth'); const { searchPatient } = require('../services/patient'); const { postDocumentReference, fetchDocumentReference } = require('../services/documentReference'); const { logger } = require('../utils/logger'); const { v4: uuidv4 } = require('uuid'); const DOCUMENT_REFERENCE_RESOURCE_TYPE = "DocumentReference"; const IPS_DOCUMENT_RESOURCE_TYPE = 'Bundle'; const PATIENT_RESOURCE_TYPE = 'Patient'; const LIST_RESOURCE_TYPE = 'List'; const NATIONAL_ID_SYSTEM = 'https://federador.msal.gob.ar/patient-id'; const DNI_SYSTEM = 'http://www.renaper.gob.ar/dni'; const CUSTODIAN_ID_SYSTEM = 'https://federador.msal.gob.ar/uri'; const MASTER_ID_SYSTEM = '' /** * Procesa una transacción y devuelve los recursos persistidos en el servidor HAPI FHIR * @param {*} transactionBundle * @returns */ async function processTransaction(transactionBundle) { const response = await axios.post(`${config.fhir.url}`, transactionBundle, { headers: { 'Content-Type': 'application/fhir+json' }, }); if (response.status !== 200) { throw createError(response.status, response.data.issue.diagnostics); } const processed = response.data; const resources = []; const responses = processed.entry.map(e => e.response); for (i = 0; i < responses.length; i++) { const resource = await axios.get(`${config.fhir.url}/${responses[i].location}`) resources.push(resource); } return Promise.resolve(resources); } function extractResource(resources, resourceType) { return resources.map(r => r.data).filter(r => r.resourceType === resourceType)[0]; } function extractLocalIdentifier(patient) { return patient.identifier.filter(i => { return i.system !== NATIONAL_ID_SYSTEM && ((!!i.use && i.use === 'official') || (!i.use)) })[0]; } /** * * @param {*} documentReference * @returns */ async function getBusCustodianIdentifier() { return Promise.resolve({ system: CUSTODIAN_ID_SYSTEM, value: config.bus.issuer }); } async function getBusPatientReference(patient, token) { const localIdentifier = extractLocalIdentifier(patient); const nationalPatientResponse = await searchPatient(token, { identifier: `${localIdentifier.system}|${localIdentifier.value}` }); const nationalPatientBundle = nationalPatientResponse.data; if (nationalPatientBundle.total == 0) { throw createError(404, 'Patient does not exists'); } // NOTE:La siguiente consulta no debería traer mas de un resultado. return Promise.resolve(nationalPatientBundle.entry[0].fullUrl); } async function getLocalIPSDocumentReference(localIPSDocument) { return Promise.resolve(`${config.baseURL}/fhir/Bundle/${localIPSDocument.id}`); } /** * Crea un nuevo document refrence en el bus * @param {*} token * @param {*} localIPSDocument * @param {*} localPatient * @param {*} localDocumentReference * @returns */ async function createBusDocumentReference(token, localIPSDocument, localPatient) { const busCustodianIdentifier = await getBusCustodianIdentifier(); const busPatientReference = await getBusPatientReference(localPatient, token); const localIPSDocumentReference = await getLocalIPSDocumentReference(localIPSDocument); const busDocumentReference = { resourceType: 'DocumentReference', status: 'current', masterIdentifier: { system: 'urn:ietf:rfc:3986', value: `urn:uuid:${uuidv4()}` }, type: { coding: [ { system: 'http://loinc.org', code: '60591-5', display: 'Patient Summary Document', }, ], }, subject: { reference: busPatientReference }, custodian: { identifier: busCustodianIdentifier, }, content: [ { attachment: { url: localIPSDocumentReference, }, }, ], }; const response = await postDocumentReference(token, busDocumentReference); const created = await fetchDocumentReference(token, response.headers['location']); return created.data; } /** * ITI-65: Provide Document Bundle (MHD) * * POST /fhir/Bundle * * Flow: * 1. Store the IPS Bundle in HAPI FHIR. * 2. Resolve the patient national identifier via Bus $match. * 3. Register a DocumentReference in the Bus pointing to the stored Bundle. * * Required header: * x-custodian-id — Efector identifier value in the Federador MSAL system. */ async function provideDocumentBundle(req, res, next) { try { const transaction = req.body; const token = await getBusToken( config.bus.url, config.bus.jwtSecret, config.bus.issuer, [config.bus.mpiScope, config.bus.documentRegistryScope].join(',') ); // Paso 1: Ejecuto la transcción en el servidor hapi subyacente const resources = await processTransaction(transaction); // Paso 2: Obtengo el paciente local (tal como se guardo en la trasnaccion) const localPatient = extractResource(resources, PATIENT_RESOURCE_TYPE); // Paso 3: Obtengo el documento IPS local (tal como se guardo en la transacción) const localIPSDocument = extractResource(resources, IPS_DOCUMENT_RESOURCE_TYPE) // Paso 4: Creo el document reference en el indice de atenciones const busDocumentReference = await createBusDocumentReference(token, localIPSDocument, localPatient); return status(200).json(busDocumentReference); } catch (err) { next(err); } } module.exports = { provideDocumentBundle };