diff --git a/src/bilirubin_app.js b/src/bilirubin_app.js
index 3e83d03..41ad280 100644
--- a/src/bilirubin_app.js
+++ b/src/bilirubin_app.js
@@ -63,9 +63,14 @@ function plotGraphData() {
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') {
- // NYI
- chart.data.datasets[0].data = [];
- chart.data.datasets[1].data = [];
+ 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');
}
@@ -191,23 +196,35 @@ function updateBilirubin() {
return;
}
- let d, result_text, text_colour, background_colour, accent_colour;
+ 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') {
- [d, result_text, text_colour, background_colour, accent_colour] = describeBilirubin(gestation, new Date(document.getElementById('time_birth').value), new Date(document.getElementById('time_measurement').value), bilirubin);
+ phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
+ exchange_thresh_value = nice_exchange_thresh(d, gestation);
} else if (guideline === 'rwh') {
- // NYI
- resultDiv.className = 'hidden';
- return;
+ 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');
}
- chart.data.datasets[2].data = [{x: d, y: bilirubin}];
- chart.update();
+ // 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();
diff --git a/src/bilirubin_lib.js b/src/bilirubin_lib.js
index e4b3712..64b65e7 100644
--- a/src/bilirubin_lib.js
+++ b/src/bilirubin_lib.js
@@ -76,16 +76,132 @@ function nice_exchange_thresh(d, gestation) {
return thresh_at_3days;
}
+// ----------------------------
+// Threshold calculations (RWH)
+
+var RWH_CURVE_DATA = {
+ 'geq38': {
+ 'phototherapy': [[0, 110], [12, 160], [24, 205], [48, 265], [60, 290], [72, 310], [96, 340], [120, 360]],
+ 'exchange': [[0, 270], [12, 295], [24, 325], [48, 375], [60, 390], [72, 410], [96, 430], [108, 430], [120, 435]]
+ },
+ '35-37': {
+ 'phototherapy': [[0, 85], [12, 130], [24, 170], [36, 200], [48, 225], [60, 240], [72, 260], [96, 290]],
+ 'exchange': [[0, 235], [12, 260], [36, 300], [48, 325], [60, 340], [72, 360], [84, 375], [96, 385]],
+ },
+ '33-34': {
+ 'phototherapy_datpos': [[0, 80], [12, 110], [24, 145], [36, 170], [48, 190], [72, 220]],
+ 'phototherapy_datneg': [[0, 100], [12, 130], [24, 165], [36, 190], [48, 210], [72, 240]],
+ 'exchange': [[0, 210], [12, 240], [24, 260], [36, 295], [48, 315], [60, 330], [72, 340]],
+ },
+ '31-32': {
+ 'phototherapy_datpos': [[0, 70], [12, 100], [24, 135], [48, 175], [60, 190], [72, 200]],
+ 'phototherapy_datneg': [[0, 90], [12, 120], [24, 155], [48, 195], [60, 210], [72, 220]],
+ 'exchange': [[0, 190], [12, 225], [24, 255], [48, 295], [60, 310], [72, 320]],
+ },
+ '27-30': {
+ 'phototherapy_datpos': [[0, 60], [24, 120], [36, 140], [48, 155], [60, 165], [72, 170]],
+ 'phototherapy_datneg': [[0, 80], [24, 140], [36, 160], [48, 175], [60, 185], [72, 190]],
+ 'exchange': [[0, 170], [24, 230], [48, 270], [72, 290]],
+ },
+ 'lt27': {
+ 'phototherapy_datpos': [[0, 60], [12, 70], [24, 90], [48, 120], [72, 130]],
+ 'phototherapy_datneg': [[0, 80], [12, 90], [24, 110], [48, 140], [72, 150]],
+ 'exchange': [[0, 150], [12, 180], [24, 205], [48, 235], [60, 245], [72, 250]],
+ },
+};
+
+function rwh_interpolate_curve(hours, curve) {
+ if (hours < curve[0][0]) {
+ throw new Error('Attempt to interpolate before start of curve');
+ }
+
+ let leftHours = curve[0][0];
+ let leftBilirubin = curve[0][1];
+
+ for (let i = 1; i < curve.length; i++) {
+ let rightHours = curve[i][0];
+ let rightBilirubin = curve[i][1];
+
+ if (hours <= rightHours) {
+ return leftBilirubin + (hours - leftHours) / (rightHours - leftHours) * (rightBilirubin - leftBilirubin);
+ }
+
+ leftHours = rightHours;
+ leftBilirubin = rightBilirubin;
+ }
+
+ // To the right of the final point = flat part of curve
+ return leftBilirubin;
+}
+
+function rwh_phototherapy_geq35(d, chart) {
+ return rwh_interpolate_curve(d * 24, RWH_CURVE_DATA[chart]['phototherapy']);
+}
+
+function rwh_phototherapy_lt35(d, chart, dat) {
+ if (dat) {
+ return rwh_interpolate_curve(d * 24, RWH_CURVE_DATA[chart]['phototherapy_datpos']);
+ } else {
+ return rwh_interpolate_curve(d * 24, RWH_CURVE_DATA[chart]['phototherapy_datneg']);
+ }
+}
+
+function rwh_exchange(d, chart) {
+ return rwh_interpolate_curve(d * 24, RWH_CURVE_DATA[chart]['exchange']);
+}
+
+function rwh_phototherapy_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising) {
+ // Determine the correct chart
+ if (gestation >= 38) {
+ if (dat || bw_lt2500 || fhx_rbc || bruising) {
+ return rwh_phototherapy_geq35(d, '35-37');
+ } else {
+ return rwh_phototherapy_geq35(d, 'geq38');
+ }
+ } else if (gestation >= 35) {
+ return rwh_phototherapy_geq35(d, '35-37');
+ } else {
+ // Gestation < 35 weeks
+ if (bw_lt1000 || gestation < 27) {
+ return rwh_phototherapy_lt35(d, 'lt27', dat);
+ } else if (gestation < 31) {
+ return rwh_phototherapy_lt35(d, '27-30', dat);
+ } else if (gestation < 33) {
+ return rwh_phototherapy_lt35(d, '31-32', dat);
+ } else {
+ return rwh_phototherapy_lt35(d, '33-34', dat);
+ }
+ }
+}
+
+function rwh_exchange_thresh(d, gestation, dat, bw_lt2500, bw_lt1000, fhx_rbc, bruising) {
+ // Determine the correct chart
+ if (gestation >= 38) {
+ if (dat || bw_lt2500 || fhx_rbc || bruising) {
+ return rwh_exchange(d, '35-37');
+ } else {
+ return rwh_exchange(d, 'geq38');
+ }
+ } else if (gestation >= 35) {
+ return rwh_exchange(d, '35-37');
+ } else {
+ // Gestation < 35 weeks
+ if (bw_lt1000 || gestation < 27) {
+ return rwh_exchange(d, 'lt27');
+ } else if (gestation < 31) {
+ return rwh_exchange(d, '27-30');
+ } else if (gestation < 33) {
+ return rwh_exchange(d, '31-32');
+ } else {
+ return rwh_exchange(d, '33-34');
+ }
+ }
+}
+
// ----------------------------------
// Bilirubin vs threshold calculation
-function describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin) {
- // Chronological age in days
- let d = (dateMeasurement.getTime() - dateBirth.getTime()) / 1000 / 60 / 60 / 24;
-
- let phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
- let exchange_thresh_value = nice_exchange_thresh(d, gestation);
-
+function describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value) {
// Restate colours separately so Tailwind can detect them
let result_text, text_colour, background_colour, accent_colour;
@@ -115,12 +231,18 @@ function describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin) {
result_text = 'Bilirubin of ' + bilirubin + ' at ' + prettyPrintDays(d) + ' is ' + prettyPrintBilirubin(bilirubin - exchange_thresh_value) + ' above the exchange transfusion threshold.';
}
- return [d, result_text, text_colour, background_colour, accent_colour];
+ return [result_text, text_colour, background_colour, accent_colour];
}
// --------------
// Utility functions
+function chronoAgeInDays(dateBirth, dateMeasurement) {
+ // Chronological age in days
+ let d = (dateMeasurement.getTime() - dateBirth.getTime()) / 1000 / 60 / 60 / 24;
+ return d;
+}
+
function prettyPrintHours(d) {
if (d >= 23.5/24) {
throw new Error('>24 hours passed to prettyPrintHours');
diff --git a/tests/describe.test.js b/tests/describe.test.js
index 66a1da2..71d844b 100644
--- a/tests/describe.test.js
+++ b/tests/describe.test.js
@@ -1,6 +1,6 @@
/*
Neonatal jaundice treatment threshold calculator
- Copyright (C) 2024 Lee Yingtong Li
+ 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
@@ -33,7 +33,10 @@ suite('Describe bilirubin result', () => {
const expectedText = 'Bilirubin of 20 at 1 day, 0 hours is 180 below the phototherapy threshold.';
const expectedColour = 'sky';
- let [d, result_text, text_colour, background_colour, accent_colour] = describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin);
+ let d = chronoAgeInDays(dateBirth, dateMeasurement);
+ let phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
+ let exchange_thresh_value = nice_exchange_thresh(d, gestation);
+ let [result_text, text_colour, background_colour, accent_colour] = describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value);
assert.equal(d, 1);
assert.equal(result_text, expectedText);
@@ -47,7 +50,10 @@ suite('Describe bilirubin result', () => {
const expectedText = 'Bilirubin of 160 at 1 day, 0 hours is 40 below the phototherapy threshold.';
const expectedColour = 'yellow';
- let [d, result_text, text_colour, background_colour, accent_colour] = describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin);
+ let d = chronoAgeInDays(dateBirth, dateMeasurement);
+ let phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
+ let exchange_thresh_value = nice_exchange_thresh(d, gestation);
+ let [result_text, text_colour, background_colour, accent_colour] = describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value);
assert.equal(d, 1);
assert.equal(result_text, expectedText);
@@ -61,7 +67,10 @@ suite('Describe bilirubin result', () => {
const expectedText = 'Bilirubin of 220 at 1 day, 0 hours is 20 above the phototherapy threshold, 80 below the exchange transfusion threshold.';
const expectedColour = 'orange';
- let [d, result_text, text_colour, background_colour, accent_colour] = describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin);
+ let d = chronoAgeInDays(dateBirth, dateMeasurement);
+ let phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
+ let exchange_thresh_value = nice_exchange_thresh(d, gestation);
+ let [result_text, text_colour, background_colour, accent_colour] = describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value);
assert.equal(d, 1);
assert.equal(result_text, expectedText);
@@ -75,7 +84,10 @@ suite('Describe bilirubin result', () => {
const expectedText = 'Bilirubin of 350 at 1 day, 0 hours is 50 above the exchange transfusion threshold.';
const expectedColour = 'red';
- let [d, result_text, text_colour, background_colour, accent_colour] = describeBilirubin(gestation, dateBirth, dateMeasurement, bilirubin);
+ let d = chronoAgeInDays(dateBirth, dateMeasurement);
+ let phototherapy_thresh_value = nice_phototherapy_thresh(d, gestation);
+ let exchange_thresh_value = nice_exchange_thresh(d, gestation);
+ let [result_text, text_colour, background_colour, accent_colour] = describeBilirubin(d, bilirubin, phototherapy_thresh_value, exchange_thresh_value);
assert.equal(d, 1);
assert.equal(result_text, expectedText);