Update bilirubin to exact AAP 2022 values, add exchange transfusion

Phototherapy thresholds updated with exact values extracted from
PediTools (validated against AAP 2022 nomograms):
- Separate tables for GA 35, 36, and 38+ weeks
- With and without neurotoxicity risk factors
- Hour-specific values at 12, 24, 36, 48, 60, 72, 84, 96, 120h

Previous approximations were 1-3 mg/dL too low (conservative but
inaccurate). New values match the published AAP 2022 curves exactly.

Exchange transfusion thresholds added for GA 38+ weeks (with/without
risk factors). Displayed alongside phototherapy threshold in results.

GA selection expanded: 35, 36, 37, 38, 39, 40+ weeks.
Chart now shows both phototherapy and exchange transfusion lines.

Also: fixed Loki port conflict (3100->3101), added logs.pedshub.com.
This commit is contained in:
Daniel 2026-04-11 05:50:19 +02:00
parent 5bf55499a4
commit 1b5faa3a01
3 changed files with 51 additions and 20 deletions

View file

@ -10,7 +10,7 @@ services:
loki: loki:
image: grafana/loki:3.4.2 image: grafana/loki:3.4.2
ports: ports:
- "127.0.0.1:3100:3100" - "127.0.0.1:3101:3100"
command: -config.file=/etc/loki/loki-config.yaml command: -config.file=/etc/loki/loki-config.yaml
volumes: volumes:
- loki-data:/loki - loki-data:/loki

View file

@ -372,7 +372,9 @@
<option value="35">35 weeks</option> <option value="35">35 weeks</option>
<option value="36">36 weeks</option> <option value="36">36 weeks</option>
<option value="37">37 weeks</option> <option value="37">37 weeks</option>
<option value="38">38+ weeks</option> <option value="38">38 weeks</option>
<option value="39">39 weeks</option>
<option value="40">40+ weeks</option>
</select> </select>
</div> </div>
<div class="calc-field"> <div class="calc-field">

View file

