Implement RWH charts
This commit is contained in:
parent
4820669558
commit
f1041dd171
@ -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();
|
||||
|
@ -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 <span class="font-bold">exchange transfusion</span> 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');
|
||||
|
@ -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 <span class="font-bold">180 below</span> 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 <span class="font-bold">40 below</span> 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 <span class="font-bold">20 above</span> 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 <span class="font-bold">exchange transfusion</span> 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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user