История изменений

v0.6.3 29.09.2026 Последняя

Добавлено

  • • **Маркер поверх страницы** – на статье учебника, в тренировке ЕГЭ, на варианте и в практикуме, в обычном и в полноэкранном режиме: круглая кнопка у правого края экрана (или клавиша M), 8 цветов, 5 толщин, ластик, отмена (Ctrl+Z) и «Стереть всё». Обводка едет вместе с текстом при прокрутке, у каждой задачи и слайда свой рисунок. Ничего не сохраняется – рисунок живёт до перезагрузки страницы. На главной – анонс «Рисуйте прямо на уроке» вместо анонса отзывов (новый ключ news:marker, поэтому его увидят и те, кто закрыл прошлый)
  • • **Показ статьи с любого блока** – кнопка на блоке (рядом с ✎) открывает режим проектора сразу с него, а не с первого слайда

Исправлено

  • • **Время задачи в тренировке останавливается на верном ответе** (или на «Показать ответ»), а не когда ученик ушёл к соседней задаче или нажал «Завершить»: минуты над зелёной галочкой в статистику больше не идут. В повторе решения (kind='retry') часы не останавливаются – там каждый заход считается заново
  • • **Классный набор больше не входит в «из Y»** – на карточке задания, в таблице «Ученики» и в её шапке: классные задачи в статистику не идут, и прорешанный банк никогда не показывал «решено Y из Y»
v0.6.2 28.09.2026

Добавлено

  • • **Класс без дедлайнов учебника** – галочка StudentGroup.no_textbook_deadlines (например, группа ЕГЭ, где учебник – справочник): блоки не закрываются по сроку и подсказки не открываются «за три дня до дедлайна». Читается в одном месте – personal_deadlines(). У учителя сроков тоже нет
  • • **Галочка «В статистике»** (StudentGroup.in_stats) – какие классы видны во вкладках статистики учителя (учебник, ЕГЭ, фильтр на главной). Выпуск больше не прячет класс сам; миграция снимает галочку с уже выпущенных, чтобы они не вернулись в таблицы
  • • **Ученик сам вписывает фамилию и имя** – один раз, в профиле; пока поля пусты, над страницей висит напоминание. Заполненное ученик не перепишет даже запросом в обход формы, исправляет учитель в админке

Изменено

  • • **Оценка за блок – только после первой попытки практикума** (сданный тест или отправленный код). Раньше нетронутый блок получал «2», и в начале года двойки стояли по всем блокам. Учителю оценка не выставляется
v0.6.1 28.09.2026

Добавлено

  • • **Сайт в поисковой выдаче.** Иконка «K» (static/favicon.svg, PNG 120×120 для Яндекса, favicon.ico в корне домена), заголовки вида «… – Kirill Lab» и <meta name="description"> на каждой странице: без него Яндекс ставил в сниппет строку футера. Статьи берут описание из своего поля description, у разборов ЕГЭ в заголовке «ЕГЭ по информатике». /robots.txt закрывает от индексации вход, админку, сессии тренажёра и отчёты учителя; /sitemap.xml (pages/sitemaps.py) – только то, что открыто гостю: опубликованные статьи, карточки заданий ЕГЭ, спецкурс. Корневые файлы лежат в public/ и отдаются WhiteNoise (WHITENOISE_ROOT). SearchEngineFilesTests
  • • **Отзывы к статьям учебника.** Под каждой статьёй (урок, теория ЕГЭ, спецкурс) авторизованный читатель ставит 1–5 звёзд, при 1–3 может написать, что было непонятно. Правка предлагается к блоку (✎): текст и формула правятся прямо в поле, а под ним – превью так, как будет на странице, с подсвеченными новыми словами; у кода – пословный diff; картинку и виджет описывают словами, можно приложить свою картинку (до 5 МБ). Правки совещательные: в статью ничего не попадает. Учитель читает их на /textbook/feedback/ (было/стало, статус, ответ автору; вторая вкладка – статьи от худшей средней оценки), число новых – бейдж в шапке. Автор видит статус и ответ в профиле («Предложения к статьям», последние 8; все – на /textbook/suggestions/). О новой возможности вошедшим рассказывает анонс над картой курса на главной – две CSS-сцены в стиле гостевой врезки (звёзды загораются под курсором; ✎ → старая строка зачёркнута, новая вписана), закрывается кнопкой «Понятно» (флаг в localStorage). Модели ArticleRating, Suggestion; ArticleFeedbackTests
  • • **Ученики на задании ЕГЭ.** С карточки задания учитель открывает /ege/task/<N>/students/ – таблицу класса по одному номеру (вкладки классов, как на /ege/class/): решено из банка и с первой попытки, ошибки, среднее время на задачу в тренировке и на экзамене, доступ к экзамену и итог последнего, дата последней попытки. Фамилия ведёт в разбор /ege/task/<N>/students/<id>/: каждая задача, которую ученик решал, с условием, временем на неё всего и по каждой выдаче (тренировка / экзамен / переписывание, ответ, попытка) и всеми отправками кода с логом; ?errors=1 – только ошибки, туда же ведёт счётчик в таблице. Связка 19–21 – одна страница. TaskStudentsTests
v0.6.0 26.09.2026

Добавлено

  • • **Разбор задания 23 ЕГЭ – кратчайший путь в графе без циклов.** Решение – динамика по таблице: best пересчитывается проходами по всем рёбрам, пока не перестанет меняться, – без рекурсии и топологической сортировки, один приём на все вариации (min, max, число путей). Блок «Как запрограммировать» собирает программу по шагам, и итоговый код склеен из тех же кусков. _check() прогоняет код статьи на примере из условия и сверяет каждое число текста
  • • **Виджет graph-walk в режиме dag** – те же проходы по таблице на рёбрах примера задания 23: вершина без числа пропускает ребро, и это видно глазами; в конце подсвечивается кратчайший путь. WidgetMountTests доигрывает виджет до конца и сверяет ответ с кодом статьи
  • • **Разбор задания 27 ЕГЭ – кластеризация.** Два этапа: границы кластеров снимаются с графика в LibreOffice Calc (виджет screen-steps с записями экрана), дальше Python раскладывает частицы и считает Q1 и Q2. _check() проверяет, что границы из кода лежат в пустых полосах файла демоверсии. Блок «Если условие другое» – для банка, который пока в формате до 2027 года
  • • **Банки заданий 23 и 27** – 60 задач, всего в тренажёре 810
  • • **_seed_check.demo_file_link** – ссылка на файл вопроса демоверсии по external_id, общая для разборов
  • • **Документация API учебника, спецкурса и игр** (docs/api/textbook.md, spetskurs.md, games.md)

Изменено

  • • **Документация сверена с кодом**: архитектура и все 41 модель, маршруты всех семи приложений, песочница, тренажёр ЕГЭ, фронтенд, инфраструктура, README.md. DESIGN.md приведён к закрытой шкале кегля
  • • **load_ege переносит автора задачи из условия в заголовок**: «(Л. Шастин) В текстовом файле…» читалось как первое слово задачи, теперь автор стоит серым рядом с источником
  • • **CLAUDE.md**: правила работы, актуальные списки приложений, команд, скиллов и агентов

Исправлено

  • • **Задание 25 банка было нерешаемым**: парсер пишет перенос строки в эталоне двумя символами \n, и эталон не совпадал ни с одним выводом. load_ege раскрывает их при загрузке; на проде bank-25.json нужно прогнать заново, вариант EGE_3 чинится командой из docs/ege-bank-deploy.md
  • • **Виджет graph-walk не зависает на ошибке в конфиге**: число проходов ограничено числом вершин, иначе цикл отрицательного веса повесил бы вкладку на проекторе
  • • **Брифы учебника писались длинным тире** – заменено на короткое: агенты, пишущие уроки по брифу, переносили его в статьи
v0.5.9 25.09.2026

Добавлено

  • • **Разбор задания 25 ЕГЭ – обработка целых чисел.** Обе формулировки демоверсии (в бланке между ними «ИЛИ») – два разных жанра, и у каждого свой блок решения: отбор по маске и делимости (fnmatch) и отбор по простым множителям. Оба решения исполняются в _check() и сверяются с ответами ФИПИ
  • • **Четыре разбора задания 26 ЕГЭ.** Основной – демоверсия (журнал сервера и переполняющийся раздел), плюс три сюжета из банка kompege: серии подряд идущих номеров, «матрёшка» (жадный проход по отсортированным размерам) и рейтинг с проходным и полупроходным баллом. Числа «Типичных ошибок» и таблицы трассировки считает модель задачи в seed-команде, а код решения прогоняется на файле из банка тем же, что скачает ученик (_seed_check.run_solution). Ссылка на файл берётся у вопроса по external_id, а не зашивается номером: QuestionFile.id на проде свой
  • • **Пояснение на карточке задания 26**: статьи ниже разбирают по одному типу задач, а не все вариации номера

Исправлено

  • • **Кадр симуляции спецкурса занимает всю ширину рамки.** При width: auto браузер переносил max-height хозяина (на лендинге max-h-[70vh]) через aspect-ratio на ширину, кадр сужался, и справа проступал белый фон рамки
v0.5.8 21.09.2026

Добавлено

  • • **Разбор заданий 19–21 ЕГЭ – теория игр.** Одна статья на три номера: игра одна, и три разбора одной игры разошлись бы через месяц. Лежит на заглушке задания 19 под заголовком «Задания 19–21» (LINKED_ARTICLE_TITLES в seed_ege_tasks, иначе команда переименовала бы её обратно). Два примера – две кучи из демоверсии и одна куча из банка, – отдельные блоки про взаимную рекурсию win/lose и таблица «что в условии → что править»
  • • **Виджет ege-game-tree – дерево игры.** Пресеты из статьи плюс конструктор условия (число куч, до пяти ходов + − × //, порог, границы S), в который ученик вбивает свой вариант. Пять формулировок из банка – кнопками: лента S красит подходящие значения, и видно, что «Ваня мог выиграть первым ходом» (any) и «при любом ходе Пети Ваня выигрывает» (lose) – разные множества. Дерево ходов окрашено по исходу для ходящего, клик по узлу разыгрывает партию. WidgetMountTests нажимает те же кнопки и сверяет ответ ленты с ответом, который считает Python в seed-команде
  • • **Разбор задания 24 ЕГЭ – обработка символьной строки.** Два решения: регулярное выражение и два указателя – второе нужно, когда условие шаблоном не описать. К каждому свой виджет: regex-lab на шаблоне решения и loop-trace с пошаговой трассой указателей; трассу собирает тот же алгоритм, что в статье
  • • **Разборы заданий 9, 18 и 22 ЕГЭ – электронные таблицы.** Решение в LibreOffice Calc, без Python-эталона; действия показаны виджетом screen-steps с записями экрана. Числа проверены пересчётом файлов демоверсии
  • • **Архив исходников библиотеки спецкурса.** static/spetskurs/GuiLibrary-src.zip вместе с подмодулями – в школьной сети GitHub закрыт, и git clone там не работает. Ссылка стоит в статье «Программа, сборка и запуск», выкладка – rsync вместе с .wasm (docs/spetskurs-deploy.md), в git архив не идёт

Изменено

  • • **Разбор задания 17 считает свои числа сам.** Игрушечный пример, неверные ответы из «Типичных ошибок» и ответ берутся из _solve, а _check() сверяет его с ключом ФИПИ; добавлен блок «Если условие другое»
  • • **«Основы C++»: сборка на Windows – через build.bat.** Скрипт находит Visual Studio и берёт CMake из неё, так что из трёх программ остаётся одна
  • • **regex-lab красит совпадения четырьмя тонами с рамкой**, а не двумя оттенками зелёного: соседние короткие совпадения в разборе задания 24 сливались в один кусок. Цвет совпадения тот же, что у его строки в списке ниже

Исправлено

  • • **Теория связки 19–21 видна на всех трёх карточках карты ЕГЭ.** Разбор лежит на номере 19, и у 20 и 21 плашка «теория» пропадала, а прочитанный разбор считался непрочитанным на двух карточках из трёх
  • • **regex-lab печатает findall так же, как Python**: группа, не участвовавшая в совпадении, – '', а не None
  • • **Нумерованный список, прерванный таблицей, продолжает нумерацию.** Санитайзер срезал атрибут start у <ol>, и пункты после таблицы снова начинались с 1
  • • **Воркер DeepSeek принимает длинное задание.** Оно уходит в stdin, а не аргументом – на Windows командная строка ограничена 32 КБ
v0.5.7 18.09.2026

Добавлено

  • • **Разбор задания 3 ЕГЭ – реляционные базы данных в электронных таблицах.** Формулы даны в синтаксисе LibreOffice Calc (точка в ссылке на лист, ; между аргументами, русские имена функций): на экзамене Excel не выдаётся. Сумма отобранного берётся из строки состояния, а не формулой – за ПРОМЕЖУТОЧНЫЕ.ИТОГИ ученик идёт учить лишнюю функцию, тогда как Calc уже написал ответ внизу окна. Блок «Если условие другое» здесь по делу: в банке задания 3 вопрос меняется всерьёз (масса или объём, выручка или стоимость, район вместо улицы), а это одна-две правки в уже написанных формулах. Все числа проверены пересчётом файла демоверсии
  • • **Разбор задания 10 ЕГЭ – адрес сети по адресу узла и маске.** Виджет ip-mask стоит внутри разбора, как fano-code в задании 4: разбор идёт по двоичной записи, а рисует её виджет. Сразу за листингом – блок про объект IPv4Network (свойства, индексы, hosts(), in), без него net[-2] и int(ip) в «Типичных ошибках» заклинания, а не код, который ученик соберёт под новый вопрос сам. Числа не вписаны руками: адрес сети, двоичные записи октетов, префикс, широковещательный адрес и ответы задач банка считает ipaddress, а _check() сверяет их с ответом ФИПИ и исполняет листинг – разойдётся, и команда упадёт, а не выдаст статью с враньём
  • • **Разбор задания 14 ЕГЭ – вычисления в недесятичных системах счисления.** Три типа условия по нарастающей: неизвестная цифра в записи операндов, значащие нули в записи выражения, нули в выражении со слагаемым x. Скелет брифа повторён трижды, а «Типичные ошибки» одни на всех: ловушки у типов общие (цифра подставлена строкой, ответ не на тот вопрос, одиночное деление, подсчёт в десятичной записи), меняется только условие, в котором они встречаются. Листинги исполняются в _check(), числа в тексте подставляются из их результата
  • • **Виджет screen-steps – слайд-шоу «скриншот + текст».** Восемь навыков работы в Calc: плашки-оглавление со всеми шагами сразу, кадр и абзац к нему, кнопки «Назад» / «Далее». Восемь блоков статьи растянули бы вступление длиннее самого решения. Кадры лежат файлами в media, а не в ArticleBlock.image: у блока одна картинка, у виджета их восемь, – и шаг без файла рисует аккуратную заглушку «Скриншот готовится», потому что файлы заливают отдельно и позже. Вместо картинки шаг может отдать запись экрана: всплывающая подсказка аргументов, обводка диапазона мышью, заполнение столбца двойным щелчком существуют только в движении. Реплики к записи (cues) – не выжженный в кадр текст, а строки конфига: опечатку чинит правка сид-команды, а не перекодирование мегабайтов, и на проекторе подпись растёт вместе со страницей
  • • **Виджет ip-mask – адрес и маска по битам.** Три строки по 32 бита (адрес узла, маска, адрес сети), над ними шапка зон с числом бит в каждой, под каждым октетом его десятичное значение. Клик по биту маски ставит границу – маска бывает только сплошной, поэтому биты не переключаются по одному; маску можно и вписать, точками или как /22, несплошная подсвечивается красным. Под таблицей проговаривается главная мысль разбора: границу почти никогда не проводят по точке, поэтому один октет разрезан и его считают в двоичной

Изменено

  • • **Иллюстрации учебника показываются целиком.** Общий бокс aspect-ratio: 4/3 с object-fit: cover срезал каждой картинке не этой пропорции ровно то, ради чего она нарисована, и исключения приходилось заводить по имени файла – список рос с каждой новой иллюстрацией (кадр журнала в разборе задания 3 был бы четвёртым). Единообразие бокса того не стоило: обрезанная картинка не «аккуратная», она неверная. Осталась одна страховка – max-height: 80vh против очень высокого скриншота
  • • **Лайтбокс открывает картинку в её настоящих пропорциях.** В шаблоне были зашиты 1600×1200, и любой другой кадр PhotoSwipe растягивал. Размеры читает article_detail_view из самого файла; чтение обёрнуто в try – у блока бывает запись о картинке без файла на диске (сид прогнали раньше, чем приехал media), и одна битая иллюстрация не должна ронять страницу. Те же числа стоят атрибутами width/height у img: пропорцию больше не диктует CSS, и без них текст прыгал бы при загрузке

Исправлено

  • • **Кадр «Журнал с подтянутыми полями» в разборе задания 3 не разъезжается с подписью.** Картинка блоку не ставилась, а replace_blocks раздаёт уже загруженные картинки блокам без image по порядку следования – на чистой базе блок оставался пустым, а на втором прогоне утаскивал себе кадр соседнего блока со строкой состояния
v0.5.6 17.09.2026

Добавлено

  • • **Разбор задания 6 ЕГЭ – исполнитель Черепаха.** Когда все повороты кратны 90 градусам, фигура считается в уме; когда нет – её рисуют модулем turtle. Окно черновика setworldcoordinates подобрано так, чтобы обе фигуры демоверсии влезали целиком, и _check() в seed-команде это проверяет: при школьном множителе фигура уходила за край окна. Код блока «Решение» исполняется при наполнении и обязан напечатать ответ ФИПИ
  • • **Разбор задания 13 ЕГЭ – число программ исполнителя.** Соотношение записано снизу вверх ($G(n)$ – сколько программ ведёт из START в n) и показано лентой клеток, которую заполняют слева направо; рекурсия идёт вторым способом, а таблица остаётся на случай, когда рекурсия падает по глубине. Лента – разметка с классами .tape в textbook-article.css, а не картинка: дугу прыжка собирают сами клетки, поэтому она не разъедется с числами. Числа в тексте считаются _check(), а не вписываются руками
  • • **Разбор задания 16 ЕГЭ – рекуррентная функция.** Два равноправных решения: цикл по списку значений и свёртка выражения на листке – второе быстрее среды разработки и единственно возможное, когда аргумент в сотнях тысяч и список не помещается в память. Таблица «что в условии → что делать» собрана из банка задания, ни одной выдуманной строки. _check() сверяет программу со свёрткой и с ответом ФИПИ, а заодно числа, которыми статья доказывает выбор цикла: предел рекурсии, OverflowError, число знаков факториала
  • • **Яндекс.Метрика.** Номер счётчика – YANDEX_METRIKA_ID в .env прода; без него счётчика нет, и заходы с localhost в статистику не попадают. Визит помечен параметром auth (guest/user) – ни имени, ни id. У суперпользователя счётчик не грузится: Метрика отправляет в Яндекс заголовок страницы, а в заголовках учительских страниц стоят ФИО учеников. По той же причине его нет на странице чужого решения ЕГЭ, которую видят и ученики

Исправлено

  • • **Отчёт учителя по блоку сортируется по тексту ячейки.** Ученики шли в порядке профилей внутри класса, а «Без группы» – по логину. Теперь по «Фамилия Имя» (без фамилии – по логину), ё приравнена к е: в Unicode она стоит после «я»
  • • **Снимок окна turtle в разборе задания 6 показывается целиком.** Общий бокс 4:3 с object-fit: cover срезал у почти квадратной картинки верхнюю и нижнюю стороны прямоугольника – а в этой задаче они и есть условие
  • • **Многострочный комментарий в section_stats_errors.html утекал в HTML.** {# #} в Django только однострочный; заменён на {% comment %}
v0.5.5 15.09.2026

Изменено

  • • **Таблица показывает один класс за раз.** Вкладки над таблицей (?group=<id>, по умолчанию первый класс; none – «Без группы», all – все сразу), ровно та же схема, что у /ege/class/: пять таблиц подряд перестают отвечать на свой вопрос «что с этим классом». Несуществующий ?group= трактуется как «все», а не как 404 – ссылка на класс переживает удаление класса
  • • **Фамилия ведёт в разбор ответов, а не в профиль.** Из таблицы идут смотреть работу ученика по блоку, а не карточку человека; кнопка «Профиль» осталась справа в шапке самого разбора, так что путь никуда не делся, он стал на клик длиннее и перестал быть единственным
  • • **Страница /stats/<user_id>/errors/ показывает все ответы, а не одни ошибки.** Верная попытка рисуется зелёной полосой и подписью «Верно», неверная – красной, у задачи видно, сколько попыток было и сколько из них неверных. Режим «только ошибки» остался на ?errors=1 – на него ведёт счётчик ошибок в таблице, и число задач на странице совпадает с числом на кнопке
  • • **«Решена ли задача» считается по всем ответам ученика, а не по показанным.** В режиме ошибок верной попытки в выборке нет, а «решил позже» показать надо. Отсюда же бейдж «Решена» вместо «Решил позже», когда ошибок у задачи не было вовсе

Добавлено

  • • **Второй срез разбора: практикум против самопроверок** (?kind=practicum / ?kind=selfcheck). Домашнюю задачу решают, вопрос по ходу чтения щёлкают – в одном списке их сравнивать не с чем. Оба фильтра независимы и складываются, адреса вкладок собираются во вьюхе, а не в шаблоне: у страницы два параметра, и каждая вкладка обязана сохранить второй
  • • **SectionStatsTeacherTests** – сторожит три вещи, которые тихо ломаются при правке шаблона: что таблица не рисует чужой класс, что фамилия ведёт в разбор, а не в профиль, и что оба среза режут выборку и складываются
v0.5.4 15.09.2026

Добавлено

  • • **Разбор задания 5 ЕГЭ – алгоритмы для исполнителя.** Все 30 задач банка – один каркас: функция r(n) по словесному правилу плюс min/max по перебору, а различаются они основанием системы, самим правилом и тем, что спрашивают – R или N. Отсюда таблица «что в условии → что править» вместо разбора каждой вариации. Главный вывод разбора – R не растёт вместе с N (r(8) = 227, r(9) = 100: у чётного N запись длиннее на четыре разряда, у нечётного на три), поэтому останавливаться на первом подходящем нельзя. Оба способа оборвать перебор дают одно и то же неверное 115 вместо 100, и статья называет это число вслух. Числа считает _check() в seed-команде, а не автор
  • • **Разбор задания 12 ЕГЭ – исполнитель МТ.** В справочнике номер 12 назывался «Исполнитель Редактор» по снятому кодификатору, тогда как демовариант и весь банк задач – машина Тьюринга; статья-заглушка переехала на slug ege-12-ispolnitel-mt. Блок «Как рассуждать» – инструкция для тетради, а не наблюдение: нарисуй ленту, поставь стрелку над клеткой из условия, найди клетку таблицы, выполни три её части, вернись к поиску клетки. То есть буквально то, что делает виджет рядом, – его и надо уметь повторить руками. Вывод «строка q1 – это просто иди влево, ничего не меняя» остался, но последним абзацем и с оговоркой «когда рука набьётся»: это ускорение, а не метод, и пока такты считают по клеткам, писать надо все. Трассу первых тактов собирает исполнитель из seed-команды – в тетради появятся ровно эти строчки. Условие в блоке «Типовой пример» стоит **целиком**, вместе с почти страничным описанием исполнителя и разминочным примером «все Z в X»: пересказ в первом блоке учит решать, оригинал учит не пугаться той стены текста, которая придёт на экзамене. Разминочный пример тоже исполняется в _check() – его таблица набрана с листа руками. Ответ считает _execute() в seed-команде, а не автор
  • • **Виджет turing-machine.** Лента, головка с бейджем состояния и таблица команд, которую можно править прямо на странице, – поэтому виджет закрывает любую задачу этого типа, а не только ту, что в конфиге. Он единственный в реестре **сам исполняет** программу: у задания 12 она уже написана в условии, готовить трассу в seed-команде, как у loop-trace, нечего. После остановки печатает получившуюся ленту и, если на ней одни нули и единицы, её десятичное значение – это и есть ответ задания. Строки таблицы добавляются кнопками, до 10 состояний (чужая задача редко ложится в число состояний из конфига); снять можно только добавленную с экрана. Над лентой стоит постоянная подсказка про цвета и про то, что здесь можно править: цвета надо понимать до первого нажатия, иначе первый такт выглядит как «что-то мигнуло». WidgetMountTests жмёт «До конца» и сверяет результат с _run() из разбора: две реализации одной машины Тьюринга, Python и JS, обязаны давать одно число
  • • **Виджет regex-lab.** Шаблон, текст и совпадения: подсветка всех вхождений прямо в тексте, строка re.findall(...) с настоящим результатом и список совпадений с позициями и группами. Правится и шаблон, и текст – смысл регулярного выражения в том, чтобы его поменять и посмотреть, что стало, поэтому готовую трассу, как у loop-trace, здесь готовить нечего. Движок браузерный, а диалект питоновский, и расходятся они ровно там, где в учебнике весь текст: в Python \w и \b считают буквой кириллицу, в JS – нет, и на фразе «Ключ: kompege, ответ 42» виджет показывал бы не то, что покажет python. Поэтому шаблон перед запуском переводится – \w в [\p{L}\p{N}_], \b в пару lookaround'ов, – а WidgetMountTests сверяет напечатанную виджетом строку re.findall с настоящим re на тех же константах из seed-команды. Правило findall про группы виджет повторяет буквально (ноль групп – совпадение целиком, одна – её содержимое, две и больше – кортежи): урок про эту ловушку и виджет рядом с ним разойтись не могут
  • • **Практикум блока 8 – 20 задач с автопроверкой.** Раньше у блока не было обязательных задач вовсе, то есть и признака «блок пройден». Порядок по нарастающей: 1–2 базовые операции, 3–5 поиск и замена, 6–7 разбор строки посимвольно, 8–10 шифр, сжатие и перекрывающиеся вхождения (count() там даёт другой ответ – это и есть задача), 11–14 файлы, 15–17 регулярные выражения, 18–20 разбор приложенного файла окном. Ожидаемые ответы посчитаны прогоном эталонных решений, а не выписаны от руки; один тест из 55 так и нашёлся – ожидание 'егд вба' вместо 'едг вба'
  • • **Задачи с приложенным файлом.** У задач 18–20 файл лежит рядом с условием, скачивается со страницы и попадает в песочницу под тем же именем. Файлов у каждой задачи **три**, а имя нужного подаётся на вход: файлы у вопроса общие для всех тестов, и с одним файлом тест был бы один, а его ответ достаточно вписать в print. Содержимое генерируется в seed-команде, ответы считаются по нему же – разъехаться файл и ответ не могут. Алгоритмы окна сверены полным перебором на всех строках до восьми символов

Изменено

  • • **Блок 8 переразобран: «Строки в Python» → «Строки, файлы и регулярные выражения».** Было семь уроков, из которых шесть – мелко нарезанные основы строк: + * in отдельно, find/replace отдельно, upper/strip отдельно, split/join отдельно. Каждый со своей вводкой, своей шпаргалкой и своим «итогом и мостиком», то есть четыре вводки и четыре шпаргалки на один связный материал. Стало три урока про строки (символы и срезы; методы; алгоритмические задачи) и четыре новых: файлы, импорт, и два про регулярные выражения. Основы ужаты с 57 блоков до 21: каждый метод описан один раз, перечисления ушли в таблицы, «мостиков к следующему уроку» не осталось ни одного – они запрещены брифом ревизии, а тут стояли в каждой статье
  • • **В уроке про алгоритмы на строках появилось окно.** Задача про анаграмму убрана – её честное решение упирается в словари из блока 13, а без них разбор сводился к «так делать расточительно». Вместо неё разобран поиск самого короткого куска с заданным числом нужных букв: сперва честно назван перебор всех пар границ и посчитано, почему на строке в миллион символов это сотни миллиардов проверок, потом – две границы, которые двигаются только вперёд. Это же окно нужно в трёх последних задачах практикума
  • • **check_articles больше не принимает $ в коде за формулу.** Знак конца строки в регулярном выражении – это $, и в уроке 8.6 он встречается по делу; проверка склеивала два таких знака в «формулу с тегами внутри». Код теперь выкидывается и из отрендеренного HTML, а не только из исходника блока – ровно то же правило, что уже действовало для исходника
  • • **Скрипт виджетов грузится только там, где виджет есть.** textbook-widgets.js – это 30 виджетов и 148 КБ gzip, а блок widget стоит в 59 статьях из 226: на остальных трёх четвертях страниц файл скачивался, не находил ни одного [data-widget] и выходил. Тег и наблюдатель MathJax (он живёт ровно ради виджетов) уехали под {% if has_widgets %}, а флаг считается по **тому же** списку блоков, который шаблон и рисует: отдельная проверка разошлась бы с разметкой у суперпользователя, которому visible_blocks отдаёт ещё и черновики. WidgetScriptLoadingTests держит обе стороны – где виджета нет, скрипта нет; где есть, есть оба

Исправлено

  • • **Бэктики в заголовке блока статьи выводились бэктиками.** Уроки про методы и операторы состоят из кода прямо в заголовке – «find() и rfind(): где именно стоит кусок», – а {{ block.title }} шёл в шаблон сырым текстом. Видно это было не только в новом блоке 8: так же стояли «Побитовое И (&)» в блоке 6 и «in смотрит на ключи» в блоке 13. Заголовок теперь проходит через фильтр title_code – он разбирает **только** бэктики и ничего больше: полный markdown сюда нельзя, заголовок «1. Попасть в мишень» он превращает в нумерованный список внутри h2, а это ровно те заголовки, которыми открываются статьи с заданиями спецкурса. Заголовок виджета уезжает в data-атрибут и разметки не принимает вовсе – там бэктики просто убраны
v0.5.3 12.09.2026

Изменено

  • • **Задания собраны в последнюю статью каждой работы.** В статьях 1–5 не осталось ни формулировок, ни отсылок «это мы будем искать в заданиях»: разбор пишется от «возьмём» и «получится», а слово «задание» встречается только в последней статье. Повелительное наклонение «сделайте и объясните» в разборе – это и есть задание, выданное на две статьи раньше срока и сразу с решением
  • • **Шапка-таблица «задание – статья, на которой оно основано»** во всех четырёх работах, вместо абзаца о том, какие задания делаются мышкой. Ученик, у которого задание не идёт, должен видеть, куда вернуться, а не догадываться; задания, не разобранные ни в одной статье, помечены прочерком и названы дополнительными
  • • **Листинги колебаний и гравитации приведены к реальному коду.** Размер графика в create_plot больше не задаётся числами (-1, -1), раскладку окна делает set_auto_layout(0.4f) – пульту 40% ширины, остальное графику. Статья, которая печатает не тот код, что лежит в Task_N/main.cpp, – это не разбор, а второй источник правды
  • • **Убраны отсылки «задание со слайда презентации».** Разборы писались по презентации, которой ученик не видел: для него это ссылка в пустоту. Условие пишется сразу
  • • Задания 3 и 6 маятника переписаны с «опишите, что получилось» на «проверьте предсказание статьи числом». Качественную картину – три режима фазовой траектории, дрейф энергии у явной схемы – статьи теперь дают целиком, и пересказывать её своими словами не работа

Удалено

  • • **Статья маятника «Что здесь нового в C++».** Она пересказывала три чужих текста подряд: порядок присваиваний слово в слово повторял статью о численном методе, где на нём держится весь симплектический Эйлер, а препроцессор, float против double, ранний выход и деление на ноль разобраны в «Основах C++», куда разбор и ссылается. Синтаксис объясняется один раз, разбор задачи на него ссылается
  • • Из разборов вычищены абзацы, объяснявшие устройство кода, которого в задаче уже нет: ручка массы, сравнение с паузой в баллистике, история подбора границ фазовой диаграммы

Исправлено

  • • **Сид больше не оставляет за собой снятые статьи.** Слаг убран из списка – команда его не трогает, а страница живёт своей жизнью, и опубликованную ученик видит. Теперь разбор задачи – это ровно список статей в команде: лишнее в базе удаляется, а удалённые слаги команда называет в отчёте
  • • **Статья о фазовой диаграмме печатала готовый ответ второго задания** («1.999 ещё колебания, 2.001 уже вращение») – искать сепаратрису было незачем. Разбор вправе объяснить явление целиком, но не вправе называть число, которое ученику велено измерить
  • • Таблица наборов амплитуд (меандр, пила, треугольник) лежала и в статье о рядах Фурье, и в задании 4. В статье осталось то, ради чего она там была – насколько быстро спадают амплитуды с ростом номера гармоники, и почему прямоугольник на слух резкий, а треугольник мягкий
  • • Комментарий к пропорции кадра описывал маятник как вертикальное окно 850 на 1200; окно давно 1400 на 900
  • • **Симуляция маятника – витрина, а исходник задания вернулся к исходному виду.** На странице показана сборка, в которой задания 1, 5 и 7 уже сделаны: шаг по времени выведен в пульт, считается энергия, есть трение. Это нарочно, чтобы школьник видел, что примерно должно получиться. Но сделана она была правкой самого Task_2/main.cpp – файла, который школьники клонируют и по которому написаны статьи: разбор печатал один код, в репозитории лежал другой, а два задания оказывались выполненными за ученика. Решённая версия переехала в Demo_Pendulum/, туда же, где уже лежат Task_0_moving и Demo_Oscillations, и так же не попадает в репозиторий заданий; Task_2/main.cpp вернулся к тому виду, по которому статьи писались
  • • Листинг в разборе маятника печатал заголовок окна «Задача 3», тогда как в файле стоит «Задача 2»: в заголовке номер папки, а не номер работы на курсе
v0.5.2 10.09.2026

Добавлено

  • • **Разбор задания 4** – условие Фано и подбор недостающих кодовых слов. Свободные концы дерева команда считает тем же правилом, каким их рисует виджет, и называет поимённо, поэтому сверять текст с картинкой глазами не приходится. Главная ловушка вынесена в отдельный ход решения: отданный букве конец закрывает под собой всю ветку, и концы берутся по одному со свежим пересчётом
  • • **Разбор задания 7** – объём оцифрованного звука и растрового изображения. Счёт ведётся в лоб на калькуляторе, а не разложением на степени двойки: раскладывать быстрее, но потерянный при сокращении множитель в бланке уже не виден, а умножить четыре числа подряд «наполовину» нельзя. Разборов два – звук и графика: формула у них разная, а порядок действий один
  • • **Разбор задания 11** – объём информации в записи базы данных. Два примера решают задачу в разные стороны: типовой ищет алфавит по длине записи, задача из банка – длину по алфавиту. Ход один, но на одном примере вторая подстановка не видна
  • • **Урезанный вид виджета fano-code** ("minimal": true): только таблица кодов и дерево, без ленты, вердикта, пресетов и правки состава букв. Так виджет стоит внутри разбора задания 4, где буквы заданы условием, а подбирают коды – лента с чужим сообщением и готовый ответ в пресетах там уже другая задача
  • • **Воркер на DeepSeek** (scripts/deepseek-worker.sh) и инструкция DEEPSEEK_WORKER.md: что можно отдать дешёвой модели, как ставить задание и чем проверять результат. Bash воркеру не выдан намеренно – manage.py, тесты и сборку Tailwind запускает оркестратор

Исправлено

  • • **Кодовое дерево не росло при показе с проектора.** Режим показа масштабирует страницу корневым кеглем, и SVG, заданный пикселями, оставался на стене прежним рядом с выросшим текстом. Размеры дерева переведены в rem; viewBox не тронут – он безразмерный, и внутренние координаты пересчитываются сами. Так можно именно здесь: размеры этого SVG никто не меряет, а у соседних виджетов рядом живёт попадание мышью по картинке
  • • Таблица кодов и дерево делили карточку пополам, хотя в дереве столбец на каждый лист, а таблица – узкий столбик из чипа и поля. Половина карточки уходила в пустоту, пока дерево рядом ездило под полосой прокрутки: теперь треть таблице, две трети дереву
v0.5.1 08.09.2026

Добавлено

  • • **Разбор задания – блок статьи, закрытый до занятия.** ArticleBlock.visibility даёт блоку три состояния: виден всем, виден только учителю, открыт ученикам. Учитель открывает разбор кнопкой прямо со страницы, не заходя в админку, – рубильник нужен посреди урока. Гость не видит закрытого разбора никогда, даже открытого ученикам: страница задачи доступна без входа, а решение в открытом интернете перестаёт быть заданием. Ученику на месте разбора показывается заглушка, чтобы задание не выглядело брошенным без ответа
  • • Текст закрытого разбора не доезжает до чужого браузера вовсе: он стирается во visible_blocks, а не прячется стилями – спрятанное стилями достаётся любому, кто откроет исходник страницы
  • • Панель разбора сворачивается нативным <details>: работает без JS и на проекторе, по умолчанию свёрнута
  • • Открытый учителем разбор переживает пересев текста: replace_blocks пересоздаёт блоки статьи, и без переноса видимости очередная правка опечатки молча забрала бы у класса разобранный ответ
  • • publish_spetskurs <задача> [слаги статей] – выпуск разборов: без слагов выходит вся задача, со слагами по одной вычитанной статье. Опечатка в слаге падает ошибкой, а не публикует тихо ноль строк
  • • Разбор задания о максимуме дальности: вывод формулы оптимального угла при броске с высоты y0 – sin(a) = V / sqrt(2V^2 + 2g*y0), L_max = (V/g) * sqrt(V^2 + 2g*y0). Оптимальная траектория приземляется под углом, дополняющим угол броска до прямого

Изменено

  • • Выпущен разбор задачи «Движение тела, брошенного под углом» – пять статей. Заодно вычищено: вводка обещала, что программа «не умеет посчитать за один раз», хотя парабола как раз считается формулой; заданий стало шесть вместо восьми, два выпавших из логики работы удалены
  • • Симуляция первой задачи – сборка с подвижной мишенью: пример того, что должно получиться в последнем задании. Исходник лежит вне репозитория задач, как и витрина на лендинге: школьники клонируют его целиком
  • • Ускорение свободного падения в коде первой задачи – g, а не G: заглавная буква занята гравитационной постоянной, и спутать их дороже, чем отступить от соглашения об именах констант. Правило было записано в «Основах C++», но сама задача ему не следовала
  • • Статья, которая открывается пунктом «1. …», не получает карточку-вводку: это список заданий, и первый пункт в синей рамке читался бы как предисловие ко всем остальным

Исправлено

  • • Описание отрисовки траектории: библиотека рисует историю точками (ImPlot::PlotScatter), а статьи баллистики и колебаний утверждали, что соседние точки соединяются отрезками. От этого зависело объяснение ложной частоты
  • • Ученик читал о себе в третьем лице («ученик крутит», «ученик выкрутит длину в ноль») – текст обезличен
  • • bool описан как «состояние задачи, записанное двумя битами»: каждая переменная занимает байт, и школьник уносил с урока ложное представление о памяти
v0.5.0 07.09.2026

Добавлено

Спецкурс (spetskurs)

  • ◦ Лендинг курса с живой витриной: планета вокруг звезды и спутник вокруг планеты считаются прямо в шапке страницы. Карточки семестров собираются из базы, а не рисуются разметкой, – раньше они расходились с ней при первом же переименовании
  • ◦ /spetskurs/tasks/ – список задач; /spetskurs/task/<slug>/ – разбор, симуляция и переход к соседним задачам; /spetskurs/basics/ – «Основы C++»: язык ровно в том объёме, чтобы прочитать свой main.cpp
  • ◦ Модель CourseTask связывает четыре вещи, которые жили порознь: физику явления, исходник main.cpp, WASM-симуляцию и задания. Пропорция кадра своя у каждой задачи: у маятника окно вертикальное, у гравитации – широкое, и общая пропорция сплющила бы половину
  • ◦ Обложки карточек – чертежи явлений вместо картинок: биения как сумма двух синусов с огибающей, обтекание цилиндра линиями тока, орбита эллипсом со звездой в фокусе. Кривые посчитаны, а не нарисованы на глаз
  • ◦ Шесть разборов задач и десять статей «Основ C++» (seed_spetskurs_*)
  • ◦ check_articles – проверка написанных статей по базе, а не по тестовой БД: съеденный markdown-ом LaTeX, кириллица внутри формул, битые перекрёстные ссылки, разъехавшийся порядок блоков
  • ◦ docs/spetskurs-deploy.md – сборка .wasm, выкладка и порядок выпуска разборов

Изменено

  • • Кегль на всех страницах сведён к девяти ступеням (12/14/16/18/20/24/30/36/48). До этого один и тот же подзаголовок был 12px в отчёте о попытке, 14px на карточке задания и 18px на лендинге, а рядом жили девять произвольных значений между 9 и 17px. Заголовки главных страниц – учебника, спецкурса, тренажёра ЕГЭ, уроков и «Обо мне» – выросли до 48px: это точка входа, а не строка оглавления. FontScaleTest роняет сборку на произвольном размере
  • • Теория спецкурса живёт на моделях учебника (Article с track='spetskurs'). TheoryPage и TheoryBlock удалены: это была урезанная копия Article/ArticleBlock, из которой учебник когда-то и вырос. Разборам достались типографика, MathJax, прогресс чтения и показ с проектора
  • • Разборы задач выходят по одной статье, когда вычитаны. Пока статья не выпущена, все страницы задачи говорят «Разбор готовится», а симуляция работает: задача на курсе есть, не готов только её текст
  • • Страница выпускников стала внутренней: ссылки в шапке нет, вход из профиля, посторонний получает 403. На самой странице у выпускника остались только имя и год выпуска
  • • Учитель вычитывает статью на сайте, а не в поле админки: неопубликованная статья открывается ему по прямой ссылке, и соседние статьи в сайдбаре считаются тем же правилом

Исправлено

  • • Колесо над симуляцией меняло масштаб графика и одновременно уводило страницу вниз. Библиотека курса это уже пробует предотвратить, но вешает обработчик колеса на window внутри iframe, а Chrome делает такие слушатели пассивными – и запрет прокрутки там молча игнорируется
  • • Счётчик материалов на карточке задачи считал все статьи, включая невыпущенные, и обещал «7 материалов» у задачи, разбора которой на сайте нет
  • • Повторный прогон seed-команды снимал с публикации уже выпущенную статью: решение о публикации принимает учитель, а команда правит только текст
  • • Блоки кода в статьях спецкурса: тёмные чернила на тёмном фоне, когда подсветка не знала языка
  • • Практикум 4, задача 8 – убрано первое предложение условия, оно повторяло заголовок
v0.4.1 01.09.2026

Добавлено

Учебник (textbook)

  • ◦ SectionExtension – личное продление дедлайна блока: одна запись на пару «ученик + блок», правится в админке. Продление только отодвигает дату – личный срок раньше общего ничего не закрывает, чтобы опечатка не отрезала ученика от задач. Продлённый видит свою дату на главной, в учебнике и в профиле, а его оценка за блок не идёт в средний балл, пока он досдаёт
  • ◦ Показ урока с проектора (static/js/article-present.js): полный экран, масштаб текста и листание блоков статьи. Слайд – это существующий блок урока, отдельной презентации в базе нет и расходиться ей с текстом негде. Клавиши → ↓ PageDown Пробел вперёд, ← ↑ PageUp назад, Home / End: пульт-презентер шлёт не стрелки, а PageUp/PageDown
  • ◦ Виджет graph-match – разбор задания 1 по шагам: схема дорог и таблица длин, под каждой буквой то, что о ней известно на этом шаге. Раунды считает не браузер, а seed-команда
  • ◦ Виджет truth-table – черновик таблицы истинности для любой задачи вида «задание 2». Выражение вводится на Python, разбирается своим рекурсивным спуском с питоновским приоритетом (до пяти переменных), клетки фрагмента заполняются кликом, столбцы красятся кистью на четыре цвета
  • ◦ Виджет truth-steps – разбор задания 2 по шагам, без построения полной таблицы истинности
  • ◦ Разборы теории заданий 1 и 2: команды seed_ege_theory_1, seed_ege_theory_2

Тесты (textbook)

  • ◦ SectionExtensionTest, OpenSectionTest, ArticlePresentModeTests, ProjectorFontScaleTest (каждая переменная шкалы кеглей обязана иметь rem-двойник), LinkRenderTests
  • ◦ Парсер truth-table сверяется с настоящим eval() Python: эталонные таблицы считает Django-тест, прогоняет их через node (textbook/tests_truth_table_parser.js). Виджеты монтируются в node на копии разметки статьи (tests_widget_mount.js) – node --check ловит только синтаксис, а не обращение к снесённой переменной. Без node оба теста пропускаются

Изменено

  • • В шапке «База знаний» стала «Учебником» и ведёт на учебник; прежний список уроков называется «Уроки»
  • • В учебнике по умолчанию раскрыт блок, где стоит фишка ученика, а не первый по счёту
  • • Ссылки из текста урока открываются в новой вкладке все, а не только внешние: ссылка внутри урока – это справка «если подзабыли», уводить с неё читателя незачем. Ссылка в первом абзаце статьи наконец красится как ссылка
  • • Панель проектора больше не растёт вместе с материалом: её размеры в px, а на 200% ряд кнопок занимал полторы строки урока. Масштаб набирается числом от 60 до 300%, а не только кнопками по 20%; нижняя граница опущена со 100% – на большом телевизоре вместо проектора крупный кегль наоборот мешает
  • • Шкала кеглей статьи получила rem-двойник для полного экрана: объявленная в px, она не двигалась с масштабом, и текст на проекторе оставался кегля 18.5px при любом увеличении
  • • EGE_CODE_TASKS: задание 10 отвечает кодом (банк собран из задач на маску сети), 12 и 15 – коротким ответом: машина Тьюринга и ДЕЛ разбираются на бумаге, редактор кода там мешал
  • • Блок «Теперь порешаем» в статье ЕГЭ – одна кнопка «Решать задачи», без «Настроить сложность» и без обещания 45 минут
  • • Из скелета урока по заданию ЕГЭ убран блок «Если условие другое»: у большинства заданий вариаций либо нет, либо они в одну строку, и блок наполнялся выдумками (docs/ege-theory-brief.md)

Исправлено

  • • Дедлайн блока проверяется по конкретному ученику во всех трёх точках приёма решений (quiz_is_locked(quiz, user)) – иначе продление не открывало ни практикум, ни самопроверку
  • • Карта курса на узком экране: карточки рисовались одна поверх другой – высоту им задавал ряд сетки, а обе грани лежат position: absolute и сами места не занимают

Безопасность

  • • widget_key экранируется в сообщении «Неизвестный виджет»: значение приходит из поля админки и попадало в innerHTML как есть
v0.4.0 31.08.2026

Добавлено

Тренировка по темам (quizzes)

  • ◦ PracticeSession / PracticeItem – сессия из нескольких задач банка и журнал каждой попытки. Одна модель на пять сценариев отбора (kind): по теме, работа над ошибками, смешанная, работа в классе, переписать решение
  • ◦ Два режима (mode): «Тренировка» проверяет ответ сразу и разрешает вторую попытку, «Экзамен» молчит до разбора и закрывается сам через час от старта. Время считает сервер – переведённые назад часы в браузере контрольную не продлевают
  • ◦ Хаб /ege/ разложен на три вкладки: карта 27 заданий, готовые варианты, личный прогресс. Гостю вкладка прогресса показывается заполненной примером (ege_stats.demo_overview), выведенным теми же формулами, что и настоящие числа
  • ◦ Карточка задания /ege/task/<n>/: теория, «решено X из Y», «N с первой попытки», форма запуска сессии
  • ◦ Верный ответ не сериализуется в страницу ни в одном режиме. Его отдаёт только кнопка «Показать ответ» (/reveal/), и это записывается как отказ от задачи: gave_up закрывает её нерешённой, дальнейшие ответы получают 409
  • ◦ Экзамен по заданию открывается после EgeTask.exam_unlock_threshold разных решённых задач (по умолчанию 10). Проверка на сервере, а не только в форме
  • ◦ Экзаменационный резерв Question.exam_only: задачи, скрытые от тренировок, чтобы контрольная проверяла знание темы, а не память о прорешанном. Заполняется командой mark_exam_pool
  • ◦ Классный набор Question.classroom_only: одинаковые задачи в одинаковом порядке у всего класса, рубильник EgeTask.classroom_enabled прямо на карточке задания. Из всей статистики исключён
  • ◦ Связка 19–21: Question.group_id / group_order, expand_groups тянет тройку целиком в любом сценарии. Три номера живут одной страницей /ege/task/19/, форма считает тройки. Уже решённый член связки приходит с подставленным ответом (carried) и в статистику не идёт
  • ◦ /ege/task/<n>/solved/ – архив решённых задач с собственным решением ученика, метриками отправки и кнопкой «Переписать решение» для задач на коде (сессия kind='retry', не оценивается)
  • ◦ Гибридная сложность: Question.difficulty из импорта, Question.solve_rate – по фактической доле верных первых попыток. Пересчёт командой recalc_ege_difficulty, ежедневно через Celery Beat
  • ◦ Режим проектора (static/js/present-mode.js): нативный полноэкранный режим и масштаб текста через корневой font-size, общий для сессии, варианта и практикума

Частичный балл за задания 26 и 27 (quizzes/ege_scoring.py)

  • ◦ Шкала 0/1/2 по правилу КИМ-2027: ответ – два числа, 1 балл за перестановку или одну верную ячейку. Сравниваются числа, а не раскладка вывода
  • ◦ Балл пишется в CodeSubmission.score, PracticeItem.score, UserAnswer.score, ExamTaskProgress.score; во всех остальных заданиях поле пустое
  • ◦ check_code_task прогоняет по этим двум заданиям **все** тесты и берёт худший результат вместо остановки на первом провале
  • ◦ Частичный балл не решает задачу: она остаётся в работе над ошибками, но в статистике считается половиной верного ответа

Учителю (quizzes)

  • ◦ /ege/class/ – сравнительная таблица класса: прогноз балла и на скольких заданиях он держится, последний экзаменационный вариант, долг по ошибкам, три самые дорогие дыры, график активности по неделям. Один класс за раз, внутри – по прогнозу вниз. class_rows() считает весь класс семью запросами
  • ◦ /ege/student/<id>/mistakes/ – долг ученика в режиме чтения: условие, его ответ или код с логом ошибки, панель с верным ответом
  • ◦ /ege/task/<n>/bank/ – раскладка банка по трём пулам с условием, отрисованным ровно так, как его увидит ученик: страница заодно работает проверкой вёрстки после импорта
  • ◦ /ege/?student=<id> – тот же хаб с цифрами ученика, без отдельного шаблона
  • ◦ Мгновенная проверка текстового ответа в практикуме: POST /quizzes/question/<id>/check/ отдаёт «верно/неверно», ничего не сохраняя и не раскрывая ответ

Аналитика (quizzes/ege_stats.py)

  • ◦ Вся статистика ЕГЭ собрана в один модуль – хаб, профиль и учительская таблица считают по одним формулам
  • ◦ Прогноз балла строится **только по экзаменам**: задание без экзаменационных попыток не даёт ни баллов, ни минут. Прогноз по вариантам – балл последней работы, а не среднее
  • ◦ Динамика по неделям, темп против 235 минут, «что подтянуть» с весом по цене задания, покрытие теории, разбивка «тренировка / экзамен» по каждому заданию

Команды и импорт

  • ◦ load_ege принимает банк задач (quiz_type='bank'), дедуплицирует по external_id, отсеивает тест-кейсы, которые не проверяют ничего нового
  • ◦ mark_exam_pool, retag_ege, recalc_ege_difficulty, ege_pools (перенос ручных решений учителя между базами), seed_ege_tasks, seed_ege_theory_17
  • ◦ Документация формата: docs/ege-bank-format.md, выкатка – docs/ege-bank-deploy.md, бриф по теории – docs/ege-theory-brief.md

Изменено

  • • Режим study во всём интерфейсе называется «Тренировка»; значение в базе осталось прежним
  • • Варианты и тренировка разведены по разным пулам задач (PRACTICE_QUIZ_TYPES = ('bank',)). Раньше прорешанный вариант молча вычерпывал тренировочную выдачу всех 27 заданий, а счётчик открытия экзамена при этом не двигался
  • • Константы ЕГЭ вынесены в quizzes/ege_constants.py; EGE_RECOMMENDED_TIME приведено к приложению спецификации КИМ-2027 и даёт ровно 235 минут
  • • ExamTaskProgress.attempts_to_solve растёт на каждой проверке кода, а не только на верной: нерешённая задача на коде вообще не попадала в статистику
  • • Профиль показывает один блок «Учебник»; все цифры по номерам ЕГЭ переехали на вкладку «Мой прогресс» тренажёра, чтобы два места не расходились
  • • MathJax подключается на каждой странице с условием одним общим _mathjax.html – в сессии тренировки формулы раньше показывались сырым LaTeX

Исправлено

  • • **Разбор сессии открывался у незавершённой сессии.** Посреди экзамена страница /ege/practice/<pk>/result/ печатала верный ответ по каждой неверно решённой задаче, а ответы экзамена правятся до самого конца – хватало второй вкладки. В тренировке та же дыра выдавала ответ мимо «Показать ответ» и его цены. Незакрытая сессия теперь редиректит обратно на решение
  • • **Данные страницы уходили в сырой <script> без экранирования.** json.dumps не трогает < и >, а в items_json попадает ответ ученика, в last_submissions_json – его код: строка </script> закрывала тег, и остаток становился разметкой на странице, которую учитель открывает под своей учётной записью. Добавлен quizzes.utils.js_json, применён на всех одиннадцати местах вставки
  • • Нечисловой ?group= на таблице класса ронял страницу пятисоткой – теперь трактуется как «все классы»
  • • Панель с верным ответом для учителя показывала пустой блок у каждой задачи на коде: три копии шаблона читали несуществующее tc.expected_output вместо TestCase.output_data, а Django рендерит неизвестный атрибут пустой строкой

Удалено

Система помощи (quizzes)

  • ◦ Полностью убран раздел «Помощь»: ученик больше не прикрепляет комментарии к строкам кода, учитель не получает уведомлений. На практике механизм не использовался
  • ◦ Модели HelpRequest и HelpComment удалены (миграция 0047), вместе с ними – 7 маршрутов /quizzes/help-requests/…, вьюхи, админка и записи в fix_dashes
  • ◦ NotificationConsumer и маршрут ws/notifications/ удалены: единственным их назначением были бейджи помощи. Остался один consumer – QuizConsumer
  • ◦ Удалены static/js/help-requests.js, static/js/notifications.js и шаблоны help_requests_list.html, help_request_review.html
  • ◦ Из навбара пропали иконка «Запросы помощи» (учитель) и дропдаун «Уведомления» (ученик); из карточки код-задачи – кнопка «Помощь», панель треда и подсказка про клик по номеру строки; из профиля – блок «Помощь»
v0.3.1 25.08.2026

Изменено

Главная страница (pages)

  • ◦ Неопубликованные блоки на карте курса больше не рисуются каждый своей карточкой: вместо них в конце маршрута стоит одна карточка «Новые материалы добавятся скоро». Состав будущих блоков можно менять, не показывая ученикам черновые названия
  • ◦ Счётчики «блоков» и «статей» на главной считают только опубликованное

Исправлено

  • • Иллюстрации в статьях учебника центрируются: preflight Tailwind делает img блочным, и text-align у figure их не двигал
  • • Широкая ASCII-таблица в уроке 7.2 показывается целиком – общий бокс 4:3 с object-fit: cover срезал у неё верхнюю и нижнюю строки
v0.3.0 25.08.2026

Добавлено

Главная страница (pages)

  • ◦ Карта курса: маршрут-змейка по всем блокам учебника. Неопубликованные блоки показаны как «скоро» и соединены пунктиром – маршрут не обрывается на середине
  • ◦ Карточка блока повторяет строку из учебника: дедлайн, оценка, шкала «задач на оценку», практикум, самопроверки и кнопка «Решать задачи». Учителю добавлена кнопка «Статистика»
  • ◦ Переворот карточки: клик по заголовку ведёт к последней прочитанной статье блока, клик по остальной площади разворачивает карточку списком статей
  • ◦ Метка «вы здесь» переезжает на следующий блок, когда практикум сдан на оценку не ниже 3 и начата хотя бы одна статья нового блока; после дедлайна блок отпускает метку сам (CourseMapPinTest)
  • ◦ Фишки одноклассников на карточках блоков и в списке статей: кто на каком блоке и на какой статье. Учитель выбирает классы галочками, ученик видит свой класс, аноним – никого
  • ◦ Врезка «Что это за сайт» для неавторизованного гостя: три CSS-анимации показывают, как читать карту, что учебник открыт всем, а задачи – по логину, который выдаётся лично
  • ◦ textbook.services.course_map() – единый источник данных карты; frontier_by_section() сворачивает маршрут класса до блоков, не пересчитывая его заново

Архив выпускников (accounts)

  • ◦ Публичная страница /alumni/: выпускные классы с общим фото, словом учителя и карточками выпускников, от свежего выпуска к самому первому
  • ◦ StudentGroup.graduation_year – он же признак архива: заполненный год уводит класс из активных списков. Общее фото и слово учителя – graduation_photo, graduation_note
  • ◦ Карточка выпускника (Profile.alumni_place, Profile.alumni_about): вуз и пара слов о себе, заполняет сам ученик в своём профиле
  • ◦ Фильтры surname_first и student_name: «Фамилия Имя», у выпускника – с пометкой «🎓 год». Применены в профиле и в списке запросов помощи
  • ◦ Пункт «Выпускники» в навигации

Учебник (textbook)

  • ◦ Иллюстрации в статьях: команда attach_article_images привязывает картинки из media/textbook/articles/<slug>/ к image-блокам по порядковому номеру и конвертирует PNG/JPG в WebP
  • ◦ Аватарки «кто прочитал статью» в строке «Прочитано» на странице статьи
  • ◦ visible_group_ids() переехала из textbook/views.py в textbook/services.py: одно правило приватности на главную, учебник и страницу статьи

Изменено

  • • «Учебник» убран из верхней навигации – его функцию взяла на себя главная. Страница /textbook/ осталась доступной по хлебным крошкам из статей
  • • Теория ЕГЭ переехала с главной учебника на страницу тренажёра: вкладки «Задачи» и «Теория», якорь #theory открывает нужную. Хлебные крошки статьи ЕГЭ ведут туда же
  • • История версий переехала с главной на отдельную страницу /pages/changelog/
  • • Слово «урок» на главной и в карточках блоков заменено на «статья»
  • • Виджеты учебника (дерево Хаффмана): метки 0/1 отводятся по нормали к ребру, коды не налезают на цифры, вердикт встаёт рядом с лентой
  • • Блок 7 учебника (seed_textbook_block7) переработан: тексты, формулы и места под иллюстрации

Исправлено

  • • В палитру brand добавлены пропущенные ступени 200/300/400/800. Шаблоны их уже использовали (border-brand-300, text-brand-400 и другие), но Tailwind молча не выдаёт класс для несуществующего оттенка – рамки и подписи оставались бесцветными на страницах выпускников, профиля, игр, «Об авторе» и в карточке статьи
v0.2.0 20.08.2026

Добавлено

Учебник (textbook)

  • ◦ Структура учебника (seed_textbook_structure) и наполнение блоков контентом: идемпотентные seed-команды seed_textbook_blockN — повторный прогон обновляет тексты, не задевая прогресс учеников и уже решённые задачи
  • ◦ Практикум блока: Section.practicum_quiz — отдельная страница обязательных задач, определяющая, пройден ли блок
  • ◦ Оценка за блок по числу решённых задач практикума: пороги grade_3_from / grade_4_from / grade_5_from настраиваются в админке, Section.grade_for() и Section.grade_scale() показывают ученику остаток задач до следующей ступени
  • ◦ Дедлайн блока (Section.deadline): после него задачи и самопроверки переходят в режим просмотра — решения не принимаются, свои ответы видны
  • ◦ Подсказки к задачам (Question.hint, модель HintChoice): текст и сам факт наличия подсказки скрыты, пока ученик не упрётся в задачу — три неудачные попытки, меньше трёх дней до дедлайна блока или рубильник Section.hints_open. Выбор «взял / отказался» пишется в статистику учителю
  • ◦ Учёт времени чтения статьи (ArticleProgress.time_spent_seconds): накапливается только при активной вкладке, порциями через textbook-progress.js
  • ◦ Отчёт учителя по блоку (section_stats): таблица учеников по группам — оценка, задачи практикума, самопроверки, прочитанные уроки, время чтения и решения, число ошибок
  • ◦ Детализация ошибок ученика (section_stats_errors): группировка по задачам с пометкой «решил позже»
  • ◦ Аватарки учеников на маршруте учебника (frontier_positions): фишка стоит на первой невзятой теме при чтении подряд; ученик видит свой класс, учитель — любой по фильтру, аноним — никого
  • ◦ Маршруты textbook:section_stats, textbook:section_stats_errors, textbook:track_time

Тесты (quizzes)

  • ◦ Question.hint и модель HintChoice с уникальностью по паре (ученик, задача)
  • ◦ Эндпоинт quizzes:question_hint — состояние подсказки и фиксация выбора
  • ◦ Quiz.is_self_check: самопроверки учебника доступны ученику без назначения на группу

Профиль (accounts)

  • ◦ Profile.avatar с кадрированием при загрузке (avatar-crop.js)
  • ◦ Просмотр профиля ученика суперпользователем: accounts:student_profile (profile/<user_id>/), студенту на чужом профиле — 403
  • ◦ Блок «Учебник» в профиле: оценки по блокам, средний балл, прогресс по урокам и практикуму, секция «что подтянуть» (textbook.services.profile_textbook_stats)
  • ◦ Страницы смены пароля (password_change_form, password_change_done)

Изменено

  • • Список тестов (quizzes:quiz_list) убран: точка входа к тестам — учебник, из теста возврат ведёт в блок или статью (textbook_link_for_quiz)
  • • Choice.Meta.ordering = ['id'] — без явной сортировки Postgres отдавал варианты в физическом порядке, и после пересева вопросов ученик видел их каждый раз по-новому
  • • shuffle_choices тасует варианты детерминированно (зерно — хеш текста вопроса): правильный ответ больше не стоит всегда первым, но порядок стабилен между пересевами
  • • PhotoSwipe в base.html ограничен ссылками с data-pswp-width — иначе лайтбокс перехватывал обычные текстовые ссылки внутри галереи
  • • Страница теста переведена на пошаговую навигацию по задачам (Alpine.js)

Исправлено

  • • **Оценивание задач с кодом:** check_code_task засчитывал задачу решённой, если проходил хотя бы один тест-кейс. Теперь требуются все. Уже накопленные CodeSubmission.is_correct задним числом не пересчитываются
  • • Метрики времени и памяти сохраняются только у полностью решённых задач (раньше писались от первого удачного теста)
  • • N+1 на главной учебника: статьи блоков и заданий ЕГЭ забираются одним Prefetch — было около полусотни запросов, стало 3 у анонима и 14 у ученика
  • • N+1 на странице теста: hint_states() считает состояния всех подсказок четырьмя запросами вместо двух-трёх на задачу
  • • Лишний COUNT в детализации ошибок блока

Безопасность

  • • **Подсказки отдавались в обход прав доступа:** question_hint_view брал задачу по id без проверки, что тест доступен ученику. Условия открытия подсказки блочные (рубильник учителя, близкий дедлайн) и не зависят от назначения теста, поэтому перебором question_id вытаскивались подсказки к практикуму чужой группы. Эндпоинт переведён на тот же гейт, что и страница теста
  • • **Скрытый блок отдавал содержимое по прямым ссылкам:** статья бралась только по Article.is_published, без учёта Section.is_published. Добавлен visible_articles() — единый фильтр для страницы статьи и обоих эндпоинтов прогресса; quiz_is_hidden() закрывает практикумы и самопроверки скрытых блоков. Суперпользователю скрытое доступно: блок вычитывается перед публикацией на самом сайте
  • • **Накрутка времени чтения:** размер порции был ограничен, частота запросов — нет, и цикл POST'ов рисовал в отчёт учителю любое время. Порция ограничена реально прошедшим временем по серверным часам
  • • **Инъекция в песочнице выполнения кода:** input_data подставлялся в sh -c "printf ...", где шелл всё ещё разворачивает $(...) и обратные кавычки. Теперь входные данные кладутся файлом и подаются через < stdin.txt
v0.1.8 10.06.2026

Добавлено

Страница «Обо мне» (pages)

  • ◦ Модели AuthorProfile, AuthorPhoto, AuthorVideo, AuthorEvent для управления контентом страницы из админки
  • ◦ AuthorEvent поддерживает типы: образование, публикация, конференция, награда/грант; поля year, month, is_current (флаг «настоящее время»), doi_or_url
  • ◦ Admin-регистрация всех четырёх моделей с list_editable, list_filter, search_fields и fieldset-группировкой для AuthorProfile
  • ◦ Миграции 0010 (создание четырёх моделей) и 0011 (добавление поля is_current в AuthorEvent)

Изменено

Страница «Обо мне» (pages)

  • ◦ about_page_view расширен: передаёт в шаблон profile, photos, videos, education, publications, conferences
  • ◦ Полный редизайн templates/about.html: hero-секция с портретом автора, галерея фото (Swiper.js), секция презентаций с нативным видеоплеером, секция хронологии событий (образование, публикации, конференции) с данными из AuthorEvent; секция видеозаголовок переименована в «Презентации с выступлений»
v0.1.7 13.05.2026

Изменено

Инфраструктура

  • ◦ Tailwind CSS мигрирован с CDN (cdn.tailwindcss.com) на локальную сборку: добавлены tailwind.config.js, static/css/tailwind.input.css, собранный static/css/tailwind.css (Tailwind v3.4.19, минифицирован)
  • ◦ package.json: добавлены скрипты tw:build и tw:watch для пересборки CSS
  • ◦ templates/base.html: CDN-тег <script> заменён на <link> к локальному файлу — устраняет деградацию UI на клиентских машинах без доступа к CDN (старые браузеры, файрвол)
v0.1.6 06.05.2026

Добавлено

Своя игра (games)

  • ◦ Новое приложение games/ с игрой «Своя игра» (модели Category, Question, QuestionMedia, GamePack, GamePackCategory, GameSession)
  • ◦ Полный цикл создания тем: форма с вопросами и медиафайлами (изображение / аудио / видео) на каждый вопрос и ответ; лимит 20 МБ на вопрос
  • ◦ Система модерации: список заявок со статусами pending / approved / rejected, детальная страница для инлайн-редактирования текста/ответа/очков и удаления медиа; комментарий модератора
  • ◦ Автор может исправить отклонённую тему и переотправить на проверку (статус возвращается в pending)
  • ◦ Паки вопросов: сборка нескольких одобренных тем в один игровой пакет (поддержка порядка тем)
  • ◦ Управление паками для персонала: список, переключатель публичности
  • ◦ Игровая сессия: Alpine.js-доска со всеми темами и вопросами, сохранение состояния через AJAX (CSRF-safe), восстановление незавершённой сессии при повторном входе
  • ◦ Настройка игроков перед началом: добавление/удаление участников, автоматический старт с «Игроком 1»
  • ◦ Модальное окно вопроса: текст, медиа вопроса, кнопка «Показать ответ», медиа ответа, кнопки «Правильно / Неправильно» с изменением счёта, «Пропустить»
  • ◦ Редактирование счёта игрока кликом по ячейке (через prompt())
  • ◦ Кнопка «Начать заново» со сбросом очков и состояния доски
  • ◦ Страница «Мои заявки» с группировкой по статусам
  • ◦ Страница списка паков с детальным просмотром

Изменено

UI

  • ◦ Профиль (profile.html): градиентный баннер, карточки метрик с иконками, полная поддержка тёмной темы, столбец «Тип» в таблице ответов, метка «ЕГЭ 2026»
  • ◦ Списки уроков и тестов: text-lg → text-base для заголовков карточек
v0.1.5 21.04.2026

Добавлено

Тренажёр ЕГЭ (quizzes)

  • ◦ Таблица результатов варианта: колонка **«Тест»** — перевод первичных баллов в тестовые по шкале ЕГЭ 2026 (константа EGE_SCORE_CONVERSION в views.py)
  • ◦ Таблица результатов варианта: колонка **«Время»** — суммарное время ученика на весь вариант
  • ◦ Страница **детальной статистики ученика** /ege/<id>/results/student/<user_id>/ — время, попытки, статус, CPU, память по каждой задаче. Доступна только суперпользователю
  • ◦ Имя ученика в таблице результатов стало кликабельным для суперпользователя (ссылка на страницу статистики)
  • ◦ Панель **«Показать ответ»** для суперпользователя во время прохождения теста: правильный ответ для текстовых задач, тест-кейсы (вход/выход) для задач на код

Изменено

Тренажёр ЕГЭ (quizzes)

  • ◦ Логика проверки code-задач: задача засчитывается, если **хотя бы один** тест-кейс прошёл успешно (ранее требовались все). Метрики CPU/памяти берутся от первого прошедшего теста
  • ◦ Диалог выхода из тренажёра: убрана кнопка «Уйти без сохранения», заменена на «Отмена» (кнопка «Сохранить» переименована в «Сохранить и выйти»)

Исправлено

UI

  • ◦ MathJax: атрибут async заменён на defer во всех шаблонах — устранён race condition с Alpine.js, из-за которого LaTeX-формулы не отображались при первом открытии страницы (проявлялось до прогрева кэша CDN)
v0.1.4 02.04.2026

Добавлено

Спецкурс (spetskurs)

  • ◦ Новый раздел платформы /spetskurs/ для курса численной физики
  • ◦ Модель Simulation — WASM-симуляции, запускаемые прямо в браузере через WebAssembly
  • ◦ Модель TheoryBlock — теоретические материалы курса с поддержкой богатого форматирования
  • ◦ Лендинг раздела, список и детальные страницы симуляций и теоретических блоков
  • ◦ Суб-навигация внутри раздела спецкурса
  • ◦ Admin: полная поддержка новых моделей с inline-редактированием

UI

  • ◦ Переключатель тёмной/светлой темы с полной поддержкой по всему сайту (Alpine.js + Tailwind)

Изменено

Спецкурс (spetskurs)

  • ◦ Модель Simulation: поле html_path вместо генерации пути из названия

Инфраструктура

  • ◦ WASM и скомпилированные JS-файлы симуляций исключены из git (деплоятся вручную на сервер)
v0.1.3 09.03.2026

Добавлено

Уроки (lessons)

  • ◦ Модель LessonAttachment — множественные файловые вложения к уроку (файл, название, порядок). Свойства display_title (название или имя файла) и extension (расширение)
  • ◦ Поля Slidev-презентации на модели Lesson: presentation_url (путь к собранному SPA), presentation_title (отображаемое название), presentation_pdf (PDF для скачивания)
  • ◦ Endpoint GET /lessons/<id>/file/<attachment_id>/ — скачивание конкретного вложения с верификацией lesson_id
  • ◦ Endpoint GET /lessons/<id>/presentation-pdf/ — скачивание PDF-версии презентации
  • ◦ Секция презентации в lesson_detail.html: синяя карточка с кнопками «Открыть» и «PDF»
  • ◦ Секция файлов в lesson_detail.html: зелёная карточка со списком вложений, значком расширения и кнопкой скачивания
  • ◦ Admin: LessonAttachmentInline в LessonAdmin, fieldset «Презентация Slidev», колонки has_presentation и attachment_count в списке
  • ◦ docs/slidev-guide.md — инструкция по созданию и деплою Slidev-презентаций к урокам
  • ◦ Slidev установлен в проект (package.json, @slidev/cli, @slidev/theme-default)

Изменено

Уроки (lessons)

  • ◦ Все upload_to переведены на единую иерархию media/lessons/{safe_title}/ через хелпер _lesson_upload_path
  • ◦ Удалено старое поле Lesson.file (одиночный файл заменён на LessonAttachment)
  • ◦ Dev-сервер (config/urls.py): _media_serve с index.html fallback для корректного открытия Slidev SPA
v0.1.2 05.03.2026

Исправлено

Тесты (quizzes)

  • ◦ CodeMirror-редактор кода переставал работать после переключения между задачами (ошибка Cannot read properties of undefined в prepareSelection). Причина: CM5 теряет внутреннее состояние при display:none от Alpine.js x-show. Решение: полное пересоздание CM-инстанса при возврате к задаче. Затронуты quiz_detail.html и ege_detail.html

Профиль (accounts)

  • ◦ Статистика «Верных ответов» и статистика по типам вопросов теперь считают уникальные вопросы вместо суммы всех ответов из всех попыток
v0.1.1 01.03.2026

Добавлено

Профиль пользователя (accounts)

  • ◦ Расширенная страница профиля с метриками активности: суммарное время, статистика по типам вопросов (выбор/текст/код), результаты по тестам (лучший балл), прогресс по ЕГЭ-заданиям, статистика запросов помощи и лайков
  • ◦ accounts/templatetags/profile_tags.py — кастомные фильтры duration_display и duration_short для форматирования timedelta в шаблонах профиля

Изменено

Профиль пользователя (accounts)

  • ◦ Полный редизайн templates/registration/profile.html: баннер с инициалами пользователя, мини-карточки метрик, тематические секции (типы вопросов, тесты, ЕГЭ, помощь), таблица последней активности, кнопка перехода в тренажёр ЕГЭ
  • ◦ ProfileView в accounts/views.py расширен: передаёт в контекст агрегированную статистику по тестам, ответам, ЕГЭ-прогрессу, помощи и лайкам
v0.1.0 23.02.2026

Добавлено

Тренажёр ЕГЭ (quizzes)

  • ◦ Полноценный тренажёр для подготовки к ЕГЭ по информатике
  • ◦ Публичный список вариантов ЕГЭ с навигацией из главного меню
  • ◦ Страница прохождения варианта: таймер обратного отсчёта, навигация по заданиям, автосохранение ответов
  • ◦ Поддержка всех типов заданий: выбор ответа, свободный текст, Python-код с автопроверкой
  • ◦ Нормализация ответов (пробелы, регистр) для корректного сравнения
  • ◦ Страница результата после завершения варианта с подсчётом баллов
  • ◦ Таблица результатов с разбивкой по заданиям и сортировкой
  • ◦ Страница детального просмотра решения (код, тесты, метрики CPU/памяти)
  • ◦ Личная статистика: лучшие результаты, прогресс по заданиям
  • ◦ Модальное окно с предупреждением о сохранении при уходе со страницы

Модели и данные

  • ◦ Quiz.slug — уникальный слаг для URL вариантов ЕГЭ
  • ◦ Quiz.is_ege, Quiz.ege_task_number, Quiz.ege_max_score — поля экзамена
  • ◦ Question.correct_text_answer — эталонный ответ для текстовых заданий ЕГЭ
  • ◦ ExamTaskProgress — прогресс по каждому заданию варианта (ответы, код, статус проверки)
  • ◦ ExamTaskProgress.best_time, best_memory, best_cpu — лучшие метрики выполнения
  • ◦ ExamTaskProgress.best_code, best_answer — снимки лучших решений
  • ◦ SolutionAttachment — вложения к решениям
  • ◦ Индексы БД для оптимизации запросов результатов
  • ◦ 10 миграций (0021–0030) включая data-миграцию для генерации slug и переноса медиафайлов

Management-команда load_ege

  • ◦ Импорт вариантов ЕГЭ из JSON-фикстур (python manage.py load_ege <file.json>)
  • ◦ Поддержка всех типов вопросов: choice, text, code с тест-кейсами
  • ◦ Загрузка медиафайлов (изображения, файлы данных) с привязкой к slug варианта
  • ◦ Защита от дубликатов по slug

Шаблоны и UI

  • ◦ ege_list.html — список вариантов с карточками
  • ◦ ege_detail.html — интерфейс прохождения экзамена с боковой панелью навигации
  • ◦ ege_result.html — результат завершённого варианта
  • ◦ ege_results.html — таблица всех результатов с popover-сортировкой
  • ◦ ege_solution_detail.html — детальный просмотр решения с подсветкой кода
  • ◦ Ссылка «Тренажёр ЕГЭ» в навигации (base.html)

JavaScript и фронтенд

  • ◦ ege-timer.js — таймер обратного отсчёта с визуальным предупреждением при истечении времени
  • ◦ Расширение quiz-async.js для поддержки ЕГЭ-режима (прогресс заданий, обновление статусов)

Template tags

  • ◦ ege_filters.py — кастомные фильтры для шаблонов ЕГЭ (форматирование баллов, времени, метрик)

Инфраструктура

  • ◦ Структурированное хранение медиафайлов ЕГЭ по slug варианта
  • ◦ Расширение Celery-задач для сбора метрик CPU и памяти при проверке кода
  • ◦ URL-маршрутизация: urls_ege.py подключён в config/urls.py

Изменено

  • • Админка: расширены list_display, фильтры и inline-формы для ЕГЭ-моделей
  • • quizzes/utils.py — утилиты нормализации ответов и работы с результатами
  • • WebSocket consumer обновлён для поддержки ЕГЭ-задач
  • • .gitignore — добавлены fixtures и медиафайлы ЕГЭ
v0.0.2 09.02.2026

Добавлено

Система помощи (quizzes)

  • ◦ Запросы помощи: ученик задаёт вопрос по задаче, учитель отвечает в диалоге
  • ◦ Комментарии к конкретным строкам кода (inline line threads в CodeMirror)
  • ◦ Общие комментарии через панель помощи
  • ◦ Уведомления в реальном времени (WebSocket + polling fallback)
  • ◦ Бейдж непрочитанных уведомлений в навигации
  • ◦ Dropdown со списком уведомлений и превью последнего ответа
  • ◦ Авто-открытие панели помощи при клике по уведомлению (open_help параметр)
  • ◦ Точечная очистка непрочитанных: unread сбрасывается только при явном открытии диалога (mark_read)
  • ◦ Страница ревью для учителя с inline-комментариями к коду
  • ◦ Список запросов помощи для учителя с фильтрацией по статусу
  • ◦ Модели HelpRequest и HelpComment с трекингом статусов
  • ◦ WebSocket consumer NotificationConsumer (/ws/notifications/)
  • ◦ Админка для HelpRequest/HelpComment

UI улучшения

  • ◦ Подсветка синтаксиса Python через highlight.js (результаты, детали попытки)
  • ◦ Баннер статуса WebSocket-соединения (connected/disconnected/reconnecting/polling)
  • ◦ Оптимистичная блокировка кнопки отправки кода (до ответа сервера)
  • ◦ Секция «Архив» в списке тестов (завершённые тесты, свёрнуты по умолчанию)
  • ◦ Колонка «ЕГЭ» в статистике теста
  • ◦ Формат баллов X/Y в статистике (вместо просто числа)
  • ◦ Маркеры строк с комментариями (жёлтая подсветка, жирные номера строк)
  • ◦ Подсказка о кликабельности номеров строк (с авто-скрытием)

Изменено

  • • Убрана публичная регистрация: аккаунты создаёт преподаватель
  • • Страница входа: заменена ссылка «Зарегистрируйтесь» на подсказку «Данные выдаёт преподаватель»
  • • Улучшена админка пользователей: добавлены list_display, фильтр по группе и ЕГЭ
  • • Админка вопросов: поле title, поиск по заголовку и тексту
  • • Команда Celery в README обновлена (--pool=solo -Q default,code_execution)
  • • CodeMirror: фиксированная высота 500px вместо auto min/max

Модели

  • ◦ Profile.is_ege — флаг ученика, готовящегося к ЕГЭ
  • ◦ Question.title — краткое название вопроса (для отображения в уведомлениях и админке)
v0.0.1 06.02.2026

Добавлено

Аккаунты (accounts)

  • ◦ Регистрация и авторизация пользователей
  • ◦ Профили учеников с привязкой к учебным группам (классам)
  • ◦ Модель StudentGroup для организации учеников по классам

Страницы (pages)

  • ◦ Главная страница и "Обо мне" с блочным конструктором контента
  • ◦ Типы блоков: текст, изображение, текст + изображение
  • ◦ Настройки стилей: шрифты (размер, семейство), цвета текста и заголовков, фон карточек
  • ◦ Выравнивание текста и изображений
  • ◦ Обрезка, позиционирование и масштабирование изображений
  • ◦ Настройки object-fit, border-radius и opacity для изображений

Уроки (lessons)

  • ◦ Иерархия контента: Раздел (Section) > Урок (Lesson) > Блоки контента (LessonBlock)
  • ◦ Поддержка файловых вложений для скачивания
  • ◦ Поле для видео URL (YouTube, Vimeo)
  • ◦ Превью-карточки с изображениями и кратким описанием
  • ◦ Гибкая компоновка блоков: вертикально, горизонтально (текст слева/справа)
  • ◦ Настройки шрифтов, цветов и фонов для каждого блока

Тесты (quizzes)

  • ◦ Три типа вопросов: выбор ответа (choice), свободный текст (text), Python-код (code)
  • ◦ Тест-кейсы (TestCase) с входными данными и ожидаемым выводом для code-вопросов
  • ◦ Файлы с данными для скачивания в вопросах
  • ◦ Назначение тестов группам и отдельным ученикам (QuizAssignment)
  • ◦ Ограничение попыток: глобальное для теста и индивидуальное через назначения
  • ◦ Временные окна доступа: глобальные (start_date/end_date) и переопределяемые через назначения
  • ◦ Результаты прохождения (UserResult) с подсчётом баллов и времени
  • ◦ Детальные ответы (UserAnswer) с привязкой к результатам
  • ◦ Статистика по тестам и вопросам

Асинхронная проверка кода

  • ◦ Celery + Redis для фоновой проверки Python-кода в Docker-песочнице
  • ◦ WebSocket (Django Channels + Daphne) для real-time обратной связи
  • ◦ Модель CodeSubmission для отслеживания статуса (pending > running > success/failed/error)
  • ◦ Frontend quiz-async.js: WebSocket-клиент, визуальная индикация статуса, обновление UI без перезагрузки
  • ◦ WebSocket routing: /ws/quiz/<quiz_id>/

Админ-панель

  • ◦ Кастомные формы для тестов с inline-редактированием вопросов и вариантов ответа
  • ◦ Inline-редактирование тест-кейсов для code-вопросов
  • ◦ Массовое назначение тестов группам
  • ◦ Кастомная change_form для Quiz с дополнительными действиями

Инфраструктура

  • ◦ PostgreSQL через psycopg2-binary
  • ◦ Gunicorn (HTTP) + Daphne (WebSocket) + Nginx (reverse proxy + SSL)
  • ◦ Tailwind CSS для стилизации + Alpine.js для интерактивности
  • ◦ WhiteNoise для раздачи статических файлов
  • ◦ Docker-песочница для безопасного выполнения пользовательского кода
  • ◦ Systemd-сервисы: site (Gunicorn), daphne, celery, redis-server