Alert Engine

Real-time threshold monitoring with configurable rules.

How It Works

Every data point ingested into VitalClaw is evaluated against all active alert rules. When a threshold is breached, a HealthAlert is generated, stored, and emitted via the event bus.

Default Alert Rules

VitalClaw ships with these pre-configured rules:

MetricConditionThresholdSeverityCooldown
Heart Rateabove120 bpmwarning10 min
Heart Ratebelow40 bpmcritical10 min
Blood Oxygenbelow90%critical5 min
Blood Glucoseabove250 mg/dLwarning30 min
Blood Glucosebelow70 mg/dLcritical15 min
Body Temperatureabove38.5°Cwarning60 min

Custom Rules

engine.addAlertRule({
  id: "custom-rhr-high",
  metric: "resting_heart_rate",
  condition: "above",       // "above" | "below" | "outside_range"
  threshold: 90,
  severity: "warning",       // "info" | "warning" | "critical"
  message: "Resting HR elevated at {value} bpm (threshold: {threshold}).",
  cooldownMs: 600_000,       // 10 minutes
});

// Range-based rule
engine.addAlertRule({
  id: "custom-bp-range",
  metric: "blood_pressure",
  condition: "outside_range",
  threshold: 90,          // low end
  thresholdHigh: 140,      // high end
  severity: "warning",
  message: "Blood pressure out of range: {value} mmHg.",
});

Alert Lifecycle

// 1. Triggered automatically on ingest
const alerts = engine.ingest(dataPoints);

// 2. Listen for alerts
engine.events.on("alert:triggered", (event) => {
  console.log(event.alert.severity, event.alert.message);
});

// 3. Query unacknowledged alerts
const pending = engine.getAlerts(true);

// 4. Acknowledge
engine.acknowledgeAlert(pending[0].id);

// 5. Resolved event fires
engine.events.on("alert:resolved", (event) => {
  console.log("Resolved:", event.alertId);
});

HealthAlert Schema

interface HealthAlert {
  id: string;
  userId: string;
  severity: "info" | "warning" | "critical";
  category: "anomaly" | "threshold" | "trend" | "reminder" | "goal";
  metric: MetricType;
  title: string;
  message: string;
  value: number;
  threshold?: number;
  timestamp: string;
  acknowledged: boolean;
}