🧠 All Projects
📐

MedSchools.ai — Complete System Documentation (March 2026)

P3 - Low
Spec MedSchools.ai

MedSchools.ai — Complete System Documentation

Overview

MedSchools.ai is a personalized AI-powered platform helping pre-med students navigate the medical school application process. The strategic moat is personalization — using each user's actual application data (MCAT, GPA, school list, activities, personal statement) to give advice that generic AI chatbots like ChatGPT cannot.

Stack: SvelteKit 5 (Svelte 5 runes mode) + Supabase + Tailwind CSS v4 + shadcn-svelte
Repo: github.com/henryk805/medschools_ai (216+ commits)
Monorepo: Turborepo with apps/medschools-ai (main app) + apps/scraper-service
Domains: medschools.ai (prod), dev.medschools.ai (staging)
Hosting: Vercel (SvelteKit adapter-vercel)
Dev server: localhost:5180
Supabase: https://ohkdiwblocbrcfrhxeyg.supabase.co


Strategic Context

Business Goal: Mid 6-figure revenue in Year 1. First product in pipeline: MedSchools.ai → CollegeDojo.com → Real estate vertical.

Key Insight (2026-01-29): Generic RAG loses to ChatGPT on general med school Q&A. The moat is personalization — we have the user's application data that ChatGPT doesn't:

  • "Am I competitive for UCLA?" — needs user's MCAT/GPA
  • "Is my school list balanced?" — needs their school list
  • "Draft my secondary" — needs their experiences
  • "Prep me for interview" — needs their background

Architecture

Frontend (SvelteKit 5)

  • Svelte 5 runes mode — all state uses $state(), $derived(), $effect()
  • CRITICAL BUG NOTE: In runes mode, plain let declarations break reactivity. MUST use $state() for any variable that triggers UI updates.
  • UI Framework: shadcn-svelte + Tailwind CSS v4 + Lucide icons
  • Mobile-first: Bottom sheet pattern (sidebar → sheet on mobile via shadcn Sheet component)
  • Markdown rendering: marked library (for blog, guides, AI chat)
  • Rich text editor: svelte-lexical (for personal statement, secondaries)

Backend (SvelteKit API Routes)

All API routes at apps/medschools-ai/src/routes/api/:

  • Auth: /api/auth (Supabase Auth — Google, Apple, Email)
  • AI Chat: /api/chat with user context injection + school-specific RAG
  • Activities: CRUD at /api/activities
  • School List: /api/school-list (add, remove, bulk-update, status tracking)
  • Personal Statement: /api/personal-statement
  • Secondaries/Essays: /api/essay-drafts (create, update, duplicate, delete)
  • Interview: /api/interview (start, end, history, vapi-config)
  • Application Tracking: /api/application-tracking
  • Onboarding: /api/onboarding (save-profile, save-pending, restore, sync)
  • Stripe Billing: /api/stripe (checkout, portal, webhook)
  • Schools Data: /api/schools/[id], /api/v1/schools (public API for AI crawlers)
  • Admin: /api/invites (generate, validate, apply)
  • Contact: /api/contact
  • Performance: /api/performance

Server Libraries (src/lib/server/)

Module Purpose
ai/client.ts OpenAI client setup
ai/data.ts User data fetching for AI context
ai/orchestrate.ts AI chat orchestration (context injection)
ai/prompt.ts System prompt construction
rag/index.ts RAG retrieval (pgvector)
redis.ts Upstash Redis caching
stripe.ts Stripe client + helpers
billing.ts Subscription/plan logic
paywalls.ts Feature gating
auth.ts Auth helpers
admin.ts Admin role checks
audit.ts Audit logging
db/ Database connection modules
userProfile.ts Profile CRUD
cache-invalidation.ts Redis cache invalidation

Database (Supabase)

7 Schemas

Schema Purpose
med_schools School data (172 schools) — profiles, stats, interviews, curriculum, student life
content Blog posts, rankings, guides, glossary, writers. Needed manual GRANT for anon/authenticated.
user_data User profiles, activities, school lists, essay drafts, personal statements
ai Chat history, AI usage tracking
rag pgvector embeddings for school-specific knowledge (1,117 UCLA chunks including SDN/Reddit)
scraper 2,821 AI-analyzed scraped pages in scraped_pages + page_analysis_artifacts
public Auth, billing, invite codes, config

Key Tables

