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
+56
View File
@@ -0,0 +1,56 @@
"""
Единый профиль ученика — источник правды для аналитики и повторения.
Никакой LLM здесь нет — только детерминированная бизнес-логика.
"""
import hashlib
import json
from decimal import Decimal
async def get_weak_skills(conn, user_id: int, limit: int = 5):
"""Получить слабые навыки (mastery < 75 или needs_review/overdue или repeated_error).
Одинаково используется в /analytics и /review."""
rows = await conn.fetch("""
SELECT skill_tag, mastery_score, repeated_error_count,
retention_status, trend, recent_accuracy,
recognition_score, production_score, application_score,
median_correct_time_ms, days_since_last_attempt
FROM user_skill_mastery
WHERE user_id = $1 AND attempts_count >= 2
AND (mastery_score < 75
OR retention_status IN ('needs_review', 'overdue')
OR repeated_error_count > 0)
ORDER BY mastery_score ASC
LIMIT $2
""", user_id, limit)
return [dict(r) for r in rows]
async def get_strong_skills(conn, user_id: int, limit: int = 8):
"""Получить сильные навыки (mastery >= 75)."""
rows = await conn.fetch("""
SELECT skill_tag, mastery_score FROM user_skill_mastery
WHERE user_id = $1 AND attempts_count >= 2 AND mastery_score >= 75
ORDER BY mastery_score DESC
LIMIT $2
""", user_id, limit)
return [dict(r) for r in rows]
def get_profile_hash(weak_skills: list) -> str:
"""Хэш списка слабых навыков — для инвалидации кэша."""
# Конвертируем Decimal в float для JSON
serializable = []
for s in weak_skills:
item = {}
for k, v in s.items():
if isinstance(v, Decimal):
item[k] = float(v)
else:
item[k] = v
serializable.append(item)
canonical = json.dumps(serializable, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(canonical.encode()).hexdigest()[:16]