@precisa-saude/fhir 0.22.1 → 0.22.3

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.
@@ -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
- fullUrl: `urn:uuid:observation-${obs.reportId}-${obs.biomarkerCode}`,
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
- const observationIds = observations.map(
235
- (obs) => `observation-${obs.reportId}-${obs.biomarkerCode}`
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
- fullUrl: `urn:uuid:${patientId}`,
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,6 @@ function labResultToFHIRBundle(report, observations, userProfile) {
258
254
 
259
255
 
260
256
 
261
- exports.labObservationToFHIR = labObservationToFHIR; exports.labReportToFHIR = labReportToFHIR; exports.userProfileToFHIR = userProfileToFHIR; exports.labResultToFHIRBundle = labResultToFHIRBundle;
262
- //# sourceMappingURL=chunk-EAX6MHYX.cjs.map
257
+
258
+ exports.entryFullUrl = entryFullUrl; exports.labObservationToFHIR = labObservationToFHIR; exports.labReportToFHIR = labReportToFHIR; exports.userProfileToFHIR = userProfileToFHIR; exports.labResultToFHIRBundle = labResultToFHIRBundle;
259
+ //# sourceMappingURL=chunk-MZ36NSTX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-MZ36NSTX.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-MZ36NSTX.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 */\nimport type { FHIRBundleEntry } from './fhir-types';\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 */\nexport const entryFullUrl = (resource: Addressable<FHIRBundleEntry['resource']>): 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"]}
@@ -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
- fullUrl: `urn:uuid:observation-${obs.reportId}-${obs.biomarkerCode}`,
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
- const observationIds = observations.map(
235
- (obs) => `observation-${obs.reportId}-${obs.biomarkerCode}`
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
- fullUrl: `urn:uuid:${patientId}`,
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,10 @@ function labResultToFHIRBundle(report, observations, userProfile) {
254
250
  }
255
251
 
256
252
  export {
253
+ entryFullUrl,
257
254
  labObservationToFHIR,
258
255
  labReportToFHIR,
259
256
  userProfileToFHIR,
260
257
  labResultToFHIRBundle
261
258
  };
262
- //# sourceMappingURL=chunk-K3TK3OHS.js.map
259
+ //# sourceMappingURL=chunk-RZPFCPLH.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 */\nimport type { FHIRBundleEntry } from './fhir-types';\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 */\nexport const entryFullUrl = (resource: Addressable<FHIRBundleEntry['resource']>): 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":[]}
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
- fullUrl: `urn:uuid:observation-${obs.reportId}-${obs.biomarkerCode}`,
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
- const observationIds = observations.map(
4249
- (obs) => `observation-${obs.reportId}-${obs.biomarkerCode}`
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
- fullUrl: `urn:uuid:${patientId}`,
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.22.1"}
6677
+ process.stdout.write(`${"0.22.3"}
6682
6678
  `);
6683
6679
  return;
6684
6680
  }
@@ -1,5 +1,34 @@
1
1
  import { a as FHIRObservation, k as FHIRDiagnosticReport, b as FHIRBundle, m as FHIRPatient } from './fhir-types-jTJayiwn.js';
2
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
+ /**
20
+ * Recurso que já tem id, e por isso pode ser endereçado numa entrada.
21
+ *
22
+ * No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade
23
+ * própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem
24
+ * `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre
25
+ * atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar
26
+ * até a montagem da URL.
27
+ */
28
+ type Addressable<T> = T & {
29
+ id: string;
30
+ };
31
+
3
32
  /**
4
33
  * Generic Lab Result Types
5
34
  *
@@ -80,20 +109,20 @@ interface UserProfileData {
80
109
  /**
81
110
  * Convert generic lab observation to FHIR Observation
82
111
  */
83
- declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): FHIRObservation;
112
+ declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): Addressable<FHIRObservation>;
84
113
  /**
85
114
  * Convert generic lab report to FHIR DiagnosticReport
86
115
  */
87
- declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): FHIRDiagnosticReport;
116
+ declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): Addressable<FHIRDiagnosticReport>;
88
117
  /**
89
118
  * Convert user profile to FHIR Patient
90
119
  * NOTE: CPF is intentionally excluded for privacy (LGPD compliance)
91
120
  */
92
- declare function userProfileToFHIR(profile: UserProfileData): FHIRPatient;
121
+ declare function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient>;
93
122
  /**
94
123
  * Convert complete lab result to FHIR Bundle
95
124
  * This is the main function for exporting lab results to FHIR R4 format
96
125
  */
97
126
  declare function labResultToFHIRBundle(report: LabReportData, observations: LabObservationData[], userProfile: UserProfileData): FHIRBundle;
98
127
 
99
- export { 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, labObservationToFHIR as l, userProfileToFHIR as u };
128
+ export { type Addressable as A, 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, labObservationToFHIR as l, userProfileToFHIR as u };
@@ -1,5 +1,34 @@
1
1
  import { a as FHIRObservation, k as FHIRDiagnosticReport, b as FHIRBundle, m as FHIRPatient } from './fhir-types-jTJayiwn.cjs';
2
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
+ /**
20
+ * Recurso que já tem id, e por isso pode ser endereçado numa entrada.
21
+ *
22
+ * No FHIR o `id` é opcional, porque um recurso pode viajar sem identidade
23
+ * própria. Numa entrada de Bundle ele não pode: sem id não há `fullUrl`, e sem
24
+ * `fullUrl` nenhuma referência chega ao recurso. Os conversores daqui sempre
25
+ * atribuem um, e o tipo passa a dizer isso em vez de deixar `undefined` chegar
26
+ * até a montagem da URL.
27
+ */
28
+ type Addressable<T> = T & {
29
+ id: string;
30
+ };
31
+
3
32
  /**
4
33
  * Generic Lab Result Types
5
34
  *
@@ -80,20 +109,20 @@ interface UserProfileData {
80
109
  /**
81
110
  * Convert generic lab observation to FHIR Observation
82
111
  */
83
- declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): FHIRObservation;
112
+ declare function labObservationToFHIR(observation: LabObservationData, patientId: string, laboratoryName?: string): Addressable<FHIRObservation>;
84
113
  /**
85
114
  * Convert generic lab report to FHIR DiagnosticReport
86
115
  */
87
- declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): FHIRDiagnosticReport;
116
+ declare function labReportToFHIR(report: LabReportData, patientId: string, observationIds: string[]): Addressable<FHIRDiagnosticReport>;
88
117
  /**
89
118
  * Convert user profile to FHIR Patient
90
119
  * NOTE: CPF is intentionally excluded for privacy (LGPD compliance)
91
120
  */
92
- declare function userProfileToFHIR(profile: UserProfileData): FHIRPatient;
121
+ declare function userProfileToFHIR(profile: UserProfileData): Addressable<FHIRPatient>;
93
122
  /**
94
123
  * Convert complete lab result to FHIR Bundle
95
124
  * This is the main function for exporting lab results to FHIR R4 format
96
125
  */
97
126
  declare function labResultToFHIRBundle(report: LabReportData, observations: LabObservationData[], userProfile: UserProfileData): FHIRBundle;
98
127
 
99
- export { 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, labObservationToFHIR as l, userProfileToFHIR as u };
128
+ export { type Addressable as A, 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, labObservationToFHIR as l, userProfileToFHIR as u };
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkEAX6MHYXcjs = require('./chunk-EAX6MHYX.cjs');
6
+ var _chunkMZ36NSTXcjs = require('./chunk-MZ36NSTX.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 = _chunkEAX6MHYXcjs.labObservationToFHIR; exports.labReportToFHIR = _chunkEAX6MHYXcjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkEAX6MHYXcjs.labResultToFHIRBundle; exports.userProfileToFHIR = _chunkEAX6MHYXcjs.userProfileToFHIR;
15
+ exports.labObservationToFHIR = _chunkMZ36NSTXcjs.labObservationToFHIR; exports.labReportToFHIR = _chunkMZ36NSTXcjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkMZ36NSTXcjs.labResultToFHIRBundle; exports.userProfileToFHIR = _chunkMZ36NSTXcjs.userProfileToFHIR;
16
16
  //# sourceMappingURL=converter.cjs.map
@@ -1,2 +1,2 @@
1
+ export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-Bp3ZhyUQ.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';
@@ -1,2 +1,2 @@
1
+ export { l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-BciZxRkK.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';
package/dist/converter.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  labReportToFHIR,
4
4
  labResultToFHIRBundle,
5
5
  userProfileToFHIR
6
- } from "./chunk-K3TK3OHS.js";
6
+ } from "./chunk-RZPFCPLH.js";
7
7
  import "./chunk-A6HR4XDK.js";
8
8
  import "./chunk-GFT6V5GM.js";
9
9
  import "./chunk-R4MUCMO3.js";
package/dist/index.cjs CHANGED
@@ -3,7 +3,8 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkEAX6MHYXcjs = require('./chunk-EAX6MHYX.cjs');
6
+
7
+ var _chunkMZ36NSTXcjs = require('./chunk-MZ36NSTX.cjs');
7
8
 
8
9
 
9
10
 
@@ -232,23 +233,14 @@ function interventionToFHIRObservation(intervention, patientId) {
232
233
  }
