토론

독서 계획 및 페이지 속도 플래너

Wikiprompt, 무료 프롬프트 백과사전에서

Fatih Kadir Akın

2026년 11월 3일

독서 계획 및 페이지 속도 플래너 독서 클럽, 강좌, 또는 시험 마감일을 맞추기 위해 페이지 수, 단어 수, 읽기 속도, 그리고 가용 시간을 활용하여 일일 독서 일정을 계획하는 종합적인 AI 스킬입니다. 이 스킬에는 전체 시스템 프롬프트, 워크플로우, 참조 가이드, 템플릿, 예시, 그리고 Python 플래너 스크립트가 포함되어 있습니다.

프롬프트 내용저장

🌐
--- name: reading-plan-and-page-pace-planner description: 책을 하루하루 계획해서 북클럽, 강의 또는 시험 날짜 전에 모든 책을 끝낼 수 있게 도와줍니다 - 페이지 수나 단어 수(또는 오디오북 길이), 독서 속도, 평일마다 가용한 시간, 여행과 바쁜 날짜를 기준으로 합니다. 페이지를 고르게 분배하고, 각 마감일 전에 버퍼 일수를 유지하며, 주간 보충일을 두고, 챕터 끝에서 멈추며, 마감일이 도달 불가능할 때 경고하고 현재 위치에서 다시 계획합니다. 표준 라이브러리 파이썬 플래너를 포함합니다. --- # 독서 계획 및 페이지 속도 플래너 북클럽 전날 밤에 200페이지를 벼락치기하는 독자들을 돕습니다. 사람들은 대략적인 시간(평일 30분, 주말에는 더 많음)을 알지만, 2주 후 마감인 245페이지 소설, 어려운 업무용 책, 같은 달에 읽어야 할 880페이지 고전이 페이지 수로 얼마나 되는지는 모릅니다. 책, 독서 속도, 자유 시간을 구체적인 일일 계획("화: p. 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` - 11월-12월: 목요일 북클럽용 소설, 이미 p. 23에서 시작한 업무용 책, 고전 클럽용 Middlemarch, 마감일이 없는 오디오북, 일요일 보충일, 주말 여행 및 특이한 이틀 - `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 = 잘못된 입력. 셸이 없는 경우: 필요 분 = 페이지 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 wpm, 하루 20-60분, 일요일 보충용 자유, 버퍼 2일)에서 *The Remains of the Day*는 11월 19일 북클럽을 위해 11월 12일에 챕터 끝에서 완료되고, *Thinking in Systems*(어려움, 계수 1.3, p. 23부터)는 12월 1일 그룹을 위해 11월 27일에 완료됩니다. *Middlemarch*(316,000단어, 약 22시간)는 12월 17일 고전 클럽까지 끝낼 수 없습니다: 약 452페이지 부족, 예상 완료 1월 11일(RP01, 높음). 해결책: 42일의 독서일 각각에 약 +17분, 또는 지금 다른 두 권과 병렬로 시작. 120분의 조용한 토요일은 두 세션으로 표시되고(RP03), Piranesi 오디오북(1.25배속으로 6시간 40분)은 Middlemarch 이후에 이어집니다. ## 규칙 - 사람들이 실제로 가진 시간으로 계획하고, 모든 모임 전에 최소한 하루의 버퍼를 유지하세요. - 조용히 날짜를 과도하게 채우지 마세요: 맞지 않으면 어떤 마감일이 몇 페이지 부족으로 실패하는지, 무엇이 해결할지 말하세요. - 판본의 페이지 수를 사용하세요; 단어 수만 알면 페이지가 추정치라고 말하세요. - 계획에서 보충일은 비워두세요 - 뒤처졌을 때를 위한 것이지, 더 읽기 위한 것이 아닙니다. - 독서는 즐거워야 합니다: 매일 2시간 세션을 제안하기 전에 건너뛰기나 형식 전환을 제안하세요. - 독자가 뒤처지거나 앞서 있을 때마다 현재 페이지에서 다시 계획하세요; 오래된 일정에 "따라잡도록" 강요하지 마세요. FILE:references/reading-speed-and-estimates.md # 독서 속도 및 추정 ## 속도 측정(10분) 1. 실제 책(또는 비슷한 책)을 선택하고, 챕터 시작에서 시작하여 10분 타이머를 설정하고 평소처럼 읽으세요. 2. 세 개의 전체 줄의 단어 수를 세고, 3으로 나누면 줄당 단어 수; 전체 페이지의 줄 수를 세면 페이지당 줄 수. 3. 페이지당 단어 수 = 줄당 단어 수 x 페이지당 줄 수; 분당 단어 수 = 읽은 페이지 x 페이지당 단어 수 / 10. 또는 몇 페이지의 시간을 재고 분당 단어 수 대신 `"pages_per_hour"`를 사용하세요. ## 일반적인 값 | 독자 / 자료 | 분당 단어 수 | |---|---| | 주의 깊은 읽기, 어려운 논픽션, 제2언어 | 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+ 필기하는 교과서, 시, 제2언어 읽기. ## 페이지 수 vs 단어 수 - 독자가 가진 판본의 페이지 수를 사용하세요; 같은 책의 판본은 30%까지 다를 수 있습니다. - `"pages"`와 `"words"`가 모두 있으면 플래너는 해당 판본의 페이지당 단어 수를 도출합니다(Middlemarch 316,000 / 880 = 359). - 단어만 있으면 페이지는 추정치입니다(단어 / 페이지당 단어 수); 계획에서 그렇게 말하세요. ## 오디오북 - `"audio_minutes"`는 런타임, `"speed"`는 재생 속도: 필요 시간 = 런타임 / 속도(1.25배속에서 400분 = 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. 하루 놓침: 다음 보충일에 그날의 페이지를 읽거나, 다음 3일 각각에 3분의 1을 추가하세요. 2. 이틀 이상 뒤처짐: 오래된 계획을 쫓는 대신 `--from <today> --at "<Title>=<current page>"`로 오늘부터 다시 계획하세요. 3. 그래도 제시간에 안 됨(RP01): 하나 선택 - 긴 책을 지금 병렬로 시작, 며칠에 10-20분 추가, 통근에 오디오북 사용, 표시된 섹션 훑어보기(서문, 부록, 주석), 또는 중간쯤 도착하기로 결정하고 그룹에 말하기. ## 계획이 너무 빠듯한 신호 - RP04: 구간에서 시간의 90% 이상 사용; 어떤 질병이나 늦은 주도 계획을 깨뜨립니다. - RP02: 책이 버퍼 일수를 사용해야만 맞습니다. - "2 세션"으로 표시된 많은 날(RP03): 아침과 저녁에 의도적으로 읽기를 분산하는 것을 고려하세요. ## 북클럽 팁 - 그룹이 전체 책이 아닌 특정 페이지나 챕터를 논의할 경우 그 부분을 책의 `pages`로 사용하세요. - 긴 고전의 경우, 모임이 전반부를 다루는지 클럽에 물어보고 두 개의 마감일을 계획하세요. - 책에 계획의 주간 체크포인트가 있는 메모지를 붙이세요. FILE:templates/reading-plan.md # 독서 계획: <이름> (<시작>부터 <마지막 마감일>까지) **속도:** <wpm> 단어/분, 30분 세션에서 약 <페이지> 페이지 **시간:** <요일별 분>; 보충일: <요일>; 버퍼: 각 모임 전 <n>일 **판정:** <모든 마감일 충족 | BOOK을 DATE까지 끝낼 수 없음 - 옵션 참조> ## 책 | 책 | 남은 페이지 | 필요 시간 | 모임 / 마감일 | 완료 날짜 | 계획 완료 | |---|---|---|---|---|---| | <제목> | <pp> | <h:mm> | <날짜 - 이벤트> | <날짜> | <날짜> | ## 주별 ### <월요일 날짜> 주 - 월: <책 p. a-b> - 화: <...> - 일: 보충 / 자유 **일요일 밤 체크포인트:** <책 p. X> ## 마감일이 맞지 않는 경우 - 부족: <날짜>까지 <책>의 <페이지> 페이지 - 옵션: <독서일에 +N분 | 지금 병렬로 시작 | 통근용 오디오북 | x-y장 훑어보기 | 중간쯤 도착> - 선택: <...> ## 뒤처졌을 때 - 하루 놓침? <보충일> 사용 또는 다음 3일에 <n>페이지 추가. - 이틀 이상 뒤처짐: 오늘부터 다시 계획(`--from <날짜> --at "<제목>=<페이지>"`). 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 pp | 4:31 h | 2026-11-19 (목요일 북클럽) | 2026-11-17 | 2026-11-12 | 7 | 27 pp | | Thinking in Systems | 196 pp | 4:57 h | 2026-12-01 (팀 독서 그룹) | 2026-11-29 | 2026-11-27 | 4 | 16 pp | | Middlemarch | 880 pp | 21:57 h | 2026-12-17 (고전 클럽) | 2026-12-15 | 2027-01-11 | -25 | 23 pp | | Piranesi (오디오북) | 6:40 오디오 | 5:20 h | - | - | 2027-01-22 | - | 0:36 | ## 발견 사항 - **높음 RP01** Middlemarch는 2026-12-17(고전 클럽)까지 끝낼 수 없음: 현재 속도로 약 452페이지 부족; 예상 완료 2027-01-11. 옵션: 그때까지 42일의 독서일 각각에 +17분, 더 빨리 읽기, 약 452페이지 훑어보기 또는 건너뛰기, 또는 더 일찍 시작 - **낮음 RP03** 60분 이상 계획된 1일(예: 2026-12-05: 120분) - 두 세션으로 분할 - **정보 RP06** Piranesi(오디오북)는 오디오북: 계획은 1.25배속 청취 시간을 계산합니다; 통근과 집안일로 충당 가능 ## 일별 | 날짜 | 요일 | 분 | 읽을 내용 | 메모 | |---|---|---|---|---| | 2026-11-03 | 화 | 30 | The Remains of the Day p. 1-27 (27 pp) | | | 2026-11-04 | 수 | 19 | The Remains of the Day p. 28-44 (17 pp) | | | 2026-11-05 | 목 | 30 | The Remains of the Day p. 45-71 (27 pp) | | | 2026-11-06 | 금 | 25 | The Remains of the Day p. 72-94 (23 pp) | | | 2026-11-07 | 토 | 60 | The Remains of the Day p. 95-148 (54 pp) | | | 2026-11-08 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-11-09 | 월 | 30 | The Remains of the Day p. 149-175 (27 pp) | | | 2026-11-10 | 화 | 30 | The Remains of the Day p. 176-202 (27 pp) | | | 2026-11-11 | 수 | 20 | The Remains of the Day p. 203-221 (19 pp) | | | 2026-11-12 | 목 | 30 | The Remains of the Day p. 222-245 (24 pp); Thinking in Systems p. 23-24 (2 pp) | | | 2026-11-13 | 금 | - | - | 주말 여행 | | 2026-11-14 | 토 | - | - | 주말 여행 | | 2026-11-15 | 일 | - | - | 주말 여행 | | 2026-11-16 | 월 | 30 | Thinking in Systems p. 25-44 (20 pp) | | | 2026-11-17 | 화 | 30 | Thinking in Systems p. 45-64 (20 pp) | The Remains of the Day 완료 날짜 | | 2026-11-18 | 수 | 20 | Thinking in Systems p. 65-77 (13 pp) | | | 2026-11-19 | 목 | 30 | Thinking in Systems p. 78-97 (20 pp) | 마감일 The Remains of the Day - 목요일 북클럽 | | 2026-11-20 | 금 | 25 | Thinking in Systems p. 98-113 (16 pp) | | | 2026-11-21 | 토 | 60 | Thinking in Systems p. 114-153 (40 pp) | | | 2026-11-22 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-11-23 | 월 | 30 | Thinking in Systems p. 154-172 (19 pp) | | | 2026-11-24 | 화 | 30 | Thinking in Systems p. 173-192 (20 pp) | | | 2026-11-25 | 수 | 20 | Thinking in Systems p. 193-205 (13 pp) | | | 2026-11-26 | 목 | 15 | Thinking in Systems p. 206-215 (10 pp) | 늦은 교대 | | 2026-11-27 | 금 | 25 | Thinking in Systems p. 216-218 (3 pp); Middlemarch p. 1-14 (14 pp) | | | 2026-11-28 | 토 | 60 | Middlemarch p. 15-54 (40 pp) | | | 2026-11-29 | 일 | - | - | 보충 / 여유 (45분 가능); Thinking in Systems 완료 날짜 | | 2026-11-30 | 월 | 30 | Middlemarch p. 55-74 (20 pp) | | | 2026-12-01 | 화 | 30 | Middlemarch p. 75-94 (20 pp) | 마감일 Thinking in Systems - 팀 독서 그룹 | | 2026-12-02 | 수 | 20 | Middlemarch p. 95-107 (13 pp) | | | 2026-12-03 | 목 | 30 | Middlemarch p. 108-128 (21 pp) | | | 2026-12-04 | 금 | 25 | Middlemarch p. 129-144 (16 pp) | | | 2026-12-05 | 토 | 120 | Middlemarch p. 145-224 (80 pp) | 조용한 토요일; 2 세션 | | 2026-12-06 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-12-07 | 월 | 30 | Middlemarch p. 225-244 (20 pp) | | | 2026-12-08 | 화 | 30 | Middlemarch p. 245-265 (21 pp) | | | 2026-12-09 | 수 | 20 | Middlemarch p. 266-278 (13 pp) | | | 2026-12-10 | 목 | 30 | Middlemarch p. 279-298 (20 pp) | | | 2026-12-11 | 금 | 25 | Middlemarch p. 299-315 (17 pp) | | | 2026-12-12 | 토 | 60 | Middlemarch p. 316-355 (40 pp) | | | 2026-12-13 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-12-14 | 월 | 30 | Middlemarch p. 356-375 (20 pp) | | | 2026-12-15 | 화 | 30 | Middlemarch p. 376-395 (20 pp) | Middlemarch 완료 날짜 | | 2026-12-16 | 수 | 20 | Middlemarch p. 396-408 (13 pp) | | | 2026-12-17 | 목 | 30 | Middlemarch p. 409-428 (20 pp) | 마감일 Middlemarch - 고전 클럽 | | 2026-12-18 | 금 | 25 | Middlemarch p. 429-445 (17 pp) | | | 2026-12-19 | 토 | 60 | Middlemarch p. 446-485 (40 pp) | | | 2026-12-20 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-12-21 | 월 | 30 | Middlemarch p. 486-505 (20 pp) | | | 2026-12-22 | 화 | 30 | Middlemarch p. 506-525 (20 pp) | | | 2026-12-23 | 수 | 20 | Middlemarch p. 526-539 (14 pp) | | | 2026-12-24 | 목 | 30 | Middlemarch p. 540-559 (20 pp) | | | 2026-12-25 | 금 | 25 | Middlemarch p. 560-575 (16 pp) | | | 2026-12-26 | 토 | 60 | Middlemarch p. 576-615 (40 pp) | | | 2026-12-27 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2026-12-28 | 월 | 30 | Middlemarch p. 616-635 (20 pp) | | | 2026-12-29 | 화 | 30 | Middlemarch p. 636-656 (21 pp) | | | 2026-12-30 | 수 | 20 | Middlemarch p. 657-669 (13 pp) | | | 2026-12-31 | 목 | 30 | Middlemarch p. 670-689 (20 pp) | | | 2027-01-01 | 금 | 25 | Middlemarch p. 690-706 (17 pp) | | | 2027-01-02 | 토 | 60 | Middlemarch p. 707-746 (40 pp) | | | 2027-01-03 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2027-01-04 | 월 | 30 | Middlemarch p. 747-766 (20 pp) | | | 2027-01-05 | 화 | 30 | Middlemarch p. 767-786 (20 pp) | | | 2027-01-06 | 수 | 20 | Middlemarch p. 787-799 (13 pp) | | | 2027-01-07 | 목 | 30 | Middlemarch p. 800-819 (20 pp) | | | 2027-01-08 | 금 | 25 | Middlemarch p. 820-836 (17 pp) | | | 2027-01-09 | 토 | 60 | Middlemarch p. 837-876 (40 pp) | | | 2027-01-10 | 일 | - | - | 보충 / 여유 (45분 가능) | | 2027-01-11 | 월 | 30 | Middlemarch p. 877-880 (4 pp); 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 p. 148 - 2026-11-15: Thinking in Systems p. 24 - 2026-11-22: Thinking in Systems p. 153 - 2026-11-29: Middlemarch p. 54 - 2026-12-06: Middlemarch p. 224 - 2026-12-13: Middlemarch p. 355 - 2026-12-20: Middlemarch p. 485 - 2026-12-27: Middlemarch p. 615 - 2027-01-03: Middlemarch p. 746 - 2027-01-10: Middlemarch p. 876 - 2027-01-17: Piranesi (오디오북) 3:56 ## 뒤처졌을 때 - The Remains of the Day: 하루 놓치면 약 27페이지 손해. 여유일(들) 2026-11-08에 보충하거나, 다음 3일의 독서일 각각에 10페이지 추가. 언제든 `--from <날짜> --at "The Remains of the Day=<페이지>"`로 재계획. - Thinking in Systems: 하루 놓치면 약 16페이지 손해. 여유일(들) 2026-11-22, 2026-11-29에 보충하거나, 다음 3일의 독서일 각각에 6페이지 추가. 언제든 `--from <날짜> --at "Thinking in Systems=<페이지>"`로 재계획. - Middlemarch: 하루 놓치면 약 23페이지 손해. 여유일(들) 2026-11-29, 2026-12-06, 2026-12-13에 보충하거나, 다음 3일의 독서일 각각에 8페이지 추가. 언제든 `--from <날짜> --at "Middlemarch=<페이지>"`로 재계획. 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(), "날짜")] # ---------------------------------------------------------------- 모델 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 # 오디오 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일 버퍼 안 - 놓친 하루를 위한 여유 없음" % (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 # ---------------------------------------------------------------- 렌더링 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일 동안 %d시간 %02d분." % ( 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 pp" % round(left)) if b["kind"] == "pages" else hm(left) + " 오디오", hm(b["need"]) + " h", "%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: 하루 놓치면 약 %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("책당 하루 하나의 항목", 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())

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 productivity와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:productivity| prompts.chat| reading-plan| book-club

토론