<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- Neonatal jaundice treatment threshold calculator Copyright (C) 2024 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/>. --> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Neonatal jaundice treatment threshold calculator</title> <link href="build/main.css" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body class="w-full"> <div class="max-w-2xl mx-auto mt-4"> <h1 class="text-gray-900 font-medium text-xl pb-3 mb-4 border-b border-gray-400">Neonatal jaundice treatment thresholds</h1> <div class="space-y-2"> <div class="sm:grid sm:grid-cols-3"> <label for="gestation" class="text-sm font-medium text-gray-900 pt-1.5">Gestational age:</label> <div class="col-span-2 relative rounded-md shadow-sm"> <input id="gestation" type="number" value="38" min="22" onchange="plotGraphData();updateBilirubin()" class="w-full rounded-md border-0 py-1.5 pr-[8.8rem] text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm"> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> <span class="text-gray-500 text-sm">completed weeks</span> </div> </div> </div> <div class="sm:grid sm:grid-cols-3"> <label for="time_birth" class="text-sm font-medium text-gray-900 pt-1.5">Time of birth:</label> <input id="time_birth" type="datetime-local" onchange="updateBilirubin()" class="col-span-2 w-full rounded-md shadow-sm border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm"> </div> <div class="sm:grid sm:grid-cols-3"> <label for="time_measurement" class="text-sm font-medium text-gray-900 pt-1.5">Time of measurement:</label> <input id="time_measurement" type="datetime-local" onchange="updateBilirubin()" class="col-span-2 w-full rounded-md shadow-sm border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm"> </div> <div class="sm:grid sm:grid-cols-3"> <label for="bilirubin" class="text-sm font-medium text-gray-900 pt-1.5">Total bilirubin:</label> <div class="col-span-2 relative rounded-md shadow-sm"> <input id="bilirubin" type="number" oninput="updateBilirubin()" class="w-full rounded-md border-0 py-1.5 pr-[4.5rem] text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm"> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> <span class="text-gray-500 text-sm">μmol/L</span> </div> </div> </div> <div id="result" class="d-none"> <p></p> </div> </div> <div class="border-t border-gray-400 mt-4 pt-2"> <canvas id="bilirubinChart" width="672" height="336"></canvas> </div> <div class="border-t border-gray-400 mt-4 pt-4 text-xs text-gray-600 space-y-2 mb-4"> <p>Treatment thresholds as per: 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></p> <p>This tool is made available in the hope that it will be useful, but <em>WITHOUT ANY WARRANTY</em>; without even the implied warranty of <em>MERCHANTABILITY</em> or <em>FITNESS FOR A PARTICULAR PURPOSE</em>. Information provided in this tool is intended for reference by medical professionals. Nothing in this tool is intended to constitute medical advice.</p> <p>Lee Yingtong Li, 2024. Source code available at <a href="https://yingtongli.me/git/bilirubin-calculator" class="text-blue-500 hover:underline hover:text-blue-600">https://yingtongli.me/git/bilirubin-calculator</a>.</p> </div> </div> <script> // ---------------------- // Threshold calculations function phototherapy_38wks(d) { if (d <= 0) { return 100; } if (d <= 1) { return d * 100 + 100; } if (d <= 4) { return (d - 1) * 150 / 3 + 200; } return 350; } function exchange_38wks(d) { if (d <= 0) { return 100; } if (d <= 1.75) { return d * 350 / 1.75 + 100; } return 450; } function phototherapy_thresh(d, gestation) { if (gestation >= 38) { return phototherapy_38wks(d); } if (gestation < 22) { throw new Exception('Invalid gestation'); } if (d <= 0) { return 40; } let thresh_at_3days = gestation * 10 - 100; if (d <= 3) { return d * (thresh_at_3days - 40) / 3 + 40; } return thresh_at_3days; } function exchange_thresh(d, gestation) { if (gestation >= 38) { return exchange_38wks(d); } if (gestation < 22) { throw new Exception('Invalid gestation'); } if (d <= 0) { return 80; } let thresh_at_3days = gestation * 10; if (d <= 3) { return d * (thresh_at_3days - 80) / 3 + 80; } return thresh_at_3days; } // -------------- // Graph plotting function plotGraphData() { let gestation = document.getElementById('gestation').valueAsNumber; chart.data.datasets[0].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: phototherapy_thresh(d, gestation)})); chart.data.datasets[1].data = [...Array(14*24).keys()].map((i) => i / 24).map((d) => ({x: d, y: exchange_thresh(d, gestation)})); chart.update(); } function prettyPrintDays(d) { if (d < 1) { return (d * 24).toFixed(0) + ' hours'; } if (d < 2) { return '1 day, ' + ((d % 1) * 24).toFixed(0) + ' hours'; } return Math.floor(d) + ' days, ' + ((d % 1) * 24).toFixed(0) + ' hours'; } 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: 'index', intersect: false }, plugins: { tooltip: { callbacks: { title: (context) => prettyPrintDays(context[0].parsed.x), label: (context) => context.dataset.label + ': ' + Math.round(context.parsed.y) } } }, maintainAspectRatio: false } }); plotGraphData(); // ------------ // Prefill form let dateNow = new Date(); dateNow.setSeconds(0); dateNow.setMilliseconds(0); if (document.getElementById('time_birth').value === '') { document.getElementById('time_birth').valueAsDate = dateNow; } if (document.getElementById('time_measurement').value === '') { document.getElementById('time_measurement').valueAsDate = dateNow; } // -------------------------- // Plot bilirubin measurement function prettyPrintBilirubin(b) { if (b < 10) { return b.toFixed(1); } else { return Math.round(b).toFixed(0); } } function updateBilirubin() { if (document.getElementById('time_birth').valueAsDate === null) { return; } if (document.getElementById('time_measurement').valueAsDate === null) { return; } if (document.getElementById('bilirubin').value === '') { return; } // Chronological age in days let d = (document.getElementById('time_measurement').valueAsDate.getTime() - document.getElementById('time_birth').valueAsDate.getTime()) / 1000 / 60 / 60 / 24; let bilirubin = document.getElementById('bilirubin').valueAsNumber; let gestation = document.getElementById('gestation').valueAsNumber; let phototherapy_thresh_value = phototherapy_thresh(d, gestation); let exchange_thresh_value = exchange_thresh(d, gestation); chart.data.datasets[2].data = [{x: d, y: bilirubin}]; chart.update(); let resultDiv = document.getElementById('result'); let resultP = resultDiv.querySelector('p'); if (bilirubin < phototherapy_thresh_value) { if (bilirubin < phototherapy_thresh_value - 50) { resultDiv.className = 'border-l-4 border-sky-400 bg-sky-50 py-2 px-4'; resultP.className = 'text-sm text-sky-800'; } else { resultDiv.className = 'border-l-4 border-yellow-400 bg-yellow-50 py-2 px-4'; resultP.className = 'text-sm text-yellow-800'; } resultP.innerHTML = 'Bilirubin of ' + bilirubin + ' at ' + prettyPrintDays(d) + ' is <span class="font-bold">' + prettyPrintBilirubin(phototherapy_thresh_value - bilirubin) + ' below</span> the phototherapy threshold.'; } else if (bilirubin < exchange_thresh_value) { resultDiv.className = 'border-l-4 border-orange-400 bg-orange-50 py-2 px-4'; resultP.className = 'text-sm text-orange-800'; resultP.innerHTML = 'Bilirubin of ' + bilirubin + ' at ' + prettyPrintDays(d) + ' is <span class="font-bold">' + prettyPrintBilirubin(bilirubin - phototherapy_thresh_value) + ' above</span> the phototherapy threshold, ' + prettyPrintBilirubin(exchange_thresh_value - bilirubin) + ' below the exchange transfusion threshold.'; } else { resultDiv.className = 'border-l-4 border-red-400 bg-red-50 py-2 px-4'; resultP.className = 'text-sm text-red-800'; resultP.innerHTML = 'Bilirubin of ' + bilirubin + ' at ' + prettyPrintDays(d) + ' is ' + prettyPrintBilirubin(bilirubin - exchange_thresh_value) + ' above the <span class="font-bold">exchange transfusion</span> threshold.'; } } updateBilirubin(); </script> </body> </html>