233
234
  function interventionsToFHIRBundle(interventions, userProfile) {
234
235
  const patientId = userProfile.userId;
235
- const fhirPatient = _chunkEAX6MHYXcjs.userProfileToFHIR.call(void 0, userProfile);
236
+ const fhirPatient = _chunkMZ36NSTXcjs.userProfileToFHIR.call(void 0, userProfile);
236
237
  const entries = interventions.map((intervention) => {
237
238
  const isMedication = intervention.type === "medication" || intervention.type === "supplement";
238
239
  const resource = isMedication ? interventionToFHIRMedicationStatement(intervention, patientId) : interventionToFHIRObservation(intervention, patientId);
239
- return {
240
- fullUrl: `urn:uuid:intervention-${intervention.interventionId}`,
241
- resource
242
- };
240
+ return { fullUrl: _chunkMZ36NSTXcjs.entryFullUrl.call(void 0, resource), resource };
243
241
  });
244
242
  return {
245
- entry: [
246
- {
247
- fullUrl: `urn:uuid:${patientId}`,
248
- resource: fhirPatient
249
- },
250
- ...entries
251
- ],
243
+ entry: [{ fullUrl: _chunkMZ36NSTXcjs.entryFullUrl.call(void 0, fhirPatient), resource: fhirPatient }, ...entries],
252
244
  resourceType: "Bundle",
253
245
  type: "collection"
254
246
  };
@@ -697,5 +689,5 @@ function cnsToFHIRIdentifier(cns) {
697
689
 
698
690
 
699
691
 
700
- exports.AGE_BRACKETS = AGE_BRACKETS; exports.BIOMARKER_CODE_SYSTEM = _chunkOR67NJDZcjs.BIOMARKER_CODE_SYSTEM; exports.BIOMARKER_DEFAULT_UNIT = _chunkMJ254F5Kcjs.BIOMARKER_DEFAULT_UNIT; exports.BIOMARKER_DEFINITIONS = _chunkAM2RCUUJcjs.BIOMARKER_DEFINITIONS; exports.BIOMARKER_UNITS = _chunkMJ254F5Kcjs.BIOMARKER_UNITS; exports.BODY_FAT_ZONES = BODY_FAT_ZONES; exports.CAC_INDICATOR_CODES = _chunkAM2RCUUJcjs.CAC_INDICATOR_CODES; exports.CATEGORY_GROUPS = CATEGORY_GROUPS; exports.CATEGORY_SCREENING_INTERVALS = CATEGORY_SCREENING_INTERVALS; exports.DEXA_CATEGORIES = _chunkAM2RCUUJcjs.DEXA_CATEGORIES; exports.DEXA_INDICATOR_CODES = _chunkAM2RCUUJcjs.DEXA_INDICATOR_CODES; exports.LOINC_SYSTEM = _chunkOR67NJDZcjs.LOINC_SYSTEM; exports.MAX_FILE_SIZE = _chunkHV77ZKP6cjs.MAX_FILE_SIZE; exports.MAX_OBSERVATIONS = _chunkHV77ZKP6cjs.MAX_OBSERVATIONS; exports.T_SCORE_ZONES = T_SCORE_ZONES; exports.UNIT_TO_UCUM = _chunkMJ254F5Kcjs.UNIT_TO_UCUM; exports.ZONE_DEFS = ZONE_DEFS; exports.applyFallbackReferenceRanges = _chunkERALICUKcjs.applyFallbackReferenceRanges; exports.biomarkerRangeDefinitions = _chunkERALICUKcjs.biomarkerRangeDefinitions; exports.calculateNextScreeningDate = calculateNextScreeningDate; exports.cnsToFHIRIdentifier = cnsToFHIRIdentifier; exports.codeToLoinc = _chunkAM2RCUUJcjs.codeToLoinc; exports.convertUnit = _chunkMJ254F5Kcjs.convertUnit; exports.cpfToFHIRIdentifier = cpfToFHIRIdentifier; exports.defaultReferenceRanges = _chunkERALICUKcjs.defaultReferenceRanges; exports.extractObservationsFromBundle = _chunkHV77ZKP6cjs.extractObservationsFromBundle; exports.filterVisibleBiomarkers = _chunkAM2RCUUJcjs.filterVisibleBiomarkers; exports.findCodeByName = _chunkAM2RCUUJcjs.findCodeByName; exports.formatCNS = formatCNS; exports.formatCPF = formatCPF; exports.generateCacFullReference = _chunkAM2RCUUJcjs.generateCacFullReference; exports.generateDexaFullReference = _chunkAM2RCUUJcjs.generateDexaFullReference; exports.generateFilteredLLMReference = _chunkAM2RCUUJcjs.generateFilteredLLMReference; exports.generateLLMReference = _chunkAM2RCUUJcjs.generateLLMReference; exports.getAllCodes = _chunkAM2RCUUJcjs.getAllCodes; exports.getAllDefinitions = _chunkAM2RCUUJcjs.getAllDefinitions; exports.getAllLoincCodes = _chunkAM2RCUUJcjs.getAllLoincCodes; exports.getAllSearchPatterns = _chunkAM2RCUUJcjs.getAllSearchPatterns; exports.getBiomarkersByCategory = _chunkAM2RCUUJcjs.getBiomarkersByCategory; exports.getBiomarkersForCategories = _chunkAM2RCUUJcjs.getBiomarkersForCategories; exports.getCanonicalUnit = _chunkMJ254F5Kcjs.getCanonicalUnit; exports.getCategoriesByInterval = getCategoriesByInterval; exports.getCategoryGroup = getCategoryGroup; exports.getDaysUntilScreening = getDaysUntilScreening; exports.getDefaultUnit = _chunkMJ254F5Kcjs.getDefaultUnit; exports.getDefinitionByCode = _chunkAM2RCUUJcjs.getDefinitionByCode; exports.getDefinitionByLoinc = _chunkAM2RCUUJcjs.getDefinitionByLoinc; exports.getDefinitionsBySex = _chunkAM2RCUUJcjs.getDefinitionsBySex; exports.getDueCategories = getDueCategories; exports.getFallbackReferenceRange = _chunkERALICUKcjs.getFallbackReferenceRange; exports.getRangeDirection = _chunkERALICUKcjs.getRangeDirection; exports.getReferenceRange = _chunkERALICUKcjs.getReferenceRange; exports.getSIUnit = _chunkMJ254F5Kcjs.getSIUnit; exports.getScreeningInterval = getScreeningInterval; exports.getSexForCode = _chunkAM2RCUUJcjs.getSexForCode; exports.getVisibleDefinitions = _chunkAM2RCUUJcjs.getVisibleDefinitions; exports.interventionToFHIRMedicationStatement = interventionToFHIRMedicationStatement; exports.interventionToFHIRObservation = interventionToFHIRObservation; exports.interventionsToFHIRBundle = interventionsToFHIRBundle; exports.isBiomarkerVisible = _chunkAM2RCUUJcjs.isBiomarkerVisible; exports.isCacDocument = _chunkAM2RCUUJcjs.isCacDocument; exports.isDexaDocument = _chunkAM2RCUUJcjs.isDexaDocument; exports.isScreeningDue = isScreeningDue; exports.isValidCode = _chunkAM2RCUUJcjs.isValidCode; exports.isValidLoinc = _chunkAM2RCUUJcjs.isValidLoinc; exports.labObservationToFHIR = _chunkEAX6MHYXcjs.labObservationToFHIR; exports.labReportToFHIR = _chunkEAX6MHYXcjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkEAX6MHYXcjs.labResultToFHIRBundle; exports.listMappedSubcategories = listMappedSubcategories; exports.loincToCode = _chunkAM2RCUUJcjs.loincToCode; exports.mapFHIRObservationToInternal = _chunkHV77ZKP6cjs.mapFHIRObservationToInternal; exports.normalizeCode = _chunkAM2RCUUJcjs.normalizeCode; exports.plural = plural; exports.pluralCount = pluralCount; exports.pluralPhrase = pluralPhrase; exports.pluralPhraseCount = pluralPhraseCount; exports.processImportBundle = _chunkHV77ZKP6cjs.processImportBundle; exports.toBiomarkerTests = _chunkAM2RCUUJcjs.toBiomarkerTests; exports.unitToUCUM = _chunkMJ254F5Kcjs.unitToUCUM; exports.userProfileToFHIR = _chunkEAX6MHYXcjs.userProfileToFHIR; exports.validateCNS = validateCNS; exports.validateCPF = validateCPF; exports.validateFHIRDiagnosticReport = _chunk3ILBFLVQcjs.validateFHIRDiagnosticReport; exports.validateFHIRImportBundle = _chunk3ILBFLVQcjs.validateFHIRImportBundle; exports.validateFHIRObservation = _chunk3ILBFLVQcjs.validateFHIRObservation; exports.validateLoincNameMatch = _chunkAM2RCUUJcjs.validateLoincNameMatch;
692
+ exports.AGE_BRACKETS = AGE_BRACKETS; exports.BIOMARKER_CODE_SYSTEM = _chunkOR67NJDZcjs.BIOMARKER_CODE_SYSTEM; exports.BIOMARKER_DEFAULT_UNIT = _chunkMJ254F5Kcjs.BIOMARKER_DEFAULT_UNIT; exports.BIOMARKER_DEFINITIONS = _chunkAM2RCUUJcjs.BIOMARKER_DEFINITIONS; exports.BIOMARKER_UNITS = _chunkMJ254F5Kcjs.BIOMARKER_UNITS; exports.BODY_FAT_ZONES = BODY_FAT_ZONES; exports.CAC_INDICATOR_CODES = _chunkAM2RCUUJcjs.CAC_INDICATOR_CODES; exports.CATEGORY_GROUPS = CATEGORY_GROUPS; exports.CATEGORY_SCREENING_INTERVALS = CATEGORY_SCREENING_INTERVALS; exports.DEXA_CATEGORIES = _chunkAM2RCUUJcjs.DEXA_CATEGORIES; exports.DEXA_INDICATOR_CODES = _chunkAM2RCUUJcjs.DEXA_INDICATOR_CODES; exports.LOINC_SYSTEM = _chunkOR67NJDZcjs.LOINC_SYSTEM; exports.MAX_FILE_SIZE = _chunkHV77ZKP6cjs.MAX_FILE_SIZE; exports.MAX_OBSERVATIONS = _chunkHV77ZKP6cjs.MAX_OBSERVATIONS; exports.T_SCORE_ZONES = T_SCORE_ZONES; exports.UNIT_TO_UCUM = _chunkMJ254F5Kcjs.UNIT_TO_UCUM; exports.ZONE_DEFS = ZONE_DEFS; exports.applyFallbackReferenceRanges = _chunkERALICUKcjs.applyFallbackReferenceRanges; exports.biomarkerRangeDefinitions = _chunkERALICUKcjs.biomarkerRangeDefinitions; exports.calculateNextScreeningDate = calculateNextScreeningDate; exports.cnsToFHIRIdentifier = cnsToFHIRIdentifier; exports.codeToLoinc = _chunkAM2RCUUJcjs.codeToLoinc; exports.convertUnit = _chunkMJ254F5Kcjs.convertUnit; exports.cpfToFHIRIdentifier = cpfToFHIRIdentifier; exports.defaultReferenceRanges = _chunkERALICUKcjs.defaultReferenceRanges; exports.extractObservationsFromBundle = _chunkHV77ZKP6cjs.extractObservationsFromBundle; exports.filterVisibleBiomarkers = _chunkAM2RCUUJcjs.filterVisibleBiomarkers; exports.findCodeByName = _chunkAM2RCUUJcjs.findCodeByName; exports.formatCNS = formatCNS; exports.formatCPF = formatCPF; exports.generateCacFullReference = _chunkAM2RCUUJcjs.generateCacFullReference; exports.generateDexaFullReference = _chunkAM2RCUUJcjs.generateDexaFullReference; exports.generateFilteredLLMReference = _chunkAM2RCUUJcjs.generateFilteredLLMReference; exports.generateLLMReference = _chunkAM2RCUUJcjs.generateLLMReference; exports.getAllCodes = _chunkAM2RCUUJcjs.getAllCodes; exports.getAllDefinitions = _chunkAM2RCUUJcjs.getAllDefinitions; exports.getAllLoincCodes = _chunkAM2RCUUJcjs.getAllLoincCodes; exports.getAllSearchPatterns = _chunkAM2RCUUJcjs.getAllSearchPatterns; exports.getBiomarkersByCategory = _chunkAM2RCUUJcjs.getBiomarkersByCategory; exports.getBiomarkersForCategories = _chunkAM2RCUUJcjs.getBiomarkersForCategories; exports.getCanonicalUnit = _chunkMJ254F5Kcjs.getCanonicalUnit; exports.getCategoriesByInterval = getCategoriesByInterval; exports.getCategoryGroup = getCategoryGroup; exports.getDaysUntilScreening = getDaysUntilScreening; exports.getDefaultUnit = _chunkMJ254F5Kcjs.getDefaultUnit; exports.getDefinitionByCode = _chunkAM2RCUUJcjs.getDefinitionByCode; exports.getDefinitionByLoinc = _chunkAM2RCUUJcjs.getDefinitionByLoinc; exports.getDefinitionsBySex = _chunkAM2RCUUJcjs.getDefinitionsBySex; exports.getDueCategories = getDueCategories; exports.getFallbackReferenceRange = _chunkERALICUKcjs.getFallbackReferenceRange; exports.getRangeDirection = _chunkERALICUKcjs.getRangeDirection; exports.getReferenceRange = _chunkERALICUKcjs.getReferenceRange; exports.getSIUnit = _chunkMJ254F5Kcjs.getSIUnit; exports.getScreeningInterval = getScreeningInterval; exports.getSexForCode = _chunkAM2RCUUJcjs.getSexForCode; exports.getVisibleDefinitions = _chunkAM2RCUUJcjs.getVisibleDefinitions; exports.interventionToFHIRMedicationStatement = interventionToFHIRMedicationStatement; exports.interventionToFHIRObservation = interventionToFHIRObservation; exports.interventionsToFHIRBundle = interventionsToFHIRBundle; exports.isBiomarkerVisible = _chunkAM2RCUUJcjs.isBiomarkerVisible; exports.isCacDocument = _chunkAM2RCUUJcjs.isCacDocument; exports.isDexaDocument = _chunkAM2RCUUJcjs.isDexaDocument; exports.isScreeningDue = isScreeningDue; exports.isValidCode = _chunkAM2RCUUJcjs.isValidCode; exports.isValidLoinc = _chunkAM2RCUUJcjs.isValidLoinc; exports.labObservationToFHIR = _chunkMZ36NSTXcjs.labObservationToFHIR; exports.labReportToFHIR = _chunkMZ36NSTXcjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkMZ36NSTXcjs.labResultToFHIRBundle; exports.listMappedSubcategories = listMappedSubcategories; exports.loincToCode = _chunkAM2RCUUJcjs.loincToCode; exports.mapFHIRObservationToInternal = _chunkHV77ZKP6cjs.mapFHIRObservationToInternal; exports.normalizeCode = _chunkAM2RCUUJcjs.normalizeCode; exports.plural = plural; exports.pluralCount = pluralCount; exports.pluralPhrase = pluralPhrase; exports.pluralPhraseCount = pluralPhraseCount; exports.processImportBundle = _chunkHV77ZKP6cjs.processImportBundle; exports.toBiomarkerTests = _chunkAM2RCUUJcjs.toBiomarkerTests; exports.unitToUCUM = _chunkMJ254F5Kcjs.unitToUCUM; exports.userProfileToFHIR = _chunkMZ36NSTXcjs.userProfileToFHIR; exports.validateCNS = validateCNS; exports.validateCPF = validateCPF; exports.validateFHIRDiagnosticReport = _chunk3ILBFLVQcjs.validateFHIRDiagnosticReport; exports.validateFHIRImportBundle = _chunk3ILBFLVQcjs.validateFHIRImportBundle; exports.validateFHIRObservation = _chunk3ILBFLVQcjs.validateFHIRObservation; exports.validateLoincNameMatch = _chunkAM2RCUUJcjs.validateLoincNameMatch;
701
693
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/index.cjs","../src/category-groups.ts","../src/intervention-converter.ts","../src/dexa-zone-data.ts","../src/screening-intervals.ts","../src/i18n.ts","../src/identifiers.ts"],"names":[],"mappings":"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CH,EAAA;AACO,EAAA;AACT;AAE6C;AACd,EAAA;AACE,EAAA;AACjC;AAW2C;AACS,EAAA;AACE,EAAA;AACL,EAAA;AACjD;AHmNoD;AACA;AIzTqB;AAAA;AAEvE,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AAAA;AAGA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AAAA;AAGA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACF;AAKiG;AAC7C,EAAA;AACpD;AAOkC;AACkB,EAAA;AACpD;AAK+D;AACf,EAAA;AACxB,EAAA;AAEgB,EAAA;AACW,EAAA;AAC1C,EAAA;AACT;AAQE;AAE4C,EAAA;AACtB,EAAA;AACE,EAAA;AAC1B;AAOE;AAE4C,EAAA;AACM,IAAA;AAC1B,IAAA;AACmB,IAAA;AAC1C,EAAA;AACH;AAOE;AAG4C,EAAA;AACtB,EAAA;AAEgB,EAAA;AACW,EAAA;AACnD;AJkRoD;AACA;AKpeJ;AAML;AAAA;AAEhC,EAAA;AACI,EAAA;AAAA;AAED,EAAA;AAAA;AAED,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACF,EAAA;AACG,EAAA;AACD,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AAEA,EAAA;AAAA;AAEF,EAAA;AACD,EAAA;AACG,EAAA;AACJ,EAAA;AACC,EAAA;AACC,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AAEC,EAAA;AACF,EAAA;AAEC,EAAA;AACD,EAAA;AACX;AAMoD;AACR,EAAA;AAC5C;AAW+D;AACxB,EAAA;AACY,EAAA;AACnD;AASoE;AAC5B,EAAA;AACxC;AAWwF;AACjD,EAAA;AACjB,EAAA;AACS,IAAA;AAC7B,EAAA;AAC+C,EAAA;AACjD;AAS6F;AAC9C,EAAA;AAC/C;ALqboD;AACA;AMthBjC;AACA;AAKwB;AACX,EAAA;AAChC;AAQkD;AACnB,EAAA;AAEI,EAAA;AAGO,EAAA;AAG9B,EAAA;AACkB,EAAA;AACO,IAAA;AACnC,EAAA;AAC6B,EAAA;AACK,EAAA;AACU,EAAA;AAGtC,EAAA;AACuB,EAAA;AACM,IAAA;AACnC,EAAA;AACyB,EAAA;AACS,EAAA;AACW,EAAA;AAEtC,EAAA;AACT;AAWkD;AACnB,EAAA;AAEI,EAAA;AAEN,EAAA;AAGuB,EAAA;AAGxC,EAAA;AACmB,EAAA;AACM,IAAA;AACnC,EAAA;AACoB,EAAA;AACtB;AAQ+C;AAChB,EAAA;AACI,EAAA;AACiB,EAAA;AACpD;AAQ+C;AAChB,EAAA;AACI,EAAA;AACiB,EAAA;AACpD;AASiE;AACxC,EAAA;AACS,IAAA;AAChC,EAAA;AACO,EAAA;AACG,IAAA;AACH,IAAA;AACgB,IAAA;AACvB,EAAA;AACF;AASiE;AACxC,EAAA;AACS,IAAA;AAChC,EAAA;AACO,EAAA;AACG,IAAA;AACH,IAAA;AACgB,IAAA;AACvB,EAAA;AACF;ANudoD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/index.cjs","sourcesContent":[null,"/**\n * Agrupamento de categorias clínicas em 10 grupos de alto nível.\n *\n * `BiomarkerDefinition.category` armazena 20 sub-categorias (granularidade\n * fina, ex: `tireoide`, `pancreas`). Este módulo agrupa essas\n * sub-categorias em 10 buckets clínicos amplos para apresentação no\n * site, na API pública e em material de divulgação.\n *\n * As sub-categorias permanecem como fonte da verdade nos dados; este\n * agrupamento é uma camada derivada.\n */\n\nexport type CategoryGroup =\n | 'cardiovascular'\n | 'metabolico-endocrino'\n | 'renal-eletrolitico'\n | 'hepatico-biliar'\n | 'hematologico'\n | 'imunologico'\n | 'oncologico'\n | 'nutricional-ambiental'\n | 'saude-reprodutiva'\n | 'composicao-envelhecimento';\n\nexport interface CategoryGroupInfo {\n /** Rótulo em inglês */\n en: string;\n /** Rótulo em português */\n pt: string;\n /** Slug (kebab-case, sem acento) */\n slug: CategoryGroup;\n /** Sub-categorias da fonte agrupadas neste bucket */\n subcategories: readonly string[];\n}\n\nexport const CATEGORY_GROUPS: Record<CategoryGroup, CategoryGroupInfo> = {\n cardiovascular: {\n en: 'Cardiovascular',\n pt: 'Cardiovascular',\n slug: 'cardiovascular',\n subcategories: ['coracao'],\n },\n 'composicao-envelhecimento': {\n en: 'Body Composition & Aging',\n pt: 'Composição Corporal e Envelhecimento',\n slug: 'composicao-envelhecimento',\n subcategories: ['composicao-corporal', 'densidade-ossea', 'estresse-envelhecimento'],\n },\n hematologico: {\n en: 'Hematology',\n pt: 'Hematológico',\n slug: 'hematologico',\n subcategories: ['sangue'],\n },\n 'hepatico-biliar': {\n en: 'Hepatic & Biliary',\n pt: 'Hepático e Biliar',\n slug: 'hepatico-biliar',\n subcategories: ['figado'],\n },\n imunologico: {\n en: 'Immunology',\n pt: 'Imunológico',\n slug: 'imunologico',\n subcategories: ['autoimunidade', 'regulacao-imunologica'],\n },\n 'metabolico-endocrino': {\n en: 'Metabolic & Endocrine',\n pt: 'Metabólico e Endócrino',\n slug: 'metabolico-endocrino',\n subcategories: ['metabolico', 'pancreas', 'hormonios', 'tireoide'],\n },\n 'nutricional-ambiental': {\n en: 'Nutrition & Environmental Exposure',\n pt: 'Nutricional e Exposição Ambiental',\n slug: 'nutricional-ambiental',\n subcategories: ['nutrientes', 'toxinas-ambientais'],\n },\n oncologico: {\n en: 'Oncology',\n pt: 'Oncológico',\n slug: 'oncologico',\n subcategories: ['marcadores-tumorais'],\n },\n 'renal-eletrolitico': {\n en: 'Renal & Electrolytes',\n pt: 'Renal e Eletrolítico',\n slug: 'renal-eletrolitico',\n subcategories: ['rins', 'urina', 'eletrolitos'],\n },\n 'saude-reprodutiva': {\n en: 'Reproductive Health',\n pt: 'Saúde Reprodutiva',\n slug: 'saude-reprodutiva',\n subcategories: ['saude-feminina', 'saude-masculina'],\n },\n};\n\nconst SUBCATEGORY_TO_GROUP = new Map<string, CategoryGroup>();\nfor (const group of Object.values(CATEGORY_GROUPS)) {\n for (const sub of group.subcategories) {\n SUBCATEGORY_TO_GROUP.set(sub, group.slug);\n }\n}\n\n/**\n * Resolve a sub-categoria (granular) para o grupo de alto nível (10 buckets).\n */\nexport function getCategoryGroup(subcategory: string): CategoryGroup | undefined {\n return SUBCATEGORY_TO_GROUP.get(subcategory);\n}\n\n/**\n * Lista todas as sub-categorias mapeadas em algum grupo.\n */\nexport function listMappedSubcategories(): readonly string[] {\n return [...SUBCATEGORY_TO_GROUP.keys()];\n}\n","/**\n * FHIR Intervention Converter\n *\n * Converts interventions (medication, supplement, diet, exercise, sleep)\n * to FHIR R4 MedicationStatement and Observation resources.\n */\n\nimport { userProfileToFHIR } from './converter';\nimport type { FHIRBundle, FHIRMedicationStatement, FHIRObservation } from './fhir-types';\nimport type { InterventionData, UserProfileData } from './types';\n\n/**\n * Determine MedicationStatement/Observation status based on end date\n */\nfunction interventionStatus(endDate?: string): 'active' | 'completed' {\n if (!endDate) return 'active';\n return new Date(endDate) < new Date() ? 'completed' : 'active';\n}\n\n/**\n * LOINC-like codes for lifestyle observation types\n */\nconst LIFESTYLE_CODES: Record<string, { code: string; display: string }> = {\n diet: { code: '81259-4', display: 'Diet' },\n exercise: { code: '73985-4', display: 'Exercise activity' },\n sleep: { code: '93832-4', display: 'Sleep duration' },\n};\n\n/**\n * Convert medication/supplement intervention to FHIR MedicationStatement\n */\nexport function interventionToFHIRMedicationStatement(\n intervention: InterventionData,\n patientId: string,\n): FHIRMedicationStatement {\n const statement: FHIRMedicationStatement = {\n category: {\n coding: [\n {\n code: 'patientspecified',\n display: 'Patient Specified',\n system: 'http://terminology.hl7.org/CodeSystem/medication-statement-category',\n },\n ],\n },\n dateAsserted: intervention.startDate,\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n medicationCodeableConcept: {\n text: intervention.name,\n },\n resourceType: 'MedicationStatement',\n status: interventionStatus(intervention.endDate),\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n if (intervention.notes) {\n statement.note = [{ text: intervention.notes }];\n }\n\n return statement;\n}\n\n/**\n * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)\n */\nexport function interventionToFHIRObservation(\n intervention: InterventionData,\n patientId: string,\n): FHIRObservation {\n const lifestyleCode = LIFESTYLE_CODES[intervention.type];\n\n const observation: FHIRObservation = {\n category: [\n {\n coding: [\n {\n code: 'social-history',\n display: 'Social History',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n coding: lifestyleCode\n ? [\n {\n code: lifestyleCode.code,\n display: lifestyleCode.display,\n system: 'http://loinc.org',\n },\n ]\n : [],\n text: intervention.name,\n },\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n valueString: intervention.name,\n };\n\n if (intervention.notes) {\n observation.note = [{ text: intervention.notes }];\n }\n\n return observation;\n}\n\n/**\n * Convert all interventions to a FHIR Bundle\n */\nexport function interventionsToFHIRBundle(\n interventions: InterventionData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n const fhirPatient = userProfileToFHIR(userProfile);\n\n const entries = interventions.map((intervention) => {\n const isMedication = intervention.type === 'medication' || intervention.type === 'supplement';\n const resource = isMedication\n ? interventionToFHIRMedicationStatement(intervention, patientId)\n : interventionToFHIRObservation(intervention, patientId);\n\n return {\n fullUrl: `urn:uuid:intervention-${intervention.interventionId}`,\n resource,\n };\n });\n\n return {\n entry: [\n {\n fullUrl: `urn:uuid:${patientId}`,\n resource: fhirPatient,\n },\n ...entries,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n","/**\n * Structured zone data for DEXA body composition and bone density charts.\n *\n * Body fat zones derived from Gallagher et al. Am J Clin Nutr 2000;72:694-701 (PMID: 10966886)\n * and ACSM Guidelines for Exercise Testing, 11th Ed (2021).\n *\n * T-Score zones from WHO criteria (Kanis JA, Osteoporos Int, PMID: 7696835).\n */\n\nexport interface BodyFatZone {\n ageMax: number;\n ageMin: number;\n color: string;\n fatPctMax: number;\n fatPctMin: number;\n label: string;\n sex: 'F' | 'M';\n}\n\ninterface AgeBracket {\n ageMax: number;\n ageMin: number;\n label: string;\n}\n\nconst AGE_BRACKETS: AgeBracket[] = [\n { ageMax: 25, ageMin: 18, label: '18-25' },\n { ageMax: 35, ageMin: 26, label: '26-35' },\n { ageMax: 45, ageMin: 36, label: '36-45' },\n { ageMax: 55, ageMin: 46, label: '46-55' },\n { ageMax: 99, ageMin: 56, label: '56+' },\n];\n\n// Zone boundaries per age bracket for men: [essential, athletic, fitness, average, obese]\n// Each value is the upper bound of the zone\nconst MALE_ZONES: number[][] = [\n [5, 10, 20, 25, 40],\n [5, 11, 21, 26, 40],\n [5, 12, 22, 27, 40],\n [5, 13, 23, 28, 40],\n [5, 14, 24, 29, 40],\n];\n\nconst FEMALE_ZONES: number[][] = [\n [13, 18, 28, 32, 45],\n [13, 18, 29, 33, 45],\n [13, 19, 30, 34, 45],\n [13, 20, 31, 35, 45],\n [13, 20, 32, 36, 45],\n];\n\ninterface ZoneDefinition {\n color: string;\n label: string;\n}\n\nconst ZONE_DEFS: ZoneDefinition[] = [\n { color: '#3b82f6', label: 'Essencial' },\n { color: '#06b6d4', label: 'Atlético' },\n { color: '#22c55e', label: 'Fitness' },\n { color: '#eab308', label: 'Média' },\n { color: '#ef4444', label: 'Obeso' },\n];\n\nfunction buildZones(sex: 'F' | 'M', zoneData: number[][]): BodyFatZone[] {\n const zones: BodyFatZone[] = [];\n for (let i = 0; i < AGE_BRACKETS.length; i++) {\n const bracket = AGE_BRACKETS[i]!;\n const b = zoneData[i]!;\n zones.push({\n ...bracket,\n color: ZONE_DEFS[0]!.color,\n fatPctMax: b[0]!,\n fatPctMin: 0,\n label: ZONE_DEFS[0]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[1]!.color,\n fatPctMax: b[1]!,\n fatPctMin: b[0]!,\n label: ZONE_DEFS[1]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[2]!.color,\n fatPctMax: b[2]!,\n fatPctMin: b[1]!,\n label: ZONE_DEFS[2]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[3]!.color,\n fatPctMax: b[3]!,\n fatPctMin: b[2]!,\n label: ZONE_DEFS[3]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[4]!.color,\n fatPctMax: b[4]!,\n fatPctMin: b[3]!,\n label: ZONE_DEFS[4]!.label,\n sex,\n });\n }\n return zones;\n}\n\nexport const BODY_FAT_ZONES: BodyFatZone[] = [\n ...buildZones('M', MALE_ZONES),\n ...buildZones('F', FEMALE_ZONES),\n];\n\nexport { AGE_BRACKETS, ZONE_DEFS };\n\nexport interface TScoreZone {\n color: string;\n label: string;\n max: number;\n min: number;\n}\n\nexport const T_SCORE_ZONES: TScoreZone[] = [\n { color: '#22c55e', label: 'Normal', max: 4, min: -1.0 },\n { color: '#eab308', label: 'Osteopenia', max: -1.0, min: -2.5 },\n { color: '#ef4444', label: 'Osteoporose', max: -2.5, min: -5 },\n];\n","/**\n * Screening Intervals Configuration\n *\n * Defines recommended screening intervals for different biomarker categories\n * based on clinical guidelines and best practices.\n */\n\n/**\n * Screening interval in months\n */\nexport type ScreeningIntervalMonths = 3 | 6 | 12;\n\n/**\n * Biomarker category with its recommended screening interval\n */\nexport interface CategoryScreeningInterval {\n category: string;\n intervalMonths: ScreeningIntervalMonths;\n nameEn: string;\n namePt: string;\n}\n\n/**\n * Screening interval configuration for each category\n *\n * Categories are grouped by their recommended screening intervals:\n * - 3 months: Body composition and bone density (frequently changing metrics)\n * - 6 months: Metabolic panel and nutrients (moderate change rate)\n * - 12 months: Standard blood panels (stable long-term markers)\n */\nexport const CATEGORY_SCREENING_INTERVALS: CategoryScreeningInterval[] = [\n // 3-month intervals - Body composition (frequently changing)\n {\n category: 'composicao-corporal',\n intervalMonths: 3,\n nameEn: 'Body Composition',\n namePt: 'Composição Corporal',\n },\n {\n category: 'densidade-ossea',\n intervalMonths: 3,\n nameEn: 'Bone Density',\n namePt: 'Densidade Óssea',\n },\n\n // 6-month intervals - Metabolic and nutrients\n {\n category: 'metabolico',\n intervalMonths: 6,\n nameEn: 'Metabolic Panel',\n namePt: 'Painel Metabólico',\n },\n {\n category: 'nutrientes',\n intervalMonths: 6,\n nameEn: 'Nutrients',\n namePt: 'Nutrientes',\n },\n {\n category: 'pancreas',\n intervalMonths: 6,\n nameEn: 'Pancreas',\n namePt: 'Pâncreas',\n },\n\n // 12-month intervals - Standard blood panels\n {\n category: 'coracao',\n intervalMonths: 12,\n nameEn: 'Heart Health',\n namePt: 'Saúde Cardiovascular',\n },\n {\n category: 'tireoide',\n intervalMonths: 12,\n nameEn: 'Thyroid',\n namePt: 'Tireoide',\n },\n {\n category: 'sangue',\n intervalMonths: 12,\n nameEn: 'Blood Count',\n namePt: 'Hemograma',\n },\n {\n category: 'figado',\n intervalMonths: 12,\n nameEn: 'Liver Function',\n namePt: 'Função Hepática',\n },\n {\n category: 'rins',\n intervalMonths: 12,\n nameEn: 'Kidney Function',\n namePt: 'Função Renal',\n },\n {\n category: 'saude-feminina',\n intervalMonths: 12,\n nameEn: \"Women's Health\",\n namePt: 'Saúde Feminina',\n },\n {\n category: 'saude-masculina',\n intervalMonths: 12,\n nameEn: \"Men's Health\",\n namePt: 'Saúde Masculina',\n },\n {\n category: 'eletrolitos',\n intervalMonths: 12,\n nameEn: 'Electrolytes',\n namePt: 'Eletrólitos',\n },\n {\n category: 'estresse-envelhecimento',\n intervalMonths: 12,\n nameEn: 'Stress & Aging',\n namePt: 'Estresse e Envelhecimento',\n },\n {\n category: 'autoimunidade',\n intervalMonths: 12,\n nameEn: 'Autoimmunity',\n namePt: 'Autoimunidade',\n },\n {\n category: 'regulacao-imunologica',\n intervalMonths: 12,\n nameEn: 'Immune Regulation',\n namePt: 'Regulação Imunológica',\n },\n {\n category: 'toxinas-ambientais',\n intervalMonths: 12,\n nameEn: 'Environmental Toxins',\n namePt: 'Toxinas Ambientais',\n },\n {\n category: 'urina',\n intervalMonths: 12,\n nameEn: 'Urinalysis',\n namePt: 'Urina',\n },\n];\n\n/**\n * Get screening interval for a category\n */\nexport const getScreeningInterval = (category: string): CategoryScreeningInterval | undefined => {\n return CATEGORY_SCREENING_INTERVALS.find((c) => c.category === category);\n};\n\n/**\n * Get all categories with a specific interval\n */\nexport const getCategoriesByInterval = (\n intervalMonths: ScreeningIntervalMonths,\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((c) => c.intervalMonths === intervalMonths);\n};\n\n/**\n * Calculate next screening date based on last test date and category\n */\nexport const calculateNextScreeningDate = (lastTestDate: Date, category: string): Date | null => {\n const interval = getScreeningInterval(category);\n if (!interval) return null;\n\n const nextDate = new Date(lastTestDate);\n nextDate.setMonth(nextDate.getMonth() + interval.intervalMonths);\n return nextDate;\n};\n\n/**\n * Check if a category is due for screening\n */\nexport const isScreeningDue = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): boolean => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return false;\n return referenceDate >= nextDate;\n};\n\n/**\n * Get categories that are due for screening based on last test dates\n */\nexport const getDueCategories = (\n lastTestDates: Record<string, Date>,\n referenceDate: Date = new Date(),\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((interval) => {\n const lastDate = lastTestDates[interval.category];\n if (!lastDate) return true; // Never tested = due\n return isScreeningDue(lastDate, interval.category, referenceDate);\n });\n};\n\n/**\n * Get days until next screening for a category\n */\nexport const getDaysUntilScreening = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): number | null => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return null;\n\n const diffTime = nextDate.getTime() - referenceDate.getTime();\n return Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n};\n","/**\n * Portuguese pluralization utility using native Intl.PluralRules\n * Provides automatic pluralization for common words used in the app\n */\n\nconst pluralRules = new Intl.PluralRules('pt-BR');\n\n/**\n * Dictionary of Portuguese words with their plural forms\n * Key is the singular form, value is the plural form\n */\nconst dictionary: Record<string, string> = {\n // Common nouns\n arquivo: 'arquivos',\n biomarcador: 'biomarcadores',\n // Past participles (masculine)\n cadastrado: 'cadastrados',\n // Past participles (feminine)\n concluída: 'concluídas',\n confirmado: 'confirmados',\n convertido: 'convertidos',\n convidado: 'convidados',\n convite: 'convites',\n disponível: 'disponíveis',\n documento: 'documentos',\n enviado: 'enviados',\n exame: 'exames',\n excluída: 'excluídas',\n\n excluído: 'excluídos',\n // Verbs (3rd person)\n falhou: 'falharam',\n falta: 'faltam',\n ignorado: 'ignorados',\n item: 'itens',\n outro: 'outros',\n página: 'páginas',\n pendente: 'pendentes',\n registro: 'registros',\n removido: 'removidos',\n\n resultado: 'resultados',\n revisão: 'revisões',\n\n revogado: 'revogados',\n usuário: 'usuários',\n};\n\n/**\n * Get the plural form of a word from the dictionary\n * Falls back to adding 's' if word is not in dictionary\n */\nconst getPluralForm = (singular: string): string => {\n return dictionary[singular] ?? `${singular}s`;\n};\n\n/**\n * Returns the correct singular or plural form based on count\n * Uses Intl.PluralRules for proper locale-aware pluralization\n *\n * @example\n * plural(1, 'usuário') // 'usuário'\n * plural(3, 'usuário') // 'usuários'\n * plural(0, 'registro') // 'registros'\n */\nexport const plural = (count: number, word: string): string => {\n const rule = pluralRules.select(count);\n return rule === 'one' ? word : getPluralForm(word);\n};\n\n/**\n * Returns count with the correct singular or plural form\n *\n * @example\n * pluralCount(1, 'usuário') // '1 usuário'\n * pluralCount(3, 'usuário') // '3 usuários'\n */\nexport const pluralCount = (count: number, word: string): string => {\n return `${count} ${plural(count, word)}`;\n};\n\n/**\n * Returns the correct form for compound phrases (noun + adjective)\n * Both words are pluralized together\n *\n * @example\n * pluralPhrase(1, 'usuário', 'cadastrado') // 'usuário cadastrado'\n * pluralPhrase(3, 'usuário', 'cadastrado') // 'usuários cadastrados'\n * pluralPhrase(2, 'revisão', 'excluída') // 'revisões excluídas'\n */\nexport const pluralPhrase = (count: number, noun: string, adjective: string): string => {\n const rule = pluralRules.select(count);\n if (rule === 'one') {\n return `${noun} ${adjective}`;\n }\n return `${getPluralForm(noun)} ${getPluralForm(adjective)}`;\n};\n\n/**\n * Returns count with the correct compound phrase form\n *\n * @example\n * pluralPhraseCount(1, 'usuário', 'cadastrado') // '1 usuário cadastrado'\n * pluralPhraseCount(3, 'convite', 'enviado') // '3 convites enviados'\n */\nexport const pluralPhraseCount = (count: number, noun: string, adjective: string): string => {\n return `${count} ${pluralPhrase(count, noun, adjective)}`;\n};\n","/**\n * Helpers para identificadores brasileiros — CPF e CNS\n *\n * Validação, formatação e conversão para FHIR Identifier.\n * Algoritmos de validação baseados nas especificações oficiais:\n * - CPF: Receita Federal (mod-11, dois dígitos verificadores)\n * - CNS: Ministério da Saúde (mod-11 para definitivos, soma ponderada para provisórios)\n */\n\nimport type { FHIRIdentifier } from './fhir-types';\n\nconst CPF_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf';\nconst CNS_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns';\n\n/**\n * Remove caracteres não-numéricos de uma string.\n */\nfunction digitsOnly(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/**\n * Valida um CPF brasileiro usando algoritmo mod-11.\n *\n * @param cpf — CPF com ou sem formatação (ex: \"123.456.789-09\" ou \"12345678909\")\n * @returns true se o CPF é estruturalmente válido\n */\nexport function validateCPF(cpf: string): boolean {\n const digits = digitsOnly(cpf);\n\n if (digits.length !== 11) return false;\n\n // Rejeitar sequências de dígitos iguais (ex: 111.111.111-11)\n if (/^(\\d)\\1{10}$/.test(digits)) return false;\n\n // Primeiro dígito verificador\n let sum = 0;\n for (let i = 0; i < 9; i++) {\n sum += Number(digits[i]) * (10 - i);\n }\n let remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[9])) return false;\n\n // Segundo dígito verificador\n sum = 0;\n for (let i = 0; i < 10; i++) {\n sum += Number(digits[i]) * (11 - i);\n }\n remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[10])) return false;\n\n return true;\n}\n\n/**\n * Valida um CNS (Cartão Nacional de Saúde) brasileiro.\n *\n * CNS definitivos começam com 1 ou 2 (mod-11).\n * CNS provisórios começam com 7, 8 ou 9 (soma ponderada mod-11 = 0).\n *\n * @param cns — CNS com 15 dígitos\n * @returns true se o CNS é estruturalmente válido\n */\nexport function validateCNS(cns: string): boolean {\n const digits = digitsOnly(cns);\n\n if (digits.length !== 15) return false;\n\n const firstDigit = digits[0]!;\n\n // CNS deve começar com 1, 2 (definitivo) ou 7, 8, 9 (provisório)\n if (!['1', '2', '7', '8', '9'].includes(firstDigit)) return false;\n\n // Ambos os tipos usam soma ponderada mod-11 = 0\n let sum = 0;\n for (let i = 0; i < 15; i++) {\n sum += Number(digits[i]) * (15 - i);\n }\n return sum % 11 === 0;\n}\n\n/**\n * Formata um CPF como XXX.XXX.XXX-XX.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns CPF formatado ou a string original se inválido\n */\nexport function formatCPF(cpf: string): string {\n const digits = digitsOnly(cpf);\n if (digits.length !== 11) return cpf;\n return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;\n}\n\n/**\n * Formata um CNS como XXX XXXX XXXX XXXX.\n *\n * @param cns — CNS com 15 dígitos\n * @returns CNS formatado ou a string original se inválido\n */\nexport function formatCNS(cns: string): string {\n const digits = digitsOnly(cns);\n if (digits.length !== 15) return cns;\n return `${digits.slice(0, 3)} ${digits.slice(3, 7)} ${digits.slice(7, 11)} ${digits.slice(11)}`;\n}\n\n/**\n * Converte um CPF para um FHIR Identifier.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns FHIR Identifier com sistema RNDS para CPF\n * @throws Error se o CPF for inválido\n */\nexport function cpfToFHIRIdentifier(cpf: string): FHIRIdentifier {\n if (!validateCPF(cpf)) {\n throw new Error('CPF inválido');\n }\n return {\n system: CPF_SYSTEM,\n use: 'official',\n value: digitsOnly(cpf),\n };\n}\n\n/**\n * Converte um CNS para um FHIR Identifier.\n *\n * @param cns — CNS com 15 dígitos\n * @returns FHIR Identifier com sistema RNDS para CNS\n * @throws Error se o CNS for inválido\n */\nexport function cnsToFHIRIdentifier(cns: string): FHIRIdentifier {\n if (!validateCNS(cns)) {\n throw new Error('CNS inválido');\n }\n return {\n system: CNS_SYSTEM,\n use: 'official',\n value: digitsOnly(cns),\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/index.cjs","../src/category-groups.ts","../src/intervention-converter.ts","../src/dexa-zone-data.ts","../src/screening-intervals.ts","../src/i18n.ts","../src/identifiers.ts"],"names":[],"mappings":"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,EAAA;AACjD;AH2MoD;AACA;AIjTqB;AAAA;AAEvE,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AAAA;AAGA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AAAA;AAGA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACA,EAAA;AACY,IAAA;AACM,IAAA;AACR,IAAA;AACA,IAAA;AACV,EAAA;AACF;AAKiG;AAC7C,EAAA;AACpD;AAOkC;AACkB,EAAA;AACpD;AAK+D;AACf,EAAA;AACxB,EAAA;AAEgB,EAAA;AACW,EAAA;AAC1C,EAAA;AACT;AAQE;AAE4C,EAAA;AACtB,EAAA;AACE,EAAA;AAC1B;AAOE;AAE4C,EAAA;AACM,IAAA;AAC1B,IAAA;AACmB,IAAA;AAC1C,EAAA;AACH;AAOE;AAG4C,EAAA;AACtB,EAAA;AAEgB,EAAA;AACW,EAAA;AACnD;AJ0QoD;AACA;AK5dJ;AAML;AAAA;AAEhC,EAAA;AACI,EAAA;AAAA;AAED,EAAA;AAAA;AAED,EAAA;AACC,EAAA;AACA,EAAA;AACD,EAAA;AACF,EAAA;AACG,EAAA;AACD,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AAEA,EAAA;AAAA;AAEF,EAAA;AACD,EAAA;AACG,EAAA;AACJ,EAAA;AACC,EAAA;AACC,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AAEC,EAAA;AACF,EAAA;AAEC,EAAA;AACD,EAAA;AACX;AAMoD;AACR,EAAA;AAC5C;AAW+D;AACxB,EAAA;AACY,EAAA;AACnD;AASoE;AAC5B,EAAA;AACxC;AAWwF;AACjD,EAAA;AACjB,EAAA;AACS,IAAA;AAC7B,EAAA;AAC+C,EAAA;AACjD;AAS6F;AAC9C,EAAA;AAC/C;AL6aoD;AACA;AM9gBjC;AACA;AAKwB;AACX,EAAA;AAChC;AAQkD;AACnB,EAAA;AAEI,EAAA;AAGO,EAAA;AAG9B,EAAA;AACkB,EAAA;AACO,IAAA;AACnC,EAAA;AAC6B,EAAA;AACK,EAAA;AACU,EAAA;AAGtC,EAAA;AACuB,EAAA;AACM,IAAA;AACnC,EAAA;AACyB,EAAA;AACS,EAAA;AACW,EAAA;AAEtC,EAAA;AACT;AAWkD;AACnB,EAAA;AAEI,EAAA;AAEN,EAAA;AAGuB,EAAA;AAGxC,EAAA;AACmB,EAAA;AACM,IAAA;AACnC,EAAA;AACoB,EAAA;AACtB;AAQ+C;AAChB,EAAA;AACI,EAAA;AACiB,EAAA;AACpD;AAQ+C;AAChB,EAAA;AACI,EAAA;AACiB,EAAA;AACpD;AASiE;AACxC,EAAA;AACS,IAAA;AAChC,EAAA;AACO,EAAA;AACG,IAAA;AACH,IAAA;AACgB,IAAA;AACvB,EAAA;AACF;AASiE;AACxC,EAAA;AACS,IAAA;AAChC,EAAA;AACO,EAAA;AACG,IAAA;AACH,IAAA;AACgB,IAAA;AACvB,EAAA;AACF;AN+coD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/index.cjs","sourcesContent":[null,"/**\n * Agrupamento de categorias clínicas em 10 grupos de alto nível.\n *\n * `BiomarkerDefinition.category` armazena 20 sub-categorias (granularidade\n * fina, ex: `tireoide`, `pancreas`). Este módulo agrupa essas\n * sub-categorias em 10 buckets clínicos amplos para apresentação no\n * site, na API pública e em material de divulgação.\n *\n * As sub-categorias permanecem como fonte da verdade nos dados; este\n * agrupamento é uma camada derivada.\n */\n\nexport type CategoryGroup =\n | 'cardiovascular'\n | 'metabolico-endocrino'\n | 'renal-eletrolitico'\n | 'hepatico-biliar'\n | 'hematologico'\n | 'imunologico'\n | 'oncologico'\n | 'nutricional-ambiental'\n | 'saude-reprodutiva'\n | 'composicao-envelhecimento';\n\nexport interface CategoryGroupInfo {\n /** Rótulo em inglês */\n en: string;\n /** Rótulo em português */\n pt: string;\n /** Slug (kebab-case, sem acento) */\n slug: CategoryGroup;\n /** Sub-categorias da fonte agrupadas neste bucket */\n subcategories: readonly string[];\n}\n\nexport const CATEGORY_GROUPS: Record<CategoryGroup, CategoryGroupInfo> = {\n cardiovascular: {\n en: 'Cardiovascular',\n pt: 'Cardiovascular',\n slug: 'cardiovascular',\n subcategories: ['coracao'],\n },\n 'composicao-envelhecimento': {\n en: 'Body Composition & Aging',\n pt: 'Composição Corporal e Envelhecimento',\n slug: 'composicao-envelhecimento',\n subcategories: ['composicao-corporal', 'densidade-ossea', 'estresse-envelhecimento'],\n },\n hematologico: {\n en: 'Hematology',\n pt: 'Hematológico',\n slug: 'hematologico',\n subcategories: ['sangue'],\n },\n 'hepatico-biliar': {\n en: 'Hepatic & Biliary',\n pt: 'Hepático e Biliar',\n slug: 'hepatico-biliar',\n subcategories: ['figado'],\n },\n imunologico: {\n en: 'Immunology',\n pt: 'Imunológico',\n slug: 'imunologico',\n subcategories: ['autoimunidade', 'regulacao-imunologica'],\n },\n 'metabolico-endocrino': {\n en: 'Metabolic & Endocrine',\n pt: 'Metabólico e Endócrino',\n slug: 'metabolico-endocrino',\n subcategories: ['metabolico', 'pancreas', 'hormonios', 'tireoide'],\n },\n 'nutricional-ambiental': {\n en: 'Nutrition & Environmental Exposure',\n pt: 'Nutricional e Exposição Ambiental',\n slug: 'nutricional-ambiental',\n subcategories: ['nutrientes', 'toxinas-ambientais'],\n },\n oncologico: {\n en: 'Oncology',\n pt: 'Oncológico',\n slug: 'oncologico',\n subcategories: ['marcadores-tumorais'],\n },\n 'renal-eletrolitico': {\n en: 'Renal & Electrolytes',\n pt: 'Renal e Eletrolítico',\n slug: 'renal-eletrolitico',\n subcategories: ['rins', 'urina', 'eletrolitos'],\n },\n 'saude-reprodutiva': {\n en: 'Reproductive Health',\n pt: 'Saúde Reprodutiva',\n slug: 'saude-reprodutiva',\n subcategories: ['saude-feminina', 'saude-masculina'],\n },\n};\n\nconst SUBCATEGORY_TO_GROUP = new Map<string, CategoryGroup>();\nfor (const group of Object.values(CATEGORY_GROUPS)) {\n for (const sub of group.subcategories) {\n SUBCATEGORY_TO_GROUP.set(sub, group.slug);\n }\n}\n\n/**\n * Resolve a sub-categoria (granular) para o grupo de alto nível (10 buckets).\n */\nexport function getCategoryGroup(subcategory: string): CategoryGroup | undefined {\n return SUBCATEGORY_TO_GROUP.get(subcategory);\n}\n\n/**\n * Lista todas as sub-categorias mapeadas em algum grupo.\n */\nexport function listMappedSubcategories(): readonly string[] {\n return [...SUBCATEGORY_TO_GROUP.keys()];\n}\n","/**\n * FHIR Intervention Converter\n *\n * Converts interventions (medication, supplement, diet, exercise, sleep)\n * to FHIR R4 MedicationStatement and Observation resources.\n */\n\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { userProfileToFHIR } from './converter';\nimport type { FHIRBundle, FHIRMedicationStatement, FHIRObservation } from './fhir-types';\nimport type { InterventionData, UserProfileData } from './types';\n\n/**\n * Determine MedicationStatement/Observation status based on end date\n */\nfunction interventionStatus(endDate?: string): 'active' | 'completed' {\n if (!endDate) return 'active';\n return new Date(endDate) < new Date() ? 'completed' : 'active';\n}\n\n/**\n * LOINC-like codes for lifestyle observation types\n */\nconst LIFESTYLE_CODES: Record<string, { code: string; display: string }> = {\n diet: { code: '81259-4', display: 'Diet' },\n exercise: { code: '73985-4', display: 'Exercise activity' },\n sleep: { code: '93832-4', display: 'Sleep duration' },\n};\n\n/**\n * Convert medication/supplement intervention to FHIR MedicationStatement\n */\nexport function interventionToFHIRMedicationStatement(\n intervention: InterventionData,\n patientId: string,\n): Addressable<FHIRMedicationStatement> {\n const statement: Addressable<FHIRMedicationStatement> = {\n category: {\n coding: [\n {\n code: 'patientspecified',\n display: 'Patient Specified',\n system: 'http://terminology.hl7.org/CodeSystem/medication-statement-category',\n },\n ],\n },\n dateAsserted: intervention.startDate,\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n medicationCodeableConcept: {\n text: intervention.name,\n },\n resourceType: 'MedicationStatement',\n status: interventionStatus(intervention.endDate),\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n if (intervention.notes) {\n statement.note = [{ text: intervention.notes }];\n }\n\n return statement;\n}\n\n/**\n * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)\n */\nexport function interventionToFHIRObservation(\n intervention: InterventionData,\n patientId: string,\n): Addressable<FHIRObservation> {\n const lifestyleCode = LIFESTYLE_CODES[intervention.type];\n\n const observation: Addressable<FHIRObservation> = {\n category: [\n {\n coding: [\n {\n code: 'social-history',\n display: 'Social History',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n coding: lifestyleCode\n ? [\n {\n code: lifestyleCode.code,\n display: lifestyleCode.display,\n system: 'http://loinc.org',\n },\n ]\n : [],\n text: intervention.name,\n },\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n valueString: intervention.name,\n };\n\n if (intervention.notes) {\n observation.note = [{ text: intervention.notes }];\n }\n\n return observation;\n}\n\n/**\n * Convert all interventions to a FHIR Bundle\n */\nexport function interventionsToFHIRBundle(\n interventions: InterventionData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n const fhirPatient = userProfileToFHIR(userProfile);\n\n const entries = interventions.map((intervention) => {\n const isMedication = intervention.type === 'medication' || intervention.type === 'supplement';\n const resource = isMedication\n ? interventionToFHIRMedicationStatement(intervention, patientId)\n : interventionToFHIRObservation(intervention, patientId);\n\n return { fullUrl: entryFullUrl(resource), resource };\n });\n\n return {\n entry: [{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient }, ...entries],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n","/**\n * Structured zone data for DEXA body composition and bone density charts.\n *\n * Body fat zones derived from Gallagher et al. Am J Clin Nutr 2000;72:694-701 (PMID: 10966886)\n * and ACSM Guidelines for Exercise Testing, 11th Ed (2021).\n *\n * T-Score zones from WHO criteria (Kanis JA, Osteoporos Int, PMID: 7696835).\n */\n\nexport interface BodyFatZone {\n ageMax: number;\n ageMin: number;\n color: string;\n fatPctMax: number;\n fatPctMin: number;\n label: string;\n sex: 'F' | 'M';\n}\n\ninterface AgeBracket {\n ageMax: number;\n ageMin: number;\n label: string;\n}\n\nconst AGE_BRACKETS: AgeBracket[] = [\n { ageMax: 25, ageMin: 18, label: '18-25' },\n { ageMax: 35, ageMin: 26, label: '26-35' },\n { ageMax: 45, ageMin: 36, label: '36-45' },\n { ageMax: 55, ageMin: 46, label: '46-55' },\n { ageMax: 99, ageMin: 56, label: '56+' },\n];\n\n// Zone boundaries per age bracket for men: [essential, athletic, fitness, average, obese]\n// Each value is the upper bound of the zone\nconst MALE_ZONES: number[][] = [\n [5, 10, 20, 25, 40],\n [5, 11, 21, 26, 40],\n [5, 12, 22, 27, 40],\n [5, 13, 23, 28, 40],\n [5, 14, 24, 29, 40],\n];\n\nconst FEMALE_ZONES: number[][] = [\n [13, 18, 28, 32, 45],\n [13, 18, 29, 33, 45],\n [13, 19, 30, 34, 45],\n [13, 20, 31, 35, 45],\n [13, 20, 32, 36, 45],\n];\n\ninterface ZoneDefinition {\n color: string;\n label: string;\n}\n\nconst ZONE_DEFS: ZoneDefinition[] = [\n { color: '#3b82f6', label: 'Essencial' },\n { color: '#06b6d4', label: 'Atlético' },\n { color: '#22c55e', label: 'Fitness' },\n { color: '#eab308', label: 'Média' },\n { color: '#ef4444', label: 'Obeso' },\n];\n\nfunction buildZones(sex: 'F' | 'M', zoneData: number[][]): BodyFatZone[] {\n const zones: BodyFatZone[] = [];\n for (let i = 0; i < AGE_BRACKETS.length; i++) {\n const bracket = AGE_BRACKETS[i]!;\n const b = zoneData[i]!;\n zones.push({\n ...bracket,\n color: ZONE_DEFS[0]!.color,\n fatPctMax: b[0]!,\n fatPctMin: 0,\n label: ZONE_DEFS[0]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[1]!.color,\n fatPctMax: b[1]!,\n fatPctMin: b[0]!,\n label: ZONE_DEFS[1]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[2]!.color,\n fatPctMax: b[2]!,\n fatPctMin: b[1]!,\n label: ZONE_DEFS[2]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[3]!.color,\n fatPctMax: b[3]!,\n fatPctMin: b[2]!,\n label: ZONE_DEFS[3]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[4]!.color,\n fatPctMax: b[4]!,\n fatPctMin: b[3]!,\n label: ZONE_DEFS[4]!.label,\n sex,\n });\n }\n return zones;\n}\n\nexport const BODY_FAT_ZONES: BodyFatZone[] = [\n ...buildZones('M', MALE_ZONES),\n ...buildZones('F', FEMALE_ZONES),\n];\n\nexport { AGE_BRACKETS, ZONE_DEFS };\n\nexport interface TScoreZone {\n color: string;\n label: string;\n max: number;\n min: number;\n}\n\nexport const T_SCORE_ZONES: TScoreZone[] = [\n { color: '#22c55e', label: 'Normal', max: 4, min: -1.0 },\n { color: '#eab308', label: 'Osteopenia', max: -1.0, min: -2.5 },\n { color: '#ef4444', label: 'Osteoporose', max: -2.5, min: -5 },\n];\n","/**\n * Screening Intervals Configuration\n *\n * Defines recommended screening intervals for different biomarker categories\n * based on clinical guidelines and best practices.\n */\n\n/**\n * Screening interval in months\n */\nexport type ScreeningIntervalMonths = 3 | 6 | 12;\n\n/**\n * Biomarker category with its recommended screening interval\n */\nexport interface CategoryScreeningInterval {\n category: string;\n intervalMonths: ScreeningIntervalMonths;\n nameEn: string;\n namePt: string;\n}\n\n/**\n * Screening interval configuration for each category\n *\n * Categories are grouped by their recommended screening intervals:\n * - 3 months: Body composition and bone density (frequently changing metrics)\n * - 6 months: Metabolic panel and nutrients (moderate change rate)\n * - 12 months: Standard blood panels (stable long-term markers)\n */\nexport const CATEGORY_SCREENING_INTERVALS: CategoryScreeningInterval[] = [\n // 3-month intervals - Body composition (frequently changing)\n {\n category: 'composicao-corporal',\n intervalMonths: 3,\n nameEn: 'Body Composition',\n namePt: 'Composição Corporal',\n },\n {\n category: 'densidade-ossea',\n intervalMonths: 3,\n nameEn: 'Bone Density',\n namePt: 'Densidade Óssea',\n },\n\n // 6-month intervals - Metabolic and nutrients\n {\n category: 'metabolico',\n intervalMonths: 6,\n nameEn: 'Metabolic Panel',\n namePt: 'Painel Metabólico',\n },\n {\n category: 'nutrientes',\n intervalMonths: 6,\n nameEn: 'Nutrients',\n namePt: 'Nutrientes',\n },\n {\n category: 'pancreas',\n intervalMonths: 6,\n nameEn: 'Pancreas',\n namePt: 'Pâncreas',\n },\n\n // 12-month intervals - Standard blood panels\n {\n category: 'coracao',\n intervalMonths: 12,\n nameEn: 'Heart Health',\n namePt: 'Saúde Cardiovascular',\n },\n {\n category: 'tireoide',\n intervalMonths: 12,\n nameEn: 'Thyroid',\n namePt: 'Tireoide',\n },\n {\n category: 'sangue',\n intervalMonths: 12,\n nameEn: 'Blood Count',\n namePt: 'Hemograma',\n },\n {\n category: 'figado',\n intervalMonths: 12,\n nameEn: 'Liver Function',\n namePt: 'Função Hepática',\n },\n {\n category: 'rins',\n intervalMonths: 12,\n nameEn: 'Kidney Function',\n namePt: 'Função Renal',\n },\n {\n category: 'saude-feminina',\n intervalMonths: 12,\n nameEn: \"Women's Health\",\n namePt: 'Saúde Feminina',\n },\n {\n category: 'saude-masculina',\n intervalMonths: 12,\n nameEn: \"Men's Health\",\n namePt: 'Saúde Masculina',\n },\n {\n category: 'eletrolitos',\n intervalMonths: 12,\n nameEn: 'Electrolytes',\n namePt: 'Eletrólitos',\n },\n {\n category: 'estresse-envelhecimento',\n intervalMonths: 12,\n nameEn: 'Stress & Aging',\n namePt: 'Estresse e Envelhecimento',\n },\n {\n category: 'autoimunidade',\n intervalMonths: 12,\n nameEn: 'Autoimmunity',\n namePt: 'Autoimunidade',\n },\n {\n category: 'regulacao-imunologica',\n intervalMonths: 12,\n nameEn: 'Immune Regulation',\n namePt: 'Regulação Imunológica',\n },\n {\n category: 'toxinas-ambientais',\n intervalMonths: 12,\n nameEn: 'Environmental Toxins',\n namePt: 'Toxinas Ambientais',\n },\n {\n category: 'urina',\n intervalMonths: 12,\n nameEn: 'Urinalysis',\n namePt: 'Urina',\n },\n];\n\n/**\n * Get screening interval for a category\n */\nexport const getScreeningInterval = (category: string): CategoryScreeningInterval | undefined => {\n return CATEGORY_SCREENING_INTERVALS.find((c) => c.category === category);\n};\n\n/**\n * Get all categories with a specific interval\n */\nexport const getCategoriesByInterval = (\n intervalMonths: ScreeningIntervalMonths,\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((c) => c.intervalMonths === intervalMonths);\n};\n\n/**\n * Calculate next screening date based on last test date and category\n */\nexport const calculateNextScreeningDate = (lastTestDate: Date, category: string): Date | null => {\n const interval = getScreeningInterval(category);\n if (!interval) return null;\n\n const nextDate = new Date(lastTestDate);\n nextDate.setMonth(nextDate.getMonth() + interval.intervalMonths);\n return nextDate;\n};\n\n/**\n * Check if a category is due for screening\n */\nexport const isScreeningDue = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): boolean => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return false;\n return referenceDate >= nextDate;\n};\n\n/**\n * Get categories that are due for screening based on last test dates\n */\nexport const getDueCategories = (\n lastTestDates: Record<string, Date>,\n referenceDate: Date = new Date(),\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((interval) => {\n const lastDate = lastTestDates[interval.category];\n if (!lastDate) return true; // Never tested = due\n return isScreeningDue(lastDate, interval.category, referenceDate);\n });\n};\n\n/**\n * Get days until next screening for a category\n */\nexport const getDaysUntilScreening = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): number | null => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return null;\n\n const diffTime = nextDate.getTime() - referenceDate.getTime();\n return Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n};\n","/**\n * Portuguese pluralization utility using native Intl.PluralRules\n * Provides automatic pluralization for common words used in the app\n */\n\nconst pluralRules = new Intl.PluralRules('pt-BR');\n\n/**\n * Dictionary of Portuguese words with their plural forms\n * Key is the singular form, value is the plural form\n */\nconst dictionary: Record<string, string> = {\n // Common nouns\n arquivo: 'arquivos',\n biomarcador: 'biomarcadores',\n // Past participles (masculine)\n cadastrado: 'cadastrados',\n // Past participles (feminine)\n concluída: 'concluídas',\n confirmado: 'confirmados',\n convertido: 'convertidos',\n convidado: 'convidados',\n convite: 'convites',\n disponível: 'disponíveis',\n documento: 'documentos',\n enviado: 'enviados',\n exame: 'exames',\n excluída: 'excluídas',\n\n excluído: 'excluídos',\n // Verbs (3rd person)\n falhou: 'falharam',\n falta: 'faltam',\n ignorado: 'ignorados',\n item: 'itens',\n outro: 'outros',\n página: 'páginas',\n pendente: 'pendentes',\n registro: 'registros',\n removido: 'removidos',\n\n resultado: 'resultados',\n revisão: 'revisões',\n\n revogado: 'revogados',\n usuário: 'usuários',\n};\n\n/**\n * Get the plural form of a word from the dictionary\n * Falls back to adding 's' if word is not in dictionary\n */\nconst getPluralForm = (singular: string): string => {\n return dictionary[singular] ?? `${singular}s`;\n};\n\n/**\n * Returns the correct singular or plural form based on count\n * Uses Intl.PluralRules for proper locale-aware pluralization\n *\n * @example\n * plural(1, 'usuário') // 'usuário'\n * plural(3, 'usuário') // 'usuários'\n * plural(0, 'registro') // 'registros'\n */\nexport const plural = (count: number, word: string): string => {\n const rule = pluralRules.select(count);\n return rule === 'one' ? word : getPluralForm(word);\n};\n\n/**\n * Returns count with the correct singular or plural form\n *\n * @example\n * pluralCount(1, 'usuário') // '1 usuário'\n * pluralCount(3, 'usuário') // '3 usuários'\n */\nexport const pluralCount = (count: number, word: string): string => {\n return `${count} ${plural(count, word)}`;\n};\n\n/**\n * Returns the correct form for compound phrases (noun + adjective)\n * Both words are pluralized together\n *\n * @example\n * pluralPhrase(1, 'usuário', 'cadastrado') // 'usuário cadastrado'\n * pluralPhrase(3, 'usuário', 'cadastrado') // 'usuários cadastrados'\n * pluralPhrase(2, 'revisão', 'excluída') // 'revisões excluídas'\n */\nexport const pluralPhrase = (count: number, noun: string, adjective: string): string => {\n const rule = pluralRules.select(count);\n if (rule === 'one') {\n return `${noun} ${adjective}`;\n }\n return `${getPluralForm(noun)} ${getPluralForm(adjective)}`;\n};\n\n/**\n * Returns count with the correct compound phrase form\n *\n * @example\n * pluralPhraseCount(1, 'usuário', 'cadastrado') // '1 usuário cadastrado'\n * pluralPhraseCount(3, 'convite', 'enviado') // '3 convites enviados'\n */\nexport const pluralPhraseCount = (count: number, noun: string, adjective: string): string => {\n return `${count} ${pluralPhrase(count, noun, adjective)}`;\n};\n","/**\n * Helpers para identificadores brasileiros — CPF e CNS\n *\n * Validação, formatação e conversão para FHIR Identifier.\n * Algoritmos de validação baseados nas especificações oficiais:\n * - CPF: Receita Federal (mod-11, dois dígitos verificadores)\n * - CNS: Ministério da Saúde (mod-11 para definitivos, soma ponderada para provisórios)\n */\n\nimport type { FHIRIdentifier } from './fhir-types';\n\nconst CPF_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf';\nconst CNS_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns';\n\n/**\n * Remove caracteres não-numéricos de uma string.\n */\nfunction digitsOnly(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/**\n * Valida um CPF brasileiro usando algoritmo mod-11.\n *\n * @param cpf — CPF com ou sem formatação (ex: \"123.456.789-09\" ou \"12345678909\")\n * @returns true se o CPF é estruturalmente válido\n */\nexport function validateCPF(cpf: string): boolean {\n const digits = digitsOnly(cpf);\n\n if (digits.length !== 11) return false;\n\n // Rejeitar sequências de dígitos iguais (ex: 111.111.111-11)\n if (/^(\\d)\\1{10}$/.test(digits)) return false;\n\n // Primeiro dígito verificador\n let sum = 0;\n for (let i = 0; i < 9; i++) {\n sum += Number(digits[i]) * (10 - i);\n }\n let remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[9])) return false;\n\n // Segundo dígito verificador\n sum = 0;\n for (let i = 0; i < 10; i++) {\n sum += Number(digits[i]) * (11 - i);\n }\n remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[10])) return false;\n\n return true;\n}\n\n/**\n * Valida um CNS (Cartão Nacional de Saúde) brasileiro.\n *\n * CNS definitivos começam com 1 ou 2 (mod-11).\n * CNS provisórios começam com 7, 8 ou 9 (soma ponderada mod-11 = 0).\n *\n * @param cns — CNS com 15 dígitos\n * @returns true se o CNS é estruturalmente válido\n */\nexport function validateCNS(cns: string): boolean {\n const digits = digitsOnly(cns);\n\n if (digits.length !== 15) return false;\n\n const firstDigit = digits[0]!;\n\n // CNS deve começar com 1, 2 (definitivo) ou 7, 8, 9 (provisório)\n if (!['1', '2', '7', '8', '9'].includes(firstDigit)) return false;\n\n // Ambos os tipos usam soma ponderada mod-11 = 0\n let sum = 0;\n for (let i = 0; i < 15; i++) {\n sum += Number(digits[i]) * (15 - i);\n }\n return sum % 11 === 0;\n}\n\n/**\n * Formata um CPF como XXX.XXX.XXX-XX.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns CPF formatado ou a string original se inválido\n */\nexport function formatCPF(cpf: string): string {\n const digits = digitsOnly(cpf);\n if (digits.length !== 11) return cpf;\n return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;\n}\n\n/**\n * Formata um CNS como XXX XXXX XXXX XXXX.\n *\n * @param cns — CNS com 15 dígitos\n * @returns CNS formatado ou a string original se inválido\n */\nexport function formatCNS(cns: string): string {\n const digits = digitsOnly(cns);\n if (digits.length !== 15) return cns;\n return `${digits.slice(0, 3)} ${digits.slice(3, 7)} ${digits.slice(7, 11)} ${digits.slice(11)}`;\n}\n\n/**\n * Converte um CPF para um FHIR Identifier.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns FHIR Identifier com sistema RNDS para CPF\n * @throws Error se o CPF for inválido\n */\nexport function cpfToFHIRIdentifier(cpf: string): FHIRIdentifier {\n if (!validateCPF(cpf)) {\n throw new Error('CPF inválido');\n }\n return {\n system: CPF_SYSTEM,\n use: 'official',\n value: digitsOnly(cpf),\n };\n}\n\n/**\n * Converte um CNS para um FHIR Identifier.\n *\n * @param cns — CNS com 15 dígitos\n * @returns FHIR Identifier com sistema RNDS para CNS\n * @throws Error se o CNS for inválido\n */\nexport function cnsToFHIRIdentifier(cns: string): FHIRIdentifier {\n if (!validateCNS(cns)) {\n throw new Error('CNS inválido');\n }\n return {\n system: CNS_SYSTEM,\n use: 'official',\n value: digitsOnly(cns),\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InterventionData, U as UserProfileData } from './converter-BAWBB9Gp.cjs';
2
- export { F as Flag, G as Gender, L as LabObservationData, a as LabReportData, O as OverallStatus, l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-BAWBB9Gp.cjs';
1
+ import { I as InterventionData, A as Addressable, U as UserProfileData } from './converter-Bp3ZhyUQ.cjs';
2
+ export { F as Flag, G as Gender, L as LabObservationData, a as LabReportData, O as OverallStatus, l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-Bp3ZhyUQ.cjs';
3
3
  export { BIOMARKER_DEFINITIONS, BiomarkerDefinition, BiomarkerSearchPattern, CAC_INDICATOR_CODES, DEXA_CATEGORIES, DEXA_INDICATOR_CODES, SupportedBiomarker, codeToLoinc, filterVisibleBiomarkers, findCodeByName, generateCacFullReference, generateDexaFullReference, generateFilteredLLMReference, generateLLMReference, getAllCodes, getAllDefinitions, getAllLoincCodes, getAllSearchPatterns, getBiomarkersByCategory, getBiomarkersForCategories, getDefinitionByCode, getDefinitionByLoinc, getDefinitionsBySex, getSexForCode, getVisibleDefinitions, isBiomarkerVisible, isCacDocument, isDexaDocument, isValidCode, isValidLoinc, loincToCode, normalizeCode, toBiomarkerTests, validateLoincNameMatch } from './biomarkers.cjs';
4
4
  export { BIOMARKER_DEFAULT_UNIT, BIOMARKER_UNITS, BiomarkerUnitConfig, ConversionResult, UNIT_TO_UCUM, convertUnit, getCanonicalUnit, getDefaultUnit, getSIUnit, unitToUCUM } from './units.cjs';
5
5
  import { F as FHIRMedicationStatement, a as FHIRObservation, b as FHIRBundle, c as FHIRIdentifier } from './fhir-types-jTJayiwn.cjs';
@@ -68,11 +68,11 @@ declare function listMappedSubcategories(): readonly string[];
68
68
  /**
69
69
  * Convert medication/supplement intervention to FHIR MedicationStatement
70
70
  */
71
- declare function interventionToFHIRMedicationStatement(intervention: InterventionData, patientId: string): FHIRMedicationStatement;
71
+ declare function interventionToFHIRMedicationStatement(intervention: InterventionData, patientId: string): Addressable<FHIRMedicationStatement>;
72
72
  /**
73
73
  * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)
74
74
  */
75
- declare function interventionToFHIRObservation(intervention: InterventionData, patientId: string): FHIRObservation;
75
+ declare function interventionToFHIRObservation(intervention: InterventionData, patientId: string): Addressable<FHIRObservation>;
76
76
  /**
77
77
  * Convert all interventions to a FHIR Bundle
78
78
  */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InterventionData, U as UserProfileData } from './converter-D74wBs5k.js';
2
- export { F as Flag, G as Gender, L as LabObservationData, a as LabReportData, O as OverallStatus, l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-D74wBs5k.js';
1
+ import { I as InterventionData, A as Addressable, U as UserProfileData } from './converter-BciZxRkK.js';
2
+ export { F as Flag, G as Gender, L as LabObservationData, a as LabReportData, O as OverallStatus, l as labObservationToFHIR, b as labReportToFHIR, c as labResultToFHIRBundle, u as userProfileToFHIR } from './converter-BciZxRkK.js';
3
3
  export { BIOMARKER_DEFINITIONS, BiomarkerDefinition, BiomarkerSearchPattern, CAC_INDICATOR_CODES, DEXA_CATEGORIES, DEXA_INDICATOR_CODES, SupportedBiomarker, codeToLoinc, filterVisibleBiomarkers, findCodeByName, generateCacFullReference, generateDexaFullReference, generateFilteredLLMReference, generateLLMReference, getAllCodes, getAllDefinitions, getAllLoincCodes, getAllSearchPatterns, getBiomarkersByCategory, getBiomarkersForCategories, getDefinitionByCode, getDefinitionByLoinc, getDefinitionsBySex, getSexForCode, getVisibleDefinitions, isBiomarkerVisible, isCacDocument, isDexaDocument, isValidCode, isValidLoinc, loincToCode, normalizeCode, toBiomarkerTests, validateLoincNameMatch } from './biomarkers.js';
4
4
  export { BIOMARKER_DEFAULT_UNIT, BIOMARKER_UNITS, BiomarkerUnitConfig, ConversionResult, UNIT_TO_UCUM, convertUnit, getCanonicalUnit, getDefaultUnit, getSIUnit, unitToUCUM } from './units.js';
5
5
  import { F as FHIRMedicationStatement, a as FHIRObservation, b as FHIRBundle, c as FHIRIdentifier } from './fhir-types-jTJayiwn.js';
@@ -68,11 +68,11 @@ declare function listMappedSubcategories(): readonly string[];
68
68
  /**
69
69
  * Convert medication/supplement intervention to FHIR MedicationStatement
70
70
  */
71
- declare function interventionToFHIRMedicationStatement(intervention: InterventionData, patientId: string): FHIRMedicationStatement;
71
+ declare function interventionToFHIRMedicationStatement(intervention: InterventionData, patientId: string): Addressable<FHIRMedicationStatement>;
72
72
  /**
73
73
  * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)
74
74
  */
75
- declare function interventionToFHIRObservation(intervention: InterventionData, patientId: string): FHIRObservation;
75
+ declare function interventionToFHIRObservation(intervention: InterventionData, patientId: string): Addressable<FHIRObservation>;
76
76
  /**
77
77
  * Convert all interventions to a FHIR Bundle
78
78
  */
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import {
2
+ entryFullUrl,
2
3
  labObservationToFHIR,
3
4
  labReportToFHIR,
4
5
  labResultToFHIRBundle,
5
6
  userProfileToFHIR
6
- } from "./chunk-K3TK3OHS.js";
7
+ } from "./chunk-RZPFCPLH.js";
7
8
  import {
8
9
  MAX_FILE_SIZE,
9
10
  MAX_OBSERVATIONS,
@@ -236,19 +237,10 @@ function interventionsToFHIRBundle(interventions, userProfile) {
236
237
  const entries = interventions.map((intervention) => {
237
238
  const isMedication = intervention.type === "medication" || intervention.type === "supplement";
238
239
  const resource = isMedication ? interventionToFHIRMedicationStatement(intervention, patientId) : interventionToFHIRObservation(intervention, patientId);
239
- return {
240
- fullUrl: `urn:uuid:intervention-${intervention.interventionId}`,
241
- resource
242
- };
240
+ return { fullUrl: entryFullUrl(resource), resource };
243
241
  });
244
242
  return {
245
- entry: [
246
- {
247
- fullUrl: `urn:uuid:${patientId}`,
248
- resource: fhirPatient
249
- },
250
- ...entries
251
- ],
243
+ entry: [{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient }, ...entries],
252
244
  resourceType: "Bundle",
253
245
  type: "collection"
254
246
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/category-groups.ts","../src/intervention-converter.ts","../src/dexa-zone-data.ts","../src/screening-intervals.ts","../src/i18n.ts","../src/identifiers.ts"],"sourcesContent":["/**\n * Agrupamento de categorias clínicas em 10 grupos de alto nível.\n *\n * `BiomarkerDefinition.category` armazena 20 sub-categorias (granularidade\n * fina, ex: `tireoide`, `pancreas`). Este módulo agrupa essas\n * sub-categorias em 10 buckets clínicos amplos para apresentação no\n * site, na API pública e em material de divulgação.\n *\n * As sub-categorias permanecem como fonte da verdade nos dados; este\n * agrupamento é uma camada derivada.\n */\n\nexport type CategoryGroup =\n | 'cardiovascular'\n | 'metabolico-endocrino'\n | 'renal-eletrolitico'\n | 'hepatico-biliar'\n | 'hematologico'\n | 'imunologico'\n | 'oncologico'\n | 'nutricional-ambiental'\n | 'saude-reprodutiva'\n | 'composicao-envelhecimento';\n\nexport interface CategoryGroupInfo {\n /** Rótulo em inglês */\n en: string;\n /** Rótulo em português */\n pt: string;\n /** Slug (kebab-case, sem acento) */\n slug: CategoryGroup;\n /** Sub-categorias da fonte agrupadas neste bucket */\n subcategories: readonly string[];\n}\n\nexport const CATEGORY_GROUPS: Record<CategoryGroup, CategoryGroupInfo> = {\n cardiovascular: {\n en: 'Cardiovascular',\n pt: 'Cardiovascular',\n slug: 'cardiovascular',\n subcategories: ['coracao'],\n },\n 'composicao-envelhecimento': {\n en: 'Body Composition & Aging',\n pt: 'Composição Corporal e Envelhecimento',\n slug: 'composicao-envelhecimento',\n subcategories: ['composicao-corporal', 'densidade-ossea', 'estresse-envelhecimento'],\n },\n hematologico: {\n en: 'Hematology',\n pt: 'Hematológico',\n slug: 'hematologico',\n subcategories: ['sangue'],\n },\n 'hepatico-biliar': {\n en: 'Hepatic & Biliary',\n pt: 'Hepático e Biliar',\n slug: 'hepatico-biliar',\n subcategories: ['figado'],\n },\n imunologico: {\n en: 'Immunology',\n pt: 'Imunológico',\n slug: 'imunologico',\n subcategories: ['autoimunidade', 'regulacao-imunologica'],\n },\n 'metabolico-endocrino': {\n en: 'Metabolic & Endocrine',\n pt: 'Metabólico e Endócrino',\n slug: 'metabolico-endocrino',\n subcategories: ['metabolico', 'pancreas', 'hormonios', 'tireoide'],\n },\n 'nutricional-ambiental': {\n en: 'Nutrition & Environmental Exposure',\n pt: 'Nutricional e Exposição Ambiental',\n slug: 'nutricional-ambiental',\n subcategories: ['nutrientes', 'toxinas-ambientais'],\n },\n oncologico: {\n en: 'Oncology',\n pt: 'Oncológico',\n slug: 'oncologico',\n subcategories: ['marcadores-tumorais'],\n },\n 'renal-eletrolitico': {\n en: 'Renal & Electrolytes',\n pt: 'Renal e Eletrolítico',\n slug: 'renal-eletrolitico',\n subcategories: ['rins', 'urina', 'eletrolitos'],\n },\n 'saude-reprodutiva': {\n en: 'Reproductive Health',\n pt: 'Saúde Reprodutiva',\n slug: 'saude-reprodutiva',\n subcategories: ['saude-feminina', 'saude-masculina'],\n },\n};\n\nconst SUBCATEGORY_TO_GROUP = new Map<string, CategoryGroup>();\nfor (const group of Object.values(CATEGORY_GROUPS)) {\n for (const sub of group.subcategories) {\n SUBCATEGORY_TO_GROUP.set(sub, group.slug);\n }\n}\n\n/**\n * Resolve a sub-categoria (granular) para o grupo de alto nível (10 buckets).\n */\nexport function getCategoryGroup(subcategory: string): CategoryGroup | undefined {\n return SUBCATEGORY_TO_GROUP.get(subcategory);\n}\n\n/**\n * Lista todas as sub-categorias mapeadas em algum grupo.\n */\nexport function listMappedSubcategories(): readonly string[] {\n return [...SUBCATEGORY_TO_GROUP.keys()];\n}\n","/**\n * FHIR Intervention Converter\n *\n * Converts interventions (medication, supplement, diet, exercise, sleep)\n * to FHIR R4 MedicationStatement and Observation resources.\n */\n\nimport { userProfileToFHIR } from './converter';\nimport type { FHIRBundle, FHIRMedicationStatement, FHIRObservation } from './fhir-types';\nimport type { InterventionData, UserProfileData } from './types';\n\n/**\n * Determine MedicationStatement/Observation status based on end date\n */\nfunction interventionStatus(endDate?: string): 'active' | 'completed' {\n if (!endDate) return 'active';\n return new Date(endDate) < new Date() ? 'completed' : 'active';\n}\n\n/**\n * LOINC-like codes for lifestyle observation types\n */\nconst LIFESTYLE_CODES: Record<string, { code: string; display: string }> = {\n diet: { code: '81259-4', display: 'Diet' },\n exercise: { code: '73985-4', display: 'Exercise activity' },\n sleep: { code: '93832-4', display: 'Sleep duration' },\n};\n\n/**\n * Convert medication/supplement intervention to FHIR MedicationStatement\n */\nexport function interventionToFHIRMedicationStatement(\n intervention: InterventionData,\n patientId: string,\n): FHIRMedicationStatement {\n const statement: FHIRMedicationStatement = {\n category: {\n coding: [\n {\n code: 'patientspecified',\n display: 'Patient Specified',\n system: 'http://terminology.hl7.org/CodeSystem/medication-statement-category',\n },\n ],\n },\n dateAsserted: intervention.startDate,\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n medicationCodeableConcept: {\n text: intervention.name,\n },\n resourceType: 'MedicationStatement',\n status: interventionStatus(intervention.endDate),\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n if (intervention.notes) {\n statement.note = [{ text: intervention.notes }];\n }\n\n return statement;\n}\n\n/**\n * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)\n */\nexport function interventionToFHIRObservation(\n intervention: InterventionData,\n patientId: string,\n): FHIRObservation {\n const lifestyleCode = LIFESTYLE_CODES[intervention.type];\n\n const observation: FHIRObservation = {\n category: [\n {\n coding: [\n {\n code: 'social-history',\n display: 'Social History',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n coding: lifestyleCode\n ? [\n {\n code: lifestyleCode.code,\n display: lifestyleCode.display,\n system: 'http://loinc.org',\n },\n ]\n : [],\n text: intervention.name,\n },\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n valueString: intervention.name,\n };\n\n if (intervention.notes) {\n observation.note = [{ text: intervention.notes }];\n }\n\n return observation;\n}\n\n/**\n * Convert all interventions to a FHIR Bundle\n */\nexport function interventionsToFHIRBundle(\n interventions: InterventionData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n const fhirPatient = userProfileToFHIR(userProfile);\n\n const entries = interventions.map((intervention) => {\n const isMedication = intervention.type === 'medication' || intervention.type === 'supplement';\n const resource = isMedication\n ? interventionToFHIRMedicationStatement(intervention, patientId)\n : interventionToFHIRObservation(intervention, patientId);\n\n return {\n fullUrl: `urn:uuid:intervention-${intervention.interventionId}`,\n resource,\n };\n });\n\n return {\n entry: [\n {\n fullUrl: `urn:uuid:${patientId}`,\n resource: fhirPatient,\n },\n ...entries,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n","/**\n * Structured zone data for DEXA body composition and bone density charts.\n *\n * Body fat zones derived from Gallagher et al. Am J Clin Nutr 2000;72:694-701 (PMID: 10966886)\n * and ACSM Guidelines for Exercise Testing, 11th Ed (2021).\n *\n * T-Score zones from WHO criteria (Kanis JA, Osteoporos Int, PMID: 7696835).\n */\n\nexport interface BodyFatZone {\n ageMax: number;\n ageMin: number;\n color: string;\n fatPctMax: number;\n fatPctMin: number;\n label: string;\n sex: 'F' | 'M';\n}\n\ninterface AgeBracket {\n ageMax: number;\n ageMin: number;\n label: string;\n}\n\nconst AGE_BRACKETS: AgeBracket[] = [\n { ageMax: 25, ageMin: 18, label: '18-25' },\n { ageMax: 35, ageMin: 26, label: '26-35' },\n { ageMax: 45, ageMin: 36, label: '36-45' },\n { ageMax: 55, ageMin: 46, label: '46-55' },\n { ageMax: 99, ageMin: 56, label: '56+' },\n];\n\n// Zone boundaries per age bracket for men: [essential, athletic, fitness, average, obese]\n// Each value is the upper bound of the zone\nconst MALE_ZONES: number[][] = [\n [5, 10, 20, 25, 40],\n [5, 11, 21, 26, 40],\n [5, 12, 22, 27, 40],\n [5, 13, 23, 28, 40],\n [5, 14, 24, 29, 40],\n];\n\nconst FEMALE_ZONES: number[][] = [\n [13, 18, 28, 32, 45],\n [13, 18, 29, 33, 45],\n [13, 19, 30, 34, 45],\n [13, 20, 31, 35, 45],\n [13, 20, 32, 36, 45],\n];\n\ninterface ZoneDefinition {\n color: string;\n label: string;\n}\n\nconst ZONE_DEFS: ZoneDefinition[] = [\n { color: '#3b82f6', label: 'Essencial' },\n { color: '#06b6d4', label: 'Atlético' },\n { color: '#22c55e', label: 'Fitness' },\n { color: '#eab308', label: 'Média' },\n { color: '#ef4444', label: 'Obeso' },\n];\n\nfunction buildZones(sex: 'F' | 'M', zoneData: number[][]): BodyFatZone[] {\n const zones: BodyFatZone[] = [];\n for (let i = 0; i < AGE_BRACKETS.length; i++) {\n const bracket = AGE_BRACKETS[i]!;\n const b = zoneData[i]!;\n zones.push({\n ...bracket,\n color: ZONE_DEFS[0]!.color,\n fatPctMax: b[0]!,\n fatPctMin: 0,\n label: ZONE_DEFS[0]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[1]!.color,\n fatPctMax: b[1]!,\n fatPctMin: b[0]!,\n label: ZONE_DEFS[1]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[2]!.color,\n fatPctMax: b[2]!,\n fatPctMin: b[1]!,\n label: ZONE_DEFS[2]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[3]!.color,\n fatPctMax: b[3]!,\n fatPctMin: b[2]!,\n label: ZONE_DEFS[3]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[4]!.color,\n fatPctMax: b[4]!,\n fatPctMin: b[3]!,\n label: ZONE_DEFS[4]!.label,\n sex,\n });\n }\n return zones;\n}\n\nexport const BODY_FAT_ZONES: BodyFatZone[] = [\n ...buildZones('M', MALE_ZONES),\n ...buildZones('F', FEMALE_ZONES),\n];\n\nexport { AGE_BRACKETS, ZONE_DEFS };\n\nexport interface TScoreZone {\n color: string;\n label: string;\n max: number;\n min: number;\n}\n\nexport const T_SCORE_ZONES: TScoreZone[] = [\n { color: '#22c55e', label: 'Normal', max: 4, min: -1.0 },\n { color: '#eab308', label: 'Osteopenia', max: -1.0, min: -2.5 },\n { color: '#ef4444', label: 'Osteoporose', max: -2.5, min: -5 },\n];\n","/**\n * Screening Intervals Configuration\n *\n * Defines recommended screening intervals for different biomarker categories\n * based on clinical guidelines and best practices.\n */\n\n/**\n * Screening interval in months\n */\nexport type ScreeningIntervalMonths = 3 | 6 | 12;\n\n/**\n * Biomarker category with its recommended screening interval\n */\nexport interface CategoryScreeningInterval {\n category: string;\n intervalMonths: ScreeningIntervalMonths;\n nameEn: string;\n namePt: string;\n}\n\n/**\n * Screening interval configuration for each category\n *\n * Categories are grouped by their recommended screening intervals:\n * - 3 months: Body composition and bone density (frequently changing metrics)\n * - 6 months: Metabolic panel and nutrients (moderate change rate)\n * - 12 months: Standard blood panels (stable long-term markers)\n */\nexport const CATEGORY_SCREENING_INTERVALS: CategoryScreeningInterval[] = [\n // 3-month intervals - Body composition (frequently changing)\n {\n category: 'composicao-corporal',\n intervalMonths: 3,\n nameEn: 'Body Composition',\n namePt: 'Composição Corporal',\n },\n {\n category: 'densidade-ossea',\n intervalMonths: 3,\n nameEn: 'Bone Density',\n namePt: 'Densidade Óssea',\n },\n\n // 6-month intervals - Metabolic and nutrients\n {\n category: 'metabolico',\n intervalMonths: 6,\n nameEn: 'Metabolic Panel',\n namePt: 'Painel Metabólico',\n },\n {\n category: 'nutrientes',\n intervalMonths: 6,\n nameEn: 'Nutrients',\n namePt: 'Nutrientes',\n },\n {\n category: 'pancreas',\n intervalMonths: 6,\n nameEn: 'Pancreas',\n namePt: 'Pâncreas',\n },\n\n // 12-month intervals - Standard blood panels\n {\n category: 'coracao',\n intervalMonths: 12,\n nameEn: 'Heart Health',\n namePt: 'Saúde Cardiovascular',\n },\n {\n category: 'tireoide',\n intervalMonths: 12,\n nameEn: 'Thyroid',\n namePt: 'Tireoide',\n },\n {\n category: 'sangue',\n intervalMonths: 12,\n nameEn: 'Blood Count',\n namePt: 'Hemograma',\n },\n {\n category: 'figado',\n intervalMonths: 12,\n nameEn: 'Liver Function',\n namePt: 'Função Hepática',\n },\n {\n category: 'rins',\n intervalMonths: 12,\n nameEn: 'Kidney Function',\n namePt: 'Função Renal',\n },\n {\n category: 'saude-feminina',\n intervalMonths: 12,\n nameEn: \"Women's Health\",\n namePt: 'Saúde Feminina',\n },\n {\n category: 'saude-masculina',\n intervalMonths: 12,\n nameEn: \"Men's Health\",\n namePt: 'Saúde Masculina',\n },\n {\n category: 'eletrolitos',\n intervalMonths: 12,\n nameEn: 'Electrolytes',\n namePt: 'Eletrólitos',\n },\n {\n category: 'estresse-envelhecimento',\n intervalMonths: 12,\n nameEn: 'Stress & Aging',\n namePt: 'Estresse e Envelhecimento',\n },\n {\n category: 'autoimunidade',\n intervalMonths: 12,\n nameEn: 'Autoimmunity',\n namePt: 'Autoimunidade',\n },\n {\n category: 'regulacao-imunologica',\n intervalMonths: 12,\n nameEn: 'Immune Regulation',\n namePt: 'Regulação Imunológica',\n },\n {\n category: 'toxinas-ambientais',\n intervalMonths: 12,\n nameEn: 'Environmental Toxins',\n namePt: 'Toxinas Ambientais',\n },\n {\n category: 'urina',\n intervalMonths: 12,\n nameEn: 'Urinalysis',\n namePt: 'Urina',\n },\n];\n\n/**\n * Get screening interval for a category\n */\nexport const getScreeningInterval = (category: string): CategoryScreeningInterval | undefined => {\n return CATEGORY_SCREENING_INTERVALS.find((c) => c.category === category);\n};\n\n/**\n * Get all categories with a specific interval\n */\nexport const getCategoriesByInterval = (\n intervalMonths: ScreeningIntervalMonths,\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((c) => c.intervalMonths === intervalMonths);\n};\n\n/**\n * Calculate next screening date based on last test date and category\n */\nexport const calculateNextScreeningDate = (lastTestDate: Date, category: string): Date | null => {\n const interval = getScreeningInterval(category);\n if (!interval) return null;\n\n const nextDate = new Date(lastTestDate);\n nextDate.setMonth(nextDate.getMonth() + interval.intervalMonths);\n return nextDate;\n};\n\n/**\n * Check if a category is due for screening\n */\nexport const isScreeningDue = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): boolean => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return false;\n return referenceDate >= nextDate;\n};\n\n/**\n * Get categories that are due for screening based on last test dates\n */\nexport const getDueCategories = (\n lastTestDates: Record<string, Date>,\n referenceDate: Date = new Date(),\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((interval) => {\n const lastDate = lastTestDates[interval.category];\n if (!lastDate) return true; // Never tested = due\n return isScreeningDue(lastDate, interval.category, referenceDate);\n });\n};\n\n/**\n * Get days until next screening for a category\n */\nexport const getDaysUntilScreening = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): number | null => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return null;\n\n const diffTime = nextDate.getTime() - referenceDate.getTime();\n return Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n};\n","/**\n * Portuguese pluralization utility using native Intl.PluralRules\n * Provides automatic pluralization for common words used in the app\n */\n\nconst pluralRules = new Intl.PluralRules('pt-BR');\n\n/**\n * Dictionary of Portuguese words with their plural forms\n * Key is the singular form, value is the plural form\n */\nconst dictionary: Record<string, string> = {\n // Common nouns\n arquivo: 'arquivos',\n biomarcador: 'biomarcadores',\n // Past participles (masculine)\n cadastrado: 'cadastrados',\n // Past participles (feminine)\n concluída: 'concluídas',\n confirmado: 'confirmados',\n convertido: 'convertidos',\n convidado: 'convidados',\n convite: 'convites',\n disponível: 'disponíveis',\n documento: 'documentos',\n enviado: 'enviados',\n exame: 'exames',\n excluída: 'excluídas',\n\n excluído: 'excluídos',\n // Verbs (3rd person)\n falhou: 'falharam',\n falta: 'faltam',\n ignorado: 'ignorados',\n item: 'itens',\n outro: 'outros',\n página: 'páginas',\n pendente: 'pendentes',\n registro: 'registros',\n removido: 'removidos',\n\n resultado: 'resultados',\n revisão: 'revisões',\n\n revogado: 'revogados',\n usuário: 'usuários',\n};\n\n/**\n * Get the plural form of a word from the dictionary\n * Falls back to adding 's' if word is not in dictionary\n */\nconst getPluralForm = (singular: string): string => {\n return dictionary[singular] ?? `${singular}s`;\n};\n\n/**\n * Returns the correct singular or plural form based on count\n * Uses Intl.PluralRules for proper locale-aware pluralization\n *\n * @example\n * plural(1, 'usuário') // 'usuário'\n * plural(3, 'usuário') // 'usuários'\n * plural(0, 'registro') // 'registros'\n */\nexport const plural = (count: number, word: string): string => {\n const rule = pluralRules.select(count);\n return rule === 'one' ? word : getPluralForm(word);\n};\n\n/**\n * Returns count with the correct singular or plural form\n *\n * @example\n * pluralCount(1, 'usuário') // '1 usuário'\n * pluralCount(3, 'usuário') // '3 usuários'\n */\nexport const pluralCount = (count: number, word: string): string => {\n return `${count} ${plural(count, word)}`;\n};\n\n/**\n * Returns the correct form for compound phrases (noun + adjective)\n * Both words are pluralized together\n *\n * @example\n * pluralPhrase(1, 'usuário', 'cadastrado') // 'usuário cadastrado'\n * pluralPhrase(3, 'usuário', 'cadastrado') // 'usuários cadastrados'\n * pluralPhrase(2, 'revisão', 'excluída') // 'revisões excluídas'\n */\nexport const pluralPhrase = (count: number, noun: string, adjective: string): string => {\n const rule = pluralRules.select(count);\n if (rule === 'one') {\n return `${noun} ${adjective}`;\n }\n return `${getPluralForm(noun)} ${getPluralForm(adjective)}`;\n};\n\n/**\n * Returns count with the correct compound phrase form\n *\n * @example\n * pluralPhraseCount(1, 'usuário', 'cadastrado') // '1 usuário cadastrado'\n * pluralPhraseCount(3, 'convite', 'enviado') // '3 convites enviados'\n */\nexport const pluralPhraseCount = (count: number, noun: string, adjective: string): string => {\n return `${count} ${pluralPhrase(count, noun, adjective)}`;\n};\n","/**\n * Helpers para identificadores brasileiros — CPF e CNS\n *\n * Validação, formatação e conversão para FHIR Identifier.\n * Algoritmos de validação baseados nas especificações oficiais:\n * - CPF: Receita Federal (mod-11, dois dígitos verificadores)\n * - CNS: Ministério da Saúde (mod-11 para definitivos, soma ponderada para provisórios)\n */\n\nimport type { FHIRIdentifier } from './fhir-types';\n\nconst CPF_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf';\nconst CNS_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns';\n\n/**\n * Remove caracteres não-numéricos de uma string.\n */\nfunction digitsOnly(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/**\n * Valida um CPF brasileiro usando algoritmo mod-11.\n *\n * @param cpf — CPF com ou sem formatação (ex: \"123.456.789-09\" ou \"12345678909\")\n * @returns true se o CPF é estruturalmente válido\n */\nexport function validateCPF(cpf: string): boolean {\n const digits = digitsOnly(cpf);\n\n if (digits.length !== 11) return false;\n\n // Rejeitar sequências de dígitos iguais (ex: 111.111.111-11)\n if (/^(\\d)\\1{10}$/.test(digits)) return false;\n\n // Primeiro dígito verificador\n let sum = 0;\n for (let i = 0; i < 9; i++) {\n sum += Number(digits[i]) * (10 - i);\n }\n let remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[9])) return false;\n\n // Segundo dígito verificador\n sum = 0;\n for (let i = 0; i < 10; i++) {\n sum += Number(digits[i]) * (11 - i);\n }\n remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[10])) return false;\n\n return true;\n}\n\n/**\n * Valida um CNS (Cartão Nacional de Saúde) brasileiro.\n *\n * CNS definitivos começam com 1 ou 2 (mod-11).\n * CNS provisórios começam com 7, 8 ou 9 (soma ponderada mod-11 = 0).\n *\n * @param cns — CNS com 15 dígitos\n * @returns true se o CNS é estruturalmente válido\n */\nexport function validateCNS(cns: string): boolean {\n const digits = digitsOnly(cns);\n\n if (digits.length !== 15) return false;\n\n const firstDigit = digits[0]!;\n\n // CNS deve começar com 1, 2 (definitivo) ou 7, 8, 9 (provisório)\n if (!['1', '2', '7', '8', '9'].includes(firstDigit)) return false;\n\n // Ambos os tipos usam soma ponderada mod-11 = 0\n let sum = 0;\n for (let i = 0; i < 15; i++) {\n sum += Number(digits[i]) * (15 - i);\n }\n return sum % 11 === 0;\n}\n\n/**\n * Formata um CPF como XXX.XXX.XXX-XX.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns CPF formatado ou a string original se inválido\n */\nexport function formatCPF(cpf: string): string {\n const digits = digitsOnly(cpf);\n if (digits.length !== 11) return cpf;\n return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;\n}\n\n/**\n * Formata um CNS como XXX XXXX XXXX XXXX.\n *\n * @param cns — CNS com 15 dígitos\n * @returns CNS formatado ou a string original se inválido\n */\nexport function formatCNS(cns: string): string {\n const digits = digitsOnly(cns);\n if (digits.length !== 15) return cns;\n return `${digits.slice(0, 3)} ${digits.slice(3, 7)} ${digits.slice(7, 11)} ${digits.slice(11)}`;\n}\n\n/**\n * Converte um CPF para um FHIR Identifier.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns FHIR Identifier com sistema RNDS para CPF\n * @throws Error se o CPF for inválido\n */\nexport function cpfToFHIRIdentifier(cpf: string): FHIRIdentifier {\n if (!validateCPF(cpf)) {\n throw new Error('CPF inválido');\n }\n return {\n system: CPF_SYSTEM,\n use: 'official',\n value: digitsOnly(cpf),\n };\n}\n\n/**\n * Converte um CNS para um FHIR Identifier.\n *\n * @param cns — CNS com 15 dígitos\n * @returns FHIR Identifier com sistema RNDS para CNS\n * @throws Error se o CNS for inválido\n */\nexport function cnsToFHIRIdentifier(cns: string): FHIRIdentifier {\n if (!validateCNS(cns)) {\n throw new Error('CNS inválido');\n }\n return {\n system: CNS_SYSTEM,\n use: 'official',\n value: digitsOnly(cns),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCO,IAAM,kBAA4D;AAAA,EACvE,gBAAgB;AAAA,IACd,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,SAAS;AAAA,EAC3B;AAAA,EACA,6BAA6B;AAAA,IAC3B,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,uBAAuB,mBAAmB,yBAAyB;AAAA,EACrF;AAAA,EACA,cAAc;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,QAAQ;AAAA,EAC1B;AAAA,EACA,mBAAmB;AAAA,IACjB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,QAAQ;AAAA,EAC1B;AAAA,EACA,aAAa;AAAA,IACX,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,iBAAiB,uBAAuB;AAAA,EAC1D;AAAA,EACA,wBAAwB;AAAA,IACtB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,cAAc,YAAY,aAAa,UAAU;AAAA,EACnE;AAAA,EACA,yBAAyB;AAAA,IACvB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,cAAc,oBAAoB;AAAA,EACpD;AAAA,EACA,YAAY;AAAA,IACV,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,qBAAqB;AAAA,EACvC;AAAA,EACA,sBAAsB;AAAA,IACpB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,QAAQ,SAAS,aAAa;AAAA,EAChD;AAAA,EACA,qBAAqB;AAAA,IACnB,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe,CAAC,kBAAkB,iBAAiB;AAAA,EACrD;AACF;AAEA,IAAM,uBAAuB,oBAAI,IAA2B;AAC5D,WAAW,SAAS,OAAO,OAAO,eAAe,GAAG;AAClD,aAAW,OAAO,MAAM,eAAe;AACrC,yBAAqB,IAAI,KAAK,MAAM,IAAI;AAAA,EAC1C;AACF;AAKO,SAAS,iBAAiB,aAAgD;AAC/E,SAAO,qBAAqB,IAAI,WAAW;AAC7C;AAKO,SAAS,0BAA6C;AAC3D,SAAO,CAAC,GAAG,qBAAqB,KAAK,CAAC;AACxC;;;ACvGA,SAAS,mBAAmB,SAA0C;AACpE,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,IAAI,KAAK,OAAO,IAAI,oBAAI,KAAK,IAAI,cAAc;AACxD;AAKA,IAAM,kBAAqE;AAAA,EACzE,MAAM,EAAE,MAAM,WAAW,SAAS,OAAO;AAAA,EACzC,UAAU,EAAE,MAAM,WAAW,SAAS,oBAAoB;AAAA,EAC1D,OAAO,EAAE,MAAM,WAAW,SAAS,iBAAiB;AACtD;AAKO,SAAS,sCACd,cACA,WACyB;AACzB,QAAM,YAAqC;AAAA,IACzC,UAAU;AAAA,MACR,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,cAAc,aAAa;AAAA,IAC3B,iBAAiB;AAAA,MACf,KAAK,aAAa;AAAA,MAClB,OAAO,aAAa;AAAA,IACtB;AAAA,IACA,IAAI,gBAAgB,aAAa,cAAc;AAAA,IAC/C,2BAA2B;AAAA,MACzB,MAAM,aAAa;AAAA,IACrB;AAAA,IACA,cAAc;AAAA,IACd,QAAQ,mBAAmB,aAAa,OAAO;AAAA,IAC/C,SAAS;AAAA,MACP,WAAW,WAAW,SAAS;AAAA,IACjC;AAAA,EACF;AAEA,MAAI,aAAa,OAAO;AACtB,cAAU,OAAO,CAAC,EAAE,MAAM,aAAa,MAAM,CAAC;AAAA,EAChD;AAEA,SAAO;AACT;AAKO,SAAS,8BACd,cACA,WACiB;AACjB,QAAM,gBAAgB,gBAAgB,aAAa,IAAI;AAEvD,QAAM,cAA+B;AAAA,IACnC,UAAU;AAAA,MACR;AAAA,QACE,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,SAAS;AAAA,YACT,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,gBACJ;AAAA,QACE;AAAA,UACE,MAAM,cAAc;AAAA,UACpB,SAAS,cAAc;AAAA,UACvB,QAAQ;AAAA,QACV;AAAA,MACF,IACA,CAAC;AAAA,MACL,MAAM,aAAa;AAAA,IACrB;AAAA,IACA,iBAAiB;AAAA,MACf,KAAK,aAAa;AAAA,MAClB,OAAO,aAAa;AAAA,IACtB;AAAA,IACA,IAAI,gBAAgB,aAAa,cAAc;AAAA,IAC/C,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,WAAW,WAAW,SAAS;AAAA,IACjC;AAAA,IACA,aAAa,aAAa;AAAA,EAC5B;AAEA,MAAI,aAAa,OAAO;AACtB,gBAAY,OAAO,CAAC,EAAE,MAAM,aAAa,MAAM,CAAC;AAAA,EAClD;AAEA,SAAO;AACT;AAKO,SAAS,0BACd,eACA,aACY;AACZ,QAAM,YAAY,YAAY;AAC9B,QAAM,cAAc,kBAAkB,WAAW;AAEjD,QAAM,UAAU,cAAc,IAAI,CAAC,iBAAiB;AAClD,UAAM,eAAe,aAAa,SAAS,gBAAgB,aAAa,SAAS;AACjF,UAAM,WAAW,eACb,sCAAsC,cAAc,SAAS,IAC7D,8BAA8B,cAAc,SAAS;AAEzD,WAAO;AAAA,MACL,SAAS,yBAAyB,aAAa,cAAc;AAAA,MAC7D;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,QACE,SAAS,YAAY,SAAS;AAAA,QAC9B,UAAU;AAAA,MACZ;AAAA,MACA,GAAG;AAAA,IACL;AAAA,IACA,cAAc;AAAA,IACd,MAAM;AAAA,EACR;AACF;;;ACjIA,IAAM,eAA6B;AAAA,EACjC,EAAE,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ;AAAA,EACzC,EAAE,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ;AAAA,EACzC,EAAE,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ;AAAA,EACzC,EAAE,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ;AAAA,EACzC,EAAE,QAAQ,IAAI,QAAQ,IAAI,OAAO,MAAM;AACzC;AAIA,IAAM,aAAyB;AAAA,EAC7B,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,EAClB,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,EAClB,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,EAClB,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;AAAA,EAClB,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;AACpB;AAEA,IAAM,eAA2B;AAAA,EAC/B,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AAAA,EACnB,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AAAA,EACnB,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AAAA,EACnB,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AAAA,EACnB,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE;AACrB;AAOA,IAAM,YAA8B;AAAA,EAClC,EAAE,OAAO,WAAW,OAAO,YAAY;AAAA,EACvC,EAAE,OAAO,WAAW,OAAO,cAAW;AAAA,EACtC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,EACrC,EAAE,OAAO,WAAW,OAAO,WAAQ;AAAA,EACnC,EAAE,OAAO,WAAW,OAAO,QAAQ;AACrC;AAEA,SAAS,WAAW,KAAgB,UAAqC;AACvE,QAAM,QAAuB,CAAC;AAC9B,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,UAAU,aAAa,CAAC;AAC9B,UAAM,IAAI,SAAS,CAAC;AACpB,UAAM,KAAK;AAAA,MACT,GAAG;AAAA,MACH,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB,WAAW,EAAE,CAAC;AAAA,MACd,WAAW;AAAA,MACX,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB;AAAA,IACF,CAAC;AACD,UAAM,KAAK;AAAA,MACT,GAAG;AAAA,MACH,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB,WAAW,EAAE,CAAC;AAAA,MACd,WAAW,EAAE,CAAC;AAAA,MACd,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB;AAAA,IACF,CAAC;AACD,UAAM,KAAK;AAAA,MACT,GAAG;AAAA,MACH,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB,WAAW,EAAE,CAAC;AAAA,MACd,WAAW,EAAE,CAAC;AAAA,MACd,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB;AAAA,IACF,CAAC;AACD,UAAM,KAAK;AAAA,MACT,GAAG;AAAA,MACH,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB,WAAW,EAAE,CAAC;AAAA,MACd,WAAW,EAAE,CAAC;AAAA,MACd,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB;AAAA,IACF,CAAC;AACD,UAAM,KAAK;AAAA,MACT,GAAG;AAAA,MACH,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB,WAAW,EAAE,CAAC;AAAA,MACd,WAAW,EAAE,CAAC;AAAA,MACd,OAAO,UAAU,CAAC,EAAG;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,IAAM,iBAAgC;AAAA,EAC3C,GAAG,WAAW,KAAK,UAAU;AAAA,EAC7B,GAAG,WAAW,KAAK,YAAY;AACjC;AAWO,IAAM,gBAA8B;AAAA,EACzC,EAAE,OAAO,WAAW,OAAO,UAAU,KAAK,GAAG,KAAK,GAAK;AAAA,EACvD,EAAE,OAAO,WAAW,OAAO,cAAc,KAAK,IAAM,KAAK,KAAK;AAAA,EAC9D,EAAE,OAAO,WAAW,OAAO,eAAe,KAAK,MAAM,KAAK,GAAG;AAC/D;;;ACrGO,IAAM,+BAA4D;AAAA;AAAA,EAEvE;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA;AAAA,EAGA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA;AAAA,EAGA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAKO,IAAM,uBAAuB,CAAC,aAA4D;AAC/F,SAAO,6BAA6B,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AACzE;AAKO,IAAM,0BAA0B,CACrC,mBACgC;AAChC,SAAO,6BAA6B,OAAO,CAAC,MAAM,EAAE,mBAAmB,cAAc;AACvF;AAKO,IAAM,6BAA6B,CAAC,cAAoB,aAAkC;AAC/F,QAAM,WAAW,qBAAqB,QAAQ;AAC9C,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAW,IAAI,KAAK,YAAY;AACtC,WAAS,SAAS,SAAS,SAAS,IAAI,SAAS,cAAc;AAC/D,SAAO;AACT;AAKO,IAAM,iBAAiB,CAC5B,cACA,UACA,gBAAsB,oBAAI,KAAK,MACnB;AACZ,QAAM,WAAW,2BAA2B,cAAc,QAAQ;AAClE,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,iBAAiB;AAC1B;AAKO,IAAM,mBAAmB,CAC9B,eACA,gBAAsB,oBAAI,KAAK,MACC;AAChC,SAAO,6BAA6B,OAAO,CAAC,aAAa;AACvD,UAAM,WAAW,cAAc,SAAS,QAAQ;AAChD,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,eAAe,UAAU,SAAS,UAAU,aAAa;AAAA,EAClE,CAAC;AACH;AAKO,IAAM,wBAAwB,CACnC,cACA,UACA,gBAAsB,oBAAI,KAAK,MACb;AAClB,QAAM,WAAW,2BAA2B,cAAc,QAAQ;AAClE,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAW,SAAS,QAAQ,IAAI,cAAc,QAAQ;AAC5D,SAAO,KAAK,KAAK,YAAY,MAAO,KAAK,KAAK,GAAG;AACnD;;;ACjNA,IAAM,cAAc,IAAI,KAAK,YAAY,OAAO;AAMhD,IAAM,aAAqC;AAAA;AAAA,EAEzC,SAAS;AAAA,EACT,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA;AAAA,EAEZ,gBAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,SAAS;AAAA,EACT,iBAAY;AAAA,EACZ,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAU;AAAA,EAEV,eAAU;AAAA;AAAA,EAEV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EAEV,WAAW;AAAA,EACX,cAAS;AAAA,EAET,UAAU;AAAA,EACV,cAAS;AACX;AAMA,IAAM,gBAAgB,CAAC,aAA6B;AAClD,SAAO,WAAW,QAAQ,KAAK,GAAG,QAAQ;AAC5C;AAWO,IAAM,SAAS,CAAC,OAAe,SAAyB;AAC7D,QAAM,OAAO,YAAY,OAAO,KAAK;AACrC,SAAO,SAAS,QAAQ,OAAO,cAAc,IAAI;AACnD;AASO,IAAM,cAAc,CAAC,OAAe,SAAyB;AAClE,SAAO,GAAG,KAAK,IAAI,OAAO,OAAO,IAAI,CAAC;AACxC;AAWO,IAAM,eAAe,CAAC,OAAe,MAAc,cAA8B;AACtF,QAAM,OAAO,YAAY,OAAO,KAAK;AACrC,MAAI,SAAS,OAAO;AAClB,WAAO,GAAG,IAAI,IAAI,SAAS;AAAA,EAC7B;AACA,SAAO,GAAG,cAAc,IAAI,CAAC,IAAI,cAAc,SAAS,CAAC;AAC3D;AASO,IAAM,oBAAoB,CAAC,OAAe,MAAc,cAA8B;AAC3F,SAAO,GAAG,KAAK,IAAI,aAAa,OAAO,MAAM,SAAS,CAAC;AACzD;;;AChGA,IAAM,aAAa;AACnB,IAAM,aAAa;AAKnB,SAAS,WAAW,OAAuB;AACzC,SAAO,MAAM,QAAQ,OAAO,EAAE;AAChC;AAQO,SAAS,YAAY,KAAsB;AAChD,QAAM,SAAS,WAAW,GAAG;AAE7B,MAAI,OAAO,WAAW,GAAI,QAAO;AAGjC,MAAI,eAAe,KAAK,MAAM,EAAG,QAAO;AAGxC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;AAAA,EACnC;AACA,MAAI,YAAa,MAAM,KAAM;AAC7B,MAAI,cAAc,GAAI,aAAY;AAClC,MAAI,cAAc,OAAO,OAAO,CAAC,CAAC,EAAG,QAAO;AAG5C,QAAM;AACN,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,WAAO,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;AAAA,EACnC;AACA,cAAa,MAAM,KAAM;AACzB,MAAI,cAAc,GAAI,aAAY;AAClC,MAAI,cAAc,OAAO,OAAO,EAAE,CAAC,EAAG,QAAO;AAE7C,SAAO;AACT;AAWO,SAAS,YAAY,KAAsB;AAChD,QAAM,SAAS,WAAW,GAAG;AAE7B,MAAI,OAAO,WAAW,GAAI,QAAO;AAEjC,QAAM,aAAa,OAAO,CAAC;AAG3B,MAAI,CAAC,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,EAAE,SAAS,UAAU,EAAG,QAAO;AAG5D,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,WAAO,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;AAAA,EACnC;AACA,SAAO,MAAM,OAAO;AACtB;AAQO,SAAS,UAAU,KAAqB;AAC7C,QAAM,SAAS,WAAW,GAAG;AAC7B,MAAI,OAAO,WAAW,GAAI,QAAO;AACjC,SAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAC7F;AAQO,SAAS,UAAU,KAAqB;AAC7C,QAAM,SAAS,WAAW,GAAG;AAC7B,MAAI,OAAO,WAAW,GAAI,QAAO;AACjC,SAAO,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,EAAE,CAAC,IAAI,OAAO,MAAM,EAAE,CAAC;AAC/F;AASO,SAAS,oBAAoB,KAA6B;AAC/D,MAAI,CAAC,YAAY,GAAG,GAAG;AACrB,UAAM,IAAI,MAAM,iBAAc;AAAA,EAChC;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,OAAO,WAAW,GAAG;AAAA,EACvB;AACF;AASO,SAAS,oBAAoB,KAA6B;AAC/D,MAAI,CAAC,YAAY,GAAG,GAAG;AACrB,UAAM,IAAI,MAAM,iBAAc;AAAA,EAChC;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,OAAO,WAAW,GAAG;AAAA,EACvB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/category-groups.ts","../src/intervention-converter.ts","../src/dexa-zone-data.ts","../src/screening-intervals.ts","../src/i18n.ts","../src/identifiers.ts"],"sourcesContent":["/**\n * Agrupamento de categorias clínicas em 10 grupos de alto nível.\n *\n * `BiomarkerDefinition.category` armazena 20 sub-categorias (granularidade\n * fina, ex: `tireoide`, `pancreas`). Este módulo agrupa essas\n * sub-categorias em 10 buckets clínicos amplos para apresentação no\n * site, na API pública e em material de divulgação.\n *\n * As sub-categorias permanecem como fonte da verdade nos dados; este\n * agrupamento é uma camada derivada.\n */\n\nexport type CategoryGroup =\n | 'cardiovascular'\n | 'metabolico-endocrino'\n | 'renal-eletrolitico'\n | 'hepatico-biliar'\n | 'hematologico'\n | 'imunologico'\n | 'oncologico'\n | 'nutricional-ambiental'\n | 'saude-reprodutiva'\n | 'composicao-envelhecimento';\n\nexport interface CategoryGroupInfo {\n /** Rótulo em inglês */\n en: string;\n /** Rótulo em português */\n pt: string;\n /** Slug (kebab-case, sem acento) */\n slug: CategoryGroup;\n /** Sub-categorias da fonte agrupadas neste bucket */\n subcategories: readonly string[];\n}\n\nexport const CATEGORY_GROUPS: Record<CategoryGroup, CategoryGroupInfo> = {\n cardiovascular: {\n en: 'Cardiovascular',\n pt: 'Cardiovascular',\n slug: 'cardiovascular',\n subcategories: ['coracao'],\n },\n 'composicao-envelhecimento': {\n en: 'Body Composition & Aging',\n pt: 'Composição Corporal e Envelhecimento',\n slug: 'composicao-envelhecimento',\n subcategories: ['composicao-corporal', 'densidade-ossea', 'estresse-envelhecimento'],\n },\n hematologico: {\n en: 'Hematology',\n pt: 'Hematológico',\n slug: 'hematologico',\n subcategories: ['sangue'],\n },\n 'hepatico-biliar': {\n en: 'Hepatic & Biliary',\n pt: 'Hepático e Biliar',\n slug: 'hepatico-biliar',\n subcategories: ['figado'],\n },\n imunologico: {\n en: 'Immunology',\n pt: 'Imunológico',\n slug: 'imunologico',\n subcategories: ['autoimunidade', 'regulacao-imunologica'],\n },\n 'metabolico-endocrino': {\n en: 'Metabolic & Endocrine',\n pt: 'Metabólico e Endócrino',\n slug: 'metabolico-endocrino',\n subcategories: ['metabolico', 'pancreas', 'hormonios', 'tireoide'],\n },\n 'nutricional-ambiental': {\n en: 'Nutrition & Environmental Exposure',\n pt: 'Nutricional e Exposição Ambiental',\n slug: 'nutricional-ambiental',\n subcategories: ['nutrientes', 'toxinas-ambientais'],\n },\n oncologico: {\n en: 'Oncology',\n pt: 'Oncológico',\n slug: 'oncologico',\n subcategories: ['marcadores-tumorais'],\n },\n 'renal-eletrolitico': {\n en: 'Renal & Electrolytes',\n pt: 'Renal e Eletrolítico',\n slug: 'renal-eletrolitico',\n subcategories: ['rins', 'urina', 'eletrolitos'],\n },\n 'saude-reprodutiva': {\n en: 'Reproductive Health',\n pt: 'Saúde Reprodutiva',\n slug: 'saude-reprodutiva',\n subcategories: ['saude-feminina', 'saude-masculina'],\n },\n};\n\nconst SUBCATEGORY_TO_GROUP = new Map<string, CategoryGroup>();\nfor (const group of Object.values(CATEGORY_GROUPS)) {\n for (const sub of group.subcategories) {\n SUBCATEGORY_TO_GROUP.set(sub, group.slug);\n }\n}\n\n/**\n * Resolve a sub-categoria (granular) para o grupo de alto nível (10 buckets).\n */\nexport function getCategoryGroup(subcategory: string): CategoryGroup | undefined {\n return SUBCATEGORY_TO_GROUP.get(subcategory);\n}\n\n/**\n * Lista todas as sub-categorias mapeadas em algum grupo.\n */\nexport function listMappedSubcategories(): readonly string[] {\n return [...SUBCATEGORY_TO_GROUP.keys()];\n}\n","/**\n * FHIR Intervention Converter\n *\n * Converts interventions (medication, supplement, diet, exercise, sleep)\n * to FHIR R4 MedicationStatement and Observation resources.\n */\n\nimport { type Addressable, entryFullUrl } from './bundle-urls';\nimport { userProfileToFHIR } from './converter';\nimport type { FHIRBundle, FHIRMedicationStatement, FHIRObservation } from './fhir-types';\nimport type { InterventionData, UserProfileData } from './types';\n\n/**\n * Determine MedicationStatement/Observation status based on end date\n */\nfunction interventionStatus(endDate?: string): 'active' | 'completed' {\n if (!endDate) return 'active';\n return new Date(endDate) < new Date() ? 'completed' : 'active';\n}\n\n/**\n * LOINC-like codes for lifestyle observation types\n */\nconst LIFESTYLE_CODES: Record<string, { code: string; display: string }> = {\n diet: { code: '81259-4', display: 'Diet' },\n exercise: { code: '73985-4', display: 'Exercise activity' },\n sleep: { code: '93832-4', display: 'Sleep duration' },\n};\n\n/**\n * Convert medication/supplement intervention to FHIR MedicationStatement\n */\nexport function interventionToFHIRMedicationStatement(\n intervention: InterventionData,\n patientId: string,\n): Addressable<FHIRMedicationStatement> {\n const statement: Addressable<FHIRMedicationStatement> = {\n category: {\n coding: [\n {\n code: 'patientspecified',\n display: 'Patient Specified',\n system: 'http://terminology.hl7.org/CodeSystem/medication-statement-category',\n },\n ],\n },\n dateAsserted: intervention.startDate,\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n medicationCodeableConcept: {\n text: intervention.name,\n },\n resourceType: 'MedicationStatement',\n status: interventionStatus(intervention.endDate),\n subject: {\n reference: `Patient/${patientId}`,\n },\n };\n\n if (intervention.notes) {\n statement.note = [{ text: intervention.notes }];\n }\n\n return statement;\n}\n\n/**\n * Convert diet/exercise/sleep intervention to FHIR Observation (social-history)\n */\nexport function interventionToFHIRObservation(\n intervention: InterventionData,\n patientId: string,\n): Addressable<FHIRObservation> {\n const lifestyleCode = LIFESTYLE_CODES[intervention.type];\n\n const observation: Addressable<FHIRObservation> = {\n category: [\n {\n coding: [\n {\n code: 'social-history',\n display: 'Social History',\n system: 'http://terminology.hl7.org/CodeSystem/observation-category',\n },\n ],\n },\n ],\n code: {\n coding: lifestyleCode\n ? [\n {\n code: lifestyleCode.code,\n display: lifestyleCode.display,\n system: 'http://loinc.org',\n },\n ]\n : [],\n text: intervention.name,\n },\n effectivePeriod: {\n end: intervention.endDate,\n start: intervention.startDate,\n },\n id: `intervention-${intervention.interventionId}`,\n resourceType: 'Observation',\n status: 'final',\n subject: {\n reference: `Patient/${patientId}`,\n },\n valueString: intervention.name,\n };\n\n if (intervention.notes) {\n observation.note = [{ text: intervention.notes }];\n }\n\n return observation;\n}\n\n/**\n * Convert all interventions to a FHIR Bundle\n */\nexport function interventionsToFHIRBundle(\n interventions: InterventionData[],\n userProfile: UserProfileData,\n): FHIRBundle {\n const patientId = userProfile.userId;\n const fhirPatient = userProfileToFHIR(userProfile);\n\n const entries = interventions.map((intervention) => {\n const isMedication = intervention.type === 'medication' || intervention.type === 'supplement';\n const resource = isMedication\n ? interventionToFHIRMedicationStatement(intervention, patientId)\n : interventionToFHIRObservation(intervention, patientId);\n\n return { fullUrl: entryFullUrl(resource), resource };\n });\n\n return {\n entry: [{ fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient }, ...entries],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n","/**\n * Structured zone data for DEXA body composition and bone density charts.\n *\n * Body fat zones derived from Gallagher et al. Am J Clin Nutr 2000;72:694-701 (PMID: 10966886)\n * and ACSM Guidelines for Exercise Testing, 11th Ed (2021).\n *\n * T-Score zones from WHO criteria (Kanis JA, Osteoporos Int, PMID: 7696835).\n */\n\nexport interface BodyFatZone {\n ageMax: number;\n ageMin: number;\n color: string;\n fatPctMax: number;\n fatPctMin: number;\n label: string;\n sex: 'F' | 'M';\n}\n\ninterface AgeBracket {\n ageMax: number;\n ageMin: number;\n label: string;\n}\n\nconst AGE_BRACKETS: AgeBracket[] = [\n { ageMax: 25, ageMin: 18, label: '18-25' },\n { ageMax: 35, ageMin: 26, label: '26-35' },\n { ageMax: 45, ageMin: 36, label: '36-45' },\n { ageMax: 55, ageMin: 46, label: '46-55' },\n { ageMax: 99, ageMin: 56, label: '56+' },\n];\n\n// Zone boundaries per age bracket for men: [essential, athletic, fitness, average, obese]\n// Each value is the upper bound of the zone\nconst MALE_ZONES: number[][] = [\n [5, 10, 20, 25, 40],\n [5, 11, 21, 26, 40],\n [5, 12, 22, 27, 40],\n [5, 13, 23, 28, 40],\n [5, 14, 24, 29, 40],\n];\n\nconst FEMALE_ZONES: number[][] = [\n [13, 18, 28, 32, 45],\n [13, 18, 29, 33, 45],\n [13, 19, 30, 34, 45],\n [13, 20, 31, 35, 45],\n [13, 20, 32, 36, 45],\n];\n\ninterface ZoneDefinition {\n color: string;\n label: string;\n}\n\nconst ZONE_DEFS: ZoneDefinition[] = [\n { color: '#3b82f6', label: 'Essencial' },\n { color: '#06b6d4', label: 'Atlético' },\n { color: '#22c55e', label: 'Fitness' },\n { color: '#eab308', label: 'Média' },\n { color: '#ef4444', label: 'Obeso' },\n];\n\nfunction buildZones(sex: 'F' | 'M', zoneData: number[][]): BodyFatZone[] {\n const zones: BodyFatZone[] = [];\n for (let i = 0; i < AGE_BRACKETS.length; i++) {\n const bracket = AGE_BRACKETS[i]!;\n const b = zoneData[i]!;\n zones.push({\n ...bracket,\n color: ZONE_DEFS[0]!.color,\n fatPctMax: b[0]!,\n fatPctMin: 0,\n label: ZONE_DEFS[0]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[1]!.color,\n fatPctMax: b[1]!,\n fatPctMin: b[0]!,\n label: ZONE_DEFS[1]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[2]!.color,\n fatPctMax: b[2]!,\n fatPctMin: b[1]!,\n label: ZONE_DEFS[2]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[3]!.color,\n fatPctMax: b[3]!,\n fatPctMin: b[2]!,\n label: ZONE_DEFS[3]!.label,\n sex,\n });\n zones.push({\n ...bracket,\n color: ZONE_DEFS[4]!.color,\n fatPctMax: b[4]!,\n fatPctMin: b[3]!,\n label: ZONE_DEFS[4]!.label,\n sex,\n });\n }\n return zones;\n}\n\nexport const BODY_FAT_ZONES: BodyFatZone[] = [\n ...buildZones('M', MALE_ZONES),\n ...buildZones('F', FEMALE_ZONES),\n];\n\nexport { AGE_BRACKETS, ZONE_DEFS };\n\nexport interface TScoreZone {\n color: string;\n label: string;\n max: number;\n min: number;\n}\n\nexport const T_SCORE_ZONES: TScoreZone[] = [\n { color: '#22c55e', label: 'Normal', max: 4, min: -1.0 },\n { color: '#eab308', label: 'Osteopenia', max: -1.0, min: -2.5 },\n { color: '#ef4444', label: 'Osteoporose', max: -2.5, min: -5 },\n];\n","/**\n * Screening Intervals Configuration\n *\n * Defines recommended screening intervals for different biomarker categories\n * based on clinical guidelines and best practices.\n */\n\n/**\n * Screening interval in months\n */\nexport type ScreeningIntervalMonths = 3 | 6 | 12;\n\n/**\n * Biomarker category with its recommended screening interval\n */\nexport interface CategoryScreeningInterval {\n category: string;\n intervalMonths: ScreeningIntervalMonths;\n nameEn: string;\n namePt: string;\n}\n\n/**\n * Screening interval configuration for each category\n *\n * Categories are grouped by their recommended screening intervals:\n * - 3 months: Body composition and bone density (frequently changing metrics)\n * - 6 months: Metabolic panel and nutrients (moderate change rate)\n * - 12 months: Standard blood panels (stable long-term markers)\n */\nexport const CATEGORY_SCREENING_INTERVALS: CategoryScreeningInterval[] = [\n // 3-month intervals - Body composition (frequently changing)\n {\n category: 'composicao-corporal',\n intervalMonths: 3,\n nameEn: 'Body Composition',\n namePt: 'Composição Corporal',\n },\n {\n category: 'densidade-ossea',\n intervalMonths: 3,\n nameEn: 'Bone Density',\n namePt: 'Densidade Óssea',\n },\n\n // 6-month intervals - Metabolic and nutrients\n {\n category: 'metabolico',\n intervalMonths: 6,\n nameEn: 'Metabolic Panel',\n namePt: 'Painel Metabólico',\n },\n {\n category: 'nutrientes',\n intervalMonths: 6,\n nameEn: 'Nutrients',\n namePt: 'Nutrientes',\n },\n {\n category: 'pancreas',\n intervalMonths: 6,\n nameEn: 'Pancreas',\n namePt: 'Pâncreas',\n },\n\n // 12-month intervals - Standard blood panels\n {\n category: 'coracao',\n intervalMonths: 12,\n nameEn: 'Heart Health',\n namePt: 'Saúde Cardiovascular',\n },\n {\n category: 'tireoide',\n intervalMonths: 12,\n nameEn: 'Thyroid',\n namePt: 'Tireoide',\n },\n {\n category: 'sangue',\n intervalMonths: 12,\n nameEn: 'Blood Count',\n namePt: 'Hemograma',\n },\n {\n category: 'figado',\n intervalMonths: 12,\n nameEn: 'Liver Function',\n namePt: 'Função Hepática',\n },\n {\n category: 'rins',\n intervalMonths: 12,\n nameEn: 'Kidney Function',\n namePt: 'Função Renal',\n },\n {\n category: 'saude-feminina',\n intervalMonths: 12,\n nameEn: \"Women's Health\",\n namePt: 'Saúde Feminina',\n },\n {\n category: 'saude-masculina',\n intervalMonths: 12,\n nameEn: \"Men's Health\",\n namePt: 'Saúde Masculina',\n },\n {\n category: 'eletrolitos',\n intervalMonths: 12,\n nameEn: 'Electrolytes',\n namePt: 'Eletrólitos',\n },\n {\n category: 'estresse-envelhecimento',\n intervalMonths: 12,\n nameEn: 'Stress & Aging',\n namePt: 'Estresse e Envelhecimento',\n },\n {\n category: 'autoimunidade',\n intervalMonths: 12,\n nameEn: 'Autoimmunity',\n namePt: 'Autoimunidade',\n },\n {\n category: 'regulacao-imunologica',\n intervalMonths: 12,\n nameEn: 'Immune Regulation',\n namePt: 'Regulação Imunológica',\n },\n {\n category: 'toxinas-ambientais',\n intervalMonths: 12,\n nameEn: 'Environmental Toxins',\n namePt: 'Toxinas Ambientais',\n },\n {\n category: 'urina',\n intervalMonths: 12,\n nameEn: 'Urinalysis',\n namePt: 'Urina',\n },\n];\n\n/**\n * Get screening interval for a category\n */\nexport const getScreeningInterval = (category: string): CategoryScreeningInterval | undefined => {\n return CATEGORY_SCREENING_INTERVALS.find((c) => c.category === category);\n};\n\n/**\n * Get all categories with a specific interval\n */\nexport const getCategoriesByInterval = (\n intervalMonths: ScreeningIntervalMonths,\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((c) => c.intervalMonths === intervalMonths);\n};\n\n/**\n * Calculate next screening date based on last test date and category\n */\nexport const calculateNextScreeningDate = (lastTestDate: Date, category: string): Date | null => {\n const interval = getScreeningInterval(category);\n if (!interval) return null;\n\n const nextDate = new Date(lastTestDate);\n nextDate.setMonth(nextDate.getMonth() + interval.intervalMonths);\n return nextDate;\n};\n\n/**\n * Check if a category is due for screening\n */\nexport const isScreeningDue = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): boolean => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return false;\n return referenceDate >= nextDate;\n};\n\n/**\n * Get categories that are due for screening based on last test dates\n */\nexport const getDueCategories = (\n lastTestDates: Record<string, Date>,\n referenceDate: Date = new Date(),\n): CategoryScreeningInterval[] => {\n return CATEGORY_SCREENING_INTERVALS.filter((interval) => {\n const lastDate = lastTestDates[interval.category];\n if (!lastDate) return true; // Never tested = due\n return isScreeningDue(lastDate, interval.category, referenceDate);\n });\n};\n\n/**\n * Get days until next screening for a category\n */\nexport const getDaysUntilScreening = (\n lastTestDate: Date,\n category: string,\n referenceDate: Date = new Date(),\n): number | null => {\n const nextDate = calculateNextScreeningDate(lastTestDate, category);\n if (!nextDate) return null;\n\n const diffTime = nextDate.getTime() - referenceDate.getTime();\n return Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n};\n","/**\n * Portuguese pluralization utility using native Intl.PluralRules\n * Provides automatic pluralization for common words used in the app\n */\n\nconst pluralRules = new Intl.PluralRules('pt-BR');\n\n/**\n * Dictionary of Portuguese words with their plural forms\n * Key is the singular form, value is the plural form\n */\nconst dictionary: Record<string, string> = {\n // Common nouns\n arquivo: 'arquivos',\n biomarcador: 'biomarcadores',\n // Past participles (masculine)\n cadastrado: 'cadastrados',\n // Past participles (feminine)\n concluída: 'concluídas',\n confirmado: 'confirmados',\n convertido: 'convertidos',\n convidado: 'convidados',\n convite: 'convites',\n disponível: 'disponíveis',\n documento: 'documentos',\n enviado: 'enviados',\n exame: 'exames',\n excluída: 'excluídas',\n\n excluído: 'excluídos',\n // Verbs (3rd person)\n falhou: 'falharam',\n falta: 'faltam',\n ignorado: 'ignorados',\n item: 'itens',\n outro: 'outros',\n página: 'páginas',\n pendente: 'pendentes',\n registro: 'registros',\n removido: 'removidos',\n\n resultado: 'resultados',\n revisão: 'revisões',\n\n revogado: 'revogados',\n usuário: 'usuários',\n};\n\n/**\n * Get the plural form of a word from the dictionary\n * Falls back to adding 's' if word is not in dictionary\n */\nconst getPluralForm = (singular: string): string => {\n return dictionary[singular] ?? `${singular}s`;\n};\n\n/**\n * Returns the correct singular or plural form based on count\n * Uses Intl.PluralRules for proper locale-aware pluralization\n *\n * @example\n * plural(1, 'usuário') // 'usuário'\n * plural(3, 'usuário') // 'usuários'\n * plural(0, 'registro') // 'registros'\n */\nexport const plural = (count: number, word: string): string => {\n const rule = pluralRules.select(count);\n return rule === 'one' ? word : getPluralForm(word);\n};\n\n/**\n * Returns count with the correct singular or plural form\n *\n * @example\n * pluralCount(1, 'usuário') // '1 usuário'\n * pluralCount(3, 'usuário') // '3 usuários'\n */\nexport const pluralCount = (count: number, word: string): string => {\n return `${count} ${plural(count, word)}`;\n};\n\n/**\n * Returns the correct form for compound phrases (noun + adjective)\n * Both words are pluralized together\n *\n * @example\n * pluralPhrase(1, 'usuário', 'cadastrado') // 'usuário cadastrado'\n * pluralPhrase(3, 'usuário', 'cadastrado') // 'usuários cadastrados'\n * pluralPhrase(2, 'revisão', 'excluída') // 'revisões excluídas'\n */\nexport const pluralPhrase = (count: number, noun: string, adjective: string): string => {\n const rule = pluralRules.select(count);\n if (rule === 'one') {\n return `${noun} ${adjective}`;\n }\n return `${getPluralForm(noun)} ${getPluralForm(adjective)}`;\n};\n\n/**\n * Returns count with the correct compound phrase form\n *\n * @example\n * pluralPhraseCount(1, 'usuário', 'cadastrado') // '1 usuário cadastrado'\n * pluralPhraseCount(3, 'convite', 'enviado') // '3 convites enviados'\n */\nexport const pluralPhraseCount = (count: number, noun: string, adjective: string): string => {\n return `${count} ${pluralPhrase(count, noun, adjective)}`;\n};\n","/**\n * Helpers para identificadores brasileiros — CPF e CNS\n *\n * Validação, formatação e conversão para FHIR Identifier.\n * Algoritmos de validação baseados nas especificações oficiais:\n * - CPF: Receita Federal (mod-11, dois dígitos verificadores)\n * - CNS: Ministério da Saúde (mod-11 para definitivos, soma ponderada para provisórios)\n */\n\nimport type { FHIRIdentifier } from './fhir-types';\n\nconst CPF_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cpf';\nconst CNS_SYSTEM = 'http://rnds.saude.gov.br/fhir/r4/NamingSystem/cns';\n\n/**\n * Remove caracteres não-numéricos de uma string.\n */\nfunction digitsOnly(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/**\n * Valida um CPF brasileiro usando algoritmo mod-11.\n *\n * @param cpf — CPF com ou sem formatação (ex: \"123.456.789-09\" ou \"12345678909\")\n * @returns true se o CPF é estruturalmente válido\n */\nexport function validateCPF(cpf: string): boolean {\n const digits = digitsOnly(cpf);\n\n if (digits.length !== 11) return false;\n\n // Rejeitar sequências de dígitos iguais (ex: 111.111.111-11)\n if (/^(\\d)\\1{10}$/.test(digits)) return false;\n\n // Primeiro dígito verificador\n let sum = 0;\n for (let i = 0; i < 9; i++) {\n sum += Number(digits[i]) * (10 - i);\n }\n let remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[9])) return false;\n\n // Segundo dígito verificador\n sum = 0;\n for (let i = 0; i < 10; i++) {\n sum += Number(digits[i]) * (11 - i);\n }\n remainder = (sum * 10) % 11;\n if (remainder === 10) remainder = 0;\n if (remainder !== Number(digits[10])) return false;\n\n return true;\n}\n\n/**\n * Valida um CNS (Cartão Nacional de Saúde) brasileiro.\n *\n * CNS definitivos começam com 1 ou 2 (mod-11).\n * CNS provisórios começam com 7, 8 ou 9 (soma ponderada mod-11 = 0).\n *\n * @param cns — CNS com 15 dígitos\n * @returns true se o CNS é estruturalmente válido\n */\nexport function validateCNS(cns: string): boolean {\n const digits = digitsOnly(cns);\n\n if (digits.length !== 15) return false;\n\n const firstDigit = digits[0]!;\n\n // CNS deve começar com 1, 2 (definitivo) ou 7, 8, 9 (provisório)\n if (!['1', '2', '7', '8', '9'].includes(firstDigit)) return false;\n\n // Ambos os tipos usam soma ponderada mod-11 = 0\n let sum = 0;\n for (let i = 0; i < 15; i++) {\n sum += Number(digits[i]) * (15 - i);\n }\n return sum % 11 === 0;\n}\n\n/**\n * Formata um CPF como XXX.XXX.XXX-XX.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns CPF formatado ou a string original se inválido\n */\nexport function formatCPF(cpf: string): string {\n const digits = digitsOnly(cpf);\n if (digits.length !== 11) return cpf;\n return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;\n}\n\n/**\n * Formata um CNS como XXX XXXX XXXX XXXX.\n *\n * @param cns — CNS com 15 dígitos\n * @returns CNS formatado ou a string original se inválido\n */\nexport function formatCNS(cns: string): string {\n const digits = digitsOnly(cns);\n if (digits.length !== 15) return cns;\n return `${digits.slice(0, 3)} ${digits.slice(3, 7)} ${digits.slice(7, 11)} ${digits.slice(11)}`;\n}\n\n/**\n * Converte um CPF para um FHIR Identifier.\n *\n * @param cpf — CPF com 11 dígitos (com ou sem formatação)\n * @returns FHIR Identifier com sistema RNDS para CPF\n * @throws Error se o CPF for inválido\n */\nexport function cpfToFHIRIdentifier(cpf: string): FHIRIdentifier {\n if (!validateCPF(cpf)) {\n throw new Error('CPF inválido');\n }\n return {\n system: CPF_SYSTEM,\n use: 'official',\n value: digitsOnly(cpf),\n };\n}\n\n/**\n * Converte um CNS para um FHIR Identifier.\n *\n * @param cns — CNS com 15 dígitos\n * @returns FHIR Identifier com sistema RNDS para CNS\n * @throws Error se o CNS for inválido\n */\nexport function cnsToFHIRIdentifier(cns: string): FHIRIdentifier {\n if (!validateCNS(cns)) {\n throw new Error('CNS inválido');\n }\n return {\n system: CNS_SYSTEM,\n use: 'official',\n value: digitsOnly(cns),\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@precisa-saude/fhir",
3
- "version": "0.22.1",
3
+ "version": "0.22.3",
4
4
  "description": "Tipos FHIR R4, definições de biomarcadores, faixas de referência e conversores para o ecossistema de saúde brasileiro",
5
5
  "keywords": [
6
6
  "fhir",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-EAX6MHYX.cjs","../src/converter.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-EAX6MHYX.cjs","sourcesContent":[null,"/**\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 { 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): 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: 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): 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): 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 fullUrl: `urn:uuid:observation-${obs.reportId}-${obs.biomarkerCode}`,\n resource: labObservationToFHIR(\n { ...obs, collectionDate: report.collectionDate },\n patientId,\n report.laboratoryName,\n ),\n }));\n\n const observationIds = observations.map(\n (obs) => `observation-${obs.reportId}-${obs.biomarkerCode}`,\n );\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 {\n fullUrl: `urn:uuid:${patientId}`,\n resource: fhirPatient,\n },\n {\n fullUrl: `urn:uuid:diagnostic-report-${report.reportId}`,\n resource: diagnosticReport,\n },\n ...fhirObservations,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/converter.ts"],"sourcesContent":["/**\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 { 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): 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: 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): 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): 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 fullUrl: `urn:uuid:observation-${obs.reportId}-${obs.biomarkerCode}`,\n resource: labObservationToFHIR(\n { ...obs, collectionDate: report.collectionDate },\n patientId,\n report.laboratoryName,\n ),\n }));\n\n const observationIds = observations.map(\n (obs) => `observation-${obs.reportId}-${obs.biomarkerCode}`,\n );\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 {\n fullUrl: `urn:uuid:${patientId}`,\n resource: fhirPatient,\n },\n {\n fullUrl: `urn:uuid:diagnostic-report-${report.reportId}`,\n resource: diagnosticReport,\n },\n ...fhirObservations,\n ],\n resourceType: 'Bundle',\n type: 'collection',\n };\n}\n"],"mappings":"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","names":[]}