This repository contains a small school timetabling engine in Python.
Core idea:
- each subject has a workload weight (difficulty points per lesson);
- for each grade we specify:
- the maximum number of lessons per day;
- the maximum daily workload in points;
- the algorithm generates a weekly timetable for several classes so that:
- the weekly curriculum is satisfied (required lessons per subject);
- no day exceeds the allowed workload or lesson count;
- "heavier" subjects are placed on peak-performance days (e.g. Tuesday/Wednesday);
- Monday and Friday stay lighter.
The current dataset uses Russian sanitary workload norms (SanPiN) as an example, but the engine itself is generic: all limits are configured via YAML and can be adapted to any country’s school regulations.
Учебное расписание в школе должно соответствовать не только учебному плану, но и санитарно-гигиеническим требованиям. На практике это означает, что:
- у каждого учебного предмета есть своя «трудность» (количество баллов за урок);
- суточная и недельная нагрузка учащихся ограничена;
- тяжёлые предметы нельзя ставить подряд и на последние уроки;
- распределение нагрузки по дням недели должно учитывать динамику работоспособности (наиболее нагруженные дни — вторник и среда).
Ручное составление расписания с учётом всех этих факторов сложно и занимает много времени. Цель данного проекта — показать, как можно формализовать эти требования и использовать алгоритмы на языке Python для автоматического формирования школьного расписания.
Проект SANPIN-Schedule реализован как учебный прототип, но со «взрослой»
структурой кода: данные в YAML, модели в виде dataclass, модульные тесты,
генерация отчётов в CSV и Markdown.
Цель: разработать прототип системы, которая автоматически формирует школьное расписание, учитывая:
- нормативные ограничения по нагрузке (в баллах и часах);
- количество кабинетов и их вместимость;
- занятость учителей;
- одногорбый и двугорбый профиль недельной нагрузки.
Основные задачи:
- Определить формат входных данных (классы, предметы, учителя, кабинеты, лимиты СанПиН) в машиночитаемом виде (YAML-файлы).
- Сформулировать математическую модель задачи (переменные, ограничения, целевая функция).
- Реализовать каркас решателя на Python:
- жадный эвристический алгоритм (учебная версия);
- возможность в будущем заменить его на CP-SAT/ILP (например, OR-Tools).
- Продемонстрировать работу системы на примере нескольких классов (5А, 5Б, 6А, 7А) и визуализировать результат.
Проект организован как небольшой Python-пакет:
school-timetabling-sanpin/
├─ data/
│ ├─ subjects.yaml # предметы + трудность (баллы) и часы по ступеням
│ ├─ classes.yaml # классы, численность, ступень (grade)
│ ├─ rooms.yaml # кабинеты, вместимость, профиль
│ ├─ teachers.yaml # учителя и их предметы
│ └─ sanpin_limits.yaml # лимиты по урокам и баллам, недельный профиль нагрузки
├─ app.py # простой API-сервис для генерации школьного расписания
├─ src/
│ ├─ __init__.py
│ ├─ models/
│ │ └─ entities.py # dataclass-сущности: Subject, SchoolClass, Room, Teacher, TimetableEntry, SchoolData
│ ├─ io/
│ │ ├─ loader.py # загрузка YAML в виде сырых dict и SchoolData
│ │ └─ exporter.py # экспорт расписания в CSV
│ ├─ solvers/
│ │ └─ ortools_solver.py # жадный генератор расписания (готов к замене на CP-SAT/ILP)
│ └─ cli.py # CLI: «сгенерировать расписание» → timetable.csv
├─ tools/
│ └─ generate_timetable_md.py # создаёт timetable.md (Markdown-таблицы) из timetable.csv
├─ notebooks/
│ └─ 01_check_daily_load.py # проверка суточной нагрузки по баллам
├─ tests/
│ └─ test_daily_load_limits.py # pytest: контроль лимитов по урокам и баллам
│ └─ test_teacher_constraints.py # тесты ограничений по учителям
│ └─ test_class_sanpin_constraints.py # тест соблюдения СанПиН по баллам для классов
│ └─ test_api_smoke.py # простейшие smoke-тесты для HTTP-API
├─ docs/
│ ├─ Описание_проекта.md # (этот файл) техническое описание
│ └─ Краткое_описание_для_школы.md # краткое объяснение для учителя/жюри
│ └─ Pipeline.md
├─ scripts/
│ └─ class_load_report.py
│ └─ teacher_load_report.py
│ └─ export_timetable.py
├─ CHANGELOG.md
├─ requirements.txt
├─ requirements-dev.txt
├─ pytest.ini
├─ timetable.csv
├─ timetable.md
├─ LICENSE
├─ .gitignore
├─ .editorconfig
└─ README.md
4. **Формат входных данных**
4.1. Предметы (data/subjects.yaml)
Каждый предмет описывается:
id — идентификатор (латиницей, для кода),
name — полное название (на русском),
difficulty_points — трудность в баллах,
weekly_hours_by_grade — количество уроков в неделю по ступеням.
Пример:
- id: math
name: "Математика"
difficulty_points: 4
weekly_hours_by_grade:
"5": 5
"6": 5
"7": 5
4.2. Классы (data/classes.yaml)
- id: "5A"
grade: 5
size: 25
id — обозначение класса,
grade — ступень (5, 6, 7 и т.д.),
size — количество учеников (нужно для сопоставления с вместимостью кабинетов).
4.3. Кабинеты (data/rooms.yaml)
- id: "101"
name: "Кабинет 101"
capacity: 28
type: "общий"
capacity — максимальное число учеников,
type — профиль кабинета (например, «физика», «биология», «спорт»).
4.4. Учителя (data/teachers.yaml)
- id: "t_math_1"
name: "Иванова И.И."
subjects: ["math"]
max_lessons_per_day: 5
max_lessons_per_week: 25
4.5. Лимиты СанПиН (data/sanpin_limits.yaml)
max_lessons_per_day_by_grade:
"5": 6
"6": 6
"7": 7
max_points_per_day_by_grade:
"5": 20
"6": 22
"7": 24
slots_per_day: 6
target_load_profile:
monday: 0.8
tuesday: 1.1
wednesday: 1.1
thursday: 1.0
friday: 0.7
heavy_subject_ids:
- "math"
- "foreign"
- "history"
5. **Математическая постановка** (упрощённо)
Полная задача составления расписания — это задача целочисленного программирования.
Вводятся бинарные переменные:
- \(x_{c,d,p,s} \in \{0,1\}\) — равно 1, если в классе \(c\) в день \(d\) на уроке \(p\)
стоит предмет \(s\); иначе 0.
Базовые ограничения:
1. **Ограничение уникальности урока**
В каждом слоте (день \(d\) + номер урока \(p\)) у класса \(c\) может быть не более одного предмета:
\[
\sum_{s} x_{c,d,p,s} \le 1.
\]
2. **Выполнение недельного плана**
Суммарное количество слотов по предмету \(s\) за неделю для класса \(c\)
должно совпадать с учебным планом \(H_{c,s}\) (уроков в неделю по предмету \(s\)):
\[
\sum_{d,p} x_{c,d,p,s} = H_{c,s}.
\]
3. **Суточный лимит по баллам**
Пусть \(w_s\) — трудность предмета \(s\) в баллах. Тогда для каждого дня \(d\)
суммарная нагрузка класса \(c\) не должна превышать допустимый максимум
`max_points_per_day(grade(c))`:
\[
\sum_{p,s} x_{c,d,p,s} \cdot w_s
\le \text{max\_points\_per\_day}(\text{grade}(c)).
\]
4. **Лимит по числу уроков**
Аналогично ограничивается общее число уроков в день:
\[
\sum_{p,s} x_{c,d,p,s}
\le \text{max\_lessons\_per\_day}(\text{grade}(c)).
\]
В полном варианте модели сюда добавляются также ограничения по:
- кабинетам (один кабинет — не более одного класса в один слот),
- учителям (один учитель — не более одного урока в слот),
- размещению «тяжёлых» предметов (например, запрет последних уроков).
5. **Ограничения по тяжёлым предметам**
Тяжёлые предметы не ставятся на последние уроки дня (или на 1-й урок, в зависимости от методики).
В полном CP-SAT/ILP-решателе к этому добавляются ограничения по кабинетам и учителям:
- один кабинет — не более одного класса на слот;
- один учитель — не более одного урока в слот;
- класс не может быть одновременно в двух кабинетах.
-----
## 6. **Реализованный алгоритм (жадный прототип)**
В файле src/solvers/ortools_solver.py реализован жадный алгоритм:
Для каждого класса строится недельный план по предметам:
используется информация из weekly_hours_by_grade.
Дни недели сортируются по профилю нагрузки (target_load_profile):
сначала дни с большим коэффициентом (вторник, среда),
затем остальные.
Для каждого дня и слота алгоритм:
выбирает предметы, у которых ещё остались невыполненные часы;
сортирует их по убыванию трудности и оставшихся часов;
выбирает такой предмет, который:
не нарушит лимит по баллам на день;
не повторяется два раза подряд, если есть альтернатива.
Результат:
формируется список записей TimetableEntry / dict;
далее он экспортируется в timetable.csv и timetable.md.
Достоинства жадного подхода:
простота реализации и объяснения;
работает без внешних библиотек оптимизации;
хорошо подходит как первый шаг для школьного проекта.
Ограничения:
не гарантирует глобально оптимального расписания;
распределение по кабинетам и учителям пока не учитывается.
## 7. **Выходные артефакты**
7.1. CSV-расписание (timetable.csv)
Генерируется командой:
python -m src.cli generate --data-dir data/ --output timetable.csv
Структура:
class_id,day,slot,subject_id,room_id,teacher_id
5A,tuesday,1,math,,
5A,tuesday,2,russian,,
...
7.2. Markdown-расписание (timetable.md)
Генерируется из CSV:
python tools/generate_timetable_md.py
В нём для каждого класса построены таблицы по дням недели и урокам
с русскими названиями предметов.
## 8. **Отчёты и аналитика**
После генерации расписания (по классам, учителям и дням) проект
предоставляет несколько утилит для анализа нагрузки.
8.1. Экспорт расписания
Скрипт `scripts/export_timetable.py`:
- вызывает ядро планировщика `generate_timetable(...)`;
- записывает результат в:
- `timetable.csv` — таблица `class_id, day, slot, subject_id, room_id, teacher_id`;
- `timetable.md` — сгруппированное по классам и дням расписание в формате Markdown
(удобно для чтения директором, завучем, учителями).
8.2. Нагрузка учителей
Скрипт `scripts/teacher_load_report.py`:
- использует:
- `timetable.csv` — итоговое расписание,
- `data/teachers.yaml` — список учителей, их предметы и лимиты нагрузки;
- считает фактическое количество уроков:
- в неделю для каждого учителя,
- по каждому дню недели;
- сравнивает эти значения с ограничениями:
- `max_lessons_per_day`,
- `max_lessons_per_week`;
- формирует текстовый отчёт, где для каждого учителя видно:
- соблюдается ли нормативная нагрузка,
- в какие дни есть перегрузка.
Этот отчёт показывает сложность задачи не только с точки зрения
распределения уроков по классам, но и с точки зрения реальной
занятости учителей.
8.3. Нагрузка классов по СанПиН
Скрипт `scripts/class_load_report.py`:
- использует:
- `timetable.csv`,
- `data/subjects.yaml` (баллы трудности `difficulty_points`),
- `data/classes.yaml` (ступень класса),
- `data/sanpin_limits.yaml` (лимиты баллов),
- для каждого класса и дня считает суммарную «трудность» дня
в баллах;
- сравнивает её с `max_points_per_day_by_grade` для соответствующей
ступени;
- подсвечивает дни, в которых класс перегружен по СанПиН.
Таким образом, ядро планировщика даёт не только расписание, но и
обратную связь: какие решения по распределению уроков и учителей
реально соответствуют нормативам, а какие требуют корректировки.
## 9. **Проверка ограничений и тестирование**
Проект содержит:
Скрипт notebooks/01_check_daily_load.py:
читает timetable.csv, subjects.yaml, sanpin_limits.yaml;
считает суточную нагрузку по баллам;
выводит отчёт по каждому классу и дню.
- Автотест `tests/test_daily_load_limits.py (pytest)`:
вызывает generate_timetable(...) напрямую;
вычисляет по каждому дню число уроков и сумму баллов;
убеждается, что лимиты по урокам и баллам не нарушены.
Таким образом, проект иллюстрирует использование автоматических тестов
для проверки школьной модели расписания.
В проекте есть интеграционные тесты, которые проверяют ключевые ограничения:
- `tests/test_teacher_constraints.py`
Проверяет, что:
- учитель не стоит в двух классах в один и тот же день/урок;
- недельная и дневная нагрузка не превышает лимиты `max_lessons_per_week` и `max_lessons_per_day`.
- `tests/test_class_sanpin_constraints.py`
Проверяет, что для каждого класса суточная сумма `difficulty_points`
не превышает `max_points_per_day_by_grade` из `sanpin_limits.yaml`.
- `tests/test_api_smoke.py`
Smoke-тесты для HTTP-сервиса:
- `GET /health` возвращает `{"status": "ok"}`;
- `GET /generate-timetable` возвращает список строк расписания с ожидаемыми полями.
Запуск тестов:
```bash
pytest -vv
## 10. **Отчёты и вспомогательные скрипты**
В проекте есть несколько небольших скриптов для анализа уже сгенерированного расписания.
10.1. Экспорт расписания
```bash
python scripts/export_timetable.py
Скрипт:
вызывает generate_timetable(...) из src/solvers/ortools_solver.py;
сохраняет результат в два файла:
timetable.csv — машинно-читаемый CSV,
timetable.md — человекочитаемое расписание по классам и дням в Markdown.
10.2. Отчёт по нагрузке учителей
python scripts/teacher_load_report.py
Скрипт:
читает timetable.csv и data/teachers.yaml;
считает для каждого учителя:
общее число уроков в неделю,
распределение уроков по дням недели;
сравнивает фактическую нагрузку с лимитами
max_lessons_per_day и max_lessons_per_week;
выводит краткий текстовый отчёт и помечает перегрузки ⚠.
10.3. Отчёт по нагрузке классов (СанПиН)
python scripts/class_load_report.py
Скрипт:
читает:
timetable.csv — готовое расписание,
data/subjects.yaml — трудность предметов (difficulty_points),
data/classes.yaml — ступень (grade) для каждого класса,
data/sanpin_limits.yaml — лимиты баллов в день по ступеням;
для каждого класса и дня считает сумму баллов;
сравнивает её с max_points_per_day_by_grade;
показывает, где расписание укладывается в норму, а где есть превышения ⚠.
## 11. **Возможные направления развития**
Подключение CP-SAT/ILP (OR-Tools)
Перевести жадную эвристику в строгую модель целочисленного программирования
для поиска более качественных расписаний.
Учет кабинетов и учителей
Добавить ограничения:
не более одного класса в кабинете в слот;
не более одного урока на учителя в слот;
учёт вместимости кабинетов.
Расширение данных
вторая смена;
внеурочная деятельность;
сокращённые дни и каникулы.
Веб-интерфейс
Простой фронтенд для:
редактирования входных данных;
запуска генерации;
просмотра и экспорта расписания.
## 12. **Статусы файлов в проекте**
В проекте есть три основных типа файлов:
Исходный код
Всё, что живёт в src/, tools/, notebooks/, tests/:
коммитится в репозиторий;
меняется осознанно через pull request / коммиты;
это «мозг» системы.
## 13. **Данные и конфигурация**
Папки и файлы:
data/subjects.yaml
data/classes.yaml
data/rooms.yaml
data/teachers.yaml
data/sanpin_limits.yaml
Эти файлы тоже хранятся в git, так как описывают:
учебный план,
шкалу трудности предметов,
лимиты по СанПиН,
структуру школы.
Их можно править вручную (например, под другую школу), после чего
проект пересобирает расписание.
## 14. **Генерируемые артефакты**
timetable.csv
**Технический файл** создаётся командой:
python -m src.cli generate --data-dir data/ --output timetable.csv
Используется:
для работы скриптов проверки;
для обмена с другими программами (Excel, Google Sheets и т.п.).
Не хранится в репозитории (игнорируется через .gitignore),
так как всегда может быть заново сгенерирован из исходных данных.
timetable.md
**Человеко-читаемый отчёт** создаётся командой:
python tools/generate_timetable_md.py
В нём — готовые таблицы расписания по классам, удобные:
для просмотра на GitHub;
для демонстрации на защите проекта;
для распечатки.
Хранится в репозитории, чтобы всегда было видно «текущий результат»
работы алгоритма для заданных входных данных.
**Такое разделение** делает проект понятным:
что является кодом,
что — исходными данными,
а что — выходом алгоритма, который можно пересоздать в любой момент.
## 15. **Схема пайплайна**
Общий конвейер «YAML → решатель → расписание → отчёты» описан в:
docs/Pipeline.md
Там показано, как данные проходят через проект:
от data/*.yaml до timetable.csv, timetable.md и отчётов по классам/учителям.
## 16. **HTTP-API (школьный сервер)**
Минимальный API-сервис на FastAPI реализован в файле:
api/app.py
Эндпоинты:
GET /health — проверка доступности сервиса.
GET /generate-timetable — вызов планировщика и получение полного расписания в формате JSON.
Запуск сервера (локально):
uvicorn api.app:app --reload
## 17. **Заключение**
Проект SANPIN-Schedule демонстрирует, что:
школьное расписание можно описать как формальную задачу с ограничениями;
даже учебный проект может использовать профессиональные подходы:
структурированный код, YAML-конфигурации, dataclass-модели, автотесты;
Python подходит не только для учебных задач, но и для прототипирования
реальных управленческих решений (в данном случае — расписания школы).
Файлы в папке docs/ можно использовать как основу для отчёта,
курсовой или презентации по теме «Автоматизация составления школьного расписания».
## 18. **MIT License**
Copyright (c) 2025 Svetlana Romanova
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.