muhammad naveed commited on
Commit
a02d20d
·
verified ·
1 Parent(s): 0510d8d

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. Dockerfile +21 -0
  2. README.md +34 -5
  3. app/__pycache__/main.cpython-314.pyc +0 -0
  4. app/main.py +553 -0
  5. requirements.txt +7 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Create non-root user for Hugging Face
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ # Copy requirements and install
11
+ COPY --chown=user:user requirements.txt .
12
+ RUN pip install --no-cache-dir --user -r requirements.txt
13
+
14
+ # Copy application code
15
+ COPY --chown=user:user app/ ./app/
16
+
17
+ # Hugging Face Spaces uses port 7860
18
+ EXPOSE 7860
19
+
20
+ # Start the application
21
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,39 @@
1
  ---
2
- title: Learnflow Api
3
- emoji: 📊
4
- colorFrom: gray
5
- colorTo: red
6
  sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: LearnFlow API
3
+ emoji: 🐍
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
+ app_port: 7860
10
  ---
11
 
12
+ # LearnFlow - AI-Powered Python Learning API
13
+
14
+ This is the backend API for LearnFlow, an AI-powered Python tutoring platform.
15
+
16
+ ## Features
17
+
18
+ - **AI Chat**: Get Python help powered by OpenRouter/GPT
19
+ - **User Authentication**: JWT-based auth system
20
+ - **Code Execution**: Execute Python code safely
21
+ - **Progress Tracking**: Track learning progress
22
+
23
+ ## API Endpoints
24
+
25
+ - `GET /` - API Info
26
+ - `GET /health` - Health check
27
+ - `POST /auth/register` - Register new user
28
+ - `POST /auth/login` - Login
29
+ - `POST /chat` - AI chat endpoint
30
+ - `GET /docs` - Swagger documentation
31
+
32
+ ## Environment Variables
33
+
34
+ Set these in your Space secrets:
35
+ - `OPENROUTER_API_KEY` - Your OpenRouter API key
36
+
37
+ ## Usage
38
+
39
+ Visit `/docs` for interactive API documentation.
app/__pycache__/main.cpython-314.pyc ADDED
Binary file (26.1 kB). View file
 
app/main.py ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Depends, status
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4
+ from pydantic import BaseModel, EmailStr
5
+ from typing import Optional, List
6
+ import os
7
+ import jwt
8
+ import httpx
9
+ import hashlib
10
+ import json
11
+ from datetime import datetime, timedelta
12
+
13
+ app = FastAPI(title="LearnFlow API Gateway")
14
+
15
+ # CORS configuration
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["http://localhost:3000", "http://localhost:3001", "*"],
19
+ allow_credentials=True,
20
+ allow_methods=["*"],
21
+ allow_headers=["*"],
22
+ )
23
+
24
+ # Configuration
25
+ SECRET_KEY = os.getenv("JWT_SECRET_KEY", "learnflow-secret-key-change-in-production")
26
+ ALGORITHM = "HS256"
27
+ ACCESS_TOKEN_EXPIRE_HOURS = 24
28
+
29
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
30
+ OPENROUTER_BASE_URL = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
31
+ LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-3.5-turbo")
32
+
33
+ # Service URLs
34
+ SERVICES = {
35
+ "triage": os.getenv("TRIAGE_URL", "http://localhost:8001"),
36
+ "concepts": os.getenv("CONCEPTS_URL", "http://localhost:8002"),
37
+ "code_review": os.getenv("CODE_REVIEW_URL", "http://localhost:8003"),
38
+ "debug": os.getenv("DEBUG_URL", "http://localhost:8004"),
39
+ "exercise": os.getenv("EXERCISE_URL", "http://localhost:8005"),
40
+ "progress": os.getenv("PROGRESS_URL", "http://localhost:8006"),
41
+ }
42
+
43
+ # In-memory user store (replace with PostgreSQL in production)
44
+ users_db = {}
45
+
46
+ security = HTTPBearer()
47
+
48
+ # ==================== MODELS ====================
49
+
50
+ class UserRegister(BaseModel):
51
+ name: str
52
+ email: EmailStr
53
+ password: str
54
+ role: str = "student"
55
+
56
+ class UserLogin(BaseModel):
57
+ email: EmailStr
58
+ password: str
59
+
60
+ class UserResponse(BaseModel):
61
+ id: str
62
+ name: str
63
+ email: str
64
+ role: str
65
+
66
+ class AuthResponse(BaseModel):
67
+ token: str
68
+ user: UserResponse
69
+
70
+ class ChatMessage(BaseModel):
71
+ role: str
72
+ content: str
73
+
74
+ class ChatRequest(BaseModel):
75
+ messages: List[ChatMessage]
76
+ user_id: Optional[str] = None
77
+ context: Optional[str] = None
78
+
79
+ class ChatResponse(BaseModel):
80
+ response: str
81
+ agent_used: str
82
+
83
+ class CodeExecuteRequest(BaseModel):
84
+ code: str
85
+ user_id: Optional[str] = None
86
+
87
+ class CodeExecuteResponse(BaseModel):
88
+ output: str
89
+ error: Optional[str] = None
90
+ execution_time_ms: int
91
+
92
+ class ExplainRequest(BaseModel):
93
+ topic: str
94
+ level: str = "intermediate"
95
+
96
+ # ==================== AUTH HELPERS ====================
97
+
98
+ def hash_password(password: str) -> str:
99
+ return hashlib.sha256(password.encode()).hexdigest()
100
+
101
+ def create_token(user_id: str, email: str, role: str) -> str:
102
+ expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
103
+ payload = {
104
+ "sub": user_id,
105
+ "email": email,
106
+ "role": role,
107
+ "exp": expire
108
+ }
109
+ return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
110
+
111
+ def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
112
+ try:
113
+ payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
114
+ return payload
115
+ except jwt.ExpiredSignatureError:
116
+ raise HTTPException(status_code=401, detail="Token expired")
117
+ except jwt.InvalidTokenError:
118
+ raise HTTPException(status_code=401, detail="Invalid token")
119
+
120
+ # ==================== AUTH ENDPOINTS ====================
121
+
122
+ @app.post("/auth/register", response_model=AuthResponse)
123
+ async def register(data: UserRegister):
124
+ if data.email in users_db:
125
+ raise HTTPException(status_code=400, detail="Email already registered")
126
+
127
+ user_id = hashlib.md5(data.email.encode()).hexdigest()[:12]
128
+ hashed_password = hash_password(data.password)
129
+
130
+ users_db[data.email] = {
131
+ "id": user_id,
132
+ "name": data.name,
133
+ "email": data.email,
134
+ "password": hashed_password,
135
+ "role": data.role,
136
+ "created_at": datetime.utcnow().isoformat()
137
+ }
138
+
139
+ token = create_token(user_id, data.email, data.role)
140
+
141
+ return AuthResponse(
142
+ token=token,
143
+ user=UserResponse(
144
+ id=user_id,
145
+ name=data.name,
146
+ email=data.email,
147
+ role=data.role
148
+ )
149
+ )
150
+
151
+ @app.post("/auth/login", response_model=AuthResponse)
152
+ async def login(data: UserLogin):
153
+ user = users_db.get(data.email)
154
+
155
+ if not user or user["password"] != hash_password(data.password):
156
+ raise HTTPException(status_code=401, detail="Invalid email or password")
157
+
158
+ token = create_token(user["id"], user["email"], user["role"])
159
+
160
+ return AuthResponse(
161
+ token=token,
162
+ user=UserResponse(
163
+ id=user["id"],
164
+ name=user["name"],
165
+ email=user["email"],
166
+ role=user["role"]
167
+ )
168
+ )
169
+
170
+ @app.get("/auth/me", response_model=UserResponse)
171
+ async def get_current_user(payload: dict = Depends(verify_token)):
172
+ user = users_db.get(payload["email"])
173
+ if not user:
174
+ raise HTTPException(status_code=404, detail="User not found")
175
+
176
+ return UserResponse(
177
+ id=user["id"],
178
+ name=user["name"],
179
+ email=user["email"],
180
+ role=user["role"]
181
+ )
182
+
183
+ # ==================== AI CHAT ENDPOINT ====================
184
+
185
+ @app.post("/chat", response_model=ChatResponse)
186
+ async def chat_with_ai(data: ChatRequest, payload: dict = Depends(verify_token)):
187
+ """
188
+ Chat with AI tutor using OpenRouter API
189
+ """
190
+ if not OPENROUTER_API_KEY:
191
+ # Fallback to simulated response if no API key
192
+ return ChatResponse(
193
+ response=get_simulated_response(data.messages[-1].content if data.messages else ""),
194
+ agent_used="simulated"
195
+ )
196
+
197
+ try:
198
+ async with httpx.AsyncClient() as client:
199
+ # Build system prompt for Python tutor
200
+ system_prompt = """You are a friendly and knowledgeable Python programming tutor.
201
+ Your goal is to help students learn Python effectively.
202
+ - Explain concepts clearly with examples
203
+ - When students share code, provide constructive feedback
204
+ - If they have errors, help them understand why and how to fix them
205
+ - Encourage good coding practices
206
+ - Keep responses concise but informative
207
+ - Use code blocks for code examples"""
208
+
209
+ messages = [{"role": "system", "content": system_prompt}]
210
+ for msg in data.messages:
211
+ messages.append({"role": msg.role, "content": msg.content})
212
+
213
+ response = await client.post(
214
+ f"{OPENROUTER_BASE_URL}/chat/completions",
215
+ headers={
216
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
217
+ "Content-Type": "application/json",
218
+ "HTTP-Referer": "http://localhost:3000",
219
+ "X-Title": "LearnFlow Python Tutor"
220
+ },
221
+ json={
222
+ "model": LLM_MODEL,
223
+ "messages": messages,
224
+ "max_tokens": 1000,
225
+ "temperature": 0.7
226
+ },
227
+ timeout=30.0
228
+ )
229
+
230
+ if response.status_code == 200:
231
+ result = response.json()
232
+ ai_response = result["choices"][0]["message"]["content"]
233
+ return ChatResponse(
234
+ response=ai_response,
235
+ agent_used="openrouter"
236
+ )
237
+ else:
238
+ # Fallback to simulated
239
+ return ChatResponse(
240
+ response=get_simulated_response(data.messages[-1].content if data.messages else ""),
241
+ agent_used="simulated-fallback"
242
+ )
243
+ except Exception as e:
244
+ return ChatResponse(
245
+ response=get_simulated_response(data.messages[-1].content if data.messages else ""),
246
+ agent_used="simulated-error"
247
+ )
248
+
249
+ def get_simulated_response(query: str) -> str:
250
+ """Simulated AI response when API is unavailable"""
251
+ query_lower = query.lower()
252
+
253
+ if "for loop" in query_lower or "loop" in query_lower:
254
+ return """**For Loops in Python**
255
+
256
+ For loops iterate over sequences (lists, strings, ranges, etc.):
257
+
258
+ ```python
259
+ # Loop through a list
260
+ fruits = ['apple', 'banana', 'cherry']
261
+ for fruit in fruits:
262
+ print(fruit)
263
+
264
+ # Loop with range
265
+ for i in range(5):
266
+ print(i) # Prints 0, 1, 2, 3, 4
267
+
268
+ # Loop with enumerate
269
+ for index, fruit in enumerate(fruits):
270
+ print(f"{index}: {fruit}")
271
+ ```
272
+
273
+ Key points:
274
+ - `range(n)` generates numbers from 0 to n-1
275
+ - Use `enumerate()` when you need both index and value
276
+ - `break` exits the loop, `continue` skips to next iteration"""
277
+
278
+ elif "function" in query_lower or "def" in query_lower:
279
+ return """**Functions in Python**
280
+
281
+ Functions are reusable blocks of code:
282
+
283
+ ```python
284
+ # Basic function
285
+ def greet(name):
286
+ return f"Hello, {name}!"
287
+
288
+ # Function with default parameter
289
+ def greet(name, greeting="Hello"):
290
+ return f"{greeting}, {name}!"
291
+
292
+ # Function with multiple return values
293
+ def get_stats(numbers):
294
+ return min(numbers), max(numbers), sum(numbers)/len(numbers)
295
+
296
+ minimum, maximum, average = get_stats([1, 2, 3, 4, 5])
297
+ ```
298
+
299
+ Key points:
300
+ - Use `def` keyword to define functions
301
+ - Parameters can have default values
302
+ - Use `return` to send back values
303
+ - Functions can return multiple values as tuples"""
304
+
305
+ elif "list" in query_lower:
306
+ return """**Lists in Python**
307
+
308
+ Lists are ordered, mutable collections:
309
+
310
+ ```python
311
+ # Create a list
312
+ numbers = [1, 2, 3, 4, 5]
313
+ mixed = [1, "hello", 3.14, True]
314
+
315
+ # Access elements
316
+ first = numbers[0] # 1
317
+ last = numbers[-1] # 5
318
+
319
+ # Modify lists
320
+ numbers.append(6) # Add to end
321
+ numbers.insert(0, 0) # Insert at index
322
+ numbers.remove(3) # Remove value
323
+ popped = numbers.pop() # Remove and return last
324
+
325
+ # List comprehension
326
+ squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
327
+ ```
328
+
329
+ Key points:
330
+ - Lists are zero-indexed
331
+ - Use negative indices to access from the end
332
+ - List comprehensions are concise ways to create lists"""
333
+
334
+ elif "error" in query_lower or "debug" in query_lower:
335
+ return """**Debugging Python Errors**
336
+
337
+ Common errors and fixes:
338
+
339
+ 1. **SyntaxError**: Check for missing colons, parentheses, or quotes
340
+ 2. **NameError**: Variable not defined - check spelling
341
+ 3. **TypeError**: Wrong type - check your data types
342
+ 4. **IndexError**: List index out of range - check list length
343
+ 5. **KeyError**: Dictionary key not found - use `.get()` method
344
+
345
+ ```python
346
+ # Use try-except for error handling
347
+ try:
348
+ result = 10 / 0
349
+ except ZeroDivisionError:
350
+ print("Cannot divide by zero!")
351
+
352
+ # Debug with print statements
353
+ print(f"Variable value: {my_var}")
354
+
355
+ # Use type() to check types
356
+ print(type(my_var))
357
+ ```
358
+
359
+ Share your error message and I'll help you fix it!"""
360
+
361
+ else:
362
+ return """I'm your Python tutor! I can help you with:
363
+
364
+ - **Concepts**: Variables, loops, functions, classes, etc.
365
+ - **Code Review**: Share your code for feedback
366
+ - **Debugging**: Paste your error and I'll explain the fix
367
+ - **Exercises**: Practice problems to build skills
368
+
369
+ What would you like to learn about? Try asking:
370
+ - "How do for loops work?"
371
+ - "Explain functions in Python"
372
+ - "Help me fix this error: [paste error]"
373
+ - "Review my code: [paste code]" """
374
+
375
+ # ==================== CODE EXECUTION ENDPOINT ====================
376
+
377
+ @app.post("/execute", response_model=CodeExecuteResponse)
378
+ async def execute_code(data: CodeExecuteRequest, payload: dict = Depends(verify_token)):
379
+ """
380
+ Execute Python code in a sandboxed environment
381
+ """
382
+ import subprocess
383
+ import tempfile
384
+ import time
385
+
386
+ try:
387
+ # Create temporary file
388
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
389
+ f.write(data.code)
390
+ temp_file = f.name
391
+
392
+ start_time = time.time()
393
+
394
+ # Execute with timeout
395
+ result = subprocess.run(
396
+ ['python3', temp_file],
397
+ capture_output=True,
398
+ text=True,
399
+ timeout=5 # 5 second timeout
400
+ )
401
+
402
+ execution_time = int((time.time() - start_time) * 1000)
403
+
404
+ # Cleanup
405
+ os.unlink(temp_file)
406
+
407
+ if result.returncode == 0:
408
+ return CodeExecuteResponse(
409
+ output=result.stdout,
410
+ error=None,
411
+ execution_time_ms=execution_time
412
+ )
413
+ else:
414
+ return CodeExecuteResponse(
415
+ output=result.stdout,
416
+ error=result.stderr,
417
+ execution_time_ms=execution_time
418
+ )
419
+
420
+ except subprocess.TimeoutExpired:
421
+ os.unlink(temp_file)
422
+ return CodeExecuteResponse(
423
+ output="",
424
+ error="Execution timed out (5 second limit)",
425
+ execution_time_ms=5000
426
+ )
427
+ except Exception as e:
428
+ return CodeExecuteResponse(
429
+ output="",
430
+ error=str(e),
431
+ execution_time_ms=0
432
+ )
433
+
434
+ # ==================== CONCEPTS ENDPOINT ====================
435
+
436
+ @app.post("/explain")
437
+ async def explain_concept(data: ExplainRequest, payload: dict = Depends(verify_token)):
438
+ """
439
+ Get explanation for a Python concept
440
+ """
441
+ if OPENROUTER_API_KEY:
442
+ try:
443
+ async with httpx.AsyncClient() as client:
444
+ response = await client.post(
445
+ f"{OPENROUTER_BASE_URL}/chat/completions",
446
+ headers={
447
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
448
+ "Content-Type": "application/json",
449
+ },
450
+ json={
451
+ "model": LLM_MODEL,
452
+ "messages": [
453
+ {"role": "system", "content": f"You are a Python tutor. Explain concepts at a {data.level} level with examples."},
454
+ {"role": "user", "content": f"Explain {data.topic} in Python"}
455
+ ],
456
+ "max_tokens": 800
457
+ },
458
+ timeout=30.0
459
+ )
460
+
461
+ if response.status_code == 200:
462
+ result = response.json()
463
+ return {
464
+ "topic": data.topic,
465
+ "explanation": result["choices"][0]["message"]["content"],
466
+ "level": data.level
467
+ }
468
+ except:
469
+ pass
470
+
471
+ # Fallback
472
+ return {
473
+ "topic": data.topic,
474
+ "explanation": get_simulated_response(data.topic),
475
+ "level": data.level
476
+ }
477
+
478
+ # ==================== PROGRESS ENDPOINTS ====================
479
+
480
+ # In-memory progress store
481
+ progress_db = {}
482
+
483
+ @app.get("/progress/{user_id}")
484
+ async def get_progress(user_id: str, payload: dict = Depends(verify_token)):
485
+ """Get user's learning progress"""
486
+ if user_id not in progress_db:
487
+ progress_db[user_id] = {
488
+ "user_id": user_id,
489
+ "overall_mastery": 0,
490
+ "modules_completed": 0,
491
+ "current_module": "Basics",
492
+ "modules": {
493
+ "Basics": {"variables": {"mastery_score": 0}, "operators": {"mastery_score": 0}},
494
+ "Control Flow": {"if_statements": {"mastery_score": 0}, "loops": {"mastery_score": 0}},
495
+ "Functions": {"basic": {"mastery_score": 0}, "advanced": {"mastery_score": 0}},
496
+ },
497
+ "quiz_scores": [],
498
+ "total_time_spent": 0
499
+ }
500
+
501
+ return progress_db[user_id]
502
+
503
+ @app.post("/progress")
504
+ async def update_progress(data: dict, payload: dict = Depends(verify_token)):
505
+ """Update user's learning progress"""
506
+ user_id = data.get("user_id")
507
+ module = data.get("module")
508
+ topic = data.get("topic")
509
+ score = data.get("score", 0)
510
+
511
+ if user_id not in progress_db:
512
+ await get_progress(user_id, payload)
513
+
514
+ # Update module/topic score
515
+ if module in progress_db[user_id]["modules"]:
516
+ if topic in progress_db[user_id]["modules"][module]:
517
+ old_score = progress_db[user_id]["modules"][module][topic]["mastery_score"]
518
+ # Weighted average with new score
519
+ progress_db[user_id]["modules"][module][topic]["mastery_score"] = (old_score * 0.7) + (score * 100 * 0.3)
520
+
521
+ # Recalculate overall mastery
522
+ total_score = 0
523
+ count = 0
524
+ for mod in progress_db[user_id]["modules"].values():
525
+ for top in mod.values():
526
+ total_score += top["mastery_score"]
527
+ count += 1
528
+
529
+ progress_db[user_id]["overall_mastery"] = total_score / count if count > 0 else 0
530
+
531
+ return progress_db[user_id]
532
+
533
+ # ==================== HEALTH CHECK ====================
534
+
535
+ @app.get("/health")
536
+ async def health_check():
537
+ return {
538
+ "status": "healthy",
539
+ "service": "api-gateway",
540
+ "timestamp": datetime.utcnow().isoformat()
541
+ }
542
+
543
+ @app.get("/")
544
+ async def root():
545
+ return {
546
+ "message": "Welcome to LearnFlow API Gateway",
547
+ "docs": "/docs",
548
+ "health": "/health"
549
+ }
550
+
551
+ if __name__ == "__main__":
552
+ import uvicorn
553
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ pydantic[email]
4
+ python-jose[cryptography]
5
+ PyJWT
6
+ httpx
7
+ python-multipart