如何在Chained through time模式中计算休息与停顿?TimeFold技术问询
TimeFold连续规划中休息与额外工作时间的处理方案
核心考量逻辑
在TimeFold的连续规划场景里,休息时间和额外工作时间一般靠约束规则加影子变量配合处理:
- 休息时间:可设为硬约束(必须满足,比如连续工作4小时后必须休息30分钟)或软约束(违反会触发惩罚,比如尽量避免短于规定时长的休息)。同时通过影子变量跟踪员工上一个任务的结束时间,自动计算下一个任务的最早可开始时间,确保休息间隔达标。
- 额外工作时间:大多作为软约束处理,比如超出每日8小时标准时长的部分,每小时扣一定分数,引导求解器尽量少安排加班;也可设置允许少量加班,但超出阈值的部分惩罚权重更高。
代码实现示例
1. 实体类与影子变量定义
在员工实体中定义影子变量,跟踪其最后任务的结束时间,作为休息时间计算的基础:
@PlanningEntity public class Employee { private Long id; private String name; private LocalTime dailyStartTime; private LocalTime dailyEndTime; // 影子变量:跟踪员工最后一个任务的结束时间 @ShadowVariable(sourceVariableName = "endTime", variableListenerClass = EmployeeLastEndTimeUpdater.class) private LocalDateTime lastTaskEndTime; // getter、setter 省略 } @PlanningEntity public class Shift { @PlanningId private Long id; private LocalDateTime startTime; private LocalDateTime endTime; @PlanningVariable(valueRangeProviderRefs = "employeeRange") private Employee employee; // getter、setter 省略 }
2. 影子变量更新监听器
实现监听器维护员工的lastTaskEndTime,确保该值随任务分配动态更新:
public class EmployeeLastEndTimeUpdater extends VariableListenerSupport<Shift> { @Override public void afterEntityAdded(ScoreDirector scoreDirector, Shift shift) { updateEmployeeLastEndTime(scoreDirector, shift); } @Override public void afterVariableChanged(ScoreDirector scoreDirector, Shift shift) { updateEmployeeLastEndTime(scoreDirector, shift); } private void updateEmployeeLastEndTime(ScoreDirector scoreDirector, Shift shift) { Employee employee = shift.getEmployee(); if (employee == null) { return; } // 找出该员工所有已分配班次的最晚结束时间 LocalDateTime newLastEndTime = scoreDirector.getWorkingSolution().getShiftList().stream() .filter(s -> employee.equals(s.getEmployee())) .map(Shift::getEndTime) .max(Comparator.naturalOrder()) .orElse(null); scoreDirector.beforeVariableChanged(employee, "lastTaskEndTime"); employee.setLastTaskEndTime(newLastEndTime); scoreDirector.afterVariableChanged(employee, "lastTaskEndTime"); } }
3. 约束规则定义
通过约束流添加休息时间校验和额外工作时间惩罚规则:
public class ShiftConstraints { // 硬约束:连续工作后必须休息至少30分钟 @Constraint(constraintPackage = "shift", name = "MinimumRestBetweenShifts") public Constraint minimumRestBetweenShifts(ConstraintFactory factory) { return factory.forEach(Shift.class) .join(Shift.class, Joiners.equal(Shift::getEmployee), Joiners.lessThan(Shift::getStartTime, Shift::getEndTime), Joiners.greaterThanOrEqual(Shift::getStartTime, other -> other.getEndTime().plusMinutes(30).minusSeconds(1))) .penalize(HardSoftScore.ONE_HARD, (shift1, shift2) -> 1) .asConstraint(); } // 软约束:超出每日8小时标准时长的部分,每小时惩罚10分 @Constraint(constraintPackage = "shift", name = "OvertimePenalty") public Constraint overtimePenalty(ConstraintFactory factory) { return factory.forEach(Employee.class) .join(Shift.class, Joiners.equal(Shift::getEmployee)) .groupBy(Employee::getId, sum(shift -> Duration.between( shift.getStartTime().toLocalTime(), shift.getEndTime().toLocalTime()).toHours())) .filter((employeeId, totalHours) -> totalHours > 8) .penalize(HardSoftScore.ofSoft(10), (employeeId, totalHours) -> totalHours - 8) .asConstraint(); } }
关于休息时间计算的位置
休息时间的计算分为两个核心环节:
- 影子变量阶段:通过监听器维护员工的最后任务结束时间,这是判断是否满足休息间隔的基础数据来源;
- 约束规则阶段:基于影子变量提供的时间数据,定义约束来校验休息间隔是否合规,或计算休息不足的惩罚。
简单来说,影子变量负责动态跟踪时间状态,约束规则负责利用这些状态执行休息时间的考量逻辑。
内容的提问来源于stack exchange,提问作者BrayanMa
相关产品推荐
相关产品推荐

