var bpCache = null; export function loadBpReferenceData() { if (bpCache) return Promise.resolve(bpCache); return fetch('/data/calculators/bp-reference.json', { credentials: 'same-origin' }) .then(function(response) { if (!response.ok) throw new Error('Failed to load BP reference data'); return response.json(); }) .then(function(data) { bpCache = data; return bpCache; }); } function normalCDF(z) { var a1=0.254829592, a2=-0.284496736, a3=1.421413741, a4=-1.453152027, a5=1.061405429, p=0.3275911; var sign = z < 0 ? -1 : 1; z = Math.abs(z) / Math.sqrt(2); var t = 1 / (1 + p * z); var y = 1 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*Math.exp(-z*z); return 0.5 * (1 + sign * y); } export function calcHeightPercentile(reference, age, sex, heightCm) { var idx = Math.max(0, Math.min(217, Math.round(age * 12) - 24)); var table = reference.heightLms[sex === 'female' ? 'female' : 'male']; var L = table.L[idx], M = table.M[idx], S = table.S[idx]; var z = (Math.pow(heightCm / M, L) - 1) / (L * S); return { z: z, percentile: normalCDF(z) * 100 }; } export function computeBPPercentile(reference, age, sex, heightCm, bpType, bpValue) { var t1, t2, t3, t4, t5, ta1, ta2, ta3, ta4, ta5, tb1, tb2, tb3, tb4, tb5, w; if (sex === 'female') { t1=106.7; t2=140.7; t3=154.0; t4=160.5; t5=168.9; ta1=5.00; ta2=10.70; ta3=13.16; ta4=14.51; ta5=17.33; tb1=6.701; tb2=16.438; tb3=46.80; tb4=84.46; tb5=203.608; w = (age - 10) * (heightCm - 147); } else { t1=107.8; t2=140.0; t3=154.5; t4=166.4; t5=179.1; ta1=5.06; ta2=10.79; ta3=13.22; ta4=14.51; ta5=17.30; tb1=-15; tb2=8.9; tb3=50.375; tb4=112.684; tb5=250.04; w = (age - 10) * (heightCm - 150); } var x = heightCm, y = age; var x2a = Math.max(0, x - t1); var x2b = Math.max(0, x - t4); var x2c = Math.max(0, x - t5); var x2 = Math.pow(x2a,3) - Math.pow(x2b,3)*(t5-t1)/(t5-t4) + Math.pow(x2c,3)*(t4-t1)/(t5-t4); var x3a = Math.max(0, x - t2); var x3 = Math.pow(x3a,3) - Math.pow(x2b,3)*(t5-t2)/(t5-t4) + Math.pow(x2c,3)*(t4-t2)/(t5-t4); var x4a = Math.max(0, x - t3); var x4 = Math.pow(x4a,3) - Math.pow(x2b,3)*(t5-t3)/(t5-t4) + Math.pow(x2c,3)*(t4-t3)/(t5-t4); var x2s = x2/100, x3s = x3/100, x4s = x4/100; var y2a = Math.max(0, y - ta1); var y2b = Math.max(0, y - ta4); var y2c = Math.max(0, y - ta5); var y2 = Math.pow(y2a,3) - Math.pow(y2b,3)*(ta5-ta1)/(ta5-ta4) + Math.pow(y2c,3)*(ta4-ta1)/(ta5-ta4); var y3a = Math.max(0, y - ta2); var y3 = Math.pow(y3a,3) - Math.pow(y2b,3)*(ta5-ta2)/(ta5-ta4) + Math.pow(y2c,3)*(ta4-ta2)/(ta5-ta4); var y4a = Math.max(0, y - ta3); var y4 = Math.pow(y4a,3) - Math.pow(y2b,3)*(ta5-ta3)/(ta5-ta4) + Math.pow(y2c,3)*(ta4-ta3)/(ta5-ta4); var y2s = y2/100, y3s = y3/100, y4s = y4/100; var w2a = Math.max(0, w - tb1); var w2b = Math.max(0, w - tb4); var w2c = Math.max(0, w - tb5); var w2 = Math.pow(w2a,3) - Math.pow(w2b,3)*(tb5-tb1)/(tb5-tb4) + Math.pow(w2c,3)*(tb4-tb1)/(tb5-tb4); var w3a = Math.max(0, w - tb2); var w3 = Math.pow(w3a,3) - Math.pow(w2b,3)*(tb5-tb2)/(tb5-tb4) + Math.pow(w2c,3)*(tb4-tb2)/(tb5-tb4); var w4a = Math.max(0, w - tb3); var w4 = Math.pow(w4a,3) - Math.pow(w2b,3)*(tb5-tb3)/(tb5-tb4) + Math.pow(w2c,3)*(tb4-tb3)/(tb5-tb4); var w2s = w2/10000, w3s = w3/10000, w4s = w4/10000; var coeff = reference.coefficients[sex === 'female' ? 'female' : 'male'][bpType === 'sys' ? 'sys' : 'dia']; var predicted = new Array(99); for (var i = 0; i < 99; i++) { predicted[i] = coeff[i][0] + coeff[i][5]*x + coeff[i][6]*x2s + coeff[i][7]*x3s + coeff[i][8]*x4s + coeff[i][1]*y + coeff[i][2]*y2s + coeff[i][3]*y3s + coeff[i][4]*y4s + coeff[i][9]*w + coeff[i][10]*w2s + coeff[i][11]*w3s + coeff[i][12]*w4s; } var minDiff = Infinity, percentile = 50; for (var j = 0; j < 99; j++) { var diff = Math.abs(bpValue - predicted[j]); if (diff < minDiff) { minDiff = diff; percentile = j + 1; } } return { percentile: percentile, predicted: predicted }; } export function classifyBPFromPercentiles(age, sysPctile, diaPctile, sys, dia) { var sysClass, diaClass; if (age >= 13) { if (sys >= 140) sysClass = 'stage2'; else if (sys >= 130) sysClass = 'stage1'; else if (sys >= 120) sysClass = 'elevated'; else sysClass = 'normal'; if (dia >= 90) diaClass = 'stage2'; else if (dia >= 80) diaClass = 'stage1'; else diaClass = 'normal'; } else { if (sysPctile >= 95 + 12 || sys >= 140) sysClass = 'stage2'; else if (sysPctile >= 95 || sys >= 130) sysClass = 'stage1'; else if (sysPctile >= 90 || sys >= 120) sysClass = 'elevated'; else sysClass = 'normal'; if (diaPctile >= 95 + 12 || dia >= 90) diaClass = 'stage2'; else if (diaPctile >= 95 || dia >= 80) diaClass = 'stage1'; else if (diaPctile >= 90) diaClass = 'elevated'; else diaClass = 'normal'; } var levels = { normal: 0, elevated: 1, stage1: 2, stage2: 3 }; var overall = levels[sysClass] >= levels[diaClass] ? sysClass : diaClass; return { sysClass: sysClass, diaClass: diaClass, classification: overall }; } export function calculateBPAssessment(reference, age, sex, heightCm, sys, dia) { var height = calcHeightPercentile(reference, age, sex, heightCm); var systolic = computeBPPercentile(reference, age, sex, heightCm, 'sys', sys); var diastolic = computeBPPercentile(reference, age, sex, heightCm, 'dia', dia); var classification = classifyBPFromPercentiles(age, systolic.percentile, diastolic.percentile, sys, dia); return { height: height, systolic: systolic, diastolic: diastolic, classification: classification }; }