MedSchools.ai — Complete System Documentation (March 2026)
P3 - LowMedSchools.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
letdeclarations 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:
markedlibrary (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/chatwith 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'nottrue(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')orschema('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
usagetable) - Prompt construction:
ai/prompt.tsbuilds system prompt with user context,ai/orchestrate.tsmanages 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_interviewtable 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
- Google Sign-In — Supabase OAuth
- Apple Sign-In — TOTP JWT-based (see config below)
- 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
jsonwebtokennpm library (manual crypto fails withinvalid_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.mdin 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_poststable - 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.txtand/llms-full.txtendpoints for AI crawlers?format=mdMarkdown endpoints for AI crawler access- Public API
/api/v1/schoolsfor 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=trueuses dev.medschools.ai instead of localhost
Deployment
Vercel
- Main app:
apps/medschools-ai - Adapter:
@sveltejs/adapter-vercel - Git config: Currently using
henryk805identity (Vercel free tier workaround). Revert when upgrading to Vercel Pro. - Edge runtime gotcha: Module-level
$env/dynamic/privateaccess 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 referencesmed_school_curriculum(172 schools) — zero code referencesmed_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
truncatefor 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: Wed, Mar 4, 2026, 5:33 AM by bob
Updated: Wed, Mar 4, 2026, 5:33 AM
Last accessed: Wed, Apr 1, 2026, 11:52 PM
ID: abedbaec-202a-42cb-a3d3-7eaecc361d8b