DEV Community

Cover image for VitalSense: Real-Time Patient Triage on Redis (Streams • Search • JSON • TimeSeries)
devin nicholson
devin nicholson

Posted on

VitalSense: Real-Time Patient Triage on Redis (Streams • Search • JSON • TimeSeries)

Redis AI Challenge: Beyond the Cache

This is a submission for the Redis AI Challenge: Beyond the Cache.

What I Built

A real-time patient triage dashboard that uses Redis 8 as the primary data platform. It ingests vitals (HR/SpO₂/BP) via Redis Streams, persists the latest state per patient in Hashes, indexes snapshots with RediSearch for instant querying, publishes alerts with Pub/Sub, stores adjustable alert thresholds in RedisJSON, and renders 24-hour trends from RedisTimeSeries. The React/Next.js UI is real-time (WebSocket), offline-first (IndexedDB hydration), supports threshold configuration (persisted in Redis), and provides a drill-down panel with a per-patient sparkline (via TS.RANGE).

Key properties:

  • Primary DB: No external database; Redis is the system of record for runtime state + settings.
  • Search & Rules: RediSearch powers numeric queries for “critical” patients (low SpO₂ / high HR), pulled by a watcher that publishes alerts.
  • Streams + Pub/Sub: Streams for ingestion/fan-out; Pub/Sub for real-time alert fan-out to the browser.
  • Time series: Per-patient SpO₂ time series with 24h retention for drill-down trends.
  • UX: Progressive skeletons, dark mode, drill-down, offline cache, threshold modal (live-reconfigures the watcher).

Demo

Github:

VitalSense Hackathon Project

A real-time health data pipeline using Redis Streams, RedisJSON, RediSearch Pub/Sub, and a beautiful Tailwind/Next.js dashboard.


Prerequisites

  • Node.js v16+

  • Python 3 (for the static dashboard, optional)

  • Redis 8 instance with modules:

    • RedisJSON
    • Redis Streams
    • RediSearch
    • (Optional) RedisAI
  • Environment variable

    export REDIS_URL="redis://default:<PASSWORD>@<HOST>:<PORT>"
    Enter fullscreen mode Exit fullscreen mode

1. Vitals Generator

Emits synthetic patient vitals into patients:stream.

cd vital-sense-generator
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

You’ll see logs like:

Emitted <streamId> { patientId: 'P01', hr: 82, spo2: 95, sys: 120, dia: 80 }

2. Stream Consumer

Reads from patients:stream, writes snapshots to patient:<ID>:snapshot, and acknowledges.

cd vital-sense-consumer
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

You’ll see:

✔ Consumer group "consumer-group" created on "patients:stream"
▶ Listening for new entries…
🗂 Got 1 msg(s) from stream "patients:stream"
✅ Processed <streamId> → patient:P01:snapshot { hr: '82', spo2: '95', sys: '120', dia: '80' }

3. Index Creation (RediSearch)

Drop & recreate the snapshots index to cover all…


Demo:

How I Used Redis 8

Data ingestion (Streams)

  • XADD patients:stream * id P01 heart_rate 82 spo2 95 bp_systolic 120 bp_diastolic 80
  • Consumer group (XGROUP CREATE / XREADGROUP) provides reliable processing and back-pressure.

Primary state (Hashes)

  • Latest snapshot per patient in Hash:
  • HSET patient:P01:snapshot hr 82 spo2 95 sys 120 dia 80 timestamp <iso>

No external DB; Redis holds the live source of truth.

Indexed queries (RediSearch)

  • FT.CREATE idx:snapshots ON HASH PREFIX 1 "patient:" SCHEMA hr NUMERIC SORTABLE spo2 NUMERIC SORTABLE sys NUMERIC dia NUMERIC timestamp TAG
  • Query examples used by the watcher/UI:
  • Low oxygen: @spo2:[-inf 92]
  • High HR: @hr:[120 +inf]
  • Combined: (@spo2:[-inf 92]) | (@hr:[120 +inf])
  • Event fan-out (Pub/Sub)
  • Watcher runs a periodic RediSearch query, publishes critical hits to alerts:critical.
  • A Node WS proxy subscribes and forwards JSON to browser WebSockets.

Time series analytics (RedisTimeSeries)

Per-patient SpO₂:

  • TS.CREATE ts:patient:Pxx:spo2 RETENTION 86400000 LABELS patient Pxx
  • TS.ADD ts:patient:Pxx:spo2 <ts> <spo2>
  • Drill-down: TS.RANGE ts:patient:Pxx:spo2 <now-24h> <now>
  • Dynamic configuration (RedisJSON)

Alert thresholds live in JSON:

  • JSON.SET settings:thresholds . '{"spo2":92,"hr":100}'
  • Next API GET/PATCH /api/settings/thresholds reads/writes this key.
  • Watcher reloads thresholds each loop → instant effect.

Team Submissions:
Devin Nicholson

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.