@precisa-saude/fhir 0.22.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-K3TK3OHS.js → chunk-NKA4JGNI.js} +15 -17
- package/dist/chunk-NKA4JGNI.js.map +1 -0
- package/dist/{chunk-EAX6MHYX.cjs → chunk-RSKFZ5UB.cjs} +16 -18
- package/dist/chunk-RSKFZ5UB.cjs.map +1 -0
- package/dist/cli.js +13 -17
- package/dist/converter-BXidLD5t.d.ts +153 -0
- package/dist/converter-D3h6dLHm.d.cts +153 -0
- package/dist/converter.cjs +2 -2
- package/dist/converter.d.cts +1 -1
- package/dist/converter.d.ts +1 -1
- package/dist/converter.js +1 -1
- package/dist/index.cjs +9 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EAX6MHYX.cjs.map +0 -1
- package/dist/chunk-K3TK3OHS.js.map +0 -1
- package/dist/converter-BAWBB9Gp.d.cts +0 -99
- package/dist/converter-D74wBs5k.d.ts +0 -99
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
unitToUCUM
|
|
11
11
|
} from "./chunk-R4MUCMO3.js";
|
|
12
12
|
|
|
13
|
+
// src/bundle-urls.ts
|
|
14
|
+
var BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
15
|
+
var entryFullUrl = (resource) => `${BUNDLE_BASE_URL}/${resource.resourceType}/${resource.id}`;
|
|
16
|
+
|
|
13
17
|
// src/converter.ts
|
|
14
18
|
function interpretationCode(flag) {
|
|
15
19
|
switch (flag) {
|
|
@@ -223,29 +227,21 @@ function userProfileToFHIR(profile) {
|
|
|
223
227
|
}
|
|
224
228
|
function labResultToFHIRBundle(report, observations, userProfile) {
|
|
225
229
|
const patientId = userProfile.userId;
|
|
226
|
-
const fhirObservations = observations.map((obs) =>
|
|
227
|
-
|
|
228
|
-
resource: labObservationToFHIR(
|
|
230
|
+
const fhirObservations = observations.map((obs) => {
|
|
231
|
+
const resource = labObservationToFHIR(
|
|
229
232
|
{ ...obs, collectionDate: report.collectionDate },
|
|
230
233
|
patientId,
|
|
231
234
|
report.laboratoryName
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
235
|
+
);
|
|
236
|
+
return { fullUrl: entryFullUrl(resource), resource };
|
|
237
|
+
});
|
|
238
|
+
const observationIds = fhirObservations.map((entry) => entry.resource.id);
|
|
237
239
|
const diagnosticReport = labReportToFHIR(report, patientId, observationIds);
|
|
238
240
|
const fhirPatient = userProfileToFHIR(userProfile);
|
|
239
241
|
return {
|
|
240
242
|
entry: [
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
resource: fhirPatient
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
fullUrl: `urn:uuid:diagnostic-report-${report.reportId}`,
|
|
247
|
-
resource: diagnosticReport
|
|
248
|
-
},
|
|
243
|
+
{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },
|
|
244
|
+
{ fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },
|
|
249
245
|
...fhirObservations
|
|
250
246
|
],
|
|
251
247
|
resourceType: "Bundle",
|
|
@@ -254,9 +250,11 @@ function labResultToFHIRBundle(report, observations, userProfile) {
|
|
|
254
250
|
}
|
|
255
251
|
|
|
256
252
|
export {
|
|
253
|
+
BUNDLE_BASE_URL,
|
|
254
|
+
entryFullUrl,
|
|
257
255
|
labObservationToFHIR,
|
|
258
256
|
labReportToFHIR,
|
|
259
257
|
userProfileToFHIR,
|
|
260
258
|
labResultToFHIRBundle
|
|
261
259
|
};
|
|
262
|
-
//# sourceMappingURL=chunk-
|
|
260
|
+
//# sourceMappingURL=chunk-NKA4JGNI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bundle-urls.ts","../src/converter.ts"],"sourcesContent":["/**\n * Endereço das entradas de um Bundle.\n *\n * O `fullUrl` identifica a entrada, e é contra ele que o validador resolve as\n * referências entre os recursos. Um Bundle com `subject.reference` valendo\n * `Patient/abc` só resolve se alguma entrada tiver `fullUrl` terminando em\n * `/Patient/abc`: a referência relativa é lida contra a base do `fullUrl` da\n * entrada, que é como os exemplos da própria HL7 montam Bundle de coleção.\n *\n * Antes o `fullUrl` era `urn:uuid:observation-<laudo>-<código>`, que erra duas\n * vezes. `urn:uuid:` exige a sintaxe de UUID e aquilo não era um UUID, e num\n * Bundle de entradas `urn:uuid:` a referência precisa repetir a URN inteira,\n * então nenhuma das relativas resolvia. Um laudo de 22 marcadores saía com 24\n * erros de URN e 22 referências perdidas.\n */\n/**\n * Recurso que já tem id, e por isso pode ser endereçado numa entrada.\n *\n * No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade\n * própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem\n * `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre\n * atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar\n * até a montagem da URL.\n */\nexport type Addressable<T> = T & { id: string };\n\n/**\n * Base dos `fullUrl`.\n *\n * Não precisa responder a uma requisição: no FHIR o `fullUrl` é identidade, não\n * endereço de download. Fica sob um domínio nosso para não colidir com a\n * identidade de recurso de outra instituição, que é o risco real de usar\n * `example.org` em dado que sai da máquina.\n */\nexport const BUNDLE_BASE_URL = 'https://precisa-saude.com.br/fhir';\n\n/**\n * Monta o `fullUrl` a partir do próprio recurso.\n *\n * Recebe o recurso em vez do tipo e do id soltos de propósito. O defeito que\n * isto substitui nasceu de montar os dois lados em separado: o `fullUrl` dizia\n * `observation-demo-Hgb` enquanto o recurso tinha id `demo-Hgb`, e ninguém\n * percebeu porque nada obrigava os dois a concordarem.\n *\n * O parâmetro pede o mínimo que a URL consome, e não a união de recursos que\n * este pacote converte. Um Bundle pode carregar qualquer recurso do R4, e quem\n * acrescenta uma entrada de um tipo que não está nessa união (um `Specimen`,\n * por exemplo) precisa do mesmo endereço, senão monta o dele e as duas formas\n * divergem outra vez.\n */\nexport const entryFullUrl = (resource: Addressable<{ resourceType: string }>): string =>\n `${BUNDLE_BASE_URL}/${resource.resourceType}/${resource.id}`;\n","/**\n * FHIR Converter\n *\n * Converts lab results to FHIR R4 DiagnosticReport and Observation resources.\n * See: https://hl7.org/fhir/diagnosticreport.html\n */\n\nimport { codeToLoinc } from './biomarkers';\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type { FHIRBundle, FHIRDiagnosticReport, FHIRObservation, FHIRPatient } from './fhir-types';\nimport type { Flag, LabObservationData, LabReportData, UserProfileData } from './types';\nimport { getDefaultUnit, unitToUCUM } from './units';\n\n// Re-export all types and functions\nexport * from './fhir-types';\n\n/**\n * Convert Flag to FHIR interpretation code\n */\nfunction interpretationCode(flag: Flag): string {\n switch (flag) {\n case 'H':\n return 'H'; // High\n case 'L':\n return 'L'; // Low\n default:\n return 'N'; // Normal\n }\n}\n\n/**\n * Convert Flag to FHIR interpretation display\n */\nfunction interpretationDisplay(flag: Flag): string {\n switch (flag) {\n case 'H':\n return 'High';\n case 'L':\n return 'Low';\n default:\n return 'Normal';\n }\n}\n\n/**\n * Convert generic lab observation to FHIR Observation\n */\nexport function labObservationToFHIR(\n observation: LabObservationData,\n patientId: string,\n laboratoryName?: string,\n): Addressable<FHIRObservation> {\n const loincCode = codeToLoinc(observation.biomarkerCode);\n // Use default unit if source unit is empty\n const sourceUnit =\n observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;\n const ucumUnit = unitToUCUM(sourceUnit);\n const isQualitative = observation.isQualitative || typeof observation.value === 'string';\n\n // Base observation structure\n const fhirObs: Addressable<FHIRObservation> = {\n category: [\n {\n coding: [\n {\n code: 'laboratory',\n display: 'Laboratory',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n // Sem LOINC, o coding LOINC simplesmente não sai. Antes ia `99999-9`,\n // que não é código LOINC nenhum: publicava sob `http://loinc.org` uma\n // afirmação falsa, e quem consumisse o bundle confiando no system\n // trataria aquilo como código de verdade. Composição corporal, densidade\n // óssea e escore de cálcio não têm LOINC, e o certo é a lacuna explícita.\n coding: [\n ...(loincCode\n ? [\n {\n code: loincCode,\n display: observation.biomarkerName,\n system: LOINC_SYSTEM,\n },\n ]\n : []),\n {\n code: observation.biomarkerCode,\n display: observation.biomarkerName,\n system: BIOMARKER_CODE_SYSTEM,\n },\n ],\n text: observation.biomarkerName,\n },\n effectiveDateTime: observation.collectionDate,\n id: `${observation.reportId}-${observation.biomarkerCode}`,\n interpretation: [\n {\n coding: [\n {\n code: interpretationCode(observation.flag),\n display: interpretationDisplay(observation.flag),\n system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation',\n },\n ],\n },\n ],\n performer: laboratoryName ? [{ display: laboratoryName }] : undefined,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n // Add value based on type (qualitative = string, quantitative = number)\n if (isQualitative) {\n fhirObs.valueString = String(observation.value);\n } else {\n fhirObs.valueQuantity = {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.value as number,\n };\n\n // Reference range only applies to quantitative values\n if (observation.referenceMin !== undefined && observation.referenceMax !== undefined) {\n fhirObs.referenceRange = [\n {\n high: {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.referenceMax,\n },\n low: {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.referenceMin,\n },\n },\n ];\n }\n }\n\n return fhirObs;\n}\n\n/**\n * Convert generic lab report to FHIR DiagnosticReport\n */\nexport function labReportToFHIR(\n report: LabReportData,\n patientId: string,\n observationIds: string[],\n): Addressable<FHIRDiagnosticReport> {\n // Map processing status to FHIR status\n let status: FHIRDiagnosticReport['status'];\n switch (report.processingStatus) {\n case 'complete':\n status = 'final';\n break;\n case 'partial':\n status = 'partial';\n break;\n case 'pending_review':\n status = 'preliminary';\n break;\n default:\n status = 'final';\n }\n\n return {\n category: [\n {\n coding: [\n {\n code: 'LAB',\n display: 'Laboratory',\n system: 'http://terminology.hl7.org/CodeSystem/v2-0074',\n },\n ],\n },\n ],\n code: {\n coding: [\n {\n code: '11502-2', // Laboratory report\n display: 'Laboratory report',\n system: 'http://loinc.org',\n },\n ],\n text: 'Laboratory Results',\n },\n conclusion:\n report.overallStatus === 'NORMAL'\n ? 'All results within normal limits'\n : 'One or more abnormal results detected',\n conclusionCode:\n report.overallStatus === 'ANORMAL'\n ? [\n {\n coding: [\n {\n code: 'A',\n display: 'Abnormal',\n system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation',\n },\n ],\n },\n ]\n : undefined,\n effectiveDateTime: report.collectionDate,\n id: report.reportId,\n issued: report.createdAt,\n performer: report.laboratoryName ? [{ display: report.laboratoryName }] : undefined,\n resourceType: 'DiagnosticReport',\n result: observationIds.map((id) => ({ reference: `Observation/${id}` })),\n status,\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n}\n\n/**\n * Convert user profile to FHIR Patient\n * NOTE: CPF is intentionally excluded for privacy (LGPD compliance)\n */\nexport function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient> {\n const nameParts = profile.name.split(' ');\n const given = nameParts.slice(0, -1);\n const family = nameParts[nameParts.length - 1] || '';\n\n return {\n address: profile.address\n ? [\n {\n city: profile.address.city,\n country: profile.address.country || 'BR',\n line: [\n profile.address.street && profile.address.number\n ? `${profile.address.street}, ${profile.address.number}`\n : profile.address.street,\n profile.address.complement,\n ].filter(Boolean) as string[],\n postalCode: profile.address.postalCode,\n state: profile.address.state,\n },\n ]\n : undefined,\n birthDate: profile.birthDate,\n gender: profile.gender,\n id: profile.userId,\n name: [\n {\n family,\n given: given.length > 0 ? given : undefined,\n text: profile.name,\n },\n ],\n resourceType: 'Patient',\n telecom:\n [\n ...(profile.email ? [{ system: 'email' as const, value: profile.email }] : []),\n ...(profile.phone ? [{ system: 'phone' as const, value: profile.phone }] : []),\n ].length > 0\n ? [\n ...(profile.email ? [{ system: 'email' as const, value: profile.email }] : []),\n ...(profile.phone ? [{ system: 'phone' as const, value: profile.phone }] : []),\n ]\n : undefined,\n };\n}\n\n/**\n * Convert complete lab result to FHIR Bundle\n * This is the main function for exporting lab results to FHIR R4 format\n */\nexport function labResultToFHIRBundle(\n report: LabReportData,\n observations: LabObservationData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n\n // Convert observations\n const fhirObservations = observations.map((obs) => {\n const resource = labObservationToFHIR(\n { ...obs, collectionDate: report.collectionDate },\n patientId,\n report.laboratoryName,\n );\n\n return { fullUrl: entryFullUrl(resource), resource };\n });\n\n // Os ids vêm dos recursos já montados, e não de uma segunda montagem da mesma\n // regra. O `DiagnosticReport.result` aponta para eles, e era aqui que a\n // referência se separava do recurso.\n const observationIds = fhirObservations.map((entry) => entry.resource.id);\n\n // Convert report\n const diagnosticReport = labReportToFHIR(report, patientId, observationIds);\n\n // Convert patient\n const fhirPatient = userProfileToFHIR(userProfile);\n\n return {\n entry: [\n { fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },\n { fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },\n ...fhirObservations,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -10,6 +10,10 @@ var _chunkAM2RCUUJcjs = require('./chunk-AM2RCUUJ.cjs');
|
|
|
10
10
|
|
|
11
11
|
var _chunkMJ254F5Kcjs = require('./chunk-MJ254F5K.cjs');
|
|
12
12
|
|
|
13
|
+
// src/bundle-urls.ts
|
|
14
|
+
var BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
15
|
+
var entryFullUrl = (resource) => `${BUNDLE_BASE_URL}/${resource.resourceType}/${resource.id}`;
|
|
16
|
+
|
|
13
17
|
// src/converter.ts
|
|
14
18
|
function interpretationCode(flag) {
|
|
15
19
|
switch (flag) {
|
|
@@ -223,29 +227,21 @@ function userProfileToFHIR(profile) {
|
|
|
223
227
|
}
|
|
224
228
|
function labResultToFHIRBundle(report, observations, userProfile) {
|
|
225
229
|
const patientId = userProfile.userId;
|
|
226
|
-
const fhirObservations = observations.map((obs) =>
|
|
227
|
-
|
|
228
|
-
resource: labObservationToFHIR(
|
|
230
|
+
const fhirObservations = observations.map((obs) => {
|
|
231
|
+
const resource = labObservationToFHIR(
|
|
229
232
|
{ ...obs, collectionDate: report.collectionDate },
|
|
230
233
|
patientId,
|
|
231
234
|
report.laboratoryName
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
235
|
+
);
|
|
236
|
+
return { fullUrl: entryFullUrl(resource), resource };
|
|
237
|
+
});
|
|
238
|
+
const observationIds = fhirObservations.map((entry) => entry.resource.id);
|
|
237
239
|
const diagnosticReport = labReportToFHIR(report, patientId, observationIds);
|
|
238
240
|
const fhirPatient = userProfileToFHIR(userProfile);
|
|
239
241
|
return {
|
|
240
242
|
entry: [
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
resource: fhirPatient
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
fullUrl: `urn:uuid:diagnostic-report-${report.reportId}`,
|
|
247
|
-
resource: diagnosticReport
|
|
248
|
-
},
|
|
243
|
+
{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },
|
|
244
|
+
{ fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },
|
|
249
245
|
...fhirObservations
|
|
250
246
|
],
|
|
251
247
|
resourceType: "Bundle",
|
|
@@ -258,5 +254,7 @@ function labResultToFHIRBundle(report, observations, userProfile) {
|
|
|
258
254
|
|
|
259
255
|
|
|
260
256
|
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
exports.BUNDLE_BASE_URL = BUNDLE_BASE_URL; exports.entryFullUrl = entryFullUrl; exports.labObservationToFHIR = labObservationToFHIR; exports.labReportToFHIR = labReportToFHIR; exports.userProfileToFHIR = userProfileToFHIR; exports.labResultToFHIRBundle = labResultToFHIRBundle;
|
|
260
|
+
//# sourceMappingURL=chunk-RSKFZ5UB.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-RSKFZ5UB.cjs","../src/bundle-urls.ts","../src/converter.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-RSKFZ5UB.cjs","sourcesContent":[null,"/**\n * Endereço das entradas de um Bundle.\n *\n * O `fullUrl` identifica a entrada, e é contra ele que o validador resolve as\n * referências entre os recursos. Um Bundle com `subject.reference` valendo\n * `Patient/abc` só resolve se alguma entrada tiver `fullUrl` terminando em\n * `/Patient/abc`: a referência relativa é lida contra a base do `fullUrl` da\n * entrada, que é como os exemplos da própria HL7 montam Bundle de coleção.\n *\n * Antes o `fullUrl` era `urn:uuid:observation-<laudo>-<código>`, que erra duas\n * vezes. `urn:uuid:` exige a sintaxe de UUID e aquilo não era um UUID, e num\n * Bundle de entradas `urn:uuid:` a referência precisa repetir a URN inteira,\n * então nenhuma das relativas resolvia. Um laudo de 22 marcadores saía com 24\n * erros de URN e 22 referências perdidas.\n */\n/**\n * Recurso que já tem id, e por isso pode ser endereçado numa entrada.\n *\n * No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade\n * própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem\n * `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre\n * atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar\n * até a montagem da URL.\n */\nexport type Addressable<T> = T & { id: string };\n\n/**\n * Base dos `fullUrl`.\n *\n * Não precisa responder a uma requisição: no FHIR o `fullUrl` é identidade, não\n * endereço de download. Fica sob um domínio nosso para não colidir com a\n * identidade de recurso de outra instituição, que é o risco real de usar\n * `example.org` em dado que sai da máquina.\n */\nexport const BUNDLE_BASE_URL = 'https://precisa-saude.com.br/fhir';\n\n/**\n * Monta o `fullUrl` a partir do próprio recurso.\n *\n * Recebe o recurso em vez do tipo e do id soltos de propósito. O defeito que\n * isto substitui nasceu de montar os dois lados em separado: o `fullUrl` dizia\n * `observation-demo-Hgb` enquanto o recurso tinha id `demo-Hgb`, e ninguém\n * percebeu porque nada obrigava os dois a concordarem.\n *\n * O parâmetro pede o mínimo que a URL consome, e não a união de recursos que\n * este pacote converte. Um Bundle pode carregar qualquer recurso do R4, e quem\n * acrescenta uma entrada de um tipo que não está nessa união (um `Specimen`,\n * por exemplo) precisa do mesmo endereço, senão monta o dele e as duas formas\n * divergem outra vez.\n */\nexport const entryFullUrl = (resource: Addressable<{ resourceType: string }>): string =>\n `${BUNDLE_BASE_URL}/${resource.resourceType}/${resource.id}`;\n","/**\n * FHIR Converter\n *\n * Converts lab results to FHIR R4 DiagnosticReport and Observation resources.\n * See: https://hl7.org/fhir/diagnosticreport.html\n */\n\nimport { codeToLoinc } from './biomarkers';\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type { FHIRBundle, FHIRDiagnosticReport, FHIRObservation, FHIRPatient } from './fhir-types';\nimport type { Flag, LabObservationData, LabReportData, UserProfileData } from './types';\nimport { getDefaultUnit, unitToUCUM } from './units';\n\n// Re-export all types and functions\nexport * from './fhir-types';\n\n/**\n * Convert Flag to FHIR interpretation code\n */\nfunction interpretationCode(flag: Flag): string {\n switch (flag) {\n case 'H':\n return 'H'; // High\n case 'L':\n return 'L'; // Low\n default:\n return 'N'; // Normal\n }\n}\n\n/**\n * Convert Flag to FHIR interpretation display\n */\nfunction interpretationDisplay(flag: Flag): string {\n switch (flag) {\n case 'H':\n return 'High';\n case 'L':\n return 'Low';\n default:\n return 'Normal';\n }\n}\n\n/**\n * Convert generic lab observation to FHIR Observation\n */\nexport function labObservationToFHIR(\n observation: LabObservationData,\n patientId: string,\n laboratoryName?: string,\n): Addressable<FHIRObservation> {\n const loincCode = codeToLoinc(observation.biomarkerCode);\n // Use default unit if source unit is empty\n const sourceUnit =\n observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;\n const ucumUnit = unitToUCUM(sourceUnit);\n const isQualitative = observation.isQualitative || typeof observation.value === 'string';\n\n // Base observation structure\n const fhirObs: Addressable<FHIRObservation> = {\n category: [\n {\n coding: [\n {\n code: 'laboratory',\n display: 'Laboratory',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n // Sem LOINC, o coding LOINC simplesmente não sai. Antes ia `99999-9`,\n // que não é código LOINC nenhum: publicava sob `http://loinc.org` uma\n // afirmação falsa, e quem consumisse o bundle confiando no system\n // trataria aquilo como código de verdade. Composição corporal, densidade\n // óssea e escore de cálcio não têm LOINC, e o certo é a lacuna explícita.\n coding: [\n ...(loincCode\n ? [\n {\n code: loincCode,\n display: observation.biomarkerName,\n system: LOINC_SYSTEM,\n },\n ]\n : []),\n {\n code: observation.biomarkerCode,\n display: observation.biomarkerName,\n system: BIOMARKER_CODE_SYSTEM,\n },\n ],\n text: observation.biomarkerName,\n },\n effectiveDateTime: observation.collectionDate,\n id: `${observation.reportId}-${observation.biomarkerCode}`,\n interpretation: [\n {\n coding: [\n {\n code: interpretationCode(observation.flag),\n display: interpretationDisplay(observation.flag),\n system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation',\n },\n ],\n },\n ],\n performer: laboratoryName ? [{ display: laboratoryName }] : undefined,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n // Add value based on type (qualitative = string, quantitative = number)\n if (isQualitative) {\n fhirObs.valueString = String(observation.value);\n } else {\n fhirObs.valueQuantity = {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.value as number,\n };\n\n // Reference range only applies to quantitative values\n if (observation.referenceMin !== undefined && observation.referenceMax !== undefined) {\n fhirObs.referenceRange = [\n {\n high: {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.referenceMax,\n },\n low: {\n code: ucumUnit,\n system: 'http://unitsofmeasure.org',\n unit: sourceUnit,\n value: observation.referenceMin,\n },\n },\n ];\n }\n }\n\n return fhirObs;\n}\n\n/**\n * Convert generic lab report to FHIR DiagnosticReport\n */\nexport function labReportToFHIR(\n report: LabReportData,\n patientId: string,\n observationIds: string[],\n): Addressable<FHIRDiagnosticReport> {\n // Map processing status to FHIR status\n let status: FHIRDiagnosticReport['status'];\n switch (report.processingStatus) {\n case 'complete':\n status = 'final';\n break;\n case 'partial':\n status = 'partial';\n break;\n case 'pending_review':\n status = 'preliminary';\n break;\n default:\n status = 'final';\n }\n\n return {\n category: [\n {\n coding: [\n {\n code: 'LAB',\n display: 'Laboratory',\n system: 'http://terminology.hl7.org/CodeSystem/v2-0074',\n },\n ],\n },\n ],\n code: {\n coding: [\n {\n code: '11502-2', // Laboratory report\n display: 'Laboratory report',\n system: 'http://loinc.org',\n },\n ],\n text: 'Laboratory Results',\n },\n conclusion:\n report.overallStatus === 'NORMAL'\n ? 'All results within normal limits'\n : 'One or more abnormal results detected',\n conclusionCode:\n report.overallStatus === 'ANORMAL'\n ? [\n {\n coding: [\n {\n code: 'A',\n display: 'Abnormal',\n system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation',\n },\n ],\n },\n ]\n : undefined,\n effectiveDateTime: report.collectionDate,\n id: report.reportId,\n issued: report.createdAt,\n performer: report.laboratoryName ? [{ display: report.laboratoryName }] : undefined,\n resourceType: 'DiagnosticReport',\n result: observationIds.map((id) => ({ reference: `Observation/${id}` })),\n status,\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n}\n\n/**\n * Convert user profile to FHIR Patient\n * NOTE: CPF is intentionally excluded for privacy (LGPD compliance)\n */\nexport function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient> {\n const nameParts = profile.name.split(' ');\n const given = nameParts.slice(0, -1);\n const family = nameParts[nameParts.length - 1] || '';\n\n return {\n address: profile.address\n ? [\n {\n city: profile.address.city,\n country: profile.address.country || 'BR',\n line: [\n profile.address.street && profile.address.number\n ? `${profile.address.street}, ${profile.address.number}`\n : profile.address.street,\n profile.address.complement,\n ].filter(Boolean) as string[],\n postalCode: profile.address.postalCode,\n state: profile.address.state,\n },\n ]\n : undefined,\n birthDate: profile.birthDate,\n gender: profile.gender,\n id: profile.userId,\n name: [\n {\n family,\n given: given.length > 0 ? given : undefined,\n text: profile.name,\n },\n ],\n resourceType: 'Patient',\n telecom:\n [\n ...(profile.email ? [{ system: 'email' as const, value: profile.email }] : []),\n ...(profile.phone ? [{ system: 'phone' as const, value: profile.phone }] : []),\n ].length > 0\n ? [\n ...(profile.email ? [{ system: 'email' as const, value: profile.email }] : []),\n ...(profile.phone ? [{ system: 'phone' as const, value: profile.phone }] : []),\n ]\n : undefined,\n };\n}\n\n/**\n * Convert complete lab result to FHIR Bundle\n * This is the main function for exporting lab results to FHIR R4 format\n */\nexport function labResultToFHIRBundle(\n report: LabReportData,\n observations: LabObservationData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n\n // Convert observations\n const fhirObservations = observations.map((obs) => {\n const resource = labObservationToFHIR(\n { ...obs, collectionDate: report.collectionDate },\n patientId,\n report.laboratoryName,\n );\n\n return { fullUrl: entryFullUrl(resource), resource };\n });\n\n // Os ids vêm dos recursos já montados, e não de uma segunda montagem da mesma\n // regra. O `DiagnosticReport.result` aponta para eles, e era aqui que a\n // referência se separava do recurso.\n const observationIds = fhirObservations.map((entry) => entry.resource.id);\n\n // Convert report\n const diagnosticReport = labReportToFHIR(report, patientId, observationIds);\n\n // Convert patient\n const fhirPatient = userProfileToFHIR(userProfile);\n\n return {\n entry: [\n { fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },\n { fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },\n ...fhirObservations,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -3174,6 +3174,10 @@ async function categories(_args, json) {
|
|
|
3174
3174
|
outputText(lines.join("\n"));
|
|
3175
3175
|
}
|
|
3176
3176
|
|
|
3177
|
+
// src/bundle-urls.ts
|
|
3178
|
+
var BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
3179
|
+
var entryFullUrl = (resource) => `${BUNDLE_BASE_URL}/${resource.resourceType}/${resource.id}`;
|
|
3180
|
+
|
|
3177
3181
|
// src/code-systems.ts
|
|
3178
3182
|
var LOINC_SYSTEM = "http://loinc.org";
|
|
3179
3183
|
var BIOMARKER_CODE_SYSTEM = "http://fhir-brasil.dev/biomarker-codes";
|
|
@@ -4237,29 +4241,21 @@ function userProfileToFHIR(profile) {
|
|
|
4237
4241
|
}
|
|
4238
4242
|
function labResultToFHIRBundle(report, observations, userProfile) {
|
|
4239
4243
|
const patientId = userProfile.userId;
|
|
4240
|
-
const fhirObservations = observations.map((obs) =>
|
|
4241
|
-
|
|
4242
|
-
resource: labObservationToFHIR(
|
|
4244
|
+
const fhirObservations = observations.map((obs) => {
|
|
4245
|
+
const resource = labObservationToFHIR(
|
|
4243
4246
|
{ ...obs, collectionDate: report.collectionDate },
|
|
4244
4247
|
patientId,
|
|
4245
4248
|
report.laboratoryName
|
|
4246
|
-
)
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
);
|
|
4249
|
+
);
|
|
4250
|
+
return { fullUrl: entryFullUrl(resource), resource };
|
|
4251
|
+
});
|
|
4252
|
+
const observationIds = fhirObservations.map((entry) => entry.resource.id);
|
|
4251
4253
|
const diagnosticReport = labReportToFHIR(report, patientId, observationIds);
|
|
4252
4254
|
const fhirPatient = userProfileToFHIR(userProfile);
|
|
4253
4255
|
return {
|
|
4254
4256
|
entry: [
|
|
4255
|
-
{
|
|
4256
|
-
|
|
4257
|
-
resource: fhirPatient
|
|
4258
|
-
},
|
|
4259
|
-
{
|
|
4260
|
-
fullUrl: `urn:uuid:diagnostic-report-${report.reportId}`,
|
|
4261
|
-
resource: diagnosticReport
|
|
4262
|
-
},
|
|
4257
|
+
{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },
|
|
4258
|
+
{ fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },
|
|
4263
4259
|
...fhirObservations
|
|
4264
4260
|
],
|
|
4265
4261
|
resourceType: "Bundle",
|
|
@@ -6678,7 +6674,7 @@ async function main() {
|
|
|
6678
6674
|
strict: false
|
|
6679
6675
|
});
|
|
6680
6676
|
if (values.version) {
|
|
6681
|
-
process.stdout.write(`${"0.
|
|
6677
|
+
process.stdout.write(`${"0.23.0"}
|
|
6682
6678
|
`);
|
|
6683
6679
|
return;
|
|
6684
6680
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { a as FHIRObservation, k as FHIRDiagnosticReport, b as FHIRBundle, m as FHIRPatient } from './fhir-types-jTJayiwn.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Endereço das entradas de um Bundle.
|
|
5
|
+
*
|
|
6
|
+
* O `fullUrl` identifica a entrada, e é contra ele que o validador resolve as
|
|
7
|
+
* referências entre os recursos. Um Bundle com `subject.reference` valendo
|
|
8
|
+
* `Patient/abc` só resolve se alguma entrada tiver `fullUrl` terminando em
|
|
9
|
+
* `/Patient/abc`: a referência relativa é lida contra a base do `fullUrl` da
|
|
10
|
+
* entrada, que é como os exemplos da própria HL7 montam Bundle de coleção.
|
|
11
|
+
*
|
|
12
|
+
* Antes o `fullUrl` era `urn:uuid:observation-<laudo>-<código>`, que erra duas
|
|
13
|
+
* vezes. `urn:uuid:` exige a sintaxe de UUID e aquilo não era um UUID, e num
|
|
14
|
+
* Bundle de entradas `urn:uuid:` a referência precisa repetir a URN inteira,
|
|
15
|
+
* então nenhuma das relativas resolvia. Um laudo de 22 marcadores saía com 24
|
|
16
|
+
* erros de URN e 22 referências perdidas.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Recurso que já tem id, e por isso pode ser endereçado numa entrada.
|
|
20
|
+
*
|
|
21
|
+
* No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade
|
|
22
|
+
* própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem
|
|
23
|
+
* `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre
|
|
24
|
+
* atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar
|
|
25
|
+
* até a montagem da URL.
|
|
26
|
+
*/
|
|
27
|
+
type Addressable<T> = T & {
|
|
28
|
+
id: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Base dos `fullUrl`.
|
|
32
|
+
*
|
|
33
|
+
* Não precisa responder a uma requisição: no FHIR o `fullUrl` é identidade, não
|
|
34
|
+
* endereço de download. Fica sob um domínio nosso para não colidir com a
|
|
35
|
+
* identidade de recurso de outra instituição, que é o risco real de usar
|
|
36
|
+
* `example.org` em dado que sai da máquina.
|
|
37
|
+
*/
|
|
38
|
+
declare const BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
39
|
+
/**
|
|
40
|
+
* Monta o `fullUrl` a partir do próprio recurso.
|
|
41
|
+
*
|
|
42
|
+
* Recebe o recurso em vez do tipo e do id soltos de propósito. O defeito que
|
|
43
|
+
* isto substitui nasceu de montar os dois lados em separado: o `fullUrl` dizia
|
|
44
|
+
* `observation-demo-Hgb` enquanto o recurso tinha id `demo-Hgb`, e ninguém
|
|
45
|
+
* percebeu porque nada obrigava os dois a concordarem.
|
|
46
|
+
*
|
|
47
|
+
* O parâmetro pede o mínimo que a URL consome, e não a união de recursos que
|
|
48
|
+
* este pacote converte. Um Bundle pode carregar qualquer recurso do R4, e quem
|
|
49
|
+
* acrescenta uma entrada de um tipo que não está nessa união (um `Specimen`,
|
|
50
|
+
* por exemplo) precisa do mesmo endereço, senão monta o dele e as duas formas
|
|
51
|
+
* divergem outra vez.
|
|
52
|
+
*/
|
|
53
|
+
declare const entryFullUrl: (resource: Addressable<{
|
|
54
|
+
resourceType: string;
|
|
55
|
+
}>) => string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generic Lab Result Types
|
|
59
|
+
*
|
|
60
|
+
* Type definitions for lab results that work with any laboratory format.
|
|
61
|
+
* Used by the FHIR converter to transform internal data to FHIR R4 resources.
|
|
62
|
+
*/
|
|
63
|
+
type OverallStatus = 'NORMAL' | 'ANORMAL';
|
|
64
|
+
type Flag = 'H' | 'L' | '';
|
|
65
|
+
type Gender = 'male' | 'female' | 'other' | 'unknown';
|
|
66
|
+
/**
|
|
67
|
+
* Lab report data for FHIR conversion
|
|
68
|
+
*/
|
|
69
|
+
interface LabReportData {
|
|
70
|
+
collectionDate: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
laboratoryName?: string;
|
|
73
|
+
overallStatus: OverallStatus;
|
|
74
|
+
processingStatus?: 'complete' | 'partial' | 'pending_review';
|
|
75
|
+
reportId: string;
|
|
76
|
+
userId: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Individual biomarker observation for FHIR conversion
|
|
80
|
+
*/
|
|
81
|
+
interface LabObservationData {
|
|
82
|
+
biomarkerCode: string;
|
|
83
|
+
biomarkerName: string;
|
|
84
|
+
collectionDate?: string;
|
|
85
|
+
flag: Flag;
|
|
86
|
+
isQualitative?: boolean;
|
|
87
|
+
referenceMax?: number;
|
|
88
|
+
referenceMin?: number;
|
|
89
|
+
reportId: string;
|
|
90
|
+
unit: string;
|
|
91
|
+
value: number | string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Intervention data for FHIR conversion
|
|
95
|
+
*/
|
|
96
|
+
interface InterventionData {
|
|
97
|
+
endDate?: string;
|
|
98
|
+
interventionId: string;
|
|
99
|
+
name: string;
|
|
100
|
+
notes?: string;
|
|
101
|
+
startDate: string;
|
|
102
|
+
type: 'medication' | 'diet' | 'exercise' | 'sleep' | 'supplement';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* User profile data for FHIR Patient resource
|
|
106
|
+
*/
|
|
107
|
+
interface UserProfileData {
|
|
108
|
+
address?: {
|
|
109
|
+
street?: string;
|
|
110
|
+
number?: string;
|
|
111
|
+
complement?: string;
|
|
112
|
+
neighborhood?: string;
|
|
113
|
+
city?: string;
|
|
114
|
+
state?: string;
|
|
115
|
+
postalCode?: string;
|
|
116
|
+
country?: string;
|
|
117
|
+
};
|
|
118
|
+
birthDate?: string;
|
|
119
|
+
cpf?: string;
|
|
120
|
+
email?: string;
|
|
121
|
+
gender?: Gender;
|
|
122
|
+
name: string;
|
|
123
|
+
phone?: string;
|
|
124
|
+
userId: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* FHIR Converter
|
|
129
|
+
*
|
|
130
|
+
* Converts lab results to FHIR R4 DiagnosticReport and Observation resources.
|
|
131
|
+
* See: https://hl7.org/fhir/diagnosticreport.html
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Convert generic lab observation to FHIR Observation
|
|
136
|
+
*/
|
|
137
|
+
declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): Addressable<FHIRObservation>;
|
|
138
|
+
/**
|
|
139
|
+
* Convert generic lab report to FHIR DiagnosticReport
|
|
140
|
+
*/
|
|
141
|
+
declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): Addressable<FHIRDiagnosticReport>;
|
|
142
|
+
/**
|
|
143
|
+
* Convert user profile to FHIR Patient
|
|
144
|
+
* NOTE: CPF is intentionally excluded for privacy (LGPD compliance)
|
|
145
|
+
*/
|
|
146
|
+
declare function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient>;
|
|
147
|
+
/**
|
|
148
|
+
* Convert complete lab result to FHIR Bundle
|
|
149
|
+
* This is the main function for exporting lab results to FHIR R4 format
|
|
150
|
+
*/
|
|
151
|
+
declare function labResultToFHIRBundle(report: LabReportData, observations: LabObservationData[], userProfile: UserProfileData): FHIRBundle;
|
|
152
|
+
|
|
153
|
+
export { type Addressable as A, BUNDLE_BASE_URL as B, type Flag as F, type Gender as G, type InterventionData as I, type LabObservationData as L, type OverallStatus as O, type UserProfileData as U, type LabReportData as a, labReportToFHIR as b, labResultToFHIRBundle as c, entryFullUrl as e, labObservationToFHIR as l, userProfileToFHIR as u };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { a as FHIRObservation, k as FHIRDiagnosticReport, b as FHIRBundle, m as FHIRPatient } from './fhir-types-jTJayiwn.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Endereço das entradas de um Bundle.
|
|
5
|
+
*
|
|
6
|
+
* O `fullUrl` identifica a entrada, e é contra ele que o validador resolve as
|
|
7
|
+
* referências entre os recursos. Um Bundle com `subject.reference` valendo
|
|
8
|
+
* `Patient/abc` só resolve se alguma entrada tiver `fullUrl` terminando em
|
|
9
|
+
* `/Patient/abc`: a referência relativa é lida contra a base do `fullUrl` da
|
|
10
|
+
* entrada, que é como os exemplos da própria HL7 montam Bundle de coleção.
|
|
11
|
+
*
|
|
12
|
+
* Antes o `fullUrl` era `urn:uuid:observation-<laudo>-<código>`, que erra duas
|
|
13
|
+
* vezes. `urn:uuid:` exige a sintaxe de UUID e aquilo não era um UUID, e num
|
|
14
|
+
* Bundle de entradas `urn:uuid:` a referência precisa repetir a URN inteira,
|
|
15
|
+
* então nenhuma das relativas resolvia. Um laudo de 22 marcadores saía com 24
|
|
16
|
+
* erros de URN e 22 referências perdidas.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Recurso que já tem id, e por isso pode ser endereçado numa entrada.
|
|
20
|
+
*
|
|
21
|
+
* No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade
|
|
22
|
+
* própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem
|
|
23
|
+
* `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre
|
|
24
|
+
* atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar
|
|
25
|
+
* até a montagem da URL.
|
|
26
|
+
*/
|
|
27
|
+
type Addressable<T> = T & {
|
|
28
|
+
id: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Base dos `fullUrl`.
|
|
32
|
+
*
|
|
33
|
+
* Não precisa responder a uma requisição: no FHIR o `fullUrl` é identidade, não
|
|
34
|
+
* endereço de download. Fica sob um domínio nosso para não colidir com a
|
|
35
|
+
* identidade de recurso de outra instituição, que é o risco real de usar
|
|
36
|
+
* `example.org` em dado que sai da máquina.
|
|
37
|
+
*/
|
|
38
|
+
declare const BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
39
|
+
/**
|
|
40
|
+
* Monta o `fullUrl` a partir do próprio recurso.
|
|
41
|
+
*
|
|
42
|
+
* Recebe o recurso em vez do tipo e do id soltos de propósito. O defeito que
|
|
43
|
+
* isto substitui nasceu de montar os dois lados em separado: o `fullUrl` dizia
|
|
44
|
+
* `observation-demo-Hgb` enquanto o recurso tinha id `demo-Hgb`, e ninguém
|
|
45
|
+
* percebeu porque nada obrigava os dois a concordarem.
|
|
46
|
+
*
|
|
47
|
+
* O parâmetro pede o mínimo que a URL consome, e não a união de recursos que
|
|
48
|
+
* este pacote converte. Um Bundle pode carregar qualquer recurso do R4, e quem
|
|
49
|
+
* acrescenta uma entrada de um tipo que não está nessa união (um `Specimen`,
|
|
50
|
+
* por exemplo) precisa do mesmo endereço, senão monta o dele e as duas formas
|
|
51
|
+
* divergem outra vez.
|
|
52
|
+
*/
|
|
53
|
+
declare const entryFullUrl: (resource: Addressable<{
|
|
54
|
+
resourceType: string;
|
|
55
|
+
}>) => string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generic Lab Result Types
|
|
59
|
+
*
|
|
60
|
+
* Type definitions for lab results that work with any laboratory format.
|
|
61
|
+
* Used by the FHIR converter to transform internal data to FHIR R4 resources.
|
|
62
|
+
*/
|
|
63
|
+
type OverallStatus = 'NORMAL' | 'ANORMAL';
|
|
64
|
+
type Flag = 'H' | 'L' | '';
|
|
65
|
+
type Gender = 'male' | 'female' | 'other' | 'unknown';
|
|
66
|
+
/**
|
|
67
|
+
* Lab report data for FHIR conversion
|
|
68
|
+
*/
|
|
69
|
+
interface LabReportData {
|
|
70
|
+
collectionDate: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
laboratoryName?: string;
|
|
73
|
+
overallStatus: OverallStatus;
|
|
74
|
+
processingStatus?: 'complete' | 'partial' | 'pending_review';
|
|
75
|
+
reportId: string;
|
|
76
|
+
userId: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Individual biomarker observation for FHIR conversion
|
|
80
|
+
*/
|
|
81
|
+
interface LabObservationData {
|
|
82
|
+
biomarkerCode: string;
|
|
83
|
+
biomarkerName: string;
|
|
84
|
+
collectionDate?: string;
|
|
85
|
+
flag: Flag;
|
|
86
|
+
isQualitative?: boolean;
|
|
87
|
+
referenceMax?: number;
|
|
88
|
+
referenceMin?: number;
|
|
89
|
+
reportId: string;
|
|
90
|
+
unit: string;
|
|
91
|
+
value: number | string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Intervention data for FHIR conversion
|
|
95
|
+
*/
|
|
96
|
+
interface InterventionData {
|
|
97
|
+
endDate?: string;
|
|
98
|
+
interventionId: string;
|
|
99
|
+
name: string;
|
|
100
|
+
notes?: string;
|
|
101
|
+
startDate: string;
|
|
102
|
+
type: 'medication' | 'diet' | 'exercise' | 'sleep' | 'supplement';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* User profile data for FHIR Patient resource
|
|
106
|
+
*/
|
|
107
|
+
interface UserProfileData {
|
|
108
|
+
address?: {
|
|
109
|
+
street?: string;
|
|
110
|
+
number?: string;
|
|
111
|
+
complement?: string;
|
|
112
|
+
neighborhood?: string;
|
|
113
|
+
city?: string;
|
|
114
|
+
state?: string;
|
|
115
|
+
postalCode?: string;
|
|
116
|
+
country?: string;
|
|
117
|
+
};
|
|
118
|
+
birthDate?: string;
|
|
119
|
+
cpf?: string;
|
|
120
|
+
email?: string;
|
|
121
|
+
gender?: Gender;
|
|
122
|
+
name: string;
|
|
123
|
+
phone?: string;
|
|
124
|
+
userId: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* FHIR Converter
|
|
129
|
+
*
|
|
130
|
+
* Converts lab results to FHIR R4 DiagnosticReport and Observation resources.
|
|
131
|
+
* See: https://hl7.org/fhir/diagnosticreport.html
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Convert generic lab observation to FHIR Observation
|
|
136
|
+
*/
|
|
137
|
+
declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): Addressable<FHIRObservation>;
|
|
138
|
+
/**
|
|
139
|
+
* Convert generic lab report to FHIR DiagnosticReport
|
|
140
|
+
*/
|
|
141
|
+
declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): Addressable<FHIRDiagnosticReport>;
|
|
142
|
+
/**
|
|
143
|
+
* Convert user profile to FHIR Patient
|
|
144
|
+
* NOTE: CPF is intentionally excluded for privacy (LGPD compliance)
|
|
145
|
+
*/
|
|
146
|
+
declare function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient>;
|
|
147
|
+
/**
|
|
148
|
+
* Convert complete lab result to FHIR Bundle
|
|
149
|
+
* This is the main function for exporting lab results to FHIR R4 format
|
|
150
|
+
*/
|
|
151
|
+
declare function labResultToFHIRBundle(report: LabReportData, observations: LabObservationData[], userProfile: UserProfileData): FHIRBundle;
|
|
152
|
+
|
|
153
|
+
export { type Addressable as A, BUNDLE_BASE_URL as B, type Flag as F, type Gender as G, type InterventionData as I, type LabObservationData as L, type OverallStatus as O, type UserProfileData as U, type LabReportData as a, labReportToFHIR as b, labResultToFHIRBundle as c, entryFullUrl as e, labObservationToFHIR as l, userProfileToFHIR as u };
|
package/dist/converter.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkRSKFZ5UBcjs = require('./chunk-RSKFZ5UB.cjs');
|
|
7
7
|
require('./chunk-OR67NJDZ.cjs');
|
|
8
8
|
require('./chunk-AM2RCUUJ.cjs');
|
|
9
9
|
require('./chunk-MJ254F5K.cjs');
|
|
@@ -12,5 +12,5 @@ require('./chunk-MJ254F5K.cjs');
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
exports.labObservationToFHIR =
|
|
15
|
+
exports.labObservationToFHIR = _chunkRSKFZ5UBcjs.labObservationToFHIR; exports.labReportToFHIR = _chunkRSKFZ5UBcjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkRSKFZ5UBcjs.labResultToFHIRBundle; exports.userProfileToFHIR = _chunkRSKFZ5UBcjs.userProfileToFHIR;
|
|
16
16
|
//# sourceMappingURL=converter.cjs.map
|
package/dist/converter.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
+
export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-D3h6dLHm.cjs';
|
|
1
2
|
export { d as FHIRAddress, e as FHIRAnnotation, f as FHIRAttachment, b as FHIRBundle, g as FHIRBundleEntry, h as FHIRCodeableConcept, i as FHIRCoding, j as FHIRContactPoint, k as FHIRDiagnosticReport, l as FHIRHumanName, c as FHIRIdentifier, F as FHIRMedicationStatement, a as FHIRObservation, m as FHIRPatient, n as FHIRPeriod, o as FHIRQuantity, p as FHIRReference, q as FHIRReferenceRange } from './fhir-types-jTJayiwn.cjs';
|
|
2
|
-
export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-BAWBB9Gp.cjs';
|
package/dist/converter.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
+
export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-BXidLD5t.js';
|
|
1
2
|
export { d as FHIRAddress, e as FHIRAnnotation, f as FHIRAttachment, b as FHIRBundle, g as FHIRBundleEntry, h as FHIRCodeableConcept, i as FHIRCoding, j as FHIRContactPoint, k as FHIRDiagnosticReport, l as FHIRHumanName, c as FHIRIdentifier, F as FHIRMedicationStatement, a as FHIRObservation, m as FHIRPatient, n as FHIRPeriod, o as FHIRQuantity, p as FHIRReference, q as FHIRReferenceRange } from './fhir-types-jTJayiwn.js';
|
|
2
|
-
export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-D74wBs5k.js';
|