Table Schema Notes
medical_school med_schools 172 schools. active_flag='Y' not true. public_private='P'/'I'
med_school_interview med_schools Interview format/style/duration/delivery per school
med_school_mat_data med_schools Matriculation data. mat_type='APPLIED'/'INTERVW'/'MATRIC'
med_school_stud_life med_schools Student life data (172 schools, currently unused in code)
med_school_curriculum med_schools Curriculum data (172 schools, currently unused in code)
med_school_location med_schools Location data (172 schools, currently unused in code)
user_profiles user_data User application profiles (MCAT, GPA, demographics)
activities user_data Extracurricular activities
school_list user_data User's saved school list with status tracking
essay_drafts user_data Secondary essay drafts
personal_statements user_data PS drafts and versions
experience_ideas user_data AI-generated experience suggestions
application_tracking user_data Application status per school
interview_sessions public Vapi interview sessions
interview_trials public Free trial tracking
blog_posts content Blog content with 6 writer personas
school_artifacts content School-specific content artifacts
subscriptions public Stripe subscription data
payments public Payment records
plans public Plan definitions
usage public AI usage tracking (5 free chats/day)
invite_codes public Beta invite system
app_config public Runtime configuration
audit_log public Admin audit trail
pending_signups public Onboarding data before account creation

DB Gotchas

  • active_flag='Y' not true (string, not boolean)
  • public_private='P' (public) or 'I' (private)
  • mat_type='APPLIED'/'INTERVW'/'MATRIC' (string enums)
  • Content schema tables need explicit GRANT for anon/authenticated roles
  • Use schema('user_data') or schema('med_schools') — default schema is public

Pages & Features

Public Pages

Route Description
/ Landing page
/medical-schools School directory (paginated, 30/page, 143KB optimized)
/medical-schools/[state] State-filtered school list
/medical-schools/[state]/[slug] School profile (with sub-pages)
/medical-schools/[state]/[slug]/admissions Admissions data
/medical-schools/[state]/[slug]/interview Interview info
/medical-schools/[state]/[slug]/secondary-essays Secondary prompts
/medical-schools/rankings Rankings hub
/medical-schools/rankings/[slug] Individual ranking
/blog Blog (Medium/Substack quality design)
/blog/[slug] Blog post
/blog/writers Writer profiles (6 AI personas)
/guides How-to guides
/glossary Medical education glossary
/pricing Pricing page
/tools/application-cost-calculator Cost calculator tool
/about, /contact, /privacy, /terms Standard pages

Auth Pages

