@precisa-saude/fhir-calculators 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/index.cjs +981 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +560 -0
- package/dist/index.d.ts +560 -0
- package/dist/index.js +981 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/phenoage/index.ts
|
|
8
|
+
var phenoage_exports = {};
|
|
9
|
+
__export(phenoage_exports, {
|
|
10
|
+
BIOMARKER_DESCRIPTIONS_PT: () => BIOMARKER_DESCRIPTIONS_PT,
|
|
11
|
+
BIOMARKER_LAB_INFO: () => BIOMARKER_LAB_INFO,
|
|
12
|
+
BIOMARKER_NAMES_PT: () => BIOMARKER_NAMES_PT,
|
|
13
|
+
BIOMARKER_RANGES: () => BIOMARKER_RANGES,
|
|
14
|
+
CONVERSION_FACTORS: () => CONVERSION_FACTORS,
|
|
15
|
+
FHIR_CODE_TO_PHENOAGE: () => FHIR_CODE_TO_PHENOAGE,
|
|
16
|
+
GOMPERTZ_PARAMS: () => GOMPERTZ_PARAMS,
|
|
17
|
+
PHENOAGE_COEFFICIENTS: () => PHENOAGE_COEFFICIENTS,
|
|
18
|
+
REQUIRED_BIOMARKERS: () => REQUIRED_BIOMARKERS,
|
|
19
|
+
TARGET_UNITS: () => TARGET_UNITS,
|
|
20
|
+
autoConvertToSI: () => autoConvertToSI,
|
|
21
|
+
calculatePhenoAge: () => calculatePhenoAge,
|
|
22
|
+
convertToSI: () => convertToSI,
|
|
23
|
+
needsConversion: () => needsConversion,
|
|
24
|
+
validateBiomarkers: () => validateBiomarkers
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/phenoage/constants.ts
|
|
28
|
+
var PHENOAGE_COEFFICIENTS = {
|
|
29
|
+
age: 0.0804,
|
|
30
|
+
albumin: -0.0336,
|
|
31
|
+
alkalinePhosphatase: 19e-4,
|
|
32
|
+
creatinine: 95e-4,
|
|
33
|
+
glucose: 0.1953,
|
|
34
|
+
intercept: -19.9067,
|
|
35
|
+
logCrp: 0.0954,
|
|
36
|
+
lymphocytePercent: -0.012,
|
|
37
|
+
mcv: 0.0268,
|
|
38
|
+
rdw: 0.3306,
|
|
39
|
+
wbc: 0.0554
|
|
40
|
+
};
|
|
41
|
+
var GOMPERTZ_PARAMS = {
|
|
42
|
+
ageCoefficient: 0.090165,
|
|
43
|
+
baseAge: 141.50225,
|
|
44
|
+
gamma: 76927e-7,
|
|
45
|
+
mortalityConstant: 553e-5
|
|
46
|
+
};
|
|
47
|
+
var BIOMARKER_RANGES = {
|
|
48
|
+
albumin: { max: 60, min: 10, typical: { max: 50, min: 35 }, unit: "g/L" },
|
|
49
|
+
alkalinePhosphatase: { max: 500, min: 10, typical: { max: 130, min: 40 }, unit: "U/L" },
|
|
50
|
+
chronologicalAge: { max: 120, min: 18, typical: { max: 100, min: 18 }, unit: "anos" },
|
|
51
|
+
creatinine: { max: 500, min: 20, typical: { max: 110, min: 60 }, unit: "\u03BCmol/L" },
|
|
52
|
+
crp: { max: 200, min: 0.01, typical: { max: 3, min: 0 }, unit: "mg/L" },
|
|
53
|
+
glucose: { max: 30, min: 2, typical: { max: 6, min: 4 }, unit: "mmol/L" },
|
|
54
|
+
lymphocytePercent: { max: 80, min: 1, typical: { max: 40, min: 20 }, unit: "%" },
|
|
55
|
+
mcv: { max: 150, min: 50, typical: { max: 100, min: 80 }, unit: "fL" },
|
|
56
|
+
rdw: { max: 25, min: 8, typical: { max: 15, min: 11 }, unit: "%" },
|
|
57
|
+
wbc: { max: 30, min: 1, typical: { max: 11, min: 4 }, unit: "10^9/L" }
|
|
58
|
+
};
|
|
59
|
+
var FHIR_CODE_TO_PHENOAGE = {
|
|
60
|
+
Albumin: "albumin",
|
|
61
|
+
AlkalinePhosphatase: "alkalinePhosphatase",
|
|
62
|
+
Creatinine: "creatinine",
|
|
63
|
+
CRP: "crp",
|
|
64
|
+
Glucose: "glucose",
|
|
65
|
+
Lymphocytes: "lymphocytePercent",
|
|
66
|
+
MCV: "mcv",
|
|
67
|
+
RDW: "rdw",
|
|
68
|
+
WBC: "wbc"
|
|
69
|
+
};
|
|
70
|
+
var REQUIRED_BIOMARKERS = [
|
|
71
|
+
"Albumin",
|
|
72
|
+
"Creatinine",
|
|
73
|
+
"Glucose",
|
|
74
|
+
"CRP",
|
|
75
|
+
"Lymphocytes",
|
|
76
|
+
"MCV",
|
|
77
|
+
"RDW",
|
|
78
|
+
"AlkalinePhosphatase",
|
|
79
|
+
"WBC"
|
|
80
|
+
];
|
|
81
|
+
var BIOMARKER_NAMES_PT = {
|
|
82
|
+
age: "Idade",
|
|
83
|
+
albumin: "Albumina",
|
|
84
|
+
alkalinePhosphatase: "Fosfatase Alcalina",
|
|
85
|
+
creatinine: "Creatinina",
|
|
86
|
+
crp: "PCR",
|
|
87
|
+
glucose: "Glicose",
|
|
88
|
+
intercept: "Intercepto",
|
|
89
|
+
lymphocytePercent: "Linf\xF3citos",
|
|
90
|
+
mcv: "VCM",
|
|
91
|
+
rdw: "RDW",
|
|
92
|
+
wbc: "Leuc\xF3citos"
|
|
93
|
+
};
|
|
94
|
+
var BIOMARKER_DESCRIPTIONS_PT = {
|
|
95
|
+
age: "Sua idade cronol\xF3gica em anos",
|
|
96
|
+
albumin: "Prote\xEDna do f\xEDgado que indica estado nutricional e fun\xE7\xE3o hep\xE1tica",
|
|
97
|
+
alkalinePhosphatase: "Enzima que reflete sa\xFAde do f\xEDgado e dos ossos",
|
|
98
|
+
creatinine: "Marcador de fun\xE7\xE3o renal produzido pelos m\xFAsculos",
|
|
99
|
+
crp: "Prote\xEDna C-reativa, indica inflama\xE7\xE3o no corpo",
|
|
100
|
+
glucose: "N\xEDvel de a\xE7\xFAcar no sangue, relacionado ao metabolismo",
|
|
101
|
+
intercept: "Constante da f\xF3rmula de regress\xE3o",
|
|
102
|
+
lymphocytePercent: "C\xE9lulas de defesa do sistema imunol\xF3gico",
|
|
103
|
+
mcv: "Volume m\xE9dio das hem\xE1cias, indica sa\xFAde das c\xE9lulas vermelhas",
|
|
104
|
+
rdw: "Varia\xE7\xE3o no tamanho das hem\xE1cias, marcador de inflama\xE7\xE3o",
|
|
105
|
+
wbc: "Total de c\xE9lulas brancas, indica estado imunol\xF3gico"
|
|
106
|
+
};
|
|
107
|
+
var BIOMARKER_LAB_INFO = {
|
|
108
|
+
Albumin: {
|
|
109
|
+
loincCode: "1751-7",
|
|
110
|
+
nameEn: "Albumin [Mass/volume] in Serum or Plasma",
|
|
111
|
+
namePt: "Albumina"
|
|
112
|
+
},
|
|
113
|
+
AlkalinePhosphatase: {
|
|
114
|
+
loincCode: "6768-6",
|
|
115
|
+
nameEn: "Alkaline phosphatase [Enzymatic activity/volume] in Serum or Plasma",
|
|
116
|
+
namePt: "Fosfatase Alcalina"
|
|
117
|
+
},
|
|
118
|
+
Creatinine: {
|
|
119
|
+
loincCode: "2160-0",
|
|
120
|
+
nameEn: "Creatinine [Mass/volume] in Serum or Plasma",
|
|
121
|
+
namePt: "Creatinina"
|
|
122
|
+
},
|
|
123
|
+
CRP: {
|
|
124
|
+
loincCode: "1988-5",
|
|
125
|
+
nameEn: "C reactive protein [Mass/volume] in Serum or Plasma",
|
|
126
|
+
namePt: "Prote\xEDna C-Reativa (PCR)"
|
|
127
|
+
},
|
|
128
|
+
Glucose: {
|
|
129
|
+
loincCode: "2345-7",
|
|
130
|
+
nameEn: "Glucose [Mass/volume] in Serum or Plasma",
|
|
131
|
+
namePt: "Glicose"
|
|
132
|
+
},
|
|
133
|
+
Lymphocytes: {
|
|
134
|
+
loincCode: "736-9",
|
|
135
|
+
nameEn: "Lymphocytes/100 leukocytes in Blood by Automated count",
|
|
136
|
+
namePt: "Linf\xF3citos (%)"
|
|
137
|
+
},
|
|
138
|
+
MCV: {
|
|
139
|
+
loincCode: "787-2",
|
|
140
|
+
nameEn: "MCV [Entitic volume] by Automated count",
|
|
141
|
+
namePt: "Volume Corpuscular M\xE9dio (VCM)"
|
|
142
|
+
},
|
|
143
|
+
RDW: {
|
|
144
|
+
loincCode: "788-0",
|
|
145
|
+
nameEn: "Erythrocyte distribution width [Ratio] by Automated count",
|
|
146
|
+
namePt: "Amplitude de Distribui\xE7\xE3o dos Eritr\xF3citos (RDW)"
|
|
147
|
+
},
|
|
148
|
+
WBC: {
|
|
149
|
+
loincCode: "6690-2",
|
|
150
|
+
nameEn: "Leukocytes [#/volume] in Blood by Automated count",
|
|
151
|
+
namePt: "Contagem de Leuc\xF3citos"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/phenoage/calculator.ts
|
|
156
|
+
var calculateLinearPredictor = (input) => {
|
|
157
|
+
const logCrp = Math.log(Math.max(input.crp, 0.1));
|
|
158
|
+
const components = [
|
|
159
|
+
{
|
|
160
|
+
coefficient: PHENOAGE_COEFFICIENTS.albumin,
|
|
161
|
+
key: "albumin",
|
|
162
|
+
unit: BIOMARKER_RANGES.albumin.unit,
|
|
163
|
+
value: input.albumin
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
coefficient: PHENOAGE_COEFFICIENTS.creatinine,
|
|
167
|
+
key: "creatinine",
|
|
168
|
+
unit: BIOMARKER_RANGES.creatinine.unit,
|
|
169
|
+
value: input.creatinine
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
coefficient: PHENOAGE_COEFFICIENTS.glucose,
|
|
173
|
+
key: "glucose",
|
|
174
|
+
unit: BIOMARKER_RANGES.glucose.unit,
|
|
175
|
+
value: input.glucose
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
coefficient: PHENOAGE_COEFFICIENTS.logCrp,
|
|
179
|
+
displayValue: `ln(${input.crp.toFixed(2)})`,
|
|
180
|
+
key: "crp",
|
|
181
|
+
unit: "ln(mg/L)",
|
|
182
|
+
value: logCrp
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
coefficient: PHENOAGE_COEFFICIENTS.lymphocytePercent,
|
|
186
|
+
key: "lymphocytePercent",
|
|
187
|
+
unit: BIOMARKER_RANGES.lymphocytePercent.unit,
|
|
188
|
+
value: input.lymphocytePercent
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
coefficient: PHENOAGE_COEFFICIENTS.mcv,
|
|
192
|
+
key: "mcv",
|
|
193
|
+
unit: BIOMARKER_RANGES.mcv.unit,
|
|
194
|
+
value: input.mcv
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
coefficient: PHENOAGE_COEFFICIENTS.rdw,
|
|
198
|
+
key: "rdw",
|
|
199
|
+
unit: BIOMARKER_RANGES.rdw.unit,
|
|
200
|
+
value: input.rdw
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
coefficient: PHENOAGE_COEFFICIENTS.alkalinePhosphatase,
|
|
204
|
+
key: "alkalinePhosphatase",
|
|
205
|
+
unit: BIOMARKER_RANGES.alkalinePhosphatase.unit,
|
|
206
|
+
value: input.alkalinePhosphatase
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
coefficient: PHENOAGE_COEFFICIENTS.wbc,
|
|
210
|
+
key: "wbc",
|
|
211
|
+
unit: BIOMARKER_RANGES.wbc.unit,
|
|
212
|
+
value: input.wbc
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
coefficient: PHENOAGE_COEFFICIENTS.age,
|
|
216
|
+
displayValue: `${Math.round(input.chronologicalAge)} anos`,
|
|
217
|
+
key: "age",
|
|
218
|
+
unit: "anos",
|
|
219
|
+
value: input.chronologicalAge
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
const breakdown = components.map((c) => ({
|
|
223
|
+
coefficient: c.coefficient,
|
|
224
|
+
contribution: Math.round(c.coefficient * c.value * 1e4) / 1e4,
|
|
225
|
+
key: c.key,
|
|
226
|
+
name: _nullishCoalesce(BIOMARKER_NAMES_PT[c.key], () => ( c.key)),
|
|
227
|
+
valueWithUnit: c.displayValue ? `${c.displayValue}` : `${c.value.toFixed(2)} ${c.unit}`
|
|
228
|
+
}));
|
|
229
|
+
breakdown.push({
|
|
230
|
+
coefficient: PHENOAGE_COEFFICIENTS.intercept,
|
|
231
|
+
contribution: PHENOAGE_COEFFICIENTS.intercept,
|
|
232
|
+
key: "intercept",
|
|
233
|
+
name: _nullishCoalesce(BIOMARKER_NAMES_PT["intercept"], () => ( "Intercepto")),
|
|
234
|
+
valueWithUnit: "-"
|
|
235
|
+
});
|
|
236
|
+
const xb = PHENOAGE_COEFFICIENTS.intercept + components.reduce((sum, c) => sum + c.coefficient * c.value, 0);
|
|
237
|
+
return { breakdown, xb };
|
|
238
|
+
};
|
|
239
|
+
var calculateMortalityScore = (xb) => {
|
|
240
|
+
const { gamma } = GOMPERTZ_PARAMS;
|
|
241
|
+
const hazardRatio = Math.exp(xb);
|
|
242
|
+
const cumulativeHazard = hazardRatio * (Math.exp(120 * gamma) - 1) / gamma;
|
|
243
|
+
return 1 - Math.exp(-cumulativeHazard);
|
|
244
|
+
};
|
|
245
|
+
var mortalityScoreToPhenoAge = (mortalityScore) => {
|
|
246
|
+
const { ageCoefficient, baseAge, mortalityConstant } = GOMPERTZ_PARAMS;
|
|
247
|
+
const innerLog = Math.log(1 - mortalityScore);
|
|
248
|
+
const outerLog = Math.log(-mortalityConstant * innerLog);
|
|
249
|
+
return baseAge + outerLog / ageCoefficient;
|
|
250
|
+
};
|
|
251
|
+
var calculatePhenoAge = (input) => {
|
|
252
|
+
const inputValues = {
|
|
253
|
+
albumin: input.albumin,
|
|
254
|
+
alkalinePhosphatase: input.alkalinePhosphatase,
|
|
255
|
+
chronologicalAge: input.chronologicalAge,
|
|
256
|
+
creatinine: input.creatinine,
|
|
257
|
+
crp: input.crp,
|
|
258
|
+
glucose: input.glucose,
|
|
259
|
+
lymphocytePercent: input.lymphocytePercent,
|
|
260
|
+
mcv: input.mcv,
|
|
261
|
+
rdw: input.rdw,
|
|
262
|
+
wbc: input.wbc
|
|
263
|
+
};
|
|
264
|
+
for (const [key, value] of Object.entries(inputValues)) {
|
|
265
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
266
|
+
throw new Error(`Invalid input value for ${key}: ${value}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const { breakdown, xb } = calculateLinearPredictor(input);
|
|
270
|
+
const mortalityScore = calculateMortalityScore(xb);
|
|
271
|
+
const phenoAge = mortalityScoreToPhenoAge(mortalityScore);
|
|
272
|
+
const ageDifference = phenoAge - input.chronologicalAge;
|
|
273
|
+
return {
|
|
274
|
+
ageDifference: Math.round(ageDifference * 10) / 10,
|
|
275
|
+
breakdown,
|
|
276
|
+
calculatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
|
+
chronologicalAge: input.chronologicalAge,
|
|
278
|
+
linearPredictor: Math.round(xb * 1e4) / 1e4,
|
|
279
|
+
mortalityScore: Math.round(mortalityScore * 1e4) / 1e4,
|
|
280
|
+
phenoAge: Math.round(phenoAge * 10) / 10
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
var validateBiomarkers = (input) => {
|
|
284
|
+
const errors = [];
|
|
285
|
+
const checkRange = (value, key, name) => {
|
|
286
|
+
const range = BIOMARKER_RANGES[key];
|
|
287
|
+
if (value < range.min || value > range.max) {
|
|
288
|
+
errors.push(`${name} (${value}) fora do intervalo esperado [${range.min}-${range.max}]`);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
checkRange(input.albumin, "albumin", "Albumina");
|
|
292
|
+
checkRange(input.creatinine, "creatinine", "Creatinina");
|
|
293
|
+
checkRange(input.glucose, "glucose", "Glicose");
|
|
294
|
+
checkRange(input.crp, "crp", "PCR");
|
|
295
|
+
checkRange(input.lymphocytePercent, "lymphocytePercent", "Linf\xF3citos");
|
|
296
|
+
checkRange(input.mcv, "mcv", "VCM");
|
|
297
|
+
checkRange(input.rdw, "rdw", "RDW");
|
|
298
|
+
checkRange(input.alkalinePhosphatase, "alkalinePhosphatase", "Fosfatase Alcalina");
|
|
299
|
+
checkRange(input.wbc, "wbc", "Leuc\xF3citos");
|
|
300
|
+
checkRange(input.chronologicalAge, "chronologicalAge", "Idade");
|
|
301
|
+
if (input.crp <= 0) {
|
|
302
|
+
errors.push("PCR deve ser maior que 0");
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
errors,
|
|
306
|
+
isValid: errors.length === 0
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/phenoage/unit-converters.ts
|
|
311
|
+
var CONVERSION_FACTORS = {
|
|
312
|
+
albumin: {
|
|
313
|
+
"g/dL": 10,
|
|
314
|
+
// g/dL → g/L
|
|
315
|
+
"g/L": 1
|
|
316
|
+
},
|
|
317
|
+
alkalinePhosphatase: {
|
|
318
|
+
"U/L": 1
|
|
319
|
+
},
|
|
320
|
+
creatinine: {
|
|
321
|
+
"mg/dL": 88.4,
|
|
322
|
+
// mg/dL → μmol/L
|
|
323
|
+
"umol/L": 1,
|
|
324
|
+
"\u03BCmol/L": 1
|
|
325
|
+
},
|
|
326
|
+
crp: {
|
|
327
|
+
"mg/dL": 10,
|
|
328
|
+
// mg/dL → mg/L
|
|
329
|
+
"mg/L": 1
|
|
330
|
+
},
|
|
331
|
+
glucose: {
|
|
332
|
+
"mg/dL": 1 / 18.0182,
|
|
333
|
+
// mg/dL → mmol/L
|
|
334
|
+
"mmol/L": 1
|
|
335
|
+
},
|
|
336
|
+
lymphocytePercent: {
|
|
337
|
+
"%": 1
|
|
338
|
+
},
|
|
339
|
+
mcv: {
|
|
340
|
+
fL: 1
|
|
341
|
+
},
|
|
342
|
+
rdw: {
|
|
343
|
+
"%": 1
|
|
344
|
+
},
|
|
345
|
+
wbc: {
|
|
346
|
+
"/uL": 1e-3,
|
|
347
|
+
"/\xB5L": 1e-3,
|
|
348
|
+
// cells/μL → 10^9/L
|
|
349
|
+
"10^9/L": 1,
|
|
350
|
+
"1000/\u03BCL": 1,
|
|
351
|
+
"10\xB3/\xB5L": 1,
|
|
352
|
+
// Same as 10^9/L
|
|
353
|
+
"K/uL": 1,
|
|
354
|
+
"K/\xB5L": 1,
|
|
355
|
+
"Thousand/uL": 1,
|
|
356
|
+
// Brazilian labs often use this format
|
|
357
|
+
"thousand/uL": 1,
|
|
358
|
+
"Thousand/\xB5L": 1,
|
|
359
|
+
"thousand/\xB5L": 1
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var TARGET_UNITS = {
|
|
363
|
+
albumin: "g/L",
|
|
364
|
+
alkalinePhosphatase: "U/L",
|
|
365
|
+
creatinine: "\u03BCmol/L",
|
|
366
|
+
crp: "mg/L",
|
|
367
|
+
glucose: "mmol/L",
|
|
368
|
+
lymphocytePercent: "%",
|
|
369
|
+
mcv: "fL",
|
|
370
|
+
rdw: "%",
|
|
371
|
+
wbc: "10^9/L"
|
|
372
|
+
};
|
|
373
|
+
var convertToSI = (biomarker, value, sourceUnit) => {
|
|
374
|
+
const factors = CONVERSION_FACTORS[biomarker];
|
|
375
|
+
if (!factors) {
|
|
376
|
+
throw new Error(`Unknown biomarker: ${biomarker}`);
|
|
377
|
+
}
|
|
378
|
+
const normalizedUnit = sourceUnit.trim();
|
|
379
|
+
const factor = factors[normalizedUnit];
|
|
380
|
+
if (factor === void 0) {
|
|
381
|
+
const normalizedLower = normalizedUnit.toLowerCase();
|
|
382
|
+
const exactMatch = Object.keys(factors).find((key) => key.toLowerCase() === normalizedLower);
|
|
383
|
+
if (exactMatch) {
|
|
384
|
+
return value * factors[exactMatch];
|
|
385
|
+
}
|
|
386
|
+
const sortedKeys = Object.keys(factors).sort((a, b) => b.length - a.length);
|
|
387
|
+
const partialMatch = sortedKeys.find((key) => normalizedLower.includes(key.toLowerCase()));
|
|
388
|
+
if (partialMatch) {
|
|
389
|
+
return value * factors[partialMatch];
|
|
390
|
+
}
|
|
391
|
+
throw new Error(`Unknown unit "${sourceUnit}" for ${biomarker}`);
|
|
392
|
+
}
|
|
393
|
+
return value * factor;
|
|
394
|
+
};
|
|
395
|
+
var needsConversion = (biomarker, sourceUnit) => {
|
|
396
|
+
const targetUnit = TARGET_UNITS[biomarker];
|
|
397
|
+
if (!targetUnit) return false;
|
|
398
|
+
const normalizedSource = sourceUnit.trim().toLowerCase();
|
|
399
|
+
const normalizedTarget = targetUnit.toLowerCase();
|
|
400
|
+
return normalizedSource !== normalizedTarget;
|
|
401
|
+
};
|
|
402
|
+
var autoConvertToSI = (biomarker, value, unit) => {
|
|
403
|
+
if (unit) {
|
|
404
|
+
try {
|
|
405
|
+
const converted = convertToSI(biomarker, value, unit);
|
|
406
|
+
const wasConverted = needsConversion(biomarker, unit);
|
|
407
|
+
return {
|
|
408
|
+
unit: TARGET_UNITS[biomarker] || unit,
|
|
409
|
+
value: converted,
|
|
410
|
+
wasConverted
|
|
411
|
+
};
|
|
412
|
+
} catch (e) {
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
switch (biomarker) {
|
|
416
|
+
case "albumin":
|
|
417
|
+
if (value < 10) {
|
|
418
|
+
return { unit: "g/L", value: value * 10, wasConverted: true };
|
|
419
|
+
}
|
|
420
|
+
return { unit: "g/L", value, wasConverted: false };
|
|
421
|
+
case "creatinine":
|
|
422
|
+
if (value < 15) {
|
|
423
|
+
return { unit: "\u03BCmol/L", value: value * 88.4, wasConverted: true };
|
|
424
|
+
}
|
|
425
|
+
return { unit: "\u03BCmol/L", value, wasConverted: false };
|
|
426
|
+
case "glucose":
|
|
427
|
+
if (value > 20) {
|
|
428
|
+
return { unit: "mmol/L", value: value / 18.0182, wasConverted: true };
|
|
429
|
+
}
|
|
430
|
+
return { unit: "mmol/L", value, wasConverted: false };
|
|
431
|
+
case "wbc":
|
|
432
|
+
if (value > 100) {
|
|
433
|
+
return { unit: "10^9/L", value: value / 1e3, wasConverted: true };
|
|
434
|
+
}
|
|
435
|
+
return { unit: "10^9/L", value, wasConverted: false };
|
|
436
|
+
default:
|
|
437
|
+
return { unit: TARGET_UNITS[biomarker] || "", value, wasConverted: false };
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// src/brdmrisc/index.ts
|
|
442
|
+
var brdmrisc_exports = {};
|
|
443
|
+
__export(brdmrisc_exports, {
|
|
444
|
+
BIOMARKER_NAMES_PT: () => BIOMARKER_NAMES_PT2,
|
|
445
|
+
BIOMARKER_RANGES: () => BIOMARKER_RANGES2,
|
|
446
|
+
BIOMARKER_UNITS: () => BIOMARKER_UNITS,
|
|
447
|
+
BRDMRISC_BIOMARKER_CODES: () => BRDMRISC_BIOMARKER_CODES,
|
|
448
|
+
BRDMRISC_MODELS: () => BRDMRISC_MODELS,
|
|
449
|
+
CONVERSION_FACTORS: () => CONVERSION_FACTORS2,
|
|
450
|
+
FHIR_CODE_TO_BRDMRISC: () => FHIR_CODE_TO_BRDMRISC,
|
|
451
|
+
FOLLOW_UP_YEARS: () => FOLLOW_UP_YEARS,
|
|
452
|
+
LAB_MODEL_PRIORITY: () => LAB_MODEL_PRIORITY,
|
|
453
|
+
LAB_ONLY_MODELS: () => LAB_ONLY_MODELS,
|
|
454
|
+
RISK_THRESHOLDS: () => RISK_THRESHOLDS,
|
|
455
|
+
TARGET_UNITS: () => TARGET_UNITS2,
|
|
456
|
+
autoConvertToTarget: () => autoConvertToTarget,
|
|
457
|
+
calculateBrDMrisc: () => calculateBrDMrisc,
|
|
458
|
+
classifyRisk: () => classifyRisk,
|
|
459
|
+
convertToTargetUnit: () => convertToTargetUnit,
|
|
460
|
+
selectModel: () => selectModel,
|
|
461
|
+
validateBiomarkers: () => validateBiomarkers2
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// src/brdmrisc/constants.ts
|
|
465
|
+
var FOLLOW_UP_YEARS = 7.4;
|
|
466
|
+
var BRDMRISC_MODELS = [
|
|
467
|
+
// === Lab-only models (MVP) ===
|
|
468
|
+
{
|
|
469
|
+
auc: 0.776,
|
|
470
|
+
coefficients: {
|
|
471
|
+
fpg: 0.0352
|
|
472
|
+
},
|
|
473
|
+
id: 1,
|
|
474
|
+
intercept: -5.8282,
|
|
475
|
+
isLabOnly: true,
|
|
476
|
+
name: "FPG only",
|
|
477
|
+
namePt: "Apenas Glicemia de Jejum",
|
|
478
|
+
requiredBiomarkers: ["fpg"]
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
auc: 0.668,
|
|
482
|
+
coefficients: {
|
|
483
|
+
hba1c: 0.731
|
|
484
|
+
},
|
|
485
|
+
id: 2,
|
|
486
|
+
intercept: -6.3199,
|
|
487
|
+
isLabOnly: true,
|
|
488
|
+
name: "HbA1c only",
|
|
489
|
+
namePt: "Apenas HbA1c",
|
|
490
|
+
requiredBiomarkers: ["hba1c"]
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
auc: 0.793,
|
|
494
|
+
coefficients: {
|
|
495
|
+
fpg: 0.029,
|
|
496
|
+
hba1c: 0.4427
|
|
497
|
+
},
|
|
498
|
+
id: 3,
|
|
499
|
+
intercept: -7.37,
|
|
500
|
+
isLabOnly: true,
|
|
501
|
+
name: "FPG + HbA1c",
|
|
502
|
+
namePt: "Glicemia + HbA1c",
|
|
503
|
+
requiredBiomarkers: ["fpg", "hba1c"]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
auc: 0.79,
|
|
507
|
+
coefficients: {
|
|
508
|
+
fpg: 0.0345,
|
|
509
|
+
triglycerides: 17e-4
|
|
510
|
+
},
|
|
511
|
+
id: 4,
|
|
512
|
+
intercept: -5.9706,
|
|
513
|
+
isLabOnly: true,
|
|
514
|
+
name: "FPG + Triglycerides",
|
|
515
|
+
namePt: "Glicemia + Triglicer\xEDdeos",
|
|
516
|
+
requiredBiomarkers: ["fpg", "triglycerides"]
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
auc: 0.796,
|
|
520
|
+
coefficients: {
|
|
521
|
+
fpg: 0.0338,
|
|
522
|
+
hdlc: -0.0161,
|
|
523
|
+
triglycerides: 11e-4
|
|
524
|
+
},
|
|
525
|
+
id: 5,
|
|
526
|
+
intercept: -5.3879,
|
|
527
|
+
isLabOnly: true,
|
|
528
|
+
name: "FPG + Lipids",
|
|
529
|
+
namePt: "Glicemia + Lip\xEDdios",
|
|
530
|
+
requiredBiomarkers: ["fpg", "triglycerides", "hdlc"]
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
auc: 0.813,
|
|
534
|
+
coefficients: {
|
|
535
|
+
fpg: 0.0267,
|
|
536
|
+
hba1c: 0.3943,
|
|
537
|
+
hdlc: -0.0149,
|
|
538
|
+
triglycerides: 9e-4
|
|
539
|
+
},
|
|
540
|
+
id: 6,
|
|
541
|
+
intercept: -6.9195,
|
|
542
|
+
isLabOnly: true,
|
|
543
|
+
name: "FPG + HbA1c + Lipids",
|
|
544
|
+
namePt: "Glicemia + HbA1c + Lip\xEDdios",
|
|
545
|
+
requiredBiomarkers: ["fpg", "hba1c", "triglycerides", "hdlc"]
|
|
546
|
+
},
|
|
547
|
+
// === Clinical models (Phase 2 — require user input) ===
|
|
548
|
+
{
|
|
549
|
+
auc: 0.744,
|
|
550
|
+
coefficients: {
|
|
551
|
+
bmi: 0.0642,
|
|
552
|
+
familyHistory: 0.5915,
|
|
553
|
+
waist: 0.0111
|
|
554
|
+
},
|
|
555
|
+
id: 7,
|
|
556
|
+
intercept: -6.5023,
|
|
557
|
+
isLabOnly: false,
|
|
558
|
+
name: "Clinical only",
|
|
559
|
+
namePt: "Apenas Cl\xEDnico",
|
|
560
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory"]
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
auc: 0.8,
|
|
564
|
+
coefficients: {
|
|
565
|
+
bmi: 0.0454,
|
|
566
|
+
familyHistory: 0.475,
|
|
567
|
+
fpg: 0.0305,
|
|
568
|
+
waist: 47e-4
|
|
569
|
+
},
|
|
570
|
+
id: 8,
|
|
571
|
+
intercept: -7.3297,
|
|
572
|
+
isLabOnly: false,
|
|
573
|
+
name: "Clinical + FPG",
|
|
574
|
+
namePt: "Cl\xEDnico + Glicemia",
|
|
575
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "fpg"]
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
auc: 0.725,
|
|
579
|
+
coefficients: {
|
|
580
|
+
bmi: 0.0471,
|
|
581
|
+
familyHistory: 0.5284,
|
|
582
|
+
hba1c: 0.6001,
|
|
583
|
+
waist: 63e-4
|
|
584
|
+
},
|
|
585
|
+
id: 9,
|
|
586
|
+
intercept: -8.0917,
|
|
587
|
+
isLabOnly: false,
|
|
588
|
+
name: "Clinical + HbA1c",
|
|
589
|
+
namePt: "Cl\xEDnico + HbA1c",
|
|
590
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "hba1c"]
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
auc: 0.814,
|
|
594
|
+
coefficients: {
|
|
595
|
+
bmi: 0.0373,
|
|
596
|
+
familyHistory: 0.4393,
|
|
597
|
+
fpg: 0.0253,
|
|
598
|
+
hba1c: 0.3688,
|
|
599
|
+
waist: 29e-4
|
|
600
|
+
},
|
|
601
|
+
id: 10,
|
|
602
|
+
intercept: -8.5817,
|
|
603
|
+
isLabOnly: false,
|
|
604
|
+
name: "Clinical + FPG + HbA1c",
|
|
605
|
+
namePt: "Cl\xEDnico + Glicemia + HbA1c",
|
|
606
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "fpg", "hba1c"]
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
auc: 0.808,
|
|
610
|
+
coefficients: {
|
|
611
|
+
bmi: 0.0427,
|
|
612
|
+
familyHistory: 0.4532,
|
|
613
|
+
fpg: 0.0299,
|
|
614
|
+
triglycerides: 15e-4,
|
|
615
|
+
waist: 31e-4
|
|
616
|
+
},
|
|
617
|
+
id: 11,
|
|
618
|
+
intercept: -7.5028,
|
|
619
|
+
isLabOnly: false,
|
|
620
|
+
name: "Clinical + FPG + Triglycerides",
|
|
621
|
+
namePt: "Cl\xEDnico + Glicemia + Triglicer\xEDdeos",
|
|
622
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "fpg", "triglycerides"]
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
auc: 0.813,
|
|
626
|
+
coefficients: {
|
|
627
|
+
bmi: 0.0408,
|
|
628
|
+
familyHistory: 0.4396,
|
|
629
|
+
fpg: 0.0293,
|
|
630
|
+
hdlc: -0.0128,
|
|
631
|
+
triglycerides: 9e-4,
|
|
632
|
+
waist: 19e-4
|
|
633
|
+
},
|
|
634
|
+
id: 12,
|
|
635
|
+
intercept: -6.9757,
|
|
636
|
+
isLabOnly: false,
|
|
637
|
+
name: "Clinical + FPG + Lipids",
|
|
638
|
+
namePt: "Cl\xEDnico + Glicemia + Lip\xEDdios",
|
|
639
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "fpg", "triglycerides", "hdlc"]
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
auc: 0.822,
|
|
643
|
+
coefficients: {
|
|
644
|
+
bmi: 0.0334,
|
|
645
|
+
familyHistory: 0.4107,
|
|
646
|
+
fpg: 0.0238,
|
|
647
|
+
hba1c: 0.3266,
|
|
648
|
+
hdlc: -0.0117,
|
|
649
|
+
triglycerides: 6e-4,
|
|
650
|
+
waist: 15e-4
|
|
651
|
+
},
|
|
652
|
+
id: 13,
|
|
653
|
+
intercept: -8.2,
|
|
654
|
+
isLabOnly: false,
|
|
655
|
+
name: "Clinical + FPG + HbA1c + Lipids",
|
|
656
|
+
namePt: "Cl\xEDnico + Glicemia + HbA1c + Lip\xEDdios",
|
|
657
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "fpg", "hba1c", "triglycerides", "hdlc"]
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
auc: 0.699,
|
|
661
|
+
coefficients: {
|
|
662
|
+
bmi: 0.0494,
|
|
663
|
+
ethnicity: 0.2901,
|
|
664
|
+
familyHistory: 0.557,
|
|
665
|
+
hypertension: 0.3653,
|
|
666
|
+
waist: 71e-4
|
|
667
|
+
},
|
|
668
|
+
id: 14,
|
|
669
|
+
intercept: -7.244,
|
|
670
|
+
isLabOnly: false,
|
|
671
|
+
name: "Clinical extended",
|
|
672
|
+
namePt: "Cl\xEDnico Estendido",
|
|
673
|
+
requiredBiomarkers: ["bmi", "waist", "familyHistory", "ethnicity", "hypertension"]
|
|
674
|
+
}
|
|
675
|
+
];
|
|
676
|
+
var LAB_ONLY_MODELS = BRDMRISC_MODELS.filter((m) => m.isLabOnly);
|
|
677
|
+
var LAB_MODEL_PRIORITY = [6, 5, 3, 4, 1, 2];
|
|
678
|
+
var FHIR_CODE_TO_BRDMRISC = {
|
|
679
|
+
Glucose: "fpg",
|
|
680
|
+
HbA1c: "hba1c",
|
|
681
|
+
HDL: "hdlc",
|
|
682
|
+
Triglycerides: "triglycerides"
|
|
683
|
+
};
|
|
684
|
+
var BRDMRISC_BIOMARKER_CODES = ["Glucose", "HbA1c", "Triglycerides", "HDL"];
|
|
685
|
+
var BIOMARKER_NAMES_PT2 = {
|
|
686
|
+
fpg: "Glicemia de Jejum",
|
|
687
|
+
hba1c: "Hemoglobina Glicada",
|
|
688
|
+
hdlc: "HDL-colesterol",
|
|
689
|
+
intercept: "Intercepto",
|
|
690
|
+
triglycerides: "Triglicer\xEDdeos"
|
|
691
|
+
};
|
|
692
|
+
var BIOMARKER_UNITS = {
|
|
693
|
+
fpg: "mg/dL",
|
|
694
|
+
hba1c: "%",
|
|
695
|
+
hdlc: "mg/dL",
|
|
696
|
+
triglycerides: "mg/dL"
|
|
697
|
+
};
|
|
698
|
+
var BIOMARKER_RANGES2 = {
|
|
699
|
+
fpg: { max: 500, min: 40, unit: "mg/dL" },
|
|
700
|
+
hba1c: { max: 20, min: 3, unit: "%" },
|
|
701
|
+
hdlc: { max: 150, min: 10, unit: "mg/dL" },
|
|
702
|
+
triglycerides: { max: 2e3, min: 20, unit: "mg/dL" }
|
|
703
|
+
};
|
|
704
|
+
var RISK_THRESHOLDS = {
|
|
705
|
+
high: 0.2,
|
|
706
|
+
// 20-35% — high risk
|
|
707
|
+
moderate: 0.1,
|
|
708
|
+
// 10-20% — moderate risk
|
|
709
|
+
veryHigh: 0.35
|
|
710
|
+
// >= 35% — very high risk
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// src/brdmrisc/calculator.ts
|
|
714
|
+
var selectModel = (input, labOnly = true) => {
|
|
715
|
+
const available = /* @__PURE__ */ new Set();
|
|
716
|
+
if (input.fpg !== void 0 && !Number.isNaN(input.fpg)) available.add("fpg");
|
|
717
|
+
if (input.hba1c !== void 0 && !Number.isNaN(input.hba1c)) available.add("hba1c");
|
|
718
|
+
if (input.triglycerides !== void 0 && !Number.isNaN(input.triglycerides))
|
|
719
|
+
available.add("triglycerides");
|
|
720
|
+
if (input.hdlc !== void 0 && !Number.isNaN(input.hdlc)) available.add("hdlc");
|
|
721
|
+
if (available.size === 0) return null;
|
|
722
|
+
const priority = labOnly ? LAB_MODEL_PRIORITY : BRDMRISC_MODELS.map((m) => m.id);
|
|
723
|
+
for (const modelId of priority) {
|
|
724
|
+
const model = BRDMRISC_MODELS.find((m) => m.id === modelId);
|
|
725
|
+
if (!model) continue;
|
|
726
|
+
if (labOnly && !model.isLabOnly) continue;
|
|
727
|
+
const hasAll = model.requiredBiomarkers.every((b) => available.has(b));
|
|
728
|
+
if (hasAll) return model;
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
};
|
|
732
|
+
var classifyRisk = (riskPercent) => {
|
|
733
|
+
const risk = riskPercent / 100;
|
|
734
|
+
if (risk >= RISK_THRESHOLDS.veryHigh) return "very-high";
|
|
735
|
+
if (risk >= RISK_THRESHOLDS.high) return "high";
|
|
736
|
+
if (risk >= RISK_THRESHOLDS.moderate) return "moderate";
|
|
737
|
+
return "low";
|
|
738
|
+
};
|
|
739
|
+
var calculateBrDMrisc = (input, model) => {
|
|
740
|
+
const selectedModel = _nullishCoalesce(model, () => ( selectModel(input)));
|
|
741
|
+
if (!selectedModel) {
|
|
742
|
+
throw new Error("No suitable model found for the available biomarkers");
|
|
743
|
+
}
|
|
744
|
+
const breakdown = [];
|
|
745
|
+
let x = selectedModel.intercept;
|
|
746
|
+
for (const [key, coeff] of Object.entries(selectedModel.coefficients)) {
|
|
747
|
+
const value = input[key];
|
|
748
|
+
if (value === void 0) {
|
|
749
|
+
throw new Error(`Missing required biomarker: ${key}`);
|
|
750
|
+
}
|
|
751
|
+
const contribution = coeff * value;
|
|
752
|
+
x += contribution;
|
|
753
|
+
breakdown.push({
|
|
754
|
+
coefficient: coeff,
|
|
755
|
+
contribution: Math.round(contribution * 1e4) / 1e4,
|
|
756
|
+
key,
|
|
757
|
+
name: _nullishCoalesce(BIOMARKER_NAMES_PT2[key], () => ( key)),
|
|
758
|
+
value,
|
|
759
|
+
valueWithUnit: `${value.toFixed(1)} ${_nullishCoalesce(BIOMARKER_UNITS[key], () => ( ""))}`.trim()
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
breakdown.push({
|
|
763
|
+
coefficient: selectedModel.intercept,
|
|
764
|
+
contribution: selectedModel.intercept,
|
|
765
|
+
key: "intercept",
|
|
766
|
+
name: _nullishCoalesce(BIOMARKER_NAMES_PT2["intercept"], () => ( "Intercepto")),
|
|
767
|
+
value: 1,
|
|
768
|
+
valueWithUnit: "-"
|
|
769
|
+
});
|
|
770
|
+
const p = 1 / (1 + Math.exp(-x));
|
|
771
|
+
const risk10y = 1 - Math.pow(1 - p, 10 / FOLLOW_UP_YEARS);
|
|
772
|
+
const riskPercent = Math.round(risk10y * 1e3) / 10;
|
|
773
|
+
return {
|
|
774
|
+
breakdown,
|
|
775
|
+
calculatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
776
|
+
modelUsed: selectedModel,
|
|
777
|
+
risk10y: Math.round(risk10y * 1e4) / 1e4,
|
|
778
|
+
riskCategory: classifyRisk(riskPercent),
|
|
779
|
+
riskPercent
|
|
780
|
+
};
|
|
781
|
+
};
|
|
782
|
+
var validateBiomarkers2 = (input) => {
|
|
783
|
+
const errors = [];
|
|
784
|
+
const checkRange = (value, key, name) => {
|
|
785
|
+
if (value === void 0) return;
|
|
786
|
+
const range = BIOMARKER_RANGES2[key];
|
|
787
|
+
if (!range) return;
|
|
788
|
+
if (value < range.min || value > range.max) {
|
|
789
|
+
errors.push(
|
|
790
|
+
`${name} (${value}) fora do intervalo esperado [${range.min}-${range.max} ${range.unit}]`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
checkRange(input.fpg, "fpg", "Glicemia de Jejum");
|
|
795
|
+
checkRange(input.hba1c, "hba1c", "HbA1c");
|
|
796
|
+
checkRange(input.triglycerides, "triglycerides", "Triglicer\xEDdeos");
|
|
797
|
+
checkRange(input.hdlc, "hdlc", "HDL-colesterol");
|
|
798
|
+
return {
|
|
799
|
+
errors,
|
|
800
|
+
isValid: errors.length === 0
|
|
801
|
+
};
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// src/brdmrisc/unit-converters.ts
|
|
805
|
+
var CONVERSION_FACTORS2 = {
|
|
806
|
+
fpg: {
|
|
807
|
+
"mg/dL": 1,
|
|
808
|
+
"mmol/L": 18.0182
|
|
809
|
+
// mmol/L → mg/dL
|
|
810
|
+
},
|
|
811
|
+
hba1c: {
|
|
812
|
+
"%": 1
|
|
813
|
+
// IFCC mmol/mol → NGSP %: HbA1c(%) = 0.0915 × HbA1c(mmol/mol) + 2.15
|
|
814
|
+
// Handled as special case in convertToTargetUnit
|
|
815
|
+
},
|
|
816
|
+
hdlc: {
|
|
817
|
+
"mg/dL": 1,
|
|
818
|
+
"mmol/L": 38.67
|
|
819
|
+
// mmol/L → mg/dL
|
|
820
|
+
},
|
|
821
|
+
triglycerides: {
|
|
822
|
+
"mg/dL": 1,
|
|
823
|
+
"mmol/L": 88.57
|
|
824
|
+
// mmol/L → mg/dL
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var TARGET_UNITS2 = {
|
|
828
|
+
fpg: "mg/dL",
|
|
829
|
+
hba1c: "%",
|
|
830
|
+
hdlc: "mg/dL",
|
|
831
|
+
triglycerides: "mg/dL"
|
|
832
|
+
};
|
|
833
|
+
var convertToTargetUnit = (biomarker, value, sourceUnit) => {
|
|
834
|
+
const normalizedUnit = sourceUnit.trim();
|
|
835
|
+
if (biomarker === "hba1c" && normalizedUnit.includes("mmol/mol")) {
|
|
836
|
+
return 0.0915 * value + 2.15;
|
|
837
|
+
}
|
|
838
|
+
const factors = CONVERSION_FACTORS2[biomarker];
|
|
839
|
+
if (!factors) return value;
|
|
840
|
+
const factor = factors[normalizedUnit];
|
|
841
|
+
if (factor !== void 0) return value * factor;
|
|
842
|
+
const match = Object.keys(factors).find((k) => k.toLowerCase() === normalizedUnit.toLowerCase());
|
|
843
|
+
if (match) return value * factors[match];
|
|
844
|
+
return value;
|
|
845
|
+
};
|
|
846
|
+
var autoConvertToTarget = (biomarker, value, unit) => {
|
|
847
|
+
const targetUnit = TARGET_UNITS2[biomarker] || "";
|
|
848
|
+
if (unit) {
|
|
849
|
+
const normalizedUnit = unit.trim().toLowerCase();
|
|
850
|
+
const normalizedTarget = targetUnit.toLowerCase();
|
|
851
|
+
if (normalizedUnit === normalizedTarget) {
|
|
852
|
+
return { unit: targetUnit, value, wasConverted: false };
|
|
853
|
+
}
|
|
854
|
+
const converted = convertToTargetUnit(biomarker, value, unit);
|
|
855
|
+
return {
|
|
856
|
+
unit: targetUnit,
|
|
857
|
+
value: converted,
|
|
858
|
+
wasConverted: normalizedUnit !== normalizedTarget
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
switch (biomarker) {
|
|
862
|
+
case "fpg":
|
|
863
|
+
if (value < 30) {
|
|
864
|
+
return { unit: "mg/dL", value: value * 18.0182, wasConverted: true };
|
|
865
|
+
}
|
|
866
|
+
return { unit: "mg/dL", value, wasConverted: false };
|
|
867
|
+
case "hba1c":
|
|
868
|
+
if (value > 20) {
|
|
869
|
+
return { unit: "%", value: 0.0915 * value + 2.15, wasConverted: true };
|
|
870
|
+
}
|
|
871
|
+
return { unit: "%", value, wasConverted: false };
|
|
872
|
+
case "triglycerides":
|
|
873
|
+
if (value < 15) {
|
|
874
|
+
return { unit: "mg/dL", value: value * 88.57, wasConverted: true };
|
|
875
|
+
}
|
|
876
|
+
return { unit: "mg/dL", value, wasConverted: false };
|
|
877
|
+
case "hdlc":
|
|
878
|
+
if (value < 5) {
|
|
879
|
+
return { unit: "mg/dL", value: value * 38.67, wasConverted: true };
|
|
880
|
+
}
|
|
881
|
+
return { unit: "mg/dL", value, wasConverted: false };
|
|
882
|
+
default:
|
|
883
|
+
return { unit: targetUnit, value, wasConverted: false };
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// src/derived/calculator.ts
|
|
888
|
+
var _fhir = require('@precisa-saude/fhir');
|
|
889
|
+
var CALCULATIONS = [
|
|
890
|
+
{
|
|
891
|
+
calculate: (v) => v.get("Glucose") * v.get("Insulin") / 405,
|
|
892
|
+
code: "HOMA_IR",
|
|
893
|
+
inputs: ["Glucose", "Insulin"],
|
|
894
|
+
unit: "index"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
calculate: (v) => v.get("Triglycerides") / 5,
|
|
898
|
+
code: "VLDL",
|
|
899
|
+
inputs: ["Triglycerides"],
|
|
900
|
+
unit: "mg/dL"
|
|
901
|
+
}
|
|
902
|
+
];
|
|
903
|
+
var CONTEXT_CALCULATIONS = [
|
|
904
|
+
{
|
|
905
|
+
calculate: (v, ctx) => {
|
|
906
|
+
const weightKg = v.get("TotalMass");
|
|
907
|
+
const heightM = ctx.heightCm / 100;
|
|
908
|
+
return weightKg / (heightM * heightM);
|
|
909
|
+
},
|
|
910
|
+
canCalculate: (ctx) => typeof ctx.heightCm === "number" && ctx.heightCm >= 50 && ctx.heightCm <= 250,
|
|
911
|
+
code: "BMI",
|
|
912
|
+
inputs: ["TotalMass"],
|
|
913
|
+
unit: "kg/m2"
|
|
914
|
+
}
|
|
915
|
+
];
|
|
916
|
+
function computeDerivedBiomarkers(biomarkers, options) {
|
|
917
|
+
const lookupLoinc = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.codeToLoinc]), () => ( _fhir.codeToLoinc));
|
|
918
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
919
|
+
for (const b of biomarkers) {
|
|
920
|
+
byCode.set(b.code, b);
|
|
921
|
+
}
|
|
922
|
+
const added = [];
|
|
923
|
+
for (const calc of CALCULATIONS) {
|
|
924
|
+
if (byCode.has(calc.code)) continue;
|
|
925
|
+
const values = /* @__PURE__ */ new Map();
|
|
926
|
+
let allPresent = true;
|
|
927
|
+
for (const input of calc.inputs) {
|
|
928
|
+
const b = byCode.get(input);
|
|
929
|
+
if (!b || typeof b.value !== "number") {
|
|
930
|
+
allPresent = false;
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
values.set(input, b.value);
|
|
934
|
+
}
|
|
935
|
+
if (!allPresent) continue;
|
|
936
|
+
const rawValue = calc.calculate(values);
|
|
937
|
+
const value = parseFloat(rawValue.toPrecision(10));
|
|
938
|
+
const loinc = lookupLoinc(calc.code);
|
|
939
|
+
added.push({
|
|
940
|
+
code: calc.code,
|
|
941
|
+
loincCode: _nullishCoalesce(loinc, () => ( void 0)),
|
|
942
|
+
name: calc.code,
|
|
943
|
+
unit: calc.unit,
|
|
944
|
+
value
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (_optionalChain([options, 'optionalAccess', _2 => _2.userContext])) {
|
|
948
|
+
for (const calc of CONTEXT_CALCULATIONS) {
|
|
949
|
+
if (byCode.has(calc.code)) continue;
|
|
950
|
+
if (!calc.canCalculate(options.userContext)) continue;
|
|
951
|
+
const values = /* @__PURE__ */ new Map();
|
|
952
|
+
let allPresent = true;
|
|
953
|
+
for (const input of calc.inputs) {
|
|
954
|
+
const b = byCode.get(input);
|
|
955
|
+
if (!b || typeof b.value !== "number") {
|
|
956
|
+
allPresent = false;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
values.set(input, b.value);
|
|
960
|
+
}
|
|
961
|
+
if (!allPresent) continue;
|
|
962
|
+
const rawValue = calc.calculate(values, options.userContext);
|
|
963
|
+
const value = parseFloat(rawValue.toPrecision(10));
|
|
964
|
+
const loinc = lookupLoinc(calc.code);
|
|
965
|
+
added.push({
|
|
966
|
+
code: calc.code,
|
|
967
|
+
loincCode: _nullishCoalesce(loinc, () => ( void 0)),
|
|
968
|
+
name: calc.code,
|
|
969
|
+
unit: calc.unit,
|
|
970
|
+
value
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return added;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
exports.brdmrisc = brdmrisc_exports; exports.computeDerivedBiomarkers = computeDerivedBiomarkers; exports.phenoage = phenoage_exports;
|
|
981
|
+
//# sourceMappingURL=index.cjs.map
|