Compare commits
No commits in common. "master" and "agomez_feature_gdhcn_activo" have entirely different histories.
master
...
agomez_fea
54
.env.example
54
.env.example
@ -4,35 +4,29 @@
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# VARIABLES DE ENTORNO REQUERIDAS
|
||||
# NODO
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# URL base del nodo (usada por bus-gateway internamente)
|
||||
NODO_URL_BASE=http://localhost
|
||||
|
||||
# Zona horaria para todos los servicios
|
||||
TZ=America/Argentina/Buenos_Aires
|
||||
|
||||
# =============================================================================
|
||||
# BUS DE SALUD — Requeridas
|
||||
# =============================================================================
|
||||
|
||||
# URL base del Bus de salud nacional
|
||||
BUS_URL=https://bus-test.msal.gob.ar
|
||||
|
||||
# Secreto compartido para firmar tokens JWT (HS256) contra el Bus
|
||||
BUS_JWT_SECRET=your-shared-secret
|
||||
|
||||
# Identificador del emisor JWT (ej.: URL del repositorio del nodo)
|
||||
BUS_ISSUER=https://your-repositorio-url
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INFRAESTRUCTURA
|
||||
# =============================================================================
|
||||
|
||||
# Zona horaria para todos los servicios
|
||||
TZ=America/Argentina/Buenos_Aires
|
||||
|
||||
# =============================================================================
|
||||
# BUS DE SALUD
|
||||
# =============================================================================
|
||||
|
||||
# URL base del Bus de salud nacional
|
||||
BUS_URL=https://bus-test.msal.gob.ar
|
||||
|
||||
|
||||
|
||||
# Scopes OAuth para el Master Patient Index
|
||||
MPI_SCOPE=Patient/*.read,Patient/*.write
|
||||
|
||||
@ -49,20 +43,9 @@ 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
|
||||
|
||||
# =============================================================================
|
||||
# NODO NACIONAL
|
||||
# =============================================================================
|
||||
|
||||
# URL del nodo nacional (bundle-signer): ejecuta las transformaciones IPS ($ddcc, $dvc, $icvp2, $meow)
|
||||
BUNDLE_SIGNER_URL=https://conectaton.msal.gob.ar/nodo/fhir
|
||||
|
||||
# =============================================================================
|
||||
# NGINX
|
||||
# =============================================================================
|
||||
@ -74,6 +57,19 @@ NGINX_CONF=http
|
||||
# SSL_CERT_PATH=./certs/server.crt
|
||||
# SSL_KEY_PATH=./certs/server.key
|
||||
|
||||
# =============================================================================
|
||||
# CERTIFICADOS / DOCKER SECRETS
|
||||
# Los archivos se inyectan como Docker secrets.
|
||||
# Los valores por defecto apuntan a ./certs/ — reemplazar en producción.
|
||||
# =============================================================================
|
||||
|
||||
# Certificado de la red de confianza (TNG/GDHCN)
|
||||
SIGNATURE_CERT_PATH=./certs/trust-network.pem
|
||||
SIGNATURE_KEY_PATH=./certs/trust-network.key
|
||||
|
||||
# Clave privada para firma de documentos (Document Signing Certificate)
|
||||
SSL_DCC_KEY_PATH=./certs/signature.key
|
||||
|
||||
# =============================================================================
|
||||
# HAPI FHIR (Spring Boot)
|
||||
# =============================================================================
|
||||
|
||||
53
README.md
53
README.md
@ -14,6 +14,7 @@ Internet / Red interna
|
||||
│ /fhir/IPSDocument → bus-gateway:3000 │
|
||||
│ /fhir/DocumentReference→ bus-gateway:3000 │
|
||||
│ /fhir/Patient → bus-gateway:3000 │
|
||||
│ /gdhcn/* → gdhcn-validator-service │
|
||||
│ /fhir/* (resto) → hapi-fhir:8080 │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
@ -25,11 +26,13 @@ Internet / Red interna
|
||||
| `hapi-fhir` | `hapiproject/hapi:latest` | 8080 (interno) | Servidor FHIR R4 (Spring Boot) con búsqueda Lucene |
|
||||
| `hapi-db` | `postgres:14.6` | 5433 (interno) | Base de datos PostgreSQL para HAPI FHIR |
|
||||
| `bus-gateway` | `./bus-gateway` (Node.js) | 3000 (interno) | Gateway al Bus de salud nacional (MPI + Document Registry) |
|
||||
| `gdhcn-validator-service` | `createchile/gdhcn-validator_backend:v1.1` | 8182 | Validador de documentos de salud verificables (firmas digitales) |
|
||||
| `gdhcn-db` | `postgres:16-alpine` | 5432 (interno) | Base de datos PostgreSQL para el validador GDHCN |
|
||||
| `nginx` | `nginx:alpine` | 80 / 443 | Proxy inverso — punto de entrada HTTP/HTTPS |
|
||||
|
||||
**Red interna**: todos los servicios se comunican a través de la red Docker `hapi-network`.
|
||||
|
||||
**Volúmenes persistentes**: `hapi-data` (PostgreSQL de HAPI).
|
||||
**Volúmenes persistentes**: `hapi-data` (PostgreSQL de HAPI), `gdhcn-data` (PostgreSQL de GDHCN).
|
||||
|
||||
## Transacciones IHE implementadas
|
||||
|
||||
@ -47,21 +50,9 @@ El `bus-gateway` implementa los siguientes perfiles de interoperabilidad:
|
||||
|
||||
## Requisitos
|
||||
|
||||
### Software
|
||||
|
||||
- Docker >= 20.10.8
|
||||
- Docker Compose >= 1.29.2
|
||||
|
||||
### Hardware del servidor
|
||||
|
||||
| Recurso | Mínimo | Recomendado |
|
||||
|---|---|---|
|
||||
| CPU | 2 núcleos | 4 núcleos |
|
||||
| Memoria RAM | 4 GB | 8 GB |
|
||||
| Disco | 10 GB | 30 GB |
|
||||
|
||||
> El mayor consumo de recursos corresponde a **HAPI FHIR** (JVM + índice Lucene), que requiere al menos 2 GB de RAM para arrancar correctamente. El volumen de disco recomendado contempla el crecimiento de los datos clínicos almacenados en PostgreSQL.
|
||||
|
||||
## Instalación y configuración
|
||||
|
||||
### 1. Variables de entorno
|
||||
@ -79,13 +70,8 @@ Los certificados se inyectan como **Docker secrets**. Las rutas se configuran en
|
||||
| Archivo por defecto | Descripción |
|
||||
|---|---|
|
||||
| `./certs/server.crt` / `./certs/server.key` | Certificado TLS para nginx (solo modo HTTPS) |
|
||||
|
||||
> **Importante:** Los archivos `ssl_cert` y `ssl_key` deben existir en el sistema de archivos para que Docker pueda montarlos como secrets, incluso si se usa el modo HTTP. Si no se cuenta con certificados reales, crear archivos vacíos:
|
||||
>
|
||||
> ```bash
|
||||
> touch ./certs/server.crt
|
||||
> touch ./certs/server.key
|
||||
> ```
|
||||
| `./certs/trust-network.pem` / `./certs/trust-network.key` | Certificado de la red de confianza (GDHCN/TNG) |
|
||||
| `./certs/signature.key` | Clave privada para firma de documentos (DSC) |
|
||||
|
||||
> Ver [certs/README.md](certs/README.md) para instrucciones de generación de certificados de prueba.
|
||||
|
||||
@ -119,17 +105,7 @@ Los parámetros del servidor FHIR se ajustan en [hapi-config/application.yaml](h
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
HAPI FHIR tarda aproximadamente **30–40 segundos** en inicializar. El `bus-gateway` espera a que el contenedor `hapi-fhir` esté iniciado, pero no verifica que el servidor FHIR haya completado su arranque. Antes de usar los servicios, verificar que HAPI FHIR inicializó correctamente:
|
||||
|
||||
```bash
|
||||
docker compose logs -f hapi-fhir
|
||||
```
|
||||
|
||||
Buscar la línea que confirma el inicio exitoso:
|
||||
|
||||
```
|
||||
Started Application in XX seconds
|
||||
```
|
||||
HAPI FHIR tarda aproximadamente **30–40 segundos** en inicializar. El `bus-gateway` espera activamente a que el servidor FHIR esté listo antes de aceptar conexiones (hasta ~3 minutos de reintentos).
|
||||
|
||||
## Verificar el despliegue
|
||||
|
||||
@ -140,8 +116,8 @@ docker compose ps
|
||||
# CapabilityStatement FHIR a través de nginx
|
||||
curl http://localhost/fhir/metadata
|
||||
|
||||
# Verificación de llegada al Bus mediante la consulta de un paciente.
|
||||
curl --location 'http://localhost/fhir/Patient?identifier=http%3A%2F%2Fwww.renaper.gob.ar%2Fdni%7C30945027'
|
||||
# Health check del Bus Gateway a través de nginx
|
||||
curl http://localhost/fhir/Patient?_summary=count
|
||||
|
||||
# Logs de un servicio específico
|
||||
docker compose logs -f bus-gateway
|
||||
@ -183,6 +159,7 @@ Todas las variables se definen en el archivo `.env` de la raíz del proyecto.
|
||||
|
||||
| Variable | Default | Descripción |
|
||||
|---|---|---|
|
||||
| `NODO_BASE_URL` | `http://localhost` | URL pública del nodo (usada por GDHCN para construir URLs) |
|
||||
| `NODO_URL_BASE` | `http://localhost` | URL base del nodo (usada por bus-gateway) |
|
||||
| `TZ` | `America/Argentina/Buenos_Aires` | Zona horaria para todos los servicios |
|
||||
|
||||
@ -200,6 +177,14 @@ Todas las variables se definen en el archivo `.env` de la raíz del proyecto.
|
||||
| `SSL_CERT_PATH` | `./certs/server.crt` | Ruta al certificado TLS del servidor |
|
||||
| `SSL_KEY_PATH` | `./certs/server.key` | Ruta a la clave privada TLS del servidor |
|
||||
|
||||
### Certificados / Secrets
|
||||
|
||||
| Variable | Default | Descripción |
|
||||
|---|---|---|
|
||||
| `SIGNATURE_CERT_PATH` | `./certs/trust-network.pem` | Certificado de la red de confianza (TNG/GDHCN) |
|
||||
| `SIGNATURE_KEY_PATH` | `./certs/trust-network.key` | Clave privada de la red de confianza |
|
||||
| `SSL_DCC_KEY_PATH` | `./certs/signature.key` | Clave privada para firma de documentos (DSC) |
|
||||
|
||||
### HAPI FHIR / PostgreSQL
|
||||
|
||||
| Variable | Default | Descripción |
|
||||
@ -218,7 +203,7 @@ Todas las variables se definen en el archivo `.env` de la raíz del proyecto.
|
||||
|
||||
```
|
||||
ips-nodo-dominio/
|
||||
├── docker-compose.yml # Orquestación principal (4 servicios)
|
||||
├── docker-compose.yml # Orquestación principal (6 servicios)
|
||||
├── .env.example # Plantilla de variables de entorno
|
||||
├── hapi-config/
|
||||
│ └── application.yaml # Configuración de HAPI FHIR (Spring Boot)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
NODO_URL_BASE=localhost
|
||||
NODO_BASE_URL=localhost
|
||||
|
||||
# Bus FHIR (base, usado como fallback si no se definen MPI_URL o DOCUMENT_REGISTRY_URL)
|
||||
BUS_URL=http://bus-host:8080
|
||||
@ -13,16 +13,9 @@ 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
|
||||
|
||||
# Nodo nacional (bundle-signer): ejecuta las transformaciones IPS ($ddcc, $dvc, $icvp2, $meow)
|
||||
BUNDLE_SIGNER_URL=https://conectaton.msal.gob.ar/nodo/fhir
|
||||
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Habilita logs de requests/responses salientes al Bus (true | false)
|
||||
|
||||
@ -14,21 +14,12 @@ El componente actúa exclusivamente como gateway: no contiene lógica de negocio
|
||||
|
||||
| Transacción | Método | Ruta | Descripción |
|
||||
|---|---|---|---|
|
||||
| ITI-65 | `POST` | `/fhir/IPSTransaction` | Provide Document Bundle (IPS): variante transacción — el cliente envía el Bundle transaction completo (Patient, Bundle document, DocumentReference, List). |
|
||||
| ITI-65 | `POST` | `/fhir/IPSDocument` | Provide Document Bundle (IPS): variante document — el cliente envía solo el Bundle IPS (`type: document`); el gateway arma la transacción internamente. |
|
||||
| ITI-65 | `POST` | `/fhir/MeOWTransaction` | Provide Document Bundle (MeOW - Medication Overview): variante transacción. |
|
||||
| ITI-65 | `POST` | `/fhir/MeOWDocument` | Provide Document Bundle (MeOW - Medication Overview): variante document. |
|
||||
| ITI-65 | `POST` | `/fhir/ITTransaction` | Provide Document Bundle (IT - Consultation Note / respuesta de interconsulta): variante transacción. |
|
||||
| ITI-65 | `POST` | `/fhir/ITDocument` | Provide Document Bundle (IT - Consultation Note): variante document. |
|
||||
| ITI-67 | `GET` | `/fhir/DocumentReference` | Find Document References: busca el paciente por ID local en el MPI para obtener su ID nacional y consulta los DocumentReferences en el Document Registry. Admite filtrar por tipo de documento con el parámetro opcional `type`, un token FHIR `system\|code` (ej. `http://loinc.org\|60591-5` para IPS, `http://loinc.org\|56445-0` para MeOW, `http://loinc.org\|57133-1` para IT). |
|
||||
| ITI-65 | `POST` | `/fhir/Bundle` | Provide Document Bundle: almacena el Bundle en HAPI FHIR, resuelve el ID nacional del paciente vía `$match` en el Bus y registra el DocumentReference en el Document Registry. |
|
||||
| ITI-67 | `GET` | `/fhir/DocumentReference` | Find Document References: busca el paciente por ID local en el MPI para obtener su ID nacional y consulta los DocumentReferences en el Document Registry. |
|
||||
| ITI-78 | `GET` | `/fhir/Patient` | Patient Demographics Query: búsqueda de pacientes en el MPI. |
|
||||
| ITI-78 | `GET` | `/fhir/Patient/:id` | Patient Demographics Query: obtención de un paciente por ID en el MPI. |
|
||||
| ITI-104 | `POST` | `/fhir/Patient` | Patient Identity Feed: alta de paciente en el MPI. |
|
||||
| ITI-104 | `PUT` | `/fhir/Patient/:id` | Patient Identity Feed: actualización de paciente en el MPI. |
|
||||
| — | `GET` | `/fhir/Bundle/:id/$ddcc` | Obtiene el Bundle IPS del HAPI FHIR local y delega la transformación DDCC al nodo nacional (bundle-signer). |
|
||||
| — | `GET` | `/fhir/Bundle/:id/$dvc` | Obtiene el Bundle IPS del HAPI FHIR local y delega la transformación DVC al nodo nacional (bundle-signer). |
|
||||
| — | `GET` | `/fhir/Bundle/:id/$icvp2` | Obtiene el Bundle IPS del HAPI FHIR local y delega la transformación ICVP (HCERT) al nodo nacional (bundle-signer). |
|
||||
| — | `GET` | `/fhir/Bundle/:id/$meow` | Obtiene el Bundle del HAPI FHIR local y delega la transformación MeOW (HCERT) al nodo nacional (bundle-signer). |
|
||||
|
||||
## Variables de entorno
|
||||
|
||||
@ -48,7 +39,6 @@ cp .env.example .env
|
||||
| `MPI_URL` | No | URL base del servicio MPI (Master Patient Index). Si no se define, usa `BUS_URL`. |
|
||||
| `DOCUMENT_REGISTRY_URL` | No | URL base del Document Registry. Si no se define, usa `BUS_URL`. |
|
||||
| `FHIR_URL` | Sí | URL base del servidor HAPI FHIR local. |
|
||||
| `BUNDLE_SIGNER_URL` | Sí | URL base del nodo nacional (bundle-signer) donde se ejecutan las transformaciones IPS (`$ddcc`, `$dvc`, `$icvp2`, `$meow`). |
|
||||
| `PORT` | No | Puerto en que escucha el servidor. Por defecto `3000`. |
|
||||
|
||||
### Usar la misma URL para todos los servicios del Bus
|
||||
@ -141,7 +131,7 @@ docker compose logs -f
|
||||
|
||||
## Headers requeridos por transacción
|
||||
|
||||
### ITI-65 `POST /fhir/IPSTransaction`, `/fhir/IPSDocument`, `/fhir/MeOWTransaction`, `/fhir/MeOWDocument`, `/fhir/ITTransaction`, `/fhir/ITDocument`
|
||||
### ITI-65 `POST /fhir/Bundle`
|
||||
|
||||
| Header | Descripción |
|
||||
|---|---|
|
||||
|
||||
@ -8,7 +8,6 @@ var iti65Router = require('./routes/iti65');
|
||||
var iti67Router = require('./routes/iti67');
|
||||
var iti78Router = require('./routes/iti78');
|
||||
var iti104Router = require('./routes/iti104');
|
||||
var bundleSignerRouter = require('./routes/bundleSigner');
|
||||
|
||||
var app = express();
|
||||
|
||||
@ -28,9 +27,6 @@ app.use('/fhir', iti78Router);
|
||||
// ITI-104: Patient Identity Feed → POST /fhir/Patient, PUT /fhir/Patient/:id
|
||||
app.use('/fhir', iti104Router);
|
||||
|
||||
// Transformaciones IPS (nodo nacional) → GET /fhir/Bundle/:id/$ddcc, $dvc, $icvp2, $meow
|
||||
app.use('/fhir', bundleSignerRouter);
|
||||
|
||||
// 404
|
||||
app.use(function (req, res, next) {
|
||||
const host = req.get('host') || 'localhost';
|
||||
|
||||
@ -9,16 +9,10 @@ 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,
|
||||
},
|
||||
bundleSigner: {
|
||||
url: process.env.BUNDLE_SIGNER_URL,
|
||||
},
|
||||
logging: {
|
||||
enabled: true,
|
||||
level: 'debug',
|
||||
@ -30,7 +24,7 @@ const config = {
|
||||
errorKey: 'err',
|
||||
nestedKey: null,
|
||||
},
|
||||
baseURL: process.env.NODO_URL_BASE || 'http://localhost',
|
||||
baseURL: process.env.NODO_BASE_URL || 'http://localhost',
|
||||
debug: process.env.BUS_DEBUG === 'true',
|
||||
};
|
||||
|
||||
@ -41,7 +35,6 @@ const required = [
|
||||
['MPI_SCOPE', config.bus.mpiScope],
|
||||
['DOCUMENT_REGISTRY_SCOPE', config.bus.documentRegistryScope],
|
||||
['FHIR_URL', config.fhir.url],
|
||||
['BUNDLE_SIGNER_URL', config.bundleSigner.url],
|
||||
];
|
||||
|
||||
const missing = required.filter(([, val]) => !val).map(([key]) => key);
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Tipos de documento soportados por ITI-65 (Provide Document Bundle) e ITI-67
|
||||
* (Find Document References), cada uno con su código LOINC para
|
||||
* DocumentReference.type.
|
||||
*/
|
||||
const DOCUMENT_TYPES = {
|
||||
IPS: {
|
||||
label: 'IPS',
|
||||
system: 'http://loinc.org',
|
||||
code: '60591-5',
|
||||
display: 'Patient Summary Document',
|
||||
},
|
||||
MEOW: {
|
||||
label: 'MeOW',
|
||||
system: 'http://loinc.org',
|
||||
code: '56445-0',
|
||||
display: 'Medication summary',
|
||||
},
|
||||
IT: {
|
||||
label: 'IT',
|
||||
system: 'http://loinc.org',
|
||||
code: '57133-1',
|
||||
display: 'Referral note',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { DOCUMENT_TYPES };
|
||||
@ -1,83 +0,0 @@
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
|
||||
/**
|
||||
* Fetches the Bundle from the local HAPI FHIR server and delegates the
|
||||
* transformation to the national node (bundle-signer), which holds the
|
||||
* signing keys. Returns the national node's response as-is.
|
||||
*/
|
||||
async function transformBundle(id, operation, query) {
|
||||
const bundleUrl = `${config.fhir.url}/Bundle/${id}`;
|
||||
const { data: bundle } = await axios.get(bundleUrl);
|
||||
|
||||
const response = await axios.post(
|
||||
`${config.bundleSigner.url}/Bundle/$${operation}`,
|
||||
bundle,
|
||||
{ params: query },
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /fhir/Bundle/:id/$ddcc
|
||||
*/
|
||||
async function getDDCC(req, res, next) {
|
||||
try {
|
||||
const response = await transformBundle(req.params.id, 'ddcc', req.query);
|
||||
res.status(response.status).json(response.data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /fhir/Bundle/:id/$dvc
|
||||
*/
|
||||
async function getDVC(req, res, next) {
|
||||
try {
|
||||
const response = await transformBundle(req.params.id, 'dvc', req.query);
|
||||
res.status(response.status).json(response.data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /fhir/Bundle/:id/$icvp
|
||||
*/
|
||||
async function getICVP(req, res, next) {
|
||||
try {
|
||||
const response = await transformBundle(req.params.id, 'icvp', req.query);
|
||||
res.status(response.status).json(response.data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /fhir/Bundle/:id/$icvp2
|
||||
*/
|
||||
async function getICVP2(req, res, next) {
|
||||
try {
|
||||
const response = await transformBundle(req.params.id, 'icvp2', req.query);
|
||||
res.status(response.status).json(response.data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /fhir/Bundle/:id/$meow
|
||||
*/
|
||||
async function getMeow(req, res, next) {
|
||||
try {
|
||||
const response = await transformBundle(req.params.id, 'meow', req.query);
|
||||
res.status(response.status).json(response.data);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDDCC, getDVC, getICVP, getICVP2, getMeow,
|
||||
};
|
||||
@ -7,7 +7,6 @@ const { createDocumentReference, findDocumentReferenceById } = require('../servi
|
||||
const { logger } = require('../utils/logger');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { getResourceByUrl, processDocumentBundleTransaction } = require('../services/fhir');
|
||||
const { DOCUMENT_TYPES } = require('../constants/documentTypes');
|
||||
|
||||
|
||||
const DOCUMENT_REFERENCE_RESOURCE_TYPE = "DocumentReference";
|
||||
@ -32,7 +31,7 @@ function extractLocalIdentifier(patient) {
|
||||
})[0];
|
||||
}
|
||||
|
||||
function generateDocumentReferenceResource(subjectReference, bundleUrl, docType) {
|
||||
function generateDocumentReferenceResource(subjectReference, bundleUrl) {
|
||||
const documentRefernece = {
|
||||
resourceType: 'DocumentReference',
|
||||
status: 'current',
|
||||
@ -44,9 +43,9 @@ function generateDocumentReferenceResource(subjectReference, bundleUrl, docType)
|
||||
type: {
|
||||
coding: [
|
||||
{
|
||||
system: docType.system,
|
||||
code: docType.code,
|
||||
display: docType.display,
|
||||
system: 'http://loinc.org',
|
||||
code: '60591-5',
|
||||
display: 'Patient Summary Document',
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -78,20 +77,19 @@ async function getResourcesFromTransactionResponse(transactionResponse) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye un Bundle de tipo transaction a partir de un Bundle de tipo document
|
||||
* (IPS, MeOW, IT, ...). Genera Patient, Bundle (documento), DocumentReference y
|
||||
* List (SubmissionSet MHD).
|
||||
* Construye un Bundle de tipo transaction a partir de un Bundle de tipo document (IPS).
|
||||
* Genera Patient, Bundle (IPS), DocumentReference y List (SubmissionSet MHD).
|
||||
* Usa urn:uuid: como fullUrl para que HAPI FHIR resuelva las referencias internas.
|
||||
*/
|
||||
function buildTransactionFromDocumentBundle(documentBundle, docType) {
|
||||
if (documentBundle.type !== 'document') {
|
||||
function buildTransactionFromIPSDocument(ipsBundle) {
|
||||
if (ipsBundle.type !== 'document') {
|
||||
throw createError(400, 'Bundle must be of type document');
|
||||
}
|
||||
const patientEntry = (documentBundle.entry || []).find(
|
||||
const patientEntry = (ipsBundle.entry || []).find(
|
||||
e => e.resource && e.resource.resourceType === 'Patient'
|
||||
);
|
||||
if (!patientEntry) {
|
||||
throw createError(400, `${docType.label} Bundle must contain a Patient resource`);
|
||||
throw createError(400, 'IPS Bundle must contain a Patient resource');
|
||||
}
|
||||
|
||||
const patientFullUrl = `urn:uuid:${uuidv4()}`;
|
||||
@ -110,9 +108,9 @@ function buildTransactionFromDocumentBundle(documentBundle, docType) {
|
||||
},
|
||||
type: {
|
||||
coding: [{
|
||||
system: docType.system,
|
||||
code: docType.code,
|
||||
display: docType.display,
|
||||
system: 'http://loinc.org',
|
||||
code: '60591-5',
|
||||
display: 'Patient Summary Document',
|
||||
}]
|
||||
},
|
||||
date: now,
|
||||
@ -160,7 +158,7 @@ function buildTransactionFromDocumentBundle(documentBundle, docType) {
|
||||
},
|
||||
{
|
||||
fullUrl: bundleFullUrl,
|
||||
resource: documentBundle,
|
||||
resource: ipsBundle,
|
||||
request: { method: 'POST', url: 'Bundle' }
|
||||
},
|
||||
{
|
||||
@ -180,7 +178,7 @@ function buildTransactionFromDocumentBundle(documentBundle, docType) {
|
||||
* Lógica central de ITI-65: persiste la transacción en HAPI FHIR, resuelve el
|
||||
* paciente en el Bus y registra un DocumentReference apuntando al Bundle guardado.
|
||||
*/
|
||||
async function executeITI65(transaction, token, docType) {
|
||||
async function executeITI65(transaction, token) {
|
||||
const transactionResponse = await processDocumentBundleTransaction(transaction);
|
||||
const resources = await getResourcesFromTransactionResponse(transactionResponse);
|
||||
|
||||
@ -195,73 +193,59 @@ async function executeITI65(transaction, token, docType) {
|
||||
const nationalPatientId = patientSearchset.entry[0].fullUrl;
|
||||
const bundleReference = `${config.baseURL}/fhir/Bundle/${localIPSDocument.id}`;
|
||||
|
||||
const documentReference = generateDocumentReferenceResource(nationalPatientId, bundleReference, docType);
|
||||
const documentReference = generateDocumentReferenceResource(nationalPatientId, bundleReference);
|
||||
return createDocumentReference(token, documentReference);
|
||||
}
|
||||
|
||||
async function acquireToken() {
|
||||
return getBusToken(
|
||||
config.bus.url,
|
||||
config.bus.jwtSecret,
|
||||
config.bus.issuer,
|
||||
[config.bus.mpiScope, config.bus.documentRegistryScope].join(',')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ITI-65: Provide Document Bundle (MHD) — variante transacción
|
||||
*
|
||||
* POST /fhir/iti65
|
||||
*
|
||||
* Espera un Bundle de tipo transaction construido por el cliente.
|
||||
*/
|
||||
function provideTransaction(docType) {
|
||||
return async function (req, res, next) {
|
||||
async function provideIPSTransaction(req, res, next) {
|
||||
try {
|
||||
const transaction = req.body;
|
||||
const token = await acquireToken();
|
||||
const result = await executeITI65(transaction, token, docType);
|
||||
const token = await getBusToken(
|
||||
config.bus.url,
|
||||
config.bus.jwtSecret,
|
||||
config.bus.issuer,
|
||||
[config.bus.mpiScope, config.bus.documentRegistryScope].join(',')
|
||||
);
|
||||
const result = await executeITI65(transaction, token);
|
||||
return res.status(200).json(result);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ITI-65: Provide Document Bundle (MHD) — variante document
|
||||
* ITI-65: Provide Document Bundle (MHD) — variante IPS document
|
||||
*
|
||||
* Espera un Bundle de tipo document. Genera internamente el Bundle transaction
|
||||
* (Patient, Bundle, DocumentReference, List) y ejecuta el mismo flujo que
|
||||
* provideTransaction.
|
||||
* POST /fhir/Bundle
|
||||
*
|
||||
* Espera un Bundle de tipo document (IPS). Genera internamente el Bundle
|
||||
* transaction y ejecuta el mismo flujo que provideDocumentBundle.
|
||||
*/
|
||||
function provideDocumentBundle(docType) {
|
||||
return async function (req, res, next) {
|
||||
async function provideIPSDocumentBundle(req, res, next) {
|
||||
try {
|
||||
const documentBundle = req.body;
|
||||
if (!documentBundle || documentBundle.resourceType !== 'Bundle') {
|
||||
const ipsBundle = req.body;
|
||||
if (!ipsBundle || ipsBundle.resourceType !== 'Bundle') {
|
||||
throw createError(400, 'Request body must be a FHIR Bundle resource');
|
||||
}
|
||||
const transaction = buildTransactionFromDocumentBundle(documentBundle, docType);
|
||||
const token = await acquireToken();
|
||||
const result = await executeITI65(transaction, token, docType);
|
||||
const transaction = buildTransactionFromIPSDocument(ipsBundle);
|
||||
const token = await getBusToken(
|
||||
config.bus.url,
|
||||
config.bus.jwtSecret,
|
||||
config.bus.issuer,
|
||||
[config.bus.mpiScope, config.bus.documentRegistryScope].join(',')
|
||||
);
|
||||
const result = await executeITI65(transaction, token);
|
||||
return res.status(200).json(result);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const provideIPSTransaction = provideTransaction(DOCUMENT_TYPES.IPS);
|
||||
const provideIPSDocumentBundle = provideDocumentBundle(DOCUMENT_TYPES.IPS);
|
||||
const provideMeOWTransaction = provideTransaction(DOCUMENT_TYPES.MEOW);
|
||||
const provideMeOWDocumentBundle = provideDocumentBundle(DOCUMENT_TYPES.MEOW);
|
||||
const provideITTransaction = provideTransaction(DOCUMENT_TYPES.IT);
|
||||
const provideITDocumentBundle = provideDocumentBundle(DOCUMENT_TYPES.IT);
|
||||
|
||||
module.exports = {
|
||||
provideIPSTransaction,
|
||||
provideIPSDocumentBundle,
|
||||
provideMeOWTransaction,
|
||||
provideMeOWDocumentBundle,
|
||||
provideITTransaction,
|
||||
provideITDocumentBundle,
|
||||
};
|
||||
module.exports = { provideIPSTransaction, provideIPSDocumentBundle };
|
||||
|
||||
@ -4,6 +4,16 @@ const { getBusToken } = require('../utils/busAuth');
|
||||
const { findPatient } = require('../services/patient');
|
||||
const { findDocumentReferenceByPatient } = require('../services/documentReference');
|
||||
|
||||
const NATIONAL_ID_SYSTEM = 'https://federador.msal.gob.ar/patient-id';
|
||||
|
||||
/**
|
||||
* Extracts the Federador national identifier from a Patient resource.
|
||||
*/
|
||||
function extractNationalIdentifier(patient) {
|
||||
const identifiers = Array.isArray(patient.identifier) ? patient.identifier : [];
|
||||
return identifiers.find(id => id.system === NATIONAL_ID_SYSTEM) || null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@ -11,24 +21,15 @@ const { findDocumentReferenceByPatient } = require('../services/documentReferenc
|
||||
*
|
||||
* GET /fhir/DocumentReference?subject=:localId
|
||||
* GET /fhir/DocumentReference?patient.identifier=:localId
|
||||
* GET /fhir/DocumentReference?subject=:localId&type=:type
|
||||
*
|
||||
* Flow:
|
||||
* 1. Fetch the Patient from the Bus by local ID to obtain the national identifier.
|
||||
* 2. Search DocumentReferences in the Bus by that national identifier
|
||||
* (opcionalmente filtrando por tipo de documento).
|
||||
* 2. Search DocumentReferences in the Bus by that national identifier.
|
||||
* 3. Return the resulting Bundle.
|
||||
*
|
||||
* Required query parameter (one of):
|
||||
* subject — local Patient ID in the Bus.
|
||||
* patient.identifier — same semantics, alternate FHIR parameter name.
|
||||
*
|
||||
* Optional query parameter:
|
||||
* type — token FHIR de DocumentReference.type (`system|code`, `|code` o `code`),
|
||||
* ej: `http://loinc.org|60591-5` (IPS), `http://loinc.org|56445-0` (MeOW),
|
||||
* `http://loinc.org|57133-1` (IT). Se pasa tal cual al Bus, sin traducir
|
||||
* alias propios, para no apartarse de la semántica estándar de búsqueda
|
||||
* por token de FHIR. Si se omite, devuelve documentos de cualquier tipo.
|
||||
*/
|
||||
async function listDocumentReference(req, res, next) {
|
||||
try {
|
||||
@ -36,7 +37,6 @@ async function listDocumentReference(req, res, next) {
|
||||
if (!localPatientIdentifier) {
|
||||
throw createError(400, 'Missing required query parameter: subject or patient.identifier');
|
||||
}
|
||||
const documentType = req.query.type;
|
||||
const token = await getBusToken(
|
||||
config.bus.url,
|
||||
config.bus.jwtSecret,
|
||||
@ -47,15 +47,13 @@ async function listDocumentReference(req, res, next) {
|
||||
].join(',')
|
||||
);
|
||||
// 1. Obtengo el identificador nacional del paciente en forma de referencia
|
||||
// (fullUrl del recurso Patient en el Bus).
|
||||
const patientSearchset = await findPatient(token, { identifier: localPatientIdentifier });
|
||||
if (!patientSearchset || !Array.isArray(patientSearchset.entry) || patientSearchset.entry.length === 0) {
|
||||
if (!patientSearchset) {
|
||||
throw createError(422, 'Could not resolve national identifier for the given patient');
|
||||
}
|
||||
const patientNationalId = patientSearchset.entry[0].fullUrl;
|
||||
// 2. Obtengo el listado de entradas del indice de atenciones asociadada al id de paciente
|
||||
// (y, si se pidió, al tipo de documento).
|
||||
const documentReferenceSearchset = await findDocumentReferenceByPatient(token, patientNationalId, documentType)
|
||||
const patientNationalId = patientSearchset.entry[0].fullUrl
|
||||
// 2. Obtengo el listado de entradas del indice de atenciones asociadada al id de paciente.
|
||||
const documentReferenceSearchset = await findDocumentReferenceByPatient(token, patientNationalId)
|
||||
|
||||
res.status(200).json(documentReferenceSearchset);
|
||||
} catch (err) {
|
||||
|
||||
2
bus-gateway/docs/.gitignore
vendored
2
bus-gateway/docs/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*.png
|
||||
*.pdf
|
||||
@ -1,13 +1,13 @@
|
||||
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1D4ED8', 'actorBorder': '#1E3A8A', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#93C5FD', 'noteBkgColor': '#FEF3C7', 'noteTextColor': '#92400E', 'noteBorderColor': '#F59E0B', 'signalColor': '#1E293B', 'signalTextColor': '#1E293B', 'labelBoxBkgColor': '#7C3AED', 'labelBoxBorderColor': '#5B21B6', 'labelTextColor': '#FFFFFF'}}}%%
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant HIS_A
|
||||
participant NodoDominio as Nodo
|
||||
box rgb(237,233,254) PINS
|
||||
participant NodoDominio as NodoDominio (HIS_A)
|
||||
participant BusNacion as BusNacion
|
||||
participant FederadorNacion as Federador
|
||||
end
|
||||
Note over HIS_A,FederadorNacion: Gestion de Identidad
|
||||
HIS_A->>NodoDominio: ITI-104: Federa un nuevo paciente en el MPI <br /> [POST /Patient]
|
||||
NodoDominio->>FederadorNacion: Federa un nuevo paciente en el Federador <br /> [POST /Patient]
|
||||
FederadorNacion-->>NodoDominio: 201 (Created)
|
||||
NodoDominio-->>HIS_A: 200 OK (Patient)
|
||||
Note over HIS_A,FederadorNacion: Gestion de Identidad (PIXm/PDQm)
|
||||
HIS_A->>NodoDominio: ITI-104 (Crea/Actualiza)
|
||||
NodoDominio->>BusNacion: Enruta petición FHIR (POST /Patient)
|
||||
BusNacion->>FederadorNacion: Ejecuta operación en el Federador
|
||||
FederadorNacion-->>BusNacion: 201 Created (Recurso)
|
||||
BusNacion-->>NodoDominio: Retorna Respuesta FHIR
|
||||
NodoDominio-->>HIS_A: Confirma Operación
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1D4ED8', 'actorBorder': '#1E3A8A', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#93C5FD', 'noteBkgColor': '#FEF3C7', 'noteTextColor': '#92400E', 'noteBorderColor': '#F59E0B', 'signalColor': '#1E293B', 'signalTextColor': '#1E293B', 'labelBoxBkgColor': '#7C3AED', 'labelBoxBorderColor': '#5B21B6', 'labelTextColor': '#FFFFFF'}}}%%
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant HIS_A
|
||||
participant NodoDominio as Nodo (Emisor)
|
||||
box rgb(237,233,254) PINS
|
||||
participant FederadorNacion as Federador
|
||||
participant IndiceNacion as Indice Documentos
|
||||
end
|
||||
Note over HIS_A,IndiceNacion: Variantes: /IPSTransaction, /IPSDocument, /MeOWTransaction, /MeOWDocument, /ITTransaction, /ITDocument
|
||||
Note over HIS_A,IndiceNacion: 1. Recepcion Local del Documento Bundle
|
||||
HIS_A->>NodoDominio: ITI65: Provee un nuevo documento (bundle)
|
||||
Note right of NodoDominio: El Nodo almacena físicamente el documento y extrae la metadata.
|
||||
Note over NodoDominio,FederadorNacion: 2. Resolución de Identidad
|
||||
NodoDominio->>FederadorNacion: Busca al paciente por el su identificador local [GET /Patient?identifier=<ID_Local>]
|
||||
FederadorNacion-->>NodoDominio: 200 OK (Patient Searchset)
|
||||
Note over NodoDominio,IndiceNacion: 3. Enrutamiento de la Petición
|
||||
Note right of NodoDominio: El Nodo genera la referencia al documento y la asocia al identificador nacional del paciente.
|
||||
NodoDominio->>IndiceNacion: Guarda la referencia al documento en el índice de documentos [POST /DocumentReference]
|
||||
IndiceNacion-->>NodoDominio: 201 (Created)
|
||||
NodoDominio-->>HIS_A: 200 OK (DocumentReference)
|
||||
participant NodoDominio as "Nodo Dominio (emisor)"
|
||||
participant BusNacion as "Bus Nacion"
|
||||
participant FederadorNacion as "Federador Nacion"
|
||||
participant IndiceNacion as "Indice Nacion"
|
||||
Note over HIS_A,IndiceNacion: 1. Recepcion Local del Documento Bundle (POST /Bundle)
|
||||
HIS_A->>NodoDominio: ITI-65 (Crea/Actualiza)
|
||||
Note over NodoDominio,FederadorNacion: 2. Resolución de Identidad (ITI-83)
|
||||
NodoDominio->>BusNacion: Get /Patient/$ihe-pix?sourceIdentifier=[ID_Local]
|
||||
BusNacion->>FederadorNacion: Consulta correspondencia
|
||||
FederadorNacion-->>BusNacion: 200 OK (Retorna ID Nacional)
|
||||
BusNacion-->>NodoDominio: Entrega ID Nacional
|
||||
Note over NodoDominio,IndiceNacion: 3. Enrutamiento de la Petición (POST /Bundle)
|
||||
NodoDominio->>BusNacion: Publica Document Reference (GET /DocumentRefereence?subject=[ID_Nacional])
|
||||
BusNacion->>IndiceNacion: Registra Referencia
|
||||
IndiceNacion-->>BusNacion: 201 (Created)
|
||||
BusNacion-->>NodoDominio: Confirma operación
|
||||
NodoDominio-->>HIS_A: 200 OK (Publicación exitosa)
|
||||
@ -1,23 +1,27 @@
|
||||
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1D4ED8', 'actorBorder': '#1E3A8A', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#93C5FD', 'noteBkgColor': '#FEF3C7', 'noteTextColor': '#92400E', 'noteBorderColor': '#F59E0B', 'signalColor': '#1E293B', 'signalTextColor': '#1E293B', 'labelBoxBkgColor': '#7C3AED', 'labelBoxBorderColor': '#5B21B6', 'labelTextColor': '#FFFFFF'}}}%%
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant HIS_A
|
||||
participant NodoDominio as Nodo A
|
||||
box rgb(237,233,254) PINS
|
||||
participant FederadorNacion as Federador
|
||||
participant IndiceNacion as Indice Documentos
|
||||
end
|
||||
participant NodoDominio2 as Nodo B
|
||||
Note over HIS_A,FederadorNacion: Tipos soportados (LOINC): 60591-5 IPS, 56445-0 MeOW, 57133-1 IT (parámetro opcional type=system|code)
|
||||
Note over HIS_A,FederadorNacion: 1. Busqueda y resolución de Identidad
|
||||
HIS_A->>NodoDominio: ITI67: Solicita los documentos asociados al paciente por su identificador local, opcionalmente filtrando por tipo <br /> [GET /DocumentReference?patient.identifier=<ID_Local>&type=<system|code>]
|
||||
NodoDominio->>FederadorNacion: Busca el paciente por su identificador local <br /> [GET /Patient?identifier=<ID_Local>]
|
||||
FederadorNacion-->>NodoDominio: 200 OK (Patient Searchset)
|
||||
Note over NodoDominio,IndiceNacion: 2. Busqueda de Metadatos
|
||||
Note right of NodoDominio: El Nodo obtiene el identificador nacional del paciente
|
||||
NodoDominio->>IndiceNacion: Busca los documentos asociados al paciente por su identificador nacional (y tipo, si se pidió) <br /> [GET /DocumentReference?patient=<ID_Nacional>&type=<system|code>]
|
||||
IndiceNacion-->>NodoDominio: 200 OK (DocumentReference Searchset)
|
||||
NodoDominio-->>HIS_A: 200 OK (DocumentReference Searchset)
|
||||
Note over HIS_A,NodoDominio2: 3. Descarga del documento (ITI-68)
|
||||
HIS_A->>NodoDominio2: Obtiene el documento <br /> [GET <URL_Bundle>]
|
||||
NodoDominio2-->>HIS_A: 200 OK (Bundle)
|
||||
participant NodoDominio as "Nodo Dominio (emisor)"
|
||||
participant BusNacion as "Bus Nacion"
|
||||
participant FederadorNacion as "Federador Nacion"
|
||||
participant IndiceNacion as "Indice Nacion"
|
||||
participant NodoDominio2 as "Nodo Dominio (receptor)"
|
||||
Note over HIS_A,FederadorNacion: 1. Busqueda y resolución de Identidad (PIXm ITI-83)
|
||||
HIS_A->>NodoDominio: ITI-67: GET /DocumentReference?subject=[ID_Lcocal]
|
||||
NodoDominio->>BusNacion: GET /Patient/$ihe-pix?sourceIdentifier=[ID_Local]
|
||||
BusNacion->>FederadorNacion: Consulta correspondencia en MPI
|
||||
FederadorNacion-->>BusNacion: 200 OK (Retorna ID Nacional)
|
||||
BusNacion-->>NodoDominio: Entrega ID Nacional
|
||||
Note over NodoDominio,IndiceNacion: 2. Busqueda de Metadatos (MHD ITI-67 adaptada)
|
||||
NodoDominio->>BusNacion: GET /DocumentReference?subject=[ID_Nacional]
|
||||
BusNacion->>IndiceNacion: Consulta en el índice de referencias
|
||||
IndiceNacion-->>BusNacion: 200 OK (Bundle "searchset")
|
||||
BusNacion-->>NodoDominio: Retorna Bundle con Document Reference
|
||||
Note over HIS_A,NodoDominio2: 3. Descarga del documento (ITI-68 P2P)
|
||||
HIS_A->>NodoDominio: Solicitar descarga de [URL_Directa]
|
||||
NodoDominio->>NodoDominio2: GET [URL_Directa] (Conexión P2P, bypass BusNación)
|
||||
NodoDominio2-->>NodoDominio: 200 OK (Retorna el documento clinico)
|
||||
NodoDominio-->>HIS_A: Entrega documento fisico
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1D4ED8', 'actorBorder': '#1E3A8A', 'actorTextColor': '#FFFFFF', 'actorLineColor': '#93C5FD', 'noteBkgColor': '#FEF3C7', 'noteTextColor': '#92400E', 'noteBorderColor': '#F59E0B', 'signalColor': '#1E293B', 'signalTextColor': '#1E293B', 'labelBoxBkgColor': '#7C3AED', 'labelBoxBorderColor': '#5B21B6', 'labelTextColor': '#FFFFFF'}}}%%
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant HIS_A
|
||||
participant NodoDominio as Nodo
|
||||
box rgb(237,233,254) PINS
|
||||
participant FederadorNacion as Federador
|
||||
end
|
||||
Note over HIS_A,FederadorNacion: Gestion de Identidad
|
||||
HIS_A->>NodoDominio: ITI-78: Busca un paciente en el MPI <br /> [GET /Patient?identifier=<ID_Local>]
|
||||
NodoDominio->>FederadorNacion: Busca un paciente en el Federador <br /> [GET /Patient?identifier=<ID_Local>]
|
||||
FederadorNacion-->>NodoDominio: 200 OK (PatientSearchset)
|
||||
NodoDominio-->>HIS_A: 200 OK (Patient Searchset)
|
||||
participant NodoDominio as "Nodo Dominio (Repositorio Local)"
|
||||
participant BusNacion as "Bus Nacion"
|
||||
participant FederadorNacion as "Federador Nacion"
|
||||
Note over HIS_A,FederadorNacion: Gestion de Identidad (PIXm/PDQm)
|
||||
HIS_A->>NodoDominio: ITI-78 (Consulta)
|
||||
NodoDominio->>BusNacion: Enruta petición FHIR (GET /Partient)
|
||||
BusNacion->>FederadorNacion: Ejecuta operación en el Federador
|
||||
FederadorNacion-->>BusNacion: 200 OK (Bundle)
|
||||
BusNacion-->>NodoDominio: Retorna Respuesta FHIR
|
||||
NodoDominio-->>HIS_A: Confirma Operación
|
||||
@ -7,7 +7,7 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.14.0",
|
||||
"axios": "^1.3.5",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^16.0.0",
|
||||
@ -16,15 +16,13 @@
|
||||
"jade": "~1.11.0",
|
||||
"morgan": "~1.9.1",
|
||||
"pino": "^10.3.1",
|
||||
"uuid": "9.0.1"
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^30.3.0"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"setupFiles": [
|
||||
"./tests/setup.js"
|
||||
]
|
||||
"setupFiles": ["./tests/setup.js"]
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const {
|
||||
getDDCC, getDVC, getICVP, getICVP2, getMeow,
|
||||
} = require('../controllers/bundleSigner');
|
||||
|
||||
// Transformaciones IPS delegadas al nodo nacional (bundle-signer):
|
||||
// obtienen el Bundle del HAPI FHIR local y postean al nodo nacional.
|
||||
router.get('/Bundle/:id/([\$])ddcc', getDDCC);
|
||||
router.get('/Bundle/:id/([\$])dvc', getDVC);
|
||||
router.get('/Bundle/:id/([\$])icvp', getICVP);
|
||||
router.get('/Bundle/:id/([\$])icvp2', getICVP2);
|
||||
router.get('/Bundle/:id/([\$])meow', getMeow);
|
||||
|
||||
module.exports = router;
|
||||
@ -1,24 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const {
|
||||
provideIPSTransaction,
|
||||
provideIPSDocumentBundle,
|
||||
provideMeOWTransaction,
|
||||
provideMeOWDocumentBundle,
|
||||
provideITTransaction,
|
||||
provideITDocumentBundle,
|
||||
} = require('../controllers/iti65');
|
||||
const { provideIPSTransaction, provideIPSDocumentBundle } = require('../controllers/iti65');
|
||||
|
||||
// ITI-65: Provide Document Bundle — IPS
|
||||
// ITI-65: Provide Document Bundle — variante transaction Bundle
|
||||
router.post('/IPSTransaction', provideIPSTransaction);
|
||||
|
||||
// ITI-65: Provide Document Bundle — variante IPS document Bundle (type: document)
|
||||
router.post('/IPSDocument', provideIPSDocumentBundle);
|
||||
|
||||
// ITI-65: Provide Document Bundle — MeOW (Medication Overview)
|
||||
router.post('/MeOWTransaction', provideMeOWTransaction);
|
||||
router.post('/MeOWDocument', provideMeOWDocumentBundle);
|
||||
|
||||
// ITI-65: Provide Document Bundle — IT (Consultation Note / respuesta de interconsulta)
|
||||
router.post('/ITTransaction', provideITTransaction);
|
||||
router.post('/ITDocument', provideITDocumentBundle);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -69,84 +69,22 @@ async function findDocumentReferenceBySubject(token, subjectSystem, subjectValue
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Searches DocumentReferences in the document registry by patient internal ID
|
||||
* (GET /fhir/DocumentReference?patient=value).
|
||||
* @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.
|
||||
* @param {string} [type] - Token FHIR (`system|code` o `code`) de DocumentReference.type, ej: `http://loinc.org|60591-5`.
|
||||
* @returns {Promise<object>} FHIR Bundle (searchset) with matching DocumentReferences.
|
||||
*/
|
||||
async function findDocumentReferenceByPatient(token, patientId, type) {
|
||||
async function findDocumentReferenceByPatient(token, patientId) {
|
||||
const request = createBusRequest(token);
|
||||
const filterLocally = Boolean(type) && config.bus.filterDocumentTypeLocally;
|
||||
const params = { subject: patientId };
|
||||
if (type && !filterLocally) {
|
||||
params.type = type;
|
||||
}
|
||||
const response = await request.get(
|
||||
URL.parse(INDICE_ATENCION_URL),
|
||||
{ params }
|
||||
{
|
||||
params: { subject: patientId },
|
||||
}
|
||||
);
|
||||
return filterLocally ? filterBundleByType(response.data, type) : response.data;
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
const config = require('../../config');
|
||||
const { getBusToken } = require('../../utils/busAuth');
|
||||
const {
|
||||
createDocumentReference,
|
||||
@ -77,10 +76,6 @@ 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: [] };
|
||||
@ -94,55 +89,6 @@ 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', () => {
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
@startuml
|
||||
|
||||
skinparam backgroundColor #FFFFFF
|
||||
|
||||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
|
||||
|
||||
title Diagrama de Contenedores para la Conectatón Argentina
|
||||
|
||||
System_Ext(historiaClinica, 'HCI', 'Registtra la historia clinica y genera resumenes de historia clinica (IPS)')
|
||||
|
||||
|
||||
System_Boundary(nodo, 'Nodo'){
|
||||
Container(nginx, 'nginx', 'Nginx', 'Direcciona las llamadas a los contenedores correspondientes')
|
||||
Container(hapiFHIR, 'hapi-fhir', 'Java', 'Servidor HAPI FHIR. Brinda soporte para el almacenamiento de documentos IPS')
|
||||
Container(busGateway, 'bus-gateway', 'NodeJS/Express', 'Orquesta la comunicación con los servicios del Ministerio de Salud de la nación')
|
||||
ContainerDb(hapiDB, 'hapi-db', 'Postgres', 'Almacena datos del servidor HAPI FHIR')
|
||||
}
|
||||
|
||||
System_Boundary(pins, 'PINS'){
|
||||
Container(federador, 'Federador', 'Java/SpringBoot', 'Brinda la funcionalidad para la federación, busqueda y obtención de pacientes')
|
||||
Container(indiceDocumentos, 'Indice de Documentos', 'Java/SpringBoot', 'Servicio de registro, busqueda y obtencion de referencias a documentos de salud')
|
||||
}
|
||||
|
||||
|
||||
|
||||
Rel(historiaClinica, nginx, 'Provee documento IPS')
|
||||
Rel(historiaClinica, nginx, 'Consulta el indice de documentos')
|
||||
Rel(historiaClinica, nginx, 'Obtiene documentos IPS')
|
||||
Rel(historiaClinica, nginx, 'Federa pacientes')
|
||||
|
||||
Rel(nginx, busGateway, 'Delega ITI65, ITI67, ITI104 e ITI78')
|
||||
Rel(nginx, hapiFHIR, 'Delega ITI68')
|
||||
|
||||
Rel(busGateway, hapiFHIR, 'Guarda documento IPS')
|
||||
|
||||
Rel(hapiFHIR, hapiDB, 'Persiste datos')
|
||||
|
||||
Rel(busGateway, federador, 'Federa, busca y recupera pacientes')
|
||||
Rel(busGateway, indiceDocumentos, 'Registra, busca y repuera referencias a documentos')
|
||||
|
||||
|
||||
|
||||
@enduml
|
||||
@ -3,8 +3,7 @@ services:
|
||||
container_name: hapi-fhir
|
||||
image: "hapiproject/hapi:latest"
|
||||
depends_on:
|
||||
hapi-db:
|
||||
condition: service_healthy
|
||||
- hapi-db
|
||||
volumes:
|
||||
- ./hapi-config:/data/hapi
|
||||
environment:
|
||||
@ -38,11 +37,47 @@ services:
|
||||
retries: 5
|
||||
command: -p 5433
|
||||
|
||||
gdhcn-validator-service:
|
||||
container_name: gdhcn-validator-service
|
||||
image: createchile/gdhcn-validator_backend:v1.1
|
||||
ports:
|
||||
- 8182:8080
|
||||
secrets:
|
||||
- trust-network-cert
|
||||
- trust-network-key
|
||||
- signature-key
|
||||
environment:
|
||||
- POSTGRES_DB=gdhcn-validator
|
||||
- SERVER_PORT=8080
|
||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://gdhcn-db:5432/gdhcn-validator
|
||||
- SPRING_DATASOURCE_USERNAME=root
|
||||
- SPRING_DATASOURCE_PASSWORD=root
|
||||
- TNG_TLS_PEM=/run/secrets/trust-network-cert
|
||||
- TNG_TLS_KEY=/run/secrets/trust-network-key
|
||||
- TNG_DSC_PRIVATEKEY=/run/secrets/signature-key
|
||||
- TNG_COUNTRY=AR
|
||||
- TNG_DSC_PRIVATEKEY_KID=I1BAX8FATLs=
|
||||
- GDHCN_BASEURL=${NODO_URL_BASE:-http://localhost}/gdhcn
|
||||
networks:
|
||||
- hapi-network
|
||||
volumes:
|
||||
- ./json:/json
|
||||
|
||||
gdhcn-db:
|
||||
container_name: gdhcn-db
|
||||
image: postgres:16-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- gdhcn-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- hapi-network
|
||||
environment:
|
||||
- POSTGRES_DB=gdhcn-validator
|
||||
- POSTGRES_USER=root
|
||||
- POSTGRES_PASSWORD=root
|
||||
|
||||
bus-gateway:
|
||||
container_name: bus-gateway
|
||||
depends_on:
|
||||
hapi-fhir:
|
||||
condition: service_started
|
||||
build:
|
||||
context: ./bus-gateway
|
||||
environment:
|
||||
@ -55,9 +90,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}
|
||||
ports:
|
||||
- 9229:9229
|
||||
@ -69,10 +102,8 @@ services:
|
||||
container_name: nginx
|
||||
image: nginx:alpine
|
||||
depends_on:
|
||||
hapi-fhir:
|
||||
condition: service_started
|
||||
bus-gateway:
|
||||
condition: service_started
|
||||
- hapi-fhir
|
||||
- bus-gateway
|
||||
environment:
|
||||
TZ: ${TZ:-America/Argentina/Buenos_Aires}
|
||||
ports:
|
||||
@ -95,8 +126,17 @@ volumes:
|
||||
hapi-data:
|
||||
name: hapi-data
|
||||
driver: local
|
||||
gdhcn-data:
|
||||
name: gdhcn-data
|
||||
driver: local
|
||||
secrets:
|
||||
ssl_cert:
|
||||
file: ${SSL_CERT_PATH:-./certs/server.crt}
|
||||
ssl_key:
|
||||
file: ${SSL_KEY_PATH:-./certs/server.key}
|
||||
trust-network-cert:
|
||||
file: ${SIGNATURE_CERT_PATH:-./certs/trust-network.pem}
|
||||
trust-network-key:
|
||||
file: ${SIGNATURE_KEY_PATH:-./certs/trust-network.key}
|
||||
signature-key:
|
||||
file: ${SSL_DCC_KEY_PATH:-./certs/signature.key}
|
||||
|
||||
@ -35,46 +35,6 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — MeOW (Medication Overview) transaction Bundle
|
||||
location /fhir/MeOWTransaction {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — MeOW (Medication Overview) document Bundle
|
||||
location /fhir/MeOWDocument {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — IT (Consultation Note) transaction Bundle
|
||||
location /fhir/ITTransaction {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — IT (Consultation Note) document Bundle
|
||||
location /fhir/ITDocument {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-67: Find Document References
|
||||
location /fhir/DocumentReference {
|
||||
proxy_pass http://bus_gateway;
|
||||
@ -96,10 +56,9 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# Transformaciones de bundle (nodo nacional, via bus-gateway/bundleSigner):
|
||||
# /fhir/Bundle/:id/$ddcc, $dvc, $icvp2, $meow
|
||||
location ~ ^/fhir/Bundle/[^/]+/\$(ddcc|dvc|icvp|icvp2|meow)$ {
|
||||
proxy_pass http://bus_gateway;
|
||||
# Resto de /fhir/* va a hapi-fhir
|
||||
location /fhir/ {
|
||||
proxy_pass http://hapi_fhir;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@ -107,9 +66,8 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# Resto de /fhir/* va a hapi-fhir
|
||||
location /fhir/ {
|
||||
proxy_pass http://hapi_fhir;
|
||||
location /gdhcn/ {
|
||||
proxy_pass http://gdhcn-validator-service:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
@ -51,46 +51,6 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — MeOW (Medication Overview) transaction Bundle
|
||||
location /fhir/MeOWTransaction {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — MeOW (Medication Overview) document Bundle
|
||||
location /fhir/MeOWDocument {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — IT (Consultation Note) transaction Bundle
|
||||
location /fhir/ITTransaction {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-65: Provide Document Bundle — IT (Consultation Note) document Bundle
|
||||
location /fhir/ITDocument {
|
||||
proxy_pass http://bus_gateway;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# ITI-67: Find Document References
|
||||
location /fhir/DocumentReference {
|
||||
proxy_pass http://bus_gateway;
|
||||
@ -112,10 +72,9 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# Transformaciones de bundle (nodo nacional, via bus-gateway/bundleSigner):
|
||||
# /fhir/Bundle/:id/$ddcc, $dvc, $icvp2, $meow
|
||||
location ~ ^/fhir/Bundle/[^/]+/\$(ddcc|dvc|icvp|icvp2|meow)$ {
|
||||
proxy_pass http://bus_gateway;
|
||||
# Resto de /fhir/* va a hapi-fhir
|
||||
location /fhir/ {
|
||||
proxy_pass http://hapi_fhir;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@ -123,9 +82,8 @@ http {
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# Resto de /fhir/* va a hapi-fhir
|
||||
location /fhir/ {
|
||||
proxy_pass http://hapi_fhir;
|
||||
location /gdhcn/ {
|
||||
proxy_pass http://gdhcn-validator-service:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
File diff suppressed because one or more lines are too long
121
tests/fixtures/iti65/bundle-document-it.json
vendored
121
tests/fixtures/iti65/bundle-document-it.json
vendored
@ -1,121 +0,0 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "document",
|
||||
"timestamp": "2026-04-23T07:50:00-03:00",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:5ca8da35-efb1-498a-93bc-ed34524a360f",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "urn:oid:2.16.858.1.858.1.1.1",
|
||||
"value": "123456789"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"name": [
|
||||
{
|
||||
"use": "official",
|
||||
"family": "Pérez",
|
||||
"given": [
|
||||
"Juan"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:4d58cb4f-1f8b-4f94-9877-bb5a0de47955",
|
||||
"resource": {
|
||||
"resourceType": "Composition",
|
||||
"status": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "11488-4",
|
||||
"display": "Consultation note"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:5ca8da35-efb1-498a-93bc-ed34524a360f"
|
||||
},
|
||||
"date": "2026-04-23T07:50:00-03:00",
|
||||
"author": [
|
||||
{
|
||||
"display": "Dra. Especialista - Nodo Panamá"
|
||||
}
|
||||
],
|
||||
"title": "Informe de Contra-referencia Dermatológica",
|
||||
"section": [
|
||||
{
|
||||
"title": "Motivo de Interconsulta",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "42349-1",
|
||||
"display": "Reason for referral"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Se solicita segunda opinión para evaluación de lesión pigmentada en región escapular, con el fin de descartar malignidad tras hallazgos clínicos iniciales en Uruguay.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Revisión de Evidencia",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "30954-2",
|
||||
"display": "Relevant diagnostic tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Se re-evaluaron los resultados del IPS (Bundle/2313), específicamente el DiagnosticReport/biopsia-001 y las imágenes dermatoscópicas adjuntas.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Juicio Clínico",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "51848-0",
|
||||
"display": "Assessment note"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Tras el análisis de la evidencia, se concluye que la morfología de la lesión es compatible con un Nevus Displásico. No se identifican criterios histológicos de melanoma.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Recomendaciones",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "18776-5",
|
||||
"display": "Plan of care"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><ul><li>Control dermatoscópico cada 6 meses.</li><li>Uso de fotoprotección (FPS 50+).</li><li>No se requiere intervención quirúrgica inmediata.</li></ul></div>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
177
tests/fixtures/iti65/bundle-document-meow.json
vendored
177
tests/fixtures/iti65/bundle-document-meow.json
vendored
@ -1,177 +0,0 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"id": "medication-overview-example",
|
||||
"meta": {
|
||||
"profile": [
|
||||
"http://profiles.ihe.net/PHARM/MEOW/StructureDefinition/MedicationOverview"
|
||||
]
|
||||
},
|
||||
"identifier": {
|
||||
"system": "urn:ietf:rfc:3986",
|
||||
"value": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a71"
|
||||
},
|
||||
"type": "document",
|
||||
"timestamp": "2025-06-17T18:36:18+00:00",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a72",
|
||||
"resource": {
|
||||
"resourceType": "Composition",
|
||||
"id": "composition-example",
|
||||
"meta": {
|
||||
"profile": [
|
||||
"http://profiles.ihe.net/PHARM/MEOW/StructureDefinition/MedicationOverviewComposition"
|
||||
]
|
||||
},
|
||||
"status": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "56445-0",
|
||||
"display": "Medication summary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"date": "2025-06-17T18:36:18+00:00",
|
||||
"author": [
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
}
|
||||
],
|
||||
"title": "Medication Overview",
|
||||
"section": [
|
||||
{
|
||||
"title": "Medicamentos",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "55112-7",
|
||||
"display": "Document summary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Resumen de medicamentos</p></div>"
|
||||
},
|
||||
"entry": [
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a74"
|
||||
},
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a75"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "patient-example",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "http://hospital.example.org",
|
||||
"value": "12345"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"family": "Doe",
|
||||
"given": [
|
||||
"John"
|
||||
]
|
||||
}
|
||||
],
|
||||
"gender": "male",
|
||||
"birthDate": "1970-01-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a74",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "medication-statement-1",
|
||||
"status": "active",
|
||||
"medicationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://snomed.info/sct",
|
||||
"code": "1222004",
|
||||
"display": "Metronidazole-containing product"
|
||||
}
|
||||
],
|
||||
"text": "Metronidazole-containing product"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"effectiveDateTime": "2025-01-01",
|
||||
"effectivePeriod": {
|
||||
"start": "2025-01-01",
|
||||
"end": "2025-12-31"
|
||||
},
|
||||
"dateAsserted": "2025-06-17T18:36:18+00:00",
|
||||
"dosage": [
|
||||
{
|
||||
"text": "500 mg daily",
|
||||
"route": {
|
||||
"text": "Oral"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reasonCode": [
|
||||
{
|
||||
"text": "Infection"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a75",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "medication-statement-2",
|
||||
"status": "active",
|
||||
"medicationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://snomed.info/sct",
|
||||
"code": "796001",
|
||||
"display": "Digoxin-containing product"
|
||||
}
|
||||
],
|
||||
"text": "Digoxin-containing product"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"effectiveDateTime": "2025-01-01",
|
||||
"effectivePeriod": {
|
||||
"start": "2025-01-01"
|
||||
},
|
||||
"dateAsserted": "2025-06-17T18:36:18+00:00",
|
||||
"dosage": [
|
||||
{
|
||||
"text": "0.125 mg daily",
|
||||
"route": {
|
||||
"text": "Oral"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reasonCode": [
|
||||
{
|
||||
"text": "Heart failure"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
188
tests/fixtures/iti65/bundle-transaction-it.json
vendored
188
tests/fixtures/iti65/bundle-transaction-it.json
vendored
@ -1,188 +0,0 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:5ca8da35-efb1-498a-93bc-ed34524a360f",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "urn:oid:2.16.858.1.858.1.1.1",
|
||||
"value": "123456789"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"name": [
|
||||
{
|
||||
"use": "official",
|
||||
"family": "Pérez",
|
||||
"given": [
|
||||
"Juan"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Patient"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:15f62e6b-f4d9-43d8-91f5-ee963a34339c",
|
||||
"resource": {
|
||||
"resourceType": "DocumentReference",
|
||||
"status": "current",
|
||||
"docStatus": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "57133-1",
|
||||
"display": "Referral note"
|
||||
}
|
||||
],
|
||||
"text": "Informe de Contra-referencia Dermatológica"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:5ca8da35-efb1-498a-93bc-ed34524a360f"
|
||||
},
|
||||
"date": "2026-04-23T07:50:00-03:00",
|
||||
"author": [
|
||||
{
|
||||
"display": "Dra. Especialista - Nodo Panamá"
|
||||
}
|
||||
],
|
||||
"description": "Respuesta a interconsulta - Caso Dermatología",
|
||||
"content": [
|
||||
{
|
||||
"attachment": {
|
||||
"contentType": "application/fhir+json",
|
||||
"url": "urn:uuid:03e4a890-b152-481a-9ca5-685e03550a08"
|
||||
}
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"related": [
|
||||
{
|
||||
"identifier": {
|
||||
"system": "https://salud.gub.uy/interconsultas",
|
||||
"value": "UY-PA-2026-998877"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "DocumentReference"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:03e4a890-b152-481a-9ca5-685e03550a08",
|
||||
"resource": {
|
||||
"resourceType": "Bundle",
|
||||
"type": "document",
|
||||
"timestamp": "2026-04-23T07:50:00-03:00",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:4d58cb4f-1f8b-4f94-9877-bb5a0de47955",
|
||||
"resource": {
|
||||
"resourceType": "Composition",
|
||||
"status": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "11488-4",
|
||||
"display": "Consultation note"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:5ca8da35-efb1-498a-93bc-ed34524a360f"
|
||||
},
|
||||
"date": "2026-04-23T07:50:00-03:00",
|
||||
"author": [
|
||||
{
|
||||
"display": "Dra. Especialista - Nodo Panamá"
|
||||
}
|
||||
],
|
||||
"title": "Informe de Contra-referencia Dermatológica",
|
||||
"section": [
|
||||
{
|
||||
"title": "Motivo de Interconsulta",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "42349-1",
|
||||
"display": "Reason for referral"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Se solicita segunda opinión para evaluación de lesión pigmentada en región escapular, con el fin de descartar malignidad tras hallazgos clínicos iniciales en Uruguay.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Revisión de Evidencia",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "30954-2",
|
||||
"display": "Relevant diagnostic tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Se re-evaluaron los resultados del IPS (Bundle/2313), específicamente el DiagnosticReport/biopsia-001 y las imágenes dermatoscópicas adjuntas.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Juicio Clínico",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "51848-0",
|
||||
"display": "Assessment note"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Tras el análisis de la evidencia, se concluye que la morfología de la lesión es compatible con un Nevus Displásico. No se identifican criterios histológicos de melanoma.</p></div>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Recomendaciones",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "18776-5",
|
||||
"display": "Plan of care"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><ul><li>Control dermatoscópico cada 6 meses.</li><li>Uso de fotoprotección (FPS 50+).</li><li>No se requiere intervención quirúrgica inmediata.</li></ul></div>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Bundle"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
258
tests/fixtures/iti65/bundle-transaction-meow.json
vendored
258
tests/fixtures/iti65/bundle-transaction-meow.json
vendored
@ -1,258 +0,0 @@
|
||||
{
|
||||
"resourceType": "Bundle",
|
||||
"id": "iti65-transaction-medication",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "patient-example",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "http://hospital.example.org",
|
||||
"value": "12345"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"family": "Doe",
|
||||
"given": [
|
||||
"John"
|
||||
]
|
||||
}
|
||||
],
|
||||
"gender": "male",
|
||||
"birthDate": "1970-01-01"
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Patient"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a80",
|
||||
"resource": {
|
||||
"resourceType": "DocumentReference",
|
||||
"status": "current",
|
||||
"docStatus": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "56445-0",
|
||||
"display": "Medication summary"
|
||||
}
|
||||
],
|
||||
"text": "Medication Overview"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"date": "2025-06-17T18:36:18+00:00",
|
||||
"author": [
|
||||
{
|
||||
"display": "Dra. Especialista"
|
||||
}
|
||||
],
|
||||
"description": "Medication Overview - Patient Medication Summary",
|
||||
"content": [
|
||||
{
|
||||
"attachment": {
|
||||
"contentType": "application/fhir+json",
|
||||
"url": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a71"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "DocumentReference"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a71",
|
||||
"resource": {
|
||||
"resourceType": "Bundle",
|
||||
"id": "medication-overview-example",
|
||||
"meta": {
|
||||
"profile": [
|
||||
"http://profiles.ihe.net/PHARM/MEOW/StructureDefinition/MedicationOverview"
|
||||
]
|
||||
},
|
||||
"identifier": {
|
||||
"system": "urn:ietf:rfc:3986",
|
||||
"value": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a71"
|
||||
},
|
||||
"type": "document",
|
||||
"timestamp": "2025-06-17T18:36:18+00:00",
|
||||
"entry": [
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a72",
|
||||
"resource": {
|
||||
"resourceType": "Composition",
|
||||
"id": "composition-example",
|
||||
"meta": {
|
||||
"profile": [
|
||||
"http://profiles.ihe.net/PHARM/MEOW/StructureDefinition/MedicationOverviewComposition"
|
||||
]
|
||||
},
|
||||
"status": "final",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "56445-0",
|
||||
"display": "Medication summary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"date": "2025-06-17T18:36:18+00:00",
|
||||
"author": [
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
}
|
||||
],
|
||||
"title": "Medication Overview",
|
||||
"section": [
|
||||
{
|
||||
"title": "Medicamentos",
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://loinc.org",
|
||||
"code": "55112-7",
|
||||
"display": "Document summary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated",
|
||||
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Resumen de medicamentos</p></div>"
|
||||
},
|
||||
"entry": [
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a74"
|
||||
},
|
||||
{
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a75"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73",
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "patient-example",
|
||||
"identifier": [
|
||||
{
|
||||
"system": "http://hospital.example.org",
|
||||
"value": "12345"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"family": "Doe",
|
||||
"given": [
|
||||
"John"
|
||||
]
|
||||
}
|
||||
],
|
||||
"gender": "male",
|
||||
"birthDate": "1970-01-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a74",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "medication-statement-1",
|
||||
"status": "active",
|
||||
"medicationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://snomed.info/sct",
|
||||
"code": "1222004",
|
||||
"display": "Metronidazole-containing product"
|
||||
}
|
||||
],
|
||||
"text": "Metronidazole-containing product"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"effectiveDateTime": "2025-01-01",
|
||||
"effectivePeriod": {
|
||||
"start": "2025-01-01",
|
||||
"end": "2025-12-31"
|
||||
},
|
||||
"dateAsserted": "2025-06-17T18:36:18+00:00",
|
||||
"dosage": [
|
||||
{
|
||||
"text": "500 mg daily",
|
||||
"route": {
|
||||
"text": "Oral"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reasonCode": [
|
||||
{
|
||||
"text": "Infection"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a75",
|
||||
"resource": {
|
||||
"resourceType": "MedicationStatement",
|
||||
"id": "medication-statement-2",
|
||||
"status": "active",
|
||||
"medicationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://snomed.info/sct",
|
||||
"code": "796001",
|
||||
"display": "Digoxin-containing product"
|
||||
}
|
||||
],
|
||||
"text": "Digoxin-containing product"
|
||||
},
|
||||
"subject": {
|
||||
"reference": "urn:uuid:68c15694-52f1-4171-bc01-e63d666d2a73"
|
||||
},
|
||||
"effectiveDateTime": "2025-01-01",
|
||||
"effectivePeriod": {
|
||||
"start": "2025-01-01"
|
||||
},
|
||||
"dateAsserted": "2025-06-17T18:36:18+00:00",
|
||||
"dosage": [
|
||||
{
|
||||
"text": "0.125 mg daily",
|
||||
"route": {
|
||||
"text": "Oral"
|
||||
}
|
||||
}
|
||||
],
|
||||
"reasonCode": [
|
||||
{
|
||||
"text": "Heart failure"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "Bundle"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user