diff --git a/medcards-ai/PRODUCT_STRATEGY.md b/medcards-ai/PRODUCT_STRATEGY.md new file mode 100644 index 00000000..d50ed88c --- /dev/null +++ b/medcards-ai/PRODUCT_STRATEGY.md @@ -0,0 +1,716 @@ +# MEDCARDS.AI - Product Strategy & Network Effects Architecture + +## ๐ŸŽฏ Product Vision: From Tool to Platform + +**Current State**: Individual study tool (MVP) +**Future State**: Network-powered medical education platform with defensible moats + +--- + +## ๐Ÿ”„ Network Effects Strategy + +### 1. **Data Network Effect** (Primary Moat) + +#### The Flywheel +``` +More Students โ†’ More Interactions โ†’ Better AI Predictions โ†’ +Better Learning Outcomes โ†’ More Students โ†’ ... +``` + +**Implementation:** + +Every interaction improves the system for ALL users: + +```typescript +// Database additions to existing schema +CREATE TABLE case_difficulty_calibration ( + case_id UUID REFERENCES clinical_cases(id), + actual_difficulty_score NUMERIC, -- Calculated from real user performance + expected_vs_actual_delta NUMERIC, -- How off were we? + sample_size INTEGER, + confidence_level NUMERIC, + updated_at TIMESTAMP +); + +CREATE TABLE prediction_model_versions ( + id UUID PRIMARY KEY, + version TEXT, + training_data_size INTEGER, + accuracy_metrics JSONB, + deployed_at TIMESTAMP, + performance_improvement_vs_previous NUMERIC +); +``` + +**Value Proposition:** +- First 1,000 users: AI accuracy ~70% +- At 10,000 users: AI accuracy ~85% +- At 100,000 users: AI accuracy ~95% + +**โ†’ Late entrants can never match prediction quality without the data** + +--- + +### 2. **Content Network Effect** (Secondary Moat) + +#### Community-Contributed Cases + +**Phase 1: Curated Contributions** +```typescript +CREATE TABLE community_cases ( + id UUID PRIMARY KEY, + created_by_user_id UUID REFERENCES users(id), + case_content JSONB, -- Same structure as clinical_cases + status TEXT CHECK (status IN ('draft', 'submitted', 'under_review', 'approved', 'rejected')), + community_rating NUMERIC, + times_used INTEGER DEFAULT 0, + success_rate NUMERIC, + curator_notes TEXT, + approved_by_user_id UUID REFERENCES users(id), + approved_at TIMESTAMP, + earnings_generated NUMERIC DEFAULT 0 -- For revenue sharing +); + +CREATE TABLE case_reviews ( + id UUID PRIMARY KEY, + case_id UUID REFERENCES community_cases(id), + reviewer_user_id UUID REFERENCES users(id), + clinical_accuracy_score INTEGER CHECK (1 <= score <= 5), + educational_value_score INTEGER CHECK (1 <= score <= 5), + review_text TEXT, + is_expert_review BOOLEAN DEFAULT false -- Verified doctors/professors +); +``` + +**Incentive Mechanics:** +- Users who create approved cases earn credits +- Credits = access to premium features OR cash payout +- Top contributors get "Verified Educator" badge +- Cases that perform well (high success in teaching) earn more + +**Network Effect:** +- 1,000 users โ†’ ~50 quality cases/month +- 10,000 users โ†’ ~500 quality cases/month +- 100,000 users โ†’ ~5,000 quality cases/month + +**โ†’ Library becomes impossible to replicate** + +--- + +### 3. **Social Learning Network Effect** + +#### Study Groups & Peer Competition + +```typescript +CREATE TABLE study_groups ( + id UUID PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + created_by_user_id UUID REFERENCES users(id), + is_public BOOLEAN DEFAULT false, + member_limit INTEGER, + created_at TIMESTAMP, + + -- Group configuration + focus_specialties TEXT[], + target_exam TEXT, -- "REVALIDA 2025", "USP Clรญnica Mรฉdica", etc. + study_schedule JSONB, -- When they study together + + -- Group stats + total_cases_solved INTEGER DEFAULT 0, + avg_group_success_rate NUMERIC, + active_members_count INTEGER +); + +CREATE TABLE study_group_members ( + group_id UUID REFERENCES study_groups(id), + user_id UUID REFERENCES users(id), + joined_at TIMESTAMP, + role TEXT CHECK (role IN ('owner', 'admin', 'member')), + contribution_score INTEGER DEFAULT 0, -- Based on activity + PRIMARY KEY (group_id, user_id) +); + +CREATE TABLE group_challenges ( + id UUID PRIMARY KEY, + group_id UUID REFERENCES study_groups(id), + created_by_user_id UUID REFERENCES users(id), + challenge_type TEXT, -- "speed_run", "accuracy_battle", "specialty_mastery" + + case_pool UUID[], -- Array of case IDs for this challenge + start_time TIMESTAMP, + end_time TIMESTAMP, + + prize_type TEXT, -- "badges", "credits", "bragging_rights" + status TEXT CHECK (status IN ('upcoming', 'active', 'completed')) +); + +CREATE TABLE challenge_leaderboard ( + challenge_id UUID REFERENCES group_challenges(id), + user_id UUID REFERENCES users(id), + score INTEGER, + time_completed_seconds INTEGER, + rank INTEGER, + PRIMARY KEY (challenge_id, user_id) +); + +CREATE TABLE peer_interactions ( + id UUID PRIMARY KEY, + from_user_id UUID REFERENCES users(id), + to_user_id UUID REFERENCES users(id), + interaction_type TEXT, -- "study_together", "case_recommendation", "explanation_request" + context JSONB, + created_at TIMESTAMP +); +``` + +**Social Features:** + +1. **Study Groups** + - Create private/public groups + - Compete on group leaderboards + - Shared progress tracking + - Group study sessions (everyone does same cases simultaneously) + +2. **Peer Challenges** + - "Beat my time on this cardiology case!" + - Weekly group tournaments + - Specialty mastery races + +3. **Collaborative Learning** + - Ask peer who scored high: "How did you approach this?" + - Share case explanations + - Study buddy matching algorithm + +**Network Effect:** +- Student invites 3 friends to their study group +- Friends see their progress and want to compete +- Group creates challenges โ†’ more engagement +- Students stay because their friends are here + +**โ†’ Social lock-in (WhatsApp effect)** + +--- + +### 4. **Marketplace Network Effect** + +#### Two-Sided Market: Students โ†” Educators + +```typescript +CREATE TABLE premium_content ( + id UUID PRIMARY KEY, + creator_user_id UUID REFERENCES users(id), + content_type TEXT, -- "course", "case_pack", "specialty_bundle", "ai_tutor_session" + + title TEXT NOT NULL, + description TEXT, + price_credits INTEGER, + price_reais NUMERIC, -- For direct purchase + + content_metadata JSONB, + /* + { + "case_count": 50, + "specialty": "cardiologia", + "difficulty_range": [3, 5], + "includes_video_explanations": true, + "creator_credentials": "Cardiologista HC-USP" + } + */ + + -- Performance metrics + purchases_count INTEGER DEFAULT 0, + avg_rating NUMERIC, + review_count INTEGER, + revenue_generated NUMERIC, + + is_verified BOOLEAN DEFAULT false, -- Verified quality + created_at TIMESTAMP +); + +CREATE TABLE content_purchases ( + id UUID PRIMARY KEY, + user_id UUID REFERENCES users(id), + content_id UUID REFERENCES premium_content(id), + purchased_at TIMESTAMP, + price_paid_credits INTEGER, + price_paid_reais NUMERIC +); + +CREATE TABLE creator_profiles ( + user_id UUID PRIMARY KEY REFERENCES users(id), + is_verified_educator BOOLEAN DEFAULT false, + credentials TEXT, -- "Mรฉdico residente R3 Cardiologia USP" + bio TEXT, + + -- Creator stats + total_content_created INTEGER DEFAULT 0, + total_revenue_earned NUMERIC DEFAULT 0, + follower_count INTEGER DEFAULT 0, + avg_content_rating NUMERIC, + + -- Payout info + payout_method TEXT, + payout_details JSONB +); + +CREATE TABLE creator_followers ( + follower_user_id UUID REFERENCES users(id), + creator_user_id UUID REFERENCES users(id), + followed_at TIMESTAMP, + PRIMARY KEY (follower_user_id, creator_user_id) +); +``` + +**Marketplace Mechanics:** + +**For Students:** +- Buy specialized case packs from top educators +- Subscribe to favorite creators +- Access expert-made content +- Get 1-on-1 AI tutoring sessions (premium) + +**For Educators:** +- Create and sell content +- Earn 70% of sales (platform keeps 30%) +- Build following and reputation +- Verified badges for credentials + +**Network Effect:** +- More students โ†’ attract more educators (bigger market) +- More educators โ†’ more quality content โ†’ attract more students +- Best educators make real money โ†’ more educators join +- Platform becomes THE marketplace for medical ed content + +**โ†’ Two-sided marketplace moat** + +--- + +## ๐Ÿฐ Defensible Moats Summary + +### 1. **Data Moat** (Strongest) +- Millions of student-case interactions +- Proprietary adaptive algorithm trained on real performance +- Prediction accuracy improves with scale +- **Time to replicate**: 3-5 years minimum + +### 2. **Network Effects Moat** +- Social graph (study groups, peer learning) +- Content library (community cases) +- Marketplace (two-sided) +- **Switching cost**: Lose all friends, content, progress + +### 3. **Brand & Community Moat** +- "The platform where serious residents study" +- Community trust and identity +- User-generated content and culture +- **Intangible but powerful** + +### 4. **Regulatory/Trust Moat** (Future) +- Official partnerships with medical schools +- Endorsements from medical councils +- Verified by actual residency programs +- **Exclusive relationships** + +### 5. **Technology Moat** +- Proprietary AI architecture +- Medical-specific NLP models +- Clinical reasoning engine +- **Patent-pending algorithms** + +--- + +## ๐Ÿ’ฐ SaaS Business Model Evolution + +### Phase 1: Freemium (Launch - 12 months) + +**Free Tier:** +- 10 cases/day +- Basic AI feedback +- Solo study only +- Generic study plan + +**Premium ($29/month or R$149/month):** +- Unlimited cases +- Advanced AI tutor (chat) +- Study groups & challenges +- Personalized adaptive learning +- Performance analytics +- Badge system +- 100 credits/month for marketplace + +**Conversion Strategy:** +- Free tier proves value +- Hit daily limit โ†’ upgrade friction point +- Study group invites from premium users +- "Your friends are Premium, join them" + +**Target**: 10% conversion (industry standard) + +--- + +### Phase 2: Tiered SaaS (12-24 months) + +**Free:** 5 cases/day +**Basic ($19/month):** 20 cases/day + groups +**Pro ($39/month):** Unlimited + AI tutor + analytics +**Elite ($79/month):** Everything + marketplace credits + priority support + verified mentor matching + +**New Revenue Stream: Credits** +- Buy credits for marketplace +- $10 = 100 credits +- Spend on premium cases, tutoring, etc. + +--- + +### Phase 3: B2B SaaS (18+ months) + +**Target**: Medical Schools & Prep Courses + +**School Plans:** +- $999/month for 100 students +- $4,999/month for unlimited students +- White-label option +- Admin dashboard with class analytics +- Custom case library management +- Integration with school LMS + +**Value Prop for Schools:** +- Track student progress +- Identify struggling students early +- Improve board exam pass rates +- Data-driven curriculum decisions + +**Moat**: Once a school adopts, students use it โ†’ network effect when they graduate and tell others + +--- + +### Phase 4: Enterprise & API (24+ months) + +**API Access:** +- Other edtech companies license our AI +- Healthcare systems for resident training +- $0.10 per AI inference + +**Enterprise Partnerships:** +- Hospitals for resident education +- Medical associations for CME +- Insurance companies (better trained doctors = better outcomes) + +--- + +## ๐Ÿ“ˆ Scalability Architecture + +### Current Architecture (Good for 0-10k users) +``` +Vercel Edge Functions โ†’ Supabase PostgreSQL โ†’ Claude API +``` + +### Growth Architecture (10k-100k users) + +```typescript +// Add to schema +CREATE TABLE cache_ai_responses ( + cache_key TEXT PRIMARY KEY, + response_data JSONB, + created_at TIMESTAMP, + hit_count INTEGER DEFAULT 0, + ttl INTEGER DEFAULT 3600 -- seconds +); + +-- Index for faster lookups +CREATE INDEX idx_cache_ttl ON cache_ai_responses(created_at) +WHERE (EXTRACT(EPOCH FROM (NOW() - created_at)) < ttl); +``` + +**Caching Strategy:** +- Common case feedback cached (80% hit rate) +- AI responses for popular cases +- User profiles in Redis +- CDN for static assets + +**Database Optimization:** +- Read replicas for analytics queries +- Partitioning interactions table by month +- Materialized views for dashboards + +--- + +### Scale Architecture (100k-1M+ users) + +**Microservices Split:** +``` +โ”œโ”€โ”€ Case Service (Supabase) +โ”œโ”€โ”€ AI Service (Dedicated Claude inference server) +โ”œโ”€โ”€ User Service (Supabase) +โ”œโ”€โ”€ Analytics Service (Separate read DB) +โ””โ”€โ”€ Marketplace Service (Separate transaction DB) +``` + +**Infrastructure:** +- PostgreSQL: Supabase Pro โ†’ Dedicated instance with pgBouncer +- Caching: Vercel Edge Cache โ†’ Redis (Upstash) โ†’ CloudFlare CDN +- AI: Claude API โ†’ Anthropic batch API (cheaper for non-real-time) +- Background Jobs: Inngest or Temporal for async processing +- Monitoring: Datadog + Sentry + +**Cost at Scale:** +- 100k active users +- 1M cases/day +- Estimated: $15k/month infrastructure +- AI costs: $5k/month (with caching) +- **Total**: ~$20k/month = $0.20/user/month +- **Revenue** (10% paid at $29): $290k/month +- **Gross Margin**: 93% + +--- + +## ๐ŸŽฎ Gamification & Engagement Design + +### Core Engagement Loop (Daily) + +``` +1. Open App โ†’ See streak (don't break it!) +2. Dashboard shows: "Your friend Joรฃo just beat your cardiology score" +3. Do 5 quick cases to regain #1 spot +4. Unlock badge โ†’ Share on WhatsApp +5. Friend sees โ†’ comes back to compete +``` + +### Retention Mechanics + +**Daily:** +- Streak counter (Duolingo-style) +- Daily challenge case (bonus points) +- Study group activity feed + +**Weekly:** +- Group leaderboard reset +- Weekly progress report email +- "You vs Last Week" comparison + +**Monthly:** +- Specialty mastery level-ups +- Community case voting +- Creator earnings payout + +**Quarterly:** +- Nationwide leaderboards +- Seasonal tournaments ($1000 prize) +- Medical school rankings + +--- + +## ๐ŸŒ Community Features (Social Layer) + +### Discussion Forum + +```typescript +CREATE TABLE forum_posts ( + id UUID PRIMARY KEY, + user_id UUID REFERENCES users(id), + category TEXT, -- "case_discussion", "study_tips", "exam_strategies" + title TEXT NOT NULL, + content TEXT NOT NULL, + related_case_id UUID REFERENCES clinical_cases(id), + upvotes INTEGER DEFAULT 0, + view_count INTEGER DEFAULT 0, + created_at TIMESTAMP +); + +CREATE TABLE forum_comments ( + id UUID PRIMARY KEY, + post_id UUID REFERENCES forum_posts(id), + user_id UUID REFERENCES users(id), + content TEXT NOT NULL, + upvotes INTEGER DEFAULT 0, + is_expert_answer BOOLEAN DEFAULT false, + created_at TIMESTAMP +); +``` + +**Use Cases:** +- "Can someone explain this cardio case differently?" +- "Study tips for neurologia?" +- "Who else is taking REVALIDA March 2025?" + +**Network Effect**: More users โ†’ more discussions โ†’ more value โ†’ more users + +--- + +### Study Buddy Matching + +```typescript +CREATE TABLE study_preferences ( + user_id UUID PRIMARY KEY REFERENCES users(id), + target_exam TEXT, + exam_date DATE, + weak_specialties TEXT[], + preferred_study_times TEXT[], -- "weekday_mornings", "weekend_afternoons" + study_style TEXT, -- "competitive", "collaborative", "solo_with_accountability" + looking_for_buddy BOOLEAN DEFAULT false +); + +-- ML-powered matching +CREATE TABLE study_buddy_matches ( + id UUID PRIMARY KEY, + user1_id UUID REFERENCES users(id), + user2_id UUID REFERENCES users(id), + match_score NUMERIC, -- Compatibility score + match_reason JSONB, + status TEXT CHECK (status IN ('suggested', 'accepted', 'active', 'ended')), + created_at TIMESTAMP +); +``` + +**Algorithm:** +- Match by: similar level, complementary weaknesses, same exam date, compatible schedules +- "You're both weak in neuro โ†’ practice together" +- "Joรฃo is strong where you're weak โ†’ learn from him" + +--- + +## ๐Ÿš€ Go-to-Market Strategy + +### Phase 1: Seed Community (0-100 users) +**Tactic**: Manual recruitment from specific medical school +- Offer free premium for 6 months +- Recruit 20 students from USP/UNIFESP +- Ask them to invite friends +- Dogfood the product hard + +### Phase 2: Single University Dominance (100-1000 users) +**Tactic**: Win one school completely +- Become "the platform" at USP Medicina +- 70%+ of students using it +- Leverage social proof: "Everyone at USP uses this" +- Case studies of students who passed + +### Phase 3: University Expansion (1k-10k users) +**Tactic**: Replicate to other top schools +- UNIFESP, UFRJ, UFMG, etc. +- University ambassadors (pay in credits) +- School leaderboards (create competition) +- "USP vs UNIFESP" challenges + +### Phase 4: National Scale (10k-100k users) +**Tactic**: Paid acquisition + viral loops +- Facebook/Instagram ads targeting "residรชncia mรฉdica" +- Referral program: "Invite 3 friends โ†’ 1 month free" +- Content marketing (blog about exam strategies) +- YouTube: "How I passed with 85% using MedCards" + +### Phase 5: Platform Lock-in (100k+ users) +**Tactic**: Become infrastructure +- Partner with medical schools officially +- Licensing to prep courses +- Government partnerships (SUS resident training) + +--- + +## ๐Ÿ“Š Success Metrics (North Star + Supporting) + +### North Star Metric +**Weekly Active Cases Solved** +- Measures: Engagement ร— Value delivered +- Target Growth: 20% MoM + +### Supporting Metrics + +**Acquisition:** +- Signups/week +- Source attribution +- Activation rate (completed 10 cases in first week) + +**Engagement:** +- DAU/MAU ratio (target: >40%) +- Cases per session +- Streak retention + +**Monetization:** +- Free โ†’ Paid conversion rate +- MRR growth +- LTV/CAC ratio + +**Network Effects:** +- Study group creation rate +- Avg group size +- Community case submissions/week +- Marketplace transactions/week + +**Retention:** +- D7, D30, D90 retention +- Churn rate +- Win-back rate + +--- + +## ๐ŸŽฏ Product Roadmap + +### Q1 2025: Foundation + MVP +- Core case training +- Basic AI feedback +- Authentication +- Solo study mode + +### Q2 2025: Social Layer +- Study groups +- Peer challenges +- Leaderboards +- Basic community features + +### Q3 2025: Marketplace +- Community case submissions +- Premium content +- Creator tools +- Credits system + +### Q4 2025: B2B Pilot +- School admin dashboard +- Class analytics +- Custom case libraries +- API access (beta) + +### 2026: Platform +- Mobile app (React Native) +- API productization +- International expansion +- Enterprise features + +--- + +## ๐Ÿ’ก Moat Reinforcement Strategy + +**Continuous Improvement Loop:** + +1. **Data Moat**: Every case solved โ†’ better AI โ†’ better outcomes โ†’ more users +2. **Content Moat**: Best community cases promoted โ†’ creators earn โ†’ more quality content +3. **Network Moat**: Study group features โ†’ invite friends โ†’ social lock-in +4. **Brand Moat**: Best students use it โ†’ aspirational brand โ†’ more sign-ups + +**Defensive Tactics:** +- Long-term contracts with medical schools (lock-in) +- Exclusive partnerships with exam boards +- Patent AI methodology (if truly novel) +- Build community identity ("MedCards Residents") + +--- + +## ๐Ÿ”ฎ 10-Year Vision + +**Year 1-2**: Best residency exam prep in Brazil +**Year 3-5**: Platform for all medical education in Brazil (undergrad โ†’ CME) +**Year 5-7**: Expand to Latin America (same market dynamics) +**Year 7-10**: Global platform for medical education + +**End State:** +- 500k+ active learners +- $50M+ ARR +- Acquisition target for Duolingo, Coursera, or major medical publisher +- OR: IPO as EdTech/HealthTech platform + +--- + +**This is how you build an unassailable position in medical education.** + +Ready to implement the enhanced schema with network effects? diff --git a/medcards-ai/SCALABILITY_ARCHITECTURE.md b/medcards-ai/SCALABILITY_ARCHITECTURE.md new file mode 100644 index 00000000..e9fc03f8 --- /dev/null +++ b/medcards-ai/SCALABILITY_ARCHITECTURE.md @@ -0,0 +1,731 @@ +# MEDCARDS.AI - Scalability Architecture & Technical Infrastructure + +## ๐ŸŽฏ Scaling Philosophy + +**Build for 10k users, architect for 1M users.** + +This document outlines how MEDCARDS.AI scales from MVP (1k users) to platform (1M+ users) without major rewrites. + +--- + +## ๐Ÿ“Š Growth Stages & Infrastructure Evolution + +### Stage 1: MVP (0-10k users) +**Monthly Active Users**: 0-10,000 +**Daily Interactions**: 0-100k +**Infrastructure Cost**: $500-1,000/month + +**Stack:** +``` +Frontend: Vercel Edge Network +Backend: Next.js Server Actions (Vercel Serverless) +Database: Supabase Free/Pro (PostgreSQL) +AI: Anthropic Claude API (pay-per-use) +Cache: None (database only) +CDN: Vercel automatic +``` + +**Why it works:** +- Serverless scales automatically +- No DevOps required +- Pay only for usage +- Deploy in minutes + +**Bottlenecks:** +- None at this scale +- Database has 10GB limit (sufficient for 10k users) + +--- + +### Stage 2: Growth (10k-100k users) +**Monthly Active Users**: 10,000-100,000 +**Daily Interactions**: 100k-1M +**Infrastructure Cost**: $2,000-5,000/month + +**Stack Upgrades:** +``` +Frontend: Vercel Edge Network (same) +Backend: Next.js Server Actions (same) +Database: Supabase Pro โ†’ Team plan + - Connection pooling (pgBouncer) + - Read replicas for analytics + - 100GB storage +AI: Anthropic Claude API + Response caching +Cache: Upstash Redis (Vercel KV) + - Cache AI responses (24h TTL) + - Cache user sessions + - Rate limiting +CDN: CloudFlare in front of Vercel (optional) +Monitoring: Vercel Analytics + Sentry +``` + +**Architecture Pattern:** + +```typescript +// lib/cache/redis.ts +import { Redis } from '@upstash/redis'; + +const redis = Redis.fromEnv(); + +export async function getCachedAIResponse(cacheKey: string) { + return await redis.get(cacheKey); +} + +export async function setCachedAIResponse( + cacheKey: string, + response: any, + ttlSeconds: number = 86400 // 24 hours +) { + await redis.setex(cacheKey, ttlSeconds, JSON.stringify(response)); +} + +// Usage in AI feedback generation +export async function generateFeedback(context: FeedbackContext): Promise { + const cacheKey = `feedback:${context.case.id}:${context.student_answer.selected_answer_id}`; + + // Try cache first + const cached = await getCachedAIResponse(cacheKey); + if (cached) { + console.log('Cache hit for feedback'); + return JSON.parse(cached as string); + } + + // Generate new feedback + const feedback = await callClaudeAPI(context); + + // Cache for future students + await setCachedAIResponse(cacheKey, feedback); + + return feedback; +} +``` + +**Database Optimizations:** + +```sql +-- Partition interactions table by month (reduces query time) +CREATE TABLE interactions_2025_01 PARTITION OF interactions +FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); + +CREATE TABLE interactions_2025_02 PARTITION OF interactions +FOR VALUES FROM ('2025-02-01') TO ('2025-03-01'); + +-- Indexes for hot queries +CREATE INDEX CONCURRENTLY idx_interactions_user_recent +ON interactions(user_id, created_at DESC) +WHERE created_at > NOW() - INTERVAL '30 days'; + +-- Materialized view for dashboard stats (refresh every hour) +CREATE MATERIALIZED VIEW user_stats_cache AS +SELECT + user_id, + COUNT(*) as total_cases, + AVG(CASE WHEN is_correct THEN 1.0 ELSE 0.0 END) as success_rate, + MAX(created_at) as last_activity +FROM interactions +GROUP BY user_id; + +CREATE UNIQUE INDEX ON user_stats_cache(user_id); + +-- Auto-refresh via pg_cron +SELECT cron.schedule('refresh-user-stats', '0 * * * *', + 'REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats_cache'); +``` + +**Expected Performance:** +- API response time: <200ms (p95) +- Database query time: <50ms (p95) +- AI response time: 1-3s (depending on Claude API) +- Cache hit rate: 70-80% for common operations + +--- + +### Stage 3: Scale (100k-1M users) +**Monthly Active Users**: 100,000-1,000,000 +**Daily Interactions**: 1M-10M +**Infrastructure Cost**: $10,000-30,000/month + +**Major Architecture Changes:** + +#### 1. **Database Sharding Strategy** + +**Shard by User ID** (most queries are user-scoped): + +```sql +-- Shard 1: Users with ID hash % 4 = 0 +-- Shard 2: Users with ID hash % 4 = 1 +-- Shard 3: Users with ID hash % 4 = 2 +-- Shard 4: Users with ID hash % 4 = 3 + +-- Routing logic in application +function getShardForUser(userId: string): number { + const hash = hashUserId(userId); + return hash % 4; +} + +// Connection pool per shard +const shardConnections = { + 0: createSupabaseClient(SHARD_0_URL), + 1: createSupabaseClient(SHARD_1_URL), + 2: createSupabaseClient(SHARD_2_URL), + 3: createSupabaseClient(SHARD_3_URL), +}; + +export function getDbForUser(userId: string) { + const shard = getShardForUser(userId); + return shardConnections[shard]; +} +``` + +**Cross-shard queries** (leaderboards, analytics) go to read replicas or data warehouse. + +#### 2. **AI Infrastructure Optimization** + +**Problem**: Claude API costs scale linearly ($1M+ users = $50k+/month in AI costs) + +**Solution**: Multi-tier AI strategy + +```typescript +// Tier 1: Pre-computed responses (instant, free) +// For common case + answer combinations (80% of traffic) +const precomputedFeedback = await db + .from('precomputed_feedback') + .select('*') + .eq('case_id', caseId) + .eq('selected_answer', answerId) + .single(); + +if (precomputedFeedback) return precomputedFeedback; + +// Tier 2: Cached responses (fast, cheap) +// For less common combinations (15% of traffic) +const cached = await redis.get(`feedback:${caseId}:${answerId}`); +if (cached) return JSON.parse(cached); + +// Tier 3: Real-time AI generation (slow, expensive) +// For rare combinations or premium users (5% of traffic) +const feedback = await generateWithClaude(context); +await redis.setex(`feedback:${caseId}:${answerId}`, 86400, JSON.stringify(feedback)); +return feedback; +``` + +**Cost Impact:** +- Before: 1M API calls/day ร— $0.003 = $3,000/day = $90,000/month +- After: 50k API calls/day ร— $0.003 = $150/day = $4,500/month +- **Savings**: $85,500/month (95% reduction) + +#### 3. **Background Job Processing** + +**Move heavy operations off request path:** + +```typescript +// lib/jobs/queue.ts +import { Inngest } from 'inngest'; + +const inngest = new Inngest({ name: 'MedCards' }); + +// Heavy operations run async +export const calculateUserMetrics = inngest.createFunction( + { name: 'Calculate User Metrics' }, + { event: 'user/interaction.created' }, + async ({ event }) => { + const userId = event.data.userId; + + // Recalculate all user stats + const stats = await computeComprehensiveStats(userId); + + // Update database + await db.from('users').update({ progress: stats }).eq('id', userId); + + // Check for badge unlocks + await checkBadgeUnlocks(userId, stats); + + // Update leaderboards + await updateLeaderboards(userId, stats); + } +); + +// Badge unlock notifications +export const notifyBadgeUnlock = inngest.createFunction( + { name: 'Notify Badge Unlock' }, + { event: 'badge/unlocked' }, + async ({ event }) => { + // Send email + // Push notification + // Update UI via WebSocket + } +); +``` + +**Benefits:** +- API response time: 2s โ†’ 200ms +- Better user experience +- Can retry failed jobs +- Scale workers independently + +#### 4. **Read/Write Separation** + +```typescript +// lib/db/routing.ts + +// Write operations โ†’ Primary database +export async function writeInteraction(data: InteractionData) { + return await primaryDb.from('interactions').insert(data); +} + +// Read operations โ†’ Read replicas (distribute load) +const readReplicas = [replicaDb1, replicaDb2, replicaDb3]; +let currentReplica = 0; + +export async function getUser Interactions(userId: string) { + const db = readReplicas[currentReplica % readReplicas.length]; + currentReplica++; + + return await db + .from('interactions') + .select('*') + .eq('user_id', userId) + .order('created_at', { ascending: false }) + .limit(20); +} +``` + +#### 5. **CDN & Static Asset Optimization** + +```typescript +// next.config.ts +export default { + images: { + loader: 'cloudinary', // Or imgix, cloudflare + domains: ['res.cloudinary.com'], + }, + // Serve heavy assets from CDN + assetPrefix: process.env.CDN_URL, +}; +``` + +**Asset Strategy:** +- Case images โ†’ CloudFlare R2 (S3-compatible, cheaper) +- User avatars โ†’ CloudFlare Images (auto-optimization) +- Video explanations โ†’ Mux (video streaming CDN) + +--- + +### Stage 4: Platform (1M+ users) +**Monthly Active Users**: 1M+ +**Daily Interactions**: 10M+ +**Infrastructure Cost**: $50,000-100,000/month + +**Full Microservices Architecture:** + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ CloudFlare CDN โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Load Balancer โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Web Frontend โ”‚ โ”‚ Mobile API โ”‚ +โ”‚ (Vercel Edge) โ”‚ โ”‚ (Dedicated) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ API Gateway โ”‚ + โ”‚ (Rate limiting, Auth) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ User Service โ”‚ โ”‚ Cache โ”‚ โ”‚ Case Service โ”‚ +โ”‚ (Supabase) โ”‚ โ”‚ (Redis) โ”‚ โ”‚ (Dedicated DB) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ AI Service โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ (Claude + โ”‚ + โ”‚ Fine-tune) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Analytics Service โ”‚ + โ”‚ (ClickHouse) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Service Breakdown:** + +| Service | Tech | Purpose | +|---------|------|---------| +| User Service | Supabase | User profiles, auth, progress | +| Case Service | Dedicated PostgreSQL | Clinical cases, interactions | +| AI Service | Claude API + Custom models | Feedback, coaching, adaptive | +| Analytics | ClickHouse | Real-time analytics, dashboards | +| Search | Elasticsearch | Case search, user search | +| Notifications | Pusher / Socket.io | Real-time updates | +| Jobs | Temporal | Background processing | +| Cache | Redis Cluster | Multi-layer caching | + +--- + +## ๐Ÿ’ฐ Cost Breakdown by Stage + +### Stage 1: MVP (10k users) +``` +Vercel Pro: $20/month +Supabase Pro: $25/month +Anthropic API: $300/month (100k AI calls) +Domain + SSL: $15/month +Monitoring: $50/month +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TOTAL: $410/month +Cost per user: $0.041/month +``` + +### Stage 2: Growth (100k users) +``` +Vercel Enterprise: $500/month +Supabase Team: $599/month +Anthropic API: $1,500/month (500k AI calls, 70% cached) +Upstash Redis: $200/month +CloudFlare Pro: $20/month +Sentry: $100/month +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TOTAL: $2,919/month +Cost per user: $0.029/month +``` + +### Stage 3: Scale (1M users) +``` +Vercel Enterprise: $2,000/month +Supabase (4 shards): $2,400/month ($600 each) +Anthropic API: $4,500/month (cached 95%) +Redis Cluster: $1,000/month +CloudFlare: $200/month +Sentry: $500/month +Inngest (jobs): $300/month +Datadog: $800/month +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TOTAL: $11,700/month +Cost per user: $0.012/month +``` + +**Key Insight**: Cost per user DECREASES as you scale (economies of scale). + +--- + +## ๐Ÿ”ฅ Performance Targets + +### API Response Times (p95) +- **Homepage load**: <500ms +- **Dashboard load**: <800ms +- **Case presentation**: <300ms +- **Submit answer**: <400ms +- **AI feedback**: <2s (with streaming) +- **Chat message**: <500ms (streaming) + +### Database Query Times (p95) +- **Simple SELECT**: <10ms +- **Complex JOIN**: <50ms +- **Analytics query**: <200ms +- **Leaderboard**: <100ms (cached) + +### Availability +- **Uptime SLA**: 99.9% (8.76 hours downtime/year) +- **Zero-downtime deployments**: Required +- **Disaster recovery**: <15 minute RPO/RTO + +--- + +## ๐Ÿ›ก๏ธ Reliability & Monitoring + +### Error Budget +``` +Monthly Uptime Target: 99.9% +Error Budget: 0.1% = 43 minutes downtime/month + +Week 1: 5 minutes โ†’ 37 minutes left +Week 2: 10 minutes โ†’ 27 minutes left +Week 3: 30 minutes โ†’ -3 minutes (EXCEEDED!) + โ†’ Freeze feature releases + โ†’ Focus on stability + โ†’ Root cause analysis +``` + +### Monitoring Stack + +```typescript +// lib/monitoring/metrics.ts +import * as Sentry from '@sentry/nextjs'; +import { track } from '@vercel/analytics'; + +// Track all API calls +export async function monitoredAPICall( + operation: string, + fn: () => Promise +): Promise { + const startTime = Date.now(); + + try { + const result = await fn(); + const duration = Date.now() - startTime; + + // Success metrics + track('api_call_success', { + operation, + duration, + }); + + return result; + } catch (error) { + // Error tracking + Sentry.captureException(error, { + tags: { operation }, + extra: { duration: Date.now() - startTime }, + }); + + // Error metrics + track('api_call_error', { + operation, + error: error.message, + }); + + throw error; + } +} + +// Usage +export async function submitAnswer(data: AnswerData) { + return monitoredAPICall('submit_answer', async () => { + // ... actual implementation + }); +} +``` + +### Alerts Configuration + +```yaml +alerts: + - name: High Error Rate + condition: error_rate > 5% + window: 5 minutes + severity: critical + notify: pagerduty + + - name: Slow API Responses + condition: p95_latency > 2 seconds + window: 10 minutes + severity: warning + notify: slack + + - name: Database Connection Pool Exhaustion + condition: available_connections < 10 + severity: critical + notify: pagerduty + + - name: AI API Rate Limit Approaching + condition: anthropic_remaining_requests < 100 + severity: warning + notify: slack + + - name: Daily Active Users Drop + condition: dau_vs_yesterday_decrease > 20% + severity: warning + notify: slack +``` + +--- + +## ๐Ÿ“ˆ Capacity Planning + +### User Growth Projections + +``` +Month 1: 100 users +Month 3: 1,000 users (10x growth) +Month 6: 10,000 users (10x growth) +Month 12: 50,000 users (5x growth) +Month 18: 150,000 users (3x growth) +Month 24: 500,000 users (3.3x growth) +``` + +### Infrastructure Scaling Triggers + +| Metric | Trigger | Action | +|--------|---------|--------| +| Database CPU | >70% for 1h | Add read replica | +| Database Storage | >80% used | Upgrade plan OR archive old data | +| API Error Rate | >5% for 5min | Scale up serverless OR rollback | +| Redis Memory | >80% used | Upgrade OR implement LRU eviction | +| AI API Costs | >$10k/month | Implement aggressive caching | + +### Scaling Checklist + +**At 10k users:** +- [ ] Enable Redis caching +- [ ] Add database indexes +- [ ] Set up monitoring +- [ ] Implement rate limiting + +**At 50k users:** +- [ ] Add read replicas +- [ ] Implement job queue +- [ ] Aggressive AI response caching +- [ ] CloudFlare Pro + +**At 100k users:** +- [ ] Database sharding +- [ ] Microservices architecture +- [ ] Dedicated analytics database +- [ ] Content delivery optimization + +--- + +## ๐Ÿš€ Deployment Strategy + +### Zero-Downtime Deployments + +```bash +# Blue-Green Deployment on Vercel +1. Deploy new version to staging +2. Run smoke tests +3. Deploy to production (Vercel handles canary rollout) +4. Monitor error rates for 15 minutes +5. If errors spike: automatic rollback +6. If stable: full rollout +``` + +### Database Migrations + +```typescript +// migrations/0015_add_community_cases.ts +export async function up() { + // Safe migration: additive only + await db.schema + .createTable('community_cases') + .addColumn('id', 'uuid', (col) => col.primaryKey()) + .addColumn('created_at', 'timestamp') + // ... other columns + .execute(); +} + +export async function down() { + // Rollback (but never run in production!) + await db.schema.dropTable('community_cases').execute(); +} +``` + +**Migration Rules:** +1. Never drop columns (deprecate instead) +2. Add new columns as nullable +3. Backfill data async +4. Test on staging with production data snapshot + +--- + +## ๐Ÿ”’ Security at Scale + +### Rate Limiting + +```typescript +// middleware.ts +import { Ratelimit } from '@upstash/ratelimit'; +import { Redis } from '@upstash/redis'; + +const ratelimit = new Ratelimit({ + redis: Redis.fromEnv(), + limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 requests per minute +}); + +export async function middleware(request: Request) { + const ip = request.headers.get('x-forwarded-for') ?? 'unknown'; + const { success, limit, remaining } = await ratelimit.limit(ip); + + if (!success) { + return new Response('Rate limit exceeded', { status: 429 }); + } + + return NextResponse.next(); +} +``` + +### DDoS Protection + +``` +CloudFlare WAF โ†’ Vercel โ†’ Application + +- CloudFlare: Block malicious IPs, rate limit per IP +- Vercel: Edge protection, DDoS mitigation +- Application: User-level rate limits +``` + +### Data Encryption + +``` +- At Rest: Supabase encrypts all data (AES-256) +- In Transit: TLS 1.3 everywhere +- Backups: Encrypted, geographically distributed +- Secrets: Managed via Vercel environment variables +``` + +--- + +## ๐Ÿ“Š Analytics Architecture + +### Real-Time Analytics + +```sql +-- ClickHouse table for real-time analytics (better than PostgreSQL for OLAP) +CREATE TABLE analytics.interactions ( + user_id UUID, + case_id UUID, + is_correct Boolean, + time_to_answer Int32, + created_at DateTime, + specialty String +) ENGINE = MergeTree() +PARTITION BY toYYYYMM(created_at) +ORDER BY (created_at, user_id); + +-- Fast aggregations +SELECT + specialty, + COUNT(*) as total, + AVG(is_correct) as success_rate +FROM analytics.interactions +WHERE created_at > now() - INTERVAL 7 DAY +GROUP BY specialty; + +-- Executes in <50ms on 100M rows +``` + +### Data Warehouse Strategy + +``` +Operational DB (PostgreSQL) โ†’ CDC โ†’ Data Warehouse (ClickHouse) + โ†“ + Analytics Dashboard (Metabase/Looker) +``` + +--- + +## ๐ŸŽฏ Summary: Scaling Path + +``` +MVP (0-10k): Simple stack, manual processes, good enough +Growth (10-100k): Add caching, optimize database, automate +Scale (100k-1M): Sharding, microservices, background jobs +Platform (1M+): Full distribution, dedicated services, ML ops + +Philosophy: Scale progressively, not prematurely. +Build what you need TODAY, architect for TOMORROW. +``` + +**Next Steps**: Implement MVP stack, monitor metrics, scale when triggers hit. diff --git a/medcards-ai/supabase/schema-network-effects.sql b/medcards-ai/supabase/schema-network-effects.sql new file mode 100644 index 00000000..e9f77380 --- /dev/null +++ b/medcards-ai/supabase/schema-network-effects.sql @@ -0,0 +1,988 @@ +-- ============================================================================ +-- MEDCARDS.AI - Network Effects & Social Features Schema Extension +-- This extends the base schema with community, marketplace, and social features +-- ============================================================================ + +-- ============================================================================ +-- DATA NETWORK EFFECT: Learning from Collective Intelligence +-- ============================================================================ + +-- Track real-world difficulty vs predicted difficulty +CREATE TABLE case_difficulty_calibration ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + case_id UUID NOT NULL REFERENCES clinical_cases(id) ON DELETE CASCADE, + + -- Calibration metrics + actual_difficulty_score NUMERIC(5, 2), -- Based on real user performance + predicted_difficulty_score NUMERIC(5, 2), -- What we thought it would be + difficulty_delta NUMERIC(5, 2), -- How off were we? + + sample_size INTEGER NOT NULL, -- Number of interactions used for calculation + confidence_level NUMERIC(3, 2), -- Statistical confidence (0.00-1.00) + + -- Performance breakdown + performance_by_level JSONB, -- {"beginner": 0.3, "intermediate": 0.6, "advanced": 0.8} + time_distribution JSONB, -- {"p50": 180, "p75": 240, "p90": 320} + + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + CONSTRAINT valid_confidence CHECK (confidence_level >= 0 AND confidence_level <= 1) +); + +CREATE INDEX idx_calibration_case ON case_difficulty_calibration(case_id); +CREATE INDEX idx_calibration_updated ON case_difficulty_calibration(updated_at DESC); + +-- Track AI model versions and performance +CREATE TABLE prediction_model_versions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + version TEXT UNIQUE NOT NULL, + deployed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Training data + training_data_size INTEGER NOT NULL, + training_period_start TIMESTAMP WITH TIME ZONE, + training_period_end TIMESTAMP WITH TIME ZONE, + + -- Performance metrics + accuracy_metrics JSONB NOT NULL, + /* + { + "case_selection_accuracy": 0.85, + "difficulty_prediction_mae": 0.3, + "time_prediction_mape": 15.2, + "student_success_prediction_auc": 0.78 + } + */ + + performance_improvement_vs_previous NUMERIC(5, 2), -- Percentage improvement + + -- Model metadata + model_architecture TEXT, + hyperparameters JSONB, + notes TEXT, + + is_active BOOLEAN DEFAULT false +); + +CREATE INDEX idx_model_active ON prediction_model_versions(is_active) WHERE is_active = true; + +-- ============================================================================ +-- CONTENT NETWORK EFFECT: Community-Contributed Cases +-- ============================================================================ + +CREATE TABLE community_cases ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Creator + created_by_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Case content (same structure as clinical_cases) + case_code TEXT UNIQUE NOT NULL, + title TEXT NOT NULL, + clinical_presentation TEXT NOT NULL, + patient_data JSONB, + question TEXT NOT NULL, + options JSONB NOT NULL, + correct_answer_id TEXT NOT NULL, + explanation TEXT NOT NULL, + clinical_reasoning TEXT NOT NULL, + key_concepts TEXT[], + differential_diagnosis TEXT[], + + -- Classification + specialty TEXT NOT NULL, + subspecialty TEXT, + difficulty_level INTEGER CHECK (difficulty_level BETWEEN 1 AND 5), + clinical_algorithm TEXT, + + -- Review status + status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'submitted', 'under_review', 'approved', 'rejected', 'needs_revision')), + submitted_at TIMESTAMP WITH TIME ZONE, + reviewed_at TIMESTAMP WITH TIME ZONE, + approved_by_user_id UUID REFERENCES users(id), + + -- Community feedback + community_rating NUMERIC(3, 2), -- 0.00 to 5.00 + rating_count INTEGER DEFAULT 0, + times_used INTEGER DEFAULT 0, + success_rate NUMERIC(5, 2), + + -- Moderation + curator_notes TEXT, + revision_requests TEXT[], + + -- Monetization + is_premium BOOLEAN DEFAULT false, + price_credits INTEGER DEFAULT 0, + earnings_generated NUMERIC(10, 2) DEFAULT 0, + + -- Quality signals + expert_verified BOOLEAN DEFAULT false, + flagged_count INTEGER DEFAULT 0, + + tags TEXT[] +); + +CREATE INDEX idx_community_cases_creator ON community_cases(created_by_user_id); +CREATE INDEX idx_community_cases_status ON community_cases(status); +CREATE INDEX idx_community_cases_specialty ON community_cases(specialty) WHERE status = 'approved'; +CREATE INDEX idx_community_cases_rating ON community_cases(community_rating DESC) WHERE status = 'approved'; + +-- Reviews for community cases +CREATE TABLE case_reviews ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + case_id UUID NOT NULL REFERENCES community_cases(id) ON DELETE CASCADE, + reviewer_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Review scores + clinical_accuracy_score INTEGER CHECK (clinical_accuracy_score BETWEEN 1 AND 5), + educational_value_score INTEGER CHECK (educational_value_score BETWEEN 1 AND 5), + clarity_score INTEGER CHECK (clarity_score BETWEEN 1 AND 5), + overall_score NUMERIC(3, 2), -- Calculated average + + -- Feedback + review_text TEXT NOT NULL, + strengths TEXT[], + areas_for_improvement TEXT[], + + -- Reviewer credibility + is_expert_review BOOLEAN DEFAULT false, -- Verified doctors/professors + reviewer_specialty TEXT, + + -- Helpfulness + helpful_count INTEGER DEFAULT 0, + + UNIQUE(case_id, reviewer_user_id) -- One review per user per case +); + +CREATE INDEX idx_reviews_case ON case_reviews(case_id); +CREATE INDEX idx_reviews_expert ON case_reviews(is_expert_review) WHERE is_expert_review = true; + +-- Case quality flags (for moderation) +CREATE TABLE case_flags ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + case_id UUID NOT NULL REFERENCES community_cases(id) ON DELETE CASCADE, + flagged_by_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + flag_reason TEXT NOT NULL CHECK (flag_reason IN ( + 'clinical_inaccuracy', + 'misleading_information', + 'inappropriate_content', + 'duplicate', + 'poor_quality', + 'other' + )), + + description TEXT NOT NULL, + status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'reviewed', 'resolved', 'dismissed')), + resolution_notes TEXT, + resolved_by_user_id UUID REFERENCES users(id), + resolved_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_flags_case ON case_flags(case_id); +CREATE INDEX idx_flags_status ON case_flags(status) WHERE status = 'pending'; + +-- ============================================================================ +-- SOCIAL NETWORK EFFECT: Study Groups & Peer Learning +-- ============================================================================ + +CREATE TABLE study_groups ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Group identity + name TEXT NOT NULL, + description TEXT, + created_by_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Access control + is_public BOOLEAN DEFAULT false, + requires_approval BOOLEAN DEFAULT false, + invite_code TEXT UNIQUE, -- For private groups + member_limit INTEGER DEFAULT 50, + + -- Configuration + focus_specialties TEXT[], + target_exam TEXT, -- "REVALIDA 2025", "USP Clรญnica Mรฉdica 2025" + exam_date DATE, + study_schedule JSONB, -- {"monday": ["19:00-21:00"], "saturday": ["09:00-12:00"]} + + -- Group stats + total_cases_solved INTEGER DEFAULT 0, + avg_group_success_rate NUMERIC(5, 2), + active_members_count INTEGER DEFAULT 0, + total_study_hours NUMERIC(10, 2) DEFAULT 0, + + -- Visibility + is_archived BOOLEAN DEFAULT false, + + -- Group culture + group_image_url TEXT, + tags TEXT[] +); + +CREATE INDEX idx_groups_public ON study_groups(is_public) WHERE is_public = true AND is_archived = false; +CREATE INDEX idx_groups_creator ON study_groups(created_by_user_id); +CREATE INDEX idx_groups_exam ON study_groups(target_exam) WHERE is_archived = false; + +CREATE TABLE study_group_members ( + group_id UUID NOT NULL REFERENCES study_groups(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Role + role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member')), + + -- Member stats + contribution_score INTEGER DEFAULT 0, -- Based on activity and helpfulness + cases_solved_in_group INTEGER DEFAULT 0, + last_active_at TIMESTAMP WITH TIME ZONE, + + -- Preferences + notifications_enabled BOOLEAN DEFAULT true, + + PRIMARY KEY (group_id, user_id) +); + +CREATE INDEX idx_group_members_user ON study_group_members(user_id); +CREATE INDEX idx_group_members_active ON study_group_members(last_active_at DESC); + +-- Group activity feed +CREATE TABLE group_activities ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + group_id UUID NOT NULL REFERENCES study_groups(id) ON DELETE CASCADE, + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + + activity_type TEXT NOT NULL CHECK (activity_type IN ( + 'member_joined', + 'member_left', + 'challenge_created', + 'challenge_completed', + 'milestone_reached', + 'case_recommended', + 'discussion_started' + )), + + activity_data JSONB, -- Context-specific data + visibility TEXT DEFAULT 'group' CHECK (visibility IN ('group', 'members_only', 'public')) +); + +CREATE INDEX idx_activities_group ON group_activities(group_id, created_at DESC); + +-- ============================================================================ +-- COMPETITIVE FEATURES: Challenges & Leaderboards +-- ============================================================================ + +CREATE TABLE group_challenges ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + group_id UUID NOT NULL REFERENCES study_groups(id) ON DELETE CASCADE, + created_by_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Challenge details + title TEXT NOT NULL, + description TEXT, + challenge_type TEXT NOT NULL CHECK (challenge_type IN ( + 'speed_run', -- Solve X cases as fast as possible + 'accuracy_battle', -- Highest success rate wins + 'specialty_mastery', -- Focus on specific specialty + 'daily_streak', -- Longest streak wins + 'total_cases' -- Most cases solved + )), + + -- Rules + case_pool UUID[], -- Specific cases OR null for any cases + specialty_filter TEXT, + difficulty_filter INTEGER, + + -- Timing + start_time TIMESTAMP WITH TIME ZONE NOT NULL, + end_time TIMESTAMP WITH TIME ZONE NOT NULL, + + -- Rewards + prize_type TEXT CHECK (prize_type IN ('badges', 'credits', 'bragging_rights', 'real_prize')), + prize_details JSONB, -- {"credits": 500, "badge_id": "uuid"} + + -- Status + status TEXT DEFAULT 'upcoming' CHECK (status IN ('upcoming', 'active', 'completed', 'cancelled')), + + -- Stats + participant_count INTEGER DEFAULT 0, + total_cases_solved INTEGER DEFAULT 0 +); + +CREATE INDEX idx_challenges_group ON group_challenges(group_id); +CREATE INDEX idx_challenges_status ON group_challenges(status, start_time); + +CREATE TABLE challenge_participants ( + challenge_id UUID NOT NULL REFERENCES group_challenges(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Performance + score INTEGER DEFAULT 0, + cases_solved INTEGER DEFAULT 0, + success_rate NUMERIC(5, 2), + time_spent_seconds INTEGER DEFAULT 0, + rank INTEGER, + + -- Completion + completed_at TIMESTAMP WITH TIME ZONE, + + PRIMARY KEY (challenge_id, user_id) +); + +CREATE INDEX idx_participants_challenge ON challenge_participants(challenge_id, score DESC); +CREATE INDEX idx_participants_user ON challenge_participants(user_id); + +-- Global leaderboards +CREATE TABLE leaderboards ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + + leaderboard_type TEXT NOT NULL CHECK (leaderboard_type IN ( + 'global_weekly', + 'global_monthly', + 'global_all_time', + 'specialty_weekly', + 'university_weekly', + 'study_group' + )), + + -- Filters + specialty TEXT, -- For specialty leaderboards + university TEXT, -- For university leaderboards + study_group_id UUID REFERENCES study_groups(id), + + -- Period + period_start TIMESTAMP WITH TIME ZONE NOT NULL, + period_end TIMESTAMP WITH TIME ZONE NOT NULL, + + -- Rankings (denormalized for performance) + rankings JSONB NOT NULL, + /* + [ + {"user_id": "uuid", "username": "Joรฃo", "score": 9500, "cases_solved": 150, "success_rate": 0.85}, + {"user_id": "uuid", "username": "Maria", "score": 9200, "cases_solved": 145, "success_rate": 0.87}, + ...top 100 + ] + */ + + last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + UNIQUE(leaderboard_type, specialty, university, study_group_id, period_start) +); + +CREATE INDEX idx_leaderboards_type ON leaderboards(leaderboard_type, period_end DESC); + +-- ============================================================================ +-- PEER INTERACTIONS: Direct User Connections +-- ============================================================================ + +CREATE TABLE peer_interactions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + from_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + to_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + interaction_type TEXT NOT NULL CHECK (interaction_type IN ( + 'study_together_request', + 'case_recommendation', + 'explanation_request', + 'kudos', -- "Nice job on that case!" + 'challenge_invite', + 'mentor_request' + )), + + context JSONB, -- Additional data depending on type + status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'declined', 'expired')), + + response_at TIMESTAMP WITH TIME ZONE, + expires_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_interactions_to_user ON peer_interactions(to_user_id, status); +CREATE INDEX idx_interactions_from_user ON peer_interactions(from_user_id); + +-- Study buddy matching preferences +CREATE TABLE study_preferences ( + user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Exam goals + target_exam TEXT, + exam_date DATE, + target_specialty TEXT, -- For residency + + -- Learning profile + weak_specialties TEXT[], + strong_specialties TEXT[], + preferred_study_times TEXT[], -- "weekday_mornings", "weekend_afternoons", etc. + study_hours_per_week INTEGER, + + -- Personality + study_style TEXT CHECK (study_style IN ('competitive', 'collaborative', 'independent_with_accountability', 'mentor', 'mentee')), + communication_preference TEXT CHECK (communication_preference IN ('chat', 'video', 'async')), + + -- Matching + looking_for_buddy BOOLEAN DEFAULT false, + open_to_group_invites BOOLEAN DEFAULT true, + university TEXT, + current_year INTEGER, -- Year of medical school + + -- Bio + bio TEXT, + interests TEXT[] +); + +CREATE INDEX idx_preferences_looking ON study_preferences(looking_for_buddy) WHERE looking_for_buddy = true; +CREATE INDEX idx_preferences_exam ON study_preferences(target_exam, exam_date); + +CREATE TABLE study_buddy_matches ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + user1_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + user2_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Match quality + match_score NUMERIC(3, 2) NOT NULL, -- 0.00 to 1.00 + match_reason JSONB NOT NULL, + /* + { + "compatibility_factors": [ + "Both preparing for REVALIDA 2025", + "Complementary strengths: You're strong in cardio, they're strong in neuro", + "Similar study schedule preferences" + ], + "suggested_first_activity": "Try a cardiology challenge together" + } + */ + + -- Status + status TEXT DEFAULT 'suggested' CHECK (status IN ('suggested', 'accepted', 'declined', 'active', 'ended')), + accepted_at TIMESTAMP WITH TIME ZONE, + ended_at TIMESTAMP WITH TIME ZONE, + + -- Activity tracking + study_sessions_together INTEGER DEFAULT 0, + cases_solved_together INTEGER DEFAULT 0, + + CONSTRAINT different_users CHECK (user1_id != user2_id), + UNIQUE(user1_id, user2_id) +); + +CREATE INDEX idx_matches_user1 ON study_buddy_matches(user1_id, status); +CREATE INDEX idx_matches_user2 ON study_buddy_matches(user2_id, status); + +-- ============================================================================ +-- MARKETPLACE: Two-Sided Market for Content +-- ============================================================================ + +CREATE TABLE premium_content ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + creator_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Content details + content_type TEXT NOT NULL CHECK (content_type IN ( + 'case_pack', -- Bundle of cases + 'specialty_course', -- Complete specialty review + 'exam_simulation', -- Full mock exam + 'video_explanations', -- Video content + 'study_guide', -- PDF/written guide + 'flashcard_deck', -- Spaced repetition cards + 'ai_tutor_session' -- 1-on-1 AI tutoring (premium) + )), + + title TEXT NOT NULL, + description TEXT NOT NULL, + detailed_description TEXT, + + -- Pricing + price_credits INTEGER NOT NULL, + price_reais NUMERIC(10, 2), -- For direct purchase + is_subscription BOOLEAN DEFAULT false, -- Monthly access vs one-time + + -- Content metadata + content_metadata JSONB NOT NULL, + /* + { + "case_count": 50, + "specialty": "cardiologia", + "difficulty_range": [3, 5], + "includes_video": true, + "estimated_hours": 10, + "prerequisites": ["Basic cardiology knowledge"], + "learning_objectives": ["Master ECG interpretation", "..."] + } + */ + + -- Files/content + content_files JSONB, -- URLs to files in Supabase storage + preview_content JSONB, -- Free preview + + -- Performance metrics + purchases_count INTEGER DEFAULT 0, + view_count INTEGER DEFAULT 0, + avg_rating NUMERIC(3, 2), + review_count INTEGER DEFAULT 0, + revenue_generated NUMERIC(10, 2) DEFAULT 0, + + -- Quality control + is_verified BOOLEAN DEFAULT false, -- Verified by MedCards team + is_featured BOOLEAN DEFAULT false, + quality_score NUMERIC(3, 2), -- Internal quality metric + + -- Status + status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'pending_review', 'published', 'unpublished')), + published_at TIMESTAMP WITH TIME ZONE, + + -- SEO + tags TEXT[], + category TEXT +); + +CREATE INDEX idx_premium_content_creator ON premium_content(creator_user_id); +CREATE INDEX idx_premium_content_published ON premium_content(status, published_at DESC) WHERE status = 'published'; +CREATE INDEX idx_premium_content_featured ON premium_content(is_featured, avg_rating DESC) WHERE is_featured = true; +CREATE INDEX idx_premium_content_category ON premium_content(category, avg_rating DESC) WHERE status = 'published'; + +CREATE TABLE content_purchases ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + purchased_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + content_id UUID NOT NULL REFERENCES premium_content(id) ON DELETE CASCADE, + + -- Transaction + price_paid_credits INTEGER, + price_paid_reais NUMERIC(10, 2), + payment_method TEXT, -- 'credits', 'card', 'pix' + + -- Access + access_expires_at TIMESTAMP WITH TIME ZONE, -- For subscriptions + + -- Engagement + last_accessed_at TIMESTAMP WITH TIME ZONE, + completion_percentage NUMERIC(5, 2) DEFAULT 0, + + -- Satisfaction + rated BOOLEAN DEFAULT false, + rating INTEGER CHECK (rating BETWEEN 1 AND 5), + review_text TEXT, + + UNIQUE(user_id, content_id) -- One purchase per user per content +); + +CREATE INDEX idx_purchases_user ON content_purchases(user_id); +CREATE INDEX idx_purchases_content ON content_purchases(content_id); +CREATE INDEX idx_purchases_recent ON content_purchases(purchased_at DESC); + +-- Creator profiles +CREATE TABLE creator_profiles ( + user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + -- Verification + is_verified_educator BOOLEAN DEFAULT false, + verified_at TIMESTAMP WITH TIME ZONE, + credentials TEXT, -- "Mรฉdico Residente R3 Cardiologia HC-USP" + credentials_verified BOOLEAN DEFAULT false, + + -- Profile + display_name TEXT NOT NULL, + bio TEXT, + profile_image_url TEXT, + specialty TEXT, + institution TEXT, + + -- Social + website_url TEXT, + twitter_handle TEXT, + linkedin_url TEXT, + + -- Creator stats + total_content_created INTEGER DEFAULT 0, + total_revenue_earned NUMERIC(10, 2) DEFAULT 0, + total_students_reached INTEGER DEFAULT 0, + follower_count INTEGER DEFAULT 0, + avg_content_rating NUMERIC(3, 2), + + -- Payout + payout_method TEXT CHECK (payout_method IN ('bank_transfer', 'pix', 'paypal')), + payout_details JSONB, -- Encrypted sensitive data + minimum_payout_threshold NUMERIC(10, 2) DEFAULT 100.00, + + -- Status + is_active BOOLEAN DEFAULT true, + terms_accepted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_creators_verified ON creator_profiles(is_verified_educator) WHERE is_verified_educator = true; +CREATE INDEX idx_creators_revenue ON creator_profiles(total_revenue_earned DESC); + +CREATE TABLE creator_followers ( + follower_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + creator_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + followed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + notifications_enabled BOOLEAN DEFAULT true, + + PRIMARY KEY (follower_user_id, creator_user_id) +); + +CREATE INDEX idx_followers_creator ON creator_followers(creator_user_id); +CREATE INDEX idx_followers_user ON creator_followers(follower_user_id); + +-- Payout tracking +CREATE TABLE creator_payouts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + creator_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Payout details + amount NUMERIC(10, 2) NOT NULL, + currency TEXT DEFAULT 'BRL', + + period_start TIMESTAMP WITH TIME ZONE NOT NULL, + period_end TIMESTAMP WITH TIME ZONE NOT NULL, + + -- Transaction + status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')), + payout_method TEXT NOT NULL, + transaction_id TEXT, + + processed_at TIMESTAMP WITH TIME ZONE, + completed_at TIMESTAMP WITH TIME ZONE, + + -- Breakdown + revenue_breakdown JSONB -- Details of what generated this revenue +); + +CREATE INDEX idx_payouts_creator ON creator_payouts(creator_user_id, created_at DESC); +CREATE INDEX idx_payouts_status ON creator_payouts(status) WHERE status IN ('pending', 'processing'); + +-- ============================================================================ +-- COMMUNITY FORUM +-- ============================================================================ + +CREATE TABLE forum_categories ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT UNIQUE NOT NULL, + slug TEXT UNIQUE NOT NULL, + description TEXT, + icon_emoji TEXT, + sort_order INTEGER DEFAULT 0, + post_count INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT true +); + +INSERT INTO forum_categories (name, slug, description, icon_emoji, sort_order) VALUES +('Discussรฃo de Casos', 'case-discussion', 'Discuta casos clรญnicos especรญficos', '๐Ÿฉบ', 1), +('Dicas de Estudo', 'study-tips', 'Compartilhe estratรฉgias e mรฉtodos de estudo', '๐Ÿ“š', 2), +('Estratรฉgias de Prova', 'exam-strategies', 'Tรกticas para diferentes provas de residรชncia', 'โœ๏ธ', 3), +('Dรบvidas Clรญnicas', 'clinical-questions', 'Tire dรบvidas sobre medicina', 'โ“', 4), +('Motivaรงรฃo', 'motivation', 'Apoio e motivaรงรฃo durante a jornada', '๐Ÿ’ช', 5), +('Anรบncios', 'announcements', 'Novidades da plataforma', '๐Ÿ“ข', 6); + +CREATE TABLE forum_posts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + category_id UUID NOT NULL REFERENCES forum_categories(id), + + -- Content + title TEXT NOT NULL, + content TEXT NOT NULL, + + -- Context + related_case_id UUID REFERENCES clinical_cases(id), + related_specialty TEXT, + tags TEXT[], + + -- Engagement + view_count INTEGER DEFAULT 0, + upvote_count INTEGER DEFAULT 0, + comment_count INTEGER DEFAULT 0, + + -- Status + is_pinned BOOLEAN DEFAULT false, + is_locked BOOLEAN DEFAULT false, + is_solved BOOLEAN DEFAULT false, -- For questions + accepted_answer_id UUID, -- For questions + + -- Moderation + is_flagged BOOLEAN DEFAULT false, + flag_count INTEGER DEFAULT 0 +); + +CREATE INDEX idx_posts_category ON forum_posts(category_id, created_at DESC); +CREATE INDEX idx_posts_user ON forum_posts(user_id); +CREATE INDEX idx_posts_popular ON forum_posts(upvote_count DESC, created_at DESC); +CREATE INDEX idx_posts_case ON forum_posts(related_case_id) WHERE related_case_id IS NOT NULL; + +CREATE TABLE forum_comments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + post_id UUID NOT NULL REFERENCES forum_posts(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + parent_comment_id UUID REFERENCES forum_comments(id), -- For threaded replies + + content TEXT NOT NULL, + + -- Engagement + upvote_count INTEGER DEFAULT 0, + is_accepted_answer BOOLEAN DEFAULT false, + + -- Quality signals + is_expert_answer BOOLEAN DEFAULT false, -- From verified educator/doctor + is_edited BOOLEAN DEFAULT false, + edited_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_comments_post ON forum_comments(post_id, created_at); +CREATE INDEX idx_comments_user ON forum_comments(user_id); +CREATE INDEX idx_comments_parent ON forum_comments(parent_comment_id) WHERE parent_comment_id IS NOT NULL; + +CREATE TABLE forum_votes ( + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + -- Polymorphic: can vote on posts or comments + votable_type TEXT NOT NULL CHECK (votable_type IN ('post', 'comment')), + votable_id UUID NOT NULL, + + vote_value INTEGER NOT NULL CHECK (vote_value IN (-1, 1)), -- -1 downvote, 1 upvote + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + + PRIMARY KEY (user_id, votable_type, votable_id) +); + +CREATE INDEX idx_votes_votable ON forum_votes(votable_type, votable_id); + +-- ============================================================================ +-- FUNCTIONS & TRIGGERS FOR NETWORK EFFECTS +-- ============================================================================ + +-- Update group stats when member activity happens +CREATE OR REPLACE FUNCTION update_group_stats() +RETURNS TRIGGER AS $$ +BEGIN + -- Update active member count + UPDATE study_groups + SET active_members_count = ( + SELECT COUNT(*) + FROM study_group_members + WHERE group_id = NEW.group_id + AND last_active_at > NOW() - INTERVAL '7 days' + ) + WHERE id = NEW.group_id; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_group_stats + AFTER INSERT OR UPDATE ON study_group_members + FOR EACH ROW + EXECUTE FUNCTION update_group_stats(); + +-- Update creator stats when content is purchased +CREATE OR REPLACE FUNCTION update_creator_stats() +RETURNS TRIGGER AS $$ +BEGIN + UPDATE creator_profiles + SET + total_revenue_earned = total_revenue_earned + COALESCE(NEW.price_paid_reais, 0), + total_students_reached = ( + SELECT COUNT(DISTINCT user_id) + FROM content_purchases + WHERE content_id IN ( + SELECT id FROM premium_content WHERE creator_user_id = ( + SELECT creator_user_id FROM premium_content WHERE id = NEW.content_id + ) + ) + ) + WHERE user_id = ( + SELECT creator_user_id FROM premium_content WHERE id = NEW.content_id + ); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_creator_stats + AFTER INSERT ON content_purchases + FOR EACH ROW + EXECUTE FUNCTION update_creator_stats(); + +-- Update forum post comment count +CREATE OR REPLACE FUNCTION update_post_comment_count() +RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'INSERT' THEN + UPDATE forum_posts + SET comment_count = comment_count + 1 + WHERE id = NEW.post_id; + ELSIF TG_OP = 'DELETE' THEN + UPDATE forum_posts + SET comment_count = comment_count - 1 + WHERE id = OLD.post_id; + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_post_comment_count + AFTER INSERT OR DELETE ON forum_comments + FOR EACH ROW + EXECUTE FUNCTION update_post_comment_count(); + +-- ============================================================================ +-- ROW LEVEL SECURITY POLICIES +-- ============================================================================ + +-- Community cases: Anyone can read approved, only creator can edit draft +ALTER TABLE community_cases ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Anyone can view approved community cases" ON community_cases + FOR SELECT USING (status = 'approved' OR created_by_user_id = auth.uid()); + +CREATE POLICY "Users can create own community cases" ON community_cases + FOR INSERT WITH CHECK (created_by_user_id = auth.uid()); + +CREATE POLICY "Users can update own draft cases" ON community_cases + FOR UPDATE USING (created_by_user_id = auth.uid() AND status IN ('draft', 'needs_revision')); + +-- Study groups: Members can view, admins can edit +ALTER TABLE study_groups ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Anyone can view public groups" ON study_groups + FOR SELECT USING ( + is_public = true + OR id IN ( + SELECT group_id FROM study_group_members WHERE user_id = auth.uid() + ) + ); + +CREATE POLICY "Members can view their groups" ON study_groups + FOR SELECT USING ( + id IN (SELECT group_id FROM study_group_members WHERE user_id = auth.uid()) + ); + +-- Marketplace: Buyers can see purchased content +ALTER TABLE premium_content ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Anyone can view published premium content" ON premium_content + FOR SELECT USING (status = 'published' OR creator_user_id = auth.uid()); + +ALTER TABLE content_purchases ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own purchases" ON content_purchases + FOR SELECT USING (user_id = auth.uid()); + +-- Forum: Public read, authenticated write +ALTER TABLE forum_posts ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Anyone can view forum posts" ON forum_posts + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can create posts" ON forum_posts + FOR INSERT WITH CHECK (auth.role() = 'authenticated' AND user_id = auth.uid()); + +CREATE POLICY "Users can update own posts" ON forum_posts + FOR UPDATE USING (user_id = auth.uid()); + +ALTER TABLE forum_comments ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Anyone can view comments" ON forum_comments + FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can comment" ON forum_comments + FOR INSERT WITH CHECK (auth.role() = 'authenticated' AND user_id = auth.uid()); + +-- ============================================================================ +-- ANALYTICS VIEWS (Materialized for performance) +-- ============================================================================ + +-- Daily network effect metrics +CREATE MATERIALIZED VIEW network_metrics_daily AS +SELECT + DATE(created_at) as date, + COUNT(DISTINCT user_id) as daily_active_users, + COUNT(*) as total_interactions, + + -- Social metrics + (SELECT COUNT(*) FROM study_group_members WHERE DATE(joined_at) = DATE(i.created_at)) as new_group_joins, + (SELECT COUNT(*) FROM peer_interactions WHERE DATE(created_at) = DATE(i.created_at)) as peer_interactions_count, + + -- Content metrics + (SELECT COUNT(*) FROM community_cases WHERE DATE(submitted_at) = DATE(i.created_at)) as community_cases_submitted, + (SELECT COUNT(*) FROM content_purchases WHERE DATE(purchased_at) = DATE(i.created_at)) as marketplace_purchases, + + -- Engagement depth + AVG(time_to_answer_seconds) as avg_time_per_case, + AVG(CASE WHEN is_correct THEN 1.0 ELSE 0.0 END) as platform_success_rate + +FROM interactions i +GROUP BY DATE(created_at); + +CREATE UNIQUE INDEX ON network_metrics_daily(date); + +-- Refresh daily (run as cron job) +-- SELECT cron.schedule('refresh-network-metrics', '0 2 * * *', 'REFRESH MATERIALIZED VIEW CONCURRENTLY network_metrics_daily'); + +-- ============================================================================ +-- SAMPLE QUERIES FOR PRODUCT ANALYTICS +-- ============================================================================ + +COMMENT ON TABLE network_metrics_daily IS 'Sample query: SELECT * FROM network_metrics_daily WHERE date > NOW() - INTERVAL ''30 days'' ORDER BY date;'; + +COMMENT ON TABLE study_groups IS ' +-- Find most active study groups +SELECT + sg.name, + sg.active_members_count, + sg.total_cases_solved, + sg.avg_group_success_rate +FROM study_groups sg +WHERE sg.is_archived = false +ORDER BY sg.total_cases_solved DESC +LIMIT 10; +'; + +COMMENT ON TABLE premium_content IS ' +-- Top selling marketplace content +SELECT + pc.title, + cp.display_name as creator, + pc.purchases_count, + pc.avg_rating, + pc.revenue_generated +FROM premium_content pc +JOIN creator_profiles cp ON pc.creator_user_id = cp.user_id +WHERE pc.status = ''published'' +ORDER BY pc.revenue_generated DESC +LIMIT 10; +';