阅读计划与页面节奏规划器
来自 Wikiprompt,自由的提示词百科全书
阅读计划与页面节奏规划器 一个全面的AI技能,用于规划每日阅读计划,以满足读书俱乐部、课程或考试截止日期,利用页数/字数、阅读速度和可用时间。它包含完整的系统提示、工作流程、参考指南、模板、示例以及一个Python规划脚本。
提示词内容收藏
🌐
---
name: reading-plan-and-page-pace-planner
description: 规划你的每日阅读,让你在读书会、课程或考试日期前读完每本书,,根据页数或字数(或有声书时长)、你的阅读速度、每个工作日可用的分钟数、出行和忙碌的日子。均匀分配页数,在每个截止日期前保留缓冲日,并安排每周补读日,在章节末尾停止,当截止日期无法达成时发出警告,并从你当前的位置重新规划。包含一个标准库 Python 规划器。
---
# 阅读计划与页面节奏规划器
你帮助读者避免在读书会前一晚突击阅读 200 页。人们大致知道自己有多少时间(工作日半小时,周末更多),但不知道这对一本两周后到期、245 页的小说、一本密集的工作书籍和同一月份内一本 880 页的经典著作意味着多少页。你将书籍、阅读速度和空闲分钟转化为具体的每日计划(“周二:第 1-27 页”),为现实生活保留余地,并尽早、清楚地说明何时无法满足截止日期以及如何解决。
## 此技能中的文件
- `scripts/reading_plan.py` - 读取一个计划 JSON(包含书籍的页数、字数或有声书分钟数、起始页、难度、截止日期、章节起始;阅读速度以每分钟字数或每小时页数计;每个工作日的分钟数;出行和例外情况;补读工作日;缓冲天数);按最早截止日期优先安排,均匀分配负载(或尽可能早),将每日停止点对齐到章节末尾,并打印书籍摘要、发现项 RP01-RP06、包含页面范围的逐日表格、每周检查点和补读建议;`--from` 加 `--at "Title=page"` 可中途重新规划(仅使用 Python 3.8+ 标准库)
- `references/reading-speed-and-estimates.md` - 测量你的速度、不同书籍类型的每页字数、难度因素、有声书
- `references/pacing-and-catch-up.md` - 缓冲、补读日、落后或截止日期不可能达成时该怎么做
- `templates/reading-plan.md` - 要交回的计划(适合日历使用)
- `examples/book-club.json` - 十一月到十二月:一本周四读书会的小说、一本已从第 23 页开始的工作书籍、一本经典俱乐部用的《米德尔马契》、一本无截止日期的有声书、周日补读日、一次周末旅行和两个特殊日子
- `examples/book-club-plan.md` - 该文件上的实际规划器输出
## 工作流程
### 1. 收集输入
询问:每本书的页数(或字数;版本很重要)、当前读到哪、截止日期及其性质(会议、考试)、是轻松还是密集;你的阅读速度(或使用 `references/reading-speed-and-estimates.md` 进行 10 分钟测试计时);每个工作日现实的分钟数,而非理想的;已知的旅行、晚班和空闲日;哪个工作日应保留为补读日;以及每次会议前你希望提前多少天完成(缓冲,默认 1-2 天)。
### 2. 运行规划器
```
python3 scripts/reading_plan.py examples/book-club.json
python3 scripts/reading_plan.py examples/book-club.json --mode asap
python3 scripts/reading_plan.py examples/book-club.json --from 2026-11-20 --at "Thinking in Systems=110"
python3 scripts/reading_plan.py plan.json --json
python3 scripts/reading_plan.py --self-test
```
退出码 0 = 所有截止日期均达成,1 = 至少一本书无法按时完成,2 = 输入错误。没有 shell 时:所需分钟数 = 页数 x 每页字数 / 每分钟字数 x 难度;与每个完成日期(截止日期减去缓冲,排除补读日)之前可用的分钟数比较,最早截止日期优先。
### 3. 阅读结果
- RP01(高):一本书无法在其截止日期前完成。消息给出页数缺口以及每个阅读日需要增加的分钟数以弥补。当计划不可行时,规划器会在此之前以全速阅读,因此较早的书籍会提前完成以腾出时间。
- RP02 / RP04(中):计划只有消耗缓冲日才能实现,或在最繁忙的时段使用了超过 90% 的时间,,一周不顺就会打破它。
- RP03(低):单日超过最大会话时长,,将其拆分。RP05/RP06(信息):已完成的书籍、临近的截止日期、有声书。
- 检查日表格中较重的周末日,以及每周检查点中“到周日我应该读到哪”。
### 4. 修复不可能的计划
按此顺序提供选项:现在并行开始长书(将其截止日期逻辑提前),在某些日子增加 10-20 分钟,使用补读日,将部分内容转为通勤时的有声书,略读或跳过明确标记的部分,或接受在会议进行到一半时到达并明确说明。每次更改后重新运行。参见 `references/pacing-and-catch-up.md`。
### 5. 交付
填写 `templates/reading-plan.md`:每本书的摘要、逐周计划(日期和页面范围,可直接用于日历或书签备注)、补读规则和重新规划命令。每当他们报告当前页数时,提供快速重新规划。
## 示例结果
在 `examples/book-club.json` 上(240 字/分钟,每天 20-60 分钟,周日空闲用于补读,2 个缓冲日),《长日将尽》于 11 月 12 日完成,为 11 月 19 日的读书会做好准备,在章节末尾停止;《系统之美》(密集,系数 1.3,从第 23 页开始)于 11 月 27 日完成,为 12 月 1 日的小组做好准备。《米德尔马契》(316,000 字,约 22 小时)无法在 12 月 17 日的经典俱乐部前完成:约差 452 页,预计完成日期为 1 月 11 日(RP01,高)。修复方法:在 42 个阅读日中的每一天增加约 17 分钟,或现在与另外两本书并行开始。120 分钟的安静周六被标记为两个会话(RP03),而《皮拉内西》有声书(6 小时 40 分钟,1.25 倍速)在《米德尔马契》之后进行。
## 规则
- 根据人们实际拥有的时间进行规划,并在每次会议前至少保留一个缓冲日。
- 切勿悄悄填满日子:如果安排不下,说明哪个截止日期会失败、差多少页,以及如何修复。
- 使用该版本的页数;当只知道字数时,说明页数是估算值。
- 在计划中留空补读日,,它们用于落后时补读,而非增加阅读量。
- 阅读应保持愉悦:在建议每天两小时会话之前,先建议跳过或切换格式。
- 每当读者落后或超前时,从当前页数重新规划;不要让他们追赶旧计划。
FILE:references/reading-speed-and-estimates.md
# 阅读速度与估算
## 测量你的速度(10 分钟)
1. 选择实际书籍(或类似书籍),从章节开头开始,设置 10 分钟计时器,正常阅读。
2. 数三个完整行的字数,除以 3 = 每行字数;数一个完整页的行数 = 每页行数。
3. 每页字数 = 每行字数 x 每页行数;每分钟字数 = 阅读页数 x 每页字数 / 10。
或者简单地对几页计时,并使用 `"pages_per_hour"` 代替每分钟字数。
## 典型值
| 读者 / 材料 | 每分钟字数 |
|---|---|
| 仔细阅读、密集非虚构、第二语言 | 150-200 |
| 普通成年人、小说 | 200-260 |
| 快速休闲读者 | 300-400 |
略读不算阅读:不要用超过 450 的速度规划。
| 书籍类型 | 每页字数 |
|---|---|
| 大众市场平装本 | 300-350 |
| 贸易平装小说 | 250-300 |
| 带注释的精装非虚构 | 350-450 |
| 大字版、儿童章节书 | 150-200 |
| 电子书 | 使用字数(商店页面或显示字数的工具)和默认每页 275 字 |
## 难度系数
`"difficulty"` 乘以阅读时间:1.0 轻松小说,1.2-1.4 密集非虚构、长句经典、带图表的科技书籍,1.5+ 需要做笔记的教科书、诗歌、第二语言阅读。
## 页数与字数
- 使用读者手中版本的页数;同一本书的不同版本可能相差 30%。
- 同时有 `"pages"` 和 `"words"` 时,规划器推导出该版本的每页字数(《米德尔马契》316,000 / 880 = 359)。
- 只有字数时,页数是估算值(字数 / 每页字数);在计划中说明这一点。
## 有声书
- `"audio_minutes"` 是时长,`"speed"` 是播放速度:所需时间 = 时长 / 速度(400 分钟在 1.25 倍速下 = 320 分钟)。
- 音频适合通勤、家务和散步:说明这些分钟是额外的还是已计入每日分钟数。
- 位置以音频的 h:mm 显示。
FILE:references/pacing-and-catch-up.md
# 节奏与补读
## 规划器如何安排
- 最早截止日期优先:完成日期(截止日期减去缓冲天数)最近的书籍先读;无截止日期的书籍最后读。
- 均匀模式(默认):对于到最紧截止日期的每个时间段,它使用每日分钟数的最小等分,仍能按时完成,因此当有空间时,30 分钟的日子可能变成 20 分钟。该截止日期后,计算下一个时间段。
- ASAP 模式:每天使用所有分钟;书籍尽可能早完成。
- 如果即使全速也无法满足截止日期,则之前每天按全速规划,书籍标记为 RP01。
- 当章节末尾在 `chapter_snap_pages`(默认 8)内且为当天页数的四分之一时,每日停止点移至章节末尾。
## 缓冲和补读日
- 缓冲日(`buffer_days`,默认 1)位于计划完成和会议之间:用于重读笔记或吸收糟糕一周的时间。
- 补读工作日(`catch_up_days`,例如周日)不安排阅读。仅在落后时使用;如果按计划进行,则为自由时间。
- 例外情况(旅行、晚班、安静的周六)覆盖这些日期的平日分钟数。
## 当你落后时
1. 错过一天:在下一个补读日阅读那天的页数,或将其中三分之一添加到接下来三天中的每一天。
2. 落后超过两天:使用 `--from <today> --at "<Title>=<current page>"` 从今天重新规划,而不是追赶旧计划。
3. 仍然无法按时(RP01):选择一项,,现在并行开始长书,在几天内增加 10-20 分钟,使用有声书用于通勤,略读标记的部分(前言、附录、注释),或决定进行到一半并告知小组。
## 计划过紧的迹象
- RP04:在一个时间段内使用了超过 90% 的时间;任何生病或糟糕的一周都会打破它。
- RP02:书籍只有使用缓冲日才能完成。
- 许多日子标记为“2 个会话”(RP03):考虑有意将阅读分散在早晚。
## 读书会提示
- 如果小组讨论的不是整本书,记下将讨论的页数或章节,并将其用作书籍的 `pages`。
- 对于长篇经典,询问俱乐部会议是否涵盖前半部分;规划两个截止日期。
- 在书中放一张便利贴,写上计划中的每周检查点。
FILE:templates/reading-plan.md
# 阅读计划:<name>(<start> 到 <last deadline>)
**节奏:** <wpm> 字/分钟,30 分钟会话约 <pages> 页
**时间:** <每个工作日的分钟数>;补读日:<day>;缓冲:每次会议前 <n> 天
**结论:** <所有截止日期均达成 | BOOK 无法在 DATE 前完成 - 见选项>
## 书籍
| 书籍 | 剩余页数 | 所需时间 | 会议 / 截止日期 | 完成日期 | 计划完成 |
|---|---|---|---|---|---|
| <title> | <pp> | <h:mm> | <date - event> | <date> | <date> |
## 逐周
### <Mon date> 那一周
- 周一:<Book p. a-b>
- 周二:<...>
- 周日:补读 / 空闲
**周日晚检查点:** <Book p. X>
## 如果截止日期无法达成
- 缺口:<date> 前 <book> 的 <pages> 页
- 选项:<阅读日 +N 分钟 | 现在并行开始 | 通勤有声书 | 略读章节 x-y | 进行到一半到达>
- 已选择:<...>
## 落后时
- 错过一天?使用 <catch-up day> 或在接下来三天每天增加 <n> 页。
- 落后超过两天:从今天重新规划(`--from <date> --at "<title>=<page>"`)。
FILE:examples/book-club.json
{
"name": "阅读计划:2026 年 11 月至 12 月(两个读书会和工作书籍)",
"start": "2026-11-03",
"reader": {"words_per_minute": 240, "words_per_page": 280},
"minutes": {"mon": 30, "tue": 30, "wed": 20, "thu": 30, "fri": 25, "sat": 60, "sun": 45},
"catch_up_days": ["sun"],
"buffer_days": 2,
"max_session_minutes": 60,
"chapter_snap_pages": 8,
"exceptions": [
{"dates": "2026-11-13..2026-11-15", "minutes": 0, "note": "周末旅行"},
{"dates": "2026-11-26", "minutes": 15, "note": "晚班"},
{"dates": "2026-12-05", "minutes": 120, "note": "安静的周六"}
],
"books": [
{"title": "The Remains of the Day", "pages": 245, "words_per_page": 265, "deadline": "2026-11-19", "event": "周四读书会",
"chapters": [1, 9, 45, 101, 125, 165, 212, 239]},
{"title": "Thinking in Systems", "pages": 218, "start_page": 23, "difficulty": 1.3, "deadline": "2026-12-01", "event": "团队阅读小组"},
{"title": "Middlemarch", "words": 316000, "pages": 880, "deadline": "2026-12-17", "event": "经典俱乐部"},
{"title": "Piranesi (有声书)", "audio_minutes": 400, "speed": 1.25}
]
}
FILE:examples/book-club-plan.md
# 阅读计划:2026 年 11 月至 12 月(两个读书会和工作书籍)
开始于 2026-11-03,均匀模式,每个截止日期前 2 个缓冲日,补读日:周日。计划阅读:81 天内 36 小时 44 分钟。
**结论:1 本书无法在其截止日期前完成 - 更改计划**
## 书籍
| 书籍 | 剩余 | 预计时间 | 截止日期 | 完成日期 | 计划完成 | 剩余天数 | 每个阅读日平均 |
|---|---|---|---|---|---|---|---|
| The Remains of the Day | 245 页 | 4:31 小时 | 2026-11-19(周四读书会) | 2026-11-17 | 2026-11-12 | 7 | 27 页 |
| Thinking in Systems | 196 页 | 4:57 小时 | 2026-12-01(团队阅读小组) | 2026-11-29 | 2026-11-27 | 4 | 16 页 |
| Middlemarch | 880 页 | 21:57 小时 | 2026-12-17(经典俱乐部) | 2026-12-15 | 2027-01-11 | -25 | 23 页 |
| Piranesi (有声书) | 6:40 音频 | 5:20 小时 | - | - | 2027-01-22 | - | 0:36 |
## 发现项
- **高 RP01** Middlemarch 无法在 2026-12-17(经典俱乐部)前完成:按你的速度约差 452 页;预计完成日期 2027-01-11。选项:在那之前的 42 个阅读日每天 +17 分钟,读得更快,略读或跳过约 452 页,或更早开始
- **低 RP03** 1 天计划超过 60 分钟(例如 2026-12-05:120 分钟)- 拆分为两个会话
- **信息 RP06** Piranesi(有声书)是有声书:计划按 1.25 倍速计算收听时间;通勤和家务可以覆盖它
## 逐日
| 日期 | 星期 | 分钟 | 阅读 | 备注 |
|---|---|---|---|---|
| 2026-11-03 | 周二 | 30 | The Remains of the Day 第 1-27 页(27 页) | |
| 2026-11-04 | 周三 | 19 | The Remains of the Day 第 28-44 页(17 页) | |
| 2026-11-05 | 周四 | 30 | The Remains of the Day 第 45-71 页(27 页) | |
| 2026-11-06 | 周五 | 25 | The Remains of the Day 第 72-94 页(23 页) | |
| 2026-11-07 | 周六 | 60 | The Remains of the Day 第 95-148 页(54 页) | |
| 2026-11-08 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-11-09 | 周一 | 30 | The Remains of the Day 第 149-175 页(27 页) | |
| 2026-11-10 | 周二 | 30 | The Remains of the Day 第 176-202 页(27 页) | |
| 2026-11-11 | 周三 | 20 | The Remains of the Day 第 203-221 页(19 页) | |
| 2026-11-12 | 周四 | 30 | The Remains of the Day 第 222-245 页(24 页);Thinking in Systems 第 23-24 页(2 页) | |
| 2026-11-13 | 周五 | - | - | 周末旅行 |
| 2026-11-14 | 周六 | - | - | 周末旅行 |
| 2026-11-15 | 周日 | - | - | 周末旅行 |
| 2026-11-16 | 周一 | 30 | Thinking in Systems 第 25-44 页(20 页) | |
| 2026-11-17 | 周二 | 30 | Thinking in Systems 第 45-64 页(20 页) | The Remains of the Day 完成日期 |
| 2026-11-18 | 周三 | 20 | Thinking in Systems 第 65-77 页(13 页) | |
| 2026-11-19 | 周四 | 30 | Thinking in Systems 第 78-97 页(20 页) | The Remains of the Day 截止日期 - 周四读书会 |
| 2026-11-20 | 周五 | 25 | Thinking in Systems 第 98-113 页(16 页) | |
| 2026-11-21 | 周六 | 60 | Thinking in Systems 第 114-153 页(40 页) | |
| 2026-11-22 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-11-23 | 周一 | 30 | Thinking in Systems 第 154-172 页(19 页) | |
| 2026-11-24 | 周二 | 30 | Thinking in Systems 第 173-192 页(20 页) | |
| 2026-11-25 | 周三 | 20 | Thinking in Systems 第 193-205 页(13 页) | |
| 2026-11-26 | 周四 | 15 | Thinking in Systems 第 206-215 页(10 页) | 晚班 |
| 2026-11-27 | 周五 | 25 | Thinking in Systems 第 216-218 页(3 页);Middlemarch 第 1-14 页(14 页) | |
| 2026-11-28 | 周六 | 60 | Middlemarch 第 15-54 页(40 页) | |
| 2026-11-29 | 周日 | - | - | 补读 / 空闲(45 分钟可用);Thinking in Systems 完成日期 |
| 2026-11-30 | 周一 | 30 | Middlemarch 第 55-74 页(20 页) | |
| 2026-12-01 | 周二 | 30 | Middlemarch 第 75-94 页(20 页) | Thinking in Systems 截止日期 - 团队阅读小组 |
| 2026-12-02 | 周三 | 20 | Middlemarch 第 95-107 页(13 页) | |
| 2026-12-03 | 周四 | 30 | Middlemarch 第 108-128 页(21 页) | |
| 2026-12-04 | 周五 | 25 | Middlemarch 第 129-144 页(16 页) | |
| 2026-12-05 | 周六 | 120 | Middlemarch 第 145-224 页(80 页) | 安静的周六;2 个会话 |
| 2026-12-06 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-12-07 | 周一 | 30 | Middlemarch 第 225-244 页(20 页) | |
| 2026-12-08 | 周二 | 30 | Middlemarch 第 245-265 页(21 页) | |
| 2026-12-09 | 周三 | 20 | Middlemarch 第 266-278 页(13 页) | |
| 2026-12-10 | 周四 | 30 | Middlemarch 第 279-298 页(20 页) | |
| 2026-12-11 | 周五 | 25 | Middlemarch 第 299-315 页(17 页) | |
| 2026-12-12 | 周六 | 60 | Middlemarch 第 316-355 页(40 页) | |
| 2026-12-13 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-12-14 | 周一 | 30 | Middlemarch 第 356-375 页(20 页) | |
| 2026-12-15 | 周二 | 30 | Middlemarch 第 376-395 页(20 页) | Middlemarch 完成日期 |
| 2026-12-16 | 周三 | 20 | Middlemarch 第 396-408 页(13 页) | |
| 2026-12-17 | 周四 | 30 | Middlemarch 第 409-428 页(20 页) | Middlemarch 截止日期 - 经典俱乐部 |
| 2026-12-18 | 周五 | 25 | Middlemarch 第 429-445 页(17 页) | |
| 2026-12-19 | 周六 | 60 | Middlemarch 第 446-485 页(40 页) | |
| 2026-12-20 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-12-21 | 周一 | 30 | Middlemarch 第 486-505 页(20 页) | |
| 2026-12-22 | 周二 | 30 | Middlemarch 第 506-525 页(20 页) | |
| 2026-12-23 | 周三 | 20 | Middlemarch 第 526-539 页(14 页) | |
| 2026-12-24 | 周四 | 30 | Middlemarch 第 540-559 页(20 页) | |
| 2026-12-25 | 周五 | 25 | Middlemarch 第 560-575 页(16 页) | |
| 2026-12-26 | 周六 | 60 | Middlemarch 第 576-615 页(40 页) | |
| 2026-12-27 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2026-12-28 | 周一 | 30 | Middlemarch 第 616-635 页(20 页) | |
| 2026-12-29 | 周二 | 30 | Middlemarch 第 636-656 页(21 页) | |
| 2026-12-30 | 周三 | 20 | Middlemarch 第 657-669 页(13 页) | |
| 2026-12-31 | 周四 | 30 | Middlemarch 第 670-689 页(20 页) | |
| 2027-01-01 | 周五 | 25 | Middlemarch 第 690-706 页(17 页) | |
| 2027-01-02 | 周六 | 60 | Middlemarch 第 707-746 页(40 页) | |
| 2027-01-03 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2027-01-04 | 周一 | 30 | Middlemarch 第 747-766 页(20 页) | |
| 2027-01-05 | 周二 | 30 | Middlemarch 第 767-786 页(20 页) | |
| 2027-01-06 | 周三 | 20 | Middlemarch 第 787-799 页(13 页) | |
| 2027-01-07 | 周四 | 30 | Middlemarch 第 800-819 页(20 页) | |
| 2027-01-08 | 周五 | 25 | Middlemarch 第 820-836 页(17 页) | |
| 2027-01-09 | 周六 | 60 | Middlemarch 第 837-876 页(40 页) | |
| 2027-01-10 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2027-01-11 | 周一 | 30 | Middlemarch 第 877-880 页(4 页);Piranesi(有声书)0:00-0:30 | |
| 2027-01-12 | 周二 | 30 | Piranesi(有声书)0:30-1:08 | |
| 2027-01-13 | 周三 | 20 | Piranesi(有声书)1:08-1:33 | |
| 2027-01-14 | 周四 | 30 | Piranesi(有声书)1:33-2:10 | |
| 2027-01-15 | 周五 | 25 | Piranesi(有声书)2:10-2:41 | |
| 2027-01-16 | 周六 | 60 | Piranesi(有声书)2:41-3:56 | |
| 2027-01-17 | 周日 | - | - | 补读 / 空闲(45 分钟可用) |
| 2027-01-18 | 周一 | 30 | Piranesi(有声书)3:56-4:34 | |
| 2027-01-19 | 周二 | 30 | Piranesi(有声书)4:34-5:11 | |
| 2027-01-20 | 周三 | 20 | Piranesi(有声书)5:11-5:36 | |
| 2027-01-21 | 周四 | 30 | Piranesi(有声书)5:36-6:14 | |
| 2027-01-22 | 周五 | 21 | Piranesi(有声书)6:14-6:40 | |
## 每周检查点(周日晚你应该读到哪)
- 2026-11-08:The Remains of the Day 第 148 页
- 2026-11-15:Thinking in Systems 第 24 页
- 2026-11-22:Thinking in Systems 第 153 页
- 2026-11-29:Middlemarch 第 54 页
- 2026-12-06:Middlemarch 第 224 页
- 2026-12-13:Middlemarch 第 355 页
- 2026-12-20:Middlemarch 第 485 页
- 2026-12-27:Middlemarch 第 615 页
- 2027-01-03:Middlemarch 第 746 页
- 2027-01-10:Middlemarch 第 876 页
- 2027-01-17:Piranesi(有声书)3:56
## 如果你落后了
- The Remains of the Day:错过一天约损失 27 页。在空闲日 2026-11-08 补上,或在接下来 3 个阅读日每天增加 10 页。随时使用 `--from <date> --at "The Remains of the Day=<page>"` 重新规划。
- Thinking in Systems:错过一天约损失 16 页。在空闲日 2026-11-22、2026-11-29 补上,或在接下来 3 个阅读日每天增加 6 页。随时使用 `--from <date> --at "Thinking in Systems=<page>"` 重新规划。
- Middlemarch:错过一天约损失 23 页。在空闲日 2026-11-29、2026-12-06、2026-12-13 补上,或在接下来 3 个阅读日每天增加 8 页。随时使用 `--from <date> --at "Middlemarch=<page>"` 重新规划。
FILE:scripts/reading_plan.py
#!/usr/bin/env python3
"""阅读计划与页面节奏规划器(仅标准库,Python 3.8+)。
将书籍列表(页数、字数或有声书时长)、你的阅读速度和每天可用的分钟数转化为逐日计划,包含页面范围,满足每个截止日期(读书会、课程、考试),在每个截止日期前保留缓冲日,并在周中保留补读日,均匀分配负载,并告诉你落后时该怎么做。
用法:
reading_plan.py PLAN.json [--mode even|asap] [--from YYYY-MM-DD] [--at "Title=page"]... [--json]
reading_plan.py --self-test
退出:0 = 所有截止日期均达成,1 = 至少一本书无法在其截止日期前完成,
2 = 输入错误。
"""
import argparse, datetime, json, math, re, sys
DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
SEV = {"high": 0, "medium": 1, "low": 2, "info": 3}
class InputError(Exception):
pass
def pdate(s, what):
try:
return datetime.date.fromisoformat(str(s))
except ValueError:
raise InputError("%s 必须是 YYYY-MM-DD,得到 %r" % (what, s))
def expand_dates(spec):
spec = str(spec)
if ".." in spec:
a, b = spec.split("..", 1)
a, b = pdate(a.strip(), "日期范围"), pdate(b.strip(), "日期范围")
if b < a:
raise InputError("日期范围 %s 在开始前结束" % spec)
return [a + datetime.timedelta(days=i) for i in range((b - a).days + 1)]
return [pdate(spec.strip(), "日期")]
# ---------------------------------------------------------------- model
def load(cfg, start_override=None, at=None):
if not isinstance(cfg, dict) or not isinstance(cfg.get("books"), list) or not cfg["books"]:
raise InputError("计划需要一个非空的 'books' 列表")
reader = cfg.get("reader") or {}
wpm = float(reader.get("words_per_minute", 230))
wpp_default = float(reader.get("words_per_page", 275))
pph = reader.get("pages_per_hour")
if wpm <= 0 or wpp_default <= 0:
raise InputError("words_per_minute 和 words_per_page 必须为正数")
start = pdate(start_override or cfg.get("start") or datetime.date.today().isoformat(), "start")
mins = cfg.get("minutes")
if isinstance(mins, (int, float)):
mins = {d: mins for d in DAYS}
if not isinstance(mins, dict):
raise InputError("'minutes' 必须是数字或 {mon: .., tue: ..}")
mins = {k.lower()[:3]: float(v) for k, v in mins.items()}
bad = set(mins) - set(DAYS)
if bad:
raise InputError("minutes 中有未知的工作日键:%s" % ", ".join(sorted(bad)))
catch = {d.lower()[:3] for d in cfg.get("catch_up_days", [])}
exc = {}
for e in cfg.get("exceptions", []):
for d in expand_dates(e.get("dates") or e.get("date")):
exc[d] = (float(e.get("minutes", 0)), e.get("note", ""))
books = []
at = at or {}
for i, b in enumerate(cfg["books"], 1):
t = b.get("title") or "书籍 %d" % i
if "audio_minutes" in b:
speed = float(b.get("speed", 1.0))
total = float(b["audio_minutes"])
unit_min = 1.0 / speed # 每分钟音频对应的你的时间分钟数
units, kind = total, "audio"
done = float(at.get(t, b.get("start_minute", 0)))
else:
wpp = float(b.get("words_per_page") or (float(b["words"]) / float(b["pages"]) if b.get("words") and b.get("pages") else wpp_default))
pages = float(b["pages"]) if b.get("pages") else (float(b["words"]) / wpp if b.get("words") else None)
if not pages or pages <= 0:
raise InputError("书籍 %r 需要 pages、words 或 audio_minutes" % t)
diff = float(b.get("difficulty", 1.0))
if pph and not b.get("words"):
unit_min = 60.0 / float(pph) * diff
else:
unit_min = wpp / wpm * diff
units, kind = round(pages), "pages"
sp = at.get(t, b.get("start_page", 1))
done = max(0.0, float(sp) - 1)
dl = pdate(b["deadline"], "书籍 %r 的截止日期" % t) if b.get("deadline") else None
chapters = sorted(int(c) for c in b.get("chapters", []))
books.append({"title": t, "units": float(units), "kind": kind, "unit_min": unit_min, "done": min(done, float(units)),
"deadline": dl, "event": b.get("event", ""), "chapters": chapters, "order": i})
buf = int(cfg.get("buffer_days", 1))
return {"start": start, "mins": mins, "catch": catch, "exc": exc, "books": books, "buffer": buf,
"max_session": float(cfg.get("max_session_minutes", 0) or 0), "snap": int(cfg.get("chapter_snap_pages", 8)),
"name": cfg.get("name", "阅读计划"), "wpm": wpm}
def capacity(m, d):
"""(计划分钟数,可用分钟数(含补读日),备注)。"""
if d in m["exc"]:
v, note = m["exc"][d]
return v, v, note or ("不阅读" if v == 0 else "")
wd = DAYS[d.weekday()]
v = m["mins"].get(wd, 0.0)
if wd in m["catch"]:
return 0.0, v, "补读日"
return v, v, ""
def plan(m, mode="even"):
F = []
books = [b for b in m["books"]]
for b in books:
b["need"] = (b["units"] - b["done"]) * b["unit_min"]
if b["need"] <= 0:
F.append(("info", "RP05", "%s 已完成 - 排除在外" % b["title"]))
b["target"] = None
if b["deadline"] and b["deadline"] < m["start"]:
if b["need"] > 0:
F.append(("medium", "RP05", "%s:截止日期 %s 已过 - 按无截止日期规划" % (b["title"], b["deadline"])))
b["deadline"] = None
if b["deadline"]:
b["target"] = b["deadline"] - datetime.timedelta(days=m["buffer"])
if b["target"] < m["start"]:
b["target"] = m["start"]
F.append(("medium", "RP05", "%s:截止日期 %s 太近,无法安排 %d 个缓冲日 - 规划到截止日期" % (b["title"], b["deadline"], m["buffer"])))
todo = [b for b in books if b["need"] > 0]
dated = sorted([b for b in todo if b["target"]], key=lambda b: (b["target"], b["order"]))
undated = [b for b in todo if not b["target"]]
seq = dated + undated
last = max([b["deadline"] for b in dated] or [m["start"]])
# 视野:足够的天数以全容量完成所有内容,加上余量
weekly = sum(m["mins"].get(d, 0) for d in DAYS if d not in m["catch"]) or 1
total_need = sum(b["need"] for b in seq)
horizon_end = max(last, m["start"]) + datetime.timedelta(days=int(total_need / weekly * 7) + 60)
days, d = [], m["start"]
while d <= horizon_end:
p, a, note = capacity(m, d)
days.append({"date": d, "cap": p, "avail": a, "note": note, "planned": 0.0, "items": []})
d += datetime.timedelta(days=1)
idx = {x["date"]: i for i, x in enumerate(days)}
# 每个时间段的负载因子(均匀模式):满足每个目标的最小均匀容量份额
load = [1.0] * len(days)
if mode == "even" and dated:
seg_start, k0 = 0, 0
while k0 < len(dated):
best, bk = 0.0, k0
cum = 0.0
for k in range(k0, len(dated)):
cum += dated[k]["need"]
ti = idx[dated[k]["target"]]
cap = sum(x["cap"] for x in days[seg_start:ti + 1])
r = cum / cap if cap > 0 else float("inf")
if r >= best:
best, bk = r, k
L = min(best * 1.03, 1.0) # 3% 余量用于章节末尾停止和舍入
end = idx[dated[bk]["target"]]
for i in range(seg_start, end + 1):
load[i] = L
seg_start, k0 = end + 1, bk + 1
# 最后一个截止日期后:无日期书籍按计划到目前为止的平均负载(至少 0.5)
tail = max(0.5, max(load[:seg_start] or [1.0]))
for i in range(seg_start, len(days)):
load[i] = tail if undated else 1.0
# 分配(最早截止日期优先),跟踪每本书的位置
qi = 0
pos = {b["title"]: b["done"] for b in seq}
for i, day in enumerate(days):
budget = day["cap"] * load[i]
while budget > 1e-9 and qi < len(seq):
b = seq[qi]
remaining = (b["units"] - pos[b["title"]]) * b["unit_min"]
use = min(budget, remaining)
before = pos[b["title"]]
after = before + use / b["unit_min"]
if after > b["units"] - 1e-6:
after = b["units"]
elif b["kind"] == "pages":
snapped = snap(after, b, m["snap"], use / b["unit_min"])
if snapped != after:
after = snapped
use = (after - before) * b["unit_min"]
budget = use # 在章节末尾停止:这一天在这里结束
pos[b["title"]] = after
day["items"].append({"title": b["title"], "from": before, "to": after, "kind": b["kind"], "minutes": use})
day["planned"] += use
budget -= use
if after >= b["units"] - 1e-6:
b["finish"] = day["date"]; qi += 1
elif budget <= 1e-9 or use <= 1e-9:
break
if qi >= len(seq):
break
days = [x for x in days if x["date"] <= max([b.get("finish", m["start"]) for b in seq] + [last])]
for b in seq:
b.setdefault("finish", None)
# 发现项
for b in dated:
f = b["finish"]
if f is None or f > b["deadline"]:
avail = [x for x in days if x["date"] <= b["deadline"]]
share = sum(it["minutes"] for x in avail for it in x["items"] if it["title"] == b["title"])
short_min = b["need"] - share
# 也计算截止日期前可用的补读/缓冲时间
reading_days = sum(1 for x in avail if x["avail"] > 0) or 1
short_units = short_min / b["unit_min"]
msg = "%s 无法在 %s%s 前完成:按你的速度约差 %d %s;预计完成日期 %s。选项:在那之前的 %d 个阅读日每天 +%d 分钟,读得更快,略读或跳过约 %d %s,或更早开始" % (
b["title"], b["deadline"], " (%s)" % b["event"] if b["event"] else "", math.ceil(short_units), "页" if b["kind"] == "pages" else "音频分钟",
f or "超出计划范围", math.ceil(short_min / reading_days), reading_days, math.ceil(short_units), "页" if b["kind"] == "pages" else "音频分钟")
F.append(("high", "RP01", msg))
elif f > b["target"]:
F.append(("medium", "RP02", "%s 在 %s 完成,在 %s 前的 %d 个缓冲日内 - 错过一天就没有余地" % (b["title"], f, b["deadline"], m["buffer"])))
if mode == "even":
peak = max(load) if load else 0
if 0.9 <= peak <= 1.0 and not any(f[1] == "RP01" for f in F):
F.append(("medium", "RP04", "计划在最繁忙时段使用了你计划阅读时间的 %d%% - 一周不顺就会把你推入缓冲期" % round(peak * 100)))
if m["max_session"]:
long = [x for x in days if x["planned"] > m["max_session"] + 0.5]
if long:
F.append(("low", "RP03", "%d 天计划超过 %d 分钟(例如 %s:%d 分钟)- 拆分为两个会话" % (
len(long), m["max_session"], long[0]["date"], round(long[0]["planned"]))))
for b in seq:
if b["kind"] == "audio":
F.append(("info", "RP06", "%s 是有声书:计划按 %s 倍速计算收听时间;通勤和家务可以覆盖它" % (b["title"], ("%.2f" % (1 / b["unit_min"])).rstrip("0").rstrip("."))))
F.sort(key=lambda f: (SEV[f[0]], f[1]))
return {"days": days, "books": seq, "all_books": books, "findings": F, "load": load}
def snap(after, b, snap_pages, day_units):
"""如果章节末尾很近(在 snap_pages 和当天 25% 内),将一天的停止点移至章节末尾。"""
if not b["chapters"] or snap_pages <= 0:
return after
lim = min(snap_pages, max(1.0, day_units * 0.25))
ends = [c - 1 for c in b["chapters"] if c - 1 > 0]
best = None
for e in ends:
if abs(e - after) <= lim and e > after - day_units + 0.5 and (best is None or abs(e - after) < abs(best - after)):
best = e
return float(best) if best is not None else after
# ---------------------------------------------------------------- rendering
def fmt_item(it):
if it["kind"] == "audio":
return "%s %s-%s" % (it["title"], hm(it["from"]), hm(it["to"]))
a = int(round(it["from"])) + 1
z = int(round(it["to"]))
if z < a:
z = a
return "%s 第 %d-%d 页(%d 页)" % (it["title"], a, z, z - a + 1)
def hm(x):
x = int(round(x))
return "%d:%02d" % (x // 60, x % 60)
def checkpoints(r):
out = []
pos = {}
for x in r["days"]:
for it in x["items"]:
pos[it["title"]] = it
if x["date"].weekday() == 6 and pos: # 周日
out.append((x["date"], "; ".join("%s %s" % (t, ("第 %d 页" % round(it["to"]) if it["kind"] == "pages" else hm(it["to"]))) for t, it in pos.items() if it["to"] < next(b["units"] for b in r["books"] if b["title"] == t) - 1e-6) or "所有当前书籍已完成"))
return out
def report(r, m, mode):
hi = [f for f in r["findings"] if f[0] == "high"]
out = ["# %s" % m["name"], ""]
tot_min = sum(x["planned"] for x in r["days"])
out.append("开始于 %s,%s 模式,每个截止日期前 %d 个缓冲日,补读日:%s。计划阅读:%d 小时 %02d 分钟,共 %d 天。" % (
m["start"], mode, m["buffer"], ", ".join(sorted(m["catch"], key=DAYS.index)) or "无", tot_min // 60, tot_min % 60, len(r["days"])))
out += ["", "**结论:%s**" % ("%d 本书无法在其截止日期前完成 - 更改计划" % len(hi) if hi else "所有截止日期均达成"), ""]
out += ["## 书籍", "", "| 书籍 | 剩余 | 预计时间 | 截止日期 | 完成日期 | 计划完成 | 剩余天数 | 每个阅读日平均 |", "|---|---|---|---|---|---|---|---|"]
for b in r["all_books"]:
left = b["units"] - b["done"]
rd = [x for x in r["days"] if any(it["title"] == b["title"] for it in x["items"])]
avg = left / len(rd) if rd else 0
spare = (b["deadline"] - b["finish"]).days if b.get("finish") and b["deadline"] else None
out.append("| %s | %s | %s | %s | %s | %s | %s | %s |" % (
b["title"], ("%d 页" % round(left)) if b["kind"] == "pages" else hm(left) + " 音频", hm(b["need"]) + " 小时",
"%s %s" % (b["deadline"], "(%s)" % b["event"] if b["event"] else "") if b["deadline"] else "-",
b["target"] or "-", b.get("finish") or ("已完成" if b["need"] <= 0 else "未按时"),
"-" if spare is None else spare, ("%.0f 页" % avg) if b["kind"] == "pages" else hm(avg)))
out += ["", "## 发现项", ""]
out += ["- **%s %s** %s" % (s.upper(), c, msg) for s, c, msg in r["findings"]] or ["- 无"]
out += ["", "## 逐日", "", "| 日期 | 星期 | 分钟 | 阅读 | 备注 |", "|---|---|---|---|---|"]
events = {}
for b in r["all_books"]:
if b["deadline"]:
events.setdefault(b["deadline"], []).append("截止日期 %s%s" % (b["title"], " - " + b["event"] if b["event"] else ""))
if b["target"] and b["target"] != b["deadline"]:
events.setdefault(b["target"], []).append("%s 完成日期" % b["title"])
for x in r["days"]:
note = [x["note"]] if x["note"] else []
if x["note"] == "补读日" or (x["planned"] < 0.5 and x["avail"] > 0):
note = ["补读 / 空闲(%d 分钟可用)" % x["avail"]] if x["avail"] > 0 else note
note += events.get(x["date"], [])
if m["max_session"] and x["planned"] > m["max_session"] + 0.5:
note.append("2 个会话")
out.append("| %s | %s | %s | %s | %s |" % (x["date"], x["date"].strftime("%a"), round(x["planned"]) if x["planned"] >= 0.5 else "-",
"; ".join(fmt_item(it) for it in x["items"]) or "-", "; ".join(note)))
cps = checkpoints(r)
if cps:
out += ["", "## 每周检查点(周日晚你应该读到哪)", ""]
out += ["- %s:%s" % (d, s) for d, s in cps]
out += ["", "## 如果你落后了", ""]
for b in r["books"]:
if b["kind"] != "pages" or not b["deadline"]:
continue
rd = [x for x in r["days"] if any(it["title"] == b["title"] for it in x["items"])]
if not rd:
continue
day_pp = sum(it["to"] - it["from"] for x in rd for it in x["items"] if it["title"] == b["title"]) / len(rd)
spare = [x for x in r["days"] if x["date"] <= b["deadline"] and x["date"] >= rd[0]["date"] and x["avail"] > 0 and x["planned"] < 0.5]
out.append("- %s:错过一天约损失 %d 页。在 %s 补上,或在接下来 3 个阅读日每天增加 %d 页。随时使用 `--from <date> --at \"%s=<page>\"` 重新规划。" % (
b["title"], round(day_pp), ("空闲日 " + ", ".join(str(x["date"]) for x in spare[:3])) if spare else "截止日期前没有空闲日", math.ceil(day_pp / 3), b["title"]))
out.append("")
return "\n".join(out)
# ---------------------------------------------------------------- self-test
def self_test():
tests = []
def t(name, cond):
tests.append((name, bool(cond)))
def run(cfg, mode="even", **kw):
m = load(cfg, **kw)
return plan(m, mode), m
base = {"start": "2026-11-02", "reader": {"words_per_minute": 250, "words_per_page": 250}, "minutes": 30, "buffer_days": 0}
r, m = run(dict(base, books=[{"title": "A", "pages": 60, "deadline": "2026-11-08"}]), "asap")
t("asap:每天 30 页,1 页/分钟,第 2 天完成", r["books"][0]["finish"] == datetime.date(2026, 11, 3))
r, m = run(dict(base, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]))
t("均匀:分散到一周(约 10 分钟/天 + 3% 余量)", r["books"][0]["finish"] >= datetime.date(2026, 11, 7) and 10 <= r["days"][0]["planned"] <= 10.31)
r, m = run(dict(base, books=[{"title": "A", "pages": 300, "deadline": "2026-11-08"}]))
t("不可行 -> RP01 高", any(f[1] == "RP01" and f[0] == "high" for f in r["findings"]))
t("RP01 建议额外分钟数", any("+13 分钟" in f[2] for f in r["findings"] if f[1] == "RP01"))
r, m = run(dict(base, buffer_days=2, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]))
t("缓冲日:提前 2 天完成", r["books"][0]["finish"] == datetime.date(2026, 11, 6))
r, m = run(dict(base, catch_up_days=["sun"], books=[{"title": "A", "pages": 60, "deadline": "2026-11-08"}]))
t("补读周日不安排阅读", r["days"][-1]["planned"] == 0 and r["books"][0]["finish"] == datetime.date(2026, 11, 7))
r, m = run(dict(base, exceptions=[{"dates": "2026-11-03..2026-11-04", "minutes": 0, "note": "旅行"}], books=[{"title": "A", "pages": 50, "deadline": "2026-11-08"}]))
t("例外范围不阅读", all(x["planned"] == 0 for x in r["days"][1:3]))
two = [{"title": "晚", "pages": 30, "deadline": "2026-11-20"}, {"title": "早", "pages": 60, "deadline": "2026-11-04"}]
r, m = run(dict(base, books=two))
t("最早截止日期优先", r["days"][0]["items"][0]["title"] == "早")
t("紧时段后负载较轻", r["load"][0] > 0.6 and r["load"][5] < 0.2)
r, m = run(dict(base, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]), at={"A": 36})
t("--at 从页数继续", abs(sum(x["planned"] for x in r["days"]) - 35) < 0.01)
r, m = run(dict(base, books=[{"title": "W", "words": 25000, "deadline": "2026-11-08"}]))
t("字数 -> 通过每页字数转为页数", r["all_books"][0]["units"] == 100)
r, m = run(dict(base, books=[{"title": "H", "pages": 70, "difficulty": 1.5, "deadline": "2026-11-08"}]))
t("难度缩放时间", abs(r["books"][0]["need"] - 105) < 0.01)
r, m = run(dict(base, books=[{"title": "Au", "audio_minutes": 300, "speed": 1.5, "deadline": "2026-11-08"}]))
t("有声书 1.5 倍速 = 200 分钟", abs(r["books"][0]["need"] - 200) < 0.01 and any(f[1] == "RP06" for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "C", "pages": 70, "deadline": "2026-11-08", "chapters": [1, 12, 23, 31, 42, 55]}]))
ends = [round(it["to"]) for x in r["days"] for it in x["items"]]
t("章节吸附在章节末尾停止", 11 in ends and 22 in ends and ends[-1] == 70)
t("每天每本书一个项目", all(len({it["title"] for it in x["items"]}) == len(x["items"]) for x in r["days"]))
t("页面范围不重叠", all(fmt_item(it) for x in r["days"] for it in x["items"]) and
[int(round(it["from"])) for x in r["days"] for it in x["items"]][1:] == [int(round(it["to"])) for x in r["days"] for it in x["items"]][:-1])
r, m = run(dict(base, max_session_minutes=20, minutes=60, books=[{"title": "A", "pages": 300, "deadline": "2026-11-08"}]))
t("长会话 -> RP03", any(f[1] == "RP03" for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "A", "pages": 200, "deadline": "2026-11-08"}]))
t("紧计划 -> RP04 或 RP01", any(f[1] in ("RP04", "RP01") for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "A", "pages": 30}, {"title": "B", "pages": 60, "deadline": "2026-11-05"}]))
t("无日期书籍在有日期书籍之后", r["books"][-1]["title"] == "A" and r["books"][-1]["finish"])
r, m = run(dict(base, books=[{"title": "A", "pages": 20, "start_page": 21}]))
t("已完成书籍 -> RP05", any(f[1] == "RP05" for f in r["findings"]))
t("pages_per_hour", abs(load(dict(base, reader={"pages_per_hour": 30}, books=[{"title": "P", "pages": 30}]))["books"][0]["unit_min"] - 2) < 1e-9)
for bad in [dict(base, books=[]), dict(base, books=[{"title": "x"}]), dict(base, minutes={"funday": 3}, books=[{"title": "a", "pages": 3}]),
dict(base, books=[{"title": "a", "pages": 3, "deadline": "2026-13-01"}])]:
try:
run(bad); okk = False
except InputError:
okk = True
t("拒绝错误输入:%s" % (list(bad.get("minutes", {})) if isinstance(bad.get("minutes"), dict) else (bad["books"][0] if bad["books"] else "无书籍")), okk)
r, m = run(dict(base, books=[{"title": "旧", "pages": 30, "deadline": "2026-10-01"}]))
t("已过截止日期 -> RP05,按无截止日期规划", any(f[1] == "RP05" for f in r["findings"]) and r["books"][0]["finish"])
rep = report(*run(dict(base, books=two)), "even")
t("报告有结论、日表格和补读建议", "结论" in rep and "| 日期 |" in rep and "如果你落后了" in rep)
for name, okk in tests:
print(("通过 " if okk else "失败 ") + name)
n = sum(1 for _, okk in tests if okk)
print("%d/%d 通过" % (n, len(tests)))
return 0 if n == len(tests) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("plan", nargs="?")
ap.add_argument("--mode", choices=["even", "asap"], default="even")
ap.add_argument("--from", dest="start", help="从此日期重新规划(YYYY-MM-DD)")
ap.add_argument("--at", action="append", default=[], help='"Title=page"(有声书为 "Title=minute"):你当前的位置')
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not a.plan:
ap.error("需要计划 JSON(或 --self-test)")
try:
try:
cfg = json.load(open(a.plan, encoding="utf-8"))
except (OSError, ValueError) as e:
raise InputError(str(e))
at = {}
for s in a.at:
if "=" not in s:
raise InputError("--at 需要 Title=page,得到 %r" % s)
k, v = s.rsplit("=", 1)
try:
at[k.strip()] = float(v)
except ValueError:
raise InputError("--at 页数必须是数字,得到 %r" % v)
titles = {b.get("title") for b in cfg.get("books", [])} if isinstance(cfg, dict) else set()
for k in at:
if k not in titles:
raise InputError("--at:没有标题为 %r 的书籍" % k)
m = load(cfg, a.start, at)
r = plan(m, a.mode)
except (InputError, KeyError, TypeError, ValueError) as e:
print("错误:%s" % e, file=sys.stderr)
return 2
if a.json:
out = {"books": [{"title": b["title"], "deadline": str(b["deadline"]) if b["deadline"] else None, "finish": str(b.get("finish")) if b.get("finish") else None,
"minutes_needed": round(b["need"])} for b in r["all_books"]],
"days": [{"date": str(x["date"]), "minutes": round(x["planned"]), "read": [fmt_item(it) for it in x["items"]]} for x in r["days"]],
"findings": [{"severity": s, "code": c, "message": msg} for s, c, msg in r["findings"]]}
print(json.dumps(out, indent=2))
else:
print(report(r, m, a.mode))
return 1 if any(f[0] == "high" for f in r["findings"]) else 0
if __name__ == "__main__":
sys.exit(main())
用法
此提示词专为 productivity 设计。复制上方内容并粘贴到你常用的 AI 工具中。
为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。
讨论
0 条评论