Inspiration
Nursing homes are where the people most likely to decline quietly get the fewest eyes at night. One caregiver can cover 15 or more residents overnight, walking fixed rounds every two hours. About half of residents fall each year, and in the elderly an infection often shows up as confusion and restlessness, not a fever.
Fixed alarm thresholds fail both ways. "HR > 100" never fires for an 87-year-old whose normal is 62, and the same thresholds ring all night for residents with COPD, beta-blockers or AFib, so staff learn to tune them out. We wanted something that judges each resident against their own normal and tells one tired aide where to walk next.
What it does
Nightwatch watches every resident of a nursing home around the clock, learns each resident's personal "normal," and gives the night shift a live, ranked list of who to check next and why.
- Rounds queue: every resident is ranked by a 0–100 priority score with a tier (CRITICAL / HIGH / WATCH / STABLE). Each card shows the top 3 reasons, computed deterministically from the data (e.g. "HR 84 (her expected 67–79)", "5 bed exits (usual ≤ 2)").
- Resident page: charts against the resident's own baseline band, Snowflake's expected range and anomaly dots, a forecast line, a NEWS2 table, and a bedside assessment from Grokbot in Snowflake Cortex that quotes the resident's care plan.
- Live hardware: a Polar Verity Sense band streams real heart rate through Web Bluetooth, and an iPhone under the mattress is the bed sensor. A high-fall-risk resident who gets out of bed at night is #1 CRITICAL in about 4 seconds.
- Replay: last night's data runs back through the live pipeline at 30–120× speed.
- Nurse notes (voice or text), an SBAR shift handoff, Ask the facility in plain English, and an Under the hood page where every number is a live query.
It's decision support that catches decline earlier. It doesn't diagnose and doesn't replace staff. All data is synthetic.
The priority score blends six signals:
$$ \text{score} = 100\,\big(0.30\,P_{\text{news}} + 0.25\,P_{\text{anom}} + 0.15\,P_{\text{trend}} + 0.10\,P_{\text{behav}} + 0.12\,P_{\text{concern}} + 0.08\,P_{\text{overdue}}\big) $$
with hard rules overriding it (a suspected fall or extreme vitals → 100, a night bed exit on fall precautions → 90), and each vital compared with the resident's own baseline:
$$ z = \frac{x - p_{50}}{(p_{95} - p_{05})/3.29} $$
How we built it
Nightwatch runs on two speeds, and each sponsor does the job only it can do.
Fast tier: Tiger Data, in seconds ("what's happening now")
- Hypertables for vitals, events and priority snapshots: about 2M rows/day from 120 residents × 10 metrics every 5 s, plus live device rows every second.
- Hierarchical continuous aggregates (
vitals_1m → vitals_15m → vitals_1h) with real-time aggregation feed the charts, the evaluator and Snowflake. - Columnstore compression (segmented by resident) makes history 14.8× smaller, with retention and refresh policies.
- An hourly
add_jobusespercentile_agg,approx_percentile,stats_aggandstate_aggto learn each resident's baseline per 4-hour block, their normal bed exits and their longest still period. heartbeat_aggtracks device liveness (a band left off is a task, never an alarm);time_bucket_gapfill+locfkeep the charts gap-free.- Every 2 s, one pipelined batch scores all 120 residents in about 0.4 s: hard rules, partial NEWS2, baseline z-scores and alarm-fatigue controls (hysteresis, persistence, suppression after a check).
Deep tier: Snowflake, in minutes ("what it means and what comes next")
- Every minute, closed 15-minute buckets sync to Snowflake with
write_pandas. - Five multi-series
SNOWFLAKE.ML.ANOMALY_DETECTIONmodels (HR, RR, SpO₂, skin temp, night movement) × 120 series judge each resident against their own learned normal. Last night is held out of training, so the replay is out-of-sample. SNOWFLAKE.ML.FORECASTwarns before a NEWS2 danger threshold is crossed ("RR forecast to cross 25 by ~04:15").- 120 care-plan PDFs →
AI_PARSE_DOCUMENT→SPLIT_TEXT_RECURSIVE_CHARACTER→ a Cortex Search service with 1,200 chunks. - Grokbot in Cortex
COMPLETEwrites each bedside assessment against a JSON schema. Validation drops any citation not in the retrieved care plan and softens diagnostic wording. AI_TRANSCRIBE+AI_EXTRACTread nurse notes,AI_AGGwrites the handoff, and Cortex Analyst answers "Ask the facility".- REST APIs with key-pair JWT auth: Cortex Search, Cortex Analyst, the SQL API (
/api/v2/statements) and Cortex Inference. Every call is counted with its latency.
How we worked: spec-first with AI coding agents
We wrote the spec before any code. SPEC.md is the single source of truth (data model, scoring formula, the scripted night), AGENTS.md holds the hard rules every agent must follow (sponsor separation, no future timestamps, every scoring function takes as_of, the deterministic core must be tested), and BUILD_PROMPTS.md splits the build into phases, each with acceptance checks. We ran the phases one at a time with AI coding agents ([tools you used]), reviewed each phase against its checks, and kept 430 backend tests green so the agents couldn't quietly break the scoring.
Graceful failure and stack
If Snowflake is slow or down, the fast tier and UI keep working: scoring falls back to Tiger z-scores, AI cards fall back to a deterministic template that still cites the care plan, and notes fall back to keyword reading.
Stack: FastAPI + psycopg 3, Next.js + TypeScript + Tailwind + shadcn/ui + ECharts + Zustand, Caddy as a single-origin proxy, and Docker Compose on Vultr. Coded using Cursor and Grok Bot to automate prompting and integration management.
Challenges we ran into
- Snowflake's warehouse was the bottleneck. Five parallel
DETECT_ANOMALIESruns saturated the XSMALL warehouse, and Analyst's SQL queued 46 s and timed out. We capped detection concurrency and moved ML training and forecasting to their own warehouse. - Cortex Inference REST returned 403 on my trial account for every model. The client tries it first, then runs the same messages through
CORTEX.COMPLETEover the SQL REST API, retrying the endpoint every 30 min. - Training is slow: five multi-series models took about 67 min. So replay never waits on Snowflake: a "prepare" step precomputes anomalies, forecasts and Cortex cards and reveals each one only when its timestamp is reached.
- Time: Tiger's policies are relative to
now(), so we never write future timestamps. Every scoring function takes anas_oftime, which lets the same pipeline run live and in replay. - iPhone as a bed sensor: motion sensors need HTTPS, a user tap to grant permission, and a screen wake lock to keep sending.
Accomplishments that I'm proud of
- 903 vs 22: on the replay night, fixed thresholds fired 903 alarms across 110 residents; Nightwatch raised 22 alerts for 4 residents. Margaret Kim (COPD): 18 fixed alarms, 0 Nightwatch alerts.
- Nightwatch had Rosa Alvarez at sustained HIGH by 01:34 and #1 at 02:14, hours before the first fixed vital-sign alarm at 04:02. The aide went because she was #1, and that's how her new confusion was found.
- Real hardware: We get out of a real bed with a real band on and I'm #1 CRITICAL in ~4 s, with my fall precautions cited from the care plan.
- Live numbers: 33.7M rows, 14.8× compression, and a week-long query ~43× faster from the continuous aggregate than from raw rows.
What we learned
- Personal baselines beat fixed thresholds in both directions: more real decline caught, far fewer false alarms.
- Splitting work by time scale (seconds vs minutes) made the architecture simpler and the demo clearer.
- With LLMs in a clinical setting, the guardrails matter more than the prompt: deterministic factors, validated citations and decision-support wording.
- A precise spec and tested rules are what make AI coding agents safe to use at hackathon speed.
What's next for Nightwatch
- A plain-language family view of the night, with no raw vitals.
- Polar accelerometer and beat-to-beat data for real wrist motion and HRV.
- A suggested round route that orders the queue by walking distance.
- A pilot with real facility staff to check the score against their judgment.
Built With
- caddy
- cortex-analyst
- cortex-search
- cursor
- docker
- echarts
- fastapi
- grok
- grokbot
- ios
- next.js
- psycopg
- python
- shadcn-ui
- snowflake
- snowflake-cortex
- snowflake-ml
- spacexsi
- tailwind
- tiger-data
- timescaledb
- typescrit
- web-bluetooth
- websockets
- zustand