This file provides project context for AI assistants working with Protocol Guide.
Protocol Guide is a national EMS protocol library with jurisdiction-aware RAG (Retrieval-Augmented Generation). It ingests clinical protocols from Local Emergency Medical Services Agencies (LEMSAs), state EMS offices, and regional medical directors across the United States, then serves them through a vector-search pipeline scoped to the user's jurisdiction.
Core constraint: EMS protocols are not national. They are set by local medical authorities, and a paramedic in one county follows different standing orders than one in the adjacent county. Every search must resolve the user's jurisdiction and scope results accordingly.
Key Value Proposition: Find the right protocol for your agency in 2 seconds.
Jurisdiction Hierarchy: United States → State (53) → LEMSA/Agency (2,738) → County → Protocol Chunks (58,000+)
Critical Concepts:
- LEMSA — Local EMS Agency that publishes clinical protocols (
manus_agenciestable) - County-Agency Mapping — Bridge table resolving county → agency_id for scoped search
- Protocol Chunk — 400-1800 char vector-embedded segment (
manus_protocol_chunks, 1536 dim) - Jurisdiction-Scoped Search — User county →
county_agency_mapping→ scoped vector search → Claude RAG with agency's protocols only
EMS Abbreviations: Query normalizer expands 150+ terms (VF→Ventricular Fibrillation, STEMI→ST-Elevation Myocardial Infarction, RSI→Rapid Sequence Intubation, etc.)
Full details: docs/EMS_ARCHITECTURE.md — Jurisdiction model, ingestion pipeline (7 steps: PDF discovery → download → text extraction → chunking → embedding → database insert), search pipeline, complete abbreviation reference.
Frontend: Expo 54 + React Native Web + Expo Router + NativeWind (Tailwind)
Backend: Express + tRPC 11.7 (end-to-end typesafe)
Database: Supabase PostgreSQL + pgvector (vector search) + Drizzle ORM
AI: Claude Haiku 4.5 / Sonnet 4.6 (Anthropic) + Voyage AI embeddings (voyage-large-2, 1536 dim)
Auth: Supabase Auth (Google/Apple OAuth)
Payments: Stripe subscriptions
Hosting: Netlify (auto-deploy from main) + PWA with offline caching
Caching: Upstash Redis (query cache, rate limits)
Full architecture diagrams: docs/BACKEND.md, docs/FRONTEND.md, docs/DATABASE.md
Frontend (app/):
_layout.tsx — Root layout + providers | (tabs)/index.tsx — Search interface | (tabs)/profile.tsx — User profile | admin/*.tsx — Admin dashboard | oauth/*.tsx — OAuth callbacks
Backend (server/):
_core/index.ts — Express entry | _core/trpc.ts — tRPC config | _core/claude.ts — Claude SDK + model routing | _core/embeddings/ — Voyage AI + pgvector | _core/rag/ — RAG pipeline | _core/ems-query-normalizer.ts — 150+ abbreviation expansion | routers/search.ts — Semantic search | routers/query.ts — Protocol queries | routers/auth.ts — Authentication | routers/user.ts — User profile | routers/subscription.ts — Stripe | routers/voice.ts — Voice transcription
Ingestion (scripts/):
ingest-ca-protocols.ts — CA LEMSA orchestrator | lib/pdf-url-discoverer.ts — PDF crawler | lib/pdf-downloader.ts — PDF cache | lib/protocol-extractor.ts — Text extraction
Hooks (hooks/):
use-auth.ts | use-protocol-search.ts | use-voice-input.ts | use-offline-cache.ts | use-favorites.ts
Libraries (lib/):
trpc.ts | supabase.ts | offline-cache.ts | tier-helpers.ts | ems-terminology.ts
Database (drizzle/):
schema.ts — Table definitions | migrations/*.sql — Auto-generated migrations
Full file reference: docs/BACKEND.md, docs/FRONTEND.md
pnpm install
pnpm dev # Server :3001 + Web :8081
pnpm dev:server # Backend only
pnpm dev:metro # Frontend onlypnpm db:push # Generate and apply migrations
npx drizzle-kit studio # View database in Studiopnpm test # Unit tests (Vitest)
pnpm test:coverage # With coverage
pnpm test:integration # Integration tests (requires DB)
pnpm test:e2e # E2E tests (Playwright)
pnpm test:e2e:visual # Visual regressionpnpm check # TypeScript type checking
pnpm lint # ESLint
pnpm format # Prettierpnpm build # Build server bundle
pnpm build:web # Build PWA for deploymentRequired variables (see .env.example):
# Core
DATABASE_URL=postgresql://...
NODE_ENV=development
# Supabase (Auth + Vector Search for protocol chunks)
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# AI/ML
ANTHROPIC_API_KEY=sk-ant-... # Claude Haiku 4.5 / Sonnet 4.6
VOYAGE_API_KEY=voyage-... # Embeddings (voyage-large-2, 1536 dim)
# Payments
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Caching (optional)
UPSTASH_REDIS_REST_URL=https://...
UPSTASH_REDIS_REST_TOKEN=...- 500 lines maximum per file
- Split large files into focused modules
- Explicit types everywhere (no
any) - Use Zod for runtime validation
- Prefer interfaces over type aliases for objects
- Files: kebab-case (
use-auth.ts,ems-query-normalizer.ts) - Components: PascalCase (
SearchBar.tsx,AgencyModal.tsx) - Functions: camelCase (
handleSearch,normalizeEmsQuery) - Constants: UPPER_SNAKE_CASE (
MAX_RESULTS,CHUNK_CONFIG) - Types/Interfaces: PascalCase (
UserTier,LEMSAConfig,NormalizedQuery)
publicProcedure— No auth requiredpublicRateLimitedProcedure— Public with rate limitingprotectedProcedure— Requires authenticationcsrfProtectedProcedure— Requires CSRF tokenrateLimitedProcedure— Auth + rate limiting
- Use
TRPCErrorfor API errors - Always include error codes
- Log errors with context
Claude Code agents for this project run on Claude Opus 4.6 for code analysis, planning, and implementation.
Free tier → Claude Haiku 4.5 only (~$0.0003/query)
Pro simple → Claude Haiku 4.5 (~$0.0003/query)
Pro complex → Claude Sonnet 4.6 (~$0.003/query)
Updated 2026-02-17 for Sonnet 4.6 release
Complexity triggers (routes Pro users to Sonnet): differential diagnosis, multi-condition interactions, pediatric edge cases, explanation requests, atypical presentations.
- Query Normalization — Expand 150+ EMS abbreviations, classify intent, detect emergent indicators
- County→Agency Resolution —
county_agency_mappingresolves user's county to agency_id - Embedding Generation — Voyage AI
voyage-large-2(1536 dim) - Scoped Vector Search — pgvector cosine similarity WHERE agency_id = X
- Re-ranking — Term frequency, synonym matching, context boost (+15 agency, +5 state)
- Response Generation — Claude RAG with retrieval-only constraint, mandatory
[Protocol #XXX]citations
Safety-Critical Queries:
- Medication dosing → Multi-query fusion (3 variations, RRF merge), threshold 0.38
- Contraindication checks → Highest priority (100), enhanced accuracy mode
- Emergent patterns (cardiac arrest, anaphylaxis, airway obstruction) → Always enhanced processing
- Pediatric + medication → Weight-based dosing alerts, enhanced accuracy regardless of tier
Full LLM architecture: docs/EMS_ARCHITECTURE.md, docs/BACKEND.md
Production (Netlify): Auto-deploys from main branch, Netlify Functions for serverless backend, edge functions for auth middleware
PWA Features: Service worker caching for offline protocol access, install prompt for home screen, standalone mode, background sync
Full deployment guide: DEPLOYMENT.md
Unit Tests (Vitest): Server utilities (query normalizer, chunker, scoring), React hooks (search, auth, offline), business logic in lib/
Integration Tests: Database operations (county→agency mapping, protocol search), tRPC router endpoints, authentication flows
E2E Tests (Playwright): Critical user journeys (search, jurisdiction selection, subscription), search functionality (jurisdiction-scoped results), visual regression tests
"Database connection failed"
- Check
DATABASE_URLin.env - Run
pnpm db:pushto apply migrations
"Search returning no results"
- Verify
VOYAGE_API_KEYis set - Check Supabase pgvector extension is enabled
- Verify the user's county has a
county_agency_mappingrow - Check
manus_protocol_chunkshas data for the resolvedagency_id
"Wrong jurisdiction protocols"
- Check
county_agency_mappingmaps the county to the correctagency_id - Verify
manus_agencieshas the correctstate_codeandname - Multi-county LEMSAs: all counties in a LEMSA should map to the same
agency_id
"Claude API error"
- Verify
ANTHROPIC_API_KEYis set - Check API rate limits
"Auth not working"
- Clear browser cookies
- Check Supabase URL/keys
- Verify OAuth redirect URLs in Supabase dashboard
- docs/EMS_ARCHITECTURE.md — Jurisdiction model, ingestion pipeline, search pipeline, EMS terminology
- docs/BACKEND.md — Backend architecture, tRPC routers, AI model usage
- docs/FRONTEND.md — Frontend architecture, component patterns, state management
- docs/DATABASE.md — Database schema, migrations, query patterns
- docs/ai/AI-CHEATSHEET.md — Quick reference for common tasks
- CONTRIBUTING.md — Contribution guidelines
- DEPLOYMENT.md — Deployment procedures
- docs/ — Full documentation suite