Add env flag to filter DocumentReference type locally in ITI-67
DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY, when enabled, skips the `type` query param on GET /DocumentReference to the document registry and filters the resulting searchset by type client-side instead, matching the same system|code / |code / code token semantics used elsewhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c5b0d1c480
commit
387ee331ed
@ -49,6 +49,10 @@ MPI_URL=https://bus-test.msal.gob.ar/masterfile-federacion-service/fhir/Patient
|
||||
# URL del Document Registry si difiere del Bus principal
|
||||
DOCUMENT_REGISTRY_URL=https://bus-test.msal.gob.ar/fhir/DocumentReference
|
||||
|
||||
# ITI-67: si es true, no se envía el parámetro `type` en el GET /DocumentReference
|
||||
# al índice de documentos; se pide todo por subject y se filtra por type localmente.
|
||||
DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY=false
|
||||
|
||||
# Habilita logs detallados de requests/responses salientes al Bus (true | false)
|
||||
BUS_DEBUG=false
|
||||
|
||||
|
||||
@ -13,6 +13,10 @@ DOCUMENT_REGISTRY_URL=http://document-registry-host:8080
|
||||
MPI_SCOPE=Patient/*.read,Patient/*.write
|
||||
DOCUMENT_REGISTRY_SCOPE=DocumentReference/*.read,DocumentReference/*.write
|
||||
|
||||
# ITI-67: si es true, no se envía el parámetro `type` en el GET /DocumentReference
|
||||
# al índice de documentos; se pide todo por subject y se filtra por type localmente.
|
||||
DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY=false
|
||||
|
||||
# HAPI FHIR Server
|
||||
FHIR_URL=http://hapi-fhir-host:8080/fhir
|
||||
|
||||
|
||||
@ -9,6 +9,9 @@ const config = {
|
||||
issuer: process.env.BUS_ISSUER,
|
||||
mpiScope: process.env.MPI_SCOPE,
|
||||
documentRegistryScope: process.env.DOCUMENT_REGISTRY_SCOPE,
|
||||
// Si es true, ITI-67 no envía el parámetro `type` al índice de documentos:
|
||||
// pide todos los DocumentReference del subject y filtra por type localmente.
|
||||
filterDocumentTypeLocally: process.env.DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY === 'true',
|
||||
},
|
||||
fhir: {
|
||||
url: process.env.FHIR_URL,
|
||||
|
||||
@ -68,10 +68,67 @@ async function findDocumentReferenceBySubject(token, subjectSystem, subjectValue
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a FHIR token search value (`system|code`, `|code` o `code`) into its parts.
|
||||
* `system === null` indica la forma `|code` (exige coding sin system).
|
||||
* `system === undefined` indica la forma `code` (sin restricción de system).
|
||||
* @param {string} token
|
||||
* @returns {{ system: string|null|undefined, code: string }}
|
||||
*/
|
||||
function parseTypeToken(token) {
|
||||
const pipeIndex = token.indexOf('|');
|
||||
if (pipeIndex === -1) {
|
||||
return { system: undefined, code: token };
|
||||
}
|
||||
const system = token.slice(0, pipeIndex);
|
||||
return { system: system === '' ? null : system, code: token.slice(pipeIndex + 1) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Indica si el CodeableConcept `type` de un DocumentReference matchea el token FHIR dado,
|
||||
* replicando la semántica estándar de búsqueda por token (`system|code`, `|code` o `code`)
|
||||
* que el índice de documentos aplicaría del lado del servidor.
|
||||
* @param {object} resource - DocumentReference resource.
|
||||
* @param {string} token - Token FHIR de DocumentReference.type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function matchesTypeToken(resource, token) {
|
||||
const { system, code } = parseTypeToken(token);
|
||||
const codings = resource?.type?.coding ?? [];
|
||||
return codings.some(coding => {
|
||||
if (system === null) {
|
||||
return !coding.system && coding.code === code;
|
||||
}
|
||||
if (system !== undefined) {
|
||||
return coding.system === system && coding.code === code;
|
||||
}
|
||||
return coding.code === code;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtra las entries de un Bundle searchset de DocumentReference por type,
|
||||
* ajustando `total` a la cantidad de entries resultantes.
|
||||
* @param {object} bundle - FHIR Bundle (searchset).
|
||||
* @param {string} type - Token FHIR de DocumentReference.type.
|
||||
* @returns {object} Bundle filtrado.
|
||||
*/
|
||||
function filterBundleByType(bundle, type) {
|
||||
if (!bundle || !Array.isArray(bundle.entry)) {
|
||||
return bundle;
|
||||
}
|
||||
const entry = bundle.entry.filter(e => matchesTypeToken(e.resource, type));
|
||||
return { ...bundle, entry, total: entry.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches DocumentReferences in the document registry by patient internal ID,
|
||||
* opcionalmente filtrando por tipo de documento
|
||||
* (GET /fhir/DocumentReference?patient=value[&type=system|code]).
|
||||
*
|
||||
* Si `config.bus.filterDocumentTypeLocally` está activo y se pidió `type`, el parámetro
|
||||
* `type` no se envía al índice de documentos: se piden todos los DocumentReference del
|
||||
* subject y el filtrado por tipo se hace localmente sobre el Bundle resultante.
|
||||
* @param {string} documentRegistryUrl - Base URL of the document registry service.
|
||||
* @param {string} token - Bearer access token.
|
||||
* @param {string} patientId - Internal patient ID in the document registry.
|
||||
@ -80,15 +137,16 @@ async function findDocumentReferenceBySubject(token, subjectSystem, subjectValue
|
||||
*/
|
||||
async function findDocumentReferenceByPatient(token, patientId, type) {
|
||||
const request = createBusRequest(token);
|
||||
const filterLocally = Boolean(type) && config.bus.filterDocumentTypeLocally;
|
||||
const params = { subject: patientId };
|
||||
if (type) {
|
||||
if (type && !filterLocally) {
|
||||
params.type = type;
|
||||
}
|
||||
const response = await request.get(
|
||||
URL.parse(INDICE_ATENCION_URL),
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
return filterLocally ? filterBundleByType(response.data, type) : response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
const config = require('../../config');
|
||||
const { getBusToken } = require('../../utils/busAuth');
|
||||
const {
|
||||
createDocumentReference,
|
||||
@ -76,6 +77,10 @@ describe('findDocumentReferenceBySubject', () => {
|
||||
});
|
||||
|
||||
describe('findDocumentReferenceByPatient', () => {
|
||||
afterEach(() => {
|
||||
config.bus.filterDocumentTypeLocally = false;
|
||||
});
|
||||
|
||||
it('GETs /fhir/DocumentReference with patient ID as subject param', async () => {
|
||||
const token = await acquireToken();
|
||||
const bundle = { resourceType: 'Bundle', entry: [] };
|
||||
@ -89,6 +94,55 @@ describe('findDocumentReferenceByPatient', () => {
|
||||
);
|
||||
expect(result).toEqual(bundle);
|
||||
});
|
||||
|
||||
it('sends the type param when filterDocumentTypeLocally is disabled', async () => {
|
||||
config.bus.filterDocumentTypeLocally = false;
|
||||
const token = await acquireToken();
|
||||
const bundle = { resourceType: 'Bundle', entry: [] };
|
||||
mockRequest.get.mockResolvedValue({ data: bundle });
|
||||
|
||||
await findDocumentReferenceByPatient(token, 'http://mpi/fhir/Patient/123', 'http://loinc.org|60591-5');
|
||||
|
||||
expect(mockRequest.get).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ params: { subject: 'http://mpi/fhir/Patient/123', type: 'http://loinc.org|60591-5' } }
|
||||
);
|
||||
});
|
||||
|
||||
it('omits the type param and filters the bundle locally when filterDocumentTypeLocally is enabled', async () => {
|
||||
config.bus.filterDocumentTypeLocally = true;
|
||||
const token = await acquireToken();
|
||||
const ipsEntry = {
|
||||
resource: { resourceType: 'DocumentReference', type: { coding: [{ system: 'http://loinc.org', code: '60591-5' }] } },
|
||||
};
|
||||
const meowEntry = {
|
||||
resource: { resourceType: 'DocumentReference', type: { coding: [{ system: 'http://loinc.org', code: '56445-0' }] } },
|
||||
};
|
||||
const bundle = { resourceType: 'Bundle', total: 2, entry: [ipsEntry, meowEntry] };
|
||||
mockRequest.get.mockResolvedValue({ data: bundle });
|
||||
|
||||
const result = await findDocumentReferenceByPatient(token, 'http://mpi/fhir/Patient/123', 'http://loinc.org|60591-5');
|
||||
|
||||
expect(mockRequest.get).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ params: { subject: 'http://mpi/fhir/Patient/123' } }
|
||||
);
|
||||
expect(result).toEqual({ resourceType: 'Bundle', total: 1, entry: [ipsEntry] });
|
||||
});
|
||||
|
||||
it('matches the bare `code` token form regardless of coding system', async () => {
|
||||
config.bus.filterDocumentTypeLocally = true;
|
||||
const token = await acquireToken();
|
||||
const matching = {
|
||||
resource: { resourceType: 'DocumentReference', type: { coding: [{ system: 'http://other-system.org', code: '60591-5' }] } },
|
||||
};
|
||||
const bundle = { resourceType: 'Bundle', total: 1, entry: [matching] };
|
||||
mockRequest.get.mockResolvedValue({ data: bundle });
|
||||
|
||||
const result = await findDocumentReferenceByPatient(token, 'http://mpi/fhir/Patient/123', '60591-5');
|
||||
|
||||
expect(result.entry).toEqual([matching]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchDocumentReference', () => {
|
||||
|
||||
@ -55,6 +55,7 @@ services:
|
||||
DOCUMENT_REGISTRY_URL: ${DOCUMENT_REGISTRY_URL}
|
||||
MPI_SCOPE: ${MPI_SCOPE}
|
||||
DOCUMENT_REGISTRY_SCOPE: ${DOCUMENT_REGISTRY_SCOPE}
|
||||
DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY: ${DOCUMENT_REGISTRY_FILTER_TYPE_LOCALLY:-false}
|
||||
FHIR_URL: http://hapi-fhir:8080/fhir
|
||||
BUNDLE_SIGNER_URL: ${BUNDLE_SIGNER_URL}
|
||||
BUS_DEBUG: ${BUS_DEBUG}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user