@precisa-saude/fhir 0.33.0 → 0.34.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 (47) hide show
  1. package/README.md +2 -0
  2. package/dist/{chunk-ZCIZUBMR.cjs → chunk-376KM7IL.cjs} +10 -3
  3. package/dist/chunk-376KM7IL.cjs.map +1 -0
  4. package/dist/{chunk-KS2YZBAS.js → chunk-K3VZ3F5Z.js} +8 -2
  5. package/dist/chunk-K3VZ3F5Z.js.map +1 -0
  6. package/dist/{chunk-X5WVIJGG.js → chunk-NJM45WAH.js} +8 -1
  7. package/dist/chunk-NJM45WAH.js.map +1 -0
  8. package/dist/{chunk-FGZXQWSR.cjs → chunk-NKDUVSDK.cjs} +8 -2
  9. package/dist/chunk-NKDUVSDK.cjs.map +1 -0
  10. package/dist/{chunk-WXCX5TJJ.js → chunk-P4G534AQ.js} +444 -60
  11. package/dist/chunk-P4G534AQ.js.map +1 -0
  12. package/dist/{chunk-LXKFVJF4.cjs → chunk-VKLWTTUO.cjs} +445 -61
  13. package/dist/chunk-VKLWTTUO.cjs.map +1 -0
  14. package/dist/cli.js +1237 -847
  15. package/dist/{converter-DZH9x52r.d.cts → converter-DP8VkkO3.d.cts} +9 -1
  16. package/dist/{converter-D1Q84Oc9.d.ts → converter-UygMzWlL.d.ts} +9 -1
  17. package/dist/converter.cjs +3 -2
  18. package/dist/converter.cjs.map +1 -1
  19. package/dist/converter.d.cts +3 -2
  20. package/dist/converter.d.ts +3 -2
  21. package/dist/converter.js +2 -1
  22. package/dist/{fhir-types-B1KQmlRb.d.ts → fhir-types-Cn5WFbOI.d.cts} +5 -0
  23. package/dist/{fhir-types-B1KQmlRb.d.cts → fhir-types-Cn5WFbOI.d.ts} +5 -0
  24. package/dist/importer.cjs +2 -2
  25. package/dist/importer.d.cts +3 -1
  26. package/dist/importer.d.ts +3 -1
  27. package/dist/importer.js +1 -1
  28. package/dist/index.cjs +11 -7
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +5 -5
  31. package/dist/index.d.ts +5 -5
  32. package/dist/index.js +8 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/reference-ranges.cjs +6 -2
  35. package/dist/reference-ranges.cjs.map +1 -1
  36. package/dist/reference-ranges.d.cts +83 -1
  37. package/dist/reference-ranges.d.ts +83 -1
  38. package/dist/reference-ranges.js +7 -3
  39. package/dist/validators.d.cts +1 -1
  40. package/dist/validators.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-FGZXQWSR.cjs.map +0 -1
  43. package/dist/chunk-KS2YZBAS.js.map +0 -1
  44. package/dist/chunk-LXKFVJF4.cjs.map +0 -1
  45. package/dist/chunk-WXCX5TJJ.js.map +0 -1
  46. package/dist/chunk-X5WVIJGG.js.map +0 -1
  47. package/dist/chunk-ZCIZUBMR.cjs.map +0 -1
package/dist/cli.js CHANGED
@@ -4412,628 +4412,89 @@ function resolveUcum(unit, biomarkerCode) {
4412
4412
  return isUcumCode(unit) ? unit : void 0;
4413
4413
  }
4414
4414
 
