taskcheck 0.2.0__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -19,7 +19,8 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
19
19
  - [x] Use arbitrarily complex time maps
20
20
  - [x] Use ical to block time from scheduling (e.g. for meetings, vacations, etc.)
21
21
  - [x] Implement scheduling algorithm for parallely working on multiple tasks
22
- - [ ] Use Google/Microsoft/Apple API to access calendars
22
+ - [ ] Use Google API to access calendars
23
+ - [ ] Export tasks to iCal calendar and API calendars
23
24
 
24
25
  ## Install
25
26
 
@@ -31,29 +32,33 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
31
32
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
32
33
  It considers their estimated time to schedule all tasks starting from the most urgent one.
33
34
 
34
- You will need to add the `estimated` and `time_map` UDAs to your tasks. The `estimated` attribute is
35
+ Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
35
36
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
36
37
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
37
38
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
38
- file of taskcheck.
39
+ file of taskcheck. For instance:
39
40
 
40
- `taskcheck` will modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
41
+ ```toml
42
+ ```toml
43
+ [time_maps]
44
+ # get an error)
45
+ [time_maps.work]
46
+ monday = [[9, 12.30], [14, 17]]
47
+ tuesday = [[9, 12.30], [14, 17]]
48
+ # ...
49
+ ```
50
+
51
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
52
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
41
53
  date of completion and the `scheduled` attribute with the date in which the task is expected to
42
54
  start.
43
55
 
44
56
  It will also print a red line for every task whose `completion_date` is after its `due_date`.
45
57
 
46
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
47
-
48
- You can see tasks that you can execute now with the `task ready` report.
49
-
50
- You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
51
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
52
-
53
58
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
54
59
  of your working day.
55
60
 
56
- ### Algorithms
61
+ ## Algorithms
57
62
 
58
63
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
59
64
 
@@ -125,6 +130,13 @@ event_all_day_is_blocking = true
125
130
  expiration = 720 # in hours (720 hours = 30 days)
126
131
  ```
127
132
 
133
+ ## Tips and Tricks
134
+
135
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
136
+ - You can see tasks that you can execute now with the `task ready` report.
137
+ - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
138
+ tomorrow)` will show you the tasks that include a scheduling for tomorrow.
139
+
128
140
  ## CLI Options
129
141
 
130
142
  ```
@@ -8,7 +8,8 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
8
8
  - [x] Use arbitrarily complex time maps
9
9
  - [x] Use ical to block time from scheduling (e.g. for meetings, vacations, etc.)
10
10
  - [x] Implement scheduling algorithm for parallely working on multiple tasks
11
- - [ ] Use Google/Microsoft/Apple API to access calendars
11
+ - [ ] Use Google API to access calendars
12
+ - [ ] Export tasks to iCal calendar and API calendars
12
13
 
13
14
  ## Install
14
15
 
@@ -20,29 +21,33 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
20
21
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
21
22
  It considers their estimated time to schedule all tasks starting from the most urgent one.
22
23
 
23
- You will need to add the `estimated` and `time_map` UDAs to your tasks. The `estimated` attribute is
24
+ Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
24
25
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
25
26
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
26
27
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
27
- file of taskcheck.
28
+ file of taskcheck. For instance:
28
29
 
29
- `taskcheck` will modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
30
+ ```toml
31
+ ```toml
32
+ [time_maps]
33
+ # get an error)
34
+ [time_maps.work]
35
+ monday = [[9, 12.30], [14, 17]]
36
+ tuesday = [[9, 12.30], [14, 17]]
37
+ # ...
38
+ ```
39
+
40
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
41
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
30
42
  date of completion and the `scheduled` attribute with the date in which the task is expected to
31
43
  start.
32
44
 
33
45
  It will also print a red line for every task whose `completion_date` is after its `due_date`.
34
46
 
35
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
36
-
37
- You can see tasks that you can execute now with the `task ready` report.
38
-
39
- You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
40
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
41
-
42
47
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
43
48
  of your working day.
44
49
 
45
- ### Algorithms
50
+ ## Algorithms
46
51
 
47
52
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
48
53
 
@@ -114,6 +119,13 @@ event_all_day_is_blocking = true
114
119
  expiration = 720 # in hours (720 hours = 30 days)
115
120
  ```
116
121
 
