Health Metrics
All health calculations in Health Observability are implemented in worker/utils.js and applied server-side before data is stored. This page documents the medical context, formulae, and implementation details.
Blood Pressure Overview
Blood pressure is expressed as systolic / diastolic in millimetres of mercury (mmHg):
- Systolic — peak pressure when the heart contracts
- Diastolic — resting pressure between beats
A measurement session captures three sequential readings to account for natural variability (white-coat effect, measurement artefact, positioning). The system automatically selects the best reading and stores derived metrics.
Best Reading Selection
Implementation (worker/utils.js):
export function selectBestReading(readings) {
return readings.reduce((best, current) => {
if (!best) return current;
if (current.systolic < best.systolic) return current;
if (current.systolic === best.systolic && current.diastolic < best.diastolic) return current;
return best;
}, null);
}
Only the best reading’s derived metrics (pulse pressure and MAP) are stored in the database. All three readings’ systolic, diastolic, and pulse values are stored for audit purposes.
Derived Metrics
Pulse Pressure (PP)
Pulse pressure is the difference between systolic and diastolic pressure. It reflects arterial stiffness; values consistently above 60 mmHg may indicate cardiovascular risk.
$$PP = \text{Systolic} - \text{Diastolic}$$
Normal range: ~40 mmHg
Mean Arterial Pressure (MAP)
MAP represents the average pressure in the arteries during one cardiac cycle. It is the perfusion pressure that drives blood to organs.
$$MAP = \text{Diastolic} + \frac{PP}{3}$$
This is equivalent to:
$$MAP \approx \frac{\text{Systolic} + 2 \times \text{Diastolic}}{3}$$
Normal range: 70–100 mmHg
Implementation:
export function calculateDerivedMetrics(reading) {
const pulsePressure = reading.systolic - reading.diastolic;
const MAP = Math.round((reading.diastolic + pulsePressure / 3) * 10) / 10;
return { pulsePressure, MAP };
}
MAP is rounded to one decimal place.
AHA/ACC 2017 Blood Pressure Categories
The system implements the 2017 American Heart Association / American College of Cardiology hypertension guidelines. These superseded the JNC 7 guidelines and lowered the threshold for hypertension diagnosis.
Category Reference Table
| Category | Systolic (mmHg) | Diastolic (mmHg) | Badge Colour | Colour Code |
|---|---|---|---|---|
| Normal | < 120 | < 80 | Green | #27ae60 |
| Elevated | 120–129 | < 80 | Amber | #f39c12 |
| High — Stage 1 | 130–139 | 80–89 | Dark Orange | #e67e22 |
| High — Stage 2 | ≥ 140 | ≥ 90 | Red | #c0392b |
| Hypertensive Crisis | ≥ 180 | ≥ 120 | Dark Red | #922b21 |
Note on Stage 1 logic: The
ORcondition in Stage 1 means a reading of 138/85 qualifies even though diastolic is below 90, and 125/85 qualifies even though systolic is below 130. This matches the AHA/ACC specification.
Implementation:
export function bpCategory(systolic, diastolic) {
if (systolic < 120 && diastolic < 80) return { label: 'Normal', color: '#27ae60' };
if (systolic < 130 && diastolic < 80) return { label: 'Elevated', color: '#f39c12' };
if (systolic < 140 || diastolic < 90) return { label: 'High — Stage 1', color: '#e67e22' };
if (systolic < 180 && diastolic < 120) return { label: 'High — Stage 2', color: '#c0392b' };
return { label: 'Hypertensive crisis', color: '#922b21' };
}
Input Validation
Server-side validation is applied to all readings before any database operation.
Validation Ranges
| Field | Min | Max | Unit |
|---|---|---|---|
| Systolic | 50 | 300 | mmHg |
| Diastolic | 30 | 200 | mmHg |
| Pulse | 20 | 300 | bpm |
| Weight | 20 | 300 | kg |
| Height | 50 | 280 | cm |
CPAP / Sleep Metrics
CPAP data is sourced from the ResMed MyAir service. The key clinical metrics are:
| Metric | Field | Unit | Clinical Significance |
|---|---|---|---|
| AHI | ahi | events/hour | Apnea-Hypopnea Index; < 5 is normal, 5–15 mild OSA, > 30 severe |
| Usage | usage_minutes | minutes | Therapeutic compliance; ≥ 4h/night is typical compliance threshold |
| Mask Leak | mask_leak_pct | % of night | High leak reduces CPAP efficacy |
| MyAir Score | myair_score | 0–100 | ResMed composite score; ≥ 70 considered good |
| Pressure | pressure_min / pressure_max | cmH₂O | APAP pressure range used during the night |
CPAP Score Colour Coding (Frontend)
The history table colours the MyAir score for quick visual assessment:
| Score | Colour | Class |
|---|---|---|
| ≥ 70 | Green #27ae60 | Good |
| 50–69 | Orange #e67e22 | Fair |
| < 50 | Red #c0392b | Poor |
Sleep–BP Correlation
The correlation chart juxtaposes each CPAP night’s sleep score against the next morning’s blood pressure reading. The hypothesis is that poor sleep quality (low score, high AHI) correlates with elevated morning blood pressure.
The join is performed by the external CPAP worker’s /api/cpap-correlation endpoint, which matches cpap_data.log_date to the first blood pressure session recorded on log_date + 1 day.
(Illustrative data — values shown are examples only)