refactor(stage-4): partition monolithic main.py into modular APIRouters
This commit is contained in:
1 parent
4e1f809018
commit
dde87e5d6c
15 files changed
+2202
-2138
No files matched your search
@@ -0,0 +1,193 @@
|
||||
from typing import Optional, List, Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.auth import require_user
|
||||
from app.database import get_db
|
||||
from app.lessons import (
|
||||
get_user_presets,
|
||||
save_user_preset,
|
||||
delete_user_preset,
|
||||
get_grammar_recommendations,
|
||||
get_curated_articles,
|
||||
record_lesson_session,
|
||||
get_lesson_history,
|
||||
)
|
||||
from app.ai import generate_lesson_ai_debrief
|
||||
|
||||
router = APIRouter(tags=["Lessons"])
|
||||
|
||||
|
||||
class SavePresetRequest(BaseModel):
|
||||
name: str
|
||||
steps: List[Dict[str, Any]]
|
||||
icon: Optional[str] = "🚀"
|
||||
description: Optional[str] = ""
|
||||
estimated_min: Optional[int] = 15
|
||||
|
||||
|
||||
class LessonDebriefRequest(BaseModel):
|
||||
preset_name: str
|
||||
duration_seconds: int
|
||||
total_score: int
|
||||
steps: List[Dict[str, Any]]
|
||||
errors: List[Dict[str, Any]]
|
||||
|
||||
|
||||
@router.get("/api/lessons/presets")
|
||||
async def api_lessons_get_presets(user: dict = Depends(require_user)):
|
||||
"""
|
||||
Returns available lesson presets (defaults + user-custom).
|
||||
"""
|
||||
with get_db() as conn:
|
||||
presets = get_user_presets(conn, user["id"])
|
||||
return {"presets": presets}
|
||||
|
||||
|
||||
@router.post("/api/lessons/presets")
|
||||
async def api_lessons_save_preset(
|
||||
req: SavePresetRequest,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Saves a custom user lesson configuration preset.
|
||||
"""
|
||||
if not req.name.strip():
|
||||
raise HTTPException(status_code=400, detail="Название шаблона не может быть пустым")
|
||||
with get_db() as conn:
|
||||
saved = save_user_preset(
|
||||
conn,
|
||||
user_id=user["id"],
|
||||
name=req.name,
|
||||
steps=req.steps,
|
||||
icon=req.icon or "🚀",
|
||||
description=req.description or "",
|
||||
estimated_min=req.estimated_min or 15,
|
||||
)
|
||||
return {"status": "ok", "preset": saved}
|
||||
|
||||
|
||||
@router.delete("/api/lessons/presets/{preset_id}")
|
||||
async def api_lessons_delete_preset(
|
||||
preset_id: int,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Deletes a custom user lesson preset.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
deleted = delete_user_preset(conn, user["id"], preset_id)
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="Пользовательский шаблон не найден")
|
||||
return {"status": "ok", "deleted": True}
|
||||
|
||||
|
||||
@router.get("/api/lessons/grammar-recommendations")
|
||||
async def api_lessons_grammar_recommendations(user: dict = Depends(require_user)):
|
||||
"""
|
||||
Calculates intelligent grammar suggestions: weakest topic, last studied topic,
|
||||
next in syllabus, and full units list.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
recs = get_grammar_recommendations(conn, user["id"])
|
||||
return recs
|
||||
|
||||
|
||||
@router.get("/api/lessons/articles")
|
||||
async def api_lessons_articles(
|
||||
topic: Optional[str] = None,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Returns curated high-yield reading articles/facts for the lesson reading block.
|
||||
"""
|
||||
articles = get_curated_articles(topic=topic)
|
||||
return {"articles": articles}
|
||||
|
||||
|
||||
@router.post("/api/lessons/debrief")
|
||||
async def api_lessons_debrief(
|
||||
req: LessonDebriefRequest,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Generates a pedagogic AI debrief for the completed lesson session,
|
||||
enriches with historical error context, and records the session.
|
||||
"""
|
||||
# 1. Fetch user error history for pedagogical pattern detection
|
||||
with get_db() as conn:
|
||||
chronic_rows = conn.execute(
|
||||
"""
|
||||
SELECT v.term, count(*) as err_cnt
|
||||
FROM srs_logs l
|
||||
JOIN vocabulary_cards v ON v.id = l.card_id
|
||||
WHERE l.user_id = ? AND l.rating = 1
|
||||
GROUP BY v.term
|
||||
HAVING err_cnt > 1
|
||||
ORDER BY err_cnt DESC
|
||||
LIMIT 10
|
||||
""",
|
||||
(user["id"],),
|
||||
).fetchall()
|
||||
chronic_vocab = [r["term"] for r in chronic_rows]
|
||||
|
||||
prog_rows = conn.execute(
|
||||
"""
|
||||
SELECT u.unit_num
|
||||
FROM grammar_progress p
|
||||
JOIN grammar_units u ON u.id = p.unit_id
|
||||
WHERE p.user_id = ? AND p.score < 80
|
||||
""",
|
||||
(user["id"],),
|
||||
).fetchall()
|
||||
past_grammar = [r["unit_num"] for r in prog_rows]
|
||||
|
||||
history_data = {
|
||||
"chronic_vocab_errors": chronic_vocab,
|
||||
"past_grammar_errors": past_grammar,
|
||||
}
|
||||
|
||||
session_payload = {
|
||||
"preset_name": req.preset_name,
|
||||
"duration_seconds": req.duration_seconds,
|
||||
"total_score": req.total_score,
|
||||
"steps": req.steps,
|
||||
"errors": req.errors,
|
||||
}
|
||||
|
||||
# 2. Generate AI or deterministic debrief
|
||||
ai_report = await generate_lesson_ai_debrief(session_payload, history_data)
|
||||
|
||||
# 3. Save completed session to SQLite
|
||||
with get_db() as conn:
|
||||
session_id = record_lesson_session(
|
||||
conn=conn,
|
||||
user_id=user["id"],
|
||||
preset_name=req.preset_name,
|
||||
duration_seconds=req.duration_seconds,
|
||||
total_score=req.total_score,
|
||||
grade=ai_report.get("grade", "A"),
|
||||
steps_summary=req.steps,
|
||||
errors_list=req.errors,
|
||||
ai_report=ai_report,
|
||||
)
|
||||
|
||||
return {
|
||||
"session_id": session_id,
|
||||
"ai_report": ai_report,
|
||||
"total_score": req.total_score,
|
||||
"grade": ai_report.get("grade", "A"),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/api/lessons/history")
|
||||
async def api_lessons_history(
|
||||
limit: int = 15,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Returns history of completed comprehensive lessons and overall learning metrics.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
data = get_lesson_history(conn, user["id"], limit=limit)
|
||||
return data
|
||||
Reference in new issue
Block a user