122
+ ## Tips and Tricks
123
+
124
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
125
+ - You can see tasks that you can execute now with the `task ready` report.
126
+ - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
127
+ tomorrow)` will show you the tasks that include a scheduling for tomorrow.
128
+
117
129
  ## CLI Options
118
130
 
119
131
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "taskcheck"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Automatic scheduler for taskwarrior"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -88,26 +88,35 @@ def install():
88
88
  ),
89
89
  ("journal.info", "0"),
90
90
  ("urgency.inherit", "1"),
91
+ ("urgency.blocked.coefficient", "0"),
92
+ ("urgency.blocking.coefficient", "0"),
93
+ ("urgency.waiting.coefficient", "0"),
94
+ ("urgency.scheduled.coefficient", "0"),
91
95
  ]
92
96
 
93
97
  answer = input(
94
- """Required configuration for taskcheck includes:
98
+ """
99
+ _________________________________________________________________________________________
100
+ Required configuration for taskcheck includes:
95
101
  - 3 UDA fields
96
102
  - turning off confirmation for recurring tasks
97
103
 
98
- Do you want to continue? [y/N]"""
104
+ Do you want to continue? [y/N] """
99
105
  )
100
106
  if answer.lower() not in ["y", "yes"]:
101
107
  return
102
108
 
103
109
  answer = input(
104
- """Optional configuration for taskcheck includes:
110
+ """
111
+ _________________________________________________________________________________________
112
+ Optional configuration for taskcheck includes:
105
113
  - urgency coefficients for estimated time (up to 36 hour duration)
106
114
  - better `ready` report columns
107
115
  - turning off printing journal logs in the task details
108
- - turning on urgency inheritance from dependant tasks
116
+ - turning on urgency inheritance from dependant tasks and disabling blocked and blocking urgency coefficients (as recommended by Taskwarrior documentation)
117
+ - turning off urgency for waiting and scheduled tasks (taskcheck already take them into account)
109
118
 
110
- Do you want to apply optional configurations? [y/N]"""
119
+ Do you want to apply optional configurations? [y/N] """
111
120
  )
112
121
  if answer.lower() not in ["y", "yes"]:
113
122
  configs = required_configs
@@ -123,7 +132,9 @@ Do you want to apply optional configurations? [y/N]"""
123
132
  print(f"Failed to apply configurations: {e}")
124
133
 
125
134
  answer = input(
126
- """Do you want to create a default config file for taskcheck? [y/N]"""
135
+ """
136
+ _________________________________________________________________________________________
137
+ Do you want to create a default config file for taskcheck? [y/N] """
127
138
  )
128
139
  if answer.lower() not in ["y", "yes"]:
129
140
  return
@@ -1,5 +1,6 @@
1
1
  import subprocess
2
2
  import re
3
+ from dataclasses import dataclass
3
4
 
4
5
  from datetime import datetime, timedelta
5
6
  from taskcheck.common import (
@@ -13,6 +14,13 @@ from taskcheck.common import (
13
14
  )
14
15
 
15
16
 
17
+ @dataclass
18
+ class UrgencyCoefficients:
19
+ estimated: dict
20
+ inherit: bool
21
+ active: float
22
+
23
+
16
24
  def get_urgency_coefficients():
17
25
  """
18
26
  Retrieves urgency coefficients from Taskwarrior configurations.
@@ -21,23 +29,31 @@ def get_urgency_coefficients():
21
29
  """
22
30
  result = subprocess.run(["task", "_show"], capture_output=True, text=True)
23
31
  inherit_urgency = False
32
+ active_task_coefficient = 0
24
33
  coefficients = {}
25
34
  pattern1 = re.compile(r"^urgency\.uda\.estimated\.(.+)\.coefficient=(.+)$")
26
35
  pattern2 = re.compile(r"^urgency\.inherit=(.+)$")
36
+ pattern3 = re.compile(r"^urgency\.active.coefficient=(.+)$")
27
37
  for line in result.stdout.splitlines():
28
38
  match = pattern1.match(line)
29
39
  if match:
30
40
  estimated_value = match.group(1)
31
41
  coefficient = float(match.group(2))
32
42
  coefficients[estimated_value] = coefficient
43
+
33
44
  match = pattern2.match(line)
34
45
  if match:
35
46
  inherit_urgency = match.group(1) == "1"
36
47
 
37
- return coefficients, inherit_urgency
48
+ match = pattern3.match(line)
49
+ if match:
50
+ active_coefficient = float(match.group(1))
51
+ active_task_coefficient = active_coefficient
52
+
53
+ return UrgencyCoefficients(coefficients, inherit_urgency, active_task_coefficient)
38
54
 
39
55
 
40
- def compute_estimated_urgency(remaining_hours, coefficients):
56
+ def compute_estimated_urgency(remaining_hours, urgency_coefficients):
41
57
  """
42
58
  Computes the estimated urgency for the given remaining hours using the coefficients.
43
59
  """
@@ -45,10 +61,10 @@ def compute_estimated_urgency(remaining_hours, coefficients):
45
61
  pdth_value = hours_to_pdth(remaining_hours)
46
62
  # Find the closest match (e.g., if '2h' is not available, use '1h' or '3h')
47
63
  closest_match = min(
48
- coefficients.keys(),
64
+ urgency_coefficients.estimated.keys(),
49
65
  key=lambda x: abs(int(pdth_value[1]) - int(pdth_to_hours(x[1]))),
50
66
  )
51
- coefficient = coefficients[closest_match]
67
+ coefficient = urgency_coefficients.estimated[closest_match]
52
68
  # Compute the urgency
53
69
  estimated_urgency = coefficient * remaining_hours
54
70
  return estimated_urgency
@@ -60,7 +76,7 @@ def check_tasks_parallel(config, verbose=False):
60
76
  days_ahead = config["scheduler"]["days_ahead"]
61
77
  calendars = get_calendars(config)
62
78
  today = datetime.today().date()
63
- urgency_coefficients, inherit_urgency = get_urgency_coefficients()
79
+ urgency_coefficients = get_urgency_coefficients()
64
80
 
65
81
  task_info = initialize_task_info(
66
82
  tasks, time_maps, days_ahead, urgency_coefficients, calendars
@@ -69,7 +85,7 @@ def check_tasks_parallel(config, verbose=False):
69
85
  for day_offset in range(days_ahead):
70
86
  date = today + timedelta(days=day_offset)
71
87
  allocate_time_for_day(
72
- task_info, day_offset, date, urgency_coefficients, inherit_urgency, verbose
88
+ task_info, day_offset, date, urgency_coefficients, verbose
73
89
  )
74
90
 
75
91
  update_tasks_with_scheduling_info(task_info, verbose)
@@ -100,13 +116,12 @@ def initialize_task_info(tasks, time_maps, days_ahead, urgency_coefficients, cal
100
116
  "scheduling": {},
101
117
  "urgency": initial_urgency,
102
118
  "estimated_urgency": estimated_urgency,
119
+ "started": False,
103
120
  }
104
121
  return task_info
105
122
 
106
123
 
107
- def allocate_time_for_day(
108
- task_info, day_offset, date, urgency_coefficients, inherit_urgency, verbose
109
- ):
124
+ def allocate_time_for_day(task_info, day_offset, date, urgency_coefficients, verbose):
110
125
  total_available_hours = compute_total_available_hours(task_info, day_offset)
111
126
  if verbose:
112
127
  print(f"Day {date}, total available hours: {total_available_hours:.2f}")
@@ -117,7 +132,7 @@ def allocate_time_for_day(
117
132
  tasks_remaining = prepare_tasks_remaining(task_info, day_offset)
118
133
 
119
134
  while day_remaining_hours > 0 and tasks_remaining:
120
- recompute_urgencies(tasks_remaining, urgency_coefficients, inherit_urgency)
135
+ recompute_urgencies(tasks_remaining, urgency_coefficients)
121
136
  sorted_task_ids = sorted(
122
137
  tasks_remaining.keys(),
123
138
  key=lambda x: (tasks_remaining[x]["urgency"], x),
@@ -125,10 +140,14 @@ def allocate_time_for_day(
125
140
  )
126
141
 
127
142
  allocated = False
128
- for id in sorted_task_ids:
129
- if id not in tasks_remaining:
143
+ for uuid in sorted_task_ids:
144
+ if uuid not in tasks_remaining:
145
+ # already completed
146
+ continue
147
+ info = tasks_remaining[uuid]
148
+ if any(d in tasks_remaining for d in info["task"].get("depends", [])):
149
+ # cannot execute this task until all its dependencies are completed
130
150
  continue
131
- info = tasks_remaining[id]
132
151
  allocation = allocate_time_to_task(info, day_offset, day_remaining_hours)
133
152
  if allocation > 0:
134
153
  day_remaining_hours -= allocation
@@ -143,7 +162,7 @@ def allocate_time_for_day(
143
162
  info["remaining_hours"] <= 0
144
163
  or info["task_time_map"][day_offset] <= 0
145
164
  ):
146
- del tasks_remaining[id]
165
+ del tasks_remaining[uuid]
147
166
  # if day_remaining_hours <= 0:
148
167
  break
149
168
  if not allocated:
@@ -168,13 +187,13 @@ def compute_total_available_hours(task_info, day_offset):
168
187
 
169
188
  def prepare_tasks_remaining(task_info, day_offset):
170
189
  return {
171
- info["task"]["id"]: info
190
+ info["task"]["uuid"]: info
172
191
  for info in task_info.values()
173
192
  if info["remaining_hours"] > 0 and info["task_time_map"][day_offset] > 0
174
193
  }
175
194
 
176
195
 
177
- def recompute_urgencies(tasks_remaining, urgency_coefficients, inherit_urgency):
196
+ def recompute_urgencies(tasks_remaining, urgency_coefficients):
178
197
  # Recompute estimated urgencies as before
179
198
  for info in tasks_remaining.values():
180
199
  remaining_hours = info["remaining_hours"]
@@ -184,23 +203,28 @@ def recompute_urgencies(tasks_remaining, urgency_coefficients, inherit_urgency):
184
203
  _old_estimated_urgency = info["estimated_urgency"]
185
204
  info["estimated_urgency"] = estimated_urgency
186
205
  info["urgency"] = info["urgency"] - _old_estimated_urgency + estimated_urgency
206
+ started_by_user = info["task"].get("start", "") != ""
207
+ started_by_scheduler = info["started"]
208
+ if started_by_scheduler and not started_by_user:
209
+ # If the task was started by the scheduler, apply the active task coefficient
210
+ info["urgency"] += urgency_coefficients.active
187
211
 
188
- if inherit_urgency:
212
+ if urgency_coefficients.inherit:
189
213
  # Define a recursive function to compute the maximum urgency
190
214
  def get_max_urgency(info, visited):
191
- task_id = info["task"]["id"]
192
- if task_id in visited:
193
- return visited[task_id] # Return cached value to avoid cycles
215
+ task_uuid = info["task"]["uuid"]
216
+ if task_uuid in visited:
217
+ return visited[task_uuid] # Return cached value to avoid cycles
194
218
  # Start with the current task's urgency
195
219
  urgency = info["urgency"]
196
- visited[task_id] = urgency # Mark as visited
220
+ visited[task_uuid] = urgency # Mark as visited
197
221
  # Recursively compute urgencies of dependents
198
- for dep_id in info["task"].get("depends", []):
199
- if dep_id in tasks_remaining:
200
- dep_info = tasks_remaining[dep_id]
222
+ for dep_uuid in info["task"].get("depends", []):
223
+ if dep_uuid in tasks_remaining:
224
+ dep_info = tasks_remaining[dep_uuid]
201
225
  dep_urgency = get_max_urgency(dep_info, visited)
202
226
  urgency = max(urgency, dep_urgency)
203
- visited[task_id] = urgency # Update with the maximum urgency found
227
+ visited[task_uuid] = urgency # Update with the maximum urgency found
204
228
  return urgency
205
229
 
206
230
  # Update urgencies based on dependents
@@ -227,6 +251,7 @@ def allocate_time_to_task(info, day_offset, day_remaining_hours):
227
251
 
228
252
  info["remaining_hours"] -= allocation
229
253
  info["task_time_map"][day_offset] -= allocation
254
+ info["started"] = True
230
255
 
231
256
  return allocation
232
257
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -19,7 +19,8 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
19
19
  - [x] Use arbitrarily complex time maps
20
20
  - [x] Use ical to block time from scheduling (e.g. for meetings, vacations, etc.)
21
21
  - [x] Implement scheduling algorithm for parallely working on multiple tasks
22
- - [ ] Use Google/Microsoft/Apple API to access calendars
22
+ - [ ] Use Google API to access calendars
23
+ - [ ] Export tasks to iCal calendar and API calendars
23
24
 
24
25
  ## Install
25
26
 
@@ -31,29 +32,33 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
31
32
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
32
33
  It considers their estimated time to schedule all tasks starting from the most urgent one.
33
34
 
34
- You will need to add the `estimated` and `time_map` UDAs to your tasks. The `estimated` attribute is
35
+ Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
35
36
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
36
37
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
37
38
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
38
- file of taskcheck.
39
+ file of taskcheck. For instance:
39
40
 
40
- `taskcheck` will modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
41
+ ```toml
42
+ ```toml
43
+ [time_maps]
44
+ # get an error)
45
+ [time_maps.work]
46
+ monday = [[9, 12.30], [14, 17]]
47
+ tuesday = [[9, 12.30], [14, 17]]
48
+ # ...
49
+ ```
50
+
51
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
52
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
41
53
  date of completion and the `scheduled` attribute with the date in which the task is expected to
42
54
  start.
43
55
 
44
56
  It will also print a red line for every task whose `completion_date` is after its `due_date`.
45
57
 
46
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
47
-
48
- You can see tasks that you can execute now with the `task ready` report.
49
-
50
- You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
51
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
52
-
53
58
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
54
59
  of your working day.
55
60
 
56
- ### Algorithms
61
+ ## Algorithms
57
62
 
58
63
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
59
64
 
@@ -125,6 +130,13 @@ event_all_day_is_blocking = true
125
130
  expiration = 720 # in hours (720 hours = 30 days)
126
131
  ```
127
132
 
133
+ ## Tips and Tricks
134
+
135
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
136
+ - You can see tasks that you can execute now with the `task ready` report.
137
+ - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
138
+ tomorrow)` will show you the tasks that include a scheduling for tomorrow.
139
+
128
140
  ## CLI Options
129
141
 
130
142
  ```
File without changes
File without changes
File without changes
File without changes