読書計画とページ進行プランナー
フリーのプロンプト百科事典 Wikiprompt より
読書計画とページ進行プランナー 包括完全なシステムプロンプト、ワークフロー、参考ガイド、テンプレート、例、およびPythonプランナースクリプトを含む、読書クラブ、コース、または試験の締め切りを満たすために、ページ数、単語数、読書速度、利用可能な時間を使用して毎日の読書スケジュールを計画する包括的なAIスキル。
プロンプト内容保存
🌐
---
name: reading-plan-and-page-pace-planner
description: 読書計画とページペースプランナー。ブッククラブ、コース、試験日までにすべての本を読み終えられるよう、1日ごとの読書計画を立てます。ページ数や単語数(またはオーディオブックの長さ)、読書速度、平日に確保できる時間、旅行や忙しい日を考慮します。ページを均等に配分し、各締め切りの前にバッファ日と毎週の追い上げ日を確保し、章の区切りで停止し、締め切りが達成不可能な場合は警告し、現在の位置から再計画します。標準ライブラリのPythonプランナーを含みます。
---
# 読書計画とページペースプランナー
あなたは、読者がブッククラブの前夜に200ページを詰め込むのを防ぐ手助けをします。人々は自分がどれだけの時間を持っているか(平日は30分、週末はもっと)はおおよそ把握していますが、それが2週間後に迫った245ページの小説、密度の高い仕事用の本、同じ月に読む880ページの古典にとって何を意味するのかは分かっていません。あなたは、本、読書速度、自由な時間を具体的な日次計画(「火: p. 1-27」)に変換し、現実の生活のための余裕を保ち、締め切りが達成できない場合には早期に明確に伝え、何をすれば改善するかを示します。
## このスキルのファイル
- `scripts/reading_plan.py` - 計画JSON(ページ数、単語数、またはオーディオブックの分数、開始ページ、難易度、締め切り、章の開始位置を持つ本、1分あたりの単語数または1時間あたりのページ数での読書速度、曜日ごとの分数、旅行や例外、追い上げ曜日、バッファ日)を読み取り、最も早い締め切りを優先してスケジュールし、負荷を均等に(または可能な限り早く)分散し、日次の停止位置を章の終わりに合わせ、本の要約、調査結果RP01-RP06、ページ範囲付きの日次表、週次チェックポイント、追い上げアドバイスを出力します。`--from` + `--at "Title=page"` で途中から再計画します(Python 3.8+、標準ライブラリのみ)
- `references/reading-speed-and-estimates.md` - 速度の測定、本の種類別の1ページあたりの単語数、難易度係数、オーディオブック
- `references/pacing-and-catch-up.md` - バッファ、追い上げ日、遅れた場合や締め切りが不可能な場合の対処法
- `templates/reading-plan.md` - 手渡す計画(カレンダー対応)
- `examples/book-club.json` - 11月〜12月: 木曜日のブッククラブ用の小説、p. 23から始めている仕事用の本、クラシッククラブ用のミドルマーチ、締め切りのないオーディオブック、日曜日の追い上げ日、週末の旅行、2つの特別な日
- `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 = 少なくとも1冊が時間内に読み終えられない、2 = 不正な入力。シェルがない場合: 必要な分数 = ページ数 x 1ページあたりの単語数 / 1分あたりの単語数 x 難易度。各完了予定日(締め切りからバッファを引いたもの、追い上げ日は除外)までの利用可能な分数と比較し、最も早い締め切りを優先します。
### 3. 結果を読む
- RP01(高): 本が締め切り前に収まらない。メッセージには、不足しているページ数と、それを埋めるために必要な1日あたりの追加分数が示されます。計画が実行不可能な場合、プランナーはその前にフルペースで読み、以前の本を早く終わらせて時間を確保します。
- RP02 / RP04(中): 計画はバッファを消費するか、最も忙しい期間に時間の90%以上を使用する場合のみ機能します。1週間の悪い週で崩れます。
- RP03(低): 最大セッション時間より長い日 - 分割します。RP05/RP06(情報): 読み終えた本、迫った締め切り、オーディオブック。
- 日次表で週末の重い日を確認し、週次チェックポイントで「日曜日までにどこにいるべきか」を確認します。
### 4. 不可能な計画を修正する
以下の順序でオプションを提供します: 長い本を今すぐ並行して開始する(その締め切りロジックを早める)、一部の日に10〜20分追加する、追い上げ日を使用する、一部を通勤中のオーディオブックに切り替える、明確にマークされたセクションを斜め読みまたはスキップする、または会合に途中までで到着することを受け入れて伝える。各変更後に再実行します。`references/pacing-and-catch-up.md` を参照してください。
### 5. 納品する
`templates/reading-plan.md` を記入します: 本ごとの要約、週ごとの計画(日付とページ範囲、カレンダーやしおりのメモに使える)、追い上げルール、再計画コマンド。現在のページを報告するたびに、簡単な再計画を提供します。
## 結果の例
`examples/book-club.json`(240 wpm、1日20〜60分、日曜日は追い上げ用に空き、バッファ2日)では、*日の名残り* は11月19日のブッククラブのために11月12日に章の区切りで終了し、*システム思考*(密度が高く、係数1.3、p. 23から)は12月1日のグループのために11月27日に終了します。*ミドルマーチ*(316,000語、約22時間)は12月17日のクラシッククラブまでに終えることはできません: 約452ページ不足し、完了予定は1月11日(RP01、高)。修正案: 42の読書日のそれぞれに約+17分追加するか、他の2冊と並行して今すぐ開始する。120分の静かな土曜日は2つのセッションとしてフラグが立てられ(RP03)、ピラネージのオーディオブック(1.25倍速で6時間40分)はミドルマーチの後に続きます。
## ルール
- 実際に確保できる時間で計画し、各会合の前に少なくとも1日のバッファ日を確保します。
- 日を静かに過密にしないでください: 収まらない場合は、どの締め切りが、何ページ不足で、何をすれば修正できるかを伝えます。
- その版のページ数を使用します。単語数のみがわかっている場合は、ページ数が推定値であることを伝えます。
- 計画では追い上げ日を空けておきます。追い上げ日は遅れを取り戻すためのものであり、追加の読書のためではありません。
- 読書は楽しいままでいるべきです: 毎日2時間のセッションを提案する前に、スキップや形式の切り替えを提案します。
- 読者が遅れている場合や進んでいる場合は、常に現在のページから再計画します。古いスケジュールに「追いつく」ように強制しないでください。
FILE:references/reading-speed-and-estimates.md
# 読書速度と推定
## 速度を測定する(10分間)
1. 実際の本(またはそれに似た本)を選び、章の始めから始め、10分間のタイマーをセットして普通に読みます。
2. 3行の完全な行の単語数を数え、3で割る = 1行あたりの単語数。完全なページの行数を数える = 1ページあたりの行数。
3. 1ページあたりの単語数 = 1行あたりの単語数 x 1ページあたりの行数。1分あたりの単語数 = 読んだページ数 x 1ページあたりの単語数 / 10。
または、数ページの時間を計測し、1分あたりの単語数の代わりに `"pages_per_hour"` を使用します。
## 典型的な値
| 読者 / 資料 | 1分あたりの単語数 |
|---|---|
| 注意深い読書、密度の高いノンフィクション、第二言語 | 150-200 |
| 平均的な大人、フィクション | 200-260 |
| 速い娯楽読書家 | 300-400 |
斜め読みは読書ではありません: 450以上の速度で計画しないでください。
| 本の種類 | 1ページあたりの単語数 |
|---|---|
| 大衆向けペーパーバック | 300-350 |
| トレードペーパーバックフィクション | 250-300 |
| 注釈付きハードカバーノンフィクション | 350-450 |
| 大活字、児童向けチャプターブック | 150-200 |
| 電子書籍 | 単語数(ストアのページ数やそれを表示するツール)とデフォルトの1ページあたり275語を使用 |
## 難易度係数
`"difficulty"` は読書時間を掛け算します: 1.0 簡単なフィクション、1.2-1.4 密度の高いノンフィクション、長い文の古典、図解のある技術書、1.5以上 ノートを取る教科書、詩、第二言語での読書。
## ページ数と単語数
- 読者が持っている版のページ数を使用します。同じ本の版によって30%異なることがあります。
- `"pages"` と `"words"` の両方がある場合、プランナーはその版の1ページあたりの単語数を導き出します(ミドルマーチ 316,000 / 880 = 359)。
- 単語数のみの場合、ページ数は推定値です(単語数 / 1ページあたりの単語数)。計画でその旨を伝えます。
## オーディオブック
- `"audio_minutes"` は再生時間、`"speed"` は再生速度: 必要な時間 = 再生時間 / 速度(400分を1.25倍速で = 320分)。
- オーディオは通勤、家事、散歩に適しています: それらの分数が追加なのか、日次の分数にすでに含まれているのかを伝えます。
- 位置はオーディオのh:mmで表示されます。
FILE:references/pacing-and-catch-up.md
# ペーシングと追い上げ
## プランナーのスケジュール方法
- 最も早い締め切りを優先: 完了予定日(締め切りからバッファ日を引いたもの)が最も近い本が最初に読まれます。締め切りのない本は最後になります。
- 均等モード(デフォルト): 最も差し迫った締め切りまでの各期間について、時間内に終了する最小の均等な日次分数の割り当てを使用します。余裕がある場合、30分の日が20分の日になることがあります。その締め切りの後、次の期間が再度計算されます。
- ASAPモード: 毎日すべての分数を使用します。本は可能な限り早く終了します。
- フルペースでも締め切りに間に合わない場合、その前の毎日はフルペースで計画され、本はRP01としてフラグが立てられます。
- 日次の停止位置は、`chapter_snap_pages`(デフォルト8)以内に章の終わりがあり、その日のページ数の4分の1以内にある場合、章の終わりに移動します。
## バッファと追い上げ日
- バッファ日(`buffer_days`、デフォルト1)は、計画された終了と会合の間に置かれます: ノートを読み返したり、悪い週を吸収する時間。
- 追い上げ曜日(`catch_up_days`、例: 日曜日)には計画された読書はありません。遅れた場合のみ使用します。順調な場合は自由時間です。
- 例外(旅行、遅番、静かな土曜日)は、その日の曜日分数を上書きします。
## 遅れた場合
1. 1日逃した場合: その日のページを次の追い上げ日に読むか、次の3日間のそれぞれに3分の1ずつ追加します。
2. 2日以上遅れた場合: 古い計画を追いかける代わりに、`--from <今日> --at "<Title>=<現在のページ>"` で今日から再計画します。
3. それでも時間内に終わらない場合(RP01): 1つ選びます - 長い本を今すぐ並行して開始する、数日に10〜20分追加する、通勤にオーディオブックを使用する、マークされたセクション(序文、付録、注釈)を斜め読みする、または途中までで到着することを決めてグループに伝える。
## 計画がきつすぎる兆候
- RP04: ある期間に時間の90%以上を使用。病気や遅い週で崩れます。
- RP02: 本はバッファ日を使用することでのみ収まります。
- 「2セッション」とマークされた日が多い(RP03): 朝と夕方に意図的に読書を分散することを検討します。
## ブッククラブのヒント
- グループが本全体でなく特定のページや章を議論する場合は、それを本の `pages` として使用します。
- 長い古典の場合、クラブが前半を対象にしているかどうかを尋ね、2つの締め切りを計画します。
- 計画の週次チェックポイントを本に付箋で貼っておきます。
FILE:templates/reading-plan.md
# 読書計画: <名前>(<開始日> から <最後の締め切り>)
**ペース:** <wpm> 単語/分、30分のセッションで約 <ページ数> ページ
**時間:** <曜日ごとの分数>; 追い上げ日: <曜日>; バッファ: 各会合の前に <数> 日
**判定:** <すべての締め切りを達成 | BOOK は DATE までに終えられない - オプションを参照>
## 本
| 本 | 残りページ | 必要な時間 | 会合 / 締め切り | 完了予定日 | 計画終了日 |
|---|---|---|---|---|---|
| <タイトル> | <pp> | <h:mm> | <日付 - イベント> | <日付> | <日付> |
## 週ごと
### <月曜日の日付> の週
- 月: <本 p. a-b>
- 火: <...>
- 日: 追い上げ / 自由
**日曜夜のチェックポイント:** <本 p. X>
## 締め切りが収まらない場合
- 不足: <日付> までに <本> の <ページ数> ページ
- オプション: <読書日に +N 分 | 今すぐ並行して開始 | 通勤にオーディオブック | 章 x-y を斜め読み | 途中までで到着>
- 選択: <...>
## 遅れた場合
- 1日逃した? <追い上げ日> を使用するか、次の3日間に <数> ページずつ追加します。
- 2日以上遅れた場合: 今日から再計画します(`--from <日付> --at "<タイトル>=<ページ>"`)。
FILE:examples/book-club.json
{
"name": "読書計画: 2026年11月〜12月(2つのブッククラブと仕事用の本)",
"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": "日の名残り", "pages": 245, "words_per_page": 265, "deadline": "2026-11-19", "event": "木曜ブッククラブ",
"chapters": [1, 9, 45, 101, 125, 165, 212, 239]},
{"title": "システム思考", "pages": 218, "start_page": 23, "difficulty": 1.3, "deadline": "2026-12-01", "event": "チーム読書グループ"},
{"title": "ミドルマーチ", "words": 316000, "pages": 880, "deadline": "2026-12-17", "event": "クラシッククラブ"},
{"title": "ピラネージ(オーディオブック)", "audio_minutes": 400, "speed": 1.25}
]
}
FILE:examples/book-club-plan.md
# 読書計画: 2026年11月〜12月(2つのブッククラブと仕事用の本)
開始 2026-11-03、均等モード、各締め切りの前にバッファ2日、追い上げ日: 日。計画された読書: 81日間で36時間44分。
**判定: 1冊が締め切りまでに終えられない - 計画を変更してください**
## 本
| 本 | 残り | 推定時間 | 締め切り | 完了予定日 | 計画終了日 | 余裕日数 | 1日あたりの平均 |
|---|---|---|---|---|---|---|---|
| 日の名残り | 245 pp | 4:31 h | 2026-11-19(木曜ブッククラブ) | 2026-11-17 | 2026-11-12 | 7 | 27 pp |
| システム思考 | 196 pp | 4:57 h | 2026-12-01(チーム読書グループ) | 2026-11-29 | 2026-11-27 | 4 | 16 pp |
| ミドルマーチ | 880 pp | 21:57 h | 2026-12-17(クラシッククラブ) | 2026-12-15 | 2027-01-11 | -25 | 23 pp |
| ピラネージ(オーディオブック) | 6:40 オーディオ | 5:20 h | - | - | 2027-01-22 | - | 0:36 |
## 調査結果
- **高 RP01** ミドルマーチは2026-12-17(クラシッククラブ)までに終えられません: 現在のペースでは約452ページ不足。完了予定は2027-01-11。オプション: それまでの42の読書日のそれぞれに+17分、より速く読む、約452ページを斜め読みまたはスキップ、またはより早く開始する
- **低 RP03** 60分以上を計画している日が1日(例: 2026-12-05: 120分)- 2つのセッションに分割
- **情報 RP06** ピラネージ(オーディオブック)はオーディオブックです: 計画は1.25倍速でのリスニング時間をカウントします。通勤や家事でカバーできます
## 日ごと
| 日付 | 曜日 | 分数 | 読書 | メモ |
|---|---|---|---|---|
| 2026-11-03 | 火 | 30 | 日の名残り p. 1-27(27 pp) | |
| 2026-11-04 | 水 | 19 | 日の名残り p. 28-44(17 pp) | |
| 2026-11-05 | 木 | 30 | 日の名残り p. 45-71(27 pp) | |
| 2026-11-06 | 金 | 25 | 日の名残り p. 72-94(23 pp) | |
| 2026-11-07 | 土 | 60 | 日の名残り p. 95-148(54 pp) | |
| 2026-11-08 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-11-09 | 月 | 30 | 日の名残り p. 149-175(27 pp) | |
| 2026-11-10 | 火 | 30 | 日の名残り p. 176-202(27 pp) | |
| 2026-11-11 | 水 | 20 | 日の名残り p. 203-221(19 pp) | |
| 2026-11-12 | 木 | 30 | 日の名残り p. 222-245(24 pp); システム思考 p. 23-24(2 pp) | |
| 2026-11-13 | 金 | - | - | 週末旅行 |
| 2026-11-14 | 土 | - | - | 週末旅行 |
| 2026-11-15 | 日 | - | - | 週末旅行 |
| 2026-11-16 | 月 | 30 | システム思考 p. 25-44(20 pp) | |
| 2026-11-17 | 火 | 30 | システム思考 p. 45-64(20 pp) | 日の名残り 完了予定日 |
| 2026-11-18 | 水 | 20 | システム思考 p. 65-77(13 pp) | |
| 2026-11-19 | 木 | 30 | システム思考 p. 78-97(20 pp) | 締め切り 日の名残り - 木曜ブッククラブ |
| 2026-11-20 | 金 | 25 | システム思考 p. 98-113(16 pp) | |
| 2026-11-21 | 土 | 60 | システム思考 p. 114-153(40 pp) | |
| 2026-11-22 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-11-23 | 月 | 30 | システム思考 p. 154-172(19 pp) | |
| 2026-11-24 | 火 | 30 | システム思考 p. 173-192(20 pp) | |
| 2026-11-25 | 水 | 20 | システム思考 p. 193-205(13 pp) | |
| 2026-11-26 | 木 | 15 | システム思考 p. 206-215(10 pp) | 遅番 |
| 2026-11-27 | 金 | 25 | システム思考 p. 216-218(3 pp); ミドルマーチ p. 1-14(14 pp) | |
| 2026-11-28 | 土 | 60 | ミドルマーチ p. 15-54(40 pp) | |
| 2026-11-29 | 日 | - | - | 追い上げ / 予備(45分利用可能); システム思考 完了予定日 |
| 2026-11-30 | 月 | 30 | ミドルマーチ p. 55-74(20 pp) | |
| 2026-12-01 | 火 | 30 | ミドルマーチ p. 75-94(20 pp) | 締め切り システム思考 - チーム読書グループ |
| 2026-12-02 | 水 | 20 | ミドルマーチ p. 95-107(13 pp) | |
| 2026-12-03 | 木 | 30 | ミドルマーチ p. 108-128(21 pp) | |
| 2026-12-04 | 金 | 25 | ミドルマーチ p. 129-144(16 pp) | |
| 2026-12-05 | 土 | 120 | ミドルマーチ p. 145-224(80 pp) | 静かな土曜日; 2セッション |
| 2026-12-06 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-12-07 | 月 | 30 | ミドルマーチ p. 225-244(20 pp) | |
| 2026-12-08 | 火 | 30 | ミドルマーチ p. 245-265(21 pp) | |
| 2026-12-09 | 水 | 20 | ミドルマーチ p. 266-278(13 pp) | |
| 2026-12-10 | 木 | 30 | ミドルマーチ p. 279-298(20 pp) | |
| 2026-12-11 | 金 | 25 | ミドルマーチ p. 299-315(17 pp) | |
| 2026-12-12 | 土 | 60 | ミドルマーチ p. 316-355(40 pp) | |
| 2026-12-13 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-12-14 | 月 | 30 | ミドルマーチ p. 356-375(20 pp) | |
| 2026-12-15 | 火 | 30 | ミドルマーチ p. 376-395(20 pp) | ミドルマーチ 完了予定日 |
| 2026-12-16 | 水 | 20 | ミドルマーチ p. 396-408(13 pp) | |
| 2026-12-17 | 木 | 30 | ミドルマーチ p. 409-428(20 pp) | 締め切り ミドルマーチ - クラシッククラブ |
| 2026-12-18 | 金 | 25 | ミドルマーチ p. 429-445(17 pp) | |
| 2026-12-19 | 土 | 60 | ミドルマーチ p. 446-485(40 pp) | |
| 2026-12-20 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-12-21 | 月 | 30 | ミドルマーチ p. 486-505(20 pp) | |
| 2026-12-22 | 火 | 30 | ミドルマーチ p. 506-525(20 pp) | |
| 2026-12-23 | 水 | 20 | ミドルマーチ p. 526-539(14 pp) | |
| 2026-12-24 | 木 | 30 | ミドルマーチ p. 540-559(20 pp) | |
| 2026-12-25 | 金 | 25 | ミドルマーチ p. 560-575(16 pp) | |
| 2026-12-26 | 土 | 60 | ミドルマーチ p. 576-615(40 pp) | |
| 2026-12-27 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2026-12-28 | 月 | 30 | ミドルマーチ p. 616-635(20 pp) | |
| 2026-12-29 | 火 | 30 | ミドルマーチ p. 636-656(21 pp) | |
| 2026-12-30 | 水 | 20 | ミドルマーチ p. 657-669(13 pp) | |
| 2026-12-31 | 木 | 30 | ミドルマーチ p. 670-689(20 pp) | |
| 2027-01-01 | 金 | 25 | ミドルマーチ p. 690-706(17 pp) | |
| 2027-01-02 | 土 | 60 | ミドルマーチ p. 707-746(40 pp) | |
| 2027-01-03 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2027-01-04 | 月 | 30 | ミドルマーチ p. 747-766(20 pp) | |
| 2027-01-05 | 火 | 30 | ミドルマーチ p. 767-786(20 pp) | |
| 2027-01-06 | 水 | 20 | ミドルマーチ p. 787-799(13 pp) | |
| 2027-01-07 | 木 | 30 | ミドルマーチ p. 800-819(20 pp) | |
| 2027-01-08 | 金 | 25 | ミドルマーチ p. 820-836(17 pp) | |
| 2027-01-09 | 土 | 60 | ミドルマーチ p. 837-876(40 pp) | |
| 2027-01-10 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2027-01-11 | 月 | 30 | ミドルマーチ p. 877-880(4 pp); ピラネージ(オーディオブック) 0:00-0:30 | |
| 2027-01-12 | 火 | 30 | ピラネージ(オーディオブック) 0:30-1:08 | |
| 2027-01-13 | 水 | 20 | ピラネージ(オーディオブック) 1:08-1:33 | |
| 2027-01-14 | 木 | 30 | ピラネージ(オーディオブック) 1:33-2:10 | |
| 2027-01-15 | 金 | 25 | ピラネージ(オーディオブック) 2:10-2:41 | |
| 2027-01-16 | 土 | 60 | ピラネージ(オーディオブック) 2:41-3:56 | |
| 2027-01-17 | 日 | - | - | 追い上げ / 予備(45分利用可能) |
| 2027-01-18 | 月 | 30 | ピラネージ(オーディオブック) 3:56-4:34 | |
| 2027-01-19 | 火 | 30 | ピラネージ(オーディオブック) 4:34-5:11 | |
| 2027-01-20 | 水 | 20 | ピラネージ(オーディオブック) 5:11-5:36 | |
| 2027-01-21 | 木 | 30 | ピラネージ(オーディオブック) 5:36-6:14 | |
| 2027-01-22 | 金 | 21 | ピラネージ(オーディオブック) 6:14-6:40 | |
## 週次チェックポイント(日曜の夜までにどこにいるべきか)
- 2026-11-08: 日の名残り p. 148
- 2026-11-15: システム思考 p. 24
- 2026-11-22: システム思考 p. 153
- 2026-11-29: ミドルマーチ p. 54
- 2026-12-06: ミドルマーチ p. 224
- 2026-12-13: ミドルマーチ p. 355
- 2026-12-20: ミドルマーチ p. 485
- 2026-12-27: ミドルマーチ p. 615
- 2027-01-03: ミドルマーチ p. 746
- 2027-01-10: ミドルマーチ p. 876
- 2027-01-17: ピラネージ(オーディオブック) 3:56
## 遅れた場合
- 日の名残り: 1日逃すと約27ページの損失。予備日 2026-11-08 で取り戻すか、次の3つの読書日のそれぞれに10ページ追加します。いつでも `--from <日付> --at "日の名残り=<ページ>"` で再計画できます。
- システム思考: 1日逃すと約16ページの損失。予備日 2026-11-22、2026-11-29 で取り戻すか、次の3つの読書日のそれぞれに6ページ追加します。いつでも `--from <日付> --at "システム思考=<ページ>"` で再計画できます。
- ミドルマーチ: 1日逃すと約23ページの損失。予備日 2026-11-29、2026-12-06、2026-12-13 で取り戻すか、次の3つの読書日のそれぞれに8ページ追加します。いつでも `--from <日付> --at "ミドルマーチ=<ページ>"` で再計画できます。
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 = 少なくとも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(), "日付")]
# ---------------------------------------------------------------- モデル
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(), "開始日")
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 # オーディオ1分あたりのあなたの時間(分)
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 バッファ日以内です - 1日逃す余裕はありません" % (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%% を使用します - 1週間の悪い週でバッファに追いやられます" % 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 分)- 2つのセッションに分割" % (
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):
"""1日の停止位置を、近くにある場合(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
# ---------------------------------------------------------------- レンダリング
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 p. %d-%d (%d pp)" % (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, ("p. %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 += ["## 本", "", "| 本 | 残り | 推定時間 | 締め切り | 完了予定日 | 計画終了日 | 余裕日数 | 1日あたりの平均 |", "|---|---|---|---|---|---|---|---|"]
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 pp" % 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 pp" % 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: 1日逃すと約 %d ページの損失。%s で取り戻すか、次の3つの読書日のそれぞれに %d ページ追加します。いつでも `--from <日付> --at \"%s=<ページ>\"` で再計画できます。" % (
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)
# ---------------------------------------------------------------- セルフテスト
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: 1ページ/分で30ページ/日、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("even: 週に分散(約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("単語数 -> words_per_page でページ数に変換", 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("1日1冊につき1項目", 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(("PASS " if okk else "FAIL ") + 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 件のコメント