taskcheck 0.3.1__tar.gz → 0.3.2__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-0.3.1/taskcheck.egg-info → taskcheck-0.3.2}/PKG-INFO +11 -26
- {taskcheck-0.3.1 → taskcheck-0.3.2}/README.md +9 -25
- {taskcheck-0.3.1 → taskcheck-0.3.2}/pyproject.toml +2 -1
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/__main__.py +1 -7
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/install.py +0 -1
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/report.py +186 -1
- {taskcheck-0.3.1 → taskcheck-0.3.2/taskcheck.egg-info}/PKG-INFO +11 -26
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck.egg-info/SOURCES.txt +0 -1
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck.egg-info/requires.txt +1 -0
- taskcheck-0.3.1/taskcheck/sequential.py +0 -150
- {taskcheck-0.3.1 → taskcheck-0.3.2}/LICENSE +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/setup.cfg +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/common.py +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/ical.py +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck/parallel.py +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck.egg-info/dependency_links.txt +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck.egg-info/entry_points.txt +0 -0
- {taskcheck-0.3.1 → taskcheck-0.3.2}/taskcheck.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: appdirs>=1.4.4
|
|
9
9
|
Requires-Dist: icalendar>=6.0.1
|
|
10
|
+
Requires-Dist: random-unicode-emoji>=2.8
|
|
10
11
|
Requires-Dist: requests>=2.32.3
|
|
11
12
|
Requires-Dist: rich>=13.9.4
|
|
12
13
|
|
|
@@ -75,29 +76,9 @@ instance:
|
|
|
75
76
|
- `taskcheck -r today` will show the tasks planned for today
|
|
76
77
|
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
77
78
|
|
|
78
|
-
##
|
|
79
|
+
## Algorithm
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#### Sequential
|
|
83
|
-
|
|
84
|
-
The `sequential` algorithm schedules tasks one after the other. It is the simplest algorithm.
|
|
85
|
-
It looks at your tasks one by one, computes the time slots available for that task, and fills them
|
|
86
|
-
as soon as possible.
|
|
87
|
-
|
|
88
|
-
While it sounds logical, it has a problem.
|
|
89
|
-
While you're working on a task, its remaining estimated time is decreasing, and if your urgency
|
|
90
|
-
depends on the estimated time left, the urgency of the task will decrease as well.
|
|
91
|
-
This is especially problematic for long tasks: imagine if you block your whole week for a single
|
|
92
|
-
task and all the other small tasks, even if urgent, don't get scheduled. After a few days, the
|
|
93
|
-
urgency of the first long task should have decreased.
|
|
94
|
-
|
|
95
|
-
#### Parallel
|
|
96
|
-
|
|
97
|
-
The `parallel` algorithm tries to solve the problem of the `sequential` algorithm.
|
|
98
|
-
|
|
99
|
-
Instead of allocating all the available time for a task until the task is finished, the `parallel`
|
|
100
|
-
algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
81
|
+
The algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
101
82
|
urgency and restart the allocation procedure.
|
|
102
83
|
|
|
103
84
|
If after 1 hour the long task has decreased urgency, that will be noticed and the newer most urgent
|
|
@@ -106,8 +87,6 @@ task will get scheduled in its place.
|
|
|
106
87
|
The minimum time for a task is by default 2 hours, but you can change it by tuning the `min_block`
|
|
107
88
|
taskwarrior UDA.
|
|
108
89
|
|
|
109
|
-
The `parallel` algorithm is the default algorithm.
|
|
110
|
-
|
|
111
90
|
## Configuration
|
|
112
91
|
|
|
113
92
|
`taskcheck --install` allows you to create required and recommended configurations for
|
|
@@ -131,7 +110,6 @@ saturday = [[9, 12.30], ]
|
|
|
131
110
|
sunday = [[9, 12.30], ]
|
|
132
111
|
|
|
133
112
|
[scheduler]
|
|
134
|
-
algorithm = "parallel" # or "sequential", see above
|
|
135
113
|
days_ahead = 1000 # how far go with the schedule (lower values make the computation faster)
|
|
136
114
|
|
|
137
115
|
[calendars]
|
|
@@ -145,6 +123,13 @@ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone va
|
|
|
145
123
|
url = "https://www.officeholidays.com/ics-clean/italy/milan"
|
|
146
124
|
event_all_day_is_blocking = true
|
|
147
125
|
expiration = 720 # in hours (720 hours = 30 days)
|
|
126
|
+
|
|
127
|
+
[report]
|
|
128
|
+
additional_attributes = ["estimated", "due", "urgency"] # additional attributes to show in the report
|
|
129
|
+
# when these words are matched in the task description, the corresponding emoji is used
|
|
130
|
+
emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"}
|
|
131
|
+
include_unplanned = true # include unplanned tasks in the report in an ad-hoc section
|
|
132
|
+
additional_attributes_unplanned = ["due", "urgency"] # additional attributes to show in the report for unplanned tasks
|
|
148
133
|
```
|
|
149
134
|
|
|
150
135
|
## Tips and Tricks
|
|
@@ -63,29 +63,9 @@ instance:
|
|
|
63
63
|
- `taskcheck -r today` will show the tasks planned for today
|
|
64
64
|
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
65
65
|
|
|
66
|
-
##
|
|
66
|
+
## Algorithm
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#### Sequential
|
|
71
|
-
|
|
72
|
-
The `sequential` algorithm schedules tasks one after the other. It is the simplest algorithm.
|
|
73
|
-
It looks at your tasks one by one, computes the time slots available for that task, and fills them
|
|
74
|
-
as soon as possible.
|
|
75
|
-
|
|
76
|
-
While it sounds logical, it has a problem.
|
|
77
|
-
While you're working on a task, its remaining estimated time is decreasing, and if your urgency
|
|
78
|
-
depends on the estimated time left, the urgency of the task will decrease as well.
|
|
79
|
-
This is especially problematic for long tasks: imagine if you block your whole week for a single
|
|
80
|
-
task and all the other small tasks, even if urgent, don't get scheduled. After a few days, the
|
|
81
|
-
urgency of the first long task should have decreased.
|
|
82
|
-
|
|
83
|
-
#### Parallel
|
|
84
|
-
|
|
85
|
-
The `parallel` algorithm tries to solve the problem of the `sequential` algorithm.
|
|
86
|
-
|
|
87
|
-
Instead of allocating all the available time for a task until the task is finished, the `parallel`
|
|
88
|
-
algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
68
|
+
The algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
89
69
|
urgency and restart the allocation procedure.
|
|
90
70
|
|
|
91
71
|
If after 1 hour the long task has decreased urgency, that will be noticed and the newer most urgent
|
|
@@ -94,8 +74,6 @@ task will get scheduled in its place.
|
|
|
94
74
|
The minimum time for a task is by default 2 hours, but you can change it by tuning the `min_block`
|
|
95
75
|
taskwarrior UDA.
|
|
96
76
|
|
|
97
|
-
The `parallel` algorithm is the default algorithm.
|
|
98
|
-
|
|
99
77
|
## Configuration
|
|
100
78
|
|
|
101
79
|
`taskcheck --install` allows you to create required and recommended configurations for
|
|
@@ -119,7 +97,6 @@ saturday = [[9, 12.30], ]
|
|
|
119
97
|
sunday = [[9, 12.30], ]
|
|
120
98
|
|
|
121
99
|
[scheduler]
|
|
122
|
-
algorithm = "parallel" # or "sequential", see above
|
|
123
100
|
days_ahead = 1000 # how far go with the schedule (lower values make the computation faster)
|
|
124
101
|
|
|
125
102
|
[calendars]
|
|
@@ -133,6 +110,13 @@ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone va
|
|
|
133
110
|
url = "https://www.officeholidays.com/ics-clean/italy/milan"
|
|
134
111
|
event_all_day_is_blocking = true
|
|
135
112
|
expiration = 720 # in hours (720 hours = 30 days)
|
|
113
|
+
|
|
114
|
+
[report]
|
|
115
|
+
additional_attributes = ["estimated", "due", "urgency"] # additional attributes to show in the report
|
|
116
|
+
# when these words are matched in the task description, the corresponding emoji is used
|
|
117
|
+
emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"}
|
|
118
|
+
include_unplanned = true # include unplanned tasks in the report in an ad-hoc section
|
|
119
|
+
additional_attributes_unplanned = ["due", "urgency"] # additional attributes to show in the report for unplanned tasks
|
|
136
120
|
```
|
|
137
121
|
|
|
138
122
|
## Tips and Tricks
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "taskcheck"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Automatic scheduler for taskwarrior"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"appdirs>=1.4.4",
|
|
9
9
|
"icalendar>=6.0.1",
|
|
10
|
+
"random-unicode-emoji>=2.8",
|
|
10
11
|
"requests>=2.32.3",
|
|
11
12
|
"rich>=13.9.4",
|
|
12
13
|
]
|
|
@@ -48,13 +48,7 @@ def main():
|
|
|
48
48
|
return
|
|
49
49
|
|
|
50
50
|
config = load_config()
|
|
51
|
-
|
|
52
|
-
if algo == "parallel":
|
|
53
|
-
check_tasks_parallel(config, verbose=args.verbose)
|
|
54
|
-
elif algo == "sequential":
|
|
55
|
-
check_tasks_sequentially(config, verbose=args.verbose)
|
|
56
|
-
else:
|
|
57
|
-
raise ValueError(f"Unknown algorithm: {algo}")
|
|
51
|
+
check_tasks_parallel(config, verbose=args.verbose)
|
|
58
52
|
|
|
59
53
|
|
|
60
54
|
if __name__ == "__main__":
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import random
|
|
1
2
|
import sys
|
|
2
3
|
from datetime import timedelta
|
|
3
4
|
from datetime import datetime
|
|
@@ -12,6 +13,170 @@ from rich.table import Table
|
|
|
12
13
|
from rich.text import Text
|
|
13
14
|
from rich.panel import Panel
|
|
14
15
|
|
|
16
|
+
from random_unicode_emoji import random_emoji
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DEFAULT_EMOJI_KEYWORDS = {
|
|
20
|
+
"meet": ":busts_in_silhouette:",
|
|
21
|
+
"incontr": ":busts_in_silhouette:",
|
|
22
|
+
"rencontr": ":busts_in_silhouette:",
|
|
23
|
+
"reun": ":busts_in_silhouette:",
|
|
24
|
+
"treff": ":busts_in_silhouette:",
|
|
25
|
+
"review": ":mag_right:",
|
|
26
|
+
"revis": ":mag_right:",
|
|
27
|
+
"révis": ":mag_right:",
|
|
28
|
+
"überprüf": ":mag_right:",
|
|
29
|
+
"write": ":pencil2:",
|
|
30
|
+
"scriv": ":pencil2:",
|
|
31
|
+
"écri": ":pencil2:",
|
|
32
|
+
"escrib": ":pencil2:",
|
|
33
|
+
"schreib": ":pencil2:",
|
|
34
|
+
"read": ":books:",
|
|
35
|
+
"legg": ":books:",
|
|
36
|
+
"lis": ":books:",
|
|
37
|
+
"le": ":books:",
|
|
38
|
+
"les": ":books:",
|
|
39
|
+
"posta": ":email:",
|
|
40
|
+
"courri": ":email:",
|
|
41
|
+
"corre": ":email:",
|
|
42
|
+
"mail": ":email:",
|
|
43
|
+
"call": ":telephone_receiver:",
|
|
44
|
+
"chiam": ":telephone_receiver:",
|
|
45
|
+
"appel": ":telephone_receiver:",
|
|
46
|
+
"llam": ":telephone_receiver:",
|
|
47
|
+
"ruf": ":telephone_receiver:",
|
|
48
|
+
"présent": ":chart_with_upwards_trend:",
|
|
49
|
+
"present": ":chart_with_upwards_trend:",
|
|
50
|
+
"präsent": ":chart_with_upwards_trend:",
|
|
51
|
+
"learn": ":mortar_board:",
|
|
52
|
+
"impar": ":mortar_board:",
|
|
53
|
+
"appren": ":mortar_board:",
|
|
54
|
+
"aprend": ":mortar_board:",
|
|
55
|
+
"lern": ":mortar_board:",
|
|
56
|
+
"search": ":mag:",
|
|
57
|
+
"cerc": ":mag:",
|
|
58
|
+
"cherch": ":mag:",
|
|
59
|
+
"busc": ":mag:",
|
|
60
|
+
"such": ":mag:",
|
|
61
|
+
"idea": ":bulb:",
|
|
62
|
+
"idé": ":bulb:",
|
|
63
|
+
"ide": ":bulb:",
|
|
64
|
+
"break": ":coffee:",
|
|
65
|
+
"paus": ":coffee:",
|
|
66
|
+
"descans": ":coffee:",
|
|
67
|
+
"lunch": ":fork_and_knife:",
|
|
68
|
+
"pranz": ":fork_and_knife:",
|
|
69
|
+
"déjeun": ":fork_and_knife:",
|
|
70
|
+
"almorz": ":fork_and_knife:",
|
|
71
|
+
"mittag": ":fork_and_knife:",
|
|
72
|
+
"test": ":test_tube:",
|
|
73
|
+
"develop": ":hammer_and_wrench:",
|
|
74
|
+
"svilupp": ":hammer_and_wrench:",
|
|
75
|
+
"développ": ":hammer_and_wrench:",
|
|
76
|
+
"desarroll": ":hammer_and_wrench:",
|
|
77
|
+
"entwickl": ":hammer_and_wrench:",
|
|
78
|
+
"disegn": ":art:",
|
|
79
|
+
"diseñ": ":art:",
|
|
80
|
+
"design": ":art:",
|
|
81
|
+
"dafar": ":memo:",
|
|
82
|
+
"tarea": ":memo:",
|
|
83
|
+
"todo": ":memo:",
|
|
84
|
+
"bug": ":beetle:",
|
|
85
|
+
"käfer": ":beetle:",
|
|
86
|
+
"fix": ":wrench:",
|
|
87
|
+
"corregg": ":wrench:",
|
|
88
|
+
"répar": ":wrench:",
|
|
89
|
+
"beheb": ":wrench:",
|
|
90
|
+
"urgent": ":exclamation:",
|
|
91
|
+
"dringend": ":exclamation:",
|
|
92
|
+
"deadlin": ":hourglass_flowing_sand:",
|
|
93
|
+
"scadenz": ":hourglass_flowing_sand:",
|
|
94
|
+
"délais": ":hourglass_flowing_sand:",
|
|
95
|
+
"fechalim": ":hourglass_flowing_sand:",
|
|
96
|
+
"frist": ":hourglass_flowing_sand:",
|
|
97
|
+
"updat": ":arrows_counterclockwise:",
|
|
98
|
+
"aggiorn": ":arrows_counterclockwise:",
|
|
99
|
+
"mett": ":arrows_counterclockwise:",
|
|
100
|
+
"actualiz": ":arrows_counterclockwise:",
|
|
101
|
+
"aktualisier": ":arrows_counterclockwise:",
|
|
102
|
+
"clean": ":broom:",
|
|
103
|
+
"pul": ":broom:",
|
|
104
|
+
"nettoy": ":broom:",
|
|
105
|
+
"limpi": ":broom:",
|
|
106
|
+
"reinig": ":broom:",
|
|
107
|
+
"deploy": ":rocket:",
|
|
108
|
+
"rilasc": ":rocket:",
|
|
109
|
+
"déploy": ":rocket:",
|
|
110
|
+
"despleg": ":rocket:",
|
|
111
|
+
"bereitstell": ":rocket:",
|
|
112
|
+
"festegg": ":tada:",
|
|
113
|
+
"célébr": ":tada:",
|
|
114
|
+
"celebr": ":tada:",
|
|
115
|
+
"feier": ":tada:",
|
|
116
|
+
"research": ":microscope:",
|
|
117
|
+
"ricerc": ":microscope:",
|
|
118
|
+
"recherch": ":microscope:",
|
|
119
|
+
"investig": ":microscope:",
|
|
120
|
+
"forsch": ":microscope:",
|
|
121
|
+
"shop": ":shopping_cart:",
|
|
122
|
+
"achet": ":shopping_cart:",
|
|
123
|
+
"compr": ":shopping_cart:",
|
|
124
|
+
"einkauf": ":shopping_cart:",
|
|
125
|
+
"social": ":handshake:",
|
|
126
|
+
"sozial": ":handshake:",
|
|
127
|
+
"exercis": ":running_shoe:",
|
|
128
|
+
"eserciz": ":running_shoe:",
|
|
129
|
+
"exercic": ":running_shoe:",
|
|
130
|
+
"ejerc": ":running_shoe:",
|
|
131
|
+
"übun": ":running_shoe:",
|
|
132
|
+
"évènement": ":calendar:",
|
|
133
|
+
"event": ":calendar:",
|
|
134
|
+
"movie": ":clapper:",
|
|
135
|
+
"pelicul": ":clapper:",
|
|
136
|
+
"film": ":clapper:",
|
|
137
|
+
"music": ":musical_note:",
|
|
138
|
+
"musik": ":musical_note:",
|
|
139
|
+
"travel": ":airplane:",
|
|
140
|
+
"viagg": ":airplane:",
|
|
141
|
+
"voyag": ":airplane:",
|
|
142
|
+
"viaj": ":airplane:",
|
|
143
|
+
"reis": ":airplane:",
|
|
144
|
+
"home": ":house:",
|
|
145
|
+
"mais": ":house:",
|
|
146
|
+
"cas": ":house:",
|
|
147
|
+
"haus": ":house:",
|
|
148
|
+
"docu": ":notebook:",
|
|
149
|
+
"dokum": ":notebook:",
|
|
150
|
+
"backup": ":floppy_disk:",
|
|
151
|
+
"salvat": ":floppy_disk:",
|
|
152
|
+
"sauveg": ":floppy_disk:",
|
|
153
|
+
"copiaseg": ":floppy_disk:",
|
|
154
|
+
"sicher": ":floppy_disk:",
|
|
155
|
+
"debug": ":bug:",
|
|
156
|
+
"débug": ":bug:",
|
|
157
|
+
"note": ":spiral_notepad:",
|
|
158
|
+
"not": ":spiral_notepad:",
|
|
159
|
+
"focus": ":dart:",
|
|
160
|
+
"concentr": ":dart:",
|
|
161
|
+
"konzentrier": ":dart:",
|
|
162
|
+
"codic": ":computer:",
|
|
163
|
+
"cod": ":computer:",
|
|
164
|
+
"code": ":computer:",
|
|
165
|
+
"serveur": ":cloud:",
|
|
166
|
+
"servidor": ":cloud:",
|
|
167
|
+
"server": ":cloud:",
|
|
168
|
+
"client": ":briefcase:",
|
|
169
|
+
"priorid": ":bangbang:",
|
|
170
|
+
"priorit": ":bangbang:",
|
|
171
|
+
"star": ":star:",
|
|
172
|
+
"stell": ":star:",
|
|
173
|
+
"étoil": ":star:",
|
|
174
|
+
"estrell": ":star:",
|
|
175
|
+
"stern": ":star:",
|
|
176
|
+
"critic": ":fire:",
|
|
177
|
+
"kritisch": ":fire:",
|
|
178
|
+
}
|
|
179
|
+
|
|
15
180
|
|
|
16
181
|
def get_tasks(config, tasks, year, month, day):
|
|
17
182
|
regex = re.compile(rf"{year}-{month}-{day} - ([PDTHM0-9]+)")
|
|
@@ -172,7 +337,21 @@ def get_task_emoji(config, task):
|
|
|
172
337
|
for keyword in config.get("emoji_keywords", []):
|
|
173
338
|
if keyword in task["description"].lower():
|
|
174
339
|
return config["emoji_keywords"][keyword]
|
|
175
|
-
|
|
340
|
+
for keyword, emoji in DEFAULT_EMOJI_KEYWORDS.items():
|
|
341
|
+
if keyword in task["description"].lower():
|
|
342
|
+
return emoji
|
|
343
|
+
# sum the unicode numbers of each letter in the description and use it as seed to get a random unicode
|
|
344
|
+
# emoji
|
|
345
|
+
seed = sum(ord(char) for char in task["description"])
|
|
346
|
+
emoji = ""
|
|
347
|
+
# seed the next call to random.choice
|
|
348
|
+
random.seed(seed)
|
|
349
|
+
emoji = random_emoji()[0]
|
|
350
|
+
# some emoji coutn for more than one character and misbehave the tables of rich
|
|
351
|
+
if len(emoji) > 1:
|
|
352
|
+
while len(emoji) != 1:
|
|
353
|
+
emoji = random_emoji()[0]
|
|
354
|
+
return emoji
|
|
176
355
|
|
|
177
356
|
|
|
178
357
|
def display_unplanned_tasks(console, config, tasks):
|
|
@@ -197,6 +376,8 @@ def build_unplanned_tasks_table(config, tasks):
|
|
|
197
376
|
table.add_column("Task", style="dim", width=12)
|
|
198
377
|
table.add_column("Project", style="dim", width=12)
|
|
199
378
|
table.add_column("Description")
|
|
379
|
+
for attr in config.get("additional_attributes_unplanned", []):
|
|
380
|
+
table.add_column(attr.capitalize(), justify="right")
|
|
200
381
|
|
|
201
382
|
for task in tasks:
|
|
202
383
|
task_id = f"[bold green]#{task['id']}[/bold green]"
|
|
@@ -208,5 +389,9 @@ def build_unplanned_tasks_table(config, tasks):
|
|
|
208
389
|
f"{emoji} {task_id}",
|
|
209
390
|
project,
|
|
210
391
|
description,
|
|
392
|
+
*[
|
|
393
|
+
tostring(task.get(attr, ""))
|
|
394
|
+
for attr in config.get("additional_attributes_unplanned", [])
|
|
395
|
+
],
|
|
211
396
|
)
|
|
212
397
|
return table
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: appdirs>=1.4.4
|
|
9
9
|
Requires-Dist: icalendar>=6.0.1
|
|
10
|
+
Requires-Dist: random-unicode-emoji>=2.8
|
|
10
11
|
Requires-Dist: requests>=2.32.3
|
|
11
12
|
Requires-Dist: rich>=13.9.4
|
|
12
13
|
|
|
@@ -75,29 +76,9 @@ instance:
|
|
|
75
76
|
- `taskcheck -r today` will show the tasks planned for today
|
|
76
77
|
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
77
78
|
|
|
78
|
-
##
|
|
79
|
+
## Algorithm
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#### Sequential
|
|
83
|
-
|
|
84
|
-
The `sequential` algorithm schedules tasks one after the other. It is the simplest algorithm.
|
|
85
|
-
It looks at your tasks one by one, computes the time slots available for that task, and fills them
|
|
86
|
-
as soon as possible.
|
|
87
|
-
|
|
88
|
-
While it sounds logical, it has a problem.
|
|
89
|
-
While you're working on a task, its remaining estimated time is decreasing, and if your urgency
|
|
90
|
-
depends on the estimated time left, the urgency of the task will decrease as well.
|
|
91
|
-
This is especially problematic for long tasks: imagine if you block your whole week for a single
|
|
92
|
-
task and all the other small tasks, even if urgent, don't get scheduled. After a few days, the
|
|
93
|
-
urgency of the first long task should have decreased.
|
|
94
|
-
|
|
95
|
-
#### Parallel
|
|
96
|
-
|
|
97
|
-
The `parallel` algorithm tries to solve the problem of the `sequential` algorithm.
|
|
98
|
-
|
|
99
|
-
Instead of allocating all the available time for a task until the task is finished, the `parallel`
|
|
100
|
-
algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
81
|
+
The algorithm allocates a fixed amount of time for each task (e.g. 1 hour) and then recomputes the
|
|
101
82
|
urgency and restart the allocation procedure.
|
|
102
83
|
|
|
103
84
|
If after 1 hour the long task has decreased urgency, that will be noticed and the newer most urgent
|
|
@@ -106,8 +87,6 @@ task will get scheduled in its place.
|
|
|
106
87
|
The minimum time for a task is by default 2 hours, but you can change it by tuning the `min_block`
|
|
107
88
|
taskwarrior UDA.
|
|
108
89
|
|
|
109
|
-
The `parallel` algorithm is the default algorithm.
|
|
110
|
-
|
|
111
90
|
## Configuration
|
|
112
91
|
|
|
113
92
|
`taskcheck --install` allows you to create required and recommended configurations for
|
|
@@ -131,7 +110,6 @@ saturday = [[9, 12.30], ]
|
|
|
131
110
|
sunday = [[9, 12.30], ]
|
|
132
111
|
|
|
133
112
|
[scheduler]
|
|
134
|
-
algorithm = "parallel" # or "sequential", see above
|
|
135
113
|
days_ahead = 1000 # how far go with the schedule (lower values make the computation faster)
|
|
136
114
|
|
|
137
115
|
[calendars]
|
|
@@ -145,6 +123,13 @@ timezone = "Europe/Rome" # if set, force timezone for this calendar; timezone va
|
|
|
145
123
|
url = "https://www.officeholidays.com/ics-clean/italy/milan"
|
|
146
124
|
event_all_day_is_blocking = true
|
|
147
125
|
expiration = 720 # in hours (720 hours = 30 days)
|
|
126
|
+
|
|
127
|
+
[report]
|
|
128
|
+
additional_attributes = ["estimated", "due", "urgency"] # additional attributes to show in the report
|
|
129
|
+
# when these words are matched in the task description, the corresponding emoji is used
|
|
130
|
+
emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"}
|
|
131
|
+
include_unplanned = true # include unplanned tasks in the report in an ad-hoc section
|
|
132
|
+
additional_attributes_unplanned = ["due", "urgency"] # additional attributes to show in the report for unplanned tasks
|
|
148
133
|
```
|
|
149
134
|
|
|
150
135
|
## Tips and Tricks
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
2
|
-
from taskcheck.common import (
|
|
3
|
-
get_calendars,
|
|
4
|
-
get_long_range_time_map,
|
|
5
|
-
get_tasks,
|
|
6
|
-
mark_end_date,
|
|
7
|
-
pdth_to_hours,
|
|
8
|
-
hours_to_pdth,
|
|
9
|
-
AVOID_STATUS,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def schedule_task_on_day(
|
|
14
|
-
is_starting,
|
|
15
|
-
day_offset,
|
|
16
|
-
start_date,
|
|
17
|
-
end_date,
|
|
18
|
-
task_remaining_hours,
|
|
19
|
-
task_time_map,
|
|
20
|
-
today,
|
|
21
|
-
used_hours,
|
|
22
|
-
wait,
|
|
23
|
-
scheduling_note,
|
|
24
|
-
verbose=False,
|
|
25
|
-
):
|
|
26
|
-
# we can schedule task on this day
|
|
27
|
-
employable_hours = task_time_map[day_offset] - used_hours[day_offset]
|
|
28
|
-
# avoid using too small values
|
|
29
|
-
employable_hours = 0.01 if 0.01 > employable_hours > 0 else employable_hours
|
|
30
|
-
current_date = today + timedelta(days=day_offset)
|
|
31
|
-
if wait and current_date <= wait:
|
|
32
|
-
if verbose:
|
|
33
|
-
print(f"Skipping date {current_date} because of wait date {wait}")
|
|
34
|
-
return start_date, end_date, task_remaining_hours, is_starting, scheduling_note
|
|
35
|
-
|
|
36
|
-
if is_starting:
|
|
37
|
-
if verbose:
|
|
38
|
-
print(f"Starting task on {current_date}")
|
|
39
|
-
is_starting = False
|
|
40
|
-
start_date = current_date
|
|
41
|
-
|
|
42
|
-
# minimum value we admit is 0.01 (1 minute)
|
|
43
|
-
if task_remaining_hours <= employable_hours + 0.01:
|
|
44
|
-
# consume all the remaining task's hours
|
|
45
|
-
if scheduling_note != "":
|
|
46
|
-
scheduling_note += "\n"
|
|
47
|
-
scheduling_note += f"{current_date} - {hours_to_pdth(task_remaining_hours)}\n"
|
|
48
|
-
used_hours[day_offset] += task_remaining_hours
|
|
49
|
-
task_remaining_hours = 0
|
|
50
|
-
end_date = current_date
|
|
51
|
-
if verbose:
|
|
52
|
-
print(f"Task can be completed on {current_date}")
|
|
53
|
-
print(f"Used hours on {current_date}: {used_hours[day_offset]}")
|
|
54
|
-
else:
|
|
55
|
-
# consume all the available hours on this task
|
|
56
|
-
if scheduling_note != "":
|
|
57
|
-
scheduling_note += "\n"
|
|
58
|
-
scheduling_note += f"{current_date} - {hours_to_pdth(employable_hours)}\n"
|
|
59
|
-
task_remaining_hours -= employable_hours
|
|
60
|
-
used_hours[day_offset] += employable_hours
|
|
61
|
-
if verbose:
|
|
62
|
-
print(f"Working for {employable_hours} hours on task on {current_date}")
|
|
63
|
-
return start_date, end_date, task_remaining_hours, is_starting, scheduling_note
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# Check if tasks can be completed on time sequentially
|
|
67
|
-
def check_tasks_sequentially(config, verbose=False):
|
|
68
|
-
tasks = get_tasks()
|
|
69
|
-
time_maps = config["time_maps"]
|
|
70
|
-
today = datetime.today().date()
|
|
71
|
-
todo = [True if t["status"] not in AVOID_STATUS else False for t in tasks]
|
|
72
|
-
used_hours = [0] * (config["scheduler"]["days_ahead"] + 1)
|
|
73
|
-
calendars = get_calendars(config)
|
|
74
|
-
|
|
75
|
-
while any(todo):
|
|
76
|
-
for i, task in enumerate(tasks):
|
|
77
|
-
if not todo[i]:
|
|
78
|
-
# skipping tasks already completed
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
due_date = (
|
|
82
|
-
datetime.strptime(task["due"], "%Y%m%dT%H%M%SZ").date()
|
|
83
|
-
if "due" in task
|
|
84
|
-
else None
|
|
85
|
-
)
|
|
86
|
-
wait_date = (
|
|
87
|
-
datetime.strptime(task["wait"], "%Y%m%dT%H%M%SZ").date()
|
|
88
|
-
if "wait" in task
|
|
89
|
-
else None
|
|
90
|
-
)
|
|
91
|
-
estimated_hours = (
|
|
92
|
-
pdth_to_hours(task["estimated"]) if "estimated" in task else None
|
|
93
|
-
) # Remove trailing "PT" and "H"
|
|
94
|
-
time_map_names = (
|
|
95
|
-
task.get("time_map").split(",") if "time_map" in task else None
|
|
96
|
-
)
|
|
97
|
-
if estimated_hours is None or time_map_names is None:
|
|
98
|
-
todo[i] = False
|
|
99
|
-
if verbose:
|
|
100
|
-
print(
|
|
101
|
-
f"Task {task['id']} ('{task['description']}') has no estimated time or time map: {estimated_hours}, {time_map_names}"
|
|
102
|
-
)
|
|
103
|
-
continue
|
|
104
|
-
if verbose:
|
|
105
|
-
print(
|
|
106
|
-
f"Checking task {task['id']} ('{task['description']}') with estimated hours: {estimated_hours} and wait date: {wait_date}"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
task_remaining_hours = estimated_hours
|
|
110
|
-
task_time_map, today_used_hours = get_long_range_time_map(
|
|
111
|
-
time_maps, time_map_names, config["scheduler"]["days_ahead"], calendars
|
|
112
|
-
)
|
|
113
|
-
used_hours[0] += today_used_hours
|
|
114
|
-
|
|
115
|
-
# Simulate work day-by-day until task is complete or past due
|
|
116
|
-
is_starting = True
|
|
117
|
-
scheduling_note = ""
|
|
118
|
-
start_date = end_date = None
|
|
119
|
-
for offset in range(len(task_time_map)):
|
|
120
|
-
if task_time_map[offset] > used_hours[offset]:
|
|
121
|
-
(
|
|
122
|
-
start_date,
|
|
123
|
-
end_date,
|
|
124
|
-
task_remaining_hours,
|
|
125
|
-
is_starting,
|
|
126
|
-
scheduling_note,
|
|
127
|
-
) = schedule_task_on_day(
|
|
128
|
-
is_starting,
|
|
129
|
-
offset,
|
|
130
|
-
start_date,
|
|
131
|
-
end_date,
|
|
132
|
-
task_remaining_hours,
|
|
133
|
-
task_time_map,
|
|
134
|
-
today,
|
|
135
|
-
used_hours,
|
|
136
|
-
wait_date,
|
|
137
|
-
scheduling_note,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
if end_date is not None:
|
|
141
|
-
todo[i] = False
|
|
142
|
-
mark_end_date(
|
|
143
|
-
due_date,
|
|
144
|
-
end_date,
|
|
145
|
-
start_date,
|
|
146
|
-
scheduling_note,
|
|
147
|
-
task["id"],
|
|
148
|
-
task["description"],
|
|
149
|
-
)
|
|
150
|
-
break
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|