feat: implement Stage 5 (Interactive Reader, EPUB/TXT parser, Bookshelf, Click-to-Translate) and Stage 4.3 (Strict CEFR level calculation)

This commit is contained in:
2026-10-05 11:42:13 +03:00
parent c51c505b61
commit d8a2c6e34f
9 changed files with 2101 additions and 30 deletions
+54
View File
@@ -248,3 +248,57 @@ def build_activity_heatmap(review_dates: Dict[str, int], days: int = 180) -> Dic
"current_streak": current_streak,
"longest_streak": max(longest_streak, current_streak),
}
def calculate_cefr_level(
mastered_count: int,
review_count: int,
retention_rate: float,
total_cards: int = 0
) -> Dict[str, Any]:
"""
Computes strict CEFR level based on active vocabulary weight and memory retention rate (PLAN.md 4.3).
Only words in Mastered (weight 1.0) and Review (weight 0.5) stages count, scaled by retention rate %.
"""
safe_retention = max(0.0, min(100.0, float(retention_rate)))
retention_factor = (safe_retention / 100.0) if safe_retention > 0 else 1.0
# Active weighted vocabulary: Mastered gives 1.0, Review gives 0.5
weighted_words = (mastered_count * 1.0) + (review_count * 0.5)
active_vocab = int(round(weighted_words * retention_factor))
# CEFR thresholds: (min_words, level, title, next_level, next_target)
brackets = [
(0, "A1", "Начальный словарный запас", "A2", 300),
(300, "A2", "Элементарный словарный запас", "B1", 800),
(800, "B1", "Уверенное понимание общих и рабочих тем", "B2", 1600),
(1600, "B2", "Свободное чтение тех. литературы и документации", "C1", 3000),
(3000, "C1", "Продвинутый профессиональный уровень", "C2", 5000),
]
current_bracket = brackets[0]
for b in brackets:
if active_vocab >= b[0]:
current_bracket = b
min_w, level, title, next_lvl, next_w = current_bracket
words_to_next = max(0, next_w - active_vocab)
range_span = next_w - min_w
current_progress = active_vocab - min_w
pct = int(min(100, max(0, (current_progress / range_span) * 100))) if range_span > 0 else 100
if words_to_next > 0:
message = f"До уровня {next_lvl} осталось закрепить {words_to_next} активных слов"
else:
message = f"Уровень {level} полностью освоен!"
return {
"level": level,
"title": title,
"active_vocab": active_vocab,
"next_level": next_lvl,
"words_to_next": words_to_next,
"progress_percent": pct,
"message": message,
"retention_rate": safe_retention,
}