Route Description
/login Login (Google, Apple, Email)
/onboarding/* 5-step signup flow (basic-info, demographics, education, mcat, signup)
/auth/callback OAuth callback
/auth/confirm Email confirmation
/forgot-password, /reset-password Password recovery

Dashboard (Authenticated)

Route Description
/dashboard Home (profile strength, quick actions)
/dashboard/my-info Edit profile (MCAT, GPA, demographics)
/dashboard/school-list Manage school list (add/remove, in-state/out-of-state views)
/dashboard/personal-statement PS editor with AI feedback
/dashboard/secondaries Secondary essays by school
/dashboard/secondaries/[school_id] School-specific secondary drafts
/dashboard/activities Extracurricular activities manager
/dashboard/interview Interview practice (Vapi voice AI)
/dashboard/interview/session Active interview session
/dashboard/interview/feedback/[id] Post-interview feedback
/dashboard/application-tracking Track application status per school
/dashboard/performance Analytics/performance metrics
/dashboard/experience-ideas AI-generated experience suggestions
/dashboard/articles Saved/recommended articles
/dashboard/write Content creation
/dashboard/settings Account settings
/dashboard/setup/* First-time setup flow (stats, schools, activities, PS, complete)
/chat AI chat (personalized advisor)

Admin Panel

Route Description
/admin Admin dashboard
/admin/users User management
/admin/config/interview Interview module configuration
/admin/analytics Platform analytics
/dashboard/admin/review Content moderation queue

Admin access: Only henryk805@gmail.com (hardcoded + DB role check)


AI System

Personalized Chat (/chat)

  • Uses OpenAI API (GPT-4 class)
  • Context injection: Every chat message includes user's profile data (MCAT, GPA, school list, activities, PS excerpts)
  • School-specific RAG: pgvector search for school-specific knowledge (SDN forums, Reddit, official data)
  • Redis caching: User context and school data cached via Upstash Redis (60-70% DB egress reduction)
  • Usage limits: Free tier gets 5 AI chats/day (tracked in usage table)
  • Prompt construction: ai/prompt.ts builds system prompt with user context, ai/orchestrate.ts manages the full flow

Interview Practice (/dashboard/interview)

  • Voice AI: Vapi (replaced OpenAI Realtime API — much more responsive)
  • TTS Options: ElevenLabs ($0.18/min), Deepgram Aura ($0.015/min), PlayHT ($0.05/min)
  • Flow: Audio check → select school → choose interview type (MMI, Traditional, Behavioral) → voice interview → AI feedback
  • School context: Uses med_school_interview table for school-specific interview format/style
  • Free trial: 1 free 3-minute session, requires profile + PS draft
  • Config: Editable at /admin/config/interview
  • API keys: VAPI_PUBLIC_KEY (client-side), VAPI_PRIVATE_KEY (server-side)

Personal Statement Feedback

  • AI scoring rubric: docs/PS_SCORING_RUBRIC.md
  • Evaluation criteria: docs/PS_EVALUATION_CRITERIA.md
  • Endpoint: /api/ai/ps-feedback

Authentication

Three Login Methods

  1. Google Sign-In — Supabase OAuth
  2. Apple Sign-In — TOTP JWT-based (see config below)
  3. Email/Password — Supabase email auth with confirmation

Apple Sign-In Config

  • Team ID: F6G9VZB9MA
  • App ID: ai.medschools.web
  • Services ID: ai.medschools.web.client (for web auth)
  • Key ID: 95V39XR9UX
  • Domains: medschools.ai, dev.medschools.ai
  • Callback: https://ohkdiwblocbrcfrhxeyg.supabase.co/auth/v1/callback
  • JWT generation: Must use jsonwebtoken npm library (manual crypto fails with invalid_client)
  • JWT expiry: ~Aug 13, 2026. Reminder set for Aug 1, 2026.

Billing (Stripe)

Pricing Structure (Approved 2026-02-06)

Plan Price Trial Includes
Free $0 Full platform except Interview, 5 AI chats/day
Monthly $49.95/mo $1 for 3 days Full platform except Interview
Application Cycle $399 one-time (12 months) $1 for 3 days 33% savings, no auto-renew
Interview Practice (add-on) $99/mo 1 free 3-min session Requires profile + PS draft

Implementation

  • Stripe test keys in .env (pk_test_..., sk_test_...)
  • Price IDs: STRIPE_PRICE_MONTHLY, STRIPE_PRICE_CYCLE, STRIPE_PRICE_INTERVIEW
  • API routes: /api/stripe/checkout, /api/stripe/webhook, /api/stripe/portal
  • Server lib: src/lib/server/stripe.ts
  • Full PRD: BILLING_PRD.md in repo root

Remaining Work

  • Billing schema in Supabase (partially done)
  • Paywalls on features
  • Coupon codes for friends & family

Content System

Blog

  • 6 AI writer personas with avatars
  • Blog posts in content.blog_posts table
  • Categories, tags, author pages
  • Medium/Substack quality design
  • Markdown rendering via marked

Rankings

  • 5 proposed rankings: Student Life, Applicant-Friendly, Research, Value, Match
  • Quick win: Student Life (uses existing 172-school data)
  • Strategy doc: ~/company/research/medschool-rankings-strategy.md

SEO/GEO (Generative Engine Optimization)

  • JSON-LD schema markup (ItemList, Dataset, WebSite/SearchAction)
  • Answer-capsule question headers on school profiles
  • FAQ sections + Last Updated timestamps
  • /llms.txt and /llms-full.txt endpoints for AI crawlers
  • ?format=md Markdown endpoints for AI crawler access
  • Public API /api/v1/schools for programmatic access
  • robots.txt with AI crawler rules
  • Sitemap.xml

Content Pipeline

  • 6 writer personas
  • UGC system with moderation queue (/dashboard/admin/review)
  • Guides and glossary wired up
  • Strategy: 4-5 articles/week → 50K organic/month in 12 months

Caching

Upstash Redis

  • Used for: chat user context, school data, API response caching
  • Impact: 60-70% DB egress reduction. Free tier capacity: 8K → 15K visitors/month.
  • Config: UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
  • Cache invalidation: src/lib/server/cache-invalidation.ts
  • Docs: REDIS_CACHING.md

Scraper Service

Located at apps/scraper-service/:

  • Standalone TypeScript service for scraping school data
  • 2,821 AI-analyzed pages stored in scraper.scraped_pages
  • Page analysis artifacts in scraper.page_analysis_artifacts
  • Includes: schema checking, source comparison, status monitoring
  • Opportunity: Secondary Essay Strategist using scraped essay positioning strategies (largely untapped)

QA & Testing

Infrastructure (2026-02-03)

  • Playwright: Visual regression (13 screenshots) + E2E tests + mobile tests
  • Vitest: Unit tests (12)
  • GitHub Actions: CI pipeline
  • Run all: pnpm test:all
  • Config: apps/medschools-ai/playwright.config.ts
  • Test dirs: tests/visual/, tests/e2e/, tests/mobile/, tests/unit/
  • Dev site testing: TEST_DEV_SITE=true uses dev.medschools.ai instead of localhost

Deployment

Vercel

  • Main app: apps/medschools-ai
  • Adapter: @sveltejs/adapter-vercel
  • Git config: Currently using henryk805 identity (Vercel free tier workaround). Revert when upgrading to Vercel Pro.
  • Edge runtime gotcha: Module-level $env/dynamic/private access breaks. Use lazy initialization:
    function getSupabaseAdmin() { 
      if (!_client) _client = createClient(...); 
      return _client; 
    }
    

Branch Strategy (Planned)

  • main → medschools.ai (production)
  • develop → dev.medschools.ai (staging)

Environment Variables

# Supabase
VITE_SUPABASE_URL=https://ohkdiwblocbrcfrhxeyg.supabase.co
VITE_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
VITE_DATABASE_URL=...
DATABASE_URL=...

# AI
OPENAI_API_KEY=...

# Redis
UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...

# Interview (Vapi)
VAPI_PUBLIC_KEY=...  (client-side)
VAPI_PRIVATE_KEY=... (server-side)

# Billing (Stripe)
PUBLIC_STRIPE_PUBLISHABLE_KEY=...
STRIPE_SECRET_KEY=...
STRIPE_PRICE_MONTHLY=...
STRIPE_PRICE_CYCLE=...
STRIPE_PRICE_INTERVIEW=...
STRIPE_WEBHOOK_SECRET=...

Known Issues & Technical Debt

High Priority

  • Billing schema needs completion (paywalls not fully wired)
  • PKCE token verification had timing issue in email confirmation
  • Apple Sign-In JWT expires ~Aug 13, 2026 (needs renewal)
  • Coupon codes not implemented

Unused Database Assets (Opportunity)

  • med_school_stud_life (172 schools) — zero code references
  • med_school_curriculum (172 schools) — zero code references
  • med_school_location (172 schools) — zero code references
  • 2,821 scraped pages in scraper schema — mostly untapped
  • 25 empty tables that could be cleaned up
  • Full audit: ~/company/research/database-utilization-audit.md

Svelte 5 Gotchas

  • MUST use $state() for reactive variables in runes mode
  • Select.Value (bits-ui v2) doesn't auto-display labels — manual render required
  • Overflow fix: Triple layer (overflow-x-hidden + min-w-0 + max-w-full)
  • Mobile sticky toolbar: Position fixed bottom-16, z-30, pb-20 md:pb-0
  • Don't use truncate for important mobile content — let text wrap naturally

Production Launch Checklist

  • P0: Email auth fix, error pages, prod env vars
  • P1: Analytics (Plausible/GA), error monitoring (Sentry)
  • P2: Sitemap.xml generation, robots.txt updates, OG images

Key Documents

Document Location Purpose
BILLING_PRD.md Repo root Full billing spec
REDIS_CACHING.md Repo root Caching architecture
PS_SCORING_RUBRIC.md docs/ AI scoring criteria
PS_EVALUATION_CRITERIA.md docs/ PS evaluation guide
ONBOARDING_IMPLEMENTATION_SUMMARY.md docs/ Onboarding flow details
ONBOARDING_SKIP_LOGIC.md docs/ Skip logic rules
ANALYTICS_SETUP.md docs/ Analytics implementation
database-utilization-audit.md ~/company/research/ DB asset audit
seo-content-strategy.md ~/company/research/ SEO strategy (7,500 words)
medschool-rankings-strategy.md ~/company/research/ Rankings strategy
scalability-analysis.md ~/company/research/ Cost/scale projections

Undergraduate Schools Data

  • 588 undergraduate schools loaded
  • API with Redis caching
  • Typeahead search component on stats page
  • Used in onboarding flow for school selection

Created: Tue, Mar 3, 2026, 9:33 PM by bob

Updated: Tue, Mar 3, 2026, 9:33 PM

Last accessed: Mon, Mar 9, 2026, 2:19 AM

ID: abedbaec-202a-42cb-a3d3-7eaecc361d8b