mirror of
https://github.com/Zie619/n8n-workflows.git
synced 2026-09-01 15:10:47 +08:00
feat: Add scalability & network effects architecture
## Strategic Additions ### 1. Product Strategy (PRODUCT_STRATEGY.md) Complete go-to-market and competitive moat strategy: **Network Effects Design:** - Data Network Effect: AI improves with every interaction - Content Network Effect: Community-contributed cases - Social Network Effect: Study groups & peer learning - Marketplace Network Effect: Two-sided creator economy **Defensible Moats:** - Data Moat: Proprietary ML models trained on millions of interactions - Network Moat: Social lock-in via study groups - Content Moat: Largest validated case library - Brand Moat: Community identity and trust - Regulatory Moat: Official partnerships with medical schools **SaaS Business Model:** - Phase 1: Freemium (10% conversion target) - Phase 2: Tiered pricing ($19-79/month) - Phase 3: B2B SaaS (medical schools) - Phase 4: API licensing & Enterprise **10-Year Vision:** Year 1-2: Best residency exam prep in Brazil Year 3-5: Platform for all medical education Year 5-7: Expand to Latin America Year 7-10: Global medical education platform ($50M+ ARR) ### 2. Network Effects Schema (schema-network-effects.sql) Complete database extension for social platform features: **New Tables (20+):** - Community cases & reviews (content network) - Study groups & challenges (social network) - Marketplace & creator profiles (two-sided market) - Forum & discussions (community) - Peer matching & interactions (social graph) - Leaderboards & competitions (gamification) - Calibration & ML models (data network) **Key Features:** - Row Level Security on all tables - Automatic triggers for stats updates - Materialized views for analytics - Cross-table relationships for network effects ### 3. Scalability Architecture (SCALABILITY_ARCHITECTURE.md) Technical roadmap from 0 to 1M+ users: **Stage 1 (0-10k users):** - Cost: $410/month - Stack: Vercel + Supabase + Claude API - No caching needed **Stage 2 (10k-100k users):** - Cost: $2,919/month ($0.029/user) - Add: Redis caching, read replicas, monitoring - 70% cache hit rate reduces AI costs **Stage 3 (100k-1M users):** - Cost: $11,700/month ($0.012/user) - Add: Database sharding (4 shards) - Microservices for AI, analytics - Background job processing - 95% AI response caching **Stage 4 (1M+ users):** - Cost: $50-100k/month - Full microservices architecture - Dedicated services per domain - ClickHouse for analytics - Global CDN distribution **Key Insights:** - Cost per user DECREASES with scale (economies of scale) - 95% AI cost reduction through intelligent caching - Zero-downtime deployments required - Progressive scaling (build for today, architect for tomorrow) ## Why This Matters **For Investors:** - Clear path to $50M+ ARR - Defensible moats (4-5 years to replicate) - 93% gross margins at scale - Network effects create winner-take-all dynamics **For Developers:** - Concrete technical roadmap - Know exactly when to scale what - Cost predictability - No premature optimization **For Users:** - Platform gets better with every user (network effects) - Social features create stickiness - Community-driven content - Clear value proposition This is the blueprint for building an unassailable position in medical education.
This commit is contained in:
@@ -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?
|
||||
@@ -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<AIFeedback> {
|
||||
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<T>(
|
||||
operation: string,
|
||||
fn: () => Promise<T>
|
||||
): Promise<T> {
|
||||
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.
|
||||
@@ -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;
|
||||
';
|
||||
Reference in New Issue
Block a user