taskcheck 0.3.3.post3__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taskcheck-1.0.0/PKG-INFO +216 -0
- taskcheck-1.0.0/README.md +204 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/pyproject.toml +21 -1
- taskcheck-1.0.0/taskcheck/__main__.py +144 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck/common.py +25 -6
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck/ical.py +2 -2
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck/install.py +7 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck/parallel.py +178 -23
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck/report.py +30 -12
- taskcheck-1.0.0/taskcheck.egg-info/PKG-INFO +216 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck.egg-info/SOURCES.txt +7 -1
- taskcheck-1.0.0/tests/test_common.py +140 -0
- taskcheck-1.0.0/tests/test_ical.py +171 -0
- taskcheck-1.0.0/tests/test_install.py +78 -0
- taskcheck-1.0.0/tests/test_main.py +586 -0
- taskcheck-1.0.0/tests/test_parallel.py +522 -0
- taskcheck-1.0.0/tests/test_report.py +203 -0
- taskcheck-0.3.3.post3/PKG-INFO +0 -154
- taskcheck-0.3.3.post3/README.md +0 -142
- taskcheck-0.3.3.post3/taskcheck/__main__.py +0 -54
- taskcheck-0.3.3.post3/taskcheck.egg-info/PKG-INFO +0 -154
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/LICENSE +0 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/setup.cfg +0 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck.egg-info/dependency_links.txt +0 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck.egg-info/entry_points.txt +0 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck.egg-info/requires.txt +0 -0
- {taskcheck-0.3.3.post3 → taskcheck-1.0.0}/taskcheck.egg-info/top_level.txt +0 -0
taskcheck-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taskcheck
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Automatic scheduler for taskwarrior
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: appdirs>=1.4.4
|
|
8
|
+
Requires-Dist: icalendar>=6.0.1
|
|
9
|
+
Requires-Dist: random-unicode-emoji>=2.8
|
|
10
|
+
Requires-Dist: requests>=2.32.3
|
|
11
|
+
Requires-Dist: rich>=13.9.4
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
888888888888 88 ,ad8888ba, 88 88
|
|
17
|
+
88 88 d8"' `"8b 88 88
|
|
18
|
+
88 88 d8' 88 88
|
|
19
|
+
88 ,adPPYYba, ,adPPYba, 88 ,d8 88 88,dPPYba, ,adPPYba, ,adPPYba, 88 ,d8
|
|
20
|
+
88 "" `Y8 I8[ "" 88 ,a8" 88 88P' "8a a8P_____88 a8" "" 88 ,a8"
|
|
21
|
+
88 ,adPPPPP88 `"Y8ba, 8888[ Y8, 88 88 8PP""""""" 8b 8888[
|
|
22
|
+
88 88, ,88 aa ]8I 88`"Yba, Y8a. .a8P 88 88 "8b, ,aa "8a, ,aa 88`"Yba,
|
|
23
|
+
88 `"8bbdP"Y8 `"YbbdP"' 88 `Y8a `"Y8888Y"' 88 88 `"Ybbd8"' `"Ybbd8"' 88 `Y8a
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
|
|
29
|
+
|
|
30
|
+
This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
|
|
31
|
+
estimated time, and calendar events, finding an optimal time to work on each task and match all your
|
|
32
|
+
deadlines.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- [x] **Use arbitrarily complex time maps for working hours**
|
|
37
|
+
- [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
|
|
38
|
+
- [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
|
|
39
|
+
- [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
|
|
40
|
+
- [x] Custom urgency weighting for scheduling (via CLI or config)
|
|
41
|
+
- [x] **Auto-fix scheduling to mach due dates**
|
|
42
|
+
- [x] Force update of iCal calendars, bypassing cache
|
|
43
|
+
- [x] Simple, customizable reports for planned and unplanned tasks
|
|
44
|
+
- [x] Emoji and attribute customization in reports
|
|
45
|
+
- [ ] Use Google API to access calendars
|
|
46
|
+
- [ ] Export tasks to iCal calendar and API calendars
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
1. `pipx install taskcheck`
|
|
51
|
+
2. `taskcheck --install`
|
|
52
|
+
|
|
53
|
+
## How does it work
|
|
54
|
+
|
|
55
|
+
This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
|
|
56
|
+
It considers their estimated time to schedule all tasks starting from the most urgent one.
|
|
57
|
+
|
|
58
|
+
#### UDAs
|
|
59
|
+
|
|
60
|
+
Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
|
|
61
|
+
the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
|
|
62
|
+
that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
|
|
63
|
+
The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
|
|
64
|
+
file of taskcheck. For instance:
|
|
65
|
+
|
|
66
|
+
```toml
|
|
67
|
+
[time_maps]
|
|
68
|
+
# get an error)
|
|
69
|
+
[time_maps.work]
|
|
70
|
+
monday = [[9, 12.30], [14, 17]]
|
|
71
|
+
tuesday = [[9, 12.30], [14, 17]]
|
|
72
|
+
# ...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### They say it's an "AI"
|
|
76
|
+
|
|
77
|
+
Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
|
|
78
|
+
It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
|
|
79
|
+
date of completion and the `scheduled` attribute with the date in which the task is expected to
|
|
80
|
+
start.
|
|
81
|
+
|
|
82
|
+
It will also print a red line for every task whose `completion_date` is after its `due_date`.
|
|
83
|
+
|
|
84
|
+
In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
|
|
85
|
+
of your working day.
|
|
86
|
+
|
|
87
|
+
#### Reports
|
|
88
|
+
|
|
89
|
+
You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
|
|
90
|
+
how much time you have to work on which task in which day. For
|
|
91
|
+
instance:
|
|
92
|
+
|
|
93
|
+
- `taskcheck -r today` will show the tasks planned for today
|
|
94
|
+
- `taskcheck -r 1w` will show the tasks planned for the next week
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
`taskcheck --install` allows you to create required and recommended configurations for
|
|
99
|
+
Taskwarrior. It will also generate a default configuration file for taskcheck.
|
|
100
|
+
|
|
101
|
+
Below is an example of a taskcheck configuration file, with all relevant options:
|
|
102
|
+
|
|
103
|
+
```toml
|
|
104
|
+
[time_maps]
|
|
105
|
+
# Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
|
|
106
|
+
[time_maps.work]
|
|
107
|
+
monday = [[9, 12.30], [14, 17]]
|
|
108
|
+
tuesday = [[9, 12.30], [14, 17]]
|
|
109
|
+
wednesday = [[9, 12.30], [14, 17]]
|
|
110
|
+
thursday = [[9, 12.30], [14, 17]]
|
|
111
|
+
friday = [[9, 12.30], [14, 17]]
|
|
112
|
+
|
|
113
|
+
[time_maps.weekend]
|
|
114
|
+
saturday = [[9, 12.30]]
|
|
115
|
+
sunday = [[9, 12.30]]
|
|
116
|
+
|
|
117
|
+
[scheduler]
|
|
118
|
+
days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
|
|
119
|
+
weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
|
|
120
|
+
# if weight_urgency is set to 0, only due urgency is considered
|
|
121
|
+
# by default, this factor is automatically reduced if some task cannot be scheduled in time,
|
|
122
|
+
# leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
|
|
123
|
+
|
|
124
|
+
[calendars]
|
|
125
|
+
# iCal calendars can be used to block your time and make the scheduling more precise
|
|
126
|
+
[calendars.1]
|
|
127
|
+
url = "https://your/url/to/calendar.ics"
|
|
128
|
+
expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
|
|
129
|
+
timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
|
|
130
|
+
|
|
131
|
+
[calendars.holidays]
|
|
132
|
+
url = "https://www.officeholidays.com/ics-clean/italy/milan"
|
|
133
|
+
event_all_day_is_blocking = true
|
|
134
|
+
expiration = 720 # In hours (720 hours = 30 days)
|
|
135
|
+
|
|
136
|
+
[report]
|
|
137
|
+
include_unplanned = true
|
|
138
|
+
additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
|
|
139
|
+
additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
|
|
140
|
+
emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Configuration Options
|
|
144
|
+
|
|
145
|
+
- **[scheduler]**
|
|
146
|
+
- `days_ahead`: How many days ahead to schedule tasks.
|
|
147
|
+
- `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
|
|
148
|
+
- **[calendars]**
|
|
149
|
+
- `url`: iCal URL to block time.
|
|
150
|
+
- `expiration`: Cache expiration in hours.
|
|
151
|
+
- `timezone`: (Optional) Force a timezone for this calendar.
|
|
152
|
+
- `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
|
|
153
|
+
- **[report]**
|
|
154
|
+
- `include_unplanned`: Show unplanned tasks in a separate section.
|
|
155
|
+
- `additional_attributes`: Extra columns to show in the report.
|
|
156
|
+
- `additional_attributes_unplanned`: Extra columns for unplanned tasks.
|
|
157
|
+
- `emoji_keywords`: Map keywords in task descriptions to emoji.
|
|
158
|
+
|
|
159
|
+
## Algorithm
|
|
160
|
+
|
|
161
|
+
The algorithm simulates what happens if you work on a task for a certain time on a given day.
|
|
162
|
+
|
|
163
|
+
For each day X starting from today, it sorts the tasks by decreasing urgency.
|
|
164
|
+
It start from the most urgent tasks that can be allocated on day X depending on the task's
|
|
165
|
+
`time_map` and on your calendars. It allocates a few number of hours to the task,
|
|
166
|
+
then recomputes the urgencies exactly as Taskwarrior would do
|
|
167
|
+
if it was running on day X. Having recomputed the urgencies, it restarts.
|
|
168
|
+
|
|
169
|
+
If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
|
|
170
|
+
task will get scheduled in its place.
|
|
171
|
+
|
|
172
|
+
For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
|
|
173
|
+
skip all the available slots until 12 pm.
|
|
174
|
+
|
|
175
|
+
The maximum time that is allocated at each attempt is by default 2 hours
|
|
176
|
+
(or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
|
|
177
|
+
|
|
178
|
+
After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
|
|
179
|
+
`weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
|
|
180
|
+
are scheduled before their due dates or the `weight_urgency` factor reaches 0.
|
|
181
|
+
|
|
182
|
+
## Tips and Tricks
|
|
183
|
+
|
|
184
|
+
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
|
|
185
|
+
- You can see tasks that you can execute now with the `task ready` report.
|
|
186
|
+
|
|
187
|
+
## CLI Options
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
-v, --verbose Increase output verbosity
|
|
191
|
+
-i, --install Install taskcheck configuration
|
|
192
|
+
-r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
|
|
193
|
+
-s, --schedule Perform the scheduling algorithm and update tasks
|
|
194
|
+
-f, --force-update Force update of all iCal calendars, ignoring cache expiration
|
|
195
|
+
--taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
|
|
196
|
+
--urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
|
|
197
|
+
--dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
|
|
198
|
+
--no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Examples
|
|
202
|
+
|
|
203
|
+
- `taskcheck --schedule`
|
|
204
|
+
Run the scheduler and update your Taskwarrior tasks.
|
|
205
|
+
- `taskcheck --schedule --dry-run`
|
|
206
|
+
Preview the schedule without modifying your database.
|
|
207
|
+
- `taskcheck --schedule --urgency-weight 0.5`
|
|
208
|
+
Use a custom urgency weighting for this run.
|
|
209
|
+
- `taskcheck --schedule --no-auto-adjust-urgency`
|
|
210
|
+
Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
|
|
211
|
+
- `taskcheck --report today`
|
|
212
|
+
Show the schedule for today.
|
|
213
|
+
- `taskcheck --report 1w`
|
|
214
|
+
Show the schedule for the next week.
|
|
215
|
+
- `taskcheck --force-update`
|
|
216
|
+
Force refresh of all iCal calendars, ignoring cache.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
```
|
|
3
|
+
|
|
4
|
+
888888888888 88 ,ad8888ba, 88 88
|
|
5
|
+
88 88 d8"' `"8b 88 88
|
|
6
|
+
88 88 d8' 88 88
|
|
7
|
+
88 ,adPPYYba, ,adPPYba, 88 ,d8 88 88,dPPYba, ,adPPYba, ,adPPYba, 88 ,d8
|
|
8
|
+
88 "" `Y8 I8[ "" 88 ,a8" 88 88P' "8a a8P_____88 a8" "" 88 ,a8"
|
|
9
|
+
88 ,adPPPPP88 `"Y8ba, 8888[ Y8, 88 88 8PP""""""" 8b 8888[
|
|
10
|
+
88 88, ,88 aa ]8I 88`"Yba, Y8a. .a8P 88 88 "8b, ,aa "8a, ,aa 88`"Yba,
|
|
11
|
+
88 `"8bbdP"Y8 `"YbbdP"' 88 `Y8a `"Y8888Y"' 88 88 `"Ybbd8"' `"Ybbd8"' 88 `Y8a
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
|
|
17
|
+
|
|
18
|
+
This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
|
|
19
|
+
estimated time, and calendar events, finding an optimal time to work on each task and match all your
|
|
20
|
+
deadlines.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- [x] **Use arbitrarily complex time maps for working hours**
|
|
25
|
+
- [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
|
|
26
|
+
- [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
|
|
27
|
+
- [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
|
|
28
|
+
- [x] Custom urgency weighting for scheduling (via CLI or config)
|
|
29
|
+
- [x] **Auto-fix scheduling to mach due dates**
|
|
30
|
+
- [x] Force update of iCal calendars, bypassing cache
|
|
31
|
+
- [x] Simple, customizable reports for planned and unplanned tasks
|
|
32
|
+
- [x] Emoji and attribute customization in reports
|
|
33
|
+
- [ ] Use Google API to access calendars
|
|
34
|
+
- [ ] Export tasks to iCal calendar and API calendars
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
1. `pipx install taskcheck`
|
|
39
|
+
2. `taskcheck --install`
|
|
40
|
+
|
|
41
|
+
## How does it work
|
|
42
|
+
|
|
43
|
+
This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
|
|
44
|
+
It considers their estimated time to schedule all tasks starting from the most urgent one.
|
|
45
|
+
|
|
46
|
+
#### UDAs
|
|
47
|
+
|
|
48
|
+
Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
|
|
49
|
+
the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
|
|
50
|
+
that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
|
|
51
|
+
The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
|
|
52
|
+
file of taskcheck. For instance:
|
|
53
|
+
|
|
54
|
+
```toml
|
|
55
|
+
[time_maps]
|
|
56
|
+
# get an error)
|
|
57
|
+
[time_maps.work]
|
|
58
|
+
monday = [[9, 12.30], [14, 17]]
|
|
59
|
+
tuesday = [[9, 12.30], [14, 17]]
|
|
60
|
+
# ...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### They say it's an "AI"
|
|
64
|
+
|
|
65
|
+
Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
|
|
66
|
+
It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
|
|
67
|
+
date of completion and the `scheduled` attribute with the date in which the task is expected to
|
|
68
|
+
start.
|
|
69
|
+
|
|
70
|
+
It will also print a red line for every task whose `completion_date` is after its `due_date`.
|
|
71
|
+
|
|
72
|
+
In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
|
|
73
|
+
of your working day.
|
|
74
|
+
|
|
75
|
+
#### Reports
|
|
76
|
+
|
|
77
|
+
You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
|
|
78
|
+
how much time you have to work on which task in which day. For
|
|
79
|
+
instance:
|
|
80
|
+
|
|
81
|
+
- `taskcheck -r today` will show the tasks planned for today
|
|
82
|
+
- `taskcheck -r 1w` will show the tasks planned for the next week
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
`taskcheck --install` allows you to create required and recommended configurations for
|
|
87
|
+
Taskwarrior. It will also generate a default configuration file for taskcheck.
|
|
88
|
+
|
|
89
|
+
Below is an example of a taskcheck configuration file, with all relevant options:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
[time_maps]
|
|
93
|
+
# Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
|
|
94
|
+
[time_maps.work]
|
|
95
|
+
monday = [[9, 12.30], [14, 17]]
|
|
96
|
+
tuesday = [[9, 12.30], [14, 17]]
|
|
97
|
+
wednesday = [[9, 12.30], [14, 17]]
|
|
98
|
+
thursday = [[9, 12.30], [14, 17]]
|
|
99
|
+
friday = [[9, 12.30], [14, 17]]
|
|
100
|
+
|
|
101
|
+
[time_maps.weekend]
|
|
102
|
+
saturday = [[9, 12.30]]
|
|
103
|
+
sunday = [[9, 12.30]]
|
|
104
|
+
|
|
105
|
+
[scheduler]
|
|
106
|
+
days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
|
|
107
|
+
weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
|
|
108
|
+
# if weight_urgency is set to 0, only due urgency is considered
|
|
109
|
+
# by default, this factor is automatically reduced if some task cannot be scheduled in time,
|
|
110
|
+
# leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
|
|
111
|
+
|
|
112
|
+
[calendars]
|
|
113
|
+
# iCal calendars can be used to block your time and make the scheduling more precise
|
|
114
|
+
[calendars.1]
|
|
115
|
+
url = "https://your/url/to/calendar.ics"
|
|
116
|
+
expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
|
|
117
|
+
timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
|
|
118
|
+
|
|
119
|
+
[calendars.holidays]
|
|
120
|
+
url = "https://www.officeholidays.com/ics-clean/italy/milan"
|
|
121
|
+
event_all_day_is_blocking = true
|
|
122
|
+
expiration = 720 # In hours (720 hours = 30 days)
|
|
123
|
+
|
|
124
|
+
[report]
|
|
125
|
+
include_unplanned = true
|
|
126
|
+
additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
|
|
127
|
+
additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
|
|
128
|
+
emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Configuration Options
|
|
132
|
+
|
|
133
|
+
- **[scheduler]**
|
|
134
|
+
- `days_ahead`: How many days ahead to schedule tasks.
|
|
135
|
+
- `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
|
|
136
|
+
- **[calendars]**
|
|
137
|
+
- `url`: iCal URL to block time.
|
|
138
|
+
- `expiration`: Cache expiration in hours.
|
|
139
|
+
- `timezone`: (Optional) Force a timezone for this calendar.
|
|
140
|
+
- `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
|
|
141
|
+
- **[report]**
|
|
142
|
+
- `include_unplanned`: Show unplanned tasks in a separate section.
|
|
143
|
+
- `additional_attributes`: Extra columns to show in the report.
|
|
144
|
+
- `additional_attributes_unplanned`: Extra columns for unplanned tasks.
|
|
145
|
+
- `emoji_keywords`: Map keywords in task descriptions to emoji.
|
|
146
|
+
|
|
147
|
+
## Algorithm
|
|
148
|
+
|
|
149
|
+
The algorithm simulates what happens if you work on a task for a certain time on a given day.
|
|
150
|
+
|
|
151
|
+
For each day X starting from today, it sorts the tasks by decreasing urgency.
|
|
152
|
+
It start from the most urgent tasks that can be allocated on day X depending on the task's
|
|
153
|
+
`time_map` and on your calendars. It allocates a few number of hours to the task,
|
|
154
|
+
then recomputes the urgencies exactly as Taskwarrior would do
|
|
155
|
+
if it was running on day X. Having recomputed the urgencies, it restarts.
|
|
156
|
+
|
|
157
|
+
If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
|
|
158
|
+
task will get scheduled in its place.
|
|
159
|
+
|
|
160
|
+
For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
|
|
161
|
+
skip all the available slots until 12 pm.
|
|
162
|
+
|
|
163
|
+
The maximum time that is allocated at each attempt is by default 2 hours
|
|
164
|
+
(or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
|
|
165
|
+
|
|
166
|
+
After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
|
|
167
|
+
`weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
|
|
168
|
+
are scheduled before their due dates or the `weight_urgency` factor reaches 0.
|
|
169
|
+
|
|
170
|
+
## Tips and Tricks
|
|
171
|
+
|
|
172
|
+
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
|
|
173
|
+
- You can see tasks that you can execute now with the `task ready` report.
|
|
174
|
+
|
|
175
|
+
## CLI Options
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
-v, --verbose Increase output verbosity
|
|
179
|
+
-i, --install Install taskcheck configuration
|
|
180
|
+
-r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
|
|
181
|
+
-s, --schedule Perform the scheduling algorithm and update tasks
|
|
182
|
+
-f, --force-update Force update of all iCal calendars, ignoring cache expiration
|
|
183
|
+
--taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
|
|
184
|
+
--urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
|
|
185
|
+
--dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
|
|
186
|
+
--no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Examples
|
|
190
|
+
|
|
191
|
+
- `taskcheck --schedule`
|
|
192
|
+
Run the scheduler and update your Taskwarrior tasks.
|
|
193
|
+
- `taskcheck --schedule --dry-run`
|
|
194
|
+
Preview the schedule without modifying your database.
|
|
195
|
+
- `taskcheck --schedule --urgency-weight 0.5`
|
|
196
|
+
Use a custom urgency weighting for this run.
|
|
197
|
+
- `taskcheck --schedule --no-auto-adjust-urgency`
|
|
198
|
+
Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
|
|
199
|
+
- `taskcheck --report today`
|
|
200
|
+
Show the schedule for today.
|
|
201
|
+
- `taskcheck --report 1w`
|
|
202
|
+
Show the schedule for the next week.
|
|
203
|
+
- `taskcheck --force-update`
|
|
204
|
+
Force refresh of all iCal calendars, ignoring cache.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "taskcheck"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "Automatic scheduler for taskwarrior"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -15,5 +15,25 @@ dependencies = [
|
|
|
15
15
|
[project.scripts]
|
|
16
16
|
taskcheck = "taskcheck.__main__:main"
|
|
17
17
|
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=45", "wheel"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
18
22
|
[tool.setuptools]
|
|
19
23
|
license-files = []
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["."]
|
|
27
|
+
include = ["taskcheck*"]
|
|
28
|
+
|
|
29
|
+
[tool.uv]
|
|
30
|
+
package = true
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"freezegun>=1.5.2",
|
|
35
|
+
"pytest>=8.3.5",
|
|
36
|
+
"pytest-cov>=6.1.1",
|
|
37
|
+
"pytest-mock>=3.14.1",
|
|
38
|
+
"responses>=0.25.7",
|
|
39
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
import argparse
|
|
3
|
+
|
|
4
|
+
from taskcheck.parallel import check_tasks_parallel
|
|
5
|
+
from taskcheck.common import config_dir
|
|
6
|
+
|
|
7
|
+
arg_parser = argparse.ArgumentParser()
|
|
8
|
+
arg_parser.add_argument(
|
|
9
|
+
"-v", "--verbose", action="store_true", help="increase output verbosity."
|
|
10
|
+
)
|
|
11
|
+
arg_parser.add_argument(
|
|
12
|
+
"-i",
|
|
13
|
+
"--install",
|
|
14
|
+
action="store_true",
|
|
15
|
+
help="install the UDAs, required settings, and default config file.",
|
|
16
|
+
)
|
|
17
|
+
arg_parser.add_argument(
|
|
18
|
+
"-r",
|
|
19
|
+
"--report",
|
|
20
|
+
action="store",
|
|
21
|
+
help="generate a report of the tasks based on the scheduling; can be any Taskwarrior datetime specification (e.g. today, tomorrow, eom, som, 1st, 2nd, etc.). It is considered as `by`, meaning that the report will be generated for all the days until the specified date and including it.",
|
|
22
|
+
)
|
|
23
|
+
arg_parser.add_argument(
|
|
24
|
+
"-s",
|
|
25
|
+
"--schedule",
|
|
26
|
+
action="store_true",
|
|
27
|
+
help="perform the scheduling algorithm, giving a schedule and a scheduling UDA and alerting for not completable tasks",
|
|
28
|
+
)
|
|
29
|
+
arg_parser.add_argument(
|
|
30
|
+
"-f",
|
|
31
|
+
"--force-update",
|
|
32
|
+
action="store_true",
|
|
33
|
+
help="force update of all ical calendars by ignoring cache expiration",
|
|
34
|
+
)
|
|
35
|
+
arg_parser.add_argument(
|
|
36
|
+
"--taskrc",
|
|
37
|
+
action="store",
|
|
38
|
+
help="set custom TASKRC directory for debugging purposes",
|
|
39
|
+
)
|
|
40
|
+
arg_parser.add_argument(
|
|
41
|
+
"--urgency-weight",
|
|
42
|
+
type=float,
|
|
43
|
+
help="weight for urgency in scheduling (0.0 to 1.0), overrides config value. Not aaplie to due urgency, i.e. when 0, only due urgency is considered.",
|
|
44
|
+
)
|
|
45
|
+
arg_parser.add_argument(
|
|
46
|
+
"--dry-run",
|
|
47
|
+
action="store_true",
|
|
48
|
+
help="perform scheduling without modifying the Taskwarrior database, useful for testing",
|
|
49
|
+
)
|
|
50
|
+
arg_parser.add_argument(
|
|
51
|
+
"--no-auto-adjust-urgency",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="disable automatically reduction of urgency weight when tasks cannot be completed on time",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Load working hours and exceptions from TOML file
|
|
58
|
+
def load_config():
|
|
59
|
+
"""
|
|
60
|
+
Loads the configuration from the TOML file located at config_dir / "taskcheck.toml".
|
|
61
|
+
Returns a dictionary with the config structure, e.g.:
|
|
62
|
+
{
|
|
63
|
+
"time_maps": {
|
|
64
|
+
"work": {
|
|
65
|
+
"monday": [[9, 12.30], [14, 17]],
|
|
66
|
+
"tuesday": [[9, 12.30], [14, 17]],
|
|
67
|
+
...
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"scheduler": {
|
|
71
|
+
"days_ahead": 365,
|
|
72
|
+
"weight_urgency": 1.0,
|
|
73
|
+
...
|
|
74
|
+
},
|
|
75
|
+
"report": {
|
|
76
|
+
"include_unplanned": true,
|
|
77
|
+
"additional_attributes": [],
|
|
78
|
+
"additional_attributes_unplanned": [],
|
|
79
|
+
"emoji_keywords": {},
|
|
80
|
+
...
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
"""
|
|
84
|
+
with open(config_dir / "taskcheck.toml", "rb") as f:
|
|
85
|
+
config = tomllib.load(f)
|
|
86
|
+
return config
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
args = arg_parser.parse_args()
|
|
91
|
+
|
|
92
|
+
# Load data and check tasks
|
|
93
|
+
print_help = True
|
|
94
|
+
result = None
|
|
95
|
+
if args.install:
|
|
96
|
+
from taskcheck.install import install
|
|
97
|
+
|
|
98
|
+
install()
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if args.schedule:
|
|
102
|
+
config = load_config()
|
|
103
|
+
check_tasks_kwargs = dict(
|
|
104
|
+
verbose=args.verbose,
|
|
105
|
+
force_update=args.force_update,
|
|
106
|
+
taskrc=args.taskrc,
|
|
107
|
+
urgency_weight_override=args.urgency_weight,
|
|
108
|
+
dry_run=args.dry_run,
|
|
109
|
+
)
|
|
110
|
+
# Only add auto_adjust_urgency if it is present and a real bool (not a mock)
|
|
111
|
+
if hasattr(args, "no_auto_adjust_urgency") and isinstance(
|
|
112
|
+
args.no_auto_adjust_urgency, bool
|
|
113
|
+
):
|
|
114
|
+
check_tasks_kwargs["auto_adjust_urgency"] = not args.auto_adjust_urgency
|
|
115
|
+
result = check_tasks_parallel(
|
|
116
|
+
config,
|
|
117
|
+
**check_tasks_kwargs,
|
|
118
|
+
)
|
|
119
|
+
print_help = False
|
|
120
|
+
|
|
121
|
+
if args.report:
|
|
122
|
+
from taskcheck.report import generate_report
|
|
123
|
+
|
|
124
|
+
config = load_config()
|
|
125
|
+
scheduling_results = None
|
|
126
|
+
if args.schedule and args.dry_run:
|
|
127
|
+
# If we just did a dry-run schedule, use those results
|
|
128
|
+
scheduling_results = result
|
|
129
|
+
generate_report(
|
|
130
|
+
config,
|
|
131
|
+
args.report,
|
|
132
|
+
args.verbose,
|
|
133
|
+
force_update=args.force_update,
|
|
134
|
+
taskrc=args.taskrc,
|
|
135
|
+
scheduling_results=scheduling_results,
|
|
136
|
+
)
|
|
137
|
+
print_help = False
|
|
138
|
+
|
|
139
|
+
if print_help:
|
|
140
|
+
arg_parser.print_help()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -17,9 +17,19 @@ AVOID_STATUS = ["completed", "deleted", "recurring"]
|
|
|
17
17
|
long_range_time_map = {}
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def get_task_env(taskrc=None):
|
|
21
|
+
import os
|
|
22
|
+
env = os.environ.copy()
|
|
23
|
+
if taskrc:
|
|
24
|
+
env['TASKRC'] = taskrc
|
|
25
|
+
env['TASKDATA'] = taskrc
|
|
26
|
+
return env
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
# Get tasks from Taskwarrior and sort by urgency
|
|
21
|
-
def get_tasks():
|
|
22
|
-
|
|
30
|
+
def get_tasks(taskrc=None):
|
|
31
|
+
env = get_task_env(taskrc)
|
|
32
|
+
result = subprocess.run(["task", "export"], capture_output=True, text=True, env=env)
|
|
23
33
|
tasks = json.loads(result.stdout)
|
|
24
34
|
return sorted(
|
|
25
35
|
(task for task in tasks if "estimated" in task),
|
|
@@ -99,12 +109,18 @@ def pdth_to_hours(duration_str):
|
|
|
99
109
|
days, duration_str = duration_str.split("D")
|
|
100
110
|
days = int(days)
|
|
101
111
|
if "H" in duration_str:
|
|
102
|
-
|
|
112
|
+
if "T" in duration_str:
|
|
113
|
+
hours = int(duration_str.split("T")[1].split("H")[0])
|
|
114
|
+
else:
|
|
115
|
+
hours = int(duration_str.split("H")[0])
|
|
103
116
|
if "M" in duration_str:
|
|
104
117
|
if "H" in duration_str:
|
|
105
118
|
minutes = int(duration_str.split("H")[1].split("M")[0])
|
|
106
119
|
else:
|
|
107
|
-
|
|
120
|
+
if "T" in duration_str:
|
|
121
|
+
minutes = int(duration_str.split("T")[1].split("M")[0])
|
|
122
|
+
else:
|
|
123
|
+
minutes = int(duration_str.split("M")[0])
|
|
108
124
|
return days * 24 + hours + minutes / 60
|
|
109
125
|
|
|
110
126
|
|
|
@@ -169,9 +185,10 @@ def get_long_range_time_map(
|
|
|
169
185
|
|
|
170
186
|
|
|
171
187
|
def mark_end_date(
|
|
172
|
-
due_date, end_date, start_date, scheduling_note, id, description=None
|
|
188
|
+
due_date, end_date, start_date, scheduling_note, id, description=None, taskrc=None
|
|
173
189
|
):
|
|
174
190
|
start_end_fields = [f"scheduled:{start_date}", f"completion_date:{end_date}"]
|
|
191
|
+
env = get_task_env(taskrc)
|
|
175
192
|
|
|
176
193
|
subprocess.run(
|
|
177
194
|
[
|
|
@@ -183,6 +200,7 @@ def mark_end_date(
|
|
|
183
200
|
],
|
|
184
201
|
stdout=subprocess.DEVNULL,
|
|
185
202
|
stderr=subprocess.DEVNULL,
|
|
203
|
+
env=env,
|
|
186
204
|
)
|
|
187
205
|
if due_date is not None and end_date > due_date:
|
|
188
206
|
# print in bold red using ANSI escape codes
|
|
@@ -190,7 +208,7 @@ def mark_end_date(
|
|
|
190
208
|
print(f"\033[1;31mTask {id} {description} may not be completed on time\033[0m")
|
|
191
209
|
|
|
192
210
|
|
|
193
|
-
def get_calendars(config, verbose=False):
|
|
211
|
+
def get_calendars(config, verbose=False, force_update=False):
|
|
194
212
|
calendars = []
|
|
195
213
|
for calname in config["calendars"]:
|
|
196
214
|
calendar = config["calendars"][calname]
|
|
@@ -201,6 +219,7 @@ def get_calendars(config, verbose=False):
|
|
|
201
219
|
expiration=calendar["expiration"],
|
|
202
220
|
verbose=verbose,
|
|
203
221
|
tz_name=calendar.get("timezone"),
|
|
222
|
+
force_update=force_update,
|
|
204
223
|
)
|
|
205
224
|
calendar.sort(key=lambda e: e["start"])
|
|
206
225
|
calendars.append(calendar)
|