Multi-Tenant Health SaaS

OOTify — Campus Mental Health

A tenant-per-school mental health companion for college students. Every partner campus gets its own branded subdomain, a resource catalog curated under its own counseling center's authority, and aggregate analytics on how its student body is actually doing — without ever seeing another school's data.

121
Commits in 5 Months
325
Pilot Resources Seeded
67
Test & E2E Spec Modules
80%
Enforced Coverage Floor

OOTify gives each partner campus its own tenant — branded subdomain, its own curated resource catalog, and its own analytics — while students get a low-friction daily mood check-in, two validated clinical screeners (GAD-7 and PHQ-9), and personalized recommendations routed only to resources their own school has approved.

It was built to close one narrow gap: the long quiet stretch between "something feels off" and "I finally got on the counseling center waitlist." The 2024–2025 Healthy Minds Study of 84,000+ students found 37% with moderate-to-severe depressive symptoms and 32% with moderate-to-severe anxiety, while counseling-center utilization has grown roughly 5× faster than enrollment and average caseloads sit near 120 students per full-time counselor.

OOTify deliberately does not attempt teletherapy, therapist matching, or peer chat. It solves four problems instead: self-assessment, resource discovery, continuity of a student's own 90-day trend, and aggregate visibility for the counseling center director defending a staffing budget. The platform is in live beta with a University of California campus as the pilot — a production tenant seeded with 325 real campus resources across 15 schools, driven by a structured tester-feedback cycle where stakeholders file issues, each is root-caused against the actual code, and fixes ship in tracked phases with dev-update notes back to the client.

Backend
FastAPI 0.115 / Python 3.12
Async REST API on Uvicorn
Database
MongoDB / Motor 3.6
Async driver, Pydantic v2 models
Frontend
Next.js 15 / React 19
App Router, TypeScript 5.6
Interface
Tailwind + shadcn/ui
Radix, framer-motion, Recharts
Data Layer
TanStack Query v5
react-hook-form + Zod validation
Security
argon2id + pyotp
PIN hashing, TOTP MFA (RFC 6238)
Testing
pytest / Playwright / Vitest
MSW mocks, axe-core a11y
Infrastructure
Azure App Service + GHCR
Docker, push-to-deploy webhook
Quality
ruff + mypy (strict)
GitHub Actions CI, structlog
01

Per-School Multi-Tenancy

Strict data isolation with the tenant resolved from the Host subdomain in production and from a header or query param in development — all through one code path with a TTL-cached lookup. Dedicated tenant-isolation suites run on both the backend and end-to-end.

02

Safety-First Clinical Triage

GAD-7 and PHQ-9 are scored into bands, then mapped to a five-tier action ladder (acute → elevated → moderate → mild → wellness). PHQ-9 item 9 — self-harm ideation — overrides all band logic straight to acute, and when the two instruments disagree the more severe tier wins.

03

Personalized Recommendation Engine

Ranks resources by targeting hits across screener bands, recent mood factors on a 7-entry lookback, sustained low-mood trend, and TIPI/Big-Five personality fit. Every card explains why it was suggested — and a student in acute crisis sees only crisis resources, with wellness content locked away until they are safe.

04

Three-Role Application

Mobile-first students, desktop-first tenant admins with full resource authoring, tags, and per-student read-only profiles, plus a hidden 2FA-gated super admin route for provisioning new campus tenants.

05

Privacy-Preserving Analytics

A cohort explorer and clinical reports with small-cohort suppression — counts below the floor collapse to a sentinel rather than leaking individuals — alongside wellness-tier distributions, DAU/WAU, and a per-resource favorability heatmap.

06

Layered Security Stack

argon2id PIN hashing, role-scoped server-side session cookies that let student, admin, and superadmin sessions coexist without clobbering each other, TOTP MFA with QR enrollment and email-OTP recovery, per-email and per-IP login rate limiting, and a full audit log.

07

WYSIWYG Resource Authoring

Three explicit presentation types — paginated in-app wizard, external link, or info card — with per-tenant private image uploads served through a tenant-locked endpoint and a live preview that renders exactly what the student will see.

08

Reward-Token Economy

Atomic, idempotent token awards for check-ins, screeners, and first-time resource completions. Crisis resources never award tokens, because help-seeking is not gamified.

Tenant context is resolved once at the edge and threaded through every layer below it.

Client
Student App (mobile-first)
Admin Console
Super Admin (2FA)
Edge
Tenant Resolution Middleware
TTL Tenant Cache
Rate Limiter
Request-ID / structlog
API
FastAPI Routers
Auth + MFA
Mood / Screener
Resources / Uploads
Admin / Super Admin
Services
Triage Engine
Resource Recommender
TIPI Personality Scoring
Rewards Ledger
Cohort Analytics
Audit / Email
Data
MongoDB via Motor
Tenant-Scoped Repos
Audit Log
Private Image Store

Clinically consequential logic is pure and testable. The backend separates routers → services → repos over Motor/MongoDB, and the decisions that actually matter — triage banding, TIPI scoring, small-cohort suppression, recommendation ranking — are deliberately factored into pure functions in the service layer so they can be unit-tested without a database. The triage module carries an explicit note in code that the band→tier mapping is a starting point requiring clinical review before it drives patient-facing routing, which is exactly the boundary a mental-health product should make visible rather than bury.

Multi-tenancy as a single code path. Rather than branching on environment, one ASGI middleware handles production Host subdomains, dev headers, and query params identically, backed by a short-lived TTL cache so tenant lookups don't hit Mongo on every request. The middleware never raises; routes that need tenant context opt in through a FastAPI dependency. A reusable deep-clone script provisions a production tenant from a test one, carrying resources, students, admins, tags, and history.

Testing that matches the risk profile. 38 backend test modules — roughly 9,300 lines of tests against 12,600 lines of application code — with an 80% coverage floor enforced in CI, plus 29 Playwright specs organized by risk. A dedicated safety/ directory covers acute-tier routing, SOS, crisis config, MFA, and clinical disclosure, alongside auth, student, admin, superadmin, and tenant-scope suites that can be pointed at either deployed environment.

services/triage.py
# NOTE: this band -> tier mapping is a starting point and requires
# clinical review before it drives patient-facing routing.

def resolve_tier(phq9: PHQ9Result, gad7: GAD7Result) -> Tier:
    # Item 9 (self-harm ideation) overrides all band logic.
    if phq9.item_9 > 0:
        return Tier.ACUTE

    candidates = (TIER_BY_BAND[phq9.band], TIER_BY_BAND[gad7.band])

    # When the instruments disagree, the more severe tier wins.
    return max(candidates, key=SEVERITY.index)