Health calculator
The health calculator tool (ctx.HEALTH_CALCULATOR) is a built-in tool available inside any loop. It exposes deterministic, PHI-free reference math for common adult vital-sign ranges, plus a country-aware emergency-routing helper.
Use it when your loop needs to classify a reading against a published reference range and you don’t want to call out to a model. Every result includes a source or note string citing the underlying public guideline (CDC, AHA, etc.) so the loop can attribute the classification when it surfaces results to a user.
What this tool is for
- Classifying a vital-sign reading against an adult reference range.
- Producing a short, source-attributed note your loop can render to the user or include in a downstream message.
- Routing a user to the right emergency number for their country when an upstream concern is flagged.
What this tool is not for
- Not a diagnosis. Every reference range is a screening boundary — categories like
high_blood_pressure_stage_2describe the reading, not the person. Loops should pair the category with the includedurgency/notestring when they show it to a user. - Not for children. The ranges encoded here are adult reference ranges from the CDC and AHA. Pediatric ranges are different and not in scope for this tool.
- Not a substitute for clinician review. The emergency-routing helper points to public emergency numbers and crisis lines; it does not page a clinician.
Tool API
ctx.HEALTH_CALCULATOR exposes six methods. All of them must be called inside a step.do callback — ctx only exists there. See Step context for the surrounding API.
type BmiResult = {
bmi: number;
category: "underweight" | "healthy_weight" | "overweight" | "obesity";
source: string;
};
type BloodPressureResult = {
category:
| "hypertensive_crisis_range"
| "high_blood_pressure_stage_2"
| "high_blood_pressure_stage_1"
| "elevated"
| "normal";
urgency: string;
source: string;
};
type RestingHeartRateResult = {
category: "very_low" | "low" | "typical_resting_range" | "high" | "very_high";
note: string;
};
type BreathingRateResult = {
category: "very_low" | "typical_adult_resting_range" | "mildly_high" | "high";
note: string;
};
type Spo2Result = {
category: "typical_range" | "lower_than_expected" | "urgent_range";
note: string;
};
type EmergencyConcern =
| "medical_emergency"
| "self_harm"
| "personal_safety"
| "mental_health_support"
| "substance_use_support";
type EmergencyRoutingResult = {
country: string;
emergency: string;
mentalHealth?: string;
personalSafety?: string;
nonEmergency?: string;
source: string;
};
type HealthCalculatorTool = {
bmi(input: { heightCm: number; weightKg: number }): Promise<BmiResult>;
bloodPressure(input: {
systolic: number;
diastolic: number;
}): Promise<BloodPressureResult>;
restingHeartRate(input: { bpm: number }): Promise<RestingHeartRateResult>;
breathingRate(input: {
breathsPerMinute: number;
}): Promise<BreathingRateResult>;
oxygenSaturation(input: { percent: number }): Promise<Spo2Result>;
emergencyRouting(input: {
country?: string;
concern: EmergencyConcern;
}): Promise<EmergencyRoutingResult>;
};| Method | Description |
|---|---|
bmi | Round-to-1dp BMI with adult CDC category. Input in centimetres and kilograms. |
bloodPressure | Classify a systolic/diastolic pair against the AHA categories. Returns a category and an urgency string suitable to show users. |
restingHeartRate | Classify a resting bpm reading. Returns a category and a note covering context (athletes, symptoms, etc.). |
breathingRate | Classify resting breaths per minute against the adult range. |
oxygenSaturation | Classify an SpO2 percentage against typical, lower-than-expected, and urgent ranges. |
emergencyRouting | Return country-appropriate emergency, mental health, and personal safety numbers for the given concern. |
Example: blood-pressure triage
import { Workflow } from "./loop";
export class HealthLoop extends Workflow {
async run(event: HealthEvent<{ systolic: number; diastolic: number }>, step: HealthStep) {
const classification = await step.do("classify-bp", async ({ ctx }) =>
ctx.HEALTH_CALCULATOR.bloodPressure({
systolic: event.body.systolic,
diastolic: event.body.diastolic,
}),
);
if (classification.category === "hypertensive_crisis_range") {
const routing = await step.do("escalate", async ({ ctx }) =>
ctx.HEALTH_CALCULATOR.emergencyRouting({
country: "South Africa",
concern: "medical_emergency",
}),
);
return {
action: "escalate",
category: classification.category,
urgency: classification.urgency,
emergency: routing.emergency,
};
}
return {
action: "respond",
category: classification.category,
urgency: classification.urgency,
};
}
}PHI posture
The calculator does not read or write any user data. It accepts numeric inputs, returns categorical labels and pre-written reference strings, and emits no audit events of its own. The Loop DO still records the calling step.do row — that’s the source of truth for what your loop computed.
If your loop is calling the calculator on user-supplied vitals, the vitals themselves are PHI; treat the request body and the step return value with the same care you would any other patient data. The tool boundary itself just doesn’t widen the surface.
Country support for emergency routing
emergencyRouting currently knows about South Africa, the United Kingdom, Ireland, the United Arab Emirates, and Saudi Arabia. Unknown countries fall back to a global response that points to findahelpline.com and tells the user to call their local emergency number.
If your loop serves a country not in the list and you need specific numbers, capture the routing locally inside your loop and bypass the tool — or open an issue with the local public-resource sources you want us to add.