Version: 0.3.6.dev.260223
feat: 🚀 新增智能编排日程接口与算法模块 * 新增智能编排日程接口,实现自动生成周维度课程安排 * 抽离核心算法至 `Logic` 包,统一存放调度与排课相关算法逻辑,优化项目结构分层 * 大多数用例测试通过,当前存在少量边界用例下“排课时间是否充足”的误判问题 * 返回的周视图数据在极端场景下存在数量偏差,待进一步完善边界控制 fix: 🐛 修复批量导入课程接口 500 错误 * 修复批量导入课程接口中未在 `event` 结构体填写时间字段的问题 * 解决因时间字段为空导致的服务端 500 错误,保证数据完整性 refactor: ♻️ 新增入参校验逻辑保障调度稳定性 * 在添加任务类时新增入参校验逻辑 * 避免非法数据进入调度流程,确保自动编排日程接口执行稳定 docs: 📚 更新 README 智能编排算法说明 * 补充智能编排日程算法的设计思路与实现说明 undo: ⚠️ 追加导入课程后缓存未自动失效 * 追加导入课程后未自动删除对应周安排缓存,存在数据不一致风险 * 当前未能稳定复现,计划后续定位缓存失效时序与触发条件问题
This commit is contained in:
57
README.md
57
README.md
@@ -291,7 +291,64 @@ PS:截至v0.3.3。其中黑色箭头为请求数据链路,绿色箭头为返
|
||||
|
||||
### 5.3.1 智能排课算法
|
||||
|
||||
本系统采用 **“原子化时间网格(Atomic TimeGrid)”** 架构,实现了针对大学生复杂课表环境的智能任务填充。算法核心分为 **“沙盘模拟”、“边界感知探测”** 与 **“逻辑位移步进”** 三大模块。
|
||||
|
||||
**1. 原子化时间沙盘 (Grid Sandboxing)**
|
||||
|
||||
算法首先将物理时间窗口(StartDate 到 EndDate)抽象为一个三维矩阵 $Grid[Week][Day][Section]$。
|
||||
|
||||
- **多维状态标记**:每个格子(Slot)是携带 `Status` 和 `EventID` 的 `slotNode` 节点。
|
||||
- **优先级注水(Hydration)**:
|
||||
1. **Blocked(屏蔽区)**:根据用户配置的 `ExcludedSlots` 强制锁定,优先级最高。
|
||||
2. **Filler(嵌入区)**:识别“水课”,标记为可利用资源。
|
||||
3. **Occupied(占用区)**:映射既有硬核课程,确保调度不产生物理冲突。
|
||||
|
||||
**2. 边界感知探测 (Boundary-Sensing Detection)**
|
||||
|
||||
为了解决“任务块跨课分身”的 Bug,算法引入了 **EventID 校验机制**。
|
||||
|
||||
- **容器自适应长度**:当算法探测到一个 `Filler` 槽位时,会向后贪心扫描,只有当相邻槽位的 `EventID` 相同且同为 `Filler` 时,才允许任务块拉伸。
|
||||
- **逻辑闭环**:这保证了任务块(TaskItem)要么完美嵌入单门水课,要么占据空地,绝不会出现一个任务横跨两门不同课程的情况。
|
||||
|
||||
**3. 稳扎稳打:逻辑位移步进 (Logical-Offset Skipping)**
|
||||
|
||||
在 `Steady`(稳扎稳打)模式下,为了实现负载均衡,算法弃用了传统的“物理时间跳跃”,改用 **“逻辑坑位跳跃”**。
|
||||
|
||||
$$Gap = \frac{TotalAvailableSlots - (TaskCount \times 2)}{TaskCount + 1}$$
|
||||
|
||||
- **物理跳跃(旧版/错误)**:直接 $Time + Gap$,容易因遇到屏蔽时段或硬核课而导致游标溢出,从而“吞掉”后续任务。
|
||||
- **逻辑跳跃(现行/优化)**:调用 `skipAvailableSlots` 函数,在 Grid 中沿时间轴向后数出 $Gap$ 个**真正可用**的格子作为下一个起点。
|
||||
- **价值**:确保了在有限的 **2C4G** 服务器资源下,任务能像“等距列队”一样均匀分布在学期空隙中。
|
||||
|
||||
------
|
||||
|
||||
**🛠️ 算法运行流程**
|
||||
|
||||
1. **Build**:调用 `buildTimeGrid`,将数据库的离散 `Schedules` 映射为内存状态网格。
|
||||
2. **Count**:统计当前窗口内所有 `Free` 与 `Filler` 的原子位总数。
|
||||
3. **Allocate**:
|
||||
- 通过 `FindNextAvailable` 锁定首个合法坑位。
|
||||
- 进行 **容器探测** 决定任务块长度。
|
||||
- 执行 `skipAvailableSlots` 寻找下一个负载均衡点。
|
||||
4. **Preview**:输出 DTO 到前端,标记 `status: "suggested"` 供用户预览高亮。
|
||||
|
||||
------
|
||||
|
||||
**⚡ 性能表现 (Optimization)**
|
||||
|
||||
- **时间复杂度**:$O(W \times D \times S)$,其中 $W$ 为任务类跨度周数。在处理典型的 16 周排程时,计算量仅在数千次操作级别,单机响应达毫秒级。
|
||||
- **空间复杂度**:由于采用了按需创建周 Map 的策略,内存占用随任务跨度动态伸缩,极大地减轻了重庆邮电大学校园服务器环境下的 GC 压力。
|
||||
|
||||
------
|
||||
|
||||
**数据回填**
|
||||
|
||||
在执行完上述算法后,将任务块分成两类数据:
|
||||
|
||||
1. 需要新建`ScheduleEvent`的,插入纯空闲时段的数据;
|
||||
2. 直接嵌入现有课程中的任务块;
|
||||
|
||||
然后分别调用不同的业务逻辑,开启大事务,批量插入,使得只需要连接2次数据库,并且若插入出错,支持批量回滚,不会存在任何脏数据。
|
||||
|
||||
## 5.4 Agent范式实现细节
|
||||
|
||||
|
||||
Reference in New Issue
Block a user