Обзор системы
10 сервисов, 3 инфраструктурных компонента, 83k строк кода
flowchart LR
subgraph "Клиент"
TG["Telegram Bot
aiogram 3.x
25k LOC"]
end
subgraph "Gateway"
GW["API Gateway :8100
proxy + auth + rate
1.8k LOC"]
end
subgraph "Core сервисы"
direction TB
PS["Progress :8103
FSRS, Selector
19k LOC"]
AI["AI :8105
AI Hub, Pipeline
14.6k LOC"]
ES["Exercise :8102
Concept Grid
3.3k LOC"]
end
subgraph "Доп. сервисы"
direction TB
LS["Lesson :8101
YAML уроки
2.3k LOC"]
PF["Profile :8104
Профили
4.4k LOC"]
EN["Engagement :8106
XP, Диалоги
2.6k LOC"]
EC["Error Clustering :8107
Qdrant
1.8k LOC"]
AA["Answer Analytics
Async
0.4k LOC"]
end
subgraph "Данные"
direction TB
PG[("PostgreSQL
65 таблиц")]
RD[("Redis
Events + Cache
FSM + Embed")]
QD[("Qdrant
Векторы 1536d")]
end
TG -->|HTTP| GW
GW --> PS & AI & ES
GW --> LS & PF & EN & EC
PS & ES & AI & PF & EN & LS --> PG
AI & EC & AA --> QD
TG -.->|Events| RD
PS -.->|Events| RD
AI -.->|Events| RD
EN -.->|Events| RD
AI -->|"GPT-4o / Claude"| AIAPI["OpenAI + Anthropic"]
style TG fill:#1f6feb,stroke:#58a6ff,color:#fff
style GW fill:#238636,stroke:#3fb950,color:#fff
style PG fill:#6e40c9,stroke:#bc8cff,color:#fff
style RD fill:#9e6a03,stroke:#d29922,color:#fff
style QD fill:#8b4513,stroke:#f0883e,color:#fff
style PS fill:#b62324,stroke:#f85149,color:#fff
style AI fill:#b62324,stroke:#f85149,color:#fff
Ключевые факты
- Красным выделены самые большие сервисы: Progress (19k LOC) и AI (14.6k LOC)
- Все HTTP-запросы идут через Gateway :8100 (JWT авторизация)
- Events — Redis Pub/Sub (DB0), без persistence (потеря при рестарте)
- Бот напрямую подключается к Redis для FSM (DB3) и публикации событий (DB0)
- AI Service вызывает внешние API: OpenAI (GPT-4o, GPT-4o-mini) и Anthropic (Sonnet, Opus)
Путь /go — от кнопки до результата
12 шагов полного цикла урока
sequenceDiagram
participant U as Ученик
participant B as Telegram Bot
participant GW as Gateway :8100
participant PS as Progress :8103
participant ES as Exercise :8102
participant AI as AI :8105
U->>B: /go
Note over B: ai_lesson_engine()
B->>GW: GET /profile/{user_id}
GW->>B: язык, уровень, настройки
B->>GW: GET /progress/next-concept/{user_id}
GW->>PS: ConceptSelector
Note over PS: ghost → due → weak → new
PS->>GW: concept_id, review_type, mastery
GW->>B: Концепт выбран
B->>GW: GET /exercises/concept/{id}/session
GW->>ES: block_exercises WHERE concept_id
ES->>GW: exercises[] + theory_blocks
GW->>B: Пул 5-15 упражнений
Note over B: MasterySession создана
B->>U: Интро: теория + кнопки
U->>B: "Начать упражнения"
Note over B: FSM → waiting_for_answer
loop Каждое упражнение
B->>U: Задание + варианты/ввод
U->>B: Ответ
Note over B: AnswerChecker
8 уровней проверки
alt Нужна AI проверка
B->>GW: POST /ai/check-answer
GW->>AI: GPT-4o-mini
AI->>GW: is_correct + reason
GW->>B: Результат
end
B->>GW: POST /progress/attempts
GW->>PS: FSRS update + mastery recalc
PS->>GW: new_mastery_level
B->>U: Фидбек (верно/неверно + объяснение)
end
Note over B: session.is_ended()?
B->>U: Результат: X/Y правильно, +XP
Критические точки
- ConceptSelector — единственный путь к выбору материала. Приоритеты: ghost → due → weak → new
- MasterySession — адаптивная сессия 5-15 упражнений. Заканчивается при 5 подряд правильных или 2 подряд неправильных
- AnswerChecker — 8-уровневый каскад (см. Tab 5). Для
translation,open_answer,transformation— сразу AI - FSRS update — SM-2 quality(0-5) конвертируется в FSRS rating(1-4), обновляется due_date
Архитектура концептов
469 концептов (235 ES + 735 EN), иерархия и mapping
flowchart LR
subgraph "Таксономия"
CAT["Категории
grammar, vocabulary,
pronunciation, culture"]
CON["Концепты
469 всего
235 ES / 735 EN"]
CB["Content Blocks
Теория + микро-концепты"]
EX["Упражнения
8 905 активных
9 типов"]
end
CAT --> CON
CON --> CB
CB --> EX
CON -.->|"каждый имеет"| F1
CON -.->|"граф зависимостей"| P1
CON -.->|"для русскоговорящих"| D5
subgraph "Поля концепта"
F1["concept_key
(articulo_definido)"]
F2["russian_l1_difficulty
1=лёгкий → 5=тяжёлый"]
F3["fsrs_init_difficulty
начальная сложность"]
F4["requires_explicit_cf
нужна Corrective Feedback"]
F5["early_intervention_critical
произношение"]
end
subgraph "Prerequisites (пример)"
P1["articles"] --> P2["noun_gender"]
P2 --> P3["adjective_agreement"]
P3 --> P4["noun_adj_position"]
end
subgraph "L1 Difficulty (русские)"
D5["5 — Артикли
mult 0.7 (больше повторений)"]
D4["4 — Предлоги, Perfect
mult 0.75"]
D3["3 — Средние
mult 0.85"]
D2["2 — Знакомые
mult 1.0"]
D1["1 — Когнаты
mult 1.1 (быстрее)"]
end
style D5 fill:#b62324,stroke:#f85149,color:#fff
style D4 fill:#9e6a03,stroke:#d29922,color:#fff
style D3 fill:#6e40c9,stroke:#bc8cff,color:#fff
style D2 fill:#238636,stroke:#3fb950,color:#fff
style D1 fill:#1f6feb,stroke:#58a6ff,color:#fff
Структура данных
- concepts →
content_blocks→block_exercises— трёхуровневая иерархия в PostgreSQL - concept_key — строковый ID (напр.
articulo_definido,ser_vs_estar) - L1 difficulty — 971 концепт с мультипликатором для русскоговорящих. Артикли (5) повторяются на 30% чаще
- Prerequisites — граф зависимостей. Новый концепт не предлагается, пока prerequisite не mastered
- Content Blocks — теория разбита на микро-блоки, к каждому привязаны упражнения
Генерация упражнений
От концепта до экрана: 9 типов, 3-Agent Pipeline, SSOT
flowchart TB
subgraph "SSOT: exercise_types.py"
T1["fill_gaps 25%
Input Processing"]
T2["transformation 20%
Focus on Form"]
T3["error_correction 10%
Metalinguistic"]
T4["translation 10%
Pushed Output L1→L2"]
T5["minimal_pairs 10%
Meaning Contrast"]
T6["open_answer 10%
Free Production"]
T7["multiple_choice 5%
Quick Recognition"]
T8["matching 5%
Pair Linking"]
T9["reorder 5%
Syntax Awareness"]
end
NORM["NORMALIZATION_MAP
54 legacy → 9 canonical"]
NORM --> T1
NORM --> T2
NORM --> T3
NORM --> T4
NORM --> T5
NORM --> T6
NORM --> T7
NORM --> T8
NORM --> T9
subgraph "3-Agent Pipeline (Прокачай)"
CP["CurriculumPlanner
Алгоритм
skeleton + gaps"]
MP["Methodologist
Claude Sonnet
~$0.03/вызов"]
OR["OpusReviewer
Claude Opus
~$0.05/вызов
4 критерия"]
end
CP --> MP --> OR
subgraph "Автоматическая генерация"
GEN["generator.py
1130 LOC"]
OPT["option_designer.py
782 LOC"]
CSD["closed_set_detector.py
36 правил: 26 ES + 10 EN"]
end
GEN --> OPT
GEN --> CSD
subgraph "Выход"
DB[("block_exercises
8 905 записей")]
QS["quality_score
0-100"]
end
OR --> DB
GEN --> DB
DB --> QS
style T1 fill:#238636,stroke:#3fb950,color:#fff
style T2 fill:#1f6feb,stroke:#58a6ff,color:#fff
style OR fill:#b62324,stroke:#f85149,color:#fff
style QS fill:#9e6a03,stroke:#d29922,color:#fff
TARGET_TYPE_WEIGHTS
fill_gaps: 25% — пропуски (VanPatten Input Processing)transformation: 20% — преобразование (Long Focus on Form)error_correction: 10% — исправление ошибокtranslation: 10% — перевод RU→ESminimal_pairs: 10% — контраст значенийopen_answer: 10% — свободный ответmultiple_choice: 5% — выбор из 4matching: 5% — сопоставление парreorder: 5% — собери предложение
Реальность vs цели
- CRITICAL Факт: 86% fill_gaps vs цель 25%
- CRITICAL 99% упражнений quality < 80
NORMALIZATION_MAP— 54 legacy типа нормализуются в 9CLOSED_SETS— 36 правил для артиклей, предлогов и др.- 3-Agent Pipeline вызывается вручную через "Прокачай"
AnswerChecker — каскад проверки
8 уровней: от exact match до AI fallback
flowchart TB
START["Ответ ученика"] --> L0
L0{"Level 0
personal_info?
true_sentences?"}
L0 -->|Да| ACCEPT["Принять любой ответ"]
L0 -->|Нет| AICHECK
AICHECK{"requires_ai_check?
translation / open_answer /
transformation"}
AICHECK -->|Да| L3["Level 2+: AI сразу
GPT-4o-mini"]
AICHECK -->|Нет| L1
L1{"Level 1.0
Exact match?"}
L1 -->|Да| CORRECT["Правильно"]
L1 -->|Нет| L11
L11{"Level 1.1
Normalized?
lowercase + без акцентов"}
L11 -->|Да| CORRECT
L11 -->|Нет| L21
L21{"Level 2.1
Опечатка?
Levenshtein ≤ 2"}
L21 -->|Да| TYPO["Принято с пометкой
Орфография: correct"]
L21 -->|Нет| L22
L22{"Level 2.2
error_correction?
слово из предложения"}
L22 -->|Да| CORRECT
L22 -->|Нет| L23
L23{"Level 2.3
Core extraction?
ядро ответа совпадает"}
L23 -->|Да| CORRECT
L23 -->|Нет| L25
L25{"Level 2.5
Disputed?
Sonnet vs Opus"}
L25 -->|Оба согласны| WRONG["Неправильно"]
L25 -->|Расхождение| DISPUTED["Принято как
альтернативный вариант"]
L3 --> L3R{"AI результат"}
L3R -->|correct| CORRECT
L3R -->|incorrect| WRONG
L3R -->|disputed| DISPUTED
style CORRECT fill:#238636,stroke:#3fb950,color:#fff
style WRONG fill:#b62324,stroke:#f85149,color:#fff
style TYPO fill:#9e6a03,stroke:#d29922,color:#fff
style DISPUTED fill:#6e40c9,stroke:#bc8cff,color:#fff
style ACCEPT fill:#1f6feb,stroke:#58a6ff,color:#fff
Типы, требующие AI проверки
translation— перевод может быть правильным в разных формулировкахopen_answer— свободный текст, нет единственного правильного ответаtransformation— преобразование предложения допускает вариацииpersonal_info,true_sentences— принимают любой ответ (Level 0)- Disputed — когда Sonnet и Opus дают разные оценки, принимается как альтернативный вариант
FSRS и прогресс
Spaced Repetition + Ghost Review + L1 мультипликаторы
stateDiagram-v2
[*] --> New: Новый концепт
New --> Learning: Первый ответ
Learning --> Review: Правильные ответы
Review --> Review: Правильный (Good/Easy)
Review --> Relearning: Неправильный (Again)
Relearning --> Review: Снова правильный
state "Ghost Review" as Ghost {
[*] --> Step0: 2+ ошибки подряд
Step0: Шаг 0 — через 4 часа
Step0 --> Step1: Если опять ошибка
Step1: Шаг 1 — через 12 часов
Step1 --> Step2: Если опять ошибка
Step2: Шаг 2 — через 24 часа
Step2 --> Step3: Если опять ошибка
Step3: Шаг 3 — через 48 часов
}
Review --> Ghost: 2 подряд неправильных
Ghost --> Review: Правильный ответ
SM-2 → FSRS конверсия
- SM-2 quality 0-2 → FSRS rating 1 (Again)
- SM-2 quality 3 → FSRS rating 2 (Hard)
- SM-2 quality 4 → FSRS rating 3 (Good)
- SM-2 quality 5 → FSRS rating 4 (Easy)
- Target retention: 87%
L1 мультипликаторы (русские)
- 5 Артикли — ×0.7 (больше повторений)
- 4 Предлоги, Perfect — ×0.75
- 3 Средние — ×0.85
- 2 Знакомые — ×1.0
- 1 Когнаты — ×1.1 (быстрее)
ConceptSelector — что учить дальше
4-приоритетная очередь, единственный путь /go
flowchart TB START["/go запрос"] --> CHECK["Получить все
progress_records
для user_id"] CHECK --> P1{"P1: Ghost Review?
ghost_review_active = true
ghost_review_next ≤ now"} P1 -->|Есть| G1["Пул Ghost-концептов"] P1 -->|Нет| P2 P2{"P2: Due Review?
fsrs_data.due_date ≤ now
mastery ≥ learning"} P2 -->|Есть| G2["Пул Due-концептов"] P2 -->|Нет| P3 P3{"P3: Weak Spots?
confidence_score ≤ 70%
attempts ≥ 3"} P3 -->|Есть| G3["Пул Weak-концептов"] P3 -->|Нет| P4 P4{"P4: New Concept?
prerequisites met?
не изучался сегодня?"} P4 -->|Есть| G4["Пул New-концептов"] P4 -->|Нет| EMPTY["Нет доступных
концептов"] G1 & G2 & G3 & G4 --> RND["Случайный из пула 5"] RND --> RESULT["concept_id +
review_type +
mastery_level"] style P1 fill:#b62324,stroke:#f85149,color:#fff style P2 fill:#9e6a03,stroke:#d29922,color:#fff style P3 fill:#6e40c9,stroke:#bc8cff,color:#fff style P4 fill:#238636,stroke:#3fb950,color:#fff style G1 fill:#b62324,stroke:#f85149,color:#fff style G2 fill:#9e6a03,stroke:#d29922,color:#fff style G3 fill:#6e40c9,stroke:#bc8cff,color:#fff style G4 fill:#238636,stroke:#3fb950,color:#fff
Алгоритм
- P1 GHOST 2+ consecutive failures → срочное повторение (fossilization prevention)
- P2 DUE FSRS scheduled review → плановое повторение
- P3 WEAK confidence < 70% при 3+ попытках → целевая практика
- P4 NEW prerequisites выполнены, не изучался сегодня → новый материал
- Из каждого пула берётся случайный из 5 (разнообразие)
- Exclude: концепты, изученные сегодня (anti-cramming)
Data Model — ключевые таблицы
PostgreSQL :5433, 65 таблиц, основные связи
erDiagram
concepts {
int id PK
string concept_key UK
string category
string name
string name_ru
int russian_l1_difficulty
float fsrs_init_difficulty
bool requires_explicit_cf
bool early_intervention_critical
}
content_blocks {
int id PK
int concept_id FK
text theory_text
jsonb theory_blocks
jsonb source_textbooks
jsonb source_units
bool is_active
}
block_exercises {
int id PK
int concept_id FK
int block_id FK
string exercise_type
string difficulty_tier
text task
text correct_answer
text explanation_ru
text hint
text format_hint
jsonb options
int quality_score
bool is_active
string review_status
}
progress_records {
int id PK
int user_id
int concept_id FK
string context_type
jsonb attempts
string srs_algorithm
jsonb sm2_data
jsonb fsrs_data
int confidence_score
string mastery_level
bool ghost_review_active
int ghost_review_step
timestamp first_practiced
timestamp last_practiced
}
student_profiles {
int user_id PK
jsonb preferences
string target_language
string current_textbook
}
user_answers {
int id PK
int user_id
int exercise_id FK
text user_answer
bool is_correct
int response_time_ms
string session_id
}
concepts ||--o{ content_blocks : "теория"
content_blocks ||--o{ block_exercises : "упражнения"
concepts ||--o{ progress_records : "прогресс"
concepts ||--o{ block_exercises : "напрямую"
block_exercises ||--o{ user_answers : "ответы"
Ключевые JSONB поля
fsrs_data: state, stability, difficulty, reps, lapses, due_date, mastery_leveltheory_blocks: массив {key, title, content} — микро-блоки теорииoptions: массив вариантов для multiple_choice, matching, minimal_pairspreferences: learning_settings (target_language, current_textbook, daily_goal)mastery_level: enum (unknown, beginner, learning, familiar, proficient, mastered)
Event Bus — Redis Pub/Sub
Асинхронные события между сервисами (Redis DB0)
flowchart LR
subgraph "Публикаторы"
direction TB
BOT["Telegram Bot"]
ES["Exercise :8102"]
PS["Progress :8103"]
PF["Profile :8104"]
end
subgraph "Redis DB0 Pub/Sub"
direction TB
E1["exercise.answered"]
E3["content.pool_low"]
E2["progress.updated"]
E4["profile.updated"]
E5["lesson.completed"]
end
subgraph "Подписчики"
direction TB
PS2["Progress :8103"]
AA["Answer Analytics"]
PF2["Profile :8104"]
EN["Engagement :8106"]
AI2["AI :8105"]
end
BOT -->|"ответ"| E1
ES -->|"валидация"| E1
ES -->|"пул ≤ 8"| E3
PS -->|"FSRS"| E2
PF -->|"изменён"| E4
BOT -->|"пройден"| E5
E1 --> PS2
E1 --> AA
E1 --> PF2
E3 --> AI2
E2 --> EN
E4 --> PS2
E5 --> EN
style E1 fill:#b62324,stroke:#f85149,color:#fff
style E2 fill:#9e6a03,stroke:#d29922,color:#fff
style E3 fill:#6e40c9,stroke:#bc8cff,color:#fff
Важно
- RISK Redis Pub/Sub не сохраняет события. При рестарте Redis — потеря всех событий в полёте
exercise.answered— самое частое событие: каждый ответ ученикаcontent.pool_low— триггер автогенерации когда упражнений < 8- Answer Analytics — фоновый worker, записывает embeddings в Qdrant
- Формат события:
{type, data, timestamp, service, correlation_id}
AI Routing — модели и стоимость
Smart routing: дешёвая модель для простых задач, дорогая для сложных
flowchart TB
REQ["Запрос"] --> ROUTER{"AI Router
Определяет сложность"}
ROUTER -->|"Простое: проверка,
batch, fallback"| MINI["GPT-4o-mini
$0.15 / 1M tokens"]
ROUTER -->|"Стандартное: генерация,
теория, анализ ошибок"| GPT4["GPT-4o
$2.50 / 1M tokens"]
ROUTER -->|"Методическое: prompt
для упражнений"| SONNET["Claude Sonnet
$3.00 / 1M tokens"]
ROUTER -->|"Ревью: проверка
качества, disputed"| OPUS["Claude Opus
$15.00 / 1M tokens"]
subgraph "Embeddings"
EMB["text-embedding-3-small
1536 dims
$0.02 / 1M tokens"]
end
subgraph "Кэш (Redis DB1)"
CACHE["TTL 30 дней
Ключ: hash(prompt)
Экономия ~70%"]
end
GPT4 --> CACHE
SONNET --> CACHE
MINI --> CACHE
EMB --> QD[("Qdrant :6333")]
ROUTER -.->|"стоимость"| C1
subgraph "Стоимость за сессию"
C1["Проверка ответа: ~$0.001"]
C2["Генерация упражнения: ~$0.01"]
C3["Методист (Прокачай): ~$0.03"]
C4["Ревью Opus: ~$0.05"]
C5["Разжуй теорию: ~$0.02"]
end
style MINI fill:#238636,stroke:#3fb950,color:#fff
style GPT4 fill:#1f6feb,stroke:#58a6ff,color:#fff
style SONNET fill:#6e40c9,stroke:#bc8cff,color:#fff
style OPUS fill:#b62324,stroke:#f85149,color:#fff
Принцип: самая дешёвая подходящая модель
- GPT-4o-mini — проверка ответов (Level 3 fallback), batch-операции
- GPT-4o — генерация упражнений, анализ ошибок, теория
- Claude Sonnet — Methodologist в 3-Agent Pipeline, "Разжуй теорию"
- Claude Opus — OpusReviewer, disputed answers (высшая инстанция)
- Кэш Redis DB1 с TTL 30 дней — экономия ~70% на повторных запросах
FSM состояния бота
Все экраны и переходы Telegram бота
flowchart TB START(["start / menu"]) --> idle["idle
Главное меню"] idle -->|"/go"| concept_intro["concept_intro
Теория + кнопки"] concept_intro -->|"Начать"| waiting["waiting_for_answer
Ввод ответа"] waiting -->|"Ответ"| feedback["feedback
Результат"] feedback -->|"Следующее"| waiting feedback -->|"Сессия окончена"| complete["concept_complete
Результаты"] complete -->|"Следующий"| concept_intro complete -->|"В меню"| idle concept_intro -->|"Разжуй теорию"| theory_ov["theory_overview
Обзор секций"] theory_ov -->|"Секция"| theory_sec["theory_section
Формула / Ловушки / ..."] theory_sec -->|"Обзор"| theory_ov theory_sec -->|"Другая"| theory_sec theory_ov -->|"Практика"| waiting idle -->|"/reading"| phrase["waiting_for_phrase
Ввод фразы"] phrase -->|"Текст"| analysis["phrase_analysis
Разбор"] analysis -->|"Ещё"| phrase idle -->|"/import_kindle"| vocab["waiting_for_vocab
Ожидание vocab.db"] vocab -->|"Файл"| imported["import_complete"] idle -->|"/lang"| lang["lang_select"] lang -->|"Выбрано"| idle idle -->|"/stats"| stats_view["stats
Статистика"] stats_view -->|"В меню"| idle style idle fill:#238636,stroke:#3fb950,color:#fff style waiting fill:#1f6feb,stroke:#58a6ff,color:#fff style concept_intro fill:#6e40c9,stroke:#bc8cff,color:#fff style phrase fill:#9e6a03,stroke:#d29922,color:#fff style theory_ov fill:#6e40c9,stroke:#bc8cff,color:#fff
FSM States (aiogram)
ExerciseStates.waiting_for_answer— основное состояние ввода ответаExerciseStates.waiting_for_unit_number— ввод номера юнита (YAML path)ReadingStates.waiting_for_phrase— ввод фразы для анализаReadingStates.waiting_for_vocab_file— ожидание файла Kindle vocab.db- FSM storage: Redis DB3
- Callback patterns: 80+ штук, формат
domain:action[:param]
Проблемы и гэпы
Что сломано, что не работает, что отсутствует
flowchart TB
subgraph "CRITICAL — Сломано"
C1["Distribution Skew
86% fill_gaps vs 25% цель
Генератор игнорирует
TARGET_TYPE_WEIGHTS"]
C2["Качество упражнений
99% quality_score ≤ 80
Нужен массовый re-review"]
end
subgraph "MEDIUM — Работает плохо"
M1["Opus Reviewer
Недостаточно строгий
для translation"]
M2["Event Bus
Redis Pub/Sub без persistence
Потеря событий при рестарте"]
M3["10 микросервисов
Distributed monolith
для 1 пользователя"]
end
subgraph "GAP — Отсутствует"
G1["Learning Outcomes
Нет метрик D1/D7/D30
Строим вслепую"]
G2["Session Analytics
Нет данных о длине сессий
и вовлечённости"]
G3["Concept Taxonomy
235 концептов слишком гранулярно
Надо 60-80"]
end
subgraph "Архитектурный долг"
D1["Bot: 25k LOC
Слишком много логики
в presentation layer"]
D2["Progress: 19k LOC
Самый большой сервис
нужен рефакторинг"]
D3["Shared generator: 1130 LOC
Файл > лимита 300"]
end
C1 -.->|"вызывает"| M1
C2 -.->|"вызывает"| M1
M2 -.->|"усугубляет"| G1
M2 -.->|"усугубляет"| G2
D1 -.->|"замедляет фикс"| C1
D2 -.->|"замедляет фикс"| C2
style C1 fill:#b62324,stroke:#f85149,color:#fff
style C2 fill:#b62324,stroke:#f85149,color:#fff
style M1 fill:#9e6a03,stroke:#d29922,color:#fff
style M2 fill:#9e6a03,stroke:#d29922,color:#fff
style M3 fill:#9e6a03,stroke:#d29922,color:#fff
style G1 fill:#6e40c9,stroke:#bc8cff,color:#fff
style G2 fill:#6e40c9,stroke:#bc8cff,color:#fff
style G3 fill:#6e40c9,stroke:#bc8cff,color:#fff
Приоритеты (рекомендация экспертов — Tab 13)
- P0 Починить distribution: генератор должен следовать TARGET_TYPE_WEIGHTS
- P0 Массовый re-review упражнений с quality_score < 50
- P1 Добавить метрики learning outcomes (retention D1/D7/D30)
- P2 Укрупнить концепты: 235 → 60-80 для испанского
- P3 Упростить архитектуру: модульный монолит вместо 10 микросервисов
Анализ экспертов
Что сказали бы лучшие специалисты о системе
Bill VanPatten
SLA Researcher, Input Processing Theory
235 испанских концептов — слишком гранулярно. Студент думает: "когда ставить el, а когда la?", а не articulo_definido_con_sustantivos_masculinos. Таксономия должна быть от коммуникативных задач, не от грамматических категорий. Исключения из правил — часть того же концепта с пометкой "irregular", а не отдельные записи в базе.
Piotr Wozniak
Создатель SM-2, идеолог Spaced Repetition
FSRS — правильный выбор, он точнее SM-2 на 12%. Но 235 концептов × 5 context_types = 1175 карточек для одного языка. Это scheduling hell — система не может эффективно планировать столько элементов. Ghost review step 0 (через 4 часа) — слишком агрессивно, студент уйдёт. Укрупнить до 50-80 концептов, вариации обрабатывать внутри одного scheduling unit.
Martin Fowler
Software Architecture, Enterprise Patterns
10 микросервисов для 1 пользователя — это distributed monolith. У вас нет независимого деплоя, нет разных команд, нет разной нагрузки по сервисам. Модульный монолит с чёткими границами модулей даст 90% бенефитов микросервисов при 10% сложности. Event Bus через Redis Pub/Sub без persistence — это потеря данных при каждом рестарте. Если нужны события — используйте Redis Streams с acknowledgment.
Duolingo Data Science Team
EdTech, Learning Analytics, A/B Testing
86% fill_gaps при таргете 25% — генератор не работает. Это самый критический баг в системе. Но главная проблема глубже: вы оптимизируете архитектуру вместо learning outcomes. Нет метрик retention (D1/D7/D30), нет данных о длине сессий, нет accuracy trend по времени. Без этих метрик вы строите вслепую — невозможно понять, учится ли вообще студент.
Консенсус: приоритеты действий
- Сейчас Починить distribution — генератор должен следовать TARGET_TYPE_WEIGHTS. Без этого все упражнения одинаковые.
- Неделя Укрупнить концепты 235→60-80. Меньше scheduling units = лучше FSRS, меньше confusion для ученика.
- Месяц Добавить learning outcome метрики: retention D1/D7/D30, accuracy trend, session analytics.
- Квартал Упростить архитектуру: модульный монолит. 10 сервисов → 1 приложение с модулями.