4415
- // src/converter.ts
4416
- function interpretationCode(flag) {
4417
- switch (flag) {
4418
- case "H":
4419
- return "H";
4420
- // High
4421
- case "L":
4422
- return "L";
4423
- // Low
4424
- default:
4425
- return "N";
4426
- }
4427
- }
4428
- function interpretationDisplay(flag) {
4429
- switch (flag) {
4430
- case "H":
4431
- return "High";
4432
- case "L":
4433
- return "Low";
4434
- default:
4435
- return "Normal";
4436
- }
4437
- }
4438
- var SEX_CODING = {
4439
- female: {
4440
- code: "female",
4441
- display: "Female",
4442
- system: "http://hl7.org/fhir/administrative-gender"
4415
+ // src/reference-ranges.ts
4416
+ var biomarkerRangeDefinitions = {
4417
+ Albumin_Creatinine_Ratio: {
4418
+ default: { max: 30, min: 0, optimalMax: 20, optimalMin: 0, unit: "mg/g" },
4419
+ kind: "decision-threshold",
4420
+ source: "tietz-7ed-2015"
4443
4421
  },
4444
- male: { code: "male", display: "Male", system: "http://hl7.org/fhir/administrative-gender" }
4445
- };
4446
- var buildReferenceRanges = (observation, quantity) => {
4447
- const toRanges = (low, high, sex) => {
4448
- if (low === void 0 && high === void 0) return [];
4449
- return [
4422
+ Adiponectin: {
4423
+ default: { max: 26, min: 4, optimalMax: 20, optimalMin: 8, unit: "mcg/mL" },
4424
+ kind: "reference-interval",
4425
+ source: "tietz-7ed-2015"
4426
+ },
4427
+ ADMA: {
4428
+ default: { max: 0.7, min: 0.3, optimalMax: 0.55, optimalMin: 0.3, unit: "umol/L" },
4429
+ kind: "reference-interval",
4430
+ source: "nemeth-adma-2017"
4431
+ },
4432
+ AFP: {
4433
+ default: { max: 10, min: 0, optimalMax: 8, optimalMin: 0, unit: "ng/mL" },
4434
+ kind: "reference-interval",
4435
+ source: "sturgeon-nacb-2008"
4436
+ },
4437
+ Albumin_Globulin_Ratio: {
4438
+ default: { max: 2.5, min: 1, optimalMax: 2.2, optimalMin: 1.2, unit: "" },
4439
+ kind: "reference-interval",
4440
+ source: "tietz-7ed-2015"
4441
+ },
4442
+ Albumin: {
4443
+ default: { max: 5, min: 3.5, optimalMax: 4.8, optimalMin: 4, unit: "g/dL" },
4444
+ kind: "reference-interval",
4445
+ source: "pns-bioquimica-2019"
4446
+ },
4447
+ AlkalinePhosphatase: {
4448
+ default: { max: 147, min: 44, optimalMax: 120, optimalMin: 50, unit: "U/L" },
4449
+ kind: "reference-interval",
4450
+ source: "tietz-7ed-2015",
4451
+ variants: [
4450
4452
  {
4451
- ...sex === void 0 ? {} : { appliesTo: [{ coding: [SEX_CODING[sex]] }] },
4452
- ...high === void 0 ? {} : { high: quantity(high) },
4453
- ...low === void 0 ? {} : { low: quantity(low) }
4454
- }
4455
- ];
4456
- };
4457
- if (observation.referenceRanges && observation.referenceRanges.length > 0) {
4458
- return observation.referenceRanges.flatMap((r) => toRanges(r.low, r.high, r.appliesTo));
4459
- }
4460
- return toRanges(observation.referenceMin, observation.referenceMax);
4461
- };
4462
- function labObservationToFHIR(observation, patientId, laboratoryName) {
4463
- const loincCode = codeToLoinc(observation.biomarkerCode);
4464
- const sourceUnit = observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;
4465
- const ucumUnit = resolveUcum(sourceUnit, observation.biomarkerCode);
4466
- const quantity = (value) => ({
4467
- ...ucumUnit ? { code: ucumUnit, system: "http://unitsofmeasure.org" } : {},
4468
- ...sourceUnit ? { unit: sourceUnit } : {},
4469
- value
4470
- });
4471
- const isQualitative = observation.isQualitative || typeof observation.value === "string";
4472
- const fhirObs = {
4473
- category: [
4453
+ ageMin: 18,
4454
+ range: { max: 147, min: 44, optimalMax: 120, optimalMin: 50, unit: "U/L" },
4455
+ sex: "M"
4456
+ },
4474
4457
  {
4475
- coding: [
4476
- {
4477
- code: "laboratory",
4478
- display: "Laboratory",
4479
- system: "http://terminology.hl7.org/CodeSystem/observation-category"
4480
- }
4481
- ]
4458
+ ageMin: 18,
4459
+ range: { max: 104, min: 35, optimalMax: 90, optimalMin: 40, unit: "U/L" },
4460
+ sex: "F"
4482
4461
  }
4483
- ],
4484
- code: {
4485
- // Sem LOINC, o coding LOINC simplesmente não sai. Antes ia `99999-9`,
4486
- // que não é código LOINC nenhum: publicava sob `http://loinc.org` uma
4487
- // afirmação falsa, e quem consumisse o bundle confiando no system
4488
- // trataria aquilo como código de verdade. Composição corporal, densidade
4489
- // óssea e escore de cálcio não têm LOINC, e o certo é a lacuna explícita.
4490
- coding: [
4491
- ...loincCode ? [
4492
- {
4493
- code: loincCode,
4494
- display: observation.biomarkerName,
4495
- system: LOINC_SYSTEM
4496
- }
4497
- ] : [],
4498
- {
4499
- code: observation.biomarkerCode,
4500
- display: observation.biomarkerName,
4501
- system: BIOMARKER_CODE_SYSTEM
4502
- }
4503
- ],
4504
- text: observation.biomarkerName
4505
- },
4506
- effectiveDateTime: observation.collectionDate,
4507
- id: `${observation.reportId}-${observation.biomarkerCode}`,
4508
- interpretation: [
4462
+ ]
4463
+ },
4464
+ ALT: {
4465
+ default: { max: 35, min: 0, optimalMax: 25, optimalMin: 0, unit: "U/L" },
4466
+ kind: "reference-interval",
4467
+ source: "tietz-7ed-2015",
4468
+ variants: [
4509
4469
  {
4510
- coding: [
4511
- {
4512
- code: interpretationCode(observation.flag),
4513
- display: interpretationDisplay(observation.flag),
4514
- system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation"
4515
- }
4516
- ]
4517
- }
4518
- ],
4519
- performer: laboratoryName ? [{ display: laboratoryName }] : void 0,
4520
- resourceType: "Observation",
4521
- status: "final",
4522
- subject: {
4523
- reference: `Patient/${patientId}`
4524
- }
4525
- };
4526
- if (isQualitative) {
4527
- fhirObs.valueString = String(observation.value);
4528
- } else {
4529
- fhirObs.valueQuantity = quantity(observation.value);
4530
- const referenceRange = buildReferenceRanges(observation, quantity);
4531
- if (referenceRange.length > 0) fhirObs.referenceRange = referenceRange;
4532
- }
4533
- return fhirObs;
4534
- }
4535
- function labReportToFHIR(report, patientId, observationIds) {
4536
- let status;
4537
- switch (report.processingStatus) {
4538
- case "complete":
4539
- status = "final";
4540
- break;
4541
- case "partial":
4542
- status = "partial";
4543
- break;
4544
- case "pending_review":
4545
- status = "preliminary";
4546
- break;
4547
- default:
4548
- status = "final";
4549
- }
4550
- return {
4551
- category: [
4470
+ ageMin: 18,
4471
+ range: { max: 56, min: 7, optimalMax: 35, optimalMin: 10, unit: "U/L" },
4472
+ sex: "M"
4473
+ },
4552
4474
  {
4553
- coding: [
4554
- {
4555
- code: "LAB",
4556
- display: "Laboratory",
4557
- system: "http://terminology.hl7.org/CodeSystem/v2-0074"
4558
- }
4559
- ]
4475
+ ageMin: 18,
4476
+ range: { max: 45, min: 7, optimalMax: 30, optimalMin: 10, unit: "U/L" },
4477
+ sex: "F"
4560
4478
  }
4561
- ],
4562
- code: {
4563
- coding: [
4564
- {
4565
- code: "11502-2",
4566
- // Laboratory report
4567
- display: "Laboratory report",
4568
- system: "http://loinc.org"
4569
- }
4570
- ],
4571
- text: "Laboratory Results"
4572
- },
4573
- conclusion: report.overallStatus === "NORMAL" ? "All results within normal limits" : "One or more abnormal results detected",
4574
- conclusionCode: report.overallStatus === "ANORMAL" ? [
4479
+ ]
4480
+ },
4481
+ // AMH — fortemente dependente de idade e sexo. As variantes femininas por
4482
+ // faixa etária são a referência clínica; o `default` foi ajustado para
4483
+ // refletir uma faixa adulta conservadora (~18-40 anos, mulheres em idade
4484
+ // reprodutiva geral). optimalMax anterior (6.9 ng/mL) sobrepunha-se a
4485
+ // valores sugestivos de SOP ou risco de OHSS; clinicamente, valores acima
4486
+ // de ~4 ng/mL já merecem investigação.
4487
+ AMH: {
4488
+ default: { max: 6, min: 1, optimalMax: 4, optimalMin: 1.5, unit: "ng/mL" },
4489
+ kind: "reference-interval",
4490
+ source: "tietz-7ed-2015",
4491
+ variants: [
4575
4492
  {
4576
- coding: [
4577
- {
4578
- code: "A",
4579
- display: "Abnormal",
4580
- system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation"
4581
- }
4582
- ]
4583
- }
4584
- ] : void 0,
4585
- effectiveDateTime: report.collectionDate,
4586
- id: report.reportId,
4587
- issued: report.createdAt,
4588
- performer: report.laboratoryName ? [{ display: report.laboratoryName }] : void 0,
4589
- resourceType: "DiagnosticReport",
4590
- result: observationIds.map((id) => ({ reference: `Observation/${id}` })),
4591
- status,
4592
- subject: {
4593
- reference: `Patient/${patientId}`
4594
- }
4595
- };
4596
- }
4597
- function userProfileToFHIR(profile) {
4598
- const nameParts = profile.name.split(" ");
4599
- const given = nameParts.slice(0, -1);
4600
- const family = nameParts[nameParts.length - 1] || "";
4601
- return {
4602
- address: profile.address ? [
4603
- {
4604
- city: profile.address.city,
4605
- country: profile.address.country || "BR",
4606
- line: [
4607
- profile.address.street && profile.address.number ? `${profile.address.street}, ${profile.address.number}` : profile.address.street,
4608
- profile.address.complement
4609
- ].filter(Boolean),
4610
- postalCode: profile.address.postalCode,
4611
- state: profile.address.state
4612
- }
4613
- ] : void 0,
4614
- birthDate: profile.birthDate,
4615
- gender: profile.gender,
4616
- id: profile.userId,
4617
- name: [
4618
- {
4619
- family,
4620
- given: given.length > 0 ? given : void 0,
4621
- text: profile.name
4622
- }
4623
- ],
4624
- resourceType: "Patient",
4625
- telecom: [
4626
- ...profile.email ? [{ system: "email", value: profile.email }] : [],
4627
- ...profile.phone ? [{ system: "phone", value: profile.phone }] : []
4628
- ].length > 0 ? [
4629
- ...profile.email ? [{ system: "email", value: profile.email }] : [],
4630
- ...profile.phone ? [{ system: "phone", value: profile.phone }] : []
4631
- ] : void 0
4632
- };
4633
- }
4634
- function labResultToFHIRBundle(report, observations, userProfile) {
4635
- const patientId = userProfile.userId;
4636
- const fhirObservations = observations.map((obs) => {
4637
- const resource = labObservationToFHIR(
4638
- { ...obs, collectionDate: report.collectionDate },
4639
- patientId,
4640
- report.laboratoryName
4641
- );
4642
- return { fullUrl: entryFullUrl(resource), resource };
4643
- });
4644
- const observationIds = fhirObservations.map((entry) => entry.resource.id);
4645
- const diagnosticReport = labReportToFHIR(report, patientId, observationIds);
4646
- const fhirPatient = userProfileToFHIR(userProfile);
4647
- return {
4648
- entry: [
4649
- { fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },
4650
- { fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },
4651
- ...fhirObservations
4652
- ],
4653
- resourceType: "Bundle",
4654
- type: "collection"
4655
- };
4656
- }
4657
-
4658
- // src/cli/commands/convert.ts
4659
- async function convert(args, json) {
4660
- const raw = await getInput(args[0]);
4661
- const data = parseJson(
4662
- raw,
4663
- "JSON inv\xE1lido. Forne\xE7a um objeto com { report, observations, profile }."
4664
- );
4665
- if (!data.report || !data.observations || !data.profile) {
4666
- exitWithError(
4667
- 'Formato esperado: { "report": LabReportData, "observations": LabObservationData[], "profile": UserProfileData }'
4668
- );
4669
- }
4670
- const bundle = labResultToFHIRBundle(data.report, data.observations, data.profile);
4671
- if (json) {
4672
- outputJson(bundle);
4673
- } else {
4674
- outputText(JSON.stringify(bundle, null, 2));
4675
- }
4676
- }
4677
-
4678
- // src/validators.ts
4679
- function validateFHIRImportBundle(data) {
4680
- const errors = [];
4681
- if (!data || typeof data !== "object") {
4682
- errors.push({ details: "Input must be a JSON object", field: "root" });
4683
- return errors;
4684
- }
4685
- const bundle = data;
4686
- if (bundle.resourceType !== "Bundle") {
4687
- errors.push({
4688
- details: `Expected resourceType "Bundle", got "${String(bundle.resourceType)}"`,
4689
- field: "resourceType"
4690
- });
4691
- }
4692
- if (!Array.isArray(bundle.entry)) {
4693
- errors.push({ details: 'Bundle must contain an "entry" array', field: "entry" });
4694
- } else if (bundle.entry.length === 0) {
4695
- errors.push({ details: "Bundle entry array is empty", field: "entry" });
4696
- }
4697
- return errors;
4698
- }
4699
-
4700
- // src/importer.ts
4701
- var MAX_OBSERVATIONS = 5e3;
4702
- var MAX_FILE_SIZE = 15 * 1024 * 1024;
4703
- function resolveBiomarkerCode(observation) {
4704
- const coding = observation.code?.coding ?? [];
4705
- const loincCode = coding.find((c) => c.system === LOINC_SYSTEM)?.code;
4706
- const declaredCode = coding.find((c) => c.system === BIOMARKER_CODE_SYSTEM)?.code;
4707
- const seenCodes = [
4708
- ...loincCode ? [`LOINC ${loincCode}`] : [],
4709
- ...declaredCode ? [`biomarker code ${declaredCode}`] : []
4710
- ];
4711
- let reason;
4712
- if (seenCodes.length > 0) {
4713
- reason = `Unknown code: ${seenCodes.join(", ")}`;
4714
- } else if (coding.length > 0) {
4715
- const systems = [...new Set(coding.map((c) => c.system ?? "(sem system)"))];
4716
- reason = `No code in a supported system (found: ${systems.join(", ")})`;
4717
- } else {
4718
- reason = "No code found in observation coding";
4719
- }
4720
- const fromLoinc = loincCode ? loincToCode(loincCode) : void 0;
4721
- if (fromLoinc) return { internalCode: fromLoinc, loincCode, reason };
4722
- if (declaredCode && isValidCode(declaredCode)) {
4723
- const canonical = normalizeCode(declaredCode);
4724
- return { internalCode: canonical, loincCode: codeToLoinc(canonical), reason };
4725
- }
4726
- return { loincCode, reason };
4727
- }
4728
- function extractFlag(observation) {
4729
- const code = observation.interpretation?.[0]?.coding?.[0]?.code;
4730
- if (code === "H" || code === "HH") return "H";
4731
- if (code === "L" || code === "LL") return "L";
4732
- return "";
4733
- }
4734
- function extractObservationsFromBundle(bundle) {
4735
- const observations = [];
4736
- const skipped = [];
4737
- for (let i = 0; i < bundle.entry.length; i++) {
4738
- const entry = bundle.entry[i];
4739
- if (!entry.resource) {
4740
- skipped.push({ index: i, reason: "Entry has no resource" });
4741
- continue;
4742
- }
4743
- if (entry.resource.resourceType !== "Observation") {
4744
- continue;
4745
- }
4746
- if (observations.length >= MAX_OBSERVATIONS) {
4747
- skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });
4748
- continue;
4749
- }
4750
- observations.push(entry.resource);
4751
- }
4752
- return { observations, skipped };
4753
- }
4754
- function mapFHIRObservationToInternal(observation, index) {
4755
- const { internalCode, loincCode, reason } = resolveBiomarkerCode(observation);
4756
- if (!internalCode) {
4757
- return {
4758
- skipped: { index, loincCode, reason, resourceType: "Observation" }
4759
- };
4760
- }
4761
- const definition = getDefinitionByCode(internalCode);
4762
- let value;
4763
- let unit = "";
4764
- let isQualitative = false;
4765
- if (observation.valueQuantity?.value !== void 0) {
4766
- value = observation.valueQuantity.value;
4767
- unit = observation.valueQuantity.unit || observation.valueQuantity.code || "";
4768
- } else if (observation.valueString) {
4769
- value = observation.valueString;
4770
- isQualitative = true;
4771
- } else {
4772
- return {
4773
- skipped: {
4774
- index,
4775
- loincCode,
4776
- reason: "Observation has no value (valueQuantity or valueString)",
4777
- resourceType: "Observation"
4778
- }
4779
- };
4780
- }
4781
- const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || "";
4782
- if (!collectionDate) {
4783
- return {
4784
- skipped: {
4785
- index,
4786
- loincCode,
4787
- reason: "Observation has no effectiveDateTime or effectivePeriod.start",
4788
- resourceType: "Observation"
4789
- }
4790
- };
4791
- }
4792
- let referenceMin;
4793
- let referenceMax;
4794
- if (observation.referenceRange?.[0]) {
4795
- referenceMin = observation.referenceRange[0].low?.value;
4796
- referenceMax = observation.referenceRange[0].high?.value;
4797
- }
4798
- const imported = {
4799
- biomarkerCode: internalCode,
4800
- biomarkerName: definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,
4801
- collectionDate,
4802
- flag: extractFlag(observation),
4803
- isQualitative,
4804
- loincCode,
4805
- referenceMax,
4806
- referenceMin,
4807
- unit: unit || definition?.unit || "",
4808
- value
4809
- };
4810
- return { observation: imported };
4811
- }
4812
- function processImportBundle(data) {
4813
- const validationErrors = validateFHIRImportBundle(data);
4814
- if (validationErrors.length > 0) {
4815
- return {
4816
- errors: validationErrors,
4817
- imported: [],
4818
- skipped: [],
4819
- totalProcessed: 0
4820
- };
4821
- }
4822
- const bundle = data;
4823
- const { observations, skipped } = extractObservationsFromBundle(bundle);
4824
- const imported = [];
4825
- const allSkipped = [...skipped];
4826
- for (let i = 0; i < observations.length; i++) {
4827
- const result = mapFHIRObservationToInternal(observations[i], i);
4828
- if ("observation" in result) {
4829
- imported.push(result.observation);
4830
- } else {
4831
- allSkipped.push(result.skipped);
4832
- }
4833
- }
4834
- return {
4835
- errors: [],
4836
- imported,
4837
- skipped: allSkipped,
4838
- totalProcessed: observations.length
4839
- };
4840
- }
4841
-
4842
- // src/cli/commands/import.ts
4843
- async function importBundle(args, json) {
4844
- const raw = await getInput(args[0]);
4845
- const bundle = parseJson(raw, "JSON inv\xE1lido.");
4846
- const result = processImportBundle(bundle);
4847
- if (json) {
4848
- outputJson(result);
4849
- return;
4850
- }
4851
- outputText(`Processados: ${result.totalProcessed}`);
4852
- outputText(`Importados: ${result.imported.length}`);
4853
- outputText(`Ignorados: ${result.skipped.length}`);
4854
- outputText(`Erros: ${result.errors.length}`);
4855
- if (result.imported.length > 0) {
4856
- outputText("\nObserva\xE7\xF5es importadas:");
4857
- for (const obs of result.imported) {
4858
- outputText(` ${obs.biomarkerCode} (${obs.loincCode}): ${obs.value} ${obs.unit}`);
4859
- }
4860
- }
4861
- if (result.errors.length > 0) {
4862
- outputText("\nErros:");
4863
- for (const err of result.errors) {
4864
- outputText(` ${err.field}: ${err.details}`);
4865
- }
4866
- }
4867
- }
4868
-
4869
- // src/cli/commands/list.ts
4870
- import { parseArgs } from "util";
4871
- async function list(args, json) {
4872
- const { values } = parseArgs({
4873
- args,
4874
- options: {
4875
- category: { type: "string" },
4876
- visible: { default: false, type: "boolean" }
4877
- },
4878
- strict: false
4879
- });
4880
- let defs;
4881
- if (values.category) {
4882
- defs = getBiomarkersByCategory(values.category);
4883
- if (defs.length === 0) {
4884
- exitWithError(`Categoria n\xE3o encontrada ou vazia: ${values.category}`);
4885
- }
4886
- } else if (values.visible) {
4887
- defs = getVisibleDefinitions();
4888
- } else {
4889
- defs = getAllDefinitions();
4890
- }
4891
- if (json) {
4892
- outputJson(defs);
4893
- return;
4894
- }
4895
- const rows = defs.map((d) => [
4896
- d.code,
4897
- d.loinc ?? "\u2014",
4898
- d.names.pt[0] ?? "",
4899
- d.unit ?? "\u2014",
4900
- Array.isArray(d.category) ? d.category.join(", ") : d.category
4901
- ]);
4902
- outputText(formatTable(["C\xF3digo", "LOINC", "Nome (pt)", "Unidade", "Categoria"], rows));
4903
- outputText(`
4904
- Total: ${defs.length} biomarcadores`);
4905
- }
4906
-
4907
- // src/cli/commands/loinc-map.ts
4908
- async function loincMap(_args, json) {
4909
- const defs = getAllDefinitions().filter((d) => d.loinc);
4910
- if (json) {
4911
- const map = Object.fromEntries(defs.map((d) => [d.loinc, d.code]));
4912
- outputJson(map);
4913
- return;
4914
- }
4915
- const rows = defs.map((d) => [d.loinc, d.code, d.names.pt[0] ?? d.names.en[0] ?? ""]);
4916
- outputText(formatTable(["LOINC", "C\xF3digo", "Nome (pt)"], rows));
4917
- outputText(`
4918
- Total: ${rows.length} mapeamentos`);
4919
- }
4920
-
4921
- // src/cli/commands/lookup.ts
4922
- function displayDefinition(def) {
4923
- const cat = Array.isArray(def.category) ? def.category.join(", ") : def.category;
4924
- outputText(
4925
- [
4926
- `Biomarcador: ${def.code}`,
4927
- ` LOINC: ${def.loinc ?? "\u2014"}`,
4928
- ` Nomes (pt): ${def.names.pt.join(", ")}`,
4929
- ` Nomes (en): ${def.names.en.join(", ")}`,
4930
- ` Categoria: ${cat}`,
4931
- ` Unidade: ${def.unit ?? "\u2014"}`,
4932
- ` Sexo: ${def.sex ?? "ambos"}`,
4933
- ` Vis\xEDvel: ${def.hidden ? "n\xE3o" : "sim"}`
4934
- ].join("\n")
4935
- );
4936
- }
4937
- async function lookup(args, json) {
4938
- const code = args[0];
4939
- if (!code) exitWithError("Uso: fhir-bio lookup <c\xF3digo>");
4940
- const def = getDefinitionByCode(code);
4941
- if (!def) exitWithError(`Biomarcador n\xE3o encontrado: ${code}`);
4942
- if (json) {
4943
- outputJson(def);
4944
- } else {
4945
- displayDefinition(def);
4946
- }
4947
- }
4948
- async function lookupLoinc(args, json) {
4949
- const loinc = args[0];
4950
- if (!loinc) exitWithError("Uso: fhir-bio lookup-loinc <loinc>");
4951
- const def = getDefinitionByLoinc(loinc);
4952
- if (!def) exitWithError(`Biomarcador n\xE3o encontrado para LOINC: ${loinc}`);
4953
- if (json) {
4954
- outputJson(def);
4955
- } else {
4956
- displayDefinition(def);
4957
- }
4958
- }
4959
-
4960
- // src/cli/commands/range.ts
4961
- import { parseArgs as parseArgs2 } from "util";
4962
-
4963
- // src/reference-ranges.ts
4964
- var biomarkerRangeDefinitions = {
4965
- Albumin_Creatinine_Ratio: {
4966
- default: { max: 30, min: 0, optimalMax: 20, optimalMin: 0, unit: "mg/g" },
4967
- source: "tietz-7ed-2015"
4968
- },
4969
- Adiponectin: {
4970
- default: { max: 26, min: 4, optimalMax: 20, optimalMin: 8, unit: "mcg/mL" },
4971
- source: "tietz-7ed-2015"
4972
- },
4973
- ADMA: {
4974
- default: { max: 0.7, min: 0.3, optimalMax: 0.55, optimalMin: 0.3, unit: "umol/L" },
4975
- source: "nemeth-adma-2017"
4976
- },
4977
- AFP: {
4978
- default: { max: 10, min: 0, optimalMax: 8, optimalMin: 0, unit: "ng/mL" },
4979
- source: "sturgeon-nacb-2008"
4980
- },
4981
- Albumin_Globulin_Ratio: {
4982
- default: { max: 2.5, min: 1, optimalMax: 2.2, optimalMin: 1.2, unit: "" },
4983
- source: "tietz-7ed-2015"
4984
- },
4985
- Albumin: {
4986
- default: { max: 5, min: 3.5, optimalMax: 4.8, optimalMin: 4, unit: "g/dL" },
4987
- source: "pns-bioquimica-2019"
4988
- },
4989
- AlkalinePhosphatase: {
4990
- default: { max: 147, min: 44, optimalMax: 120, optimalMin: 50, unit: "U/L" },
4991
- source: "tietz-7ed-2015",
4992
- variants: [
4993
- {
4994
- ageMin: 18,
4995
- range: { max: 147, min: 44, optimalMax: 120, optimalMin: 50, unit: "U/L" },
4996
- sex: "M"
4997
- },
4998
- {
4999
- ageMin: 18,
5000
- range: { max: 104, min: 35, optimalMax: 90, optimalMin: 40, unit: "U/L" },
5001
- sex: "F"
5002
- }
5003
- ]
5004
- },
5005
- ALT: {
5006
- default: { max: 35, min: 0, optimalMax: 25, optimalMin: 0, unit: "U/L" },
5007
- source: "tietz-7ed-2015",
5008
- variants: [
5009
- {
5010
- ageMin: 18,
5011
- range: { max: 56, min: 7, optimalMax: 35, optimalMin: 10, unit: "U/L" },
5012
- sex: "M"
5013
- },
5014
- {
5015
- ageMin: 18,
5016
- range: { max: 45, min: 7, optimalMax: 30, optimalMin: 10, unit: "U/L" },
5017
- sex: "F"
5018
- }
5019
- ]
5020
- },
5021
- // AMH — fortemente dependente de idade e sexo. As variantes femininas por
5022
- // faixa etária são a referência clínica; o `default` foi ajustado para
5023
- // refletir uma faixa adulta conservadora (~18-40 anos, mulheres em idade
5024
- // reprodutiva geral). optimalMax anterior (6.9 ng/mL) sobrepunha-se a
5025
- // valores sugestivos de SOP ou risco de OHSS; clinicamente, valores acima
5026
- // de ~4 ng/mL já merecem investigação.
5027
- AMH: {
5028
- default: { max: 6, min: 1, optimalMax: 4, optimalMin: 1.5, unit: "ng/mL" },
5029
- source: "tietz-7ed-2015",
5030
- variants: [
5031
- {
5032
- ageMax: 24,
5033
- ageMin: 18,
5034
- range: { max: 13, min: 1.5, optimalMax: 10, optimalMin: 3, unit: "ng/mL" },
5035
- sex: "F"
5036
- },
4493
+ ageMax: 24,
4494
+ ageMin: 18,
4495
+ range: { max: 13, min: 1.5, optimalMax: 10, optimalMin: 3, unit: "ng/mL" },
4496
+ sex: "F"
4497
+ },
5037
4498
  {
5038
4499
  ageMax: 30,
5039
4500
  ageMin: 25,
@@ -5061,14 +4522,17 @@ var biomarkerRangeDefinitions = {
5061
4522
  },
5062
4523
  Ammonia: {
5063
4524
  default: { max: 45, min: 15, optimalMax: 40, optimalMin: 20, unit: "umol/L" },
4525
+ kind: "reference-interval",
5064
4526
  source: "tietz-7ed-2015"
5065
4527
  },
5066
4528
  AntiThyroglobulin: {
5067
4529
  default: { max: 115, min: 0, optimalMax: 40, optimalMin: 0, unit: "IU/mL" },
4530
+ kind: "reference-interval",
5068
4531
  source: "tietz-7ed-2015"
5069
4532
  },
5070
4533
  AntiTPO: {
5071
4534
  default: { max: 34, min: 0, optimalMax: 9, optimalMin: 0, unit: "IU/mL" },
4535
+ kind: "reference-interval",
5072
4536
  source: "tietz-7ed-2015"
5073
4537
  },
5074
4538
  // Aortic valve calcium — no standardized clinical threshold; use mild cutoff
@@ -5076,23 +4540,28 @@ var biomarkerRangeDefinitions = {
5076
4540
  AorticValveCalcium: {
5077
4541
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5078
4542
  direction: "lower-better",
4543
+ kind: "decision-threshold",
5079
4544
  source: "rumberger-cac-1999"
5080
4545
  },
5081
4546
  ApoA1: {
5082
4547
  default: { max: 200, min: 100, optimalMax: 180, optimalMin: 120, unit: "mg/dL" },
4548
+ kind: "reference-interval",
5083
4549
  source: "contois-apoa1-1996"
5084
4550
  },
5085
4551
  ApoB: {
5086
4552
  default: { max: 90, min: 0, optimalMax: 70, optimalMin: 0, unit: "mg/dL" },
5087
4553
  direction: "lower-better",
4554
+ kind: "decision-threshold",
5088
4555
  source: "sbc-lipids-2025"
5089
4556
  },
5090
4557
  ApoCIII: {
5091
4558
  default: { max: 10, min: 0, optimalMax: 7, optimalMin: 0, unit: "mg/dL" },
4559
+ kind: "decision-threshold",
5092
4560
  source: "khetarpal-apociii-2016"
5093
4561
  },
5094
4562
  Omega6_AA: {
5095
4563
  default: { max: 15, min: 5, optimalMax: 12, optimalMin: 7, unit: "%" },
4564
+ kind: "decision-threshold",
5096
4565
  source: "simopoulos-omega-ratio-2002"
5097
4566
  },
5098
4567
  // Razão Ácido Araquidônico/EPA (sangue total) — `lower-better`: razão menor =
@@ -5105,16 +4574,19 @@ var biomarkerRangeDefinitions = {
5105
4574
  // dentro do intervalo. Sem `optimal*`: não há corte ótimo de AA/EPA com fonte.
5106
4575
  // Em telas de relatório único, prefira o intervalo impresso pelo laboratório.
5107
4576
  AA_EPA_Ratio: {
5108
- default: { max: 40.7, min: 3.7, unit: "" },
4577
+ default: { max: 40.7, min: 3.7, minKind: "display", unit: "" },
5109
4578
  direction: "lower-better",
4579
+ kind: "population",
5110
4580
  source: "torrissen-omega3-dbs-2025"
5111
4581
  },
5112
4582
  Arsenic: {
5113
4583
  default: { max: 35, min: 0, optimalMax: 20, optimalMin: 0, unit: "mcg/L" },
4584
+ kind: "decision-threshold",
5114
4585
  source: "nr7-pcmso-2020"
5115
4586
  },
5116
4587
  AST: {
5117
4588
  default: { max: 35, min: 0, optimalMax: 25, optimalMin: 0, unit: "U/L" },
4589
+ kind: "reference-interval",
5118
4590
  source: "tietz-7ed-2015",
5119
4591
  variants: [
5120
4592
  {
@@ -5133,6 +4605,7 @@ var biomarkerRangeDefinitions = {
5133
4605
  // Tabela 2 da PNS 2019. Ver o comentário em `Neutrophils_Abs`.
5134
4606
  Basophils_Abs: {
5135
4607
  default: { max: 0.072, min: 0, unit: "K/uL" },
4608
+ kind: "reference-interval",
5136
4609
  source: "pns-hemograma-2019",
5137
4610
  variants: [
5138
4611
  { ageMin: 18, range: { max: 0.062, min: 0, unit: "K/uL" }, sex: "M" },
@@ -5141,6 +4614,7 @@ var biomarkerRangeDefinitions = {
5141
4614
  },
5142
4615
  Bicarbonate: {
5143
4616
  default: { max: 29, min: 23, optimalMax: 28, optimalMin: 24, unit: "mEq/L" },
4617
+ kind: "reference-interval",
5144
4618
  source: "tietz-7ed-2015"
5145
4619
  },
5146
4620
  // β-Hidroxibutirato (BHB) sérico — corpos cetônicos. Faixa derivada de
@@ -5150,23 +4624,28 @@ var biomarkerRangeDefinitions = {
5150
4624
  // superior aqui reflete estado pós-prandial habitual, não jejum.
5151
4625
  BetaHydroxybutyrate: {
5152
4626
  default: { max: 0.28, min: 0.02, optimalMax: 0.28, optimalMin: 0.02, unit: "mmol/L" },
4627
+ kind: "reference-interval",
5153
4628
  source: "klee-bhb-2020"
5154
4629
  },
5155
4630
  BilirubinDirect: {
5156
4631
  default: { max: 0.3, min: 0, optimalMax: 0.2, optimalMin: 0, unit: "mg/dL" },
4632
+ kind: "reference-interval",
5157
4633
  source: "tietz-7ed-2015"
5158
4634
  },
5159
4635
  BilirubinIndirect: {
5160
4636
  default: { max: 0.8, min: 0, optimalMax: 0.6, optimalMin: 0, unit: "mg/dL" },
4637
+ kind: "reference-interval",
5161
4638
  source: "tietz-7ed-2015"
5162
4639
  },
5163
4640
  BilirubinTotal: {
5164
4641
  default: { max: 1.2, min: 0.1, optimalMax: 0.9, optimalMin: 0.2, unit: "mg/dL" },
4642
+ kind: "reference-interval",
5165
4643
  source: "tietz-7ed-2015"
5166
4644
  },
5167
4645
  // BNP: 35 pg/mL = corte não-agudo para triagem de IC (SBC IC 2018)
5168
4646
  BNP: {
5169
4647
  default: { max: 35, min: 0, optimalMax: 20, optimalMin: 0, unit: "pg/mL" },
4648
+ kind: "decision-threshold",
5170
4649
  source: "sbc-ic-2018"
5171
4650
  },
5172
4651
  // Razão BUN/Creatinina — faixa 10-20 aplica-se ao Nitrogênio Ureico (BUN).
@@ -5177,14 +4656,17 @@ var biomarkerRangeDefinitions = {
5177
4656
  // distinta. Ver issue #41 para alinhamento de nomenclatura/LOINC.
5178
4657
  BUN_Creatinine_Ratio: {
5179
4658
  default: { max: 20, min: 10, optimalMax: 18, optimalMin: 12, unit: "" },
4659
+ kind: "reference-interval",
5180
4660
  source: "tietz-7ed-2015"
5181
4661
  },
5182
4662
  CA125: {
5183
4663
  default: { max: 35, min: 0, optimalMax: 25, optimalMin: 0, unit: "U/mL" },
4664
+ kind: "reference-interval",
5184
4665
  source: "sturgeon-nacb-2008"
5185
4666
  },
5186
4667
  CA199: {
5187
4668
  default: { max: 37, min: 0, optimalMax: 30, optimalMin: 0, unit: "U/mL" },
4669
+ kind: "reference-interval",
5188
4670
  source: "sturgeon-nacb-2008"
5189
4671
  },
5190
4672
  // CAC: Agatston classification — 0=none, 1-99=mild, 100-399=moderate, 400+=severe
@@ -5192,44 +4674,53 @@ var biomarkerRangeDefinitions = {
5192
4674
  CAC: {
5193
4675
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5194
4676
  direction: "lower-better",
4677
+ kind: "decision-threshold",
5195
4678
  source: "rumberger-cac-1999"
5196
4679
  },
5197
4680
  // Per-vessel scores — same Agatston classification applied per vessel
5198
4681
  CAC_LAD: {
5199
4682
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5200
4683
  direction: "lower-better",
4684
+ kind: "decision-threshold",
5201
4685
  source: "rumberger-cac-1999"
5202
4686
  },
5203
4687
  CAC_LCX: {
5204
4688
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5205
4689
  direction: "lower-better",
4690
+ kind: "decision-threshold",
5206
4691
  source: "rumberger-cac-1999"
5207
4692
  },
5208
4693
  CAC_LMA: {
5209
4694
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5210
4695
  direction: "lower-better",
4696
+ kind: "decision-threshold",
5211
4697
  source: "rumberger-cac-1999"
5212
4698
  },
5213
4699
  CAC_Percentile: {
5214
4700
  default: { max: 50, min: 0, optimalMax: 25, optimalMin: 0, unit: "%", warningMax: 75 },
5215
4701
  direction: "lower-better",
4702
+ kind: "decision-threshold",
5216
4703
  source: "rumberger-cac-1999"
5217
4704
  },
5218
4705
  CAC_RCA: {
5219
4706
  default: { max: 99, min: 0, optimalMax: 0, optimalMin: 0, unit: "AU" },
5220
4707
  direction: "lower-better",
4708
+ kind: "decision-threshold",
5221
4709
  source: "rumberger-cac-1999"
5222
4710
  },
5223
4711
  Cadmium: {
5224
4712
  default: { max: 1.2, min: 0, optimalMax: 0.5, optimalMin: 0, unit: "mcg/L" },
4713
+ kind: "decision-threshold",
5225
4714
  source: "nr7-pcmso-2020"
5226
4715
  },
5227
4716
  Calcium: {
5228
4717
  default: { max: 10.5, min: 8.5, optimalMax: 10, optimalMin: 9, unit: "mg/dL" },
4718
+ kind: "reference-interval",
5229
4719
  source: "tietz-7ed-2015"
5230
4720
  },
5231
4721
  CEA: {
5232
4722
  default: { max: 3, min: 0, optimalMax: 2.5, optimalMin: 0, unit: "ng/mL" },
4723
+ kind: "reference-interval",
5233
4724
  source: "sturgeon-nacb-2008"
5234
4725
  },
5235
4726
  // CK: valores conservadores baseados em faixas laboratoriais típicas
@@ -5254,6 +4745,7 @@ var biomarkerRangeDefinitions = {
5254
4745
  // autodeclaração de raça/Cor". Ver a issue #3 para a decisão registrada.
5255
4746
  CK: {
5256
4747
  default: { max: 314, min: 30, optimalMax: 170, optimalMin: 40, unit: "U/L" },
4748
+ kind: "reference-interval",
5257
4749
  source: "kalaria-ck-ri-2026",
5258
4750
  variants: [
5259
4751
  { sex: "F", range: { max: 170, min: 30, optimalMax: 150, optimalMin: 40, unit: "U/L" } }
@@ -5261,6 +4753,7 @@ var biomarkerRangeDefinitions = {
5261
4753
  },
5262
4754
  Chloride: {
5263
4755
  default: { max: 106, min: 98, optimalMax: 105, optimalMin: 100, unit: "mEq/L" },
4756
+ kind: "reference-interval",
5264
4757
  source: "tietz-7ed-2015"
5265
4758
  },
5266
4759
  // Colesterol total — SBC 2017/2025: valor desejável < 190 mg/dL para adultos,
@@ -5268,6 +4761,7 @@ var biomarkerRangeDefinitions = {
5268
4761
  // (NCEP, 2002), superada pelas atualizações brasileiras.
5269
4762
  Cholesterol: {
5270
4763
  default: { max: 190, min: 0, optimalMax: 170, optimalMin: 0, unit: "mg/dL" },
4764
+ kind: "decision-threshold",
5271
4765
  source: "sbc-lipids-2025"
5272
4766
  },
5273
4767
  // Índice de Castelli I (CT/HDL-c)
@@ -5275,6 +4769,7 @@ var biomarkerRangeDefinitions = {
5275
4769
  // Índice de Castelli I — SBC 2017: M <4.9, F <4.3
5276
4770
  default: { max: 4.9, min: 0, optimalMax: 3.5, optimalMin: 0, unit: "" },
5277
4771
  direction: "lower-better",
4772
+ kind: "decision-threshold",
5278
4773
  source: "castelli-ratio-1992",
5279
4774
  variants: [
5280
4775
  {
@@ -5291,22 +4786,27 @@ var biomarkerRangeDefinitions = {
5291
4786
  },
5292
4787
  CO2: {
5293
4788
  default: { max: 29, min: 23, optimalMax: 28, optimalMin: 24, unit: "mEq/L" },
4789
+ kind: "reference-interval",
5294
4790
  source: "tietz-7ed-2015"
5295
4791
  },
5296
4792
  Copper: {
5297
4793
  default: { max: 175, min: 70, optimalMax: 150, optimalMin: 85, unit: "mcg/dL" },
4794
+ kind: "reference-interval",
5298
4795
  source: "tietz-7ed-2015"
5299
4796
  },
5300
4797
  CoQ10: {
5301
4798
  default: { max: 1.5, min: 0.5, optimalMax: 1.3, optimalMin: 0.7, unit: "mg/L" },
4799
+ kind: "reference-interval",
5302
4800
  source: "tietz-7ed-2015"
5303
4801
  },
5304
4802
  Cortisol: {
5305
4803
  default: { max: 25, min: 5, optimalMax: 20, optimalMin: 10, unit: "mcg/dL" },
4804
+ kind: "reference-interval",
5306
4805
  source: "tietz-7ed-2015"
5307
4806
  },
5308
4807
  CortisolFree: {
5309
4808
  default: { max: 2.5, min: 0.5, optimalMax: 2, optimalMin: 0.8, unit: "mcg/dL" },
4809
+ kind: "reference-interval",
5310
4810
  source: "tietz-7ed-2015"
5311
4811
  },
5312
4812
  CPeptide: {
@@ -5318,10 +4818,12 @@ var biomarkerRangeDefinitions = {
5318
4818
  optimalMin: 1,
5319
4819
  unit: "ng/mL"
5320
4820
  },
4821
+ kind: "reference-interval",
5321
4822
  source: "tietz-7ed-2015"
5322
4823
  },
5323
4824
  Creatinine: {
5324
4825
  default: { max: 1.2, min: 0.6, optimalMax: 1, optimalMin: 0.7, unit: "mg/dL" },
4826
+ kind: "reference-interval",
5325
4827
  source: "pns-bioquimica-2019",
5326
4828
  variants: [
5327
4829
  // Gestação: hiperfiltração glomerular (↑ 40–50% GFR) reduz a creatinina
@@ -5347,10 +4849,12 @@ var biomarkerRangeDefinitions = {
5347
4849
  CRP: {
5348
4850
  default: { max: 3, min: 0, optimalMax: 1, optimalMin: 0, unit: "mg/L" },
5349
4851
  direction: "lower-better",
4852
+ kind: "decision-threshold",
5350
4853
  source: "tietz-7ed-2015"
5351
4854
  },
5352
4855
  CystatinC: {
5353
4856
  default: { max: 1, min: 0.5, optimalMax: 0.9, optimalMin: 0.6, unit: "mg/L" },
4857
+ kind: "reference-interval",
5354
4858
  source: "tietz-7ed-2015"
5355
4859
  },
5356
4860
  // D-Dímero — exame de exclusão de TEV (tromboembolismo venoso) com
@@ -5364,6 +4868,7 @@ var biomarkerRangeDefinitions = {
5364
4868
  DDimer: {
5365
4869
  default: { max: 500, min: 0, unit: "ng/mL" },
5366
4870
  direction: "lower-better",
4871
+ kind: "decision-threshold",
5367
4872
  source: "wells-ddimer-2003",
5368
4873
  variants: [
5369
4874
  // Corte ajustado por idade — ESC 2019 (Konstantinides et al.) e
@@ -5377,10 +4882,12 @@ var biomarkerRangeDefinitions = {
5377
4882
  },
5378
4883
  Omega3_DHA: {
5379
4884
  default: { max: 8, min: 2, optimalMax: 6.5, optimalMin: 3.5, unit: "%" },
4885
+ kind: "decision-threshold",
5380
4886
  source: "harris-omega3-2004"
5381
4887
  },
5382
4888
  DHEAS: {
5383
4889
  default: { max: 500, min: 100, optimalMax: 400, optimalMin: 150, unit: "mcg/dL" },
4890
+ kind: "reference-interval",
5384
4891
  source: "tietz-7ed-2015",
5385
4892
  variants: [
5386
4893
  {
@@ -5421,20 +4928,30 @@ var biomarkerRangeDefinitions = {
5421
4928
  },
5422
4929
  Omega3_DPA: {
5423
4930
  default: { max: 2, min: 0.3, optimalMax: 1.5, optimalMin: 0.5, unit: "%" },
4931
+ kind: "decision-threshold",
5424
4932
  source: "harris-omega3-2004"
5425
4933
  },
5426
4934
  eGFR: {
5427
- default: { max: 120, min: 60, optimalMax: 120, optimalMin: 90, unit: "mL/min/1.73m\xB2" },
4935
+ default: {
4936
+ max: 120,
4937
+ maxKind: "display",
4938
+ min: 60,
4939
+ optimalMax: 120,
4940
+ optimalMin: 90,
4941
+ unit: "mL/min/1.73m\xB2"
4942
+ },
5428
4943
  // KDIGO 2024: TFG 60-89 (G2) sem marcador de lesão renal não é DRC, em
5429
4944
  // nenhuma faixa etária. A variante por idade que existia aqui rebaixava o
5430
4945
  // piso só para 60+ e ainda limitava o teto a 90, o que sinalizava como
5431
4946
  // alterado qualquer idoso com função preservada.
4947
+ kind: "decision-threshold",
5432
4948
  source: "kdigo-ckd-2024"
5433
4949
  },
5434
4950
  // `Eosinophils` (%) fica sem faixa de propósito e `Eosinophils_Abs` usa os limites da
5435
4951
  // Tabela 2 da PNS 2019. Ver o comentário em `Neutrophils_Abs`.
5436
4952
  Eosinophils_Abs: {
5437
4953
  default: { max: 0.66, min: 0, unit: "K/uL" },
4954
+ kind: "reference-interval",
5438
4955
  source: "pns-hemograma-2019",
5439
4956
  variants: [
5440
4957
  { ageMin: 18, range: { max: 0.66, min: 0, unit: "K/uL" }, sex: "M" },
@@ -5443,18 +4960,22 @@ var biomarkerRangeDefinitions = {
5443
4960
  },
5444
4961
  Omega3_EPA: {
5445
4962
  default: { max: 3.5, min: 0.5, optimalMax: 2.5, optimalMin: 1, unit: "%" },
4963
+ kind: "decision-threshold",
5446
4964
  source: "harris-omega3-2004"
5447
4965
  },
5448
4966
  EPADPADHA: {
5449
4967
  default: { max: 10, min: 3, optimalMax: 9, optimalMin: 5, unit: "%" },
4968
+ kind: "decision-threshold",
5450
4969
  source: "harris-omega3-2004"
5451
4970
  },
5452
4971
  ESR: {
5453
4972
  default: { max: 20, min: 0, optimalMax: 10, optimalMin: 0, unit: "mm/hr" },
4973
+ kind: "reference-interval",
5454
4974
  source: "tietz-7ed-2015"
5455
4975
  },
5456
4976
  Estradiol: {
5457
4977
  default: { max: 40, min: 10, optimalMax: 35, optimalMin: 15, unit: "pg/mL" },
4978
+ kind: "reference-interval",
5458
4979
  source: "tietz-7ed-2015",
5459
4980
  variants: [
5460
4981
  {
@@ -5477,6 +4998,7 @@ var biomarkerRangeDefinitions = {
5477
4998
  },
5478
4999
  F2Isoprostanes: {
5479
5000
  default: { max: 86, min: 0, optimalMax: 60, optimalMin: 0, unit: "pg/mL" },
5001
+ kind: "reference-interval",
5480
5002
  source: "tietz-7ed-2015"
5481
5003
  },
5482
5004
  // Ferritina — WHO 2020 é o documento de referência específico para avaliação
@@ -5486,6 +5008,7 @@ var biomarkerRangeDefinitions = {
5486
5008
  // tipo de faixa, mas documentado aqui para consumidores).
5487
5009
  Ferritin: {
5488
5010
  default: { max: 150, min: 12, optimalMax: 120, optimalMin: 30, unit: "ng/mL" },
5011
+ kind: "decision-threshold",
5489
5012
  source: "who-iron-2020",
5490
5013
  variants: [
5491
5014
  // Gestação: ferritina cai fisiologicamente no 2º/3º trimestre pela expansão
@@ -5518,14 +5041,17 @@ var biomarkerRangeDefinitions = {
5518
5041
  // Fibrinogênio: 200-400 mg/dL — faixa de referência padrão (método de Clauss)
5519
5042
  Fibrinogen: {
5520
5043
  default: { max: 400, min: 200, optimalMax: 350, optimalMin: 250, unit: "mg/dL" },
5044
+ kind: "reference-interval",
5521
5045
  source: "tietz-7ed-2015"
5522
5046
  },
5523
5047
  FolicAcid: {
5524
5048
  default: { max: 20, min: 3, optimalMax: 15, optimalMin: 5, unit: "ng/mL" },
5049
+ kind: "reference-interval",
5525
5050
  source: "tietz-7ed-2015"
5526
5051
  },
5527
5052
  FSH: {
5528
5053
  default: { max: 12.4, min: 1.5, optimalMax: 10, optimalMin: 3, unit: "mIU/mL" },
5054
+ kind: "reference-interval",
5529
5055
  source: "tietz-7ed-2015",
5530
5056
  variants: [
5531
5057
  {
@@ -5549,26 +5075,29 @@ var biomarkerRangeDefinitions = {
5549
5075
  GGT: {
5550
5076
  default: { max: 55, min: 0, optimalMax: 30, optimalMin: 0, unit: "U/L" },
5551
5077
  direction: "lower-better",
5078
+ kind: "reference-interval",
5552
5079
  source: "tietz-7ed-2015",
5553
5080
  variants: [
5554
5081
  {
5555
5082
  ageMin: 18,
5556
- range: { max: 61, min: 8, optimalMax: 40, optimalMin: 10, unit: "U/L" },
5083
+ range: { max: 61, min: 8, minKind: "display", optimalMax: 40, optimalMin: 10, unit: "U/L" },
5557
5084
  sex: "M"
5558
5085
  },
5559
5086
  {
5560
5087
  ageMin: 18,
5561
- range: { max: 36, min: 5, optimalMax: 25, optimalMin: 8, unit: "U/L" },
5088
+ range: { max: 36, min: 5, minKind: "display", optimalMax: 25, optimalMin: 8, unit: "U/L" },
5562
5089
  sex: "F"
5563
5090
  }
5564
5091
  ]
5565
5092
  },
5566
5093
  Ghrelin: {
5567
5094
  default: { max: 1e3, min: 300, optimalMax: 800, optimalMin: 400, unit: "pg/mL" },
5095
+ kind: "reference-interval",
5568
5096
  source: "tietz-7ed-2015"
5569
5097
  },
5570
5098
  Globulin: {
5571
5099
  default: { max: 3.5, min: 2, optimalMax: 3.2, optimalMin: 2.3, unit: "g/dL" },
5100
+ kind: "reference-interval",
5572
5101
  source: "tietz-7ed-2015"
5573
5102
  },
5574
5103
  // A faixa é de jejum, então mora na definição que afirma jejum. Glicose
@@ -5591,6 +5120,7 @@ var biomarkerRangeDefinitions = {
5591
5120
  optimalMin: 70,
5592
5121
  unit: "mg/dL"
5593
5122
  },
5123
+ kind: "decision-threshold",
5594
5124
  source: "sbd-diabetes-2024",
5595
5125
  variants: [
5596
5126
  // Gestação: DMG (IADPSG/SBD 2024) usa cortes mais restritivos na glicemia
@@ -5612,10 +5142,12 @@ var biomarkerRangeDefinitions = {
5612
5142
  },
5613
5143
  GlycoMark: {
5614
5144
  default: { max: 40, min: 10, optimalMax: 35, optimalMin: 15, unit: "mcg/mL" },
5145
+ kind: "reference-interval",
5615
5146
  source: "tietz-7ed-2015"
5616
5147
  },
5617
5148
  GrowthHormone: {
5618
5149
  default: { max: 5, min: 0, optimalMax: 3, optimalMin: 0, unit: "ng/mL" },
5150
+ kind: "reference-interval",
5619
5151
  source: "tietz-7ed-2015"
5620
5152
  },
5621
5153
  // HbA1c — SBD 2024 não estabelece piso de referência clinicamente acionável.
@@ -5625,11 +5157,13 @@ var biomarkerRangeDefinitions = {
5625
5157
  // criar flag clínico em valores fisiologicamente baixos. optimalMin preserva
5626
5158
  // o alvo fisiológico da fração glicada.
5627
5159
  HbA1c: {
5628
- default: { max: 5.7, min: 2, optimalMax: 5.3, optimalMin: 4.5, unit: "%" },
5160
+ default: { max: 5.7, min: 2, minKind: "display", optimalMax: 5.3, optimalMin: 4.5, unit: "%" },
5161
+ kind: "decision-threshold",
5629
5162
  source: "sbd-diabetes-2024"
5630
5163
  },
5631
5164
  Hct: {
5632
5165
  default: { max: 50, min: 36, optimalMax: 48, optimalMin: 40, unit: "%" },
5166
+ kind: "reference-interval",
5633
5167
  source: "pns-hemograma-2019",
5634
5168
  variants: [
5635
5169
  {
@@ -5651,18 +5185,40 @@ var biomarkerRangeDefinitions = {
5651
5185
  // não como ponto de corte clínico; consumidores devem tratar `direction:
5652
5186
  // higher-better` como sinal autoritativo.
5653
5187
  HDL: {
5654
- default: { max: 100, min: 40, optimalMax: 100, optimalMin: 50, unit: "mg/dL" },
5188
+ default: {
5189
+ max: 100,
5190
+ maxKind: "display",
5191
+ min: 40,
5192
+ optimalMax: 100,
5193
+ optimalMin: 50,
5194
+ unit: "mg/dL"
5195
+ },
5655
5196
  direction: "higher-better",
5197
+ kind: "decision-threshold",
5656
5198
  source: "sbc-lipids-2025",
5657
5199
  variants: [
5658
5200
  {
5659
5201
  ageMin: 18,
5660
- range: { max: 100, min: 40, optimalMax: 100, optimalMin: 45, unit: "mg/dL" },
5202
+ range: {
5203
+ max: 100,
5204
+ maxKind: "display",
5205
+ min: 40,
5206
+ optimalMax: 100,
5207
+ optimalMin: 45,
5208
+ unit: "mg/dL"
5209
+ },
5661
5210
  sex: "M"
5662
5211
  },
5663
5212
  {
5664
5213
  ageMin: 18,
5665
- range: { max: 100, min: 50, optimalMax: 100, optimalMin: 55, unit: "mg/dL" },
5214
+ range: {
5215
+ max: 100,
5216
+ maxKind: "display",
5217
+ min: 50,
5218
+ optimalMax: 100,
5219
+ optimalMin: 55,
5220
+ unit: "mg/dL"
5221
+ },
5666
5222
  sex: "F"
5667
5223
  }
5668
5224
  ]
@@ -5670,12 +5226,21 @@ var biomarkerRangeDefinitions = {
5670
5226
  HDL_Large: {
5671
5227
  // HDL Large: higher is better
5672
5228
  // Quest Ion Mobility reference: Male 4334-10815, Female 5038-17886 nmol/L, optimal >6729
5673
- default: { max: 17886, min: 4334, optimalMax: 17886, optimalMin: 6729, unit: "nmol/L" },
5229
+ default: {
5230
+ max: 17886,
5231
+ maxKind: "display",
5232
+ min: 4334,
5233
+ optimalMax: 17886,
5234
+ optimalMin: 6729,
5235
+ unit: "nmol/L"
5236
+ },
5674
5237
  direction: "higher-better",
5238
+ kind: "reference-interval",
5675
5239
  source: "caulfield-ionmobility-2008"
5676
5240
  },
5677
5241
  Hgb: {
5678
5242
  default: { max: 17.5, min: 12, optimalMax: 16, optimalMin: 13.5, unit: "g/dL" },
5243
+ kind: "reference-interval",
5679
5244
  source: "pns-hemograma-2019",
5680
5245
  variants: [
5681
5246
  // Gestação: hemodiluição fisiológica reduz o piso aceitável.
@@ -5731,19 +5296,23 @@ var biomarkerRangeDefinitions = {
5731
5296
  warningMax: 2.71
5732
5297
  },
5733
5298
  direction: "lower-better",
5299
+ kind: "decision-threshold",
5734
5300
  source: "geloneze-brams-2009"
5735
5301
  },
5736
5302
  Homocysteine: {
5737
- default: { max: 15, min: 4, optimalMax: 10, optimalMin: 5, unit: "umol/L" },
5303
+ default: { max: 15, min: 4, minKind: "display", optimalMax: 10, optimalMin: 5, unit: "umol/L" },
5738
5304
  direction: "lower-better",
5305
+ kind: "reference-interval",
5739
5306
  source: "selhub-homocysteine-1999"
5740
5307
  },
5741
5308
  IGF1: {
5742
5309
  default: { max: 350, min: 100, optimalMax: 300, optimalMin: 150, unit: "ng/mL" },
5310
+ kind: "reference-interval",
5743
5311
  source: "tietz-7ed-2015"
5744
5312
  },
5745
5313
  ImmatureGranulocytes: {
5746
5314
  default: { max: 1, min: 0, optimalMax: 0.5, optimalMin: 0, unit: "%" },
5315
+ kind: "reference-interval",
5747
5316
  source: "tietz-7ed-2015"
5748
5317
  },
5749
5318
  Insulin: {
@@ -5755,10 +5324,12 @@ var biomarkerRangeDefinitions = {
5755
5324
  optimalMin: 3,
5756
5325
  unit: "uIU/mL"
5757
5326
  },
5327
+ kind: "reference-interval",
5758
5328
  source: "tietz-7ed-2015"
5759
5329
  },
5760
5330
  Iron: {
5761
5331
  default: { max: 170, min: 60, optimalMax: 140, optimalMin: 80, unit: "mcg/dL" },
5332
+ kind: "reference-interval",
5762
5333
  source: "sbpc-ml-2021",
5763
5334
  variants: [
5764
5335
  {
@@ -5775,6 +5346,7 @@ var biomarkerRangeDefinitions = {
5775
5346
  },
5776
5347
  Lactate: {
5777
5348
  default: { max: 2.2, min: 0.5, optimalMax: 1.8, optimalMin: 0.7, unit: "mmol/L" },
5349
+ kind: "reference-interval",
5778
5350
  source: "tietz-7ed-2015"
5779
5351
  },
5780
5352
  // DHL/LDH — método IFCC a 37 °C (padrão atual na maioria dos laboratórios
@@ -5786,6 +5358,7 @@ var biomarkerRangeDefinitions = {
5786
5358
  LDH: {
5787
5359
  default: { max: 248, min: 0, optimalMax: 248, optimalMin: 0, unit: "U/L" },
5788
5360
  direction: "lower-better",
5361
+ kind: "reference-interval",
5789
5362
  source: "schumann-ifcc-ldh-2002",
5790
5363
  variants: [
5791
5364
  {
@@ -5803,59 +5376,109 @@ var biomarkerRangeDefinitions = {
5803
5376
  LDL: {
5804
5377
  default: { max: 100, min: 0, optimalMax: 70, optimalMin: 0, unit: "mg/dL" },
5805
5378
  direction: "lower-better",
5379
+ kind: "decision-threshold",
5806
5380
  source: "sbc-lipids-2025"
5807
5381
  },
5808
5382
  LDL_Medium: {
5809
5383
  // LDL Medium (LDL Média): lower is better
5810
5384
  // Quest Ion Mobility reference: Male 167-485, Female 121-397 nmol/L, optimal <215
5811
- default: { max: 485, min: 121, optimalMax: 215, optimalMin: 121, unit: "nmol/L" },
5385
+ default: {
5386
+ max: 485,
5387
+ min: 121,
5388
+ minKind: "display",
5389
+ optimalMax: 215,
5390
+ optimalMin: 121,
5391
+ unit: "nmol/L"
5392
+ },
5812
5393
  direction: "lower-better",
5394
+ kind: "reference-interval",
5813
5395
  source: "caulfield-ionmobility-2008"
5814
5396
  },
5815
5397
  LDL_ParticleNumber: {
5816
5398
  // LDL Particle Number: lower is better
5817
5399
  // Quest Ion Mobility reference: 1016-2185 nmol/L, optimal <1138
5818
- default: { max: 2185, min: 1016, optimalMax: 1138, optimalMin: 1016, unit: "nmol/L" },
5400
+ default: {
5401
+ max: 2185,
5402
+ min: 1016,
5403
+ minKind: "display",
5404
+ optimalMax: 1138,
5405
+ optimalMin: 1016,
5406
+ unit: "nmol/L"
5407
+ },
5819
5408
  direction: "lower-better",
5409
+ kind: "reference-interval",
5820
5410
  source: "caulfield-ionmobility-2008"
5821
5411
  },
5822
5412
  LDL_Peak_Size: {
5823
5413
  // LDL Peak Size: higher is better (larger particles less atherogenic)
5824
5414
  // Quest Ion Mobility reference: optimal >222.9 Å (22.29 nm)
5825
- default: { max: 250, min: 217.4, optimalMax: 250, optimalMin: 222.9, unit: "Angstrom" },
5415
+ default: {
5416
+ max: 250,
5417
+ maxKind: "display",
5418
+ min: 217.4,
5419
+ optimalMax: 250,
5420
+ optimalMin: 222.9,
5421
+ unit: "Angstrom"
5422
+ },
5826
5423
  direction: "higher-better",
5424
+ kind: "reference-interval",
5827
5425
  source: "caulfield-ionmobility-2008"
5828
5426
  },
5829
5427
  LDL_Small: {
5830
5428
  // LDL Small (LDL Pequena): lower is better (small dense LDL is most atherogenic)
5831
5429
  // Quest Ion Mobility reference: Male 123-441, Female 126-382 nmol/L, optimal <142
5832
- default: { max: 441, min: 123, optimalMax: 142, optimalMin: 123, unit: "nmol/L" },
5430
+ default: {
5431
+ max: 441,
5432
+ min: 123,
5433
+ minKind: "display",
5434
+ optimalMax: 142,
5435
+ optimalMin: 123,
5436
+ unit: "nmol/L"
5437
+ },
5833
5438
  direction: "lower-better",
5439
+ kind: "reference-interval",
5834
5440
  source: "caulfield-ionmobility-2008"
5835
5441
  },
5836
5442
  Lead: {
5837
5443
  default: { max: 5, min: 0, optimalMax: 2, optimalMin: 0, unit: "mcg/dL" },
5444
+ kind: "decision-threshold",
5838
5445
  source: "nr7-pcmso-2020"
5839
5446
  },
5840
5447
  Leptin: {
5841
- default: { max: 15, min: 2, optimalMax: 12, optimalMin: 3, unit: "ng/mL" },
5448
+ default: { max: 15, min: 2, minKind: "clinical", optimalMax: 12, optimalMin: 3, unit: "ng/mL" },
5842
5449
  direction: "lower-better",
5450
+ kind: "reference-interval",
5843
5451
  source: "tietz-7ed-2015",
5844
5452
  variants: [
5845
5453
  {
5846
5454
  ageMin: 18,
5847
- range: { max: 10, min: 1, optimalMax: 8, optimalMin: 2, unit: "ng/mL" },
5455
+ range: {
5456
+ max: 10,
5457
+ min: 1,
5458
+ minKind: "clinical",
5459
+ optimalMax: 8,
5460
+ optimalMin: 2,
5461
+ unit: "ng/mL"
5462
+ },
5848
5463
  sex: "M"
5849
5464
  },
5850
5465
  {
5851
5466
  ageMin: 18,
5852
- range: { max: 25, min: 4, optimalMax: 18, optimalMin: 5, unit: "ng/mL" },
5467
+ range: {
5468
+ max: 25,
5469
+ min: 4,
5470
+ minKind: "clinical",
5471
+ optimalMax: 18,
5472
+ optimalMin: 5,
5473
+ unit: "ng/mL"
5474
+ },
5853
5475
  sex: "F"
5854
5476
  }
5855
5477
  ]
5856
5478
  },
5857
5479
  LH: {
5858
5480
  default: { max: 8.6, min: 1.7, optimalMax: 8, optimalMin: 2, unit: "mIU/mL" },
5481
+ kind: "reference-interval",
5859
5482
  source: "tietz-7ed-2015",
5860
5483
  variants: [
5861
5484
  {
@@ -5878,6 +5501,7 @@ var biomarkerRangeDefinitions = {
5878
5501
  },
5879
5502
  Omega6_LA: {
5880
5503
  default: { max: 35, min: 15, optimalMax: 28, optimalMin: 18, unit: "%" },
5504
+ kind: "decision-threshold",
5881
5505
  source: "simopoulos-omega-ratio-2002"
5882
5506
  },
5883
5507
  // Lp(a) — SBC 2025 (seção 3.1.1.2.7): níveis ≥ 50 mg/dL ou ≥ 125 nmol/L são
@@ -5889,12 +5513,14 @@ var biomarkerRangeDefinitions = {
5889
5513
  Lipoprotein_a: {
5890
5514
  default: { max: 125, min: 0, optimalMax: 75, optimalMin: 0, unit: "nmol/L" },
5891
5515
  direction: "lower-better",
5516
+ kind: "decision-threshold",
5892
5517
  source: "sbc-lipids-2025"
5893
5518
  },
5894
5519
  // `Lymphocytes` (%) fica sem faixa de propósito e `Lymphocytes_Abs` usa os limites da
5895
5520
  // Tabela 2 da PNS 2019. Ver o comentário em `Neutrophils_Abs`.
5896
5521
  Lymphocytes_Abs: {
5897
5522
  default: { max: 3.414, min: 0.72, unit: "K/uL" },
5523
+ kind: "reference-interval",
5898
5524
  source: "pns-hemograma-2019",
5899
5525
  variants: [
5900
5526
  { ageMin: 18, range: { max: 3.37, min: 0.72, unit: "K/uL" }, sex: "M" },
@@ -5903,40 +5529,49 @@ var biomarkerRangeDefinitions = {
5903
5529
  },
5904
5530
  Magnesium: {
5905
5531
  default: { max: 2.2, min: 1.7, optimalMax: 2.1, optimalMin: 1.9, unit: "mg/dL" },
5532
+ kind: "reference-interval",
5906
5533
  source: "tietz-7ed-2015"
5907
5534
  },
5908
5535
  Magnesium_RBC: {
5909
5536
  default: { max: 6.8, min: 4, optimalMax: 6, optimalMin: 4.5, unit: "mg/dL" },
5537
+ kind: "reference-interval",
5910
5538
  source: "tietz-7ed-2015"
5911
5539
  },
5912
5540
  MCH: {
5913
5541
  default: { max: 33, min: 27, optimalMax: 32, optimalMin: 28, unit: "pg" },
5542
+ kind: "reference-interval",
5914
5543
  source: "pns-hemograma-2019"
5915
5544
  },
5916
5545
  MCHC: {
5917
5546
  default: { max: 36, min: 32, optimalMax: 35, optimalMin: 33, unit: "g/dL" },
5547
+ kind: "reference-interval",
5918
5548
  source: "pns-hemograma-2019"
5919
5549
  },
5920
5550
  MCV: {
5921
5551
  default: { max: 100, min: 80, optimalMax: 98, optimalMin: 82, unit: "fL" },
5552
+ kind: "reference-interval",
5922
5553
  source: "pns-hemograma-2019"
5923
5554
  },
5924
5555
  Mercury: {
5925
5556
  default: { max: 10, min: 0, optimalMax: 5, optimalMin: 0, unit: "mcg/L" },
5557
+ kind: "decision-threshold",
5926
5558
  source: "nr7-pcmso-2020"
5927
5559
  },
5928
5560
  Microalbumin: {
5929
5561
  default: { max: 30, min: 0, optimalMax: 20, optimalMin: 0, unit: "mg/L" },
5562
+ kind: "decision-threshold",
5930
5563
  source: "kdigo-ckd-2024"
5931
5564
  },
5932
5565
  MMA: {
5933
5566
  default: { max: 378, min: 0, optimalMax: 270, optimalMin: 0, unit: "nmol/L" },
5567
+ kind: "reference-interval",
5934
5568
  source: "tietz-7ed-2015"
5935
5569
  },
5936
5570
  // `Monocytes` (%) fica sem faixa de propósito e `Monocytes_Abs` usa os limites da
5937
5571
  // Tabela 2 da PNS 2019. Ver o comentário em `Neutrophils_Abs`.
5938
5572
  Monocytes_Abs: {
5939
5573
  default: { max: 0.812, min: 0.011, unit: "K/uL" },
5574
+ kind: "reference-interval",
5940
5575
  source: "pns-hemograma-2019",
5941
5576
  variants: [
5942
5577
  { ageMin: 18, range: { max: 0.812, min: 0.011, unit: "K/uL" }, sex: "M" },
@@ -5945,11 +5580,13 @@ var biomarkerRangeDefinitions = {
5945
5580
  },
5946
5581
  MPV: {
5947
5582
  default: { max: 11.5, min: 7.5, optimalMax: 10.5, optimalMin: 8, unit: "fL" },
5583
+ kind: "reference-interval",
5948
5584
  source: "pns-hemograma-2019"
5949
5585
  },
5950
5586
  // MPO: Meuwese 2007 (EPIC-Norfolk) — risco CV elevado >322 pmol/L
5951
5587
  Myeloperoxidase: {
5952
5588
  default: { max: 470, min: 0, optimalMax: 322, optimalMin: 0, unit: "pmol/L" },
5589
+ kind: "decision-threshold",
5953
5590
  source: "meuwese-mpo-2007"
5954
5591
  },
5955
5592
  // Leucograma diferencial em percentual (`Neutrophils`, `Lymphocytes`,
@@ -5973,6 +5610,7 @@ var biomarkerRangeDefinitions = {
5973
5610
  // (menor mínimo e maior máximo entre homens e mulheres).
5974
5611
  Neutrophils_Abs: {
5975
5612
  default: { max: 6.474, min: 0.576, unit: "K/uL" },
5613
+ kind: "reference-interval",
5976
5614
  source: "pns-hemograma-2019",
5977
5615
  variants: [
5978
5616
  { ageMin: 18, range: { max: 5.971, min: 0.576, unit: "K/uL" }, sex: "M" },
@@ -5982,22 +5620,27 @@ var biomarkerRangeDefinitions = {
5982
5620
  NonHDL_Cholesterol: {
5983
5621
  default: { max: 130, min: 0, optimalMax: 100, optimalMin: 0, unit: "mg/dL" },
5984
5622
  direction: "lower-better",
5623
+ kind: "decision-threshold",
5985
5624
  source: "sbc-lipids-2025"
5986
5625
  },
5987
5626
  NRBC: {
5988
5627
  default: { max: 0, min: 0, optimalMax: 0, optimalMin: 0, unit: "/100WBC" },
5628
+ kind: "reference-interval",
5989
5629
  source: "tietz-7ed-2015"
5990
5630
  },
5991
5631
  NTproBNP: {
5992
5632
  default: { max: 125, min: 0, optimalMax: 75, optimalMin: 0, unit: "pg/mL" },
5633
+ kind: "decision-threshold",
5993
5634
  source: "sbc-ic-2018"
5994
5635
  },
5995
5636
  Oleic_Acid: {
5996
5637
  default: { max: 25, min: 15, optimalMax: 22, optimalMin: 18, unit: "%" },
5638
+ kind: "reference-interval",
5997
5639
  source: "tietz-7ed-2015"
5998
5640
  },
5999
5641
  Omega3_Index: {
6000
5642
  default: { max: 8, min: 4, optimalMax: 8, optimalMin: 5.5, unit: "%" },
5643
+ kind: "decision-threshold",
6001
5644
  source: "harris-omega3-2004"
6002
5645
  },
6003
5646
  // Ômega-3 Total (% de ácidos graxos eritrocitários) — Harris & von Schacky
@@ -6007,39 +5650,55 @@ var biomarkerRangeDefinitions = {
6007
5650
  // a literatura e o próprio texto educacional do `platform`.
6008
5651
  Omega3_Total: {
6009
5652
  default: { max: 12, min: 3, optimalMax: 10, optimalMin: 8, unit: "%" },
5653
+ kind: "decision-threshold",
6010
5654
  source: "harris-omega3-2004"
6011
5655
  },
6012
5656
  Omega6_Total: {
6013
5657
  default: { max: 40, min: 20, optimalMax: 35, optimalMin: 25, unit: "%" },
5658
+ kind: "decision-threshold",
6014
5659
  source: "simopoulos-omega-ratio-2002"
6015
5660
  },
6016
5661
  Omega6_Omega3_Ratio: {
6017
- default: { max: 10, min: 1, optimalMax: 4, optimalMin: 1, unit: "" },
5662
+ default: {
5663
+ max: 10,
5664
+ min: 1,
5665
+ minKind: "display",
5666
+ optimalMax: 4,
5667
+ optimalMin: 1,
5668
+ unit: ""
5669
+ },
6018
5670
  direction: "lower-better",
5671
+ kind: "decision-threshold",
6019
5672
  source: "simopoulos-omega-ratio-2002"
6020
5673
  },
6021
5674
  OmegaCheck: {
6022
5675
  default: { max: 8, min: 4, optimalMax: 8, optimalMin: 5.5, unit: "%" },
5676
+ kind: "decision-threshold",
6023
5677
  source: "harris-omega3-2004"
6024
5678
  },
6025
5679
  Palmitic_Acid: {
6026
5680
  default: { max: 30, min: 20, optimalMax: 27, optimalMin: 22, unit: "%" },
5681
+ kind: "reference-interval",
6027
5682
  source: "tietz-7ed-2015"
6028
5683
  },
6029
5684
  Phosphorus: {
6030
5685
  default: { max: 4.5, min: 2.5, optimalMax: 4, optimalMin: 3, unit: "mg/dL" },
5686
+ kind: "reference-interval",
6031
5687
  source: "tietz-7ed-2015"
6032
5688
  },
6033
5689
  Platelets: {
6034
5690
  default: { max: 400, min: 150, optimalMax: 350, optimalMin: 180, unit: "K/uL" },
5691
+ kind: "reference-interval",
6035
5692
  source: "pns-hemograma-2019"
6036
5693
  },
6037
5694
  Potassium: {
6038
5695
  default: { max: 5, min: 3.5, optimalMax: 4.6, optimalMin: 3.8, unit: "mEq/L" },
5696
+ kind: "reference-interval",
6039
5697
  source: "tietz-7ed-2015"
6040
5698
  },
6041
5699
  Prealbumin: {
6042
5700
  default: { max: 38, min: 18, optimalMax: 35, optimalMin: 20, unit: "mg/dL" },
5701
+ kind: "reference-interval",
6043
5702
  source: "tietz-7ed-2015"
6044
5703
  },
6045
5704
  // Progesterona — varia drasticamente com a fase do ciclo menstrual.
@@ -6050,6 +5709,7 @@ var biomarkerRangeDefinitions = {
6050
5709
  // ciclo, indisponível neste contexto.
6051
5710
  Progesterone: {
6052
5711
  default: { max: 0.9, min: 0.1, optimalMax: 0.7, optimalMin: 0.2, unit: "ng/mL" },
5712
+ kind: "reference-interval",
6053
5713
  source: "tietz-7ed-2015",
6054
5714
  variants: [
6055
5715
  {
@@ -6076,6 +5736,7 @@ var biomarkerRangeDefinitions = {
6076
5736
  },
6077
5737
  Prolactin: {
6078
5738
  default: { max: 18, min: 2, optimalMax: 15, optimalMin: 4, unit: "ng/mL" },
5739
+ kind: "reference-interval",
6079
5740
  source: "tietz-7ed-2015",
6080
5741
  variants: [
6081
5742
  {
@@ -6092,6 +5753,7 @@ var biomarkerRangeDefinitions = {
6092
5753
  },
6093
5754
  PSA: {
6094
5755
  default: { max: 4, min: 0, optimalMax: 2.5, optimalMin: 0, unit: "ng/mL" },
5756
+ kind: "reference-interval",
6095
5757
  source: "sturgeon-nacb-2008",
6096
5758
  variants: [
6097
5759
  {
@@ -6121,6 +5783,7 @@ var biomarkerRangeDefinitions = {
6121
5783
  },
6122
5784
  RBC: {
6123
5785
  default: { max: 5.5, min: 4, optimalMax: 5.2, optimalMin: 4.3, unit: "M/uL" },
5786
+ kind: "reference-interval",
6124
5787
  source: "pns-hemograma-2019",
6125
5788
  variants: [
6126
5789
  {
@@ -6137,22 +5800,27 @@ var biomarkerRangeDefinitions = {
6137
5800
  },
6138
5801
  RDW: {
6139
5802
  default: { max: 14.5, min: 11.5, optimalMax: 14, optimalMin: 12, unit: "%" },
5803
+ kind: "reference-interval",
6140
5804
  source: "pns-hemograma-2019"
6141
5805
  },
6142
5806
  Reticulocytes: {
6143
5807
  default: { max: 2.5, min: 0.5, optimalMax: 2, optimalMin: 0.8, unit: "%" },
5808
+ kind: "reference-interval",
6144
5809
  source: "pns-hemograma-2019"
6145
5810
  },
6146
5811
  SDMA: {
6147
5812
  default: { max: 0.6, min: 0.3, optimalMax: 0.5, optimalMin: 0.3, unit: "umol/L" },
5813
+ kind: "reference-interval",
6148
5814
  source: "schwedhelm-sdma-2011"
6149
5815
  },
6150
5816
  Selenium: {
6151
5817
  default: { max: 150, min: 70, optimalMax: 125, optimalMin: 85, unit: "\xB5g/L" },
5818
+ kind: "reference-interval",
6152
5819
  source: "tietz-7ed-2015"
6153
5820
  },
6154
5821
  SHBG: {
6155
5822
  default: { max: 54, min: 18, optimalMax: 50, optimalMin: 20, unit: "nmol/L" },
5823
+ kind: "reference-interval",
6156
5824
  source: "tietz-7ed-2015",
6157
5825
  variants: [
6158
5826
  {
@@ -6169,34 +5837,42 @@ var biomarkerRangeDefinitions = {
6169
5837
  },
6170
5838
  Sodium: {
6171
5839
  default: { max: 145, min: 136, optimalMax: 143, optimalMin: 138, unit: "mEq/L" },
5840
+ kind: "reference-interval",
6172
5841
  source: "tietz-7ed-2015"
6173
5842
  },
6174
5843
  SpecificGravity_Urine: {
6175
5844
  default: { max: 1.03, min: 1.005, optimalMax: 1.025, optimalMin: 1.01, unit: "SG" },
5845
+ kind: "reference-interval",
6176
5846
  source: "tietz-7ed-2015"
6177
5847
  },
6178
5848
  Stearic_Acid: {
6179
5849
  default: { max: 14, min: 8, optimalMax: 12, optimalMin: 10, unit: "%" },
5850
+ kind: "reference-interval",
6180
5851
  source: "tietz-7ed-2015"
6181
5852
  },
6182
5853
  T3Free: {
6183
5854
  default: { max: 4.2, min: 2.3, optimalMax: 3.8, optimalMin: 2.8, unit: "pg/mL" },
5855
+ kind: "reference-interval",
6184
5856
  source: "tietz-7ed-2015"
6185
5857
  },
6186
5858
  T3Reverse: {
6187
5859
  default: { max: 24, min: 10, optimalMax: 20, optimalMin: 12, unit: "ng/dL" },
5860
+ kind: "reference-interval",
6188
5861
  source: "tietz-7ed-2015"
6189
5862
  },
6190
5863
  T4Free: {
6191
5864
  default: { max: 1.8, min: 0.8, optimalMax: 1.5, optimalMin: 1, unit: "ng/dL" },
5865
+ kind: "reference-interval",
6192
5866
  source: "tietz-7ed-2015"
6193
5867
  },
6194
5868
  T4Total: {
6195
5869
  default: { max: 12, min: 4.5, optimalMax: 10, optimalMin: 6, unit: "ug/dL" },
5870
+ kind: "reference-interval",
6196
5871
  source: "tietz-7ed-2015"
6197
5872
  },
6198
5873
  Testosterone: {
6199
5874
  default: { max: 1e3, min: 300, optimalMax: 800, optimalMin: 500, unit: "ng/dL" },
5875
+ kind: "reference-interval",
6200
5876
  source: "tietz-7ed-2015",
6201
5877
  variants: [
6202
5878
  {
@@ -6219,12 +5895,14 @@ var biomarkerRangeDefinitions = {
6219
5895
  },
6220
5896
  TestosteroneBioavailable: {
6221
5897
  default: { max: 200, min: 50, optimalMax: 150, optimalMin: 80, unit: "ng/dL" },
5898
+ kind: "reference-interval",
6222
5899
  source: "tietz-7ed-2015"
6223
5900
  },
6224
5901
  // TestosteroneFree - using direct immunoassay ranges (Quest/LabCorp standard)
6225
5902
  // Previous values (9.3-26.5 pg/mL) were based on equilibrium dialysis method
6226
5903
  TestosteroneFree: {
6227
5904
  default: { max: 155, min: 35, optimalMax: 120, optimalMin: 50, unit: "pg/mL" },
5905
+ kind: "reference-interval",
6228
5906
  source: "tietz-7ed-2015",
6229
5907
  variants: [
6230
5908
  {
@@ -6247,22 +5925,27 @@ var biomarkerRangeDefinitions = {
6247
5925
  },
6248
5926
  TIBC: {
6249
5927
  default: { max: 370, min: 250, optimalMax: 350, optimalMin: 280, unit: "mcg/dL" },
5928
+ kind: "reference-interval",
6250
5929
  source: "tietz-7ed-2015"
6251
5930
  },
6252
5931
  TotalProtein: {
6253
5932
  default: { max: 8.3, min: 6, optimalMax: 7.8, optimalMin: 6.5, unit: "g/dL" },
5933
+ kind: "reference-interval",
6254
5934
  source: "tietz-7ed-2015"
6255
5935
  },
6256
5936
  Trans_Fat_Index: {
6257
5937
  default: { max: 1, min: 0, optimalMax: 0.5, optimalMin: 0, unit: "%" },
5938
+ kind: "reference-interval",
6258
5939
  source: "tietz-7ed-2015"
6259
5940
  },
6260
5941
  Transferrin: {
6261
5942
  default: { max: 360, min: 200, optimalMax: 340, optimalMin: 220, unit: "mg/dL" },
5943
+ kind: "reference-interval",
6262
5944
  source: "tietz-7ed-2015"
6263
5945
  },
6264
5946
  TransferrinSaturation: {
6265
5947
  default: { max: 50, min: 20, optimalMax: 45, optimalMin: 25, unit: "%" },
5948
+ kind: "reference-interval",
6266
5949
  source: "tietz-7ed-2015",
6267
5950
  variants: [
6268
5951
  {
@@ -6290,16 +5973,19 @@ var biomarkerRangeDefinitions = {
6290
5973
  unit: "mg/dL"
6291
5974
  },
6292
5975
  direction: "lower-better",
5976
+ kind: "decision-threshold",
6293
5977
  source: "sbc-lipids-2025"
6294
5978
  },
6295
5979
  // TroponinI: 0.04 ng/mL = percentil 99 (ensaio Siemens TnI-Ultra)
6296
5980
  TroponinI: {
6297
5981
  default: { max: 0.04, min: 0, optimalMax: 0.02, optimalMin: 0, unit: "ng/mL" },
5982
+ kind: "reference-interval",
6298
5983
  source: "keller-tni-2013"
6299
5984
  },
6300
5985
  // TroponinT: 14 ng/L = percentil 99 hs-cTnT (ensaio Roche Elecsys 5ª geração)
6301
5986
  TroponinT: {
6302
5987
  default: { max: 14, min: 0, optimalMax: 10, optimalMin: 0, unit: "ng/L" },
5988
+ kind: "reference-interval",
6303
5989
  source: "giannitsis-hstnt-2010"
6304
5990
  },
6305
5991
  // TSH — 0.4–4.0 é a faixa de referência adulta geral. O alvo 2.5 µIU/mL é
@@ -6309,6 +5995,7 @@ var biomarkerRangeDefinitions = {
6309
5995
  // expandido (tolerância fisiológica do eixo em idosos, SBEM 2013).
6310
5996
  TSH: {
6311
5997
  default: { max: 4, min: 0.4, optimalMax: 3, optimalMin: 1, unit: "uIU/mL" },
5998
+ kind: "reference-interval",
6312
5999
  source: "sbem-thyroid-2013",
6313
6000
  variants: [
6314
6001
  // Variantes gestacionais (ATA 2017 / SBEM): supressão fisiológica por hCG
@@ -6357,10 +6044,12 @@ var biomarkerRangeDefinitions = {
6357
6044
  // deve ser revisto em biomarkers.ts — ver issue #41.
6358
6045
  Urea: {
6359
6046
  default: { max: 50, min: 15, optimalMax: 40, optimalMin: 20, unit: "mg/dL" },
6047
+ kind: "reference-interval",
6360
6048
  source: "tietz-7ed-2015"
6361
6049
  },
6362
6050
  UricAcid: {
6363
6051
  default: { max: 7, min: 2.5, optimalMax: 5.5, optimalMin: 3, unit: "mg/dL" },
6052
+ kind: "reference-interval",
6364
6053
  source: "tietz-7ed-2015",
6365
6054
  variants: [
6366
6055
  {
@@ -6377,50 +6066,62 @@ var biomarkerRangeDefinitions = {
6377
6066
  },
6378
6067
  Urobilinogen_Urine: {
6379
6068
  default: { max: 1, min: 0.1, optimalMax: 1, optimalMin: 0.1, unit: "mg/dL" },
6069
+ kind: "reference-interval",
6380
6070
  source: "tietz-7ed-2015"
6381
6071
  },
6382
6072
  pH_Urine: {
6383
6073
  default: { max: 8, min: 4.5, optimalMax: 7, optimalMin: 5.5, unit: "pH" },
6074
+ kind: "reference-interval",
6384
6075
  source: "tietz-7ed-2015"
6385
6076
  },
6386
6077
  HyalineCasts_Urine: {
6387
6078
  default: { max: 2, min: 0, optimalMax: 1, optimalMin: 0, unit: "/LPF" },
6079
+ kind: "reference-interval",
6388
6080
  source: "tietz-7ed-2015"
6389
6081
  },
6390
6082
  RBC_Urine: {
6391
6083
  default: { max: 3, min: 0, optimalMax: 1, optimalMin: 0, unit: "/HPF" },
6084
+ kind: "reference-interval",
6392
6085
  source: "tietz-7ed-2015"
6393
6086
  },
6394
6087
  SquamousEpithelial_Urine: {
6395
6088
  default: { max: 15, min: 0, optimalMax: 5, optimalMin: 0, unit: "/HPF" },
6089
+ kind: "reference-interval",
6396
6090
  source: "tietz-7ed-2015"
6397
6091
  },
6398
6092
  Leukocytes_Urine: {
6399
6093
  default: { max: 5, min: 0, optimalMax: 2, optimalMin: 0, unit: "/HPF" },
6094
+ kind: "reference-interval",
6400
6095
  source: "tietz-7ed-2015"
6401
6096
  },
6402
6097
  Folate: {
6403
6098
  default: { max: 20, min: 3.9, optimalMax: 17, optimalMin: 5, unit: "ng/mL" },
6099
+ kind: "reference-interval",
6404
6100
  source: "tietz-7ed-2015"
6405
6101
  },
6406
6102
  VitaminA: {
6407
6103
  default: { max: 100, min: 20, optimalMax: 80, optimalMin: 30, unit: "mcg/dL" },
6104
+ kind: "reference-interval",
6408
6105
  source: "tietz-7ed-2015"
6409
6106
  },
6410
6107
  VitaminB1: {
6411
6108
  default: { max: 180, min: 70, optimalMax: 150, optimalMin: 80, unit: "nmol/L" },
6109
+ kind: "reference-interval",
6412
6110
  source: "tietz-7ed-2015"
6413
6111
  },
6414
6112
  VitaminB12: {
6415
6113
  default: { max: 900, min: 200, optimalMax: 800, optimalMin: 400, unit: "pg/mL" },
6114
+ kind: "reference-interval",
6416
6115
  source: "tietz-7ed-2015"
6417
6116
  },
6418
6117
  VitaminB6: {
6419
6118
  default: { max: 50, min: 5, optimalMax: 40, optimalMin: 10, unit: "ng/mL" },
6119
+ kind: "reference-interval",
6420
6120
  source: "tietz-7ed-2015"
6421
6121
  },
6422
6122
  VitaminC: {
6423
6123
  default: { max: 2, min: 0.4, optimalMax: 1.5, optimalMin: 0.6, unit: "mg/dL" },
6124
+ kind: "reference-interval",
6424
6125
  source: "tietz-7ed-2015"
6425
6126
  },
6426
6127
  // Vitamina D — posicionamento conjunto SBEM/SBPC-ML 2017:
@@ -6437,6 +6138,7 @@ var biomarkerRangeDefinitions = {
6437
6138
  // quando a modelagem de comorbidades chegar ao schema.
6438
6139
  VitaminD: {
6439
6140
  default: { max: 100, min: 20, optimalMax: 70, optimalMin: 40, unit: "ng/mL" },
6141
+ kind: "decision-threshold",
6440
6142
  source: "ferreira-vitd-2017",
6441
6143
  variants: [
6442
6144
  {
@@ -6448,10 +6150,12 @@ var biomarkerRangeDefinitions = {
6448
6150
  },
6449
6151
  VitaminD_1_25: {
6450
6152
  default: { max: 72, min: 18, optimalMax: 60, optimalMin: 25, unit: "pg/mL" },
6153
+ kind: "reference-interval",
6451
6154
  source: "tietz-7ed-2015"
6452
6155
  },
6453
6156
  VitaminE: {
6454
6157
  default: { max: 17, min: 5.5, optimalMax: 14, optimalMin: 7, unit: "mg/L" },
6158
+ kind: "reference-interval",
6455
6159
  source: "tietz-7ed-2015"
6456
6160
  },
6457
6161
  // VLDL — estimado clinicamente via TG/5 (método Friedewald). O corte de
@@ -6460,30 +6164,34 @@ var biomarkerRangeDefinitions = {
6460
6164
  // método de cálculo, mas a faixa de referência tem respaldo na diretriz
6461
6165
  // SBC 2025.
6462
6166
  VLDL: {
6463
- default: { max: 30, min: 2, optimalMax: 20, optimalMin: 5, unit: "mg/dL" },
6167
+ default: { max: 30, min: 2, minKind: "display", optimalMax: 20, optimalMin: 5, unit: "mg/dL" },
6168
+ kind: "decision-threshold",
6464
6169
  source: "sbc-lipids-2025"
6465
6170
  },
6466
6171
  WBC: {
6467
6172
  default: { max: 11, min: 4, optimalMax: 8, optimalMin: 5, unit: "K/uL" },
6173
+ kind: "reference-interval",
6468
6174
  source: "pns-hemograma-2019"
6469
6175
  },
6470
6176
  Zinc: {
6471
6177
  default: { max: 120, min: 60, optimalMax: 100, optimalMin: 70, unit: "mcg/dL" },
6178
+ kind: "reference-interval",
6472
6179
  source: "tietz-7ed-2015"
6473
6180
  },
6474
6181
  AndroidFatPct: {
6475
- default: { max: 35, min: 10, optimalMax: 25, optimalMin: 15, unit: "%" },
6182
+ default: { max: 35, min: 10, minKind: "display", optimalMax: 25, optimalMin: 15, unit: "%" },
6476
6183
  direction: "lower-better",
6184
+ kind: "population",
6477
6185
  source: "kelly-dxa-2009",
6478
6186
  variants: [
6479
6187
  {
6480
6188
  ageMin: 18,
6481
- range: { max: 30, min: 10, optimalMax: 22, optimalMin: 12, unit: "%" },
6189
+ range: { max: 30, min: 10, minKind: "display", optimalMax: 22, optimalMin: 12, unit: "%" },
6482
6190
  sex: "M"
6483
6191
  },
6484
6192
  {
6485
6193
  ageMin: 18,
6486
- range: { max: 42, min: 15, optimalMax: 32, optimalMin: 20, unit: "%" },
6194
+ range: { max: 42, min: 15, minKind: "display", optimalMax: 32, optimalMin: 20, unit: "%" },
6487
6195
  sex: "F"
6488
6196
  }
6489
6197
  ]
@@ -6534,6 +6242,7 @@ var biomarkerRangeDefinitions = {
6534
6242
  CalfCircumference: {
6535
6243
  default: { unit: "cm" },
6536
6244
  direction: "higher-better",
6245
+ kind: "decision-threshold",
6537
6246
  source: "ewgsop2-2019",
6538
6247
  // O corte não é específico por sexo na fonte; as duas variantes existem
6539
6248
  // porque `RangeVariant` exige o campo, e carregam o mesmo número.
@@ -6545,38 +6254,76 @@ var biomarkerRangeDefinitions = {
6545
6254
  WaistToHeightRatio: {
6546
6255
  default: { max: 0.5, unit: "" },
6547
6256
  direction: "lower-better",
6257
+ kind: "decision-threshold",
6548
6258
  source: "browning-ashwell-2010"
6549
6259
  },
6550
6260
  AndroidGynoidRatio: {
6551
- default: { max: 1.2, min: 0.5, optimalMax: 1, optimalMin: 0.6, unit: "" },
6261
+ default: { max: 1.2, min: 0.5, minKind: "display", optimalMax: 1, optimalMin: 0.6, unit: "" },
6552
6262
  direction: "lower-better",
6263
+ kind: "population",
6553
6264
  source: "kelly-dxa-2009",
6554
6265
  variants: [
6555
6266
  {
6556
6267
  ageMin: 18,
6557
- range: { max: 1.2, min: 0.6, optimalMax: 1, optimalMin: 0.7, unit: "" },
6268
+ range: {
6269
+ max: 1.2,
6270
+ min: 0.6,
6271
+ minKind: "display",
6272
+ optimalMax: 1,
6273
+ optimalMin: 0.7,
6274
+ unit: ""
6275
+ },
6558
6276
  sex: "M"
6559
6277
  },
6560
6278
  {
6561
6279
  ageMin: 18,
6562
- range: { max: 1, min: 0.4, optimalMax: 0.8, optimalMin: 0.5, unit: "" },
6280
+ range: {
6281
+ max: 1,
6282
+ min: 0.4,
6283
+ minKind: "display",
6284
+ optimalMax: 0.8,
6285
+ optimalMin: 0.5,
6286
+ unit: ""
6287
+ },
6563
6288
  sex: "F"
6564
6289
  }
6565
6290
  ]
6566
6291
  },
6567
6292
  BMC: {
6568
- default: { max: 3.5, min: 2, optimalMax: 3.2, optimalMin: 2.3, unit: "kg" },
6293
+ default: {
6294
+ max: 3.5,
6295
+ maxKind: "display",
6296
+ min: 2,
6297
+ optimalMax: 3.2,
6298
+ optimalMin: 2.3,
6299
+ unit: "kg"
6300
+ },
6569
6301
  direction: "higher-better",
6302
+ kind: "population",
6570
6303
  source: "kelly-dxa-2009",
6571
6304
  variants: [
6572
6305
  {
6573
6306
  ageMin: 18,
6574
- range: { max: 4, min: 2.5, optimalMax: 3.5, optimalMin: 2.8, unit: "kg" },
6307
+ range: {
6308
+ max: 4,
6309
+ maxKind: "display",
6310
+ min: 2.5,
6311
+ optimalMax: 3.5,
6312
+ optimalMin: 2.8,
6313
+ unit: "kg"
6314
+ },
6575
6315
  sex: "M"
6576
6316
  },
6577
6317
  {
6578
6318
  ageMin: 18,
6579
- range: { max: 3, min: 1.8, optimalMax: 2.7, optimalMin: 2, unit: "kg" },
6319
+ range: {
6320
+ max: 3,
6321
+ maxKind: "display",
6322
+ min: 1.8,
6323
+ optimalMax: 2.7,
6324
+ optimalMin: 2,
6325
+ unit: "kg"
6326
+ },
6580
6327
  sex: "F"
6581
6328
  }
6582
6329
  ]
@@ -6584,142 +6331,148 @@ var biomarkerRangeDefinitions = {
6584
6331
  // BodyFatPct — Age-bracketed ranges from Gallagher et al. Am J Clin Nutr 2000;72:694-701
6585
6332
  // (PMID: 10966886) and ACSM Guidelines for Exercise Testing, 11th Ed (2021)
6586
6333
  BodyFatPct: {
6587
- default: { max: 30, min: 10, optimalMax: 25, optimalMin: 15, unit: "%" },
6334
+ default: { max: 30, min: 10, minKind: "clinical", optimalMax: 25, optimalMin: 15, unit: "%" },
6588
6335
  direction: "lower-better",
6336
+ kind: "decision-threshold",
6589
6337
  source: "gallagher-bodyfat-2000",
6590
6338
  variants: [
6591
6339
  // Men — age brackets
6592
6340
  {
6593
6341
  ageMax: 25,
6594
6342
  ageMin: 18,
6595
- range: { max: 25, min: 5, optimalMax: 20, optimalMin: 10, unit: "%" },
6343
+ range: { max: 25, min: 5, minKind: "clinical", optimalMax: 20, optimalMin: 10, unit: "%" },
6596
6344
  sex: "M"
6597
6345
  },
6598
6346
  {
6599
6347
  ageMax: 35,
6600
6348
  ageMin: 26,
6601
- range: { max: 26, min: 5, optimalMax: 21, optimalMin: 11, unit: "%" },
6349
+ range: { max: 26, min: 5, minKind: "clinical", optimalMax: 21, optimalMin: 11, unit: "%" },
6602
6350
  sex: "M"
6603
6351
  },
6604
6352
  {
6605
6353
  ageMax: 45,
6606
6354
  ageMin: 36,
6607
- range: { max: 27, min: 5, optimalMax: 22, optimalMin: 12, unit: "%" },
6355
+ range: { max: 27, min: 5, minKind: "clinical", optimalMax: 22, optimalMin: 12, unit: "%" },
6608
6356
  sex: "M"
6609
6357
  },
6610
6358
  {
6611
6359
  ageMax: 55,
6612
6360
  ageMin: 46,
6613
- range: { max: 28, min: 5, optimalMax: 23, optimalMin: 13, unit: "%" },
6361
+ range: { max: 28, min: 5, minKind: "clinical", optimalMax: 23, optimalMin: 13, unit: "%" },
6614
6362
  sex: "M"
6615
6363
  },
6616
6364
  {
6617
6365
  ageMin: 56,
6618
- range: { max: 29, min: 5, optimalMax: 24, optimalMin: 14, unit: "%" },
6366
+ range: { max: 29, min: 5, minKind: "clinical", optimalMax: 24, optimalMin: 14, unit: "%" },
6619
6367
  sex: "M"
6620
6368
  },
6621
6369
  // Women — age brackets
6622
6370
  {
6623
6371
  ageMax: 25,
6624
6372
  ageMin: 18,
6625
- range: { max: 32, min: 13, optimalMax: 28, optimalMin: 18, unit: "%" },
6373
+ range: { max: 32, min: 13, minKind: "clinical", optimalMax: 28, optimalMin: 18, unit: "%" },
6626
6374
  sex: "F"
6627
6375
  },
6628
6376
  {
6629
6377
  ageMax: 35,
6630
6378
  ageMin: 26,
6631
- range: { max: 33, min: 13, optimalMax: 29, optimalMin: 18, unit: "%" },
6379
+ range: { max: 33, min: 13, minKind: "clinical", optimalMax: 29, optimalMin: 18, unit: "%" },
6632
6380
  sex: "F"
6633
6381
  },
6634
6382
  {
6635
6383
  ageMax: 45,
6636
6384
  ageMin: 36,
6637
- range: { max: 34, min: 13, optimalMax: 30, optimalMin: 19, unit: "%" },
6385
+ range: { max: 34, min: 13, minKind: "clinical", optimalMax: 30, optimalMin: 19, unit: "%" },
6638
6386
  sex: "F"
6639
6387
  },
6640
6388
  {
6641
6389
  ageMax: 55,
6642
6390
  ageMin: 46,
6643
- range: { max: 35, min: 13, optimalMax: 31, optimalMin: 20, unit: "%" },
6391
+ range: { max: 35, min: 13, minKind: "clinical", optimalMax: 31, optimalMin: 20, unit: "%" },
6644
6392
  sex: "F"
6645
6393
  },
6646
6394
  {
6647
6395
  ageMin: 56,
6648
- range: { max: 36, min: 13, optimalMax: 32, optimalMin: 20, unit: "%" },
6396
+ range: { max: 36, min: 13, minKind: "clinical", optimalMax: 32, optimalMin: 20, unit: "%" },
6649
6397
  sex: "F"
6650
6398
  }
6651
6399
  ]
6652
6400
  },
6653
6401
  FatFreeMass: {
6654
- default: { max: 80, min: 40, optimalMax: 70, optimalMin: 45, unit: "kg" },
6402
+ default: { max: 80, maxKind: "display", min: 40, optimalMax: 70, optimalMin: 45, unit: "kg" },
6655
6403
  direction: "higher-better",
6404
+ kind: "population",
6656
6405
  source: "kelly-dxa-2009",
6657
6406
  variants: [
6658
6407
  {
6659
6408
  ageMin: 18,
6660
- range: { max: 90, min: 50, optimalMax: 80, optimalMin: 55, unit: "kg" },
6409
+ range: { max: 90, maxKind: "display", min: 50, optimalMax: 80, optimalMin: 55, unit: "kg" },
6661
6410
  sex: "M"
6662
6411
  },
6663
6412
  {
6664
6413
  ageMin: 18,
6665
- range: { max: 60, min: 35, optimalMax: 52, optimalMin: 38, unit: "kg" },
6414
+ range: { max: 60, maxKind: "display", min: 35, optimalMax: 52, optimalMin: 38, unit: "kg" },
6666
6415
  sex: "F"
6667
6416
  }
6668
6417
  ]
6669
6418
  },
6670
6419
  FatMass: {
6671
- default: { max: 30, min: 5, optimalMax: 20, optimalMin: 8, unit: "kg" },
6420
+ default: { max: 30, min: 5, minKind: "clinical", optimalMax: 20, optimalMin: 8, unit: "kg" },
6672
6421
  direction: "lower-better",
6422
+ kind: "population",
6673
6423
  source: "kelly-dxa-2009",
6674
6424
  variants: [
6675
6425
  {
6676
6426
  ageMin: 18,
6677
- range: { max: 25, min: 5, optimalMax: 18, optimalMin: 7, unit: "kg" },
6427
+ range: { max: 25, min: 5, minKind: "clinical", optimalMax: 18, optimalMin: 7, unit: "kg" },
6678
6428
  sex: "M"
6679
6429
  },
6680
6430
  {
6681
6431
  ageMin: 18,
6682
- range: { max: 30, min: 8, optimalMax: 22, optimalMin: 10, unit: "kg" },
6432
+ range: { max: 30, min: 8, minKind: "clinical", optimalMax: 22, optimalMin: 10, unit: "kg" },
6683
6433
  sex: "F"
6684
6434
  }
6685
6435
  ]
6686
6436
  },
6687
6437
  GynoidFatPct: {
6688
- default: { max: 45, min: 15, optimalMax: 35, optimalMin: 20, unit: "%" },
6438
+ default: { max: 45, min: 15, minKind: "display", optimalMax: 35, optimalMin: 20, unit: "%" },
6689
6439
  direction: "lower-better",
6440
+ kind: "population",
6690
6441
  source: "kelly-dxa-2009",
6691
6442
  variants: [
6692
6443
  {
6693
6444
  ageMin: 18,
6694
- range: { max: 35, min: 12, optimalMax: 28, optimalMin: 15, unit: "%" },
6445
+ range: { max: 35, min: 12, minKind: "display", optimalMax: 28, optimalMin: 15, unit: "%" },
6695
6446
  sex: "M"
6696
6447
  },
6697
6448
  {
6698
6449
  ageMin: 18,
6699
- range: { max: 50, min: 20, optimalMax: 40, optimalMin: 25, unit: "%" },
6450
+ range: { max: 50, min: 20, minKind: "display", optimalMax: 40, optimalMin: 25, unit: "%" },
6700
6451
  sex: "F"
6701
6452
  }
6702
6453
  ]
6703
6454
  },
6704
6455
  LeanMass: {
6705
- default: { max: 75, min: 35, optimalMax: 65, optimalMin: 40, unit: "kg" },
6456
+ default: { max: 75, maxKind: "display", min: 35, optimalMax: 65, optimalMin: 40, unit: "kg" },
6706
6457
  direction: "higher-better",
6458
+ kind: "population",
6707
6459
  source: "kelly-dxa-2009",
6708
6460
  variants: [
6709
6461
  {
6710
6462
  ageMin: 18,
6711
- range: { max: 85, min: 45, optimalMax: 75, optimalMin: 50, unit: "kg" },
6463
+ range: { max: 85, maxKind: "display", min: 45, optimalMax: 75, optimalMin: 50, unit: "kg" },
6712
6464
  sex: "M"
6713
6465
  },
6714
6466
  {
6715
6467
  ageMin: 18,
6716
- range: { max: 55, min: 30, optimalMax: 48, optimalMin: 35, unit: "kg" },
6468
+ range: { max: 55, maxKind: "display", min: 30, optimalMax: 48, optimalMin: 35, unit: "kg" },
6717
6469
  sex: "F"
6718
6470
  }
6719
6471
  ]
6720
6472
  },
6721
6473
  TotalMass: {
6722
6474
  default: { max: 100, min: 50, optimalMax: 85, optimalMin: 55, unit: "kg" },
6475
+ kind: "population",
6723
6476
  source: "kelly-dxa-2009",
6724
6477
  variants: [
6725
6478
  {
@@ -6737,223 +6490,853 @@ var biomarkerRangeDefinitions = {
6737
6490
  VATMass: {
6738
6491
  default: { max: 1.5, min: 0, optimalMax: 0.8, optimalMin: 0, unit: "kg" },
6739
6492
  direction: "lower-better",
6493
+ kind: "population",
6740
6494
  source: "ofenheimer-vat-2020",
6741
6495
  variants: [
6742
6496
  {
6743
6497
  ageMin: 18,
6744
- range: { max: 1.5, min: 0, optimalMax: 1, optimalMin: 0, unit: "kg" },
6498
+ range: { max: 1.5, min: 0, optimalMax: 1, optimalMin: 0, unit: "kg" },
6499
+ sex: "M"
6500
+ },
6501
+ {
6502
+ ageMin: 18,
6503
+ range: { max: 1, min: 0, optimalMax: 0.5, optimalMin: 0, unit: "kg" },
6504
+ sex: "F"
6505
+ }
6506
+ ]
6507
+ },
6508
+ VATVolume: {
6509
+ default: {
6510
+ max: 852,
6511
+ min: 0,
6512
+ optimalMax: 600,
6513
+ optimalMin: 0,
6514
+ unit: "cm\xB3",
6515
+ warningMax: 1837
6516
+ },
6517
+ direction: "lower-better",
6518
+ kind: "decision-threshold",
6519
+ source: "ge-corescan"
6520
+ },
6521
+ BMD_Total: {
6522
+ default: {
6523
+ max: 1.4,
6524
+ maxKind: "display",
6525
+ min: 0.9,
6526
+ optimalMax: 1.3,
6527
+ optimalMin: 1,
6528
+ unit: "g/cm\xB2"
6529
+ },
6530
+ direction: "higher-better",
6531
+ kind: "decision-threshold",
6532
+ source: "who-osteoporosis-1994"
6533
+ },
6534
+ TScore_Total: {
6535
+ default: {
6536
+ max: 4,
6537
+ maxKind: "display",
6538
+ min: -1,
6539
+ optimalMax: 2,
6540
+ optimalMin: -0.5,
6541
+ unit: ""
6542
+ },
6543
+ direction: "higher-better",
6544
+ kind: "decision-threshold",
6545
+ source: "who-osteoporosis-1994"
6546
+ },
6547
+ ZScore_Total: {
6548
+ default: {
6549
+ max: 2,
6550
+ maxKind: "display",
6551
+ min: -2,
6552
+ optimalMax: 1,
6553
+ optimalMin: -1,
6554
+ unit: ""
6555
+ },
6556
+ direction: "higher-better",
6557
+ kind: "decision-threshold",
6558
+ source: "who-osteoporosis-1994"
6559
+ },
6560
+ Amylase: {
6561
+ default: { max: 100, min: 28, optimalMax: 90, optimalMin: 35, unit: "U/L" },
6562
+ kind: "reference-interval",
6563
+ source: "tietz-7ed-2015"
6564
+ },
6565
+ Creatinine_Urine: {
6566
+ default: { max: 300, min: 20, optimalMax: 250, optimalMin: 40, unit: "mg/dL" },
6567
+ kind: "reference-interval",
6568
+ source: "tietz-7ed-2015",
6569
+ variants: [
6570
+ {
6571
+ ageMin: 18,
6572
+ range: { max: 300, min: 40, optimalMax: 260, optimalMin: 60, unit: "mg/dL" },
6573
+ sex: "M"
6574
+ },
6575
+ {
6576
+ ageMin: 18,
6577
+ range: { max: 220, min: 20, optimalMax: 180, optimalMin: 40, unit: "mg/dL" },
6578
+ sex: "F"
6579
+ }
6580
+ ]
6581
+ },
6582
+ IgA: {
6583
+ default: { max: 400, min: 70, optimalMax: 350, optimalMin: 100, unit: "mg/dL" },
6584
+ kind: "reference-interval",
6585
+ source: "tietz-7ed-2015"
6586
+ },
6587
+ Lipase: {
6588
+ default: { max: 60, min: 0, optimalMax: 50, optimalMin: 10, unit: "U/L" },
6589
+ kind: "reference-interval",
6590
+ source: "tietz-7ed-2015"
6591
+ },
6592
+ Microalbumin_Urine: {
6593
+ default: { max: 30, min: 0, optimalMax: 20, optimalMin: 0, unit: "mg/L" },
6594
+ kind: "decision-threshold",
6595
+ source: "kdigo-ckd-2024"
6596
+ },
6597
+ PSA_Free: {
6598
+ default: { max: 1.5, min: 0, optimalMax: 1, optimalMin: 0, unit: "ng/mL" },
6599
+ kind: "reference-interval",
6600
+ source: "sturgeon-nacb-2008"
6601
+ },
6602
+ // Relação PSA livre/total — `higher-better`: razão maior = menor risco de
6603
+ // câncer de próstata, logo não há limite superior "ruim" (acima do normal é
6604
+ // verde). Faixas alinhadas à tabela clínica: ≥25% baixo risco (verde),
6605
+ // 10–25% atenção — intermediário/moderado (âmbar), <10% alto risco >50%
6606
+ // (vermelho). Limites: `min` (25) é o piso do verde e `warningMin` (10) o
6607
+ // piso do âmbar — o vermelho é estritamente abaixo de 10% (< 10%); 10%
6608
+ // pertence ao âmbar, conforme a faixa "10–15% moderado" da tabela.
6609
+ PSA_FreeRatio: {
6610
+ default: {
6611
+ max: 100,
6612
+ maxKind: "display",
6613
+ min: 25,
6614
+ optimalMax: 100,
6615
+ optimalMin: 30,
6616
+ unit: "%",
6617
+ warningMin: 10
6618
+ },
6619
+ direction: "higher-better",
6620
+ kind: "decision-threshold",
6621
+ source: "sturgeon-nacb-2008"
6622
+ },
6623
+ // BMI - WHO classification: 18.5–24.9 normal, 25–29.9 overweight, ≥30 obese
6624
+ BMI: {
6625
+ default: { max: 30, min: 18.5, optimalMax: 24.9, optimalMin: 18.5, unit: "kg/m2" },
6626
+ kind: "decision-threshold",
6627
+ source: "who-obesity-2000"
6628
+ },
6629
+ // eAG — Estimated Average Glucose, derivada da HbA1c pela fórmula ADAG:
6630
+ // eAG (mg/dL) = 28,7 × HbA1c(%) − 46,7 (NATHAN, D. M. et al. Translating the
6631
+ // A1C assay into estimated average glucose values. Diabetes Care, v. 31, n. 8,
6632
+ // p. 1473-1478, 2008. DOI: 10.2337/dc08-0545).
6633
+ //
6634
+ // As faixas espelham os cortes diagnósticos de HbA1c da SBD (ver entrada
6635
+ // `HbA1c`) convertidos por essa fórmula, mantendo a eAG coerente com o
6636
+ // marcador de origem:
6637
+ // optimalMax 105 ← HbA1c 5,3 % (teto do controle ótimo)
6638
+ // max 117 ← HbA1c 5,7 % (teto do não diabético → início do pré-diabetes)
6639
+ // warningMax 137 ← HbA1c 6,4 % (teto do pré-diabetes → acima disso, diabetes)
6640
+ // optimalMin 82 ← HbA1c 4,5 % (piso do alvo fisiológico da entrada HbA1c)
6641
+ // min 11 ← HbA1c 2,0 % (mesmo piso de sanidade da entrada HbA1c)
6642
+ //
6643
+ // O piso era 70 nos dois campos, e não vinha desta conversão: 70 é o limite
6644
+ // inferior clássico da glicemia **de jejum**, transplantado para uma média
6645
+ // estimada de ~3 meses. Pela fórmula, 70 equivale a HbA1c 4,07 % — ou seja,
6646
+ // marcava como alterado exatamente quem a entrada `HbA1c` documenta que não
6647
+ // deve ser sinalizado, já que HbA1c baixa reflete hemólise, perda sanguínea
6648
+ // ou hemoglobinopatia, e não doença do metabolismo glicêmico. Espelhar o piso
6649
+ // do HbA1c mantém as duas entradas coerentes, que é o que o comentário sempre
6650
+ // afirmou fazer.
6651
+ //
6652
+ // Substitui a faixa anterior (optimalMax 126 = corte de glicemia de jejum;
6653
+ // max 154 = meta terapêutica do diabético, HbA1c 7 %), que classificava como
6654
+ // "Normal" valores já pré-diabéticos e diabéticos.
6655
+ eAG: {
6656
+ default: {
6657
+ max: 117,
6658
+ min: 11,
6659
+ minKind: "display",
6660
+ optimalMax: 105,
6661
+ optimalMin: 82,
6662
+ unit: "mg/dL",
6663
+ warningMax: 137
6664
+ },
6665
+ kind: "decision-threshold",
6666
+ source: "sbd-diabetes-2024"
6667
+ },
6668
+ // INR - International Normalized Ratio (non-anticoagulated patients)
6669
+ INR: {
6670
+ default: { max: 1.2, min: 0.8, optimalMax: 1.1, optimalMin: 0.9, unit: "ratio" },
6671
+ kind: "reference-interval",
6672
+ source: "tietz-7ed-2015"
6673
+ },
6674
+ // Prothrombin Time
6675
+ ProthrombinTime: {
6676
+ default: { max: 13.5, min: 11, optimalMax: 13, optimalMin: 11, unit: "seconds" },
6677
+ kind: "reference-interval",
6678
+ source: "tietz-7ed-2015"
6679
+ },
6680
+ // DHT - Dihydrotestosterone (adult male reference; female values are much lower)
6681
+ DHT: {
6682
+ default: { max: 85, min: 30, optimalMax: 85, optimalMin: 30, unit: "ng/dL" },
6683
+ kind: "reference-interval",
6684
+ source: "tietz-7ed-2015",
6685
+ variants: [
6686
+ {
6687
+ ageMin: 18,
6688
+ range: { max: 85, min: 30, optimalMax: 85, optimalMin: 30, unit: "ng/dL" },
6745
6689
  sex: "M"
6746
6690
  },
6747
6691
  {
6748
6692
  ageMin: 18,
6749
- range: { max: 1, min: 0, optimalMax: 0.5, optimalMin: 0, unit: "kg" },
6693
+ range: { max: 22, min: 4, optimalMax: 22, optimalMin: 4, unit: "ng/dL" },
6750
6694
  sex: "F"
6751
6695
  }
6752
6696
  ]
6753
6697
  },
6754
- VATVolume: {
6755
- default: {
6756
- max: 852,
6757
- min: 0,
6758
- optimalMax: 600,
6759
- optimalMin: 0,
6760
- unit: "cm\xB3",
6761
- warningMax: 1837
6762
- },
6698
+ // Total IgE - higher values suggest atopy/allergic sensitization
6699
+ IgE_Total: {
6700
+ default: { max: 100, min: 0, optimalMax: 100, optimalMin: 0, unit: "IU/mL" },
6763
6701
  direction: "lower-better",
6764
- source: "ge-corescan"
6765
- },
6766
- BMD_Total: {
6767
- default: { max: 1.4, min: 0.9, optimalMax: 1.3, optimalMin: 1, unit: "g/cm\xB2" },
6768
- direction: "higher-better",
6769
- source: "who-osteoporosis-1994"
6702
+ kind: "reference-interval",
6703
+ source: "tietz-7ed-2015"
6770
6704
  },
6771
- TScore_Total: {
6772
- default: { max: 4, min: -1, optimalMax: 2, optimalMin: -0.5, unit: "" },
6773
- direction: "higher-better",
6774
- source: "who-osteoporosis-1994"
6705
+ // IgG - Immunoglobulin G
6706
+ IgG: {
6707
+ default: { max: 1600, min: 700, optimalMax: 1600, optimalMin: 700, unit: "mg/dL" },
6708
+ kind: "reference-interval",
6709
+ source: "tietz-7ed-2015"
6775
6710
  },
6776
- ZScore_Total: {
6777
- default: { max: 2, min: -2, optimalMax: 1, optimalMin: -1, unit: "" },
6778
- direction: "higher-better",
6779
- source: "who-osteoporosis-1994"
6711
+ // IgE E1 Cat Dander - Class 0 (≤0.35 kU/L) = negative
6712
+ IgE_E1_CatDander: {
6713
+ default: { max: 0.35, min: 0, optimalMax: 0.35, optimalMin: 0, unit: "kU/L" },
6714
+ direction: "lower-better",
6715
+ kind: "decision-threshold",
6716
+ source: "tietz-7ed-2015"
6780
6717
  },
6781
- Amylase: {
6782
- default: { max: 100, min: 28, optimalMax: 90, optimalMin: 35, unit: "U/L" },
6718
+ // IgE GX1 Grasses - Class 0 (≤0.35 kU/L) = negative
6719
+ IgE_GX1_Grasses: {
6720
+ default: { max: 0.35, min: 0, optimalMax: 0.35, optimalMin: 0, unit: "kU/L" },
6721
+ direction: "lower-better",
6722
+ kind: "decision-threshold",
6783
6723
  source: "tietz-7ed-2015"
6724
+ }
6725
+ };
6726
+ var defaultReferenceRanges = Object.fromEntries(
6727
+ Object.entries(biomarkerRangeDefinitions).map(([code, def]) => [
6728
+ code,
6729
+ {
6730
+ ...def.default,
6731
+ ...def.kind && { kind: def.kind },
6732
+ ...def.source && { source: def.source }
6733
+ }
6734
+ ])
6735
+ );
6736
+ function getReferenceRange(testCode, context) {
6737
+ const definition = biomarkerRangeDefinitions[testCode];
6738
+ if (!definition) return void 0;
6739
+ const withSource = (range2, kind = definition.kind) => ({
6740
+ ...range2,
6741
+ ...kind && { kind },
6742
+ ...definition.source && { source: definition.source }
6743
+ });
6744
+ if (!context || !definition.variants || definition.variants.length === 0) {
6745
+ return withSource(definition.default);
6746
+ }
6747
+ const { age, biologicalSex, pregnancyTrimester, pregnant } = context;
6748
+ const hasPregnancyVariant = definition.variants.some((v) => v.pregnant === true);
6749
+ for (const variant of definition.variants) {
6750
+ if (variant.pregnant === true) {
6751
+ if (!pregnant) continue;
6752
+ if (variant.pregnancyTrimester !== void 0 && variant.pregnancyTrimester !== pregnancyTrimester) {
6753
+ continue;
6754
+ }
6755
+ } else if (pregnant && hasPregnancyVariant) {
6756
+ continue;
6757
+ }
6758
+ if (variant.sex !== "all" && variant.sex !== biologicalSex) {
6759
+ continue;
6760
+ }
6761
+ if (variant.ageMin !== void 0 && (age === void 0 || age < variant.ageMin)) {
6762
+ continue;
6763
+ }
6764
+ if (variant.ageMax !== void 0 && (age === void 0 || age > variant.ageMax)) {
6765
+ continue;
6766
+ }
6767
+ return withSource(variant.range, variant.kind ?? definition.kind);
6768
+ }
6769
+ return withSource(definition.default);
6770
+ }
6771
+ function getRangeDirection(testCode) {
6772
+ return biomarkerRangeDefinitions[testCode]?.direction ?? "range";
6773
+ }
6774
+ function referenceRangeMeaning(kind) {
6775
+ const system = "http://terminology.hl7.org/CodeSystem/referencerange-meaning";
6776
+ if (kind === "reference-interval") return { code: "normal", display: "Normal Range", system };
6777
+ if (kind === "decision-threshold") {
6778
+ return { code: "recommended", display: "Recommended Range", system };
6779
+ }
6780
+ return void 0;
6781
+ }
6782
+
6783
+ // src/converter.ts
6784
+ function interpretationCode(flag) {
6785
+ switch (flag) {
6786
+ case "H":
6787
+ return "H";
6788
+ // High
6789
+ case "L":
6790
+ return "L";
6791
+ // Low
6792
+ default:
6793
+ return "N";
6794
+ }
6795
+ }
6796
+ function interpretationDisplay(flag) {
6797
+ switch (flag) {
6798
+ case "H":
6799
+ return "High";
6800
+ case "L":
6801
+ return "Low";
6802
+ default:
6803
+ return "Normal";
6804
+ }
6805
+ }
6806
+ var SEX_CODING = {
6807
+ female: {
6808
+ code: "female",
6809
+ display: "Female",
6810
+ system: "http://hl7.org/fhir/administrative-gender"
6784
6811
  },
6785
- Creatinine_Urine: {
6786
- default: { max: 300, min: 20, optimalMax: 250, optimalMin: 40, unit: "mg/dL" },
6787
- source: "tietz-7ed-2015",
6788
- variants: [
6812
+ male: { code: "male", display: "Male", system: "http://hl7.org/fhir/administrative-gender" }
6813
+ };
6814
+ var buildReferenceRanges = (observation, quantity) => {
6815
+ const toRanges = (low, high, sex) => {
6816
+ if (low === void 0 && high === void 0) return [];
6817
+ return [
6789
6818
  {
6790
- ageMin: 18,
6791
- range: { max: 300, min: 40, optimalMax: 260, optimalMin: 60, unit: "mg/dL" },
6792
- sex: "M"
6793
- },
6819
+ ...sex === void 0 ? {} : { appliesTo: [{ coding: [SEX_CODING[sex]] }] },
6820
+ ...high === void 0 ? {} : { high: quantity(high) },
6821
+ ...low === void 0 ? {} : { low: quantity(low) }
6822
+ }
6823
+ ];
6824
+ };
6825
+ if (observation.referenceRanges && observation.referenceRanges.length > 0) {
6826
+ return observation.referenceRanges.flatMap((r) => toRanges(r.low, r.high, r.appliesTo));
6827
+ }
6828
+ const meaning = observation.referenceKind && referenceRangeMeaning(observation.referenceKind);
6829
+ return toRanges(observation.referenceMin, observation.referenceMax).map(
6830
+ (range2) => meaning ? { ...range2, type: { coding: [meaning] } } : range2
6831
+ );
6832
+ };
6833
+ function labObservationToFHIR(observation, patientId, laboratoryName) {
6834
+ const loincCode = codeToLoinc(observation.biomarkerCode);
6835
+ const sourceUnit = observation.unit || getDefaultUnit(observation.biomarkerCode) || observation.unit;
6836
+ const ucumUnit = resolveUcum(sourceUnit, observation.biomarkerCode);
6837
+ const quantity = (value) => ({
6838
+ ...ucumUnit ? { code: ucumUnit, system: "http://unitsofmeasure.org" } : {},
6839
+ ...sourceUnit ? { unit: sourceUnit } : {},
6840
+ value
6841
+ });
6842
+ const isQualitative = observation.isQualitative || typeof observation.value === "string";
6843
+ const fhirObs = {
6844
+ category: [
6845
+ {
6846
+ coding: [
6847
+ {
6848
+ code: "laboratory",
6849
+ display: "Laboratory",
6850
+ system: "http://terminology.hl7.org/CodeSystem/observation-category"
6851
+ }
6852
+ ]
6853
+ }
6854
+ ],
6855
+ code: {
6856
+ // Sem LOINC, o coding LOINC simplesmente não sai. Antes ia `99999-9`,
6857
+ // que não é código LOINC nenhum: publicava sob `http://loinc.org` uma
6858
+ // afirmação falsa, e quem consumisse o bundle confiando no system
6859
+ // trataria aquilo como código de verdade. Composição corporal, densidade
6860
+ // óssea e escore de cálcio não têm LOINC, e o certo é a lacuna explícita.
6861
+ coding: [
6862
+ ...loincCode ? [
6863
+ {
6864
+ code: loincCode,
6865
+ display: observation.biomarkerName,
6866
+ system: LOINC_SYSTEM
6867
+ }
6868
+ ] : [],
6869
+ {
6870
+ code: observation.biomarkerCode,
6871
+ display: observation.biomarkerName,
6872
+ system: BIOMARKER_CODE_SYSTEM
6873
+ }
6874
+ ],
6875
+ text: observation.biomarkerName
6876
+ },
6877
+ effectiveDateTime: observation.collectionDate,
6878
+ id: `${observation.reportId}-${observation.biomarkerCode}`,
6879
+ interpretation: [
6880
+ {
6881
+ coding: [
6882
+ {
6883
+ code: interpretationCode(observation.flag),
6884
+ display: interpretationDisplay(observation.flag),
6885
+ system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation"
6886
+ }
6887
+ ]
6888
+ }
6889
+ ],
6890
+ performer: laboratoryName ? [{ display: laboratoryName }] : void 0,
6891
+ resourceType: "Observation",
6892
+ status: "final",
6893
+ subject: {
6894
+ reference: `Patient/${patientId}`
6895
+ }
6896
+ };
6897
+ if (isQualitative) {
6898
+ fhirObs.valueString = String(observation.value);
6899
+ } else {
6900
+ fhirObs.valueQuantity = quantity(observation.value);
6901
+ const referenceRange = buildReferenceRanges(observation, quantity);
6902
+ if (referenceRange.length > 0) fhirObs.referenceRange = referenceRange;
6903
+ }
6904
+ return fhirObs;
6905
+ }
6906
+ function labReportToFHIR(report, patientId, observationIds) {
6907
+ let status;
6908
+ switch (report.processingStatus) {
6909
+ case "complete":
6910
+ status = "final";
6911
+ break;
6912
+ case "partial":
6913
+ status = "partial";
6914
+ break;
6915
+ case "pending_review":
6916
+ status = "preliminary";
6917
+ break;
6918
+ default:
6919
+ status = "final";
6920
+ }
6921
+ return {
6922
+ category: [
6923
+ {
6924
+ coding: [
6925
+ {
6926
+ code: "LAB",
6927
+ display: "Laboratory",
6928
+ system: "http://terminology.hl7.org/CodeSystem/v2-0074"
6929
+ }
6930
+ ]
6931
+ }
6932
+ ],
6933
+ code: {
6934
+ coding: [
6935
+ {
6936
+ code: "11502-2",
6937
+ // Laboratory report
6938
+ display: "Laboratory report",
6939
+ system: "http://loinc.org"
6940
+ }
6941
+ ],
6942
+ text: "Laboratory Results"
6943
+ },
6944
+ conclusion: report.overallStatus === "NORMAL" ? "All results within normal limits" : "One or more abnormal results detected",
6945
+ conclusionCode: report.overallStatus === "ANORMAL" ? [
6794
6946
  {
6795
- ageMin: 18,
6796
- range: { max: 220, min: 20, optimalMax: 180, optimalMin: 40, unit: "mg/dL" },
6797
- sex: "F"
6947
+ coding: [
6948
+ {
6949
+ code: "A",
6950
+ display: "Abnormal",
6951
+ system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation"
6952
+ }
6953
+ ]
6798
6954
  }
6799
- ]
6800
- },
6801
- IgA: {
6802
- default: { max: 400, min: 70, optimalMax: 350, optimalMin: 100, unit: "mg/dL" },
6803
- source: "tietz-7ed-2015"
6804
- },
6805
- Lipase: {
6806
- default: { max: 60, min: 0, optimalMax: 50, optimalMin: 10, unit: "U/L" },
6807
- source: "tietz-7ed-2015"
6808
- },
6809
- Microalbumin_Urine: {
6810
- default: { max: 30, min: 0, optimalMax: 20, optimalMin: 0, unit: "mg/L" },
6811
- source: "kdigo-ckd-2024"
6812
- },
6813
- PSA_Free: {
6814
- default: { max: 1.5, min: 0, optimalMax: 1, optimalMin: 0, unit: "ng/mL" },
6815
- source: "sturgeon-nacb-2008"
6816
- },
6817
- // Relação PSA livre/total — `higher-better`: razão maior = menor risco de
6818
- // câncer de próstata, logo não há limite superior "ruim" (acima do normal é
6819
- // verde). Faixas alinhadas à tabela clínica: ≥25% baixo risco (verde),
6820
- // 10–25% atenção — intermediário/moderado (âmbar), <10% alto risco >50%
6821
- // (vermelho). Limites: `min` (25) é o piso do verde e `warningMin` (10) o
6822
- // piso do âmbar — o vermelho é estritamente abaixo de 10% (< 10%); 10%
6823
- // pertence ao âmbar, conforme a faixa "10–15% moderado" da tabela.
6824
- PSA_FreeRatio: {
6825
- default: { max: 100, min: 25, optimalMax: 100, optimalMin: 30, unit: "%", warningMin: 10 },
6826
- direction: "higher-better",
6827
- source: "sturgeon-nacb-2008"
6828
- },
6829
- // BMI - WHO classification: 18.5–24.9 normal, 25–29.9 overweight, ≥30 obese
6830
- BMI: {
6831
- default: { max: 30, min: 18.5, optimalMax: 24.9, optimalMin: 18.5, unit: "kg/m2" },
6832
- source: "who-obesity-2000"
6833
- },
6834
- // eAG — Estimated Average Glucose, derivada da HbA1c pela fórmula ADAG:
6835
- // eAG (mg/dL) = 28,7 × HbA1c(%) − 46,7 (NATHAN, D. M. et al. Translating the
6836
- // A1C assay into estimated average glucose values. Diabetes Care, v. 31, n. 8,
6837
- // p. 1473-1478, 2008. DOI: 10.2337/dc08-0545).
6838
- //
6839
- // As faixas espelham os cortes diagnósticos de HbA1c da SBD (ver entrada
6840
- // `HbA1c`) convertidos por essa fórmula, mantendo a eAG coerente com o
6841
- // marcador de origem:
6842
- // optimalMax 105 ← HbA1c 5,3 % (teto do controle ótimo)
6843
- // max 117 ← HbA1c 5,7 % (teto do não diabético → início do pré-diabetes)
6844
- // warningMax 137 ← HbA1c 6,4 % (teto do pré-diabetes → acima disso, diabetes)
6845
- // optimalMin 82 ← HbA1c 4,5 % (piso do alvo fisiológico da entrada HbA1c)
6846
- // min 11 ← HbA1c 2,0 % (mesmo piso de sanidade da entrada HbA1c)
6847
- //
6848
- // O piso era 70 nos dois campos, e não vinha desta conversão: 70 é o limite
6849
- // inferior clássico da glicemia **de jejum**, transplantado para uma média
6850
- // estimada de ~3 meses. Pela fórmula, 70 equivale a HbA1c 4,07 % — ou seja,
6851
- // marcava como alterado exatamente quem a entrada `HbA1c` documenta que não
6852
- // deve ser sinalizado, já que HbA1c baixa reflete hemólise, perda sanguínea
6853
- // ou hemoglobinopatia, e não doença do metabolismo glicêmico. Espelhar o piso
6854
- // do HbA1c mantém as duas entradas coerentes, que é o que o comentário sempre
6855
- // afirmou fazer.
6856
- //
6857
- // Substitui a faixa anterior (optimalMax 126 = corte de glicemia de jejum;
6858
- // max 154 = meta terapêutica do diabético, HbA1c 7 %), que classificava como
6859
- // "Normal" valores já pré-diabéticos e diabéticos.
6860
- eAG: {
6861
- default: { max: 117, min: 11, optimalMax: 105, optimalMin: 82, unit: "mg/dL", warningMax: 137 },
6862
- source: "sbd-diabetes-2024"
6863
- },
6864
- // INR - International Normalized Ratio (non-anticoagulated patients)
6865
- INR: {
6866
- default: { max: 1.2, min: 0.8, optimalMax: 1.1, optimalMin: 0.9, unit: "ratio" },
6867
- source: "tietz-7ed-2015"
6868
- },
6869
- // Prothrombin Time
6870
- ProthrombinTime: {
6871
- default: { max: 13.5, min: 11, optimalMax: 13, optimalMin: 11, unit: "seconds" },
6872
- source: "tietz-7ed-2015"
6873
- },
6874
- // DHT - Dihydrotestosterone (adult male reference; female values are much lower)
6875
- DHT: {
6876
- default: { max: 85, min: 30, optimalMax: 85, optimalMin: 30, unit: "ng/dL" },
6877
- source: "tietz-7ed-2015",
6878
- variants: [
6955
+ ] : void 0,
6956
+ effectiveDateTime: report.collectionDate,
6957
+ id: report.reportId,
6958
+ issued: report.createdAt,
6959
+ performer: report.laboratoryName ? [{ display: report.laboratoryName }] : void 0,
6960
+ resourceType: "DiagnosticReport",
6961
+ result: observationIds.map((id) => ({ reference: `Observation/${id}` })),
6962
+ status,
6963
+ subject: {
6964
+ reference: `Patient/${patientId}`
6965
+ }
6966
+ };
6967
+ }
6968
+ function userProfileToFHIR(profile) {
6969
+ const nameParts = profile.name.split(" ");
6970
+ const given = nameParts.slice(0, -1);
6971
+ const family = nameParts[nameParts.length - 1] || "";
6972
+ return {
6973
+ address: profile.address ? [
6879
6974
  {
6880
- ageMin: 18,
6881
- range: { max: 85, min: 30, optimalMax: 85, optimalMin: 30, unit: "ng/dL" },
6882
- sex: "M"
6883
- },
6975
+ city: profile.address.city,
6976
+ country: profile.address.country || "BR",
6977
+ line: [
6978
+ profile.address.street && profile.address.number ? `${profile.address.street}, ${profile.address.number}` : profile.address.street,
6979
+ profile.address.complement
6980
+ ].filter(Boolean),
6981
+ postalCode: profile.address.postalCode,
6982
+ state: profile.address.state
6983
+ }
6984
+ ] : void 0,
6985
+ birthDate: profile.birthDate,
6986
+ gender: profile.gender,
6987
+ id: profile.userId,
6988
+ name: [
6884
6989
  {
6885
- ageMin: 18,
6886
- range: { max: 22, min: 4, optimalMax: 22, optimalMin: 4, unit: "ng/dL" },
6887
- sex: "F"
6990
+ family,
6991
+ given: given.length > 0 ? given : void 0,
6992
+ text: profile.name
6888
6993
  }
6889
- ]
6890
- },
6891
- // Total IgE - higher values suggest atopy/allergic sensitization
6892
- IgE_Total: {
6893
- default: { max: 100, min: 0, optimalMax: 100, optimalMin: 0, unit: "IU/mL" },
6894
- direction: "lower-better",
6895
- source: "tietz-7ed-2015"
6896
- },
6897
- // IgG - Immunoglobulin G
6898
- IgG: {
6899
- default: { max: 1600, min: 700, optimalMax: 1600, optimalMin: 700, unit: "mg/dL" },
6900
- source: "tietz-7ed-2015"
6901
- },
6902
- // IgE E1 Cat Dander - Class 0 (≤0.35 kU/L) = negative
6903
- IgE_E1_CatDander: {
6904
- default: { max: 0.35, min: 0, optimalMax: 0.35, optimalMin: 0, unit: "kU/L" },
6905
- direction: "lower-better",
6906
- source: "tietz-7ed-2015"
6907
- },
6908
- // IgE GX1 Grasses - Class 0 (≤0.35 kU/L) = negative
6909
- IgE_GX1_Grasses: {
6910
- default: { max: 0.35, min: 0, optimalMax: 0.35, optimalMin: 0, unit: "kU/L" },
6911
- direction: "lower-better",
6912
- source: "tietz-7ed-2015"
6994
+ ],
6995
+ resourceType: "Patient",
6996
+ telecom: [
6997
+ ...profile.email ? [{ system: "email", value: profile.email }] : [],
6998
+ ...profile.phone ? [{ system: "phone", value: profile.phone }] : []
6999
+ ].length > 0 ? [
7000
+ ...profile.email ? [{ system: "email", value: profile.email }] : [],
7001
+ ...profile.phone ? [{ system: "phone", value: profile.phone }] : []
7002
+ ] : void 0
7003
+ };
7004
+ }
7005
+ function labResultToFHIRBundle(report, observations, userProfile) {
7006
+ const patientId = userProfile.userId;
7007
+ const fhirObservations = observations.map((obs) => {
7008
+ const resource = labObservationToFHIR(
7009
+ { ...obs, collectionDate: report.collectionDate },
7010
+ patientId,
7011
+ report.laboratoryName
7012
+ );
7013
+ return { fullUrl: entryFullUrl(resource), resource };
7014
+ });
7015
+ const observationIds = fhirObservations.map((entry) => entry.resource.id);
7016
+ const diagnosticReport = labReportToFHIR(report, patientId, observationIds);
7017
+ const fhirPatient = userProfileToFHIR(userProfile);
7018
+ return {
7019
+ entry: [
7020
+ { fullUrl: entryFullUrl(fhirPatient), resource: fhirPatient },
7021
+ { fullUrl: entryFullUrl(diagnosticReport), resource: diagnosticReport },
7022
+ ...fhirObservations
7023
+ ],
7024
+ resourceType: "Bundle",
7025
+ type: "collection"
7026
+ };
7027
+ }
7028
+
7029
+ // src/cli/commands/convert.ts
7030
+ async function convert(args, json) {
7031
+ const raw = await getInput(args[0]);
7032
+ const data = parseJson(
7033
+ raw,
7034
+ "JSON inv\xE1lido. Forne\xE7a um objeto com { report, observations, profile }."
7035
+ );
7036
+ if (!data.report || !data.observations || !data.profile) {
7037
+ exitWithError(
7038
+ 'Formato esperado: { "report": LabReportData, "observations": LabObservationData[], "profile": UserProfileData }'
7039
+ );
6913
7040
  }
6914
- };
6915
- var defaultReferenceRanges = Object.fromEntries(
6916
- Object.entries(biomarkerRangeDefinitions).map(([code, def]) => [
6917
- code,
6918
- def.source ? { ...def.default, source: def.source } : def.default
6919
- ])
6920
- );
6921
- function getReferenceRange(testCode, context) {
6922
- const definition = biomarkerRangeDefinitions[testCode];
6923
- if (!definition) return void 0;
6924
- const withSource = (range2) => definition.source ? { ...range2, source: definition.source } : range2;
6925
- if (!context || !definition.variants || definition.variants.length === 0) {
6926
- return withSource(definition.default);
7041
+ const bundle = labResultToFHIRBundle(data.report, data.observations, data.profile);
7042
+ if (json) {
7043
+ outputJson(bundle);
7044
+ } else {
7045
+ outputText(JSON.stringify(bundle, null, 2));
7046
+ }
7047
+ }
7048
+
7049
+ // src/validators.ts
7050
+ function validateFHIRImportBundle(data) {
7051
+ const errors = [];
7052
+ if (!data || typeof data !== "object") {
7053
+ errors.push({ details: "Input must be a JSON object", field: "root" });
7054
+ return errors;
7055
+ }
7056
+ const bundle = data;
7057
+ if (bundle.resourceType !== "Bundle") {
7058
+ errors.push({
7059
+ details: `Expected resourceType "Bundle", got "${String(bundle.resourceType)}"`,
7060
+ field: "resourceType"
7061
+ });
7062
+ }
7063
+ if (!Array.isArray(bundle.entry)) {
7064
+ errors.push({ details: 'Bundle must contain an "entry" array', field: "entry" });
7065
+ } else if (bundle.entry.length === 0) {
7066
+ errors.push({ details: "Bundle entry array is empty", field: "entry" });
7067
+ }
7068
+ return errors;
7069
+ }
7070
+
7071
+ // src/importer.ts
7072
+ var MAX_OBSERVATIONS = 5e3;
7073
+ var MAX_FILE_SIZE = 15 * 1024 * 1024;
7074
+ function resolveBiomarkerCode(observation) {
7075
+ const coding = observation.code?.coding ?? [];
7076
+ const loincCode = coding.find((c) => c.system === LOINC_SYSTEM)?.code;
7077
+ const declaredCode = coding.find((c) => c.system === BIOMARKER_CODE_SYSTEM)?.code;
7078
+ const seenCodes = [
7079
+ ...loincCode ? [`LOINC ${loincCode}`] : [],
7080
+ ...declaredCode ? [`biomarker code ${declaredCode}`] : []
7081
+ ];
7082
+ let reason;
7083
+ if (seenCodes.length > 0) {
7084
+ reason = `Unknown code: ${seenCodes.join(", ")}`;
7085
+ } else if (coding.length > 0) {
7086
+ const systems = [...new Set(coding.map((c) => c.system ?? "(sem system)"))];
7087
+ reason = `No code in a supported system (found: ${systems.join(", ")})`;
7088
+ } else {
7089
+ reason = "No code found in observation coding";
6927
7090
  }
6928
- const { age, biologicalSex, pregnancyTrimester, pregnant } = context;
6929
- const hasPregnancyVariant = definition.variants.some((v) => v.pregnant === true);
6930
- for (const variant of definition.variants) {
6931
- if (variant.pregnant === true) {
6932
- if (!pregnant) continue;
6933
- if (variant.pregnancyTrimester !== void 0 && variant.pregnancyTrimester !== pregnancyTrimester) {
6934
- continue;
6935
- }
6936
- } else if (pregnant && hasPregnancyVariant) {
7091
+ const fromLoinc = loincCode ? loincToCode(loincCode) : void 0;
7092
+ if (fromLoinc) return { internalCode: fromLoinc, loincCode, reason };
7093
+ if (declaredCode && isValidCode(declaredCode)) {
7094
+ const canonical = normalizeCode(declaredCode);
7095
+ return { internalCode: canonical, loincCode: codeToLoinc(canonical), reason };
7096
+ }
7097
+ return { loincCode, reason };
7098
+ }
7099
+ function extractFlag(observation) {
7100
+ const code = observation.interpretation?.[0]?.coding?.[0]?.code;
7101
+ if (code === "H" || code === "HH") return "H";
7102
+ if (code === "L" || code === "LL") return "L";
7103
+ return "";
7104
+ }
7105
+ function extractObservationsFromBundle(bundle) {
7106
+ const observations = [];
7107
+ const skipped = [];
7108
+ for (let i = 0; i < bundle.entry.length; i++) {
7109
+ const entry = bundle.entry[i];
7110
+ if (!entry.resource) {
7111
+ skipped.push({ index: i, reason: "Entry has no resource" });
6937
7112
  continue;
6938
7113
  }
6939
- if (variant.sex !== "all" && variant.sex !== biologicalSex) {
7114
+ if (entry.resource.resourceType !== "Observation") {
6940
7115
  continue;
6941
7116
  }
6942
- if (variant.ageMin !== void 0 && (age === void 0 || age < variant.ageMin)) {
7117
+ if (observations.length >= MAX_OBSERVATIONS) {
7118
+ skipped.push({ index: i, reason: `Maximum of ${MAX_OBSERVATIONS} observations exceeded` });
6943
7119
  continue;
6944
7120
  }
6945
- if (variant.ageMax !== void 0 && (age === void 0 || age > variant.ageMax)) {
6946
- continue;
7121
+ observations.push(entry.resource);
7122
+ }
7123
+ return { observations, skipped };
7124
+ }
7125
+ function mapFHIRObservationToInternal(observation, index) {
7126
+ const { internalCode, loincCode, reason } = resolveBiomarkerCode(observation);
7127
+ if (!internalCode) {
7128
+ return {
7129
+ skipped: { index, loincCode, reason, resourceType: "Observation" }
7130
+ };
7131
+ }
7132
+ const definition = getDefinitionByCode(internalCode);
7133
+ let value;
7134
+ let unit = "";
7135
+ let isQualitative = false;
7136
+ if (observation.valueQuantity?.value !== void 0) {
7137
+ value = observation.valueQuantity.value;
7138
+ unit = observation.valueQuantity.unit || observation.valueQuantity.code || "";
7139
+ } else if (observation.valueString) {
7140
+ value = observation.valueString;
7141
+ isQualitative = true;
7142
+ } else {
7143
+ return {
7144
+ skipped: {
7145
+ index,
7146
+ loincCode,
7147
+ reason: "Observation has no value (valueQuantity or valueString)",
7148
+ resourceType: "Observation"
7149
+ }
7150
+ };
7151
+ }
7152
+ const collectionDate = observation.effectiveDateTime || observation.effectivePeriod?.start || "";
7153
+ if (!collectionDate) {
7154
+ return {
7155
+ skipped: {
7156
+ index,
7157
+ loincCode,
7158
+ reason: "Observation has no effectiveDateTime or effectivePeriod.start",
7159
+ resourceType: "Observation"
7160
+ }
7161
+ };
7162
+ }
7163
+ let referenceMin;
7164
+ let referenceMax;
7165
+ let referenceKind;
7166
+ if (observation.referenceRange?.[0]) {
7167
+ referenceMin = observation.referenceRange[0].low?.value;
7168
+ referenceMax = observation.referenceRange[0].high?.value;
7169
+ const meaning = observation.referenceRange[0].type?.coding?.find(
7170
+ (c) => c.system === "http://terminology.hl7.org/CodeSystem/referencerange-meaning"
7171
+ )?.code;
7172
+ if (meaning === "normal") referenceKind = "reference-interval";
7173
+ else if (meaning === "recommended") referenceKind = "decision-threshold";
7174
+ }
7175
+ const imported = {
7176
+ biomarkerCode: internalCode,
7177
+ biomarkerName: definition?.names.pt[0] || definition?.names.en[0] || observation.code.text || internalCode,
7178
+ collectionDate,
7179
+ flag: extractFlag(observation),
7180
+ isQualitative,
7181
+ loincCode,
7182
+ ...referenceKind && { referenceKind },
7183
+ referenceMax,
7184
+ referenceMin,
7185
+ unit: unit || definition?.unit || "",
7186
+ value
7187
+ };
7188
+ return { observation: imported };
7189
+ }
7190
+ function processImportBundle(data) {
7191
+ const validationErrors = validateFHIRImportBundle(data);
7192
+ if (validationErrors.length > 0) {
7193
+ return {
7194
+ errors: validationErrors,
7195
+ imported: [],
7196
+ skipped: [],
7197
+ totalProcessed: 0
7198
+ };
7199
+ }
7200
+ const bundle = data;
7201
+ const { observations, skipped } = extractObservationsFromBundle(bundle);
7202
+ const imported = [];
7203
+ const allSkipped = [...skipped];
7204
+ for (let i = 0; i < observations.length; i++) {
7205
+ const result = mapFHIRObservationToInternal(observations[i], i);
7206
+ if ("observation" in result) {
7207
+ imported.push(result.observation);
7208
+ } else {
7209
+ allSkipped.push(result.skipped);
6947
7210
  }
6948
- return withSource(variant.range);
6949
7211
  }
6950
- return withSource(definition.default);
7212
+ return {
7213
+ errors: [],
7214
+ imported,
7215
+ skipped: allSkipped,
7216
+ totalProcessed: observations.length
7217
+ };
6951
7218
  }
6952
- function getRangeDirection(testCode) {
6953
- return biomarkerRangeDefinitions[testCode]?.direction ?? "range";
7219
+
7220
+ // src/cli/commands/import.ts
7221
+ async function importBundle(args, json) {
7222
+ const raw = await getInput(args[0]);
7223
+ const bundle = parseJson(raw, "JSON inv\xE1lido.");
7224
+ const result = processImportBundle(bundle);
7225
+ if (json) {
7226
+ outputJson(result);
7227
+ return;
7228
+ }
7229
+ outputText(`Processados: ${result.totalProcessed}`);
7230
+ outputText(`Importados: ${result.imported.length}`);
7231
+ outputText(`Ignorados: ${result.skipped.length}`);
7232
+ outputText(`Erros: ${result.errors.length}`);
7233
+ if (result.imported.length > 0) {
7234
+ outputText("\nObserva\xE7\xF5es importadas:");
7235
+ for (const obs of result.imported) {
7236
+ outputText(` ${obs.biomarkerCode} (${obs.loincCode}): ${obs.value} ${obs.unit}`);
7237
+ }
7238
+ }
7239
+ if (result.errors.length > 0) {
7240
+ outputText("\nErros:");
7241
+ for (const err of result.errors) {
7242
+ outputText(` ${err.field}: ${err.details}`);
7243
+ }
7244
+ }
7245
+ }
7246
+
7247
+ // src/cli/commands/list.ts
7248
+ import { parseArgs } from "util";
7249
+ async function list(args, json) {
7250
+ const { values } = parseArgs({
7251
+ args,
7252
+ options: {
7253
+ category: { type: "string" },
7254
+ visible: { default: false, type: "boolean" }
7255
+ },
7256
+ strict: false
7257
+ });
7258
+ let defs;
7259
+ if (values.category) {
7260
+ defs = getBiomarkersByCategory(values.category);
7261
+ if (defs.length === 0) {
7262
+ exitWithError(`Categoria n\xE3o encontrada ou vazia: ${values.category}`);
7263
+ }
7264
+ } else if (values.visible) {
7265
+ defs = getVisibleDefinitions();
7266
+ } else {
7267
+ defs = getAllDefinitions();
7268
+ }
7269
+ if (json) {
7270
+ outputJson(defs);
7271
+ return;
7272
+ }
7273
+ const rows = defs.map((d) => [
7274
+ d.code,
7275
+ d.loinc ?? "\u2014",
7276
+ d.names.pt[0] ?? "",
7277
+ d.unit ?? "\u2014",
7278
+ Array.isArray(d.category) ? d.category.join(", ") : d.category
7279
+ ]);
7280
+ outputText(formatTable(["C\xF3digo", "LOINC", "Nome (pt)", "Unidade", "Categoria"], rows));
7281
+ outputText(`
7282
+ Total: ${defs.length} biomarcadores`);
7283
+ }
7284
+
7285
+ // src/cli/commands/loinc-map.ts
7286
+ async function loincMap(_args, json) {
7287
+ const defs = getAllDefinitions().filter((d) => d.loinc);
7288
+ if (json) {
7289
+ const map = Object.fromEntries(defs.map((d) => [d.loinc, d.code]));
7290
+ outputJson(map);
7291
+ return;
7292
+ }
7293
+ const rows = defs.map((d) => [d.loinc, d.code, d.names.pt[0] ?? d.names.en[0] ?? ""]);
7294
+ outputText(formatTable(["LOINC", "C\xF3digo", "Nome (pt)"], rows));
7295
+ outputText(`
7296
+ Total: ${rows.length} mapeamentos`);
7297
+ }
7298
+
7299
+ // src/cli/commands/lookup.ts
7300
+ function displayDefinition(def) {
7301
+ const cat = Array.isArray(def.category) ? def.category.join(", ") : def.category;
7302
+ outputText(
7303
+ [
7304
+ `Biomarcador: ${def.code}`,
7305
+ ` LOINC: ${def.loinc ?? "\u2014"}`,
7306
+ ` Nomes (pt): ${def.names.pt.join(", ")}`,
7307
+ ` Nomes (en): ${def.names.en.join(", ")}`,
7308
+ ` Categoria: ${cat}`,
7309
+ ` Unidade: ${def.unit ?? "\u2014"}`,
7310
+ ` Sexo: ${def.sex ?? "ambos"}`,
7311
+ ` Vis\xEDvel: ${def.hidden ? "n\xE3o" : "sim"}`
7312
+ ].join("\n")
7313
+ );
7314
+ }
7315
+ async function lookup(args, json) {
7316
+ const code = args[0];
7317
+ if (!code) exitWithError("Uso: fhir-bio lookup <c\xF3digo>");
7318
+ const def = getDefinitionByCode(code);
7319
+ if (!def) exitWithError(`Biomarcador n\xE3o encontrado: ${code}`);
7320
+ if (json) {
7321
+ outputJson(def);
7322
+ } else {
7323
+ displayDefinition(def);
7324
+ }
7325
+ }
7326
+ async function lookupLoinc(args, json) {
7327
+ const loinc = args[0];
7328
+ if (!loinc) exitWithError("Uso: fhir-bio lookup-loinc <loinc>");
7329
+ const def = getDefinitionByLoinc(loinc);
7330
+ if (!def) exitWithError(`Biomarcador n\xE3o encontrado para LOINC: ${loinc}`);
7331
+ if (json) {
7332
+ outputJson(def);
7333
+ } else {
7334
+ displayDefinition(def);
7335
+ }
6954
7336
  }
6955
7337
 
6956
7338
  // src/cli/commands/range.ts
7339
+ import { parseArgs as parseArgs2 } from "util";
6957
7340
  async function range(args, json) {
6958
7341
  const { positionals, values } = parseArgs2({
6959
7342
  allowPositionals: true,
@@ -6990,11 +7373,18 @@ async function range(args, json) {
6990
7373
  const sexLabel = ctx.biologicalSex === "M" ? "Homem" : ctx.biologicalSex === "F" ? "Mulher" : "Geral";
6991
7374
  const ageLabel = ctx.age !== void 0 ? `, ${ctx.age} anos` : "";
6992
7375
  const fmt = (v) => v !== void 0 ? String(v) : "\u2014";
7376
+ const desenho = (kind) => kind === "display" ? " (desenho, n\xE3o \xE9 corte)" : "";
7377
+ const tipos = {
7378
+ "decision-threshold": "limiar de decis\xE3o",
7379
+ population: "distribui\xE7\xE3o populacional",
7380
+ "reference-interval": "intervalo de refer\xEAncia"
7381
+ };
6993
7382
  outputText(
6994
7383
  [
6995
7384
  `Faixa de Refer\xEAncia: ${code} (${sexLabel}${ageLabel})`,
6996
- ` M\xEDnimo: ${fmt(ref.min)} ${ref.unit}`,
6997
- ` M\xE1ximo: ${fmt(ref.max)} ${ref.unit}`,
7385
+ ` Tipo: ${ref.kind ? tipos[ref.kind] : "\u2014"}`,
7386
+ ` M\xEDnimo: ${fmt(ref.min)} ${ref.unit}${desenho(ref.minKind)}`,
7387
+ ` M\xE1ximo: ${fmt(ref.max)} ${ref.unit}${desenho(ref.maxKind)}`,
6998
7388
  ` \xD3timo (m\xEDn): ${fmt(ref.optimalMin)} ${ref.unit}`,
6999
7389
  ` \xD3timo (m\xE1x): ${fmt(ref.optimalMax)} ${ref.unit}`,
7000
7390
  ` Alerta (m\xE1x): ${fmt(ref.warningMax)} ${ref.unit}`,
@@ -7487,7 +7877,7 @@ var COMMANDS = {
7487
7877
  async function main() {
7488
7878
  const { command, help, json, resto, version } = dividirArgv(process.argv.slice(2));
7489
7879
  if (version) {
7490
- process.stdout.write(`${"0.33.0"}
7880
+ process.stdout.write(`${"0.34.0"}
7491
7881
  `);
7492
7882
  return;
7493
7883
  }