From 8091b48e1c8ebb377a84e7965a26bfb39ebcef9f Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Mon, 15 Jun 2026 12:09:50 +0300 Subject: [PATCH] =?UTF-8?q?fix(ct-math):=20=D0=BF=D1=80=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0=20=D0=BC=D0=B5=D0=BD=D1=8C=D1=88=D0=B5=20cou?= =?UTF-8?q?nt=20+=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=BE=D0=B2=20=D0=B2?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=83=D1=80=D0=BE=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/src/routes/exam-prep.js | 23 +++++++++++++++++++++-- frontend/lesson.html | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/backend/src/routes/exam-prep.js b/backend/src/routes/exam-prep.js index 578c0ab..112c8c8 100644 --- a/backend/src/routes/exam-prep.js +++ b/backend/src/routes/exam-prep.js @@ -630,13 +630,15 @@ function pickRandomByDifficulty(examKey, count, excludeSlugs) { ? `AND (subtopic IS NULL OR subtopic NOT IN (${exParams.map(() => '?').join(',')}))` : ''; + const COLS = `id, task_idx, variant, task_type, text_html, figure_html, opts_json, + answer, solution_html, topic, subtopic, difficulty, textbook_slug, textbook_paragraph`; const out = []; + const seen = new Set(); for (let d = 1; d <= 5; d++) { const limit = dist[d - 1]; if (limit === 0) continue; const sql = ` - SELECT id, task_idx, variant, task_type, text_html, figure_html, opts_json, - answer, solution_html, topic, subtopic, difficulty, textbook_slug, textbook_paragraph + SELECT ${COLS} FROM exam_tasks WHERE exam_key = ? AND task_type IN ('mc','open') AND difficulty = ? @@ -646,8 +648,25 @@ function pickRandomByDifficulty(examKey, count, excludeSlugs) { const args = exParams ? [examKey, d, ...exParams, limit] : [examKey, d, limit]; + for (const r of db.prepare(sql).all(...args)) { if (!seen.has(r.id)) { seen.add(r.id); out.push(r); } } + } + // Backfill to `count` from any difficulty — covers tracks whose tasks don't + // span all 5 difficulty levels (otherwise empty levels would shrink the batch). + if (out.length < count) { + const ids = [...seen]; + const notIn = ids.length ? `AND id NOT IN (${ids.map(() => '?').join(',')})` : ''; + const sql = ` + SELECT ${COLS} + FROM exam_tasks + WHERE exam_key = ? AND task_type IN ('mc','open') + ${exClause} + ${notIn} + ORDER BY RANDOM() + LIMIT ?`; + const args = [examKey, ...(exParams || []), ...ids, count - out.length]; out.push(...db.prepare(sql).all(...args)); } + out.sort((a, b) => (a.difficulty || 0) - (b.difficulty || 0)); return out; } diff --git a/frontend/lesson.html b/frontend/lesson.html index 4aae0ff..3a4f13d 100644 --- a/frontend/lesson.html +++ b/frontend/lesson.html @@ -276,7 +276,7 @@ .lesson-nav-btn-prev { justify-content: flex-start; } .lesson-nav-btn-next { justify-content: flex-end; margin-left: auto; } .lesson-nav-btn-label { font-size: 0.7rem; font-weight: 600; color: var(--text-3); display: block; } - .lesson-nav-btn-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; } + .lesson-nav-btn-title { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; } /* ── complete button ── */ .lesson-complete-wrap {