@precisa-saude/fhir 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-A6HR4XDK.js +9 -0
- package/dist/chunk-A6HR4XDK.js.map +1 -0
- package/dist/chunk-ETZXOGAB.cjs +165 -0
- package/dist/chunk-ETZXOGAB.cjs.map +1 -0
- package/dist/chunk-OR67NJDZ.cjs +9 -0
- package/dist/chunk-OR67NJDZ.cjs.map +1 -0
- package/dist/{chunk-2KDLOCD2.cjs → chunk-QJF5BZ3J.cjs} +19 -8
- package/dist/chunk-QJF5BZ3J.cjs.map +1 -0
- package/dist/{chunk-6TQ24JCA.js → chunk-SN5ZJHWS.js} +19 -8
- package/dist/chunk-SN5ZJHWS.js.map +1 -0
- package/dist/{chunk-OZGOBYLX.js → chunk-YYANW65U.js} +37 -25
- package/dist/chunk-YYANW65U.js.map +1 -0
- package/dist/cli.js +52 -30
- package/dist/converter.cjs +3 -2
- package/dist/converter.cjs.map +1 -1
- package/dist/converter.js +2 -1
- package/dist/importer.cjs +3 -2
- package/dist/importer.cjs.map +1 -1
- package/dist/importer.d.cts +2 -1
- package/dist/importer.d.ts +2 -1
- package/dist/importer.js +2 -1
- package/dist/index.cjs +10 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2KDLOCD2.cjs.map +0 -1
- package/dist/chunk-6TQ24JCA.js.map +0 -1
- package/dist/chunk-CFKXCL2N.cjs +0 -153
- package/dist/chunk-CFKXCL2N.cjs.map +0 -1
- package/dist/chunk-OZGOBYLX.js.map +0 -1
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":"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","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 { 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":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-2KDLOCD2.cjs","../src/converter.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-2KDLOCD2.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 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) || '99999-9';\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 coding: [\n {\n code: loincCode,\n display: observation.biomarkerName,\n system: 'http://loinc.org',\n },\n {\n code: observation.biomarkerCode,\n display: observation.biomarkerName,\n system: 'http://fhir-brasil.dev/biomarker-codes',\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 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) || '99999-9';\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 coding: [\n {\n code: loincCode,\n display: observation.biomarkerName,\n system: 'http://loinc.org',\n },\n {\n code: observation.biomarkerCode,\n display: observation.biomarkerName,\n system: 'http://fhir-brasil.dev/biomarker-codes',\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":[]}
|
package/dist/chunk-CFKXCL2N.cjs
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var _chunkNXOTXKVBcjs = require('./chunk-NXOTXKVB.cjs');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var _chunk3ILBFLVQcjs = require('./chunk-3ILBFLVQ.cjs');
|
|
8
|
-
|
|
9
|
-
// src/importer.ts
|
|
10
|
-
var MAX_OBSERVATIONS = 5e3;
|
|
11
|
-
var MAX_FILE_SIZE = 15 * 1024 * 1024;
|
|
12
|
-
function extractLoincCode(observation) {
|
|
13
|
-
if (!_optionalChain([observation, 'access', _ => _.code, 'optionalAccess', _2 => _2.coding])) return void 0;
|
|
14
|
-
const loincCoding = observation.code.coding.find((c) => c.system === "http://loinc.org");
|
|
15
|
-
return _optionalChain([loincCoding, 'optionalAccess', _3 => _3.code]);
|
|
16
|
-
}
|
|
17
|
-
function extractFlag(observation) {
|
|
18
|
-
const code = _optionalChain([observation, 'access', _4 => _4.interpretation, 'optionalAccess', _5 => _5[0], 'optionalAccess', _6 => _6.coding, 'optionalAccess', _7 => _7[0], 'optionalAccess', _8 => _8.code]);
|
|
19
|
-
if (code === "H" || code === "HH") return "H";
|
|
20
|
-
if (code === "L" || code === "LL") return "L";
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
function extractObservationsFromBundle(bundle) {
|
|
24
|
-
const observations = [];
|
|
25
|
-
const skipped = [];
|
|
26
|
-
for (let i = 0; i < bundle.entry.length; i++) {
|
|
27
|
-
const entry = bundle.entry[i];
|
|
28
|
-
if (!entry.resource) {
|
|
29
|
-
skipped.push({ index: i, reason: "Entry has no resource" });
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (entry.resource.resourceType !== "Observation") {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (observations.length >= MAX_OBSERVATIONS) {
|
|
36
|
-
skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
observations.push(entry.resource);
|
|
40
|
-
}
|
|
41
|
-
return { observations, skipped };
|
|
42
|
-
}
|
|
43
|
-
function mapFHIRObservationToInternal(observation, index) {
|
|
44
|
-
const loincCode = extractLoincCode(observation);
|
|
45
|
-
if (!loincCode) {
|
|
46
|
-
return {
|
|
47
|
-
skipped: {
|
|
48
|
-
index,
|
|
49
|
-
reason: "No LOINC code found in observation coding",
|
|
50
|
-
resourceType: "Observation"
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const internalCode = _chunkNXOTXKVBcjs.loincToCode.call(void 0, loincCode);
|
|
55
|
-
if (!internalCode) {
|
|
56
|
-
return {
|
|
57
|
-
skipped: {
|
|
58
|
-
index,
|
|
59
|
-
loincCode,
|
|
60
|
-
reason: `Unknown LOINC code: ${loincCode}`,
|
|
61
|
-
resourceType: "Observation"
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const definition = _chunkNXOTXKVBcjs.getDefinitionByLoinc.call(void 0, loincCode);
|
|
66
|
-
let value;
|
|
67
|
-
let unit = "";
|
|
68
|
-
let isQualitative = false;
|
|
69
|
-
if (_optionalChain([observation, 'access', _9 => _9.valueQuantity, 'optionalAccess', _10 => _10.value]) !== void 0) {
|
|
70
|
-
value = observation.valueQuantity.value;
|
|
71
|
-
unit = observation.valueQuantity.unit || observation.valueQuantity.code || "";
|
|
72
|
-
} else if (observation.valueString) {
|
|
73
|
-
value = observation.valueString;
|
|
74
|
-
isQualitative = true;
|
|
75
|
-
} else {
|
|
76
|
-
return {
|
|
77
|
-
skipped: {
|
|
78
|
-
index,
|
|
79
|
-
loincCode,
|
|
80
|
-
reason: "Observation has no value (valueQuantity or valueString)",
|
|
81
|
-
resourceType: "Observation"
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
const collectionDate = observation.effectiveDateTime || _optionalChain([observation, 'access', _11 => _11.effectivePeriod, 'optionalAccess', _12 => _12.start]) || "";
|
|
86
|
-
if (!collectionDate) {
|
|
87
|
-
return {
|
|
88
|
-
skipped: {
|
|
89
|
-
index,
|
|
90
|
-
loincCode,
|
|
91
|
-
reason: "Observation has no effectiveDateTime or effectivePeriod.start",
|
|
92
|
-
resourceType: "Observation"
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
let referenceMin;
|
|
97
|
-
let referenceMax;
|
|
98
|
-
if (_optionalChain([observation, 'access', _13 => _13.referenceRange, 'optionalAccess', _14 => _14[0]])) {
|
|
99
|
-
referenceMin = _optionalChain([observation, 'access', _15 => _15.referenceRange, 'access', _16 => _16[0], 'access', _17 => _17.low, 'optionalAccess', _18 => _18.value]);
|
|
100
|
-
referenceMax = _optionalChain([observation, 'access', _19 => _19.referenceRange, 'access', _20 => _20[0], 'access', _21 => _21.high, 'optionalAccess', _22 => _22.value]);
|
|
101
|
-
}
|
|
102
|
-
const imported = {
|
|
103
|
-
biomarkerCode: internalCode,
|
|
104
|
-
biomarkerName: _optionalChain([definition, 'optionalAccess', _23 => _23.names, 'access', _24 => _24.pt, 'access', _25 => _25[0]]) || _optionalChain([definition, 'optionalAccess', _26 => _26.names, 'access', _27 => _27.en, 'access', _28 => _28[0]]) || observation.code.text || internalCode,
|
|
105
|
-
collectionDate,
|
|
106
|
-
flag: extractFlag(observation),
|
|
107
|
-
isQualitative,
|
|
108
|
-
loincCode,
|
|
109
|
-
referenceMax,
|
|
110
|
-
referenceMin,
|
|
111
|
-
unit: unit || _optionalChain([definition, 'optionalAccess', _29 => _29.unit]) || "",
|
|
112
|
-
value
|
|
113
|
-
};
|
|
114
|
-
return { observation: imported };
|
|
115
|
-
}
|
|
116
|
-
function processImportBundle(data) {
|
|
117
|
-
const validationErrors = _chunk3ILBFLVQcjs.validateFHIRImportBundle.call(void 0, data);
|
|
118
|
-
if (validationErrors.length > 0) {
|
|
119
|
-
return {
|
|
120
|
-
errors: validationErrors,
|
|
121
|
-
imported: [],
|
|
122
|
-
skipped: [],
|
|
123
|
-
totalProcessed: 0
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const bundle = data;
|
|
127
|
-
const { observations, skipped } = extractObservationsFromBundle(bundle);
|
|
128
|
-
const imported = [];
|
|
129
|
-
const allSkipped = [...skipped];
|
|
130
|
-
for (let i = 0; i < observations.length; i++) {
|
|
131
|
-
const result = mapFHIRObservationToInternal(observations[i], i);
|
|
132
|
-
if ("observation" in result) {
|
|
133
|
-
imported.push(result.observation);
|
|
134
|
-
} else {
|
|
135
|
-
allSkipped.push(result.skipped);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
errors: [],
|
|
140
|
-
imported,
|
|
141
|
-
skipped: allSkipped,
|
|
142
|
-
totalProcessed: observations.length
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
exports.MAX_OBSERVATIONS = MAX_OBSERVATIONS; exports.MAX_FILE_SIZE = MAX_FILE_SIZE; exports.extractObservationsFromBundle = extractObservationsFromBundle; exports.mapFHIRObservationToInternal = mapFHIRObservationToInternal; exports.processImportBundle = processImportBundle;
|
|
153
|
-
//# sourceMappingURL=chunk-CFKXCL2N.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-CFKXCL2N.cjs","../src/importer.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;AC+CA,IAAM,iBAAA,EAAmB,GAAA;AACzB,IAAM,cAAA,EAAgB,GAAA,EAAK,KAAA,EAAO,IAAA;AAKlC,SAAS,gBAAA,CAAiB,WAAA,EAAkD;AAC1E,EAAA,GAAA,CAAI,iBAAC,WAAA,mBAAY,IAAA,6BAAM,QAAA,EAAQ,OAAO,KAAA,CAAA;AACtC,EAAA,MAAM,YAAA,EAAc,WAAA,CAAY,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,IAAW,kBAAkB,CAAA;AACvF,EAAA,uBAAO,WAAA,6BAAa,MAAA;AACtB;AAKA,SAAS,WAAA,CAAY,WAAA,EAA8C;AACjE,EAAA,MAAM,KAAA,kBAAO,WAAA,qBAAY,cAAA,4BAAA,CAAiB,CAAC,CAAA,6BAAG,MAAA,4BAAA,CAAS,CAAC,CAAA,6BAAG,MAAA;AAC3D,EAAA,GAAA,CAAI,KAAA,IAAS,IAAA,GAAO,KAAA,IAAS,IAAA,EAAM,OAAO,GAAA;AAC1C,EAAA,GAAA,CAAI,KAAA,IAAS,IAAA,GAAO,KAAA,IAAS,IAAA,EAAM,OAAO,GAAA;AAC1C,EAAA,OAAO,EAAA;AACT;AAKO,SAAS,6BAAA,CAA8B,MAAA,EAG5C;AACA,EAAA,MAAM,aAAA,EAAkC,CAAC,CAAA;AACzC,EAAA,MAAM,QAAA,EAA0B,CAAC,CAAA;AAEjC,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC5C,IAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA;AAC5B,IAAA,GAAA,CAAI,CAAC,KAAA,CAAM,QAAA,EAAU;AACnB,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,wBAAwB,CAAC,CAAA;AAC1D,MAAA,QAAA;AAAA,IACF;AAEA,IAAA,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,aAAA,IAAiB,aAAA,EAAe;AAEjD,MAAA,QAAA;AAAA,IACF;AAEA,IAAA,GAAA,CAAI,YAAA,CAAa,OAAA,GAAU,gBAAA,EAAkB;AAC3C,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,WAAA,EAAc,gBAAgB,CAAA,sBAAA,EAAyB,CAAC,CAAA;AACzF,MAAA,QAAA;AAAA,IACF;AAEA,IAAA,YAAA,CAAa,IAAA,CAAK,KAAA,CAAM,QAA2B,CAAA;AAAA,EACrD;AAEA,EAAA,OAAO,EAAE,YAAA,EAAc,QAAQ,CAAA;AACjC;AAKO,SAAS,4BAAA,CACd,WAAA,EACA,KAAA,EACkE;AAClE,EAAA,MAAM,UAAA,EAAY,gBAAA,CAAiB,WAAW,CAAA;AAE9C,EAAA,GAAA,CAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO;AAAA,MACL,OAAA,EAAS;AAAA,QACP,KAAA;AAAA,QACA,MAAA,EAAQ,2CAAA;AAAA,QACR,YAAA,EAAc;AAAA,MAChB;AAAA,IACF,CAAA;AAAA,EACF;AAEA,EAAA,MAAM,aAAA,EAAe,2CAAA,SAAqB,CAAA;AAC1C,EAAA,GAAA,CAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAO;AAAA,MACL,OAAA,EAAS;AAAA,QACP,KAAA;AAAA,QACA,SAAA;AAAA,QACA,MAAA,EAAQ,CAAA,oBAAA,EAAuB,SAAS,CAAA,CAAA;AAC1B,QAAA;AAChB,MAAA;AACF,IAAA;AACF,EAAA;AAEwC,EAAA;AAGpC,EAAA;AACO,EAAA;AACS,EAAA;AAEqB,EAAA;AACL,IAAA;AACO,IAAA;AACP,EAAA;AACd,IAAA;AACJ,IAAA;AACX,EAAA;AACE,IAAA;AACI,MAAA;AACP,QAAA;AACA,QAAA;AACQ,QAAA;AACM,QAAA;AAChB,MAAA;AACF,IAAA;AACF,EAAA;AAGmC,EAAA;AACd,EAAA;AACZ,IAAA;AACI,MAAA;AACP,QAAA;AACA,QAAA;AACQ,QAAA;AACM,QAAA;AAChB,MAAA;AACF,IAAA;AACF,EAAA;AAGI,EAAA;AACA,EAAA;AACiC,EAAA;AACU,IAAA;AACA,IAAA;AAC/C,EAAA;AAEsC,EAAA;AACrB,IAAA;AAEc,IAAA;AAC7B,IAAA;AAC6B,IAAA;AAC7B,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACkC,IAAA;AAClC,IAAA;AACF,EAAA;AAE+B,EAAA;AACjC;AAKqE;AAE1C,EAAA;AACQ,EAAA;AACxB,IAAA;AACG,MAAA;AACG,MAAA;AACD,MAAA;AACM,MAAA;AAClB,IAAA;AACF,EAAA;AAEe,EAAA;AAGmB,EAAA;AAGO,EAAA;AACK,EAAA;AAEA,EAAA;AACA,IAAA;AAEf,IAAA;AACK,MAAA;AAC3B,IAAA;AACyB,MAAA;AAChC,IAAA;AACF,EAAA;AAEO,EAAA;AACI,IAAA;AACT,IAAA;AACS,IAAA;AACoB,IAAA;AAC/B,EAAA;AACF;ADnGiD;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/fhir-brasil/fhir-brasil/packages/core/dist/chunk-CFKXCL2N.cjs","sourcesContent":[null,"/**\n * FHIR Importer\n *\n * Parses FHIR R4 Bundles and extracts Observation resources with known LOINC codes,\n * mapping them to internal biomarker codes for storage as lab results.\n */\n\nimport { getDefinitionByLoinc, loincToCode } from './biomarkers';\nimport type { FHIRBundle, FHIRObservation } from './fhir-types';\nimport { validateFHIRImportBundle } from './validators';\n\nexport interface ImportedObservation {\n biomarkerCode: string;\n biomarkerName: string;\n collectionDate: string;\n flag: 'H' | 'L' | '';\n isQualitative: boolean;\n loincCode: string;\n referenceMax?: number;\n referenceMin?: number;\n unit: string;\n value: number | string;\n}\n\nexport interface SkippedEntry {\n index: number;\n loincCode?: string;\n reason: string;\n resourceType?: string;\n}\n\nexport interface ImportError {\n details: string;\n field: string;\n}\n\nexport interface FHIRImportResult {\n errors: ImportError[];\n imported: ImportedObservation[];\n skipped: SkippedEntry[];\n totalProcessed: number;\n}\n\n/**\n * Limites de importação.\n *\n * Uma Observation exportada ocupa cerca de 1,25KB em JSON compacto e 2,75KB\n * quando o arquivo vem indentado, medido sobre um histórico real de 998\n * Observations em 61 laudos. Nesse tamanho, 5000 Observations dão 6,0MB\n * compactos ou 13,1MB indentados, e por isso o teto de arquivo é 15MB: cobre\n * as duas formas com folga.\n *\n * Na densidade desse mesmo histórico (16 Observations por laudo), 5000\n * equivalem a cerca de 300 laudos.\n */\nconst MAX_OBSERVATIONS = 5000;\nconst MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB\n\n/**\n * Extract LOINC code from an Observation's code.coding array\n */\nfunction extractLoincCode(observation: FHIRObservation): string | undefined {\n if (!observation.code?.coding) return undefined;\n const loincCoding = observation.code.coding.find((c) => c.system === 'http://loinc.org');\n return loincCoding?.code;\n}\n\n/**\n * Extract interpretation flag from Observation\n */\nfunction extractFlag(observation: FHIRObservation): 'H' | 'L' | '' {\n const code = observation.interpretation?.[0]?.coding?.[0]?.code;\n if (code === 'H' || code === 'HH') return 'H';\n if (code === 'L' || code === 'LL') return 'L';\n return '';\n}\n\n/**\n * Extract Observation resources from a FHIR Bundle\n */\nexport function extractObservationsFromBundle(bundle: FHIRBundle): {\n observations: FHIRObservation[];\n skipped: SkippedEntry[];\n} {\n const observations: FHIRObservation[] = [];\n const skipped: SkippedEntry[] = [];\n\n for (let i = 0; i < bundle.entry.length; i++) {\n const entry = bundle.entry[i]!;\n if (!entry.resource) {\n skipped.push({ index: i, reason: 'Entry has no resource' });\n continue;\n }\n\n if (entry.resource.resourceType !== 'Observation') {\n // Non-observation resources are silently skipped (Patient, DiagnosticReport, etc.)\n continue;\n }\n\n if (observations.length >= MAX_OBSERVATIONS) {\n skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });\n continue;\n }\n\n observations.push(entry.resource as FHIRObservation);\n }\n\n return { observations, skipped };\n}\n\n/**\n * Map a FHIR Observation to internal format using LOINC→biomarker code lookup\n */\nexport function mapFHIRObservationToInternal(\n observation: FHIRObservation,\n index: number,\n): { observation: ImportedObservation } | { skipped: SkippedEntry } {\n const loincCode = extractLoincCode(observation);\n\n if (!loincCode) {\n return {\n skipped: {\n index,\n reason: 'No LOINC code found in observation coding',\n resourceType: 'Observation',\n },\n };\n }\n\n const internalCode = loincToCode(loincCode);\n if (!internalCode) {\n return {\n skipped: {\n index,\n loincCode,\n reason: `Unknown LOINC code: ${loincCode}`,\n resourceType: 'Observation',\n },\n };\n }\n\n const definition = getDefinitionByLoinc(loincCode);\n\n // Extract value\n let value: number | string;\n let unit = '';\n let isQualitative = false;\n\n if (observation.valueQuantity?.value !== undefined) {\n value = observation.valueQuantity.value;\n unit = observation.valueQuantity.unit || observation.valueQuantity.code || '';\n } else if (observation.valueString) {\n value = observation.valueString;\n isQualitative = true;\n } else {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no value (valueQuantity or valueString)',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract collection date (effectiveDateTime or effectivePeriod.start)\n const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || '';\n if (!collectionDate) {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no effectiveDateTime or effectivePeriod.start',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract reference ranges\n let referenceMin: number | undefined;\n let referenceMax: number | undefined;\n if (observation.referenceRange?.[0]) {\n referenceMin = observation.referenceRange[0].low?.value;\n referenceMax = observation.referenceRange[0].high?.value;\n }\n\n const imported: ImportedObservation = {\n biomarkerCode: internalCode,\n biomarkerName:\n definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,\n collectionDate,\n flag: extractFlag(observation),\n isQualitative,\n loincCode,\n referenceMax,\n referenceMin,\n unit: unit || definition?.unit || '',\n value,\n };\n\n return { observation: imported };\n}\n\n/**\n * Process a complete FHIR Bundle for import\n */\nexport function processImportBundle(data: unknown): FHIRImportResult {\n // Structural validation\n const validationErrors = validateFHIRImportBundle(data);\n if (validationErrors.length > 0) {\n return {\n errors: validationErrors,\n imported: [],\n skipped: [],\n totalProcessed: 0,\n };\n }\n\n const bundle = data as FHIRBundle;\n\n // Extract observations\n const { observations, skipped } = extractObservationsFromBundle(bundle);\n\n // Map each observation to internal format\n const imported: ImportedObservation[] = [];\n const allSkipped: SkippedEntry[] = [...skipped];\n\n for (let i = 0; i < observations.length; i++) {\n const result = mapFHIRObservationToInternal(observations[i]!, i);\n\n if ('observation' in result) {\n imported.push(result.observation);\n } else {\n allSkipped.push(result.skipped);\n }\n }\n\n return {\n errors: [],\n imported,\n skipped: allSkipped,\n totalProcessed: observations.length,\n };\n}\n\nexport { MAX_FILE_SIZE, MAX_OBSERVATIONS };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/importer.ts"],"sourcesContent":["/**\n * FHIR Importer\n *\n * Parses FHIR R4 Bundles and extracts Observation resources with known LOINC codes,\n * mapping them to internal biomarker codes for storage as lab results.\n */\n\nimport { getDefinitionByLoinc, loincToCode } from './biomarkers';\nimport type { FHIRBundle, FHIRObservation } from './fhir-types';\nimport { validateFHIRImportBundle } from './validators';\n\nexport interface ImportedObservation {\n biomarkerCode: string;\n biomarkerName: string;\n collectionDate: string;\n flag: 'H' | 'L' | '';\n isQualitative: boolean;\n loincCode: string;\n referenceMax?: number;\n referenceMin?: number;\n unit: string;\n value: number | string;\n}\n\nexport interface SkippedEntry {\n index: number;\n loincCode?: string;\n reason: string;\n resourceType?: string;\n}\n\nexport interface ImportError {\n details: string;\n field: string;\n}\n\nexport interface FHIRImportResult {\n errors: ImportError[];\n imported: ImportedObservation[];\n skipped: SkippedEntry[];\n totalProcessed: number;\n}\n\n/**\n * Limites de importação.\n *\n * Uma Observation exportada ocupa cerca de 1,25KB em JSON compacto e 2,75KB\n * quando o arquivo vem indentado, medido sobre um histórico real de 998\n * Observations em 61 laudos. Nesse tamanho, 5000 Observations dão 6,0MB\n * compactos ou 13,1MB indentados, e por isso o teto de arquivo é 15MB: cobre\n * as duas formas com folga.\n *\n * Na densidade desse mesmo histórico (16 Observations por laudo), 5000\n * equivalem a cerca de 300 laudos.\n */\nconst MAX_OBSERVATIONS = 5000;\nconst MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB\n\n/**\n * Extract LOINC code from an Observation's code.coding array\n */\nfunction extractLoincCode(observation: FHIRObservation): string | undefined {\n if (!observation.code?.coding) return undefined;\n const loincCoding = observation.code.coding.find((c) => c.system === 'http://loinc.org');\n return loincCoding?.code;\n}\n\n/**\n * Extract interpretation flag from Observation\n */\nfunction extractFlag(observation: FHIRObservation): 'H' | 'L' | '' {\n const code = observation.interpretation?.[0]?.coding?.[0]?.code;\n if (code === 'H' || code === 'HH') return 'H';\n if (code === 'L' || code === 'LL') return 'L';\n return '';\n}\n\n/**\n * Extract Observation resources from a FHIR Bundle\n */\nexport function extractObservationsFromBundle(bundle: FHIRBundle): {\n observations: FHIRObservation[];\n skipped: SkippedEntry[];\n} {\n const observations: FHIRObservation[] = [];\n const skipped: SkippedEntry[] = [];\n\n for (let i = 0; i < bundle.entry.length; i++) {\n const entry = bundle.entry[i]!;\n if (!entry.resource) {\n skipped.push({ index: i, reason: 'Entry has no resource' });\n continue;\n }\n\n if (entry.resource.resourceType !== 'Observation') {\n // Non-observation resources are silently skipped (Patient, DiagnosticReport, etc.)\n continue;\n }\n\n if (observations.length >= MAX_OBSERVATIONS) {\n skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });\n continue;\n }\n\n observations.push(entry.resource as FHIRObservation);\n }\n\n return { observations, skipped };\n}\n\n/**\n * Map a FHIR Observation to internal format using LOINC→biomarker code lookup\n */\nexport function mapFHIRObservationToInternal(\n observation: FHIRObservation,\n index: number,\n): { observation: ImportedObservation } | { skipped: SkippedEntry } {\n const loincCode = extractLoincCode(observation);\n\n if (!loincCode) {\n return {\n skipped: {\n index,\n reason: 'No LOINC code found in observation coding',\n resourceType: 'Observation',\n },\n };\n }\n\n const internalCode = loincToCode(loincCode);\n if (!internalCode) {\n return {\n skipped: {\n index,\n loincCode,\n reason: `Unknown LOINC code: ${loincCode}`,\n resourceType: 'Observation',\n },\n };\n }\n\n const definition = getDefinitionByLoinc(loincCode);\n\n // Extract value\n let value: number | string;\n let unit = '';\n let isQualitative = false;\n\n if (observation.valueQuantity?.value !== undefined) {\n value = observation.valueQuantity.value;\n unit = observation.valueQuantity.unit || observation.valueQuantity.code || '';\n } else if (observation.valueString) {\n value = observation.valueString;\n isQualitative = true;\n } else {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no value (valueQuantity or valueString)',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract collection date (effectiveDateTime or effectivePeriod.start)\n const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || '';\n if (!collectionDate) {\n return {\n skipped: {\n index,\n loincCode,\n reason: 'Observation has no effectiveDateTime or effectivePeriod.start',\n resourceType: 'Observation',\n },\n };\n }\n\n // Extract reference ranges\n let referenceMin: number | undefined;\n let referenceMax: number | undefined;\n if (observation.referenceRange?.[0]) {\n referenceMin = observation.referenceRange[0].low?.value;\n referenceMax = observation.referenceRange[0].high?.value;\n }\n\n const imported: ImportedObservation = {\n biomarkerCode: internalCode,\n biomarkerName:\n definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,\n collectionDate,\n flag: extractFlag(observation),\n isQualitative,\n loincCode,\n referenceMax,\n referenceMin,\n unit: unit || definition?.unit || '',\n value,\n };\n\n return { observation: imported };\n}\n\n/**\n * Process a complete FHIR Bundle for import\n */\nexport function processImportBundle(data: unknown): FHIRImportResult {\n // Structural validation\n const validationErrors = validateFHIRImportBundle(data);\n if (validationErrors.length > 0) {\n return {\n errors: validationErrors,\n imported: [],\n skipped: [],\n totalProcessed: 0,\n };\n }\n\n const bundle = data as FHIRBundle;\n\n // Extract observations\n const { observations, skipped } = extractObservationsFromBundle(bundle);\n\n // Map each observation to internal format\n const imported: ImportedObservation[] = [];\n const allSkipped: SkippedEntry[] = [...skipped];\n\n for (let i = 0; i < observations.length; i++) {\n const result = mapFHIRObservationToInternal(observations[i]!, i);\n\n if ('observation' in result) {\n imported.push(result.observation);\n } else {\n allSkipped.push(result.skipped);\n }\n }\n\n return {\n errors: [],\n imported,\n skipped: allSkipped,\n totalProcessed: observations.length,\n };\n}\n\nexport { MAX_FILE_SIZE, MAX_OBSERVATIONS };\n"],"mappings":"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","names":[]}
|