30 заданий (А1–А10 + В1–В20), перенабрано вручную в KaTeX по PDF РИКЗ
(РТ-1 23/24 В1). Геометрия закодирована текстом — чертежи не нужны.
Идемпотентный upsert, DRY-RUN по умолчанию, запись с --apply.
Верификация: node --check, валидация 30/30, KaTeX-рендер 413/413 сегментов.
+ метки вариантов 104–106 (РТ-2023/24 этап I/II/III) в routes/exam-prep.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Вместо «Вариант 101/102/103» (технические номера) показываем источник:
«РТ-2024/25 · этап I/II/III». examVariantLabel() в exam-prep.js — единый
источник подписи: listVariants (пикер/dropdown) + variant_label в ответе
mock/:id (строка прохождения и результата). Номера в БД остаются 101+
(нужны для фильтра-диапазона [101;1999] и провенанса). math9 — fallback
«Вариант N» (не затронут). Новые варианты (104+) — дописывать в VARIANT_LABEL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- exam-prep.js: MOCK_VARIANT_RANGE — для ctmath показываем как пробники
только чистые 30-задачные варианты [101;1999]; год-пачки (variant=год
2011-2024 и 0, до 114 задач) остаются пулом для тренажёра по темам,
но скрыты из пикера/mock-start/просмотра вариантов. math9 (1..80) не затронут
(диапазон только для ctmath).
- mock.js: пикер «По варианту» — выпадающий список реальных вариантов
(через listVariants) вместо number-input 1..N; раньше для ctmath он
предлагал 1..18 и не доходил до 101 → пробник по варианту не запускался.
- cleanup_ctmath_bank.js: идемпотентный скрипт — ретайр битого id=1419
(mc с противоречивым ответом → long), variants_count → 3 (чистых вариантов).
- seed_*: variants_count считается по диапазону [101;1999] (консистентно с роутом).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Не было UI для управления exam_tracks.enabled (только флаг в БД, ставился
миграцией). Добавлена админ-секция «Экзамен-модули»:
- backend exam-prep.js: GET /admin/tracks (все треки, вкл. выключенные, + число
заданий) и PATCH /admin/track (exam_key, enabled), обе requireRole('admin').
Пути без :examKey, чтобы не задеть гейт content_access.
- frontend: секция sections/exams.js (список треков + переключатель enabled),
вкладка в admin.html (admin-only через ADMIN_ONLY_TABS, locked для не-админов),
регистрация в admin.js (ROUTE_TO_SECTION).
Выключенный трек скрыт у учеников и пропадает из каталога прав доступа (тот
берёт exam_tracks WHERE enabled=1). Доступ ученикам по-прежнему в «Доступ · контент».
Требует перезапуска бэкенда + Ctrl+F5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1) exam-prep practice (strategy=random) возвращал около 0.6 от count: функция
distributeByDifficulty раскладывает count по 5 уровням сложности, а у трека
ctmath задания только уровней 1-3 (уровни 4-5 пустые) -> часть выборки терялась
(20 -> 12, 15 -> 10, 10 -> 6). В pickRandomByDifficulty добавлен добор до count
из доступных уровней. Трек math9 не затронут (там добор не требуется).
2) lesson.html: .lesson-nav-btn-title был inline-span, поэтому max-width и ellipsis
игнорировались и длинные заголовки вылезали за кнопку. Добавлен display:block.
Бэкенд-правка требует перезапуска сервера; фронт-правка видна после Ctrl+F5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coins were always 1:10 of XP. Now they have their own event log + a
helper that dedups by reason within a configurable window.
Backend:
• migration 032 creates coin_log (user_id, amount, reason, created_at)
with indices for the 'fired today?' check
• awardCoins now records into coin_log on every call (reason defaults
to 'xp_bonus' for the legacy XP-proportional path)
• awardCoinsOnce(userId, amount, reason, window) — fires the bonus
only if no row matches in the window:
'day' → DATE(created_at) = today
'week' → ISO week match
'forever' → never twice
Wired events (Phase 4 subset of the plan):
• Daily login — 10 coins, once/day. Hooked in updateStreak so the
bonus rides on the existing 'daily_activity' XP trigger.
• Daily goal completion — 15/25/40 coins (easy/medium/hard), once/day.
Sits next to the existing tier XP bonus in updateDailyGoal.
• Variant clear — 30 coins, once per (user, variant) forever. Fires
from the exam-prep attempts endpoint when the user's final correct
answer fills out a math9 variant.
Deferred (need invasive trigger hooks): weekly goal, paragraph close,
boss defeated, referral.
Verified end-to-end: awardCoinsOnce returns true→false on repeated
calls, coin_log records the first, coins balance moves once.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recent-attempts widget on /exam-prep/:examKey was showing raw LaTeX
like '\dfrac{7}{9}' because stripPreview only removed HTML tags.
Now it also converts common LaTeX to readable unicode (fractions →
a/b, \sqrt → √, \cdot → ·, comparisons → ≤≥≠, Greek letters, etc.)
before truncating.
KaTeX rendering would be overkill for a 100-char preview row; this
just makes the existing text legible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Practice (random) now picks tasks by ascending difficulty so the first
slot is always level 1 and the session ramps up. Adds ?exclude= to drop
specific subtopics from the random pool, with a per-section checkbox
modal in the UI.
Each task carries a topic_ref (textbook chapter + paragraph) shown as
a 'Учить тему · §N' button next to the solution, deep-linking to the
right section of /textbook/<slug>. Mapping seeded for all 15 math9
subtopics in migration 028.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>