@ -1282,21 +1282,37 @@
// Values read from published nomogram curves at key time points // Values read from published nomogram curves at key time points
// NOTE: The AAP 2022 raised thresholds ~2 mg/dL above the 2004 guidelines // NOTE: The AAP 2022 raised thresholds ~2 mg/dL above the 2004 guidelines
// >= 38 weeks, no neurotoxicity risk factors (Figure 2, upper curve) // AAP 2022 exact phototherapy thresholds (extracted from PediTools/AAP nomograms)
// >= 38 weeks, no neurotoxicity risk factors
var photoThresholds38 = { var photoThresholds38 = {
12: 8.0, 24: 12.0, 36: 14.0, 48: 15.5, 60: 17.0, 72: 18.0, 84: 18.5, 96: 19.0, 108: 19.5, 120: 20.0 12: 10.1, 24: 12.3, 36: 14.2, 48: 16.0, 60: 17.5, 72: 18.8, 84: 19.9, 96: 20.7, 120: 20.9
}; };
// >= 38 weeks, WITH neurotoxicity risk factors (Figure 2, lower curve) // >= 38 weeks, WITH neurotoxicity risk factors
var photoThresholds38risk = { var photoThresholds38risk = {
12: 6.0, 24: 10.0, 36: 12.0, 48: 13.5, 60: 15.0, 72: 16.0, 84: 16.5, 96: 17.0, 108: 17.5, 120: 18.0 12: 8.5, 24: 10.5, 36: 12.4, 48: 14.0, 60: 15.4, 72: 16.6, 84: 17.5, 96: 18.2, 120: 18.2
}; };
// 35-37 weeks, no neurotoxicity risk factors (Figure 3, upper curve) // 36 weeks, no neurotoxicity risk factors
var photoThresholds36 = {
12: 9.0, 24: 11.2, 36: 13.1, 48: 14.8, 60: 16.2, 72: 17.5, 84: 18.5, 96: 19.3, 120: 19.4
};
// 36 weeks, WITH neurotoxicity risk factors
var photoThresholds36risk = {
12: 7.4, 24: 9.4, 36: 11.2, 48: 12.8, 60: 14.2, 72: 15.4, 84: 16.3, 96: 17.0, 120: 17.1
};
// 35 weeks, no neurotoxicity risk factors
var photoThresholds35 = { var photoThresholds35 = {
12: 5.5, 24: 9.0, 36: 11.0, 48: 12.5, 60: 14.0, 72: 14.5, 84: 15.0, 96: 15.5, 108: 16.0, 120: 16.0 12: 8.5, 24: 10.6, 36: 12.5, 48: 14.2, 60: 15.6, 72: 16.8, 84: 17.8, 96: 18.6, 120: 18.7
}; };
// 35-37 weeks, WITH neurotoxicity risk factors (Figure 3, lower curve) // 35 weeks, WITH neurotoxicity risk factors
var photoThresholds35risk = { var photoThresholds35risk = {
12: 4.0, 24: 7.5, 36: 9.0, 48: 10.5, 60: 12.0, 72: 12.5, 84: 13.0, 96: 13.5, 108: 14.0, 120: 14.0 12: 6.9, 24: 8.9, 36: 10.6, 48: 12.2, 60: 13.5, 72: 14.6, 84: 15.5, 96: 16.1, 120: 16.3
};
// Exchange transfusion thresholds (AAP 2022)
var exchangeThresholds38 = {
12: 19.7, 24: 21.4, 36: 22.8, 48: 24.0, 60: 25.1, 72: 25.9, 84: 26.6, 96: 27.0
};
var exchangeThresholds38risk = {
12: 16.3, 24: 17.7, 36: 19.0, 48: 20.1, 60: 21.2, 72: 22.1, 84: 22.9, 96: 23.5
}; };
function interpolateThreshold(table, hours) { function interpolateThreshold(table, hours) {
@ -1333,16 +1349,24 @@
var risk = document.getElementById('bili-risk').value; var risk = document.getElementById('bili-risk').value;
if (!ga || isNaN(hours) || isNaN(tsb)) { showToast('Fill in all fields', 'error'); return; } if (!ga || isNaN(hours) || isNaN(tsb)) { showToast('Fill in all fields', 'error'); return; }
var gaNum = parseInt(ga);
var table; var table;
if (parseInt(ga) >= 38) table = risk === 'medium' ? photoThresholds38risk : photoThresholds38; if (gaNum >= 38) table = risk === 'medium' ? photoThresholds38risk : photoThresholds38;
else if (gaNum === 36 || gaNum === 37) table = risk === 'medium' ? photoThresholds36risk : photoThresholds36;
else table = risk === 'medium' ? photoThresholds35risk : photoThresholds35; else table = risk === 'medium' ? photoThresholds35risk : photoThresholds35;
var threshold = interpolateThreshold(table, hours); var threshold = interpolateThreshold(table, hours);
var aboveThreshold = tsb >= threshold; var aboveThreshold = tsb >= threshold;
var cl = aboveThreshold // Check exchange transfusion threshold too
? { text: 'Above Phototherapy Threshold', color: '#ef4444', bg: '#fee2e2' } var exchangeTable = gaNum >= 38 ? (risk === 'medium' ? exchangeThresholds38risk : exchangeThresholds38) : null;
: { text: 'Below Phototherapy Threshold', color: '#10b981', bg: '#d1fae5' }; var exchangeThreshold = exchangeTable ? interpolateThreshold(exchangeTable, hours) : null;
var aboveExchange = exchangeThreshold && tsb >= exchangeThreshold;
var cl;
if (aboveExchange) cl = { text: 'ABOVE EXCHANGE TRANSFUSION THRESHOLD', color: '#7f1d1d', bg: '#fecaca' };
else if (aboveThreshold) cl = { text: 'Above Phototherapy Threshold', color: '#ef4444', bg: '#fee2e2' };
else cl = { text: 'Below Phototherapy Threshold', color: '#10b981', bg: '#d1fae5' };
var resultDiv = document.getElementById('bili-result'); var resultDiv = document.getElementById('bili-result');
resultDiv.classList.remove('hidden'); resultDiv.classList.remove('hidden');
@ -1353,15 +1377,20 @@
'</div>' + '</div>' +
'<div class="calc-result-grid">' + '<div class="calc-result-grid">' +
'<div class="calc-result-item"><div class="calc-result-label">Patient TSB</div><div class="calc-result-value">' + tsb + '</div><div class="calc-result-sub">mg/dL</div></div>' + '<div class="calc-result-item"><div class="calc-result-label">Patient TSB</div><div class="calc-result-value">' + tsb + '</div><div class="calc-result-sub">mg/dL</div></div>' +
'<div class="calc-result-item"><div class="calc-result-label">Threshold</div><div class="calc-result-value">' + threshold.toFixed(1) + '</div><div class="calc-result-sub">mg/dL at ' + hours + 'h</div></div>' + '<div class="calc-result-item"><div class="calc-result-label">Photo Threshold</div><div class="calc-result-value">' + threshold.toFixed(1) + '</div><div class="calc-result-sub">mg/dL at ' + hours + 'h</div></div>' +
'<div class="calc-result-item"><div class="calc-result-label">Margin</div><div class="calc-result-value" style="color:' + cl.color + ';">' + (tsb >= threshold ? '+' : '') + (tsb - threshold).toFixed(1) + '</div><div class="calc-result-sub">mg/dL ' + (aboveThreshold ? 'above' : 'below') + '</div></div>' + '<div class="calc-result-item"><div class="calc-result-label">Margin</div><div class="calc-result-value" style="color:' + cl.color + ';">' + (tsb >= threshold ? '+' : '') + (tsb - threshold).toFixed(1) + '</div><div class="calc-result-sub">mg/dL ' + (aboveThreshold ? 'above' : 'below') + ' photo</div></div>' +
(exchangeThreshold ? '<div class="calc-result-item"><div class="calc-result-label">Exchange Threshold</div><div class="calc-result-value" style="color:#7f1d1d;">' + exchangeThreshold.toFixed(1) + '</div><div class="calc-result-sub">mg/dL' + (aboveExchange ? ' — EXCEEDED' : '') + '</div></div>' : '') +
'</div>' + '</div>' +
'<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">GA ' + ga + ' weeks, ' + (risk === 'medium' ? 'with' : 'without') + ' neurotoxicity risk factors. AAP 2022 CPG. Always use clinical judgment.</p>'; '<p style="font-size:11px;color:var(--g400);margin:12px 0 0;">GA ' + ga + ' weeks, ' + (risk === 'medium' ? 'with' : 'without') + ' neurotoxicity risk factors. AAP 2022 CPG (exact values from published nomograms). Always use clinical judgment.</p>';
// Chart: phototherapy threshold line vs patient // Chart: phototherapy + exchange threshold lines vs patient
renderBiliChart(hours, tsb, [ var chartDatasets = [
{ label: 'Phototherapy Threshold', data: generateThresholdCurve(table), borderColor: '#ef4444', borderWidth: 2, borderDash: [], fill: { target: 'origin', above: 'rgba(239,68,68,0.08)' } } { label: 'Phototherapy Threshold', data: generateThresholdCurve(table), borderColor: '#ef4444', borderWidth: 2, borderDash: [], fill: { target: 'origin', above: 'rgba(239,68,68,0.08)' } }
]); ];
if (exchangeTable) {
chartDatasets.push({ label: 'Exchange Transfusion', data: generateThresholdCurve(exchangeTable), borderColor: '#7f1d1d', borderWidth: 2, borderDash: [6,3], fill: false });
}
renderBiliChart(hours, tsb, chartDatasets);
}); });
document.getElementById('btn-clear-bili-aap').addEventListener('click', function() { document.getElementById('btn-clear-bili-aap').addEventListener('click', function() {