feat(lessons): implement comprehensive lesson configurator, session runner and AI debrief (Beta)

This commit is contained in:
Antigravity committed 2026-10-07 13:03:16 +03:00
1 parent 31e105b2c2
commit 718e9797eb
12 files changed
+4093 -1

No files matched your search

+191
View File
@@ -73,6 +73,17 @@ from app.listening import (
evaluate_listening_answer,
prewarm_all_listening_audio,
)
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
@@ -2044,4 +2055,184 @@ async def api_tasks_cancel(
return {"status": "ok" if cancelled else "not_found", "cancelled": cancelled}
# -----------------------------------------------------------------------------
# Comprehensive Lesson Configurator & Runner Endpoints (Beta)
# -----------------------------------------------------------------------------
@app.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}
class SavePresetRequest(BaseModel):
name: str
steps: List[Dict[str, Any]]
icon: Optional[str] = "🚀"
description: Optional[str] = ""
estimated_min: Optional[int] = 15
@app.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}
@app.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}
@app.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
@app.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}
class LessonDebriefRequest(BaseModel):
preset_name: str
duration_seconds: int
total_score: int
steps: List[Dict[str, Any]]
errors: List[Dict[str, Any]]
@app.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"),
}
@app.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