排课 · 自动排课与课表生成
把「教务排课」这条活整条接管:教学计划表进去 → 约束求解器排出无冲突课表 → 班级课表 / 教师课表 / 教室占用 / 课时核对 + 冲突体检报告出来。
排课是个约束满足问题,不是写作文。所以这里没有一句"帮你智能生成",全是可验证的算法:每节课落在哪个格、为什么落在这个格、哪条硬约束卡住了,都能倒推出来。
它解决什么问题
- 开学前教务主任对着 Excel 手工挪格子:教师撞课、实验室撞用、连堂排断了,改一处崩一片,两天排不完。
- 排完还要再抄三遍:一份班级课表贴教室、一份教师课表发老师、一份总课表存档,抄错一处又得返工。
- 网上讲排课的多是"让模型帮你想想怎么排":给不出成品课表,也保证不了硬约束;能出课表的系统又要部署、要账号。
快速开始
# 1) 先看效果:内置虚构示例(3 个班 / 7 位教师 / 两个实验室 / 一条教师禁排),跑完出全套文件
python3 pk.py demo
# 2) 生成填表模板(教学计划 / 教室 / 禁排 / 固定课 / 设置 五页)
python3 pk.py init --out 排课模板.xlsx
# 3) 按自己的教学计划排课
python3 pk.py plan --plan 排课模板.xlsx --out 排课输出
# 4) 作息不是 5 天 8 节?命令行覆盖
python3 pk.py plan --plan 计划.xlsx --periods 9 --am 4 --days 6 --teacher-day-max 5
# 5) 已经有课表了,只想查冲突
python3 pk.py check --schedule 现用课表.xlsx
python3 pk.py check --schedule 现用课表.xlsx --plan 教学计划.xlsx # 顺带核对课时/禁排/连堂
# 6) 回归自检
python3 pk.py selftest
输入怎么填
教学计划页(必需):一行 = 一个班的一门课。
| 班级 | 科目 | 教师 | 周课时 | 连堂 | 教室类型 |
|---|---|---|---|---|---|
| 示例一中 高一(1)班 | 语文 | 教师甲 | 5 | ||
| 示例一中 高一(1)班 | 物理 | 教师乙 | 3 | 是 | 实验室 |
- 列名自动认:班级/班、科目/学科/课程、教师/老师/任课教师、周课时/课时/节数、连堂/连排、教室类型/场地类型。
- 「连堂」填「是/1/✓」表示这门课按 2 节连排(3 节 = 1 对连堂 + 1 单节)。
- 「教室类型」留空 = 用本班教室,不参与教室占用;填了就必须在「教室」页有同类型教室(没给清单时会自动占位并提示)。
教室页(可选):教室 / 教室类型 / 容量。
禁排页(可选):对象类型(教师/班级)/ 名称 / 星期 / 节次 —— 教师进修、教研、外聘教师只来两天,都写这里。
固定课页(可选):班级 / 科目 / 教师 / 星期 / 节次 / 教室 —— 升旗、班会、合班课先钉死。固定课占用的是计划里本来就有的课时,不会额外加一节。
设置页(可选):days / periods / am_periods / teacher_day_max,命令行的同名参数优先级更高。
也可以直接给 csv(UTF-8 或 GBK 都认)。
排出来的东西
课表.xlsx
| 页 | 内容 |
|---|---|
| 总课表 | 长表(星期/节次/班级/科目/教师/教室),可直接导入其他系统,也可被 check 读回 |
| 每个班一页 | 班级课表矩阵(节次 × 星期,格子里科目/教师/教室),打印即用 |
| 每位教师一页 | 教师课表矩阵(格子里班级/科目),发给老师 |
| 课时核对 | 班级/科目/教师/计划课时/已排课时/差值/状态 |
| 教师负荷 | 周课时、单日最高、每日节数分布、是否超单日上限 |
| 教室占用 | 功能室逐节占用明细 |
| 冲突校验 | T1–T9 逐条结果,全部通过时给一行汇总 |
排课体检.md:结论、每班课时缺口、冲突逐条、没排进去的课及原因、软目标达成率、九条规则口径。
判定规则(都是算死的)
硬约束(违反即冲突):
| 编号 | 规则 |
|---|---|
| T1 | 教师撞课:同一教师同一节次被排到两个班 |
| T2 | 班级撞课:同一班同一节次排了两门课 |
| T3 | 教室撞用:同一教室同一节次被两个班占用 |
| T5 | 课时与计划不符:某班某科实际节数 ≠ 计划节数(含计划外课程) |
| T6 | 禁排时段被占用 |
提醒(不影响下发,按需调整):
| 编号 | 规则 |
|---|---|
| T7 | 连堂断裂:应连的成对数没凑够 |
| T8 | 教师日课时超限:某教师单日节数超过上限 |
| T9 | 主科全排下午:某班主科全部落在上午之后 |
软目标(代价函数,越低越好):主科靠上午(+26)、体育/音乐/美术不排第一节(+34)、同科不同天(+95)、教师集中到少数几天(+6)、班级上午不留空(每空一节 ×2)。平局用输入的哈希打破,同一份输入永远排出同一张课表。
排不进去怎么办
排不进去是真的无解,不是算错——报告里会写原因(教师可用时段不足 / 禁排与连堂约束过紧),并给出三档处理:放宽该教师禁排时段、给这门课加同科教师、把连堂改单节。脚本不会为了"看起来排完了"去违反硬约束。
不适用
- 走班选课、分层教学(一人一课表)不做:这里的模型是"行政班固定课表"。
- 跨校区/跨场地的路程时间不做:只按节次占位,不计算楼栋间转移。
- 不生成监考表、课后服务排班、值班表。
- 不做"最优解"承诺:这是启发式 + 局部搜索,硬约束保证满足,软目标尽力优化,不是运筹学意义上的全局最优。
隐私与边界
- 全程本地:不联网、不上传、不调用任何 API,教学计划与课表不离开本机。
- 示例数据全部为虚构(示例一中 / 教师甲 / 各科目教师),不含任何真实学校或个人信息。
测试与质量
selftest:39 项断言(星期/节次解析、10 组合法课表零误报、10 组必须命中逐条命中、求解结果零硬冲突、禁排与固定课生效、容量不足必报未排入、文件回读一致)。tests/test_v1.py:35 项端到端(GBK csv、矩阵课表、长表、模板读回、成品文件回读、生成耗时 <3 秒)。- 实测:12 个班 / 420 节课 / 5 天 × 8 节,排课 + 出全套文件 0.25 秒,硬冲突 0。