@precisa-saude/fhir 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +85 -0
  2. package/dist/biomarkers.cjs +67 -0
  3. package/dist/biomarkers.cjs.map +1 -0
  4. package/dist/biomarkers.d.cts +226 -0
  5. package/dist/biomarkers.d.ts +226 -0
  6. package/dist/biomarkers.js +67 -0
  7. package/dist/biomarkers.js.map +1 -0
  8. package/dist/chunk-2EVQ2ESB.cjs +2692 -0
  9. package/dist/chunk-2EVQ2ESB.cjs.map +1 -0
  10. package/dist/chunk-2X6MT5KE.cjs +1464 -0
  11. package/dist/chunk-2X6MT5KE.cjs.map +1 -0
  12. package/dist/chunk-3ILBFLVQ.cjs +63 -0
  13. package/dist/chunk-3ILBFLVQ.cjs.map +1 -0
  14. package/dist/chunk-FDOBUUGY.js +1464 -0
  15. package/dist/chunk-FDOBUUGY.js.map +1 -0
  16. package/dist/chunk-GFXKYXHW.cjs +436 -0
  17. package/dist/chunk-GFXKYXHW.cjs.map +1 -0
  18. package/dist/chunk-I6H35QXI.js +2692 -0
  19. package/dist/chunk-I6H35QXI.js.map +1 -0
  20. package/dist/chunk-N3ZCOLG2.js +63 -0
  21. package/dist/chunk-N3ZCOLG2.js.map +1 -0
  22. package/dist/chunk-O25F6G3K.cjs +153 -0
  23. package/dist/chunk-O25F6G3K.cjs.map +1 -0
  24. package/dist/chunk-S6VJHXJF.js +153 -0
  25. package/dist/chunk-S6VJHXJF.js.map +1 -0
  26. package/dist/chunk-UIDSSWBJ.js +251 -0
  27. package/dist/chunk-UIDSSWBJ.js.map +1 -0
  28. package/dist/chunk-VPMT4MRS.js +436 -0
  29. package/dist/chunk-VPMT4MRS.js.map +1 -0
  30. package/dist/chunk-Z6YE6FJ4.cjs +251 -0
  31. package/dist/chunk-Z6YE6FJ4.cjs.map +1 -0
  32. package/dist/converter-C-QpCcTL.d.ts +99 -0
  33. package/dist/converter-Dee-qjBV.d.cts +99 -0
  34. package/dist/converter.cjs +15 -0
  35. package/dist/converter.cjs.map +1 -0
  36. package/dist/converter.d.cts +2 -0
  37. package/dist/converter.d.ts +2 -0
  38. package/dist/converter.js +15 -0
  39. package/dist/converter.js.map +1 -0
  40. package/dist/fhir-types-D9hUzGrc.d.cts +129 -0
  41. package/dist/fhir-types-D9hUzGrc.d.ts +129 -0
  42. package/dist/importer.cjs +17 -0
  43. package/dist/importer.cjs.map +1 -0
  44. package/dist/importer.d.cts +60 -0
  45. package/dist/importer.d.ts +60 -0
  46. package/dist/importer.js +17 -0
  47. package/dist/importer.js.map +1 -0
  48. package/dist/index.cjs +540 -0
  49. package/dist/index.cjs.map +1 -0
  50. package/dist/index.d.cts +163 -0
  51. package/dist/index.d.ts +163 -0
  52. package/dist/index.js +540 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/reference-ranges.cjs +18 -0
  55. package/dist/reference-ranges.cjs.map +1 -0
  56. package/dist/reference-ranges.d.cts +106 -0
  57. package/dist/reference-ranges.d.ts +106 -0
  58. package/dist/reference-ranges.js +18 -0
  59. package/dist/reference-ranges.js.map +1 -0
  60. package/dist/units.cjs +19 -0
  61. package/dist/units.cjs.map +1 -0
  62. package/dist/units.d.cts +46 -0
  63. package/dist/units.d.ts +46 -0
  64. package/dist/units.js +19 -0
  65. package/dist/units.js.map +1 -0
  66. package/dist/validators.cjs +11 -0
  67. package/dist/validators.cjs.map +1 -0
  68. package/dist/validators.d.cts +23 -0
  69. package/dist/validators.d.ts +23 -0
  70. package/dist/validators.js +11 -0
  71. package/dist/validators.js.map +1 -0
  72. package/package.json +105 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,540 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
+
3
+
4
+
5
+
6
+
7
+
8
+ var _chunk2X6MT5KEcjs = require('./chunk-2X6MT5KE.cjs');
9
+
10
+
11
+
12
+
13
+
14
+ var _chunkZ6YE6FJ4cjs = require('./chunk-Z6YE6FJ4.cjs');
15
+
16
+
17
+
18
+
19
+
20
+
21
+ var _chunkO25F6G3Kcjs = require('./chunk-O25F6G3K.cjs');
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+ var _chunk2EVQ2ESBcjs = require('./chunk-2EVQ2ESB.cjs');
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+ var _chunkGFXKYXHWcjs = require('./chunk-GFXKYXHW.cjs');
64
+
65
+
66
+
67
+
68
+ var _chunk3ILBFLVQcjs = require('./chunk-3ILBFLVQ.cjs');
69
+
70
+ // src/intervention-converter.ts
71
+ function interventionStatus(endDate) {
72
+ if (!endDate) return "active";
73
+ return new Date(endDate) < /* @__PURE__ */ new Date() ? "completed" : "active";
74
+ }
75
+ var LIFESTYLE_CODES = {
76
+ diet: { code: "81259-4", display: "Associated precondition - Loss of appetite" },
77
+ exercise: { code: "73985-4", display: "Exercise activity" },
78
+ sleep: { code: "93832-4", display: "Sleep duration" }
79
+ };
80
+ function interventionToFHIRMedicationStatement(intervention, patientId) {
81
+ const statement = {
82
+ category: {
83
+ coding: [
84
+ {
85
+ code: "patientspecified",
86
+ display: "Patient Specified",
87
+ system: "http://terminology.hl7.org/CodeSystem/medication-statement-category"
88
+ }
89
+ ]
90
+ },
91
+ dateAsserted: intervention.startDate,
92
+ effectivePeriod: {
93
+ end: intervention.endDate,
94
+ start: intervention.startDate
95
+ },
96
+ id: `intervention-${intervention.interventionId}`,
97
+ medicationCodeableConcept: {
98
+ text: intervention.name
99
+ },
100
+ resourceType: "MedicationStatement",
101
+ status: interventionStatus(intervention.endDate),
102
+ subject: {
103
+ reference: `Patient/${patientId}`
104
+ }
105
+ };
106
+ if (intervention.notes) {
107
+ statement.note = [{ text: intervention.notes }];
108
+ }
109
+ return statement;
110
+ }
111
+ function interventionToFHIRObservation(intervention, patientId) {
112
+ const lifestyleCode = LIFESTYLE_CODES[intervention.type];
113
+ const observation = {
114
+ category: [
115
+ {
116
+ coding: [
117
+ {
118
+ code: "social-history",
119
+ display: "Social History",
120
+ system: "http://terminology.hl7.org/CodeSystem/observation-category"
121
+ }
122
+ ]
123
+ }
124
+ ],
125
+ code: {
126
+ coding: lifestyleCode ? [
127
+ {
128
+ code: lifestyleCode.code,
129
+ display: lifestyleCode.display,
130
+ system: "http://loinc.org"
131
+ }
132
+ ] : [],
133
+ text: intervention.name
134
+ },
135
+ effectivePeriod: {
136
+ end: intervention.endDate,
137
+ start: intervention.startDate
138
+ },
139
+ id: `intervention-${intervention.interventionId}`,
140
+ resourceType: "Observation",
141
+ status: "final",
142
+ subject: {
143
+ reference: `Patient/${patientId}`
144
+ },
145
+ valueString: intervention.name
146
+ };
147
+ if (intervention.notes) {
148
+ observation.note = [{ text: intervention.notes }];
149
+ }
150
+ return observation;
151
+ }
152
+ function interventionsToFHIRBundle(interventions, userProfile) {
153
+ const patientId = userProfile.userId;
154
+ const fhirPatient = _chunkZ6YE6FJ4cjs.userProfileToFHIR.call(void 0, userProfile);
155
+ const entries = interventions.map((intervention) => {
156
+ const isMedication = intervention.type === "medication" || intervention.type === "supplement";
157
+ const resource = isMedication ? interventionToFHIRMedicationStatement(intervention, patientId) : interventionToFHIRObservation(intervention, patientId);
158
+ return {
159
+ fullUrl: `urn:uuid:intervention-${intervention.interventionId}`,
160
+ resource
161
+ };
162
+ });
163
+ return {
164
+ entry: [
165
+ {
166
+ fullUrl: `urn:uuid:${patientId}`,
167
+ resource: fhirPatient
168
+ },
169
+ ...entries
170
+ ],
171
+ resourceType: "Bundle",
172
+ type: "collection"
173
+ };
174
+ }
175
+
176
+ // src/dexa-zone-data.ts
177
+ var AGE_BRACKETS = [
178
+ { ageMax: 25, ageMin: 18, label: "18-25" },
179
+ { ageMax: 35, ageMin: 26, label: "26-35" },
180
+ { ageMax: 45, ageMin: 36, label: "36-45" },
181
+ { ageMax: 55, ageMin: 46, label: "46-55" },
182
+ { ageMax: 99, ageMin: 56, label: "56+" }
183
+ ];
184
+ var MALE_ZONES = [
185
+ [5, 10, 20, 25, 40],
186
+ [5, 11, 21, 26, 40],
187
+ [5, 12, 22, 27, 40],
188
+ [5, 13, 23, 28, 40],
189
+ [5, 14, 24, 29, 40]
190
+ ];
191
+ var FEMALE_ZONES = [
192
+ [13, 18, 28, 32, 45],
193
+ [13, 18, 29, 33, 45],
194
+ [13, 19, 30, 34, 45],
195
+ [13, 20, 31, 35, 45],
196
+ [13, 20, 32, 36, 45]
197
+ ];
198
+ var ZONE_DEFS = [
199
+ { color: "#3b82f6", label: "Essencial" },
200
+ { color: "#06b6d4", label: "Atl\xE9tico" },
201
+ { color: "#22c55e", label: "Fitness" },
202
+ { color: "#eab308", label: "M\xE9dia" },
203
+ { color: "#ef4444", label: "Obeso" }
204
+ ];
205
+ function buildZones(sex, zoneData) {
206
+ const zones = [];
207
+ for (let i = 0; i < AGE_BRACKETS.length; i++) {
208
+ const bracket = AGE_BRACKETS[i];
209
+ const b = zoneData[i];
210
+ zones.push({
211
+ ...bracket,
212
+ color: ZONE_DEFS[0].color,
213
+ fatPctMax: b[0],
214
+ fatPctMin: 0,
215
+ label: ZONE_DEFS[0].label,
216
+ sex
217
+ });
218
+ zones.push({
219
+ ...bracket,
220
+ color: ZONE_DEFS[1].color,
221
+ fatPctMax: b[1],
222
+ fatPctMin: b[0],
223
+ label: ZONE_DEFS[1].label,
224
+ sex
225
+ });
226
+ zones.push({
227
+ ...bracket,
228
+ color: ZONE_DEFS[2].color,
229
+ fatPctMax: b[2],
230
+ fatPctMin: b[1],
231
+ label: ZONE_DEFS[2].label,
232
+ sex
233
+ });
234
+ zones.push({
235
+ ...bracket,
236
+ color: ZONE_DEFS[3].color,
237
+ fatPctMax: b[3],
238
+ fatPctMin: b[2],
239
+ label: ZONE_DEFS[3].label,
240
+ sex
241
+ });
242
+ zones.push({
243
+ ...bracket,
244
+ color: ZONE_DEFS[4].color,
245
+ fatPctMax: b[4],
246
+ fatPctMin: b[3],
247
+ label: ZONE_DEFS[4].label,
248
+ sex
249
+ });
250
+ }
251
+ return zones;
252
+ }
253
+ var BODY_FAT_ZONES = [
254
+ ...buildZones("M", MALE_ZONES),
255
+ ...buildZones("F", FEMALE_ZONES)
256
+ ];
257
+ var T_SCORE_ZONES = [
258
+ { color: "#22c55e", label: "Normal", max: 4, min: -1 },
259
+ { color: "#eab308", label: "Osteopenia", max: -1, min: -2.5 },
260
+ { color: "#ef4444", label: "Osteoporose", max: -2.5, min: -5 }
261
+ ];
262
+
263
+ // src/screening-intervals.ts
264
+ var CATEGORY_SCREENING_INTERVALS = [
265
+ // 3-month intervals - Body composition (frequently changing)
266
+ {
267
+ category: "composicao-corporal",
268
+ intervalMonths: 3,
269
+ nameEn: "Body Composition",
270
+ namePt: "Composi\xE7\xE3o Corporal"
271
+ },
272
+ {
273
+ category: "densidade-ossea",
274
+ intervalMonths: 3,
275
+ nameEn: "Bone Density",
276
+ namePt: "Densidade \xD3ssea"
277
+ },
278
+ // 6-month intervals - Metabolic and nutrients
279
+ {
280
+ category: "metabolico",
281
+ intervalMonths: 6,
282
+ nameEn: "Metabolic Panel",
283
+ namePt: "Painel Metab\xF3lico"
284
+ },
285
+ {
286
+ category: "nutrientes",
287
+ intervalMonths: 6,
288
+ nameEn: "Nutrients",
289
+ namePt: "Nutrientes"
290
+ },
291
+ {
292
+ category: "pancreas",
293
+ intervalMonths: 6,
294
+ nameEn: "Pancreas",
295
+ namePt: "P\xE2ncreas"
296
+ },
297
+ // 12-month intervals - Standard blood panels
298
+ {
299
+ category: "coracao",
300
+ intervalMonths: 12,
301
+ nameEn: "Heart Health",
302
+ namePt: "Sa\xFAde Cardiovascular"
303
+ },
304
+ {
305
+ category: "tireoide",
306
+ intervalMonths: 12,
307
+ nameEn: "Thyroid",
308
+ namePt: "Tireoide"
309
+ },
310
+ {
311
+ category: "sangue",
312
+ intervalMonths: 12,
313
+ nameEn: "Blood Count",
314
+ namePt: "Hemograma"
315
+ },
316
+ {
317
+ category: "figado",
318
+ intervalMonths: 12,
319
+ nameEn: "Liver Function",
320
+ namePt: "Fun\xE7\xE3o Hep\xE1tica"
321
+ },
322
+ {
323
+ category: "rins",
324
+ intervalMonths: 12,
325
+ nameEn: "Kidney Function",
326
+ namePt: "Fun\xE7\xE3o Renal"
327
+ },
328
+ {
329
+ category: "saude-feminina",
330
+ intervalMonths: 12,
331
+ nameEn: "Women's Health",
332
+ namePt: "Sa\xFAde Feminina"
333
+ },
334
+ {
335
+ category: "saude-masculina",
336
+ intervalMonths: 12,
337
+ nameEn: "Men's Health",
338
+ namePt: "Sa\xFAde Masculina"
339
+ },
340
+ {
341
+ category: "eletrolitos",
342
+ intervalMonths: 12,
343
+ nameEn: "Electrolytes",
344
+ namePt: "Eletr\xF3litos"
345
+ },
346
+ {
347
+ category: "estresse-envelhecimento",
348
+ intervalMonths: 12,
349
+ nameEn: "Stress & Aging",
350
+ namePt: "Estresse e Envelhecimento"
351
+ },
352
+ {
353
+ category: "autoimunidade",
354
+ intervalMonths: 12,
355
+ nameEn: "Autoimmunity",
356
+ namePt: "Autoimunidade"
357
+ },
358
+ {
359
+ category: "regulacao-imunologica",
360
+ intervalMonths: 12,
361
+ nameEn: "Immune Regulation",
362
+ namePt: "Regula\xE7\xE3o Imunol\xF3gica"
363
+ },
364
+ {
365
+ category: "toxinas-ambientais",
366
+ intervalMonths: 12,
367
+ nameEn: "Environmental Toxins",
368
+ namePt: "Toxinas Ambientais"
369
+ },
370
+ {
371
+ category: "urina",
372
+ intervalMonths: 12,
373
+ nameEn: "Urinalysis",
374
+ namePt: "Urina"
375
+ }
376
+ ];
377
+ var getScreeningInterval = (category) => {
378
+ return CATEGORY_SCREENING_INTERVALS.find((c) => c.category === category);
379
+ };
380
+ var getCategoriesByInterval = (intervalMonths) => {
381
+ return CATEGORY_SCREENING_INTERVALS.filter((c) => c.intervalMonths === intervalMonths);
382
+ };
383
+ var calculateNextScreeningDate = (lastTestDate, category) => {
384
+ const interval = getScreeningInterval(category);
385
+ if (!interval) return null;
386
+ const nextDate = new Date(lastTestDate);
387
+ nextDate.setMonth(nextDate.getMonth() + interval.intervalMonths);
388
+ return nextDate;
389
+ };
390
+ var isScreeningDue = (lastTestDate, category, referenceDate = /* @__PURE__ */ new Date()) => {
391
+ const nextDate = calculateNextScreeningDate(lastTestDate, category);
392
+ if (!nextDate) return false;
393
+ return referenceDate >= nextDate;
394
+ };
395
+ var getDueCategories = (lastTestDates, referenceDate = /* @__PURE__ */ new Date()) => {
396
+ return CATEGORY_SCREENING_INTERVALS.filter((interval) => {
397
+ const lastDate = lastTestDates[interval.category];
398
+ if (!lastDate) return true;
399
+ return isScreeningDue(lastDate, interval.category, referenceDate);
400
+ });
401
+ };
402
+ var getDaysUntilScreening = (lastTestDate, category, referenceDate = /* @__PURE__ */ new Date()) => {
403
+ const nextDate = calculateNextScreeningDate(lastTestDate, category);
404
+ if (!nextDate) return null;
405
+ const diffTime = nextDate.getTime() - referenceDate.getTime();
406
+ return Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
407
+ };
408
+
409
+ // src/i18n.ts
410
+ var pluralRules = new Intl.PluralRules("pt-BR");
411
+ var dictionary = {
412
+ // Common nouns
413
+ arquivo: "arquivos",
414
+ biomarcador: "biomarcadores",
415
+ // Past participles (masculine)
416
+ cadastrado: "cadastrados",
417
+ // Past participles (feminine)
418
+ conclu\u00EDda: "conclu\xEDdas",
419
+ confirmado: "confirmados",
420
+ convertido: "convertidos",
421
+ convidado: "convidados",
422
+ convite: "convites",
423
+ dispon\u00EDvel: "dispon\xEDveis",
424
+ documento: "documentos",
425
+ enviado: "enviados",
426
+ exame: "exames",
427
+ exclu\u00EDda: "exclu\xEDdas",
428
+ exclu\u00EDdo: "exclu\xEDdos",
429
+ // Verbs (3rd person)
430
+ falhou: "falharam",
431
+ falta: "faltam",
432
+ ignorado: "ignorados",
433
+ item: "itens",
434
+ outro: "outros",
435
+ p\u00E1gina: "p\xE1ginas",
436
+ pendente: "pendentes",
437
+ registro: "registros",
438
+ removido: "removidos",
439
+ resultado: "resultados",
440
+ revis\u00E3o: "revis\xF5es",
441
+ revogado: "revogados",
442
+ usu\u00E1rio: "usu\xE1rios"
443
+ };
444
+ var getPluralForm = (singular) => {
445
+ return _nullishCoalesce(dictionary[singular], () => ( `${singular}s`));
446
+ };
447
+ var plural = (count, word) => {
448
+ const rule = pluralRules.select(count);
449
+ return rule === "one" ? word : getPluralForm(word);
450
+ };
451
+ var pluralCount = (count, word) => {
452
+ return `${count} ${plural(count, word)}`;
453
+ };
454
+ var pluralPhrase = (count, noun, adjective) => {
455
+ const rule = pluralRules.select(count);
456
+ if (rule === "one") {
457
+ return `${noun} ${adjective}`;
458
+ }
459
+ return `${getPluralForm(noun)} ${getPluralForm(adjective)}`;
460
+ };
461
+ var pluralPhraseCount = (count, noun, adjective) => {
462
+ return `${count} ${pluralPhrase(count, noun, adjective)}`;
463
+ };
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+
479
+
480
+
481
+
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+ exports.AGE_BRACKETS = AGE_BRACKETS; exports.BIOMARKER_DEFAULT_UNIT = _chunkGFXKYXHWcjs.BIOMARKER_DEFAULT_UNIT; exports.BIOMARKER_DEFINITIONS = _chunk2EVQ2ESBcjs.BIOMARKER_DEFINITIONS; exports.BIOMARKER_UNITS = _chunkGFXKYXHWcjs.BIOMARKER_UNITS; exports.BODY_FAT_ZONES = BODY_FAT_ZONES; exports.CAC_INDICATOR_CODES = _chunk2EVQ2ESBcjs.CAC_INDICATOR_CODES; exports.CATEGORY_SCREENING_INTERVALS = CATEGORY_SCREENING_INTERVALS; exports.DEXA_CATEGORIES = _chunk2EVQ2ESBcjs.DEXA_CATEGORIES; exports.DEXA_INDICATOR_CODES = _chunk2EVQ2ESBcjs.DEXA_INDICATOR_CODES; exports.MAX_FILE_SIZE = _chunkO25F6G3Kcjs.MAX_FILE_SIZE; exports.MAX_OBSERVATIONS = _chunkO25F6G3Kcjs.MAX_OBSERVATIONS; exports.T_SCORE_ZONES = T_SCORE_ZONES; exports.UNIT_TO_UCUM = _chunkGFXKYXHWcjs.UNIT_TO_UCUM; exports.ZONE_DEFS = ZONE_DEFS; exports.applyFallbackReferenceRanges = _chunk2X6MT5KEcjs.applyFallbackReferenceRanges; exports.biomarkerRangeDefinitions = _chunk2X6MT5KEcjs.biomarkerRangeDefinitions; exports.calculateNextScreeningDate = calculateNextScreeningDate; exports.codeToLoinc = _chunk2EVQ2ESBcjs.codeToLoinc; exports.defaultReferenceRanges = _chunk2X6MT5KEcjs.defaultReferenceRanges; exports.extractObservationsFromBundle = _chunkO25F6G3Kcjs.extractObservationsFromBundle; exports.filterVisibleBiomarkers = _chunk2EVQ2ESBcjs.filterVisibleBiomarkers; exports.findCodeByName = _chunk2EVQ2ESBcjs.findCodeByName; exports.generateCacFullReference = _chunk2EVQ2ESBcjs.generateCacFullReference; exports.generateDexaFullReference = _chunk2EVQ2ESBcjs.generateDexaFullReference; exports.generateFilteredLLMReference = _chunk2EVQ2ESBcjs.generateFilteredLLMReference; exports.generateLLMReference = _chunk2EVQ2ESBcjs.generateLLMReference; exports.getAllCodes = _chunk2EVQ2ESBcjs.getAllCodes; exports.getAllDefinitions = _chunk2EVQ2ESBcjs.getAllDefinitions; exports.getAllLoincCodes = _chunk2EVQ2ESBcjs.getAllLoincCodes; exports.getAllSearchPatterns = _chunk2EVQ2ESBcjs.getAllSearchPatterns; exports.getBiomarkersByCategory = _chunk2EVQ2ESBcjs.getBiomarkersByCategory; exports.getBiomarkersForCategories = _chunk2EVQ2ESBcjs.getBiomarkersForCategories; exports.getCanonicalUnit = _chunkGFXKYXHWcjs.getCanonicalUnit; exports.getCategoriesByInterval = getCategoriesByInterval; exports.getDaysUntilScreening = getDaysUntilScreening; exports.getDefaultUnit = _chunkGFXKYXHWcjs.getDefaultUnit; exports.getDefinitionByCode = _chunk2EVQ2ESBcjs.getDefinitionByCode; exports.getDefinitionByLoinc = _chunk2EVQ2ESBcjs.getDefinitionByLoinc; exports.getDefinitionsBySex = _chunk2EVQ2ESBcjs.getDefinitionsBySex; exports.getDueCategories = getDueCategories; exports.getFallbackReferenceRange = _chunk2X6MT5KEcjs.getFallbackReferenceRange; exports.getRangeDirection = _chunk2X6MT5KEcjs.getRangeDirection; exports.getReferenceRange = _chunk2X6MT5KEcjs.getReferenceRange; exports.getSIUnit = _chunkGFXKYXHWcjs.getSIUnit; exports.getScreeningInterval = getScreeningInterval; exports.getSexForCode = _chunk2EVQ2ESBcjs.getSexForCode; exports.getVisibleDefinitions = _chunk2EVQ2ESBcjs.getVisibleDefinitions; exports.interventionToFHIRMedicationStatement = interventionToFHIRMedicationStatement; exports.interventionToFHIRObservation = interventionToFHIRObservation; exports.interventionsToFHIRBundle = interventionsToFHIRBundle; exports.isBiomarkerVisible = _chunk2EVQ2ESBcjs.isBiomarkerVisible; exports.isCacDocument = _chunk2EVQ2ESBcjs.isCacDocument; exports.isDexaDocument = _chunk2EVQ2ESBcjs.isDexaDocument; exports.isScreeningDue = isScreeningDue; exports.isValidCode = _chunk2EVQ2ESBcjs.isValidCode; exports.isValidLoinc = _chunk2EVQ2ESBcjs.isValidLoinc; exports.labObservationToFHIR = _chunkZ6YE6FJ4cjs.labObservationToFHIR; exports.labReportToFHIR = _chunkZ6YE6FJ4cjs.labReportToFHIR; exports.labResultToFHIRBundle = _chunkZ6YE6FJ4cjs.labResultToFHIRBundle; exports.loincToCode = _chunk2EVQ2ESBcjs.loincToCode; exports.mapFHIRObservationToInternal = _chunkO25F6G3Kcjs.mapFHIRObservationToInternal; exports.normalizeCode = _chunk2EVQ2ESBcjs.normalizeCode; exports.plural = plural; exports.pluralCount = pluralCount; exports.pluralPhrase = pluralPhrase; exports.pluralPhraseCount = pluralPhraseCount; exports.processImportBundle = _chunkO25F6G3Kcjs.processImportBundle; exports.toBiomarkerTests = _chunk2EVQ2ESBcjs.toBiomarkerTests; exports.unitToUCUM = _chunkGFXKYXHWcjs.unitToUCUM; exports.userProfileToFHIR = _chunkZ6YE6FJ4cjs.userProfileToFHIR; exports.validateFHIRDiagnosticReport = _chunk3ILBFLVQcjs.validateFHIRDiagnosticReport; exports.validateFHIRImportBundle = _chunk3ILBFLVQcjs.validateFHIRImportBundle; exports.validateFHIRObservation = _chunk3ILBFLVQcjs.validateFHIRObservation; exports.validateLoincNameMatch = _chunk2EVQ2ESBcjs.validateLoincNameMatch;
540
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/rafael/Github/precisa-saude/fhir-brasil/packages/core/dist/index.cjs","../src/intervention-converter.ts","../src/dexa-zone-data.ts","../src/screening-intervals.ts","../src/i18n.ts"],"names":[],"mappings":"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D,IAAA;AACU,IAAA;AACN,MAAA;AACkB,MAAA;AACP,MAAA;AACA,MAAA;AACO,MAAA;AACrB,MAAA;AACD,IAAA;AACU,IAAA;AACN,MAAA;AACkB,MAAA;AACP,MAAA;AACA,MAAA;AACO,MAAA;AACrB,MAAA;AACD,IAAA;AACU,IAAA;AACN,MAAA;AACkB,MAAA;AACP,MAAA;AACA,MAAA;AACO,MAAA;AACrB,MAAA;AACD,IAAA;AACU,IAAA;AACN,MAAA;AACkB,MAAA;AACP,MAAA;AACA,MAAA;AACO,MAAA;AACrB,MAAA;AACD,IAAA;AACH,EAAA;AACO,EAAA;AACT;AAE6C;AACd,EAAA;AACE,EAAA;AACjC;AAW2C;AACS,EAAA;AACE,EAAA;AACL,EAAA;AACjD;AFkIoD;AACA;AGxOqB;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;AHiMoD;AACA;AInZJ;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;AJoWoD;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":"/Users/rafael/Github/precisa-saude/fhir-brasil/packages/core/dist/index.cjs","sourcesContent":[null,"/**\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: 'Associated precondition - Loss of appetite' },\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"]}