244 lines
8.3 KiB
JavaScript
244 lines
8.3 KiB
JavaScript
/*
|
|
Neonatal jaundice treatment threshold calculator
|
|
Copyright (C) 2024-2025 Lee Yingtong Li
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// --------------------------------------
|
|
// Extra questions by guideline/gestation
|
|
|
|
function updateExtraQuestions() {
|
|
// Show or hide extra questions according to guideline and gestation
|
|
let guideline = document.getElementById('guideline').value;
|
|
let gestation = document.getElementById('gestation').valueAsNumber;
|
|
|
|
if (guideline === 'rwh') {
|
|
document.getElementById('questions_rwh').classList.remove('hidden');
|
|
if (gestation >= 35) {
|
|
document.getElementById('questions_rwh_geq35').classList.remove('hidden');
|
|
document.getElementById('questions_rwh_lt35').classList.add('hidden');
|
|
} else {
|
|
document.getElementById('questions_rwh_geq35').classList.add('hidden');
|
|
document.getElementById('questions_rwh_lt35').classList.remove('hidden');
|
|
}
|
|
} else {
|
|
document.getElementById('questions_rwh').classList.add('hidden');
|
|
}
|
|
|
|
// Also update source in footer
|
|
if (guideline === 'nice') {
|
|
document.getElementById('guideline_source').innerHTML = 'National Institute for Health and Clinical Excellence. Neonatal jaundice treatment threshold graphs. In: Jaundice in newborn babies under 28 days. London: National Institute for Health and Clinical Excellence; 2023. (NICE clinical guidelines; CG98). <a href="https://www.nice.org.uk/guidance/cg98" class="text-blue-500 hover:underline hover:text-blue-600">https://www.nice.org.uk/guidance/cg98</a>';
|
|
} else if (guideline === 'rwh') {
|
|
if (gestation >= 35) {
|
|
document.getElementById('guideline_source').innerHTML = 'Royal Women\'s Hospital. Jaundice (hyperbilirubinaemia) in newborn infants ≥ 35 weeks gestation: guideline. Version 2.0. Melbourne: Royal Women\'s Hospital; 2025.';
|
|
} else {
|
|
document.getElementById('guideline_source').innerHTML = 'Royal Women\'s Hospital. Jaundice (hyperbilirubinaemia) in preterm infants < 35 weeks gestation: guideline. Version 2.0. Melbourne: Royal Women\'s Hospital; 2025.';
|
|
}
|
|
} else {
|
|
throw new Error('Unexpected guideline');
|
|
}
|
|
}
|
|
updateExtraQuestions();
|
|
|
|
// --------------
|
|
// Graph plotting
|
|
|
|
function isGestationValid(guideline, gestation) {
|
|
if (isNaN(gestation)) {
|
|
return false;
|
|
}
|
|
if (guideline === 'nice' && gestation < 23) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function plotGraphData() {
|
|
let guideline = document.getElementById('guideline').value;
|
|
let gestation = document.getElementById('gestation').valueAsNumber;
|
|
|
|
if (!isGestationValid(guideline, gestation)) {
|
|
chart.data.datasets[0].data = [];
|
|
chart.data.datasets[1].data = [];
|
|
} else if (guideline === 'nice') {
|
|
chart.data.datasets[0].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: nice_phototherapy_thresh(d, gestation)}));
|
|
chart.data.datasets[1].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: nice_exchange_thresh(d, gestation)}));
|
|
} else if (guideline === 'rwh') {
|
|
let dat = document.getElementById('dat').checked;
|
|
let bw_lt2500 = document.getElementById('bw_lt2500').checked;
|
|
let bw_lt1000 = document.getElementById('bw_lt1000').checked;
|
|
let fhx_rbc = document.getElementById('fhx_rbc').checked;
|
|
let bruising = document.getElementById('bruising').checked;
|
|
|
|
chart.data.datasets[0].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: rwh_phototherapy_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising)}));
|
|
chart.data.datasets[1].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: rwh_exchange_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising)}));
|
|
} else {
|
|
throw new Error('Unexpected guideline');
|
|
}
|
|
|
|
chart.update();
|
|
}
|
|
|
|
Chart.defaults.color = '#111827'; // gray-900
|
|
Chart.defaults.font.family = 'Manrope, Helvetica, Arial, sans-serif';
|
|
|
|
const chart = new Chart(document.getElementById('bilirubinChart'), {
|
|
type: 'scatter',
|
|
data: {
|
|
datasets: [
|
|
{
|
|
label: 'Phototherapy',
|
|
data: [],
|
|
showLine: true,
|
|
pointStyle: false
|
|
},
|
|
{
|
|
label: 'Exchange transfusion',
|
|
data: [],
|
|
showLine: true,
|
|
pointStyle: false
|
|
},
|
|
{
|
|
label: 'Measurement',
|
|
data: []
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Chronological age (days)'
|
|
},
|
|
min: 0,
|
|
max: 14,
|
|
ticks: {
|
|
stepSize: 0.25,
|
|
callback: (value) => value % 1 == 0 ? value : '',
|
|
autoSkip: false,
|
|
maxRotation: 0
|
|
},
|
|
grid: {
|
|
color: (context) => context.tick.value % 1 == 0 ? '#9ca3af' : '#e5e7eb' // gray-400 / gray-200
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Total bilirubin (μmol/L)'
|
|
},
|
|
min: 0,
|
|
max: 550,
|
|
ticks: {
|
|
stepSize: 10,
|
|
callback: (value) => value % 50 == 0 ? value : '',
|
|
autoSkip: false
|
|
},
|
|
grid: {
|
|
color: (context) => context.tick.value % 50 == 0 ? '#9ca3af' : '#e5e7eb' // gray-400 / gray-200
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
mode: 'x',
|
|
intersect: false
|
|
},
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
title: (context) => prettyPrintDays(context[0].parsed.x),
|
|
label: (context) => context.dataset.label + ': ' + Math.round(context.parsed.y)
|
|
},
|
|
filter: (context, idx, items) => Math.abs(context.parsed.x - items[0].parsed.x) < 0.5/24
|
|
}
|
|
},
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
plotGraphData();
|
|
|
|
// ------------
|
|
// Prefill form
|
|
|
|
let dateNow = dateToISOStringLocal(new Date());
|
|
if (document.getElementById('time_birth').value === '') {
|
|
document.getElementById('time_birth').value = dateNow;
|
|
}
|
|
if (document.getElementById('time_measurement').value === '') {
|
|
document.getElementById('time_measurement').value = dateNow;
|
|
}
|
|
|
|
// --------------------------
|
|
// Plot bilirubin measurement
|
|
|
|
function updateBilirubin() {
|
|
let resultDiv = document.getElementById('result');
|
|
let resultP = resultDiv.querySelector('p');
|
|
|
|
let guideline = document.getElementById('guideline').value;
|
|
let bilirubin = document.getElementById('bilirubin').valueAsNumber;
|
|
let gestation = document.getElementById('gestation').valueAsNumber;
|
|
|
|
if (document.getElementById('time_birth').value === '') {
|
|
resultDiv.className = 'hidden';
|
|
return;
|
|
}
|
|
if (document.getElementById('time_measurement').value === '') {
|
|
resultDiv.className = 'hidden';
|
|
return;
|
|
}
|
|
if (isNaN(bilirubin)) {
|
|
resultDiv.className = 'hidden';
|
|
return;
|
|
}
|
|
if (!isGestationValid(guideline, gestation)) {
|
|
resultDiv.className = 'hidden';
|
|
return;
|
|
}
|
|
|
|
let d = chronoAgeInDays(new Date(document.getElementById('time_birth').value), new Date(document.getElementById('time_measurement').value));
|
|
|
|
// Calculate thresholds
|
|
let phototherapy_thresh_value, exchange_thresh_value;
|
|
if (guideline === 'nice') {
|
|
phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
|
|
exchange_thresh_value = nice_exchange_thresh(d, gestation);
|
|
} else if (guideline === 'rwh') {
|
|
let dat = document.getElementById('dat').checked;
|
|
let bw_lt2500 = document.getElementById('bw_lt2500').checked;
|
|
let bw_lt1000 = document.getElementById('bw_lt1000').checked;
|
|
let fhx_rbc = document.getElementById('fhx_rbc').checked;
|
|
let bruising = document.getElementById('bruising').checked;
|
|
|
|
phototherapy_thresh_value = rwh_phototherapy_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising);
|
|
exchange_thresh_value = rwh_exchange_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising);
|
|
} else {
|
|
throw new Error('Unexpected guideline');
|
|
}
|
|
|
|
// Format for display
|
|
let [result_text, text_colour, background_colour, accent_colour] = describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value);
|
|
|
|
resultDiv.className = 'border-l-4 ' + accent_colour + ' ' + background_colour + ' py-2 px-4 mt-4';
|
|
resultP.className = 'text-sm ' + text_colour;
|
|
resultP.innerHTML = result_text;
|
|
|
|
// Update graph
|
|
chart.data.datasets[2].data = [{x: d, y: bilirubin}];
|
|
chart.update();
|
|
}
|
|
updateBilirubin();
|