@precisa-saude/fhir 0.36.0 → 0.37.1
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/biomarkers.cjs +2 -2
- package/dist/biomarkers.js +1 -1
- package/dist/{chunk-WKTDMR5P.cjs → chunk-A2R65X4X.cjs} +9 -9
- package/dist/{chunk-WKTDMR5P.cjs.map → chunk-A2R65X4X.cjs.map} +1 -1
- package/dist/{chunk-F7CC4SV5.cjs → chunk-AJUDODBT.cjs} +10 -10
- package/dist/{chunk-F7CC4SV5.cjs.map → chunk-AJUDODBT.cjs.map} +1 -1
- package/dist/{chunk-JES2ZLH5.cjs → chunk-BDS6CM7F.cjs} +30 -9
- package/dist/chunk-BDS6CM7F.cjs.map +1 -0
- package/dist/{chunk-YI5WYZCP.js → chunk-CVMOQDM2.js} +2 -2
- package/dist/{chunk-LWSKO7UV.js → chunk-CZLX33DZ.js} +2 -2
- package/dist/{chunk-H64L6SOV.cjs → chunk-DE7NHXIR.cjs} +7 -9
- package/dist/chunk-DE7NHXIR.cjs.map +1 -0
- package/dist/{chunk-M6AYGD7P.js → chunk-I6FCFMAO.js} +4 -4
- package/dist/{chunk-RPGJK7ZM.cjs → chunk-LSC7NHFE.cjs} +3 -3
- package/dist/{chunk-RPGJK7ZM.cjs.map → chunk-LSC7NHFE.cjs.map} +1 -1
- package/dist/{chunk-7TDZX5HO.js → chunk-O7SK2YUU.js} +6 -8
- package/dist/chunk-O7SK2YUU.js.map +1 -0
- package/dist/{chunk-AQTHAZLK.cjs → chunk-RWUFK5AN.cjs} +3 -3
- package/dist/{chunk-AQTHAZLK.cjs.map → chunk-RWUFK5AN.cjs.map} +1 -1
- package/dist/{chunk-3UCY4HPJ.js → chunk-VOBVUACD.js} +3 -3
- package/dist/{chunk-KTV4H2QQ.js → chunk-YCJUGK6D.js} +30 -9
- package/dist/chunk-YCJUGK6D.js.map +1 -0
- package/dist/cli.js +3150 -15
- package/dist/converter.cjs +5 -5
- package/dist/converter.js +4 -4
- package/dist/importer.cjs +5 -5
- package/dist/importer.js +4 -4
- package/dist/index.cjs +3117 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -1
- package/dist/index.d.ts +141 -1
- package/dist/index.js +3112 -6
- package/dist/index.js.map +1 -1
- package/dist/reference-ranges.cjs +4 -4
- package/dist/reference-ranges.js +3 -3
- package/dist/units.cjs +3 -3
- package/dist/units.js +2 -2
- package/dist/validators.cjs +4 -4
- package/dist/validators.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-7TDZX5HO.js.map +0 -1
- package/dist/chunk-H64L6SOV.cjs.map +0 -1
- package/dist/chunk-JES2ZLH5.cjs.map +0 -1
- package/dist/chunk-KTV4H2QQ.js.map +0 -1
- /package/dist/{chunk-YI5WYZCP.js.map → chunk-CVMOQDM2.js.map} +0 -0
- /package/dist/{chunk-LWSKO7UV.js.map → chunk-CZLX33DZ.js.map} +0 -0
- /package/dist/{chunk-M6AYGD7P.js.map → chunk-I6FCFMAO.js.map} +0 -0
- /package/dist/{chunk-3UCY4HPJ.js.map → chunk-VOBVUACD.js.map} +0 -0
package/dist/biomarkers.cjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var _chunkBDS6CM7Fcjs = require('./chunk-BDS6CM7F.cjs');
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
@@ -65,5 +65,5 @@ var _chunkJES2ZLH5cjs = require('./chunk-JES2ZLH5.cjs');
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
exports.BIOMARKER_DEFINITIONS =
|
|
68
|
+
exports.BIOMARKER_DEFINITIONS = _chunkBDS6CM7Fcjs.BIOMARKER_DEFINITIONS; exports.CAC_INDICATOR_CODES = _chunkBDS6CM7Fcjs.CAC_INDICATOR_CODES; exports.DEXA_CATEGORIES = _chunkBDS6CM7Fcjs.DEXA_CATEGORIES; exports.DEXA_INDICATOR_CODES = _chunkBDS6CM7Fcjs.DEXA_INDICATOR_CODES; exports.codeToLoinc = _chunkBDS6CM7Fcjs.codeToLoinc; exports.filterVisibleBiomarkers = _chunkBDS6CM7Fcjs.filterVisibleBiomarkers; exports.findCodeByName = _chunkBDS6CM7Fcjs.findCodeByName; exports.generateCacFullReference = _chunkBDS6CM7Fcjs.generateCacFullReference; exports.generateDexaFullReference = _chunkBDS6CM7Fcjs.generateDexaFullReference; exports.generateFilteredLLMReference = _chunkBDS6CM7Fcjs.generateFilteredLLMReference; exports.generateLLMReference = _chunkBDS6CM7Fcjs.generateLLMReference; exports.getAllCodes = _chunkBDS6CM7Fcjs.getAllCodes; exports.getAllDefinitions = _chunkBDS6CM7Fcjs.getAllDefinitions; exports.getAllLoincCodes = _chunkBDS6CM7Fcjs.getAllLoincCodes; exports.getAllSearchPatterns = _chunkBDS6CM7Fcjs.getAllSearchPatterns; exports.getBiomarkersByCategory = _chunkBDS6CM7Fcjs.getBiomarkersByCategory; exports.getBiomarkersForCategories = _chunkBDS6CM7Fcjs.getBiomarkersForCategories; exports.getDefinitionByCode = _chunkBDS6CM7Fcjs.getDefinitionByCode; exports.getDefinitionByLoinc = _chunkBDS6CM7Fcjs.getDefinitionByLoinc; exports.getDefinitionsBySex = _chunkBDS6CM7Fcjs.getDefinitionsBySex; exports.getSexForCode = _chunkBDS6CM7Fcjs.getSexForCode; exports.getVisibleDefinitions = _chunkBDS6CM7Fcjs.getVisibleDefinitions; exports.isBiomarkerVisible = _chunkBDS6CM7Fcjs.isBiomarkerVisible; exports.isCacDocument = _chunkBDS6CM7Fcjs.isCacDocument; exports.isDexaDocument = _chunkBDS6CM7Fcjs.isDexaDocument; exports.isValidCode = _chunkBDS6CM7Fcjs.isValidCode; exports.isValidLoinc = _chunkBDS6CM7Fcjs.isValidLoinc; exports.loincToCode = _chunkBDS6CM7Fcjs.loincToCode; exports.methodVariantOf = _chunkBDS6CM7Fcjs.methodVariantOf; exports.normalizeCode = _chunkBDS6CM7Fcjs.normalizeCode; exports.toBiomarkerTests = _chunkBDS6CM7Fcjs.toBiomarkerTests; exports.validateLoincNameMatch = _chunkBDS6CM7Fcjs.validateLoincNameMatch;
|
|
69
69
|
//# sourceMappingURL=biomarkers.cjs.map
|
package/dist/biomarkers.js
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
var _chunkOR67NJDZcjs = require('./chunk-OR67NJDZ.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkDE7NHXIRcjs = require('./chunk-DE7NHXIR.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkRWUFK5ANcjs = require('./chunk-RWUFK5AN.cjs');
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
var
|
|
15
|
+
var _chunkBDS6CM7Fcjs = require('./chunk-BDS6CM7F.cjs');
|
|
16
16
|
|
|
17
17
|
// src/bundle-urls.ts
|
|
18
18
|
var BUNDLE_BASE_URL = "https://precisa-saude.com.br/fhir";
|
|
@@ -63,16 +63,16 @@ var buildReferenceRanges = (observation, quantity) => {
|
|
|
63
63
|
if (observation.referenceRanges && observation.referenceRanges.length > 0) {
|
|
64
64
|
return observation.referenceRanges.flatMap((r) => toRanges(r.low, r.high, r.appliesTo));
|
|
65
65
|
}
|
|
66
|
-
const meaning = observation.referenceKind &&
|
|
66
|
+
const meaning = observation.referenceKind && _chunkDE7NHXIRcjs.referenceRangeMeaning.call(void 0, observation.referenceKind);
|
|
67
67
|
return toRanges(observation.referenceMin, observation.referenceMax).map(
|
|
68
68
|
(range) => meaning ? { ...range, type: { coding: [meaning] } } : range
|
|
69
69
|
);
|
|
70
70
|
};
|
|
71
71
|
function labObservationToFHIR(observation, patientId, laboratoryName) {
|
|
72
|
-
const methodLoinc = observation.methodLoinc &&
|
|
73
|
-
const loincCode = _nullishCoalesce(methodLoinc, () => (
|
|
74
|
-
const sourceUnit = observation.unit ||
|
|
75
|
-
const ucumUnit =
|
|
72
|
+
const methodLoinc = observation.methodLoinc && _chunkBDS6CM7Fcjs.methodVariantOf.call(void 0, observation.biomarkerCode, observation.methodLoinc) ? observation.methodLoinc : void 0;
|
|
73
|
+
const loincCode = _nullishCoalesce(methodLoinc, () => ( _chunkBDS6CM7Fcjs.codeToLoinc.call(void 0, observation.biomarkerCode)));
|
|
74
|
+
const sourceUnit = observation.unit || _chunkRWUFK5ANcjs.getDefaultUnit.call(void 0, observation.biomarkerCode) || observation.unit;
|
|
75
|
+
const ucumUnit = _chunkRWUFK5ANcjs.resolveUcum.call(void 0, sourceUnit, observation.biomarkerCode);
|
|
76
76
|
const quantity = (value) => ({
|
|
77
77
|
...ucumUnit ? { code: ucumUnit, system: "http://unitsofmeasure.org" } : {},
|
|
78
78
|
...sourceUnit ? { unit: sourceUnit } : {},
|
|
@@ -273,4 +273,4 @@ function labResultToFHIRBundle(report, observations, userProfile) {
|
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
exports.BUNDLE_BASE_URL = BUNDLE_BASE_URL; exports.entryFullUrl = entryFullUrl; exports.labObservationToFHIR = labObservationToFHIR; exports.labReportToFHIR = labReportToFHIR; exports.userProfileToFHIR = userProfileToFHIR; exports.labResultToFHIRBundle = labResultToFHIRBundle;
|
|
276
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-A2R65X4X.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-WKTDMR5P.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-WKTDMR5P.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, methodVariantOf } from './biomarkers';\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type {\n FHIRBundle,\n FHIRDiagnosticReport,\n FHIRObservation,\n FHIRPatient,\n FHIRQuantity,\n FHIRReferenceRange,\n} from './fhir-types';\nimport { referenceRangeMeaning } from './reference-ranges';\nimport type { Flag, LabObservationData, LabReportData, UserProfileData } from './types';\nimport { getDefaultUnit, resolveUcum } 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 * O `coding` do sexo, que vai **dentro** do CodeableConcept do `appliesTo`.\n *\n * O nome diz `CODING` e não `APPLIES_TO` de propósito: isto não é o valor do\n * campo, é uma entrada da lista de codificações dele. O embrulho acontece no\n * uso, em `{ coding: [SEX_CODING[sex]] }`.\n *\n * É o `AdministrativeGender`, e não o v3-ObservationInterpretation nem um\n * sistema nosso: um consumidor que já lê `Patient.gender` compara os dois sem\n * tabela de tradução no meio.\n */\nconst SEX_CODING = {\n female: {\n code: 'female',\n display: 'Female',\n system: 'http://hl7.org/fhir/administrative-gender',\n },\n male: { code: 'male', display: 'Male', system: 'http://hl7.org/fhir/administrative-gender' },\n} as const;\n\n/**\n * Monta as faixas de referência do `Observation`.\n *\n * Duas mudanças em relação ao que existia, e as duas são sobre não perder o que\n * o laudo imprimiu.\n *\n * **Um limite só já basta.** Antes a faixa só saía com os dois, e um laudo que\n * publica \"inferior a 190 mg/dL\" ou \"superior a 60 mL/min/1,73m²\" perdia o\n * campo inteiro. O R4 trata `low` e `high` como opcionais independentes e\n * documenta o caso de um lado só, e o importador deste mesmo pacote já lia\n * `low?.value` e `high?.value` com acesso opcional: a assimetria era só do\n * escritor. Ver PRE-430.\n *\n * **Mais de uma faixa, anotada.** Laudo com uma coluna de referência por sexo\n * passa a sair com as duas, cada uma com o seu `appliesTo`, em vez de o\n * pipeline escolher uma sem saber de quem é o exame. Ver PRE-424 e PRE-425.\n */\nconst buildReferenceRanges = (\n observation: LabObservationData,\n quantity: (value: number) => FHIRQuantity,\n): FHIRReferenceRange[] => {\n // Devolve lista, e não uma faixa: o caso sem limite nenhum vira lista vazia\n // em vez de `undefined`, e aí os dois caminhos abaixo se compõem com\n // `flatMap` sem ninguém precisar filtrar nada depois.\n const toRanges = (low?: number, high?: number, sex?: 'female' | 'male'): FHIRReferenceRange[] => {\n if (low === undefined && high === undefined) return [];\n\n return [\n {\n ...(sex === undefined ? {} : { appliesTo: [{ coding: [SEX_CODING[sex]] }] }),\n ...(high === undefined ? {} : { high: quantity(high) }),\n ...(low === undefined ? {} : { low: quantity(low) }),\n },\n ];\n };\n\n // A lista anotada tem precedência: quando ela existe, o par simples é o\n // resumo de uma das colunas e repeti-lo publicaria a mesma faixa duas vezes,\n // uma delas sem dizer a quem se aplica.\n //\n // Lista vazia cai no par simples, igual a ausente, e isso é escolha: as duas\n // dizem \"não tenho faixa anotada\", e tratá-las diferente faria um `[]` vindo\n // de um `.filter()` apagar em silêncio a faixa que o chamador também mandou\n // em `referenceMin` e `referenceMax`.\n if (observation.referenceRanges && observation.referenceRanges.length > 0) {\n return observation.referenceRanges.flatMap((r) => toRanges(r.low, r.high, r.appliesTo));\n }\n\n const meaning = observation.referenceKind && referenceRangeMeaning(observation.referenceKind);\n return toRanges(observation.referenceMin, observation.referenceMax).map((range) =>\n meaning ? { ...range, type: { coding: [meaning] } } : range,\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 // O código por método só sai quando é variante declarada do biomarcador. Um\n // `methodLoinc` qualquer viraria afirmação sob `http://loinc.org` que o\n // catálogo não conferiu.\n const methodLoinc =\n observation.methodLoinc && methodVariantOf(observation.biomarkerCode, observation.methodLoinc)\n ? observation.methodLoinc\n : undefined;\n const loincCode = methodLoinc ?? codeToLoinc(observation.biomarkerCode);\n // Use default unit if source unit is empty\n const sourceUnit =\n observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;\n // `system` + `code` só saem quando a unidade resolve em UCUM. Unidade que o\n // pacote não sabe traduzir fica só em `unit`, como texto: afirmar\n // `http://unitsofmeasure.org` sobre `x10^3/mm3` era publicar um código\n // falso, e quem consome o Bundle confiando no system trataria aquilo como\n // UCUM de verdade.\n const ucumUnit = resolveUcum(sourceUnit, observation.biomarkerCode);\n const quantity = (value: number): FHIRQuantity => ({\n ...(ucumUnit ? { code: ucumUnit, system: 'http://unitsofmeasure.org' } : {}),\n ...(sourceUnit ? { unit: sourceUnit } : {}),\n value,\n });\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 = quantity(observation.value as number);\n\n // Reference range only applies to quantitative values\n const referenceRange = buildReferenceRanges(observation, quantity);\n if (referenceRange.length > 0) fhirObs.referenceRange = referenceRange;\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"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-A2R65X4X.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-A2R65X4X.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, methodVariantOf } from './biomarkers';\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type {\n FHIRBundle,\n FHIRDiagnosticReport,\n FHIRObservation,\n FHIRPatient,\n FHIRQuantity,\n FHIRReferenceRange,\n} from './fhir-types';\nimport { referenceRangeMeaning } from './reference-ranges';\nimport type { Flag, LabObservationData, LabReportData, UserProfileData } from './types';\nimport { getDefaultUnit, resolveUcum } 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 * O `coding` do sexo, que vai **dentro** do CodeableConcept do `appliesTo`.\n *\n * O nome diz `CODING` e não `APPLIES_TO` de propósito: isto não é o valor do\n * campo, é uma entrada da lista de codificações dele. O embrulho acontece no\n * uso, em `{ coding: [SEX_CODING[sex]] }`.\n *\n * É o `AdministrativeGender`, e não o v3-ObservationInterpretation nem um\n * sistema nosso: um consumidor que já lê `Patient.gender` compara os dois sem\n * tabela de tradução no meio.\n */\nconst SEX_CODING = {\n female: {\n code: 'female',\n display: 'Female',\n system: 'http://hl7.org/fhir/administrative-gender',\n },\n male: { code: 'male', display: 'Male', system: 'http://hl7.org/fhir/administrative-gender' },\n} as const;\n\n/**\n * Monta as faixas de referência do `Observation`.\n *\n * Duas mudanças em relação ao que existia, e as duas são sobre não perder o que\n * o laudo imprimiu.\n *\n * **Um limite só já basta.** Antes a faixa só saía com os dois, e um laudo que\n * publica \"inferior a 190 mg/dL\" ou \"superior a 60 mL/min/1,73m²\" perdia o\n * campo inteiro. O R4 trata `low` e `high` como opcionais independentes e\n * documenta o caso de um lado só, e o importador deste mesmo pacote já lia\n * `low?.value` e `high?.value` com acesso opcional: a assimetria era só do\n * escritor. Ver PRE-430.\n *\n * **Mais de uma faixa, anotada.** Laudo com uma coluna de referência por sexo\n * passa a sair com as duas, cada uma com o seu `appliesTo`, em vez de o\n * pipeline escolher uma sem saber de quem é o exame. Ver PRE-424 e PRE-425.\n */\nconst buildReferenceRanges = (\n observation: LabObservationData,\n quantity: (value: number) => FHIRQuantity,\n): FHIRReferenceRange[] => {\n // Devolve lista, e não uma faixa: o caso sem limite nenhum vira lista vazia\n // em vez de `undefined`, e aí os dois caminhos abaixo se compõem com\n // `flatMap` sem ninguém precisar filtrar nada depois.\n const toRanges = (low?: number, high?: number, sex?: 'female' | 'male'): FHIRReferenceRange[] => {\n if (low === undefined && high === undefined) return [];\n\n return [\n {\n ...(sex === undefined ? {} : { appliesTo: [{ coding: [SEX_CODING[sex]] }] }),\n ...(high === undefined ? {} : { high: quantity(high) }),\n ...(low === undefined ? {} : { low: quantity(low) }),\n },\n ];\n };\n\n // A lista anotada tem precedência: quando ela existe, o par simples é o\n // resumo de uma das colunas e repeti-lo publicaria a mesma faixa duas vezes,\n // uma delas sem dizer a quem se aplica.\n //\n // Lista vazia cai no par simples, igual a ausente, e isso é escolha: as duas\n // dizem \"não tenho faixa anotada\", e tratá-las diferente faria um `[]` vindo\n // de um `.filter()` apagar em silêncio a faixa que o chamador também mandou\n // em `referenceMin` e `referenceMax`.\n if (observation.referenceRanges && observation.referenceRanges.length > 0) {\n return observation.referenceRanges.flatMap((r) => toRanges(r.low, r.high, r.appliesTo));\n }\n\n const meaning = observation.referenceKind && referenceRangeMeaning(observation.referenceKind);\n return toRanges(observation.referenceMin, observation.referenceMax).map((range) =>\n meaning ? { ...range, type: { coding: [meaning] } } : range,\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 // O código por método só sai quando é variante declarada do biomarcador. Um\n // `methodLoinc` qualquer viraria afirmação sob `http://loinc.org` que o\n // catálogo não conferiu.\n const methodLoinc =\n observation.methodLoinc && methodVariantOf(observation.biomarkerCode, observation.methodLoinc)\n ? observation.methodLoinc\n : undefined;\n const loincCode = methodLoinc ?? codeToLoinc(observation.biomarkerCode);\n // Use default unit if source unit is empty\n const sourceUnit =\n observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;\n // `system` + `code` só saem quando a unidade resolve em UCUM. Unidade que o\n // pacote não sabe traduzir fica só em `unit`, como texto: afirmar\n // `http://unitsofmeasure.org` sobre `x10^3/mm3` era publicar um código\n // falso, e quem consome o Bundle confiando no system trataria aquilo como\n // UCUM de verdade.\n const ucumUnit = resolveUcum(sourceUnit, observation.biomarkerCode);\n const quantity = (value: number): FHIRQuantity => ({\n ...(ucumUnit ? { code: ucumUnit, system: 'http://unitsofmeasure.org' } : {}),\n ...(sourceUnit ? { unit: sourceUnit } : {}),\n value,\n });\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 = quantity(observation.value as number);\n\n // Reference range only applies to quantitative values\n const referenceRange = buildReferenceRanges(observation, quantity);\n if (referenceRange.length > 0) fhirObs.referenceRange = referenceRange;\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"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkLSC7NHFEcjs = require('./chunk-LSC7NHFE.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ var _chunkOR67NJDZcjs = require('./chunk-OR67NJDZ.cjs');
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
var
|
|
15
|
+
var _chunkBDS6CM7Fcjs = require('./chunk-BDS6CM7F.cjs');
|
|
16
16
|
|
|
17
17
|
// src/importer.ts
|
|
18
18
|
var MAX_OBSERVATIONS = 5e3;
|
|
@@ -34,11 +34,11 @@ function resolveBiomarkerCode(observation) {
|
|
|
34
34
|
} else {
|
|
35
35
|
reason = "No code found in observation coding";
|
|
36
36
|
}
|
|
37
|
-
const fromLoinc = loincCode ?
|
|
37
|
+
const fromLoinc = loincCode ? _chunkBDS6CM7Fcjs.loincToCode.call(void 0, loincCode) : void 0;
|
|
38
38
|
if (fromLoinc) return { internalCode: fromLoinc, loincCode, reason };
|
|
39
|
-
if (declaredCode &&
|
|
40
|
-
const canonical =
|
|
41
|
-
return { internalCode: canonical, loincCode:
|
|
39
|
+
if (declaredCode && _chunkBDS6CM7Fcjs.isValidCode.call(void 0, declaredCode)) {
|
|
40
|
+
const canonical = _chunkBDS6CM7Fcjs.normalizeCode.call(void 0, declaredCode);
|
|
41
|
+
return { internalCode: canonical, loincCode: _chunkBDS6CM7Fcjs.codeToLoinc.call(void 0, canonical), reason };
|
|
42
42
|
}
|
|
43
43
|
return { loincCode, reason };
|
|
44
44
|
}
|
|
@@ -75,7 +75,7 @@ function mapFHIRObservationToInternal(observation, index) {
|
|
|
75
75
|
skipped: { index, loincCode, reason, resourceType: "Observation" }
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
const definition =
|
|
78
|
+
const definition = _chunkBDS6CM7Fcjs.getDefinitionByCode.call(void 0, internalCode);
|
|
79
79
|
let value;
|
|
80
80
|
let unit = "";
|
|
81
81
|
let isQualitative = false;
|
|
@@ -125,7 +125,7 @@ function mapFHIRObservationToInternal(observation, index) {
|
|
|
125
125
|
flag: extractFlag(observation),
|
|
126
126
|
isQualitative,
|
|
127
127
|
loincCode,
|
|
128
|
-
...loincCode &&
|
|
128
|
+
...loincCode && _chunkBDS6CM7Fcjs.methodVariantOf.call(void 0, internalCode, loincCode) && { methodLoinc: loincCode },
|
|
129
129
|
...referenceKind && { referenceKind },
|
|
130
130
|
referenceMax,
|
|
131
131
|
referenceMin,
|
|
@@ -135,7 +135,7 @@ function mapFHIRObservationToInternal(observation, index) {
|
|
|
135
135
|
return { observation: imported };
|
|
136
136
|
}
|
|
137
137
|
function processImportBundle(data) {
|
|
138
|
-
const validationErrors =
|
|
138
|
+
const validationErrors = _chunkLSC7NHFEcjs.validateFHIRImportBundle.call(void 0, data);
|
|
139
139
|
if (validationErrors.length > 0) {
|
|
140
140
|
return {
|
|
141
141
|
errors: validationErrors,
|
|
@@ -171,4 +171,4 @@ function processImportBundle(data) {
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
exports.MAX_OBSERVATIONS = MAX_OBSERVATIONS; exports.MAX_FILE_SIZE = MAX_FILE_SIZE; exports.extractObservationsFromBundle = extractObservationsFromBundle; exports.mapFHIRObservationToInternal = mapFHIRObservationToInternal; exports.processImportBundle = processImportBundle;
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
174
|
+
//# sourceMappingURL=chunk-AJUDODBT.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-F7CC4SV5.cjs","../src/importer.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-F7CC4SV5.cjs","sourcesContent":[null,"/**\n * FHIR Importer\n *\n * Parses FHIR R4 Bundles and extracts Observation resources with known LOINC codes,\n * mapping them to internal biomarker codes for storage as lab results.\n */\n\nimport {\n codeToLoinc,\n getDefinitionByCode,\n isValidCode,\n loincToCode,\n methodVariantOf,\n normalizeCode,\n} from './biomarkers';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type { FHIRBundle, FHIRObservation } from './fhir-types';\nimport { validateFHIRImportBundle } from './validators';\n\nexport interface ImportedObservation {\n biomarkerCode: string;\n biomarkerName: string;\n collectionDate: string;\n flag: 'H' | 'L' | '';\n isQualitative: boolean;\n /** Ausente nos biomarcadores sem LOINC publicado, como composição corporal. */\n loincCode?: string;\n /** O `loincCode` quando ele é uma das variantes por método do biomarcador. */\n methodLoinc?: string;\n /** Lido do `referenceRange.type`, quando o Bundle o traz. */\n referenceKind?: 'decision-threshold' | 'reference-interval';\n referenceMax?: number;\n referenceMin?: number;\n unit: string;\n value: number | string;\n}\n\nexport interface SkippedEntry {\n index: number;\n loincCode?: string;\n reason: string;\n resourceType?: string;\n}\n\nexport interface ImportError {\n details: string;\n field: string;\n}\n\nexport interface FHIRImportResult {\n errors: ImportError[];\n imported: ImportedObservation[];\n skipped: SkippedEntry[];\n totalProcessed: number;\n}\n\n/**\n * Limites de importação.\n *\n * Uma Observation exportada ocupa cerca de 1,25KB em JSON compacto e 2,75KB\n * quando o arquivo vem indentado, medido sobre um histórico real de 998\n * Observations em 61 laudos. Nesse tamanho, 5000 Observations dão 6,0MB\n * compactos ou 13,1MB indentados, e por isso o teto de arquivo é 15MB: cobre\n * as duas formas com folga.\n *\n * Na densidade desse mesmo histórico (16 Observations por laudo), 5000\n * equivalem a cerca de 300 laudos.\n */\nconst MAX_OBSERVATIONS = 5000;\nconst MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB\n\n/**\n * Resolve o código interno do biomarcador a partir do `code.coding`.\n *\n * LOINC primeiro, que é o vocabulário que arquivos de terceiros usam. Quando\n * não resolve, cai para o coding de códigos internos, presente nos arquivos\n * exportados pela própria plataforma.\n *\n * O fallback cobre dois casos: biomarcadores sem LOINC publicado (composição\n * corporal, densidade óssea, escore de cálcio) e arquivos antigos, exportados\n * quando esses biomarcadores saíam com o placeholder `99999-9`, que não\n * resolve para nada.\n */\nfunction resolveBiomarkerCode(observation: FHIRObservation): {\n internalCode?: string;\n loincCode?: string;\n reason: string;\n} {\n const coding = observation.code?.coding ?? [];\n const loincCode = coding.find((c) => c.system === LOINC_SYSTEM)?.code;\n const declaredCode = coding.find((c) => c.system === BIOMARKER_CODE_SYSTEM)?.code;\n\n const seenCodes = [\n ...(loincCode ? [`LOINC ${loincCode}`] : []),\n ...(declaredCode ? [`biomarker code ${declaredCode}`] : []),\n ];\n\n // Três motivos distintos de descarte, que antes se confundiam num só. Sem\n // essa separação, um arquivo em SNOMED relatava \"nenhum código encontrado\",\n // sugerindo `coding` vazio quando na verdade o código existia e estava num\n // system que não tratamos. Para importação de terceiros, é a diferença entre\n // um diagnóstico acionável e um enigma.\n let reason: string;\n if (seenCodes.length > 0) {\n reason = `Unknown code: ${seenCodes.join(', ')}`;\n } else if (coding.length > 0) {\n const systems = [...new Set(coding.map((c) => c.system ?? '(sem system)'))];\n reason = `No code in a supported system (found: ${systems.join(', ')})`;\n } else {\n reason = 'No code found in observation coding';\n }\n\n const fromLoinc = loincCode ? loincToCode(loincCode) : undefined;\n if (fromLoinc) return { internalCode: fromLoinc, loincCode, reason };\n\n if (declaredCode && isValidCode(declaredCode)) {\n // `isValidCode` aceita alias, mas alias não serve como código armazenado:\n // 49 definições têm um, e tanto `codeToLoinc` quanto as faixas de\n // referência são indexadas só pelo canônico. Sem normalizar, `VLDL_Cholesterol`\n // entraria no lugar de `VLDL` e perderia o LOINC 13458-5 que ele tem.\n // Importar um Bundle é fronteira de dados, que é onde `normalizeCode` deve\n // ser aplicado.\n const canonical = normalizeCode(declaredCode);\n\n // `codeToLoinc` devolve undefined para quem não tem LOINC, que é o caso\n // esperado aqui. O campo fica de fora em vez de receber um valor inventado.\n return { internalCode: canonical, loincCode: codeToLoinc(canonical), reason };\n }\n\n return { loincCode, reason };\n}\n\n/**\n * Extract interpretation flag from Observation\n */\nfunction extractFlag(observation: FHIRObservation): 'H' | 'L' | '' {\n const code = observation.interpretation?.[0]?.coding?.[0]?.code;\n if (code === 'H' || code === 'HH') return 'H';\n if (code === 'L' || code === 'LL') return 'L';\n return '';\n}\n\n/**\n * Extract Observation resources from a FHIR Bundle\n */\nexport function extractObservationsFromBundle(bundle: FHIRBundle): {\n observations: FHIRObservation[];\n skipped: SkippedEntry[];\n} {\n const observations: FHIRObservation[] = [];\n const skipped: SkippedEntry[] = [];\n\n for (let i = 0; i < bundle.entry.length; i++) {\n const entry = bundle.entry[i]!;\n if (!entry.resource) {\n skipped.push({ index: i, reason: 'Entry has no resource' });\n continue;\n }\n\n if (entry.resource.resourceType !== 'Observation') {\n // Non-observation resources are silently skipped (Patient, DiagnosticReport, etc.)\n continue;\n }\n\n if (observations.length >= MAX_OBSERVATIONS) {\n skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });\n continue;\n }\n\n observations.push(entry.resource as FHIRObservation);\n }\n\n return { observations, skipped };\n}\n\n/**\n * Map a FHIR Observation to internal format using LOINC→biomarker code lookup\n */\nexport function mapFHIRObservationToInternal(\n observation: FHIRObservation,\n index: number,\n): { observation: ImportedObservation } | { skipped: SkippedEntry } {\n const { internalCode, loincCode, reason } = resolveBiomarkerCode(observation);\n\n if (!internalCode) {\n return {\n skipped: { index, loincCode, reason, resourceType: 'Observation' },\n };\n }\n\n const definition = getDefinitionByCode(internalCode);\n\n // Extract value\n let value: number | string;\n let unit = '';\n let isQualitative = false;\n\n if (observation.valueQuantity?.value !== undefined) {\n value = observation.valueQuantity.value;\n unit = observation.valueQuantity.unit || observation.valueQuantity.code || '';\n } else if (observation.valueString) {\n value = observation.valueString;\n isQualitative = true;\n } else {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no value (valueQuantity or valueString)',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract collection date (effectiveDateTime or effectivePeriod.start)\n const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || '';\n if (!collectionDate) {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no effectiveDateTime or effectivePeriod.start',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract reference ranges\n let referenceMin: number | undefined;\n let referenceMax: number | undefined;\n let referenceKind: ImportedObservation['referenceKind'];\n if (observation.referenceRange?.[0]) {\n referenceMin = observation.referenceRange[0].low?.value;\n referenceMax = observation.referenceRange[0].high?.value;\n const meaning = observation.referenceRange[0].type?.coding?.find(\n (c) => c.system === 'http://terminology.hl7.org/CodeSystem/referencerange-meaning',\n )?.code;\n if (meaning === 'normal') referenceKind = 'reference-interval';\n else if (meaning === 'recommended') referenceKind = 'decision-threshold';\n }\n\n const imported: ImportedObservation = {\n biomarkerCode: internalCode,\n biomarkerName:\n definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,\n collectionDate,\n flag: extractFlag(observation),\n isQualitative,\n loincCode,\n ...(loincCode && methodVariantOf(internalCode, loincCode) && { methodLoinc: loincCode }),\n ...(referenceKind && { referenceKind }),\n referenceMax,\n referenceMin,\n unit: unit || definition?.unit || '',\n value,\n };\n\n return { observation: imported };\n}\n\n/**\n * Process a complete FHIR Bundle for import\n */\nexport function processImportBundle(data: unknown): FHIRImportResult {\n // Structural validation\n const validationErrors = validateFHIRImportBundle(data);\n if (validationErrors.length > 0) {\n return {\n errors: validationErrors,\n imported: [],\n skipped: [],\n totalProcessed: 0,\n };\n }\n\n const bundle = data as FHIRBundle;\n\n // Extract observations\n const { observations, skipped } = extractObservationsFromBundle(bundle);\n\n // Map each observation to internal format\n const imported: ImportedObservation[] = [];\n const allSkipped: SkippedEntry[] = [...skipped];\n\n for (let i = 0; i < observations.length; i++) {\n const result = mapFHIRObservationToInternal(observations[i]!, i);\n\n if ('observation' in result) {\n imported.push(result.observation);\n } else {\n allSkipped.push(result.skipped);\n }\n }\n\n return {\n errors: [],\n imported,\n skipped: allSkipped,\n totalProcessed: observations.length,\n };\n}\n\nexport { MAX_FILE_SIZE, MAX_OBSERVATIONS };\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-AJUDODBT.cjs","../src/importer.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-AJUDODBT.cjs","sourcesContent":[null,"/**\n * FHIR Importer\n *\n * Parses FHIR R4 Bundles and extracts Observation resources with known LOINC codes,\n * mapping them to internal biomarker codes for storage as lab results.\n */\n\nimport {\n codeToLoinc,\n getDefinitionByCode,\n isValidCode,\n loincToCode,\n methodVariantOf,\n normalizeCode,\n} from './biomarkers';\nimport { BIOMARKER_CODE_SYSTEM, LOINC_SYSTEM } from './code-systems';\nimport type { FHIRBundle, FHIRObservation } from './fhir-types';\nimport { validateFHIRImportBundle } from './validators';\n\nexport interface ImportedObservation {\n biomarkerCode: string;\n biomarkerName: string;\n collectionDate: string;\n flag: 'H' | 'L' | '';\n isQualitative: boolean;\n /** Ausente nos biomarcadores sem LOINC publicado, como composição corporal. */\n loincCode?: string;\n /** O `loincCode` quando ele é uma das variantes por método do biomarcador. */\n methodLoinc?: string;\n /** Lido do `referenceRange.type`, quando o Bundle o traz. */\n referenceKind?: 'decision-threshold' | 'reference-interval';\n referenceMax?: number;\n referenceMin?: number;\n unit: string;\n value: number | string;\n}\n\nexport interface SkippedEntry {\n index: number;\n loincCode?: string;\n reason: string;\n resourceType?: string;\n}\n\nexport interface ImportError {\n details: string;\n field: string;\n}\n\nexport interface FHIRImportResult {\n errors: ImportError[];\n imported: ImportedObservation[];\n skipped: SkippedEntry[];\n totalProcessed: number;\n}\n\n/**\n * Limites de importação.\n *\n * Uma Observation exportada ocupa cerca de 1,25KB em JSON compacto e 2,75KB\n * quando o arquivo vem indentado, medido sobre um histórico real de 998\n * Observations em 61 laudos. Nesse tamanho, 5000 Observations dão 6,0MB\n * compactos ou 13,1MB indentados, e por isso o teto de arquivo é 15MB: cobre\n * as duas formas com folga.\n *\n * Na densidade desse mesmo histórico (16 Observations por laudo), 5000\n * equivalem a cerca de 300 laudos.\n */\nconst MAX_OBSERVATIONS = 5000;\nconst MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB\n\n/**\n * Resolve o código interno do biomarcador a partir do `code.coding`.\n *\n * LOINC primeiro, que é o vocabulário que arquivos de terceiros usam. Quando\n * não resolve, cai para o coding de códigos internos, presente nos arquivos\n * exportados pela própria plataforma.\n *\n * O fallback cobre dois casos: biomarcadores sem LOINC publicado (composição\n * corporal, densidade óssea, escore de cálcio) e arquivos antigos, exportados\n * quando esses biomarcadores saíam com o placeholder `99999-9`, que não\n * resolve para nada.\n */\nfunction resolveBiomarkerCode(observation: FHIRObservation): {\n internalCode?: string;\n loincCode?: string;\n reason: string;\n} {\n const coding = observation.code?.coding ?? [];\n const loincCode = coding.find((c) => c.system === LOINC_SYSTEM)?.code;\n const declaredCode = coding.find((c) => c.system === BIOMARKER_CODE_SYSTEM)?.code;\n\n const seenCodes = [\n ...(loincCode ? [`LOINC ${loincCode}`] : []),\n ...(declaredCode ? [`biomarker code ${declaredCode}`] : []),\n ];\n\n // Três motivos distintos de descarte, que antes se confundiam num só. Sem\n // essa separação, um arquivo em SNOMED relatava \"nenhum código encontrado\",\n // sugerindo `coding` vazio quando na verdade o código existia e estava num\n // system que não tratamos. Para importação de terceiros, é a diferença entre\n // um diagnóstico acionável e um enigma.\n let reason: string;\n if (seenCodes.length > 0) {\n reason = `Unknown code: ${seenCodes.join(', ')}`;\n } else if (coding.length > 0) {\n const systems = [...new Set(coding.map((c) => c.system ?? '(sem system)'))];\n reason = `No code in a supported system (found: ${systems.join(', ')})`;\n } else {\n reason = 'No code found in observation coding';\n }\n\n const fromLoinc = loincCode ? loincToCode(loincCode) : undefined;\n if (fromLoinc) return { internalCode: fromLoinc, loincCode, reason };\n\n if (declaredCode && isValidCode(declaredCode)) {\n // `isValidCode` aceita alias, mas alias não serve como código armazenado:\n // 49 definições têm um, e tanto `codeToLoinc` quanto as faixas de\n // referência são indexadas só pelo canônico. Sem normalizar, `VLDL_Cholesterol`\n // entraria no lugar de `VLDL` e perderia o LOINC 13458-5 que ele tem.\n // Importar um Bundle é fronteira de dados, que é onde `normalizeCode` deve\n // ser aplicado.\n const canonical = normalizeCode(declaredCode);\n\n // `codeToLoinc` devolve undefined para quem não tem LOINC, que é o caso\n // esperado aqui. O campo fica de fora em vez de receber um valor inventado.\n return { internalCode: canonical, loincCode: codeToLoinc(canonical), reason };\n }\n\n return { loincCode, reason };\n}\n\n/**\n * Extract interpretation flag from Observation\n */\nfunction extractFlag(observation: FHIRObservation): 'H' | 'L' | '' {\n const code = observation.interpretation?.[0]?.coding?.[0]?.code;\n if (code === 'H' || code === 'HH') return 'H';\n if (code === 'L' || code === 'LL') return 'L';\n return '';\n}\n\n/**\n * Extract Observation resources from a FHIR Bundle\n */\nexport function extractObservationsFromBundle(bundle: FHIRBundle): {\n observations: FHIRObservation[];\n skipped: SkippedEntry[];\n} {\n const observations: FHIRObservation[] = [];\n const skipped: SkippedEntry[] = [];\n\n for (let i = 0; i < bundle.entry.length; i++) {\n const entry = bundle.entry[i]!;\n if (!entry.resource) {\n skipped.push({ index: i, reason: 'Entry has no resource' });\n continue;\n }\n\n if (entry.resource.resourceType !== 'Observation') {\n // Non-observation resources are silently skipped (Patient, DiagnosticReport, etc.)\n continue;\n }\n\n if (observations.length >= MAX_OBSERVATIONS) {\n skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });\n continue;\n }\n\n observations.push(entry.resource as FHIRObservation);\n }\n\n return { observations, skipped };\n}\n\n/**\n * Map a FHIR Observation to internal format using LOINC→biomarker code lookup\n */\nexport function mapFHIRObservationToInternal(\n observation: FHIRObservation,\n index: number,\n): { observation: ImportedObservation } | { skipped: SkippedEntry } {\n const { internalCode, loincCode, reason } = resolveBiomarkerCode(observation);\n\n if (!internalCode) {\n return {\n skipped: { index, loincCode, reason, resourceType: 'Observation' },\n };\n }\n\n const definition = getDefinitionByCode(internalCode);\n\n // Extract value\n let value: number | string;\n let unit = '';\n let isQualitative = false;\n\n if (observation.valueQuantity?.value !== undefined) {\n value = observation.valueQuantity.value;\n unit = observation.valueQuantity.unit || observation.valueQuantity.code || '';\n } else if (observation.valueString) {\n value = observation.valueString;\n isQualitative = true;\n } else {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no value (valueQuantity or valueString)',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract collection date (effectiveDateTime or effectivePeriod.start)\n const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || '';\n if (!collectionDate) {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no effectiveDateTime or effectivePeriod.start',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract reference ranges\n let referenceMin: number | undefined;\n let referenceMax: number | undefined;\n let referenceKind: ImportedObservation['referenceKind'];\n if (observation.referenceRange?.[0]) {\n referenceMin = observation.referenceRange[0].low?.value;\n referenceMax = observation.referenceRange[0].high?.value;\n const meaning = observation.referenceRange[0].type?.coding?.find(\n (c) => c.system === 'http://terminology.hl7.org/CodeSystem/referencerange-meaning',\n )?.code;\n if (meaning === 'normal') referenceKind = 'reference-interval';\n else if (meaning === 'recommended') referenceKind = 'decision-threshold';\n }\n\n const imported: ImportedObservation = {\n biomarkerCode: internalCode,\n biomarkerName:\n definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,\n collectionDate,\n flag: extractFlag(observation),\n isQualitative,\n loincCode,\n ...(loincCode && methodVariantOf(internalCode, loincCode) && { methodLoinc: loincCode }),\n ...(referenceKind && { referenceKind }),\n referenceMax,\n referenceMin,\n unit: unit || definition?.unit || '',\n value,\n };\n\n return { observation: imported };\n}\n\n/**\n * Process a complete FHIR Bundle for import\n */\nexport function processImportBundle(data: unknown): FHIRImportResult {\n // Structural validation\n const validationErrors = validateFHIRImportBundle(data);\n if (validationErrors.length > 0) {\n return {\n errors: validationErrors,\n imported: [],\n skipped: [],\n totalProcessed: 0,\n };\n }\n\n const bundle = data as FHIRBundle;\n\n // Extract observations\n const { observations, skipped } = extractObservationsFromBundle(bundle);\n\n // Map each observation to internal format\n const imported: ImportedObservation[] = [];\n const allSkipped: SkippedEntry[] = [...skipped];\n\n for (let i = 0; i < observations.length; i++) {\n const result = mapFHIRObservationToInternal(observations[i]!, i);\n\n if ('observation' in result) {\n imported.push(result.observation);\n } else {\n allSkipped.push(result.skipped);\n }\n }\n\n return {\n errors: [],\n imported,\n skipped: allSkipped,\n totalProcessed: observations.length,\n };\n}\n\nexport { MAX_FILE_SIZE, MAX_OBSERVATIONS };\n"]}
|
|
@@ -1649,16 +1649,37 @@ var BIOMARKER_DEFINITIONS = [
|
|
|
1649
1649
|
category: "rins",
|
|
1650
1650
|
code: "BUN_Creatinine_Ratio",
|
|
1651
1651
|
// 3097-3 é "Urea nitrogen/Creatinine [Mass Ratio]", a razão BUN/creatinina,
|
|
1652
|
-
// e a faixa de referência (10-20, Tietz) é a dessa convenção.
|
|
1653
|
-
//
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
//
|
|
1657
|
-
//
|
|
1652
|
+
// e a faixa de referência (10-20, Tietz) é a dessa convenção. Até out/2026
|
|
1653
|
+
// esta entrada também levava os nomes "Razão Ureia / Creatinina" e
|
|
1654
|
+
// "Urea/Creatinine Ratio", e o laudo brasileiro, que dosa ureia e não
|
|
1655
|
+
// BUN, caía aqui: como ureia ≈ BUN × 2,14, a razão ureia/creatinina sai
|
|
1656
|
+
// cerca de 2,14 vezes maior, e um valor normal era sinalizado como alto
|
|
1657
|
+
// contra 10-20. Os nomes de ureia foram para `Urea_Creatinine_Ratio`
|
|
1658
|
+
// (56997-0), e aqui ficam só as grafias de BUN, pela mesma regra que
|
|
1659
|
+
// tirou "BUN" dos nomes de `Urea`: um nome exato resolve a um exame só.
|
|
1658
1660
|
loinc: "3097-3",
|
|
1659
1661
|
names: {
|
|
1660
|
-
en: ["BUN/Creatinine Ratio"
|
|
1661
|
-
pt: ["Raz\xE3o
|
|
1662
|
+
en: ["BUN/Creatinine Ratio"],
|
|
1663
|
+
pt: ["Raz\xE3o BUN / Creatinina", "Raz\xE3o Nitrog\xEAnio Ureico / Creatinina"]
|
|
1664
|
+
},
|
|
1665
|
+
unit: "raz\xE3o"
|
|
1666
|
+
},
|
|
1667
|
+
{
|
|
1668
|
+
category: "rins",
|
|
1669
|
+
code: "Urea_Creatinine_Ratio",
|
|
1670
|
+
// 56997-0 é "Urea/Creatinine [Mass Ratio] in Serum or Plasma": a mesma
|
|
1671
|
+
// propriedade (MRto) e o mesmo material de 3097-3, com componente ureia
|
|
1672
|
+
// em vez de nitrogênio ureico. É a razão que o laudo brasileiro imprime,
|
|
1673
|
+
// porque o exame de rotina aqui é a ureia (3091-6), e não o BUN. Sem
|
|
1674
|
+
// faixa de referência: o intervalo que circula (cerca de 21-43) é a
|
|
1675
|
+
// conversão 10-20 × 2,14, sem fonte publicada em `sources.ts` que o
|
|
1676
|
+
// sustente, e uma faixa derivada sem citação é justamente o que o teste
|
|
1677
|
+
// de fontes existe para barrar. Sem faixa, o valor sai sem flag, que é
|
|
1678
|
+
// melhor que o flag errado de antes.
|
|
1679
|
+
loinc: "56997-0",
|
|
1680
|
+
names: {
|
|
1681
|
+
en: ["Urea/Creatinine Ratio"],
|
|
1682
|
+
pt: ["Raz\xE3o Ureia / Creatinina", "Rela\xE7\xE3o Ureia / Creatinina"]
|
|
1662
1683
|
},
|
|
1663
1684
|
unit: "raz\xE3o"
|
|
1664
1685
|
},
|
|
@@ -3598,4 +3619,4 @@ function getBiomarkersForCategories(categories, options) {
|
|
|
3598
3619
|
|
|
3599
3620
|
|
|
3600
3621
|
exports.BIOMARKER_DEFINITIONS = BIOMARKER_DEFINITIONS; exports.loincToCode = loincToCode; exports.methodVariantOf = methodVariantOf; exports.codeToLoinc = codeToLoinc; exports.isValidLoinc = isValidLoinc; exports.isValidCode = isValidCode; exports.normalizeCode = normalizeCode; exports.getSexForCode = getSexForCode; exports.getDefinitionsBySex = getDefinitionsBySex; exports.getDefinitionByCode = getDefinitionByCode; exports.getDefinitionByLoinc = getDefinitionByLoinc; exports.getAllDefinitions = getAllDefinitions; exports.getVisibleDefinitions = getVisibleDefinitions; exports.getAllCodes = getAllCodes; exports.getAllLoincCodes = getAllLoincCodes; exports.generateLLMReference = generateLLMReference; exports.toBiomarkerTests = toBiomarkerTests; exports.getAllSearchPatterns = getAllSearchPatterns; exports.generateFilteredLLMReference = generateFilteredLLMReference; exports.DEXA_INDICATOR_CODES = DEXA_INDICATOR_CODES; exports.DEXA_CATEGORIES = DEXA_CATEGORIES; exports.generateDexaFullReference = generateDexaFullReference; exports.isDexaDocument = isDexaDocument; exports.CAC_INDICATOR_CODES = CAC_INDICATOR_CODES; exports.generateCacFullReference = generateCacFullReference; exports.isCacDocument = isCacDocument; exports.findCodeByName = findCodeByName; exports.validateLoincNameMatch = validateLoincNameMatch; exports.isBiomarkerVisible = isBiomarkerVisible; exports.filterVisibleBiomarkers = filterVisibleBiomarkers; exports.getBiomarkersByCategory = getBiomarkersByCategory; exports.getBiomarkersForCategories = getBiomarkersForCategories;
|
|
3601
|
-
//# sourceMappingURL=chunk-
|
|
3622
|
+
//# sourceMappingURL=chunk-BDS6CM7F.cjs.map
|