feat(cefr): CEFR level testing and automated gap ingestion engine (Task 15)
This commit is contained in:
1 parent
1c3f03a64c
commit
3fae321322
11 files changed
+2926
-10
No files matched your search
+87
@@ -31,6 +31,12 @@ from app.srs import calculate_sm2, generate_quiz_options, build_activity_heatmap
|
||||
from app.reader import parse_epub, parse_text, parse_pdf, count_words
|
||||
from app.ai import translate_sentence_ai, get_ollama_status
|
||||
from app.phrasal import detect_phrasal_verb
|
||||
from app.cefr import (
|
||||
get_cefr_level_summaries,
|
||||
get_cefr_quiz_batch,
|
||||
process_cefr_answer,
|
||||
reset_user_cefr_level,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +168,16 @@ class SentenceTranslateRequest(BaseModel):
|
||||
target_word: Optional[str] = ""
|
||||
|
||||
|
||||
class CefrAnswerRequest(BaseModel):
|
||||
question_id: int
|
||||
selected_option: str
|
||||
|
||||
|
||||
class CefrResetRequest(BaseModel):
|
||||
level: str
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PWA Static Endpoints
|
||||
@@ -543,6 +559,18 @@ async def api_create_card(card: CardCreateRequest, user: dict = Depends(require_
|
||||
|
||||
|
||||
|
||||
@app.get("/api/cards/{card_id}")
|
||||
async def api_get_card(card_id: int, user: dict = Depends(require_user)):
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM vocabulary_cards WHERE id = ? AND user_id = ?",
|
||||
(card_id, user["id"]),
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Card not found")
|
||||
return dict(row)
|
||||
|
||||
|
||||
@app.put("/api/cards/{card_id}")
|
||||
async def api_update_card(card_id: int, update: CardUpdateRequest, user: dict = Depends(require_user)):
|
||||
with get_db() as conn:
|
||||
@@ -1352,3 +1380,62 @@ async def api_delete_book(book_id: int, user: dict = Depends(require_user)):
|
||||
if cursor.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
return {"status": "ok", "deleted_id": book_id}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CEFR Level Testing & Automated Gap Ingestion Endpoints (Task 15)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.get("/api/cefr/levels")
|
||||
async def api_cefr_levels(user: dict = Depends(require_user)):
|
||||
"""
|
||||
Returns progress summaries for all CEFR levels (A1, A2, B1, B2).
|
||||
"""
|
||||
with get_db() as conn:
|
||||
summaries = get_cefr_level_summaries(conn, user["id"])
|
||||
return {"levels": summaries}
|
||||
|
||||
|
||||
@app.get("/api/cefr/quiz")
|
||||
async def api_cefr_quiz(
|
||||
level: str = Query("A1"),
|
||||
count: int = Query(10, ge=1, le=30),
|
||||
mode: str = Query("all"),
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Returns next batch of Cloze Tap test questions for level (A1..B2).
|
||||
"""
|
||||
with get_db() as conn:
|
||||
quiz_items = get_cefr_quiz_batch(conn, user["id"], level, count, mode)
|
||||
return {"level": level.upper(), "count": len(quiz_items), "quiz": quiz_items}
|
||||
|
||||
|
||||
@app.post("/api/cefr/answer")
|
||||
async def api_cefr_answer(
|
||||
req: CefrAnswerRequest,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Submits an answer for a CEFR question.
|
||||
If wrong, automatically ingests into user's SRS active vocabulary with status 'learning'.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
result = process_cefr_answer(conn, user["id"], req.question_id, req.selected_option)
|
||||
if "error" in result:
|
||||
raise HTTPException(status_code=404, detail=result["error"])
|
||||
return result
|
||||
|
||||
|
||||
@app.post("/api/cefr/reset")
|
||||
async def api_cefr_reset(
|
||||
req: CefrResetRequest,
|
||||
user: dict = Depends(require_user),
|
||||
):
|
||||
"""
|
||||
Resets user testing progress for a given CEFR level.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
deleted = reset_user_cefr_level(conn, user["id"], req.level)
|
||||
return {"status": "ok", "level": req.level.upper(), "reset_count": deleted}
|
||||
|
||||
Reference in new issue
Block a user