Skip to content

Массовая проверка лабораторных из админки - #53

Open
markpolyak wants to merge 4 commits into
mainfrom
feature/bulk-grading
Open

Массовая проверка лабораторных из админки#53
markpolyak wants to merge 4 commits into
mainfrom
feature/bulk-grading

Conversation

@markpolyak

@markpolyak markpolyak commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Проверка всей группы по одной лабораторной за один запуск. Разбор принятых решений — docs/BULK_GRADING_PLAN.md, поведение и API — docs/PROJECT_DESCRIPTION.md.

Как это выглядит

Кнопка «Заполнить таблицу» добавлена в строку каждой лабы на существующей странице /admin/courses/{course_id}/labs — там, где уже запускается рассылка обновлений шаблона. В диалоге выбирается группа (список лаб общий для курса, а лист у каждой группы свой) и, при необходимости, файл с ФИО; дальше — прогресс и отчёт по каждому студенту.

Два режима

Переключаются полем «Файл с ФИО студента»:

Поле Что происходит
заполнено Перебираются все репозитории организации с префиксом лабы. Из указанного файла берётся первая строка — ФИО. По ФИО находится строка в таблице, GitHub-логин из имени репозитория записывается в столбец GitHub, затем работа проверяется
пусто Проверяются только студенты, у которых логин уже указан в таблице

Подсказки в поле берутся из files лабы; новый необязательный ключ student-name-file предзаполняет его.

Проверка — тот же пайплайн, что и при самостоятельной отправке студентом: обязательные файлы, workflows, коммиты, запрещённые модификации, CI, номер варианта, баллы из логов, штраф за просрочку.

Ключевые решения

Одно ядро для одиночной и массовой проверки. grade_lab — 240 строк, где перемешаны HTTP-слой, поячеечное чтение таблицы и логика оценки. Решение об оценке вынесено в grading/bulk.py:evaluate_student, grade_lab переписан в тонкую обёртку. Контекст таблицы передаётся не значением, а функцией, вызываемой лениво — только когда CI дал результат, который есть смысл записывать. Без этого потерялось бы свойство grade_lab возвращать ошибки репозитория и CI, не открывая соединение с Google Sheets. Характеризационные тесты проходят без изменений.

Чтение таблицы одним запросом. Поячеечные хелперы тратят ~6 обращений к Sheets API на студента — на группе из 30 это ~180 запросов при квоте 60/мин. Массовый режим читает лист один раз через get_all_values() и ищет по сетке в памяти; запись — пакетами по 10 ячеек по ходу работы, чтобы падение или отмена не теряли уже проставленные оценки.

Сопоставление ФИО — только точное после нормализации (пробелы, регистр, ё/е). Нечёткий подбор не делается: цена ошибки — оценка не тому студенту. Несопоставленные и неоднозначные ФИО уходят в отчёт с прочитанным значением.

Конфликт логинов. Если в строке студента указан другой логин, репозиторий не проверяется ни под одним из них и попадает в отчёт: молча перезаписать логин — потерять факт самостоятельной регистрации, проверить под логином из строки — проверить чужую работу.

Защита оценок can_overwrite_cell действует и здесь; флага принудительной перезаписи нет.

Пробный запуск выполняет все проверки и строит полный отчёт, не записывая ни оценок, ни логинов.

Фоновая работа повторяет устройство grading/propagate.py: хранилище в памяти под блокировкой, 202 с job_id, опрос статуса, 409 на повторный запуск для той же тройки курс/группа/лаба. Переиспользованы существующие require_admin, find_lab_config, GitHubClient.list_org_repos; из нового в клиенте GitHub — только get_file_content.

Новые эндпоинты

Метод Путь
POST /admin/courses/{course_id}/groups/{group_id}/labs/{lab_id}/bulk-grade
GET /admin/bulk-grade-jobs/{job_id}
POST /admin/bulk-grade-jobs/{job_id}/cancel

GET /admin/courses/{course_id}/labs дополнен полями files и name_file.

Тесты

349 → 461. Новое: tests/test_bulk_grading.py (фильтрация репозиториев с коллизией os-task1/os-task10, разбор и сопоставление ФИО, политика записи логина, grid-хелперы, общее ядро проверки, хранилище работ, оркестратор — пакетная запись, пробный запуск, защита ячеек, отмена, изоляция ошибок по студентам, недоступный список репозиториев), get_file_content в tests/test_github_client.py, а в tests/test_admin_endpoints.py три новых маршрута добавлены в PROTECTED_ROUTES плюс покрытие 202/409/выбора режима/отмены.

Проверка

461 тест, сборка фронтенда, ESLint. Интерфейс проверен вручную в браузере целиком — на backend с заглушками Google Sheets и GitHub прогнаны оба режима: подстановка подсказок файла из конфига, отчёт по группе, статусы «зачтено», «уже проверено», «ошибка», «конфликт логинов», «ФИО не сопоставлено», отметка «логин записан», исключение репозиториев чужого префикса.

🤖 Generated with Claude Code

@markpolyak markpolyak closed this Sep 7, 2026
@markpolyak markpolyak reopened this Sep 7, 2026
markpolyak and others added 4 commits September 8, 2026 07:20
Grade every student of a group for one lab in a single run, with the same
checks as a self-submitted work.

Two modes. With a name file, every repository of the lab in the course
organization is inspected: the first line of that file gives the
student's full name, which resolves their spreadsheet row and records
their GitHub username. Without one, only students who already have a
username in the sheet are graded.

grade_lab mixed the HTTP layer, per-cell Sheets reads and the grading
logic in one 240-line function, so the bulk run could not reuse it.
grading/bulk.py:evaluate_student now holds that decision and receives
what it needs from the spreadsheet through a lazily-invoked provider, so
grade_lab still answers repository and CI errors without opening a Sheets
connection. Characterization tests pass unchanged.

The run is a background job with the same machinery as propagate: an
in-memory store under a lock, one job per (course, group, lab), 202 with
a job_id, polling and 409 on a second start. The worksheet is read once
with get_all_values() and grades are flushed in batches of 10 - the
per-cell helpers spend ~6 Sheets requests per student, which for a group
of 30 exceeds the 60 reads/minute quota.

Adds GitHubClient.get_file_content and grid-based counterparts of the
per-cell sheet helpers; reuses the existing list_org_repos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- tests/test_bulk_grading.py: repo prefix filtering (including the
  os-task1 / os-task10 collision), name file parsing, name normalization
  and matching, the GitHub-cell write policy, grid helpers, the shared
  evaluate_student decision, the job store and the orchestrator (batched
  writes, dry run, cell protection, cancellation, per-student error
  isolation, unavailable org repos).
- tests/test_github_client.py: get_file_content decoding, BOM, missing
  file, directory, size limit, non-UTF-8 and encoding "none".
- tests/test_admin_endpoints.py: the three bulk routes join
  PROTECTED_ROUTES, plus 202/409/mode-selection/cancel coverage for the
  endpoint, and the bulk job store joins the clean_job_store fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The teacher is already picking a course and a lab there for template
updates, so the run starts from the same table: a second action per row
opens a dialog that asks for the group (labs are per course, the sheet is
per group) and the file holding the student's full name, with the lab's
required files offered as suggestions and student-name-file preselected.

The dialog then shows progress and the per-student report, polling the
job every 2s like the propagate dialog does, with a stop button while the
run is going.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- docs/PROJECT_DESCRIPTION.md: the two modes, name matching rules, the
  GitHub-cell write policy, cell protection, dry run, and the job/quota
  details; the three new routes join the admin table.
- docs/COURSE_CONFIG.md: the student-name-file lab key.
- CLAUDE.md: where the shared grading decision lives and the endpoints.
- docs/BULK_GRADING_PLAN.md: rewritten as a record of the decisions
  behind the feature rather than a work plan, and trimmed to what
  PROJECT_DESCRIPTION does not already say.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant