diagcalc 3.2.3 → 3.2.4
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 +17 -0
- package/bin/diagcalc.js +5 -0
- package/index.html +1 -0
- package/lib/diagcalc-core.js +4 -1
- package/package.json +1 -1
- package/script.js +8 -2
- package/tui/index.js +10 -3
package/README.md
CHANGED
|
@@ -52,6 +52,23 @@ The web and terminal interfaces both follow that workflow.
|
|
|
52
52
|
- Fagan nomogram in the web app
|
|
53
53
|
- TUI, text CLI, and JSON CLI output in the terminal app
|
|
54
54
|
|
|
55
|
+
## Case Studies
|
|
56
|
+
|
|
57
|
+
The app includes both generic teaching scenarios and literature-based examples.
|
|
58
|
+
|
|
59
|
+
- `screening` - low-prevalence population screening workflow
|
|
60
|
+
- `caseControl` - balanced case-control teaching example
|
|
61
|
+
- `clinic` - specialist clinic setting with intermediate prevalence
|
|
62
|
+
- `ddimer` - D-dimer for pulmonary embolism rule-out
|
|
63
|
+
- `troponin` - high-sensitivity troponin for acute myocardial infarction
|
|
64
|
+
- `mammography` - screening mammography in a low-prevalence setting
|
|
65
|
+
- `covid_antigen` - rapid antigen testing for COVID-19
|
|
66
|
+
- `hiv_elisa` - fourth-generation HIV ELISA screening
|
|
67
|
+
- `strep_throat` - rapid antigen testing for streptococcal pharyngitis
|
|
68
|
+
- `xray_pneumonia` - chest X-ray for community-acquired pneumonia
|
|
69
|
+
|
|
70
|
+
These cases are included to support teaching across different prevalence settings, screening vs. confirmation logic, and Bayesian interpretation of test results.
|
|
71
|
+
|
|
55
72
|
## Web App
|
|
56
73
|
|
|
57
74
|
The web app is static. It does not need a build step.
|
package/bin/diagcalc.js
CHANGED
|
@@ -206,6 +206,11 @@ function main() {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
const metrics = core.calculateMetrics(built.input);
|
|
209
|
+
if (args.format !== undefined && typeof args.format !== "string") {
|
|
210
|
+
process.stderr.write("Missing value for --format. Use --format text or --format json.\n");
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
209
214
|
const format = typeof args.format === "string" ? args.format.toLowerCase() : "text";
|
|
210
215
|
if (format === "json") {
|
|
211
216
|
printJsonReport(built.dataset, built.input, metrics);
|
package/index.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" />
|
|
10
10
|
<link rel="stylesheet" href="styles.css" />
|
|
11
|
+
<script defer src="https://analytics.tiagojct.eu/script.js" data-website-id="2c7df365-61e6-42e6-8298-a20ada3e296a"></script>
|
|
11
12
|
</head>
|
|
12
13
|
<body>
|
|
13
14
|
<header class="intro" role="banner">
|
package/lib/diagcalc-core.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (preTestProb < 0 || preTestProb >= 100) {
|
|
24
|
-
return { valid: false, message: "Pre-test probability must be
|
|
24
|
+
return { valid: false, message: "Pre-test probability must be at least 0% and below 100%." };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (tp + fn === 0) {
|
|
@@ -135,6 +135,9 @@
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function calculateRatio(numerator, denominator) {
|
|
138
|
+
if (numerator === 0 && denominator === 0) {
|
|
139
|
+
return NaN;
|
|
140
|
+
}
|
|
138
141
|
if (denominator === 0) {
|
|
139
142
|
return Infinity;
|
|
140
143
|
}
|
package/package.json
CHANGED
package/script.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
document.addEventListener("DOMContentLoaded", () => {
|
|
2
2
|
const core = window.DiagcalcCore;
|
|
3
3
|
const datasetStore = window.DiagcalcDatasets;
|
|
4
|
+
const feedbackEl = document.getElementById("feedback");
|
|
4
5
|
|
|
5
6
|
if (!core || !datasetStore) {
|
|
7
|
+
const missing = [!core && "DiagcalcCore", !datasetStore && "DiagcalcDatasets"].filter(Boolean).join(" and ");
|
|
8
|
+
console.error(`DIAGCALC: required script(s) failed to load: ${missing}.`);
|
|
9
|
+
if (feedbackEl) {
|
|
10
|
+
feedbackEl.textContent = "Calculator failed to load. Refresh the page or check the network tab for missing scripts.";
|
|
11
|
+
feedbackEl.classList.add("error");
|
|
12
|
+
}
|
|
6
13
|
return;
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
const datasets = datasetStore.datasets;
|
|
10
17
|
const form = document.getElementById("inputForm");
|
|
11
|
-
const feedbackEl = document.getElementById("feedback");
|
|
12
18
|
const resultsEl = document.getElementById("results");
|
|
13
19
|
const datasetSelect = document.getElementById("datasetSelect");
|
|
14
20
|
const resetButton = document.getElementById("resetButton");
|
|
@@ -464,7 +470,7 @@ function renderFaganNomogram(canvas, container, data) {
|
|
|
464
470
|
drawLrLabel(lrPosY, "LR+", lrPositive, warmColor, true);
|
|
465
471
|
}
|
|
466
472
|
|
|
467
|
-
if (
|
|
473
|
+
if (lrNegative > 0) {
|
|
468
474
|
const postNegY = probToY(postNegative);
|
|
469
475
|
const lrNegY = midpointY(preY, postNegY);
|
|
470
476
|
|
package/tui/index.js
CHANGED
|
@@ -720,7 +720,10 @@ function runTui() {
|
|
|
720
720
|
resultsBox.key(["pageup"], () => resultsBox.scroll(-10));
|
|
721
721
|
resultsBox.key(["pagedown"], () => resultsBox.scroll(10));
|
|
722
722
|
|
|
723
|
-
screen.key(["
|
|
723
|
+
screen.key(["C-c"], () => {
|
|
724
|
+
process.exit(0);
|
|
725
|
+
});
|
|
726
|
+
screen.key(["q"], () => {
|
|
724
727
|
if (!state.modalOpen) {
|
|
725
728
|
process.exit(0);
|
|
726
729
|
}
|
|
@@ -821,6 +824,10 @@ function displayValue(value) {
|
|
|
821
824
|
return Number.isFinite(value) ? String(value) : "-";
|
|
822
825
|
}
|
|
823
826
|
|
|
827
|
+
function formatPreTest(value) {
|
|
828
|
+
return Number.isFinite(value) ? core.formatPercentage(value / 100) : "-";
|
|
829
|
+
}
|
|
830
|
+
|
|
824
831
|
function buildConfusionMatrixBox(input) {
|
|
825
832
|
const tp = displayValue(input.tp).padStart(5);
|
|
826
833
|
const fp = displayValue(input.fp).padStart(5);
|
|
@@ -892,7 +899,7 @@ function buildTextExport(state, metrics, validation) {
|
|
|
892
899
|
"Confusion matrix",
|
|
893
900
|
` TP ${displayValue(state.input.tp)} FP ${displayValue(state.input.fp)}`,
|
|
894
901
|
` FN ${displayValue(state.input.fn)} TN ${displayValue(state.input.tn)}`,
|
|
895
|
-
` Pre-test probability ${
|
|
902
|
+
` Pre-test probability ${formatPreTest(state.input.preTestProb)}`,
|
|
896
903
|
"",
|
|
897
904
|
];
|
|
898
905
|
|
|
@@ -916,7 +923,7 @@ function buildMarkdownExport(state, metrics, validation) {
|
|
|
916
923
|
`| Test + | ${displayValue(state.input.tp)} | ${displayValue(state.input.fp)} |`,
|
|
917
924
|
`| Test - | ${displayValue(state.input.fn)} | ${displayValue(state.input.tn)} |`,
|
|
918
925
|
"",
|
|
919
|
-
`Pre-test probability: ${
|
|
926
|
+
`Pre-test probability: ${formatPreTest(state.input.preTestProb)}`,
|
|
920
927
|
"",
|
|
921
928
|
];
|
|
922
929
|
|