""" Единый профиль ученика — источник правды для аналитики и повторения. Никакой 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]