Обзор системы
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
16.4k 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"]
AA["Answer Analytics
Async
0.4k LOC"]
end
subgraph "Данные"
direction TB
PG[("PostgreSQL
65 таблиц")]
RD[("Redis
Events + Cache
FSM + Embed")]
end
TG -->|HTTP| GW
GW --> PS & AI & ES
GW --> LS & PF & EN
PS & ES & AI & PF & EN & LS --> PG
AA -.->|"Events"| RD
TG -.->|Events| RD
PS -.->|Events| RD
AI -.->|Events| RD
EN -.->|Events| RD
AI -->|"Sonnet / Haiku / Opus"| AIAPI["Anthropic (Claude)"]
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 вызывает Anthropic API: Sonnet (генерация), Haiku (проверка ответов + диагностика), Opus (ревью)
- Все 10 подсистем AI Repetitor: Tab 14 | Путь от /go до FSRS: Tab 15
Путь /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: Claude Haiku
AI->>GW: is_correct + diagnosis
GW->>B: Результат + ErrorDiagnosis
end
B->>GW: POST /progress/attempts
GW->>PS: FSRS update + mastery recalc
PS->>GW: new_mastery_level
B->>U: Фидбек (hint 1-я попытка / full 2-я попытка)
end
Note over B: session.is_ended()?
B->>U: Результат: X/Y правильно, +XP
Критические точки
- ConceptSelector — единственный путь к выбору материала. Приоритеты: ghost → due → weak → new
- MasterySession — адаптивная сессия 5-15 упражнений. Заканчивается при 5 подряд правильных или 2 подряд неправильных
- AnswerChecker + Diagnostics — multi-level каскад + ErrorDiagnosis (см. Tab 5). Rule-based (gender, accent, word_diff) + Claude Haiku. UI: Hint→Full
- FSRS update — SM-2 quality(0-5) конвертируется в FSRS rating(1-4), обновляется due_date
Архитектура концептов
1 132 концепта (396 ES + 736 EN), иерархия и mapping
flowchart LR
subgraph "Таксономия"
CAT["Категории
grammar, vocabulary,
pronunciation, culture"]
CON["Концепты
1 132 всего
396 ES / 736 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 + Error Diagnostics
3 файла: text_normalizer → error_diagnostics → answer_checker. Hint→Full UX.
flowchart TB
START["Ответ ученика"] --> L0
L0{"Level 0
personal_info?
true_sentences?"}
L0 -->|Да| ACCEPT["Принять любой ответ"]
L0 -->|Нет| L1
L1{"Level 1.0-1.1
Exact / Normalized?"}
L1 -->|Да| CORRECT["Правильно"]
L1 -->|Нет| MG
MG{"Level 2.4
Multi-gap?
2+ пропуска"}
MG -->|Да| CORRECT
MG -->|Нет| AICHECK
AICHECK{"requires_ai_check?
translation / open_answer /
transformation"}
AICHECK -->|Да| HAIKU["Claude Haiku
validation + diagnosis
1 вызов"]
AICHECK -->|Нет| L21
L21{"Level 2.1-2.5
Typo / Core /
Error correction /
Disputed"}
L21 -->|Совпало| CORRECT
L21 -->|Нет| FALLBACK["Level 3: Claude Haiku
AI fallback + diagnosis"]
HAIKU --> HR{"Результат"}
HR -->|correct| CORRECT
HR -->|incorrect| DIAG
FALLBACK --> FR{"Результат"}
FR -->|correct| CORRECT
FR -->|incorrect| DIAG
DIAG["ErrorDiagnosis"]
subgraph "Диагностика ошибок"
DIAG --> RULE{"Rule-based
FREE, <1ms"}
RULE -->|"gender
el↔la + 100 nouns"| HINT["hint_text + full_text"]
RULE -->|"accent
ó↔o, ñ↔n"| HINT
RULE -->|"word_diff
какие слова"| HINT
RULE -->|"не определено"| AI_DIAG["AI diagnosis
Claude Haiku"]
AI_DIAG --> HINT
end
HINT --> UI["Telegram UI"]
subgraph "Hint → Full UX"
UI --> A1["1-я попытка:
hint_text
без правильного ответа"]
UI --> A2["2-я попытка:
full_text + ответ
+ explanation"]
end
style CORRECT fill:#238636,stroke:#3fb950,color:#fff
style DIAG fill:#b62324,stroke:#f85149,color:#fff
style HINT fill:#9e6a03,stroke:#d29922,color:#fff
style ACCEPT fill:#1f6feb,stroke:#58a6ff,color:#fff
style HAIKU fill:#6e40c9,stroke:#bc8cff,color:#fff
style FALLBACK fill:#6e40c9,stroke:#bc8cff,color:#fff
style A1 fill:#9e6a03,stroke:#d29922,color:#fff
style A2 fill:#238636,stroke:#3fb950,color:#fff
3-файловая архитектура
text_normalizer.py(114 LOC) — атом: normalize, levenshtein, strip_accentserror_diagnostics.py(297 LOC) — молекула: rule-based + AI (Haiku)answer_checker.py(223 LOC) — организм: multi-level cascade + diagnostics
Rule-based диагностика (бесплатно)
- Gender — el↔la, un↔una + словарь 100 существительных (mesa→f, libro→m)
- Accent — Unicode decomposition: café vs cafe
- Word diff — token-level различия между ответом и правильным
AI диагностика (Claude Haiku)
- Validation + diagnosis в одном вызове (~$0.0005)
- JSON:
{is_correct, error_type, hint_ru, explanation_ru} - Типы ошибок: gender, tense, preposition, article, accent, vocabulary, word_order
Hint → Full UX
- 1-я попытка: "Обрати внимание на род существительного" (без ответа)
- 2-я попытка: "Mesa — ж.р. Правильно: la mesa, не el mesa" + explanation
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, записывает аналитику в PostgreSQL
- Формат события:
{type, data, timestamp, service, correlation_id}
AI Routing — модели и стоимость
Smart routing: дешёвая модель для простых задач, дорогая для сложных
flowchart TB
REQ["Запрос"] --> ROUTER{"AI Router
Определяет сложность"}
ROUTER -->|"Проверка ответов,
диагностика ошибок"| HAIKU["Claude Haiku
$0.80 / 1M tokens"]
ROUTER -->|"Генерация упражнений,
теория, анализ"| SONNET["Claude Sonnet
$3.00 / 1M tokens"]
ROUTER -->|"Ревью: проверка
качества, disputed"| OPUS["Claude Opus
$15.00 / 1M tokens"]
subgraph "Кэш (Redis DB1)"
CACHE["TTL 30 дней
Ключ: hash(prompt)
Экономия ~70%"]
end
HAIKU --> CACHE
SONNET --> CACHE
ROUTER -.->|"стоимость"| C1
subgraph "Стоимость за сессию"
C1["Проверка + диагностика: ~$0.001"]
C2["Генерация упражнения: ~$0.03"]
C3["Методист (Прокачай): ~$0.03"]
C4["Ревью Opus: ~$0.05"]
C5["Разжуй теорию: ~$0.02"]
end
style HAIKU fill:#238636,stroke:#3fb950,color:#fff
style SONNET fill:#6e40c9,stroke:#bc8cff,color:#fff
style OPUS fill:#b62324,stroke:#f85149,color:#fff
Принцип: самая дешёвая подходящая модель
- Claude Haiku — проверка ответов + диагностика ошибок (validation + diagnosis в одном вызове)
- Claude Sonnet — генерация упражнений (V3), Methodologist, "Разжуй теорию"
- 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
396 ES концептов после dedup
Было 488, укрупнение идёт"]
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 Укрупнить концепты: 396 → 60-80 для испанского (dedup сделан: 488→396)
- P3 Упростить архитектуру: модульный монолит вместо 10 микросервисов
Анализ экспертов
Что сказали бы лучшие специалисты о системе
235 испанских концептов — слишком гранулярно. Студент думает: "когда ставить el, а когда la?", а не articulo_definido_con_sustantivos_masculinos. Таксономия должна быть от коммуникативных задач, не от грамматических категорий. Исключения из правил — часть того же концепта с пометкой "irregular", а не отдельные записи в базе.
FSRS — правильный выбор, он точнее SM-2 на 12%. Но 235 концептов × 5 context_types = 1175 карточек для одного языка. Это scheduling hell — система не может эффективно планировать столько элементов. Ghost review step 0 (через 4 часа) — слишком агрессивно, студент уйдёт. Укрупнить до 50-80 концептов, вариации обрабатывать внутри одного scheduling unit.
10 микросервисов для 1 пользователя — это distributed monolith. У вас нет независимого деплоя, нет разных команд, нет разной нагрузки по сервисам. Модульный монолит с чёткими границами модулей даст 90% бенефитов микросервисов при 10% сложности. Event Bus через Redis Pub/Sub без persistence — это потеря данных при каждом рестарте. Если нужны события — используйте Redis Streams с acknowledgment.
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 приложение с модулями.
AI Repetitor v2
10 подсистем персонального AI-репетитора — полная схема от /go до FSRS
flowchart TB
subgraph USER ["Что видит ученик"]
GO["/go — Начать урок"]
ANSWER["Ввод ответа"]
FB["Обратная связь"]
STATS["/stats — Мой прогресс"]
end
subgraph ENGINE ["AI Repetitor — 10 подсистем"]
direction TB
CS["7. ConceptSelector
Что учить дальше?
4 приоритета: ghost → due → weak → new"]
TH["9. Theory Engine
AI объяснение на русском
3-tier cache (Redis 90д)"]
MS["3. Mastery Session
5-15 упражнений
accuracy ≥80% = done"]
GEN["6. Generator V3
3-Agent: Planner→Sonnet→Opus
9 типов, pool-first"]
AC["4. AnswerChecker
8-level cascade
exact→normalized→AI+Haiku"]
EI["10. Error Intelligence
Rule-based + AI diagnosis
gender, accent, word_diff"]
FSRS_NODE["5. FSRS Progress
Spaced repetition
Ghost Review 4h→48h"]
SL["1. Session Logger
active_time, fatigue
response times"]
EA["2. Error Annotations
Interference zones
L1 transfer detection"]
CM["8. Competence Map
Coverage: type × difficulty
Mastery levels 0-5"]
end
GO --> CS
CS --> TH
TH --> MS
MS --> GEN
GEN -.-> MS
MS --> ANSWER
ANSWER --> AC
AC --> EI
AC --> FSRS_NODE
FSRS_NODE --> CS
AC --> SL
AC --> EA
FSRS_NODE --> CM
CM --> STATS
AC --> FB
style USER fill:#1a2332,stroke:#58a6ff,color:#e6edf3
style ENGINE fill:#161b22,stroke:#3fb950,color:#e6edf3
style CS fill:#1f6feb,stroke:#58a6ff,color:#fff
style FSRS_NODE fill:#1f6feb,stroke:#58a6ff,color:#fff
style AC fill:#da3633,stroke:#f85149,color:#fff
style GEN fill:#8957e5,stroke:#bc8cff,color:#fff
Подсистемы: где живут и как проверить
| # | Подсистема | Где живёт | Как проверить |
|---|---|---|---|
| 1 | Session Logger | bot/services/session_logger.py | SELECT count(*) FROM session_events |
| 2 | Error Annotations | progress-service/error_analyzer.py | SELECT error_type FROM error_annotations LIMIT 5 |
| 3 | Mastery Session | bot/services/session_engine.py | accuracy ≥80% при 5+ ответах = done |
| 4 | AnswerChecker | bot/services/answer_checker.py | Tab 5 — 8-level cascade |
| 5 | FSRS Progress | progress-service/fsrs_adapter.py | Tab 6 — state machine |
| 6 | Generator V3 | ai-service/core/generator_v3/ | python3 scripts/diagnose_v3.py |
| 7 | ConceptSelector | progress-service/concept_selector.py | Tab 7 — 4 priorities |
| 8 | Competence Map | progress-service/api/coverage_routes.py | GET /progress/coverage-summary/{uid} |
| 9 | Theory Engine | ai-service/core/theory_explainer.py | Redis: GET theory:{sha256} |
| 10 | Error Intelligence | bot/services/error_diagnostics.py | Rule-based (free) + Claude Haiku ($0.0005) |
Комплексная проверка всех подсистем
python3 scripts/verify_system_coherence.py— 42 проверки за 30 секpython3 scripts/diagnose_v3.py— диагностика генератора V3python3 -m tools.test_harness.run --sample 3— E2E тест: генерация + ответы + checkercurl localhost:{8100-8106}/health— health check всех 7 сервисов
Бот ↔ AI Repetitor
Реальный путь от нажатия /go в Telegram до обновления FSRS — все сервисы и вызовы
sequenceDiagram
actor U as Ученик
participant Bot as Telegram Bot
(aiogram)
participant GW as Gateway
:8100
participant PS as Progress
:8103
participant AI as AI Service
:8105
participant ES as Exercise
:8102
participant DB as PostgreSQL
:5433
Note over U,DB: 1. Выбор концепта
U->>Bot: /go
Bot->>GW: GET /progress/next-concept/{uid}
GW->>PS: ConceptSelector
PS->>DB: progress_records + session_events
DB-->>PS: stats, due_dates, accuracy
PS-->>Bot: concept_key, review_type
Note over U,DB: 2. Теория (если новый концепт)
Bot->>GW: POST /ai/theory/explain
GW->>AI: Theory Engine (Sonnet)
AI-->>Bot: 7 секций (formation, mistakes, examples...)
Bot->>U: Теория + кнопка Разжуй
Note over U,DB: 3. Генерация упражнения
Bot->>GW: POST /ai/generate-exercise-v3
GW->>AI: Generator V3
AI->>DB: Pool check (block_exercises)
alt Есть в пуле
DB-->>AI: cached exercise
else Нет в пуле
AI->>AI: Sonnet генерация + валидация
AI->>DB: Сохранить в block_exercises
end
AI-->>Bot: exercise JSON (9 типов)
Note over U,DB: 4. Ответ + проверка
Bot->>U: Упражнение + клавиатура
U->>Bot: ответ пользователя
Bot->>Bot: AnswerChecker (8 levels)
alt Нужна AI проверка
Bot->>GW: POST /ai/check (Haiku)
GW->>AI: Validation + Diagnosis
AI-->>Bot: is_correct, error_type, hint
end
Bot->>Bot: ErrorDiagnostics
Bot->>U: Hint (1-я попытка) или Full (2-я)
Note over U,DB: 5. Сохранение + FSRS
Bot->>GW: POST /progress/answer
GW->>PS: save + FSRS update
PS->>DB: user_answers, progress_records
PS->>DB: error_annotations, session_events
PS-->>Bot: next_review_date
Note over U,DB: 6. Цикл (5-15 упражнений)
Bot->>Bot: Mastery check (accuracy ≥80%?)
alt Тема закреплена
Bot->>U: Тема закреплена! Статистика сессии
else Продолжаем
Bot->>GW: POST /ai/generate-exercise-v3
Note right of Bot: Повторяем шаги 3-5
end
Ключевые моменты
- Pool-first: генератор сначала проверяет
block_exercises— экономит ~70% API вызовов - Hint→Full: 1-я ошибка = подсказка без ответа, 2-я = полное объяснение + правильный ответ
- AnswerChecker: 8 уровней (exact→normalized→multi_gap→AI→typo→error_correction→core_match→disputed→fallback)
- Ghost Review: 2+ ошибки подряд → срочный повтор через 4h→12h→24h→48h
- Mastery exit: accuracy ≥80% при 5+ ответах ИЛИ 3 быстрых правильных подряд (<3 сек)
- Frustration exit: accuracy <40% при 5+ ответах → мягкое завершение
Сервисы в цепочке
- Bot — UI, FSM, AnswerChecker, Session Engine
- Gateway :8100 — proxy, JWT auth, rate limit
- Progress :8103 — ConceptSelector, FSRS, analytics
- AI :8105 — Generator V3, Theory, Haiku checker
- Exercise :8102 — concept grid, block_exercises pool
Стоимость одной сессии
- Генерация: ~$0.03 (Sonnet) × 1-3 batch = $0.03-0.09
- Проверка: ~$0.0005 (Haiku) × 5-15 = $0.0025-0.0075
- Теория: ~$0.02 (Sonnet) × 0-1 = $0-0.02
- Итого: $0.03-0.12 за сессию (с кешем ~$0.01-0.04)