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:
Antigravity committed 2026-10-05 11:42:13 +03:00
1 parent c51c505b61
commit d8a2c6e34f
9 files changed
+2101 -30

No files matched your search

+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,
}