基础可行性排程,使用 Google OR-Tools CP-SAT。
- 依赖已在
pyproject.toml声明。使用uv运行:
uv run ortools-planner --help
The project includes a modern Vue 3 + TypeScript frontend for parameter tuning and weekly schedule visualization.
- Start the backend:
uv run python -m ortools_planner.webui_app- In another terminal, start the frontend dev server:
cd frontend
npm install
npm run devThe frontend will be available at http://localhost:5173 with hot-reload. API calls are proxied to the backend at http://localhost:8001.
To build the frontend for production:
cd frontend
npm install
npm run buildThe build output will be in frontend/dist/ and will be automatically served by FastAPI when available. Access the app at http://localhost:8001/.
- Parameter Controls: Adjust solver configuration, energy budgets, rewards, and penalties via interactive sliders
- Task Type Defaults: Set default parameters for different task types (work, meal, sleep, etc.)
- Weekly Visualization: View a 7-day timeline with scheduled tasks color-coded by type
- Real-time Progress: WebSocket updates for running plans
- Run History: Browse and inspect previous planning runs
可选:在项目根目录创建 .env 用于后续阶段的环境变量管理;本阶段不必需。
列:id,name,duration_minutes,deadline_minute(optional),fixed_start_minute(optional)
示例(无标题解释以避免“模拟数据”代码):请提供你自己的真实任务文件。
uv run ortools-planner \
--tasks /path/to/tasks.csv \
--horizon-minutes 1440 \
--start-minute 0 \
--verbose
输出将按开始时间排序,包含每个任务的 start 和 end(绝对分钟)。
- 阶段零仅实现硬约束:无重叠、截止时间、固定开始。目标为最小化
makespan。 - 如出现不可行,程序会输出诊断信息,优先检查:
duration_minutes是否大于规划窗口。- 固定开始时间是否越界。
- 截止时间是否早于持续时间可完成。
- 输入 CSV 需要新增列:
reward,energy_cost_per_minute。 - 目标:在满足硬约束前提下,最大化
总奖励 - 能量超标惩罚。 - 能量建模(阶段一最简版):
- 能量使用 =
energy_cost_per_minute * duration_minutes的线性和。 - 能量预算使用
--energy-budget限制总和,超出部分以--overuse-penalty加权惩罚。 - 任务为可选,求解器可在预算受限时倾向选择高奖励/低能耗任务。
- 注意:
Total Energy Used不包含“疲劳惩罚”,疲劳惩罚作为独立项Fatigue Penalty报告并进入目标函数。
- 能量使用 =
运行示例:
uv run ./main.py \
--stage 1 \
--tasks examples/tasks_stage1_sample.csv \
--horizon-minutes 600 \
--energy-budget 400 \
--overuse-penalty 5
对比“笨”贪心编排器:
- 贪心策略不考虑能量预算与奖励优化,仅按固定事件先放置、其余任务按“输入顺序”尽早填缝。
- 运行对比:
# CP-SAT(考虑奖励与能量惩罚)
uv run ./main.py --stage 1 --strategy cp \
--tasks examples/tasks_stage1_sample.csv --horizon-minutes 600 \
--energy-budget 400 --overuse-penalty 5
# Naive 贪心(不考虑预算与奖励)
uv run ./main.py --stage 1 --strategy naive \
--tasks examples/tasks_stage1_sample.csv --horizon-minutes 600 \
--energy-budget 400 --overuse-penalty 5
或使用仓库内示例数据:
uv run ortools-planner \
--tasks examples/tasks_stage0_sample.csv \
--horizon-minutes 480 \
--start-minute 0 \
--verbose
为避免“同一任务连续长时间”导致的非线性精力消耗,阶段一支持任务拆分与番茄钟惩罚(需显式启用):
--chunk-max-minutes <N>:将非固定开始的任务拆分为不超过N分钟的块(chunk)。0表示禁用拆分。--break-reset-minutes <B>:两个相邻块之间的休息时长阈值;休息不足B的部分将产生“疲劳惩罚”。--fatigue-penalty <W>:疲劳惩罚权重。越大越倾向于在块之间留出休息以归零疲劳。- 全局休息惩罚:不仅同一任务的块之间,任意两个相邻工作区间(跨任务)只要休息不足,也会产生惩罚。
--fatigue-reset-strict:严格模式,要求休息gap > break-reset-minutes才视为重置;若只达到阈值(等于),仍有最小惩罚。--fatigue-pw-s1-limit <L>:启用分段线性疲劳(piecewise),第一段上限为L分钟缺口;0表示禁用分段线性。--fatigue-pw-w1 <W1>、--fatigue-pw-w2 <W2>:分段线性两段的权重倍率(第一段、剩余部分)。通常设置W2 > W1以对极短休息更严厉惩罚。--fatigue-as-energy <K>:将疲劳惩罚视为能量消耗增加,按系数K把Fatigue Penalty加入能量核算(用于计算预算超标)。0表示禁用耦合。
建模细节(务实近似):
- 拆分仅作用于“非固定开始”的任务;课程等固定开始的事件不拆分。
- 奖励以“任务”为单位:要获得任务奖励,必须调度其所有块;因此对每个块的存在变量与任务存在变量强绑定。
- 疲劳惩罚通过线性项近似:对每对相邻块
k→k+1,惩罚max(0, break_reset_minutes - gap),其中gap = start_{k+1} - end_{k}。- 这是对“指数耗损”的实用近似:只要休息不足,就增加线性惩罚;权重
W决定惩罚强度。 - 如需更激进的非线性,可后续扩展为分段线性多阈值(piecewise),保持 CP-SAT 可解性。
- 分段线性(已支持):将缺口拆成两段
s1 + s2 = deficit,s1 ≤ L;总惩罚W * (W1*s1 + W2*s2),以对“极短休息”施加更高斜率但仍保持线性可解。 - 能量耦合(可选):启用
--fatigue-as-energy K后,Effective Energy Used = Total Energy Used + K * Fatigue Penalty,预算超标按有效能量计算。
- 这是对“指数耗损”的实用近似:只要休息不足,就增加线性惩罚;权重
示例:
uv run ./main.py --stage 1 --strategy cp \
--tasks examples/tasks_stage1_sample.csv --horizon-minutes 600 \
--energy-budget 400 --overuse-penalty 5 \
--chunk-max-minutes 30 --break-reset-minutes 10 --fatigue-penalty 50
输出中被拆分的任务以 task_id#index 形式展示每个块的调度结果。
同时会显示:Total Reward、Total Energy Used、Fatigue Penalty、Effective Energy Used 与 Budget;其中“疲劳惩罚”单独报告,而在启用能量耦合时也反映到 Effective Energy Used。
- 通过
--config <path>指定一个 JSON 配置文件,作为参数默认值来源;命令行上的参数会覆盖配置文件中的值(命令行优先)。
示例文件:examples/config_stage1_sample.json
{
"stage": 1,
"strategy": "cp",
"tasks": "examples/tasks_stage1_sample.csv",
"horizon_minutes": 600,
"start_minute": 0,
"energy_budget": 300,
"overuse_penalty": 2,
"chunk_max_minutes": 30,
"break_reset_minutes": 150,
"fatigue_penalty": 5,
"fatigue_reset_strict": true,
"fatigue_pw_s1_limit": 10,
"fatigue_pw_w1": 1,
"fatigue_pw_w2": 4,
"fatigue_as_energy": 1
}运行:
uv run ./main.py --config examples/config_stage1_sample.json
你仍可以在命令行上覆盖部分字段,例如提高休息阈值:
uv run ./main.py --config examples/config_stage1_sample.json --break-reset-minutes 180
在阶段一基础上进一步细化能量模型与收益结构:
- 多能量池:
mental、will、physical,分别有独立预算与超标惩罚。 - 非线性近似:
- 启动损耗(Startup Loss):开始每个任务消耗固定
willpower,通过--startup-will-cost控制。 - (更新)心流与上下文切换已移除,统一并入起始开销,不再提供心流奖励或能耗折扣。
- 启动损耗(Startup Loss):开始每个任务消耗固定
- 动态收益:
- 紧急度(Urgency):越靠近截止时间开始,奖励线性增加,
--urgency-weight控制权重。 - 机会窗口(Opportunity Window):在指定时间窗内开始可获得额外奖励(每任务在 CSV 指定)。
- 紧急度(Urgency):越靠近截止时间开始,奖励线性增加,
新增列:
id,name,duration_minutes,reward,task_type,mental_cost_per_minute,will_cost_per_minute,physical_cost_per_minute,deadline_minute(optional),fixed_start_minute(optional),opportunity_start_minute(optional),opportunity_end_minute(optional),opportunity_bonus(optional)
示例:examples/tasks_stage2_sample.csv(包含截止促急、晚间健身窗口奖励、心流任务与固定会议)。
示例文件:examples/config_stage2_sample.json(心流字段已移除)
{
"stage": 2,
"tasks": "examples/tasks_stage2_sample.csv",
"horizon_minutes": 480,
"start_minute": 0,
"verbose": true,
"mental_budget": 300,
"will_budget": 200,
"physical_budget": 150,
"energy_overuse_penalty": 2,
"urgency_weight": 2
}运行:
uv run ./main.py --config examples/config_stage2_sample.json
你可以覆盖部分字段,例如调整预算或启动损耗:
uv run ./main.py --config examples/config_stage2_sample.json --startup-will-cost 10 --mental-budget 260
- 打印按开始时间排序的任务调度。
- 目标值
Objective= 总奖励(含紧急度/机会窗口)减去各能量类型的超标惩罚(心流加成已移除)。 Energy Totals分别显示mental/will/physical的累计使用量,用于对比预算与调参。
将阶段一的拆分/疲劳(番茄钟)与阶段二的多能量池、启动损耗、紧急度、机会窗口整合到同一求解器中(心流与上下文切换已移除,统一为起始开销)。
- 固定开始任务(会议等)不进行拆分,保证时间一致性。
- 对可拆分任务采用“全或无”策略:任务存在时必须调度其所有分块,避免仅用最小分块获取全部奖励的投机行为。
- 疲劳惩罚按线性近似作用于任意相邻工作区间之间的短休缺口,并可选择将其耦合为能量消耗。
示例配置:examples/config_unified_sample.json
{
"stage": 3,
"tasks": "examples/tasks_stage2_sample.csv",
"horizon_minutes": 600,
"start_minute": 0,
"verbose": false,
"mental_budget": 300,
"will_budget": 250,
"physical_budget": 200,
"energy_overuse_penalty": 2,
"urgency_weight": 2,
"chunk_max_minutes": 50,
"break_reset_minutes": 10,
"enforce_min_rest": true,
"fatigue_penalty": 3,
"fatigue_reset_strict": false,
"fatigue_pw_s1_limit": 5,
"fatigue_pw_w1": 1,
"fatigue_pw_w2": 3,
"fatigue_as_energy": 0
}运行:
uv run ./main.py --config examples/config_unified_sample.json
输出包含:统一调度列表、Objective、各能量池累计与 Fatigue 总量,便于调参验证组合效果。