diagcalc 3.2.2 → 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 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">
@@ -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 between 0 and 99.9%." };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diagcalc",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
4
4
  "description": "Diagnostic calculator for the browser, terminal, and plain CLI.",
5
5
  "homepage": "https://diagcalc.tiagojct.eu/",
6
6
  "repository": {
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 (Number.isFinite(lrNegative) && lrNegative > 0) {
473
+ if (lrNegative > 0) {
468
474
  const postNegY = probToY(postNegative);
469
475
  const lrNegY = midpointY(preY, postNegY);
470
476
 
package/styles.css CHANGED
@@ -337,6 +337,13 @@ option {
337
337
  font-size: 1rem;
338
338
  font-family: inherit;
339
339
  background-color: var(--background-color);
340
+ color: var(--text-color);
341
+ }
342
+
343
+ .grid-item input::placeholder,
344
+ .preTestProb::placeholder {
345
+ color: var(--muted-text);
346
+ opacity: 1;
340
347
  }
341
348
 
342
349
  .grid-item input:focus,
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(["q", "C-c"], () => {
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 ${displayValue(state.input.preTestProb)}%`,
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: ${displayValue(state.input.preTestProb)}%`,
926
+ `Pre-test probability: ${formatPreTest(state.input.preTestProb)}`,
920
927
  "",
921
928
  ];
922
929