initial commit (backend)

This commit is contained in:
2026-09-20 11:45:58 +00:00
commit 033adf023e
65 changed files with 28292 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
"""
Тесты для get_next_lesson.
Проверяют приоритеты: overdue → weak → test → next.
"""
import pytest
from datetime import datetime, timedelta
# ─────────────────────────────────────────────
# ХЕЛПЕРЫ
# ─────────────────────────────────────────────
async def set_user_level(conn, user_id: int, level: str):
await conn.execute("UPDATE users SET current_level = $1 WHERE telegram_id = $2", level, user_id)
async def insert_mastery(
conn,
user_id: int,
skill_tag: str,
mastery: float,
retention: str = "fresh",
attempts: int = 3,
days_since: int = 0,
):
"""Вставляет запись в user_skill_mastery."""
last_attempt = datetime.now() - timedelta(days=days_since)
await conn.execute("""
INSERT INTO user_skill_mastery (
user_id, skill_tag, mastery_score, retention_status,
attempts_count, last_attempt_at
) VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (user_id, skill_tag) DO UPDATE SET
mastery_score = $3,
retention_status = $4,
attempts_count = $5,
last_attempt_at = $6
""", user_id, skill_tag, mastery, retention, attempts, last_attempt)
# ─────────────────────────────────────────────
# ТЕСТЫ
# ─────────────────────────────────────────────
async def test_orchestrator_prioritizes_overdue(conn, test_user, create_test_lesson, clean_mastery):
"""Если есть overdue навык — он выбирается первым."""
from routers.orchestrator import get_next_lesson
await set_user_level(conn, test_user, "A2")
await create_test_lesson("test_skill")
await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="overdue", days_since=20)
result = await get_next_lesson(conn, test_user)
assert result["type"] == "review"
assert result["reason"] == "overdue"
async def test_orchestrator_prioritizes_weak(conn, test_user, create_test_lesson, clean_mastery):
"""Если нет overdue, но есть weak (mastery < 75) — выбирается он."""
from routers.orchestrator import get_next_lesson
await set_user_level(conn, test_user, "A2")
await create_test_lesson("test_skill")
await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="fresh", days_since=1)
result = await get_next_lesson(conn, test_user)
assert result["type"] == "review"
assert result["reason"] == "weak_mastery"
async def test_orchestrator_prefers_overdue_over_weak(conn, test_user, create_test_lesson, clean_mastery):
"""Если есть и overdue, и weak — overdue важнее."""
from routers.orchestrator import get_next_lesson
await set_user_level(conn, test_user, "A2")
await create_test_lesson("weak_skill", order=1)
await create_test_lesson("overdue_skill", order=2)
await insert_mastery(conn, test_user, "weak_skill", mastery=40.0, retention="fresh", days_since=1)
await insert_mastery(conn, test_user, "overdue_skill", mastery=60.0, retention="overdue", days_since=20)
result = await get_next_lesson(conn, test_user)
assert result["reason"] == "overdue"
async def test_orchestrator_weak_requires_min_attempts(conn, test_user, create_test_lesson, clean_mastery):
"""Weak skill требует минимум 2 попытки — если меньше, он не выбирается."""
from routers.orchestrator import get_next_lesson
await set_user_level(conn, test_user, "A2")
await create_test_lesson("test_skill")
await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="fresh", attempts=1)
result = await get_next_lesson(conn, test_user)
assert result["reason"] != "weak_mastery"