A daily resilience-buffer score built from wearable signals and a few lifestyle inputs, modeled on allostatic-load indices. It deliberately doesn't try to tell you "your resistance stage lasts X more days." Instead it shows a ranked breakdown of which factors are draining your buffer right now, each with one concrete thing you can do about it today.
The score is transparent on purpose: six factors, each compared against your own rolling baseline, combined with literature-anchored weights. No black box.
AlloStatus is grounded in General Adaptation Syndrome (GAS), which proposes that the body has a finite capacity to adapt to stress. Over time, chronic stress raises allostatic load — the cumulative wear and tear on the body's stress-response systems — gradually depleting that resilience reserve. By combining wearable data such as HRV, sleep, and resting heart rate with lifestyle factors like stress, exercise, diet, and social support, AlloStatus estimates how much of that adaptive capacity you have left through a single Resilience Buffer Score, and surfaces the factors draining it most.
What the Resilience Buffer Score represents. It's a 0–100 reading of how much adaptive headroom you have right now, measured against your own rolling baseline rather than a population norm. A higher score means more reserve to absorb a hard day, a poor night, or an acute stressor before it starts to cost you; a lower score means you're running closer to depletion and recovery should take priority. An average day for you lands near 50, a strong one pushes toward the 90s, a rough one toward single digits. It's a relative signal of remaining capacity — a buffer you spend and replenish — not a clinical or diagnostic measurement, and it's deliberately framed as headroom rather than a countdown.
The app runs with zero setup in demo mode — with an empty .env it boots
against a generated 60-day history, so the whole experience (score, ranking,
trend, live sliders) works without a database or sign-in.
npm install
npm run dev # http://localhost:3000Drag the lifestyle sliders on the dashboard and watch the buffer and the ranking recompute in real time — that's the actual scoring engine running in the browser.
Provide a database and Google sign-in and the app switches to the persistent
experience automatically (see lib/config.ts).
# 1. Provision Neon Postgres via the Vercel Marketplace
vercel integration add neon
# 2. Generate an auth secret and add the Google OAuth client
# (Google Cloud Console → Credentials → OAuth client ID, type Web;
# enable the Fitness API; add the /api/auth/callback/google redirect URIs)
vercel env add AUTH_SECRET
vercel env add AUTH_GOOGLE_ID
vercel env add AUTH_GOOGLE_SECRET
# 3. Pull env, create the tables, optionally seed history
vercel env pull .env.local
npm run db:push
npm run db:seed # 60 days of synthetic history for the newest userSee .env.example for the full list.
Six factors flow into one 0–100 buffer:
| Factor | Source | Weight | Direction |
|---|---|---|---|
| Heart-rate variability (RMSSD) | Wearable | 22% | higher is better |
| Sleep consistency | Wearable | 20% | steadier is better |
| Resting heart rate | Wearable | 18% | lower is better |
| Exercise | Self / wearable | 15% | higher is better |
| Diet quality (1–10) | Self | 15% | higher is better |
| Social support (1–10) | Self | 10% | higher is better |
Weights are anchored to the allostatic-load literature (Seeman, McEwen) and live
in one place — lib/scoring/weights.ts — with a
citation per factor, surfaced in the app under "How this score works."
Per factor, each day:
raw value → personal z-score → direction-correct → clamp(-3, +3) → weight
- The z-score is against your own rolling 30-day baseline. Below a week of history it falls back to population reference ranges, and an SD floor keeps a steady stretch from turning a small wobble into a dramatic score.
- Sleep consistency isn't a single-night value — it's the circular spread of your sleep midpoint over the trailing week (so a wandering bedtime scores worse).
- Weights are renormalized over whichever factors are present, so a day with only self-reported inputs still uses the full range of the score.
Composite:
buffer = 100 × sigmoid( Σ weightᵢ × clamp(zᵢ, -3, +3) )
A sigmoid keeps it smooth and bounded — an average day for you lands near 50, a great one pushes toward the 90s, a rough one toward single digits, no cliffs.
Depletion ranking answers "what's dragging the buffer down":
depletionᵢ = weightᵢ × max(0, zᵢ(your best day) − zᵢ(today))
The biggest weighted gaps between today and your best day win, because that's
where the buffer has the most to regain. The top few get a nudge from
lib/scoring/nudges.ts.
The whole engine is pure TypeScript with no I/O, so it runs identically on the
server, in the browser, and in tests. npm test covers it.
Browser (Next.js)
Dashboard: buffer ring · ranked depletion · 30-day trend · live lifestyle sliders
│ the sliders re-run the scoring engine client-side for instant what-ifs
▼
Next.js 16 App Router (Vercel)
getViewer() ─── demo? → fixed demo viewer (lib/session.ts)
└── real? → Auth.js v5 + Google
loadAnalysis() ── demo? → generated history (lib/data.ts, lib/demo)
└── real? → Drizzle query
│
lib/scoring/ pure, I/O-free engine + vitest ← the part we iterate on
lib/wearables/ WearableSource interface
├ google-fit.ts (REST + OAuth)
└ apple-health-xml.ts (export + Shortcut parser)
/api/health/google nightly cron pull
/api/health/apple export upload + iOS Shortcut ingest
▼
Neon Postgres (Drizzle)
Auth.js tables · wearable_reading · lifestyle_entry · resilience_score · user_baseline
A few choices worth calling out:
- Demo mode is a first-class path, not a mock. A single seam
(
getViewer/loadAnalysis) decides between generated and real data, so the app is runnable from a fresh clone and every UI state is reachable without provisioning anything. The same generator backsnpm run db:seed. - The scoring engine is isolated and pure. It's the part we expect to tune most, so it has no React, no database, and no network — just data in, score out — which makes it cheap to test and safe to change.
WearableSourceis an interface. Every integration returns the sameDailyReadingshape, so swapping Google Fit for an aggregator later doesn't touch anything downstream.resilience_scoreis a cache, not source of truth. Change a weight, drop the table, recompute from the raw readings.
app/
(auth)/login/ Google sign-in (real mode)
api/auth/ Auth.js handlers
api/health/google/ nightly Google Fit cron
api/health/apple/ Apple export upload + Shortcut ingest
dashboard/ the dashboard (Server Component)
actions.ts saveLifestyle Server Action
components/ buffer ring, depletion list, factor bars, trend, today panel
lib/
scoring/ pure scoring engine (+ tests in tests/scoring)
wearables/ WearableSource + Google Fit + Apple parsers
demo/generate.ts deterministic synthetic history
config.ts demo-vs-real detection
session.ts getViewer()
data.ts loadAnalysis()
db/ Drizzle schema + lazy client
scripts/seed.ts synthetic backfill
Next.js 16 (App Router, Turbopack) · React 19 · TypeScript · Tailwind v4 · Auth.js v5 (Google) · Drizzle ORM + Neon Postgres · Zod · Vitest.
- Apple Health has no web API. The Apple path is a user-uploaded
export.xmlplus an optional iOS Shortcut that POSTs daily JSON to a per-user token. A native companion is out of scope for v1. - Google Fit's REST API is deprecated (sunsets 2026) and never exposed RMSSD HRV. That's exactly why the wearable layer is behind an interface — the Apple path or an aggregator covers HRV without changes elsewhere.