diagcalc 3.2.4 → 5.0.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 +44 -7
- package/bin/diagcalc.js +142 -41
- package/docs/PROVENANCE.md +17 -0
- package/index.html +407 -92
- package/lib/README.md +98 -0
- package/lib/diagcalc-case.js +148 -0
- package/lib/diagcalc-core.js +554 -89
- package/lib/diagcalc-datasets.js +140 -1
- package/lib/diagcalc-geometry.js +27 -0
- package/lib/diagcalc-i18n.js +255 -0
- package/lib/diagcalc-meta.js +9 -0
- package/lib/diagcalc-presentation.js +278 -0
- package/lib/diagcalc-storage.js +45 -0
- package/lib/diagcalc-types.d.ts +59 -0
- package/package.json +18 -4
- package/script.js +1232 -274
- package/styles.css +2186 -325
- package/tui/index.js +75 -272
- package/tui/input.js +24 -0
- package/tui/presentation.js +168 -0
- package/tui/report.js +130 -0
- package/web/charts.js +339 -0
- package/web/results.js +234 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === "object" && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
root.DiagcalcPresentation = factory();
|
|
7
|
+
}(/** @type {any} */ (typeof globalThis !== "undefined" ? globalThis : this), () => {
|
|
8
|
+
const labels = {
|
|
9
|
+
"sensitivity": "Sensitivity",
|
|
10
|
+
"specificity": "Specificity",
|
|
11
|
+
"ppv": "Positive predictive value (PPV)",
|
|
12
|
+
"npv": "Negative predictive value (NPV)",
|
|
13
|
+
"dor": "Diagnostic odds ratio (DOR)",
|
|
14
|
+
"numberNeededToScreen": "Number needed to screen (NNS)",
|
|
15
|
+
"lrPositive": "Positive likelihood ratio (LR+)",
|
|
16
|
+
"lrNegative": "Negative likelihood ratio (LR-)",
|
|
17
|
+
"preTestProbability": "Pre-test probability",
|
|
18
|
+
"postTestPositive": "Post-test probability (positive result)",
|
|
19
|
+
"postTestNegative": "Post-test probability (negative result)"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const portugueseLabels = {
|
|
23
|
+
"sensitivity": "Sensibilidade",
|
|
24
|
+
"specificity": "Especificidade",
|
|
25
|
+
"ppv": "Valor preditivo positivo (VPP)",
|
|
26
|
+
"npv": "Valor preditivo negativo (VPN)",
|
|
27
|
+
"dor": "Odds ratio diagnóstica (DOR)",
|
|
28
|
+
"numberNeededToScreen": "Número necessário para rastrear (NNS)",
|
|
29
|
+
"lrPositive": "Razão de verosimilhança positiva (LR+)",
|
|
30
|
+
"lrNegative": "Razão de verosimilhança negativa (LR−)",
|
|
31
|
+
"preTestProbability": "Probabilidade pré-teste",
|
|
32
|
+
"postTestPositive": "Probabilidade pós-teste (resultado positivo)",
|
|
33
|
+
"postTestNegative": "Probabilidade pós-teste (resultado negativo)"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const constantNotes = {
|
|
37
|
+
"ppv": "Probability of disease given a positive result.",
|
|
38
|
+
"npv": "Probability of no disease given a negative result.",
|
|
39
|
+
"preTestProbability": "Estimated starting point before the test result.",
|
|
40
|
+
"postTestPositive": "Updated probability of disease after a positive test.",
|
|
41
|
+
"postTestNegative": "Updated probability of disease after a negative test."
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const portugueseNotes = {
|
|
45
|
+
"ppv": "Probabilidade de doença após um resultado positivo.",
|
|
46
|
+
"npv": "Probabilidade de ausência de doença após um resultado negativo.",
|
|
47
|
+
"preTestProbability": "Estimativa inicial antes do resultado do teste.",
|
|
48
|
+
"postTestPositive": "Probabilidade de doença actualizada após um teste positivo.",
|
|
49
|
+
"postTestNegative": "Probabilidade de doença actualizada após um teste negativo."
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const english = {
|
|
53
|
+
"buildSensitivityNote.1": "Captures most cases with disease. Useful for screening.",
|
|
54
|
+
"buildSensitivityNote.2": "Moderate sensitivity: consider alongside clinical context.",
|
|
55
|
+
"buildSensitivityNote.3": "Low sensitivity: consider additional tests to reduce false negatives.",
|
|
56
|
+
"buildSpecificityNote.1": "Few false positives. Suitable for confirming diagnoses.",
|
|
57
|
+
"buildSpecificityNote.2": "Moderate specificity: confirm with other laboratory or clinical data.",
|
|
58
|
+
"buildSpecificityNote.3": "Low specificity: beware of false positives and their impact on treatment decisions.",
|
|
59
|
+
"interpretDOR.1": "DOR is undefined for this table.",
|
|
60
|
+
"interpretDOR.2": "DOR is infinite because FP or FN is zero; this does not establish perfect separation.",
|
|
61
|
+
"interpretDOR.3": "DOR ≥ 100: very strong discrimination between cases and non-cases.",
|
|
62
|
+
"interpretDOR.4": "DOR ≥ 10: clinically useful discrimination.",
|
|
63
|
+
"interpretDOR.5": "DOR > 1 but limited: the test discriminates only modestly.",
|
|
64
|
+
"interpretDOR.6": "DOR = 1: the test provides no information.",
|
|
65
|
+
"interpretDOR.7": "DOR < 1: results may be labelled the wrong way — review TP/FP/FN/TN assignments.",
|
|
66
|
+
"interpretNNS.1": "NNS is undefined here (sensitivity or pre-test probability is zero).",
|
|
67
|
+
"interpretNNS.2": "Roughly {count} screened to detect one true case — efficient at this prevalence.",
|
|
68
|
+
"interpretNNS.3": "Roughly {count} screened to detect one true case — moderate yield.",
|
|
69
|
+
"interpretNNS.4": "Roughly {count} screened to detect one true case — low yield, typical of low-prevalence screening.",
|
|
70
|
+
"interpretNNS.5": "Roughly {count} screened to detect one true case — very low yield.",
|
|
71
|
+
"interpretLRPositive.1": "LR+ is undefined for this table.",
|
|
72
|
+
"interpretLRPositive.2": "LR+ is infinite in this sample: no false positives were observed.",
|
|
73
|
+
"interpretLRPositive.3": "LR+ >= 10 indicates strong evidence in favour of disease.",
|
|
74
|
+
"interpretLRPositive.4": "Moderate LR+: substantially increases the probability of disease.",
|
|
75
|
+
"interpretLRPositive.5": "Low LR+: limited gain; combine with other data.",
|
|
76
|
+
"interpretLRPositive.6": "LR+ < 1: a positive result decreases the probability of disease; review test direction.",
|
|
77
|
+
"interpretLRPositive.7": "LR+ close to 1: a positive result barely changes the probability of disease.",
|
|
78
|
+
"interpretLRNegative.1": "LR- is undefined for this table.",
|
|
79
|
+
"interpretLRNegative.2": "LR- is infinite: a negative result does not reduce the probability of disease.",
|
|
80
|
+
"interpretLRNegative.3": "LR- <= 0.1 indicates strong evidence against disease.",
|
|
81
|
+
"interpretLRNegative.4": "Moderate LR-: reduces the probability of disease.",
|
|
82
|
+
"interpretLRNegative.5": "Low LR-: limited impact; consider further evaluation.",
|
|
83
|
+
"interpretLRNegative.6": "LR- > 1: a negative result increases the probability of disease; review test direction.",
|
|
84
|
+
"interpretLRNegative.7": "LR- close to 1: a negative result does not rule out disease.",
|
|
85
|
+
"interpretKappa.1": "Undefined",
|
|
86
|
+
"interpretKappa.2": "Poor (less than chance)",
|
|
87
|
+
"interpretKappa.3": "Slight",
|
|
88
|
+
"interpretKappa.4": "Fair",
|
|
89
|
+
"interpretKappa.5": "Moderate",
|
|
90
|
+
"interpretKappa.6": "Substantial",
|
|
91
|
+
"interpretKappa.7": "Almost perfect"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const portuguese = {
|
|
95
|
+
"buildSensitivityNote.1": "Detecta a maioria dos casos com doença. Útil para rastreio.",
|
|
96
|
+
"buildSensitivityNote.2": "Sensibilidade moderada: considerar o contexto clínico.",
|
|
97
|
+
"buildSensitivityNote.3": "Sensibilidade baixa: considerar testes adicionais para reduzir falsos negativos.",
|
|
98
|
+
"buildSpecificityNote.1": "Poucos falsos positivos. Adequado para confirmação diagnóstica.",
|
|
99
|
+
"buildSpecificityNote.2": "Especificidade moderada: confirmar com outros dados laboratoriais ou clínicos.",
|
|
100
|
+
"buildSpecificityNote.3": "Especificidade baixa: considerar os falsos positivos e o seu impacto nas decisões.",
|
|
101
|
+
"interpretDOR.1": "DOR indefinida para esta tabela.",
|
|
102
|
+
"interpretDOR.2": "DOR infinita porque FP ou FN é zero; não demonstra separação perfeita.",
|
|
103
|
+
"interpretDOR.3": "DOR ≥ 100: discriminação muito forte entre casos e não casos.",
|
|
104
|
+
"interpretDOR.4": "DOR ≥ 10: discriminação clinicamente útil.",
|
|
105
|
+
"interpretDOR.5": "DOR > 1, mas limitada: discriminação modesta.",
|
|
106
|
+
"interpretDOR.6": "DOR = 1: o teste não fornece informação.",
|
|
107
|
+
"interpretDOR.7": "DOR < 1: rever a direcção do teste e a atribuição TP/FP/FN/TN.",
|
|
108
|
+
"interpretNNS.1": "NNS indefinido (sensibilidade ou probabilidade pré-teste igual a zero).",
|
|
109
|
+
"interpretNNS.2": "Cerca de {count} rastreados para detectar um caso verdadeiro — eficiente nesta prevalência.",
|
|
110
|
+
"interpretNNS.3": "Cerca de {count} rastreados para detectar um caso verdadeiro — rendimento moderado.",
|
|
111
|
+
"interpretNNS.4": "Cerca de {count} rastreados para detectar um caso verdadeiro — baixo rendimento, típico de rastreio com baixa prevalência.",
|
|
112
|
+
"interpretNNS.5": "Cerca de {count} rastreados para detectar um caso verdadeiro — rendimento muito baixo.",
|
|
113
|
+
"interpretLRPositive.1": "LR+ indefinida para esta tabela.",
|
|
114
|
+
"interpretLRPositive.2": "LR+ infinita nesta amostra: não foram observados falsos positivos.",
|
|
115
|
+
"interpretLRPositive.3": "LR+ ≥ 10 indica forte evidência a favor da doença.",
|
|
116
|
+
"interpretLRPositive.4": "LR+ moderada: aumenta substancialmente a probabilidade de doença.",
|
|
117
|
+
"interpretLRPositive.5": "LR+ baixa: ganho limitado; combinar com outros dados.",
|
|
118
|
+
"interpretLRPositive.6": "LR+ < 1: um resultado positivo reduz a probabilidade de doença; rever a direcção do teste.",
|
|
119
|
+
"interpretLRPositive.7": "LR+ próxima de 1: um resultado positivo altera pouco a probabilidade de doença.",
|
|
120
|
+
"interpretLRNegative.1": "LR− indefinida para esta tabela.",
|
|
121
|
+
"interpretLRNegative.2": "LR− infinita: um resultado negativo não reduz a probabilidade de doença.",
|
|
122
|
+
"interpretLRNegative.3": "LR− ≤ 0,1 indica forte evidência contra a doença.",
|
|
123
|
+
"interpretLRNegative.4": "LR− moderada: reduz a probabilidade de doença.",
|
|
124
|
+
"interpretLRNegative.5": "LR− baixa: impacto limitado; considerar avaliação adicional.",
|
|
125
|
+
"interpretLRNegative.6": "LR− > 1: um resultado negativo aumenta a probabilidade de doença; rever a direcção do teste.",
|
|
126
|
+
"interpretLRNegative.7": "LR− próxima de 1: um resultado negativo não exclui doença.",
|
|
127
|
+
"interpretKappa.1": "Indefinido",
|
|
128
|
+
"interpretKappa.2": "Fraco (inferior ao acaso)",
|
|
129
|
+
"interpretKappa.3": "Ligeiro",
|
|
130
|
+
"interpretKappa.4": "Razoável",
|
|
131
|
+
"interpretKappa.5": "Moderado",
|
|
132
|
+
"interpretKappa.6": "Substancial",
|
|
133
|
+
"interpretKappa.7": "Quase perfeito"
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const helpers = {
|
|
137
|
+
"sensitivity": "buildSensitivityNote",
|
|
138
|
+
"specificity": "buildSpecificityNote",
|
|
139
|
+
"dor": "interpretDOR",
|
|
140
|
+
"numberNeededToScreen": "interpretNNS",
|
|
141
|
+
"lrPositive": "interpretLRPositive",
|
|
142
|
+
"lrNegative": "interpretLRNegative"
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
function buildSensitivityNote(value) {
|
|
146
|
+
if (value >= 0.9) {
|
|
147
|
+
return { id: "buildSensitivityNote.1", params: {} };
|
|
148
|
+
}
|
|
149
|
+
if (value >= 0.7) {
|
|
150
|
+
return { id: "buildSensitivityNote.2", params: {} };
|
|
151
|
+
}
|
|
152
|
+
return { id: "buildSensitivityNote.3", params: {} };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildSpecificityNote(value) {
|
|
156
|
+
if (value >= 0.9) {
|
|
157
|
+
return { id: "buildSpecificityNote.1", params: {} };
|
|
158
|
+
}
|
|
159
|
+
if (value >= 0.7) {
|
|
160
|
+
return { id: "buildSpecificityNote.2", params: {} };
|
|
161
|
+
}
|
|
162
|
+
return { id: "buildSpecificityNote.3", params: {} };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function interpretDOR(value) {
|
|
166
|
+
if (Number.isNaN(value)) return { id: "interpretDOR.1", params: {} };
|
|
167
|
+
if (value === Infinity) return { id: "interpretDOR.2", params: {} };
|
|
168
|
+
if (value >= 100) {
|
|
169
|
+
return { id: "interpretDOR.3", params: {} };
|
|
170
|
+
}
|
|
171
|
+
if (value >= 10) {
|
|
172
|
+
return { id: "interpretDOR.4", params: {} };
|
|
173
|
+
}
|
|
174
|
+
if (value > 1) {
|
|
175
|
+
return { id: "interpretDOR.5", params: {} };
|
|
176
|
+
}
|
|
177
|
+
if (value === 1) {
|
|
178
|
+
return { id: "interpretDOR.6", params: {} };
|
|
179
|
+
}
|
|
180
|
+
return { id: "interpretDOR.7", params: {} };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function interpretNNS(value) {
|
|
184
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
185
|
+
return { id: "interpretNNS.1", params: {} };
|
|
186
|
+
}
|
|
187
|
+
const rounded = Math.ceil(value);
|
|
188
|
+
if (rounded < 10) {
|
|
189
|
+
return { id: "interpretNNS.2", params: { count: rounded } };
|
|
190
|
+
}
|
|
191
|
+
if (rounded < 100) {
|
|
192
|
+
return { id: "interpretNNS.3", params: { count: rounded } };
|
|
193
|
+
}
|
|
194
|
+
if (rounded < 1000) {
|
|
195
|
+
return { id: "interpretNNS.4", params: { count: rounded } };
|
|
196
|
+
}
|
|
197
|
+
return { id: "interpretNNS.5", params: { count: rounded } };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function interpretLRPositive(value) {
|
|
201
|
+
if (Number.isNaN(value)) return { id: "interpretLRPositive.1", params: {} };
|
|
202
|
+
if (value === Infinity) {
|
|
203
|
+
return { id: "interpretLRPositive.2", params: {} };
|
|
204
|
+
}
|
|
205
|
+
if (value >= 10) {
|
|
206
|
+
return { id: "interpretLRPositive.3", params: {} };
|
|
207
|
+
}
|
|
208
|
+
if (value >= 5) {
|
|
209
|
+
return { id: "interpretLRPositive.4", params: {} };
|
|
210
|
+
}
|
|
211
|
+
if (value >= 2) {
|
|
212
|
+
return { id: "interpretLRPositive.5", params: {} };
|
|
213
|
+
}
|
|
214
|
+
if (value < 1) return { id: "interpretLRPositive.6", params: {} };
|
|
215
|
+
return { id: "interpretLRPositive.7", params: {} };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function interpretLRNegative(value) {
|
|
219
|
+
if (Number.isNaN(value)) return { id: "interpretLRNegative.1", params: {} };
|
|
220
|
+
if (value === Infinity) {
|
|
221
|
+
return { id: "interpretLRNegative.2", params: {} };
|
|
222
|
+
}
|
|
223
|
+
if (value <= 0.1) {
|
|
224
|
+
return { id: "interpretLRNegative.3", params: {} };
|
|
225
|
+
}
|
|
226
|
+
if (value <= 0.2) {
|
|
227
|
+
return { id: "interpretLRNegative.4", params: {} };
|
|
228
|
+
}
|
|
229
|
+
if (value <= 0.5) {
|
|
230
|
+
return { id: "interpretLRNegative.5", params: {} };
|
|
231
|
+
}
|
|
232
|
+
if (value > 1) return { id: "interpretLRNegative.6", params: {} };
|
|
233
|
+
return { id: "interpretLRNegative.7", params: {} };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function interpretKappa(value) {
|
|
237
|
+
if (!Number.isFinite(value)) return { id: "interpretKappa.1", params: {} };
|
|
238
|
+
if (value < 0) return { id: "interpretKappa.2", params: {} };
|
|
239
|
+
if (value < 0.21) return { id: "interpretKappa.3", params: {} };
|
|
240
|
+
if (value < 0.41) return { id: "interpretKappa.4", params: {} };
|
|
241
|
+
if (value < 0.61) return { id: "interpretKappa.5", params: {} };
|
|
242
|
+
if (value < 0.81) return { id: "interpretKappa.6", params: {} };
|
|
243
|
+
return { id: "interpretKappa.7", params: {} };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const selectors = { buildSensitivityNote, buildSpecificityNote, interpretDOR, interpretNNS, interpretLRPositive, interpretLRNegative, interpretKappa };
|
|
247
|
+
|
|
248
|
+
function interpretation(key, value) {
|
|
249
|
+
return helpers[key] ? selectors[helpers[key]](value) : { id: key, params: {} };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function noteFor(helper, value, locale = "en") {
|
|
253
|
+
const selected = selectors[helper](value);
|
|
254
|
+
const table = locale === "pt-PT" ? portuguese : english;
|
|
255
|
+
return (table[selected.id] || english[selected.id]).replace(/\{(\w+)\}/g, (_, key) => String(selected.params[key]));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @param {import("./diagcalc-types").NumericResults} results
|
|
260
|
+
* @param {{formatLikelihood: (value: number) => string, formatNNS: (value: number) => string}} formatters
|
|
261
|
+
* @param {string} [locale]
|
|
262
|
+
* @returns {import("./diagcalc-types").PresentedResults}
|
|
263
|
+
*/
|
|
264
|
+
function presentResults(results, formatters, locale = "en") {
|
|
265
|
+
const localLabels = locale === "pt-PT" ? portugueseLabels : labels;
|
|
266
|
+
const localNotes = locale === "pt-PT" ? portugueseNotes : constantNotes;
|
|
267
|
+
return Object.fromEntries(Object.entries(results).map(([key, metric]) => {
|
|
268
|
+
const helper = helpers[key];
|
|
269
|
+
const note = helper ? noteFor(helper, metric.value, locale) : localNotes[key];
|
|
270
|
+
const formatter = key === "numberNeededToScreen" ? formatters.formatNNS :
|
|
271
|
+
["dor", "lrPositive", "lrNegative"].includes(key) ? formatters.formatLikelihood : undefined;
|
|
272
|
+
return [key, { ...metric, label: localLabels[key], note,
|
|
273
|
+
interpretation: interpretation(key, metric.value), ...(formatter ? { formatter } : {}) }];
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return { presentResults, interpretation, noteFor };
|
|
278
|
+
}));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
if (typeof module === "object" && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
root.DiagcalcStorage = factory();
|
|
7
|
+
}(/** @type {any} */ (typeof globalThis !== "undefined" ? globalThis : this), () => {
|
|
8
|
+
/**
|
|
9
|
+
* Storage failures must never prevent calculation. Written values take
|
|
10
|
+
* precedence over the backend, including after quota or access failures.
|
|
11
|
+
* @param {() => Storage} getBackend Lazy access also guards SecurityError getters.
|
|
12
|
+
*/
|
|
13
|
+
function createStorage(getBackend) {
|
|
14
|
+
const memory = new Map();
|
|
15
|
+
return {
|
|
16
|
+
/** @param {string} key @returns {string | null} */
|
|
17
|
+
getItem(key) {
|
|
18
|
+
if (memory.has(key)) return memory.get(key);
|
|
19
|
+
try {
|
|
20
|
+
return getBackend().getItem(key);
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
/** @param {string} key @param {string} value */
|
|
26
|
+
setItem(key, value) {
|
|
27
|
+
const text = String(value);
|
|
28
|
+
memory.set(key, text);
|
|
29
|
+
try {
|
|
30
|
+
getBackend().setItem(key, text);
|
|
31
|
+
} catch (_) {
|
|
32
|
+
// Continue with the session's in-memory history/preferences.
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
/** @param {string} key */
|
|
36
|
+
removeItem(key) {
|
|
37
|
+
memory.set(key, null);
|
|
38
|
+
try {
|
|
39
|
+
getBackend().removeItem(key);
|
|
40
|
+
} catch (_) {}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { createStorage };
|
|
45
|
+
}));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Shared contracts checked against JavaScript implementations by npm run typecheck. */
|
|
2
|
+
export interface DiagnosticInput {
|
|
3
|
+
tp: number;
|
|
4
|
+
fp: number;
|
|
5
|
+
fn: number;
|
|
6
|
+
tn: number;
|
|
7
|
+
preTestProb: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CalculationOptions {
|
|
10
|
+
continuityCorrection?: "auto" | "always" | "never";
|
|
11
|
+
locale?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Interval {
|
|
14
|
+
lower: number;
|
|
15
|
+
upper: number;
|
|
16
|
+
}
|
|
17
|
+
export interface NumericMetric {
|
|
18
|
+
value: number;
|
|
19
|
+
ci?: Interval | null;
|
|
20
|
+
unit?: string;
|
|
21
|
+
interpretation?: { id: string; params: Record<string, number> };
|
|
22
|
+
}
|
|
23
|
+
export type NumericResults = Record<string, NumericMetric>;
|
|
24
|
+
export interface PresentedMetric extends NumericMetric {
|
|
25
|
+
label: string;
|
|
26
|
+
note: string;
|
|
27
|
+
formatter?: (value: number) => string;
|
|
28
|
+
}
|
|
29
|
+
export type PresentedResults = Record<string, PresentedMetric>;
|
|
30
|
+
export interface CaseInfo {
|
|
31
|
+
id?: string;
|
|
32
|
+
savedAt?: string;
|
|
33
|
+
label?: string;
|
|
34
|
+
datasetKey?: string | null;
|
|
35
|
+
modified?: boolean;
|
|
36
|
+
legacy?: boolean;
|
|
37
|
+
provenance?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface EncodedNumber {
|
|
40
|
+
value: number | null;
|
|
41
|
+
status: "finite" | "infinite" | "negative-infinite" | "undefined";
|
|
42
|
+
}
|
|
43
|
+
export interface EncodedMetric extends EncodedNumber {
|
|
44
|
+
ci: { lower: EncodedNumber; upper: EncodedNumber } | null;
|
|
45
|
+
}
|
|
46
|
+
export interface Snapshot {
|
|
47
|
+
schemaVersion: number;
|
|
48
|
+
engineVersion: string;
|
|
49
|
+
id: string;
|
|
50
|
+
savedAt: string;
|
|
51
|
+
label: string;
|
|
52
|
+
datasetKey: string | null;
|
|
53
|
+
modified: boolean;
|
|
54
|
+
legacy: boolean;
|
|
55
|
+
provenance?: unknown;
|
|
56
|
+
input: DiagnosticInput;
|
|
57
|
+
options: CalculationOptions;
|
|
58
|
+
metrics: Record<string, EncodedMetric>;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diagcalc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Diagnostic calculator for the browser, terminal, and plain CLI.",
|
|
5
|
-
"homepage": "https://diagcalc.
|
|
5
|
+
"homepage": "https://diagcalc.tiagojacinto.eu/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/tiagojct/diagcalc.git"
|
|
@@ -19,15 +19,17 @@
|
|
|
19
19
|
"tui"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=22"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin",
|
|
26
26
|
"lib",
|
|
27
27
|
"tui",
|
|
28
|
+
"web",
|
|
28
29
|
"index.html",
|
|
29
30
|
"script.js",
|
|
30
31
|
"styles.css",
|
|
32
|
+
"docs",
|
|
31
33
|
"README.md",
|
|
32
34
|
"LICENSE"
|
|
33
35
|
],
|
|
@@ -40,9 +42,21 @@
|
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
44
|
"tui": "node bin/diagcalc.js --tui",
|
|
43
|
-
"cli": "node bin/diagcalc.js --help"
|
|
45
|
+
"cli": "node bin/diagcalc.js --help",
|
|
46
|
+
"test": "node --test test/*.test.js",
|
|
47
|
+
"test:browser": "playwright test",
|
|
48
|
+
"typecheck": "tsc --project jsconfig.json",
|
|
49
|
+
"check": "npm run typecheck && npm test && npm run test:browser",
|
|
50
|
+
"prepare:site": "node scripts/prepare-site.js",
|
|
51
|
+
"pretest:browser": "npm run prepare:site"
|
|
44
52
|
},
|
|
45
53
|
"dependencies": {
|
|
46
54
|
"blessed": "^0.1.81"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@axe-core/playwright": "^4.13.0",
|
|
58
|
+
"@playwright/test": "^1.63.0",
|
|
59
|
+
"@types/node": "^26.6.3",
|
|
60
|
+
"typescript": "^7.0.2"
|
|
47
61
|
}
|
|
48
62
|
}
|