diagcalc 3.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tiago Jacinto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # DIAGCALC
2
+
3
+ DIAGCALC is a diagnostic test calculator with two interfaces:
4
+
5
+ - a web app for teaching and interactive use
6
+ - a terminal app with both TUI and plain CLI modes
7
+
8
+ Both interfaces use the same calculation engine.
9
+
10
+ ## Repository
11
+
12
+ - GitHub: `https://github.com/tiagojct/diagcalc`
13
+
14
+ ## Features
15
+
16
+ - confusion matrix input: TP, FP, FN, TN
17
+ - pre-test probability input
18
+ - sensitivity, specificity, PPV, NPV
19
+ - LR+ and LR-
20
+ - positive and negative post-test probability
21
+ - 95% confidence intervals with the Wilson method
22
+ - preset study scenarios
23
+ - Fagan nomogram in the web app
24
+ - TUI, text CLI, and JSON CLI output in the terminal app
25
+
26
+ ## Web App
27
+
28
+ The web app is static. It does not need a build step.
29
+
30
+ ### Run locally
31
+
32
+ Open `index.html` directly in a browser, or serve the folder locally:
33
+
34
+ ```bash
35
+ python3 -m http.server 8080
36
+ ```
37
+
38
+ Then open:
39
+
40
+ ```text
41
+ http://localhost:8080
42
+ ```
43
+
44
+ ### Use
45
+
46
+ 1. Load a preset scenario, or leave the selector empty.
47
+ 2. Enter TP, FP, FN, TN.
48
+ 3. Enter pre-test probability.
49
+ 4. Click `Calculate results`.
50
+ 5. Review the probability bars, result cards, and Fagan nomogram.
51
+
52
+ ## Terminal App
53
+
54
+ The terminal app requires Node.js 18 or newer.
55
+
56
+ ### Install locally
57
+
58
+ ```bash
59
+ npm install
60
+ ```
61
+
62
+ ### TUI mode
63
+
64
+ ```bash
65
+ node bin/diagcalc.js --tui
66
+ ```
67
+
68
+ If you want the short command:
69
+
70
+ ```bash
71
+ npm link
72
+ diag --tui
73
+ ```
74
+
75
+ ### CLI mode
76
+
77
+ List datasets:
78
+
79
+ ```bash
80
+ diag --list-datasets
81
+ ```
82
+
83
+ Run a preset case:
84
+
85
+ ```bash
86
+ diag --dataset hiv_elisa
87
+ ```
88
+
89
+ Run an ad hoc case:
90
+
91
+ ```bash
92
+ diag --tp 42 --fp 8 --fn 3 --tn 120 --pre 15
93
+ ```
94
+
95
+ Get JSON output:
96
+
97
+ ```bash
98
+ diag --dataset ddimer --format json
99
+ ```
100
+
101
+ ### TUI controls
102
+
103
+ - `Tab` / `Ctrl-N`: next panel
104
+ - `Shift-Tab` / `Ctrl-P`: previous panel
105
+ - arrows: move selection
106
+ - type digits directly in the input editor
107
+ - `Backspace`: delete one character from the selected field
108
+ - `Delete` or `Ctrl-U`: clear the selected field
109
+ - `Enter`: open the selected field in prompt mode
110
+ - `n`: start a blank ad hoc case
111
+ - `x`: export current case to plain text
112
+ - `m`: export current case to Markdown
113
+ - `r`: reset current case
114
+ - `q`: quit
115
+
116
+ ## Deployment
117
+
118
+ ### GitHub Pages
119
+
120
+ This repository includes a GitHub Pages workflow at `.github/workflows/deploy-pages.yml`.
121
+
122
+ To publish the web app:
123
+
124
+ 1. Push to the `main` branch.
125
+ 2. In GitHub, open `Settings -> Pages`.
126
+ 3. Set the source to `GitHub Actions`.
127
+ 4. The workflow will publish the static site automatically.
128
+
129
+ ### npm
130
+
131
+ This package is ready for npm publishing.
132
+
133
+ Publish steps:
134
+
135
+ ```bash
136
+ npm login
137
+ npm publish --access public
138
+ ```
139
+
140
+ After publishing, users can install it with:
141
+
142
+ ```bash
143
+ npm install -g diagcalc
144
+ diag --tui
145
+ ```
146
+
147
+ ## Project Structure
148
+
149
+ - `index.html` - web app markup
150
+ - `styles.css` - web app styles
151
+ - `script.js` - web app logic and Fagan nomogram rendering
152
+ - `lib/diagcalc-core.js` - shared calculations and validation
153
+ - `lib/diagcalc-datasets.js` - shared preset datasets
154
+ - `tui/index.js` - terminal UI
155
+ - `bin/diagcalc.js` - CLI and TUI entrypoint
156
+ - `.github/workflows/deploy-pages.yml` - GitHub Pages deployment workflow
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ const core = require("../lib/diagcalc-core");
4
+ const datasetStore = require("../lib/diagcalc-datasets");
5
+
6
+ function parseArgs(argv) {
7
+ const args = {
8
+ raw: [],
9
+ };
10
+
11
+ for (let index = 0; index < argv.length; index += 1) {
12
+ const token = argv[index];
13
+ if (!token.startsWith("--")) {
14
+ args.raw.push(token);
15
+ continue;
16
+ }
17
+
18
+ const key = token.slice(2);
19
+ const next = argv[index + 1];
20
+ const isFlag = !next || next.startsWith("--");
21
+
22
+ if (isFlag) {
23
+ args[key] = true;
24
+ continue;
25
+ }
26
+
27
+ args[key] = next;
28
+ index += 1;
29
+ }
30
+
31
+ return args;
32
+ }
33
+
34
+ function printHelp() {
35
+ process.stdout.write([
36
+ "DIAGCALC terminal tools",
37
+ "",
38
+ "Usage:",
39
+ " diag --tui",
40
+ " diag --dataset hiv_elisa",
41
+ " diag --tp 199 --fp 1 --fn 1 --tn 9799 --pre 2",
42
+ " diag --dataset ddimer --pre 18",
43
+ " diag --list-datasets",
44
+ "",
45
+ "Options:",
46
+ " --tui Launch the interactive terminal UI",
47
+ " --dataset <key> Load a predefined scenario",
48
+ " --tp <n> True positives",
49
+ " --fp <n> False positives",
50
+ " --fn <n> False negatives",
51
+ " --tn <n> True negatives",
52
+ " --pre <n> Pre-test probability (%)",
53
+ " --format <type> Output format: text or json",
54
+ " --list-datasets Show available dataset keys",
55
+ " --help Show this help message",
56
+ "",
57
+ "Tip:",
58
+ " Run `npm link` in this repo to use `diag` globally.",
59
+ "",
60
+ ].join("\n"));
61
+ }
62
+
63
+ function serialiseMetric(metric) {
64
+ return {
65
+ label: metric.label,
66
+ value: metric.value,
67
+ formatted: core.formatValue(metric.value, metric.formatter),
68
+ ci: metric.ci || null,
69
+ note: metric.note || null,
70
+ };
71
+ }
72
+
73
+ function printJsonReport(dataset, input, metrics) {
74
+ const payload = {
75
+ case: dataset ? dataset.name : "Ad hoc case",
76
+ datasetKey: dataset ? dataset.key || null : null,
77
+ input,
78
+ metrics: Object.fromEntries(Object.entries(metrics).map(([key, metric]) => [key, serialiseMetric(metric)])),
79
+ };
80
+
81
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
82
+ }
83
+
84
+ function printDatasets() {
85
+ const rows = datasetStore.listDatasets().map((dataset) => `${dataset.key.padEnd(16)} ${dataset.name}`);
86
+ process.stdout.write(`Available datasets\n\n${rows.join("\n")}\n`);
87
+ }
88
+
89
+ function buildInputFromArgs(args) {
90
+ const dataset = args.dataset ? datasetStore.getDataset(args.dataset) : null;
91
+ if (args.dataset && !dataset) {
92
+ return {
93
+ error: `Unknown dataset: ${args.dataset}`,
94
+ };
95
+ }
96
+
97
+ const merged = {
98
+ tp: dataset ? dataset.tp : NaN,
99
+ fp: dataset ? dataset.fp : NaN,
100
+ fn: dataset ? dataset.fn : NaN,
101
+ tn: dataset ? dataset.tn : NaN,
102
+ preTestProb: dataset ? dataset.preTestProb : NaN,
103
+ };
104
+
105
+ if (typeof args.tp === "string") {
106
+ merged.tp = core.safeParseInt(args.tp);
107
+ }
108
+ if (typeof args.fp === "string") {
109
+ merged.fp = core.safeParseInt(args.fp);
110
+ }
111
+ if (typeof args.fn === "string") {
112
+ merged.fn = core.safeParseInt(args.fn);
113
+ }
114
+ if (typeof args.tn === "string") {
115
+ merged.tn = core.safeParseInt(args.tn);
116
+ }
117
+ if (typeof args.pre === "string") {
118
+ const normalised = core.normaliseDecimal(args.pre);
119
+ merged.preTestProb = normalised === "" ? NaN : parseFloat(normalised);
120
+ }
121
+
122
+ return {
123
+ dataset: dataset ? { key: args.dataset, ...dataset } : null,
124
+ input: merged,
125
+ };
126
+ }
127
+
128
+ function renderMetricLine(metric) {
129
+ const value = core.formatValue(metric.value, metric.formatter);
130
+ const ci = metric.ci
131
+ ? ` | 95% CI ${core.formatPercentage(metric.ci.lower)} to ${core.formatPercentage(metric.ci.upper)}`
132
+ : "";
133
+ return `${metric.label}: ${value}${ci}`;
134
+ }
135
+
136
+ function printReport(dataset, metrics) {
137
+ const entries = [
138
+ metrics.sensitivity,
139
+ metrics.specificity,
140
+ metrics.ppv,
141
+ metrics.npv,
142
+ metrics.lrPositive,
143
+ metrics.lrNegative,
144
+ metrics.preTestProbability,
145
+ metrics.postTestPositive,
146
+ metrics.postTestNegative,
147
+ ];
148
+
149
+ const lines = [];
150
+ lines.push("DIAGCALC");
151
+ if (dataset) {
152
+ lines.push(dataset.name);
153
+ }
154
+ lines.push("");
155
+ entries.forEach((metric) => {
156
+ lines.push(renderMetricLine(metric));
157
+ });
158
+ lines.push("");
159
+ lines.push(`Pre-test bar [${core.buildProbabilityBar(metrics.preTestProbability.value, 24)}] ${core.formatPercentage(metrics.preTestProbability.value)}`);
160
+ lines.push(`Post-test (+) bar [${core.buildProbabilityBar(metrics.postTestPositive.value, 24)}] ${core.formatPercentage(metrics.postTestPositive.value)}`);
161
+ lines.push(`Post-test (-) bar [${core.buildProbabilityBar(metrics.postTestNegative.value, 24)}] ${core.formatPercentage(metrics.postTestNegative.value)}`);
162
+ lines.push("");
163
+ lines.push(`Interpretation LR+: ${metrics.lrPositive.note}`);
164
+ lines.push(`Interpretation LR-: ${metrics.lrNegative.note}`);
165
+ lines.push("");
166
+
167
+ process.stdout.write(`${lines.join("\n")}\n`);
168
+ }
169
+
170
+ function main() {
171
+ const args = parseArgs(process.argv.slice(2));
172
+
173
+ if (args.help) {
174
+ printHelp();
175
+ return;
176
+ }
177
+
178
+ if (args["list-datasets"]) {
179
+ printDatasets();
180
+ return;
181
+ }
182
+
183
+ if (args.tui || (process.argv.length === 2 && process.stdout.isTTY && process.stdin.isTTY)) {
184
+ const { runTui } = require("../tui/index");
185
+ runTui();
186
+ return;
187
+ }
188
+
189
+ if (process.argv.length === 2) {
190
+ printHelp();
191
+ return;
192
+ }
193
+
194
+ const built = buildInputFromArgs(args);
195
+ if (built.error) {
196
+ process.stderr.write(`${built.error}\n`);
197
+ process.exitCode = 1;
198
+ return;
199
+ }
200
+
201
+ const validation = core.validateInputs(built.input);
202
+ if (!validation.valid) {
203
+ process.stderr.write(`${validation.message}\n`);
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+
208
+ const metrics = core.calculateMetrics(built.input);
209
+ const format = typeof args.format === "string" ? args.format.toLowerCase() : "text";
210
+ if (format === "json") {
211
+ printJsonReport(built.dataset, built.input, metrics);
212
+ return;
213
+ }
214
+
215
+ if (format !== "text") {
216
+ process.stderr.write("Unsupported format. Use --format text or --format json.\n");
217
+ process.exitCode = 1;
218
+ return;
219
+ }
220
+
221
+ printReport(built.dataset, metrics);
222
+ }
223
+
224
+ main();
package/index.html ADDED
@@ -0,0 +1,266 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>DIAG-CALC</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" />
10
+ <link rel="stylesheet" href="styles.css" />
11
+ </head>
12
+ <body>
13
+ <header class="intro" role="banner">
14
+ <div class="container">
15
+ <div class="header-controls">
16
+ <button id="themeToggle" class="theme-toggle" aria-label="Toggle dark mode" type="button">
17
+ <span class="theme-icon">◐</span>
18
+ </button>
19
+ </div>
20
+ <p class="tag">Educational tool</p>
21
+ <h1>DIAGCALC</h1>
22
+ <p class="lead">
23
+ Interactive calculator for exploring diagnostic test performance measures
24
+ and applying Bayesian reasoning in clinical practice.
25
+ </p>
26
+ <div class="intro-highlights" role="list">
27
+ <article class="highlight" role="listitem">
28
+ <h2>Key measures</h2>
29
+ <p>Sensitivity, specificity, PPV, NPV, and likelihood ratios with 95%&nbsp;CI.</p>
30
+ </article>
31
+ <article class="highlight" role="listitem">
32
+ <h2>Post-test probability</h2>
33
+ <p>Convert pre-test probability into positive and negative post-test probability.</p>
34
+ </article>
35
+ <article class="highlight" role="listitem">
36
+ <h2>Guided interpretation</h2>
37
+ <p>Results explained to support self-directed learning.</p>
38
+ </article>
39
+ </div>
40
+ </div>
41
+ </header>
42
+ <main class="container" id="main" role="main">
43
+ <div class="workspace">
44
+ <section aria-labelledby="library-title" class="panel panel-library">
45
+ <div class="panel-heading">
46
+ <p class="panel-kicker">Library</p>
47
+ <h2 id="library-title">Load a study scenario</h2>
48
+ </div>
49
+ <div class="form-row">
50
+ <label class="full" for="datasetSelect">Scenario</label>
51
+ <select id="datasetSelect" class="full" aria-describedby="dataset-help">
52
+ <option value="">— Select —</option>
53
+ <optgroup label="Generic scenarios">
54
+ <option value="screening">Screening programme (low prevalence)</option>
55
+ <option value="caseControl">Case-control study</option>
56
+ <option value="clinic">Specialist clinic</option>
57
+ </optgroup>
58
+ <optgroup label="Medical literature cases">
59
+ <option value="ddimer">D-dimer for pulmonary embolism</option>
60
+ <option value="troponin">High-sensitivity troponin for AMI</option>
61
+ <option value="mammography">Screening mammography</option>
62
+ <option value="covid_antigen">Rapid COVID-19 antigen test</option>
63
+ <option value="hiv_elisa">HIV ELISA</option>
64
+ <option value="strep_throat">Rapid Streptococcus test</option>
65
+ <option value="xray_pneumonia">Chest X-ray for pneumonia</option>
66
+ </optgroup>
67
+ </select>
68
+ <p id="dataset-help" class="help-text">
69
+ Values are filled in automatically and can be adjusted afterwards.
70
+ </p>
71
+ <div id="dataset-reference" class="dataset-reference" style="display: none;"></div>
72
+ </div>
73
+ </section>
74
+
75
+ <section aria-labelledby="form-title" class="panel panel-inputs">
76
+ <div class="panel-heading">
77
+ <p class="panel-kicker">Editor</p>
78
+ <h2 id="form-title">Build the confusion matrix</h2>
79
+ <p>
80
+ Use non-negative integers and a pre-test probability under 100%.
81
+ </p>
82
+ </div>
83
+ <form id="inputForm" novalidate>
84
+ <fieldset class="confusion-matrix">
85
+ <legend>Confusion matrix</legend>
86
+ <div class="grid-container" role="group" aria-label="Diagnostic test results">
87
+ <span class="empty" aria-hidden="true"></span>
88
+ <span class="grid-header grid-header-pos">Disease +</span>
89
+ <span class="grid-header grid-header-neg">Disease -</span>
90
+ <label class="grid-label grid-label-pos" for="tp">Test +</label>
91
+ <div class="grid-item">
92
+ <input type="number" id="tp" inputmode="numeric" min="0" step="1"
93
+ placeholder="True positives"
94
+ aria-label="True positives (TP): disease present, test positive"
95
+ required />
96
+ </div>
97
+ <div class="grid-item">
98
+ <input type="number" id="fp" inputmode="numeric" min="0" step="1"
99
+ placeholder="False positives"
100
+ aria-label="False positives (FP): disease absent, test positive"
101
+ required />
102
+ </div>
103
+ <label class="grid-label grid-label-neg" for="fn">Test -</label>
104
+ <div class="grid-item">
105
+ <input type="number" id="fn" inputmode="numeric" min="0" step="1"
106
+ placeholder="False negatives"
107
+ aria-label="False negatives (FN): disease present, test negative"
108
+ required />
109
+ </div>
110
+ <div class="grid-item">
111
+ <input type="number" id="tn" inputmode="numeric" min="0" step="1"
112
+ placeholder="True negatives"
113
+ aria-label="True negatives (TN): disease absent, test negative"
114
+ required />
115
+ </div>
116
+ </div>
117
+ </fieldset>
118
+ <div class="form-row">
119
+ <label for="preTestProb">Pre-test probability (%)</label>
120
+ <input
121
+ class="preTestProb"
122
+ type="number"
123
+ id="preTestProb"
124
+ inputmode="decimal"
125
+ min="0"
126
+ max="100"
127
+ step="0.1"
128
+ placeholder="e.g. 12.5"
129
+ required
130
+ />
131
+ <p class="help-text">
132
+ Suggestion: estimate the disease prevalence in the study population or the clinical probability before the test.
133
+ </p>
134
+ </div>
135
+ <div class="form-actions" role="group">
136
+ <button type="submit">Calculate results</button>
137
+ <button type="reset" id="resetButton" class="secondary">Clear fields</button>
138
+ </div>
139
+ <p id="feedback" class="feedback" role="alert" aria-live="polite"></p>
140
+ </form>
141
+ </section>
142
+
143
+ <section aria-labelledby="results-title" class="panel panel-results results-section">
144
+ <div class="panel-heading panel-heading-inline">
145
+ <div>
146
+ <p class="panel-kicker">Output</p>
147
+ <h2 id="results-title">Results</h2>
148
+ <p class="help-text">
149
+ Values are shown with 95% confidence intervals calculated using the Wilson method.
150
+ </p>
151
+ </div>
152
+ <div class="results-actions" role="group">
153
+ <button type="button" id="printButton" class="secondary">Print report</button>
154
+ </div>
155
+ </div>
156
+ <div id="probabilityChart" class="probability-chart" aria-label="Comparison of pre- and post-test probabilities"></div>
157
+ <div id="faganNomogram" class="fagan-nomogram" style="display: none;">
158
+ <h3>Fagan Nomogram</h3>
159
+ <p class="help-text">Graphical representation of the relationship between pre-test probability, likelihood ratio, and post-test probability.</p>
160
+ <canvas id="faganCanvas" width="600" height="400" aria-label="Fagan nomogram"></canvas>
161
+ </div>
162
+ <div id="results" class="results-grid" aria-live="polite"></div>
163
+ </section>
164
+ </div>
165
+
166
+ <div class="resource-grid">
167
+ <section aria-labelledby="interpretation-title" class="interpretation panel panel-docs">
168
+ <h2 id="interpretation-title">Interpretation guide</h2>
169
+ <details>
170
+ <summary>Sensitivity and specificity</summary>
171
+ <p>
172
+ Quantify the test's ability to correctly identify people with and without the condition. Useful for understanding the intrinsic performance of the test. Learn more at <a href="https://en.wikipedia.org/wiki/Sensitivity_and_specificity" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
173
+ </p>
174
+ </details>
175
+ <details>
176
+ <summary>Predictive values</summary>
177
+ <p>
178
+ Depend on prevalence (pre-test probability). Use them to estimate the probability that a person has or does not have the disease after a positive or negative result. See <a href="https://en.wikipedia.org/wiki/Positive_and_negative_predictive_values" target="_blank" rel="noopener noreferrer">Wikipedia</a> for further reading.
179
+ </p>
180
+ </details>
181
+ <details>
182
+ <summary>Likelihood ratios</summary>
183
+ <p>
184
+ Relate the probability of a result in diseased vs. non-diseased individuals and can be applied directly in Bayes' formula to update the probability of disease. Further reference at <a href="https://en.wikipedia.org/wiki/Likelihood_ratio" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
185
+ </p>
186
+ </details>
187
+ <details>
188
+ <summary>Post-test probabilities</summary>
189
+ <p>
190
+ Result from applying the LR to pre-test odds. They are crucial for clinical decisions such as initiating treatment or ordering additional tests. See also <a href="https://en.wikipedia.org/wiki/Posterior_probability" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
191
+ </p>
192
+ </details>
193
+ </section>
194
+
195
+ <section aria-labelledby="references-title" class="references panel panel-docs">
196
+ <h2 id="references-title">Resources and references</h2>
197
+ <details>
198
+ <summary>Supporting literature</summary>
199
+ <ul class="reference-list">
200
+ <li>
201
+ <strong>Akobeng AK.</strong> Understanding diagnostic tests 1-3: sensitivity, specificity, and predictive values.
202
+ <em>Acta Paediatr.</em> 2007;96(3):338-341.
203
+ <a href="https://doi.org/10.1111/j.1651-2227.2006.00180.x" target="_blank" rel="noopener noreferrer">DOI: 10.1111/j.1651-2227.2006.00180.x</a>
204
+ </li>
205
+ <li>
206
+ <strong>McGee S.</strong> Simplifying likelihood ratios.
207
+ <em>J Gen Intern Med.</em> 2002;17(8):646-649.
208
+ <a href="https://doi.org/10.1046/j.1525-1497.2002.10750.x" target="_blank" rel="noopener noreferrer">DOI: 10.1046/j.1525-1497.2002.10750.x</a>
209
+ </li>
210
+ <li>
211
+ <strong>Fagan TJ.</strong> Nomogram for Bayes theorem.
212
+ <em>N Engl J Med.</em> 1975;293(5):257.
213
+ <a href="https://doi.org/10.1056/NEJM197507312930513" target="_blank" rel="noopener noreferrer">DOI: 10.1056/NEJM197507312930513</a>
214
+ </li>
215
+ </ul>
216
+ </details>
217
+ <details>
218
+ <summary>Online resources</summary>
219
+ <ul class="reference-list">
220
+ <li>
221
+ <strong>The Centre for Evidence-Based Medicine (CEBM):</strong>
222
+ <a href="https://www.cebm.ox.ac.uk/resources/ebm-tools" target="_blank" rel="noopener noreferrer">EBM Tools and Resources</a>
223
+ </li>
224
+ <li>
225
+ <strong>StatPearls:</strong>
226
+ <a href="https://www.ncbi.nlm.nih.gov/books/NBK557491/" target="_blank" rel="noopener noreferrer">Sensitivity and Specificity</a>
227
+ </li>
228
+ <li>
229
+ <strong>Deeks JJ, Altman DG.</strong> Diagnostic tests 4: likelihood ratios.
230
+ <em>BMJ.</em> 2004;329(7458):168-169.
231
+ <a href="https://www.bmj.com/content/329/7458/168" target="_blank" rel="noopener noreferrer">Open access</a>
232
+ </li>
233
+ </ul>
234
+ </details>
235
+ <details>
236
+ <summary>Complementary calculators</summary>
237
+ <ul class="reference-list">
238
+ <li>
239
+ <strong>MDCalc:</strong>
240
+ <a href="https://www.mdcalc.com/" target="_blank" rel="noopener noreferrer">Medical calculators</a>
241
+ </li>
242
+ <li>
243
+ <strong>ClinCalc:</strong>
244
+ <a href="https://clincalc.com/Stats/SampleSize.aspx" target="_blank" rel="noopener noreferrer">Diagnostic Test Calculator</a>
245
+ </li>
246
+ </ul>
247
+ </details>
248
+ </section>
249
+ </div>
250
+ </main>
251
+ <footer>
252
+ <p>
253
+ DIAGCALC is a diagnostic test performance calculator created for educational purposes and training of healthcare students.
254
+ </p>
255
+ <p>
256
+ Any analyses, results and conclusions may not be used, interpreted or generalised beyond this specific context.
257
+ </p>
258
+ <p>
259
+ &copy; 2025 — MIT Licence — Developed by <a href="https://tiagojct.eu">Tiago Jacinto</a>. <a href="mailto:tiagojacinto@med.up.pt">Contact</a>
260
+ </p>
261
+ </footer>
262
+ <script src="lib/diagcalc-datasets.js"></script>
263
+ <script src="lib/diagcalc-core.js"></script>
264
+ <script src="script.js"></script>
265
+ </body>
266
+ </html>