194 lines
5.6 KiB
Python
194 lines
5.6 KiB
Python
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
|