taskcheck 0.2.1__tar.gz → 0.3.0.post1__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.2.1/taskcheck.egg-info → taskcheck-0.3.0.post1}/PKG-INFO +16 -4
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/README.md +14 -3
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/pyproject.toml +7 -2
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/__main__.py +15 -2
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/common.py +25 -5
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/parallel.py +11 -5
- taskcheck-0.3.0.post1/taskcheck/report.py +122 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/sequential.py +3 -2
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1/taskcheck.egg-info}/PKG-INFO +16 -4
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck.egg-info/SOURCES.txt +1 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck.egg-info/requires.txt +1 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/LICENSE +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/setup.cfg +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/ical.py +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck/install.py +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck.egg-info/dependency_links.txt +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck.egg-info/entry_points.txt +0 -0
- {taskcheck-0.2.1 → taskcheck-0.3.0.post1}/taskcheck.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0.post1
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,6 +8,7 @@ License-File: LICENSE
|
|
|
8
8
|
Requires-Dist: appdirs>=1.4.4
|
|
9
9
|
Requires-Dist: icalendar>=6.0.1
|
|
10
10
|
Requires-Dist: requests>=2.32.3
|
|
11
|
+
Requires-Dist: rich>=13.9.4
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
> _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal/timehero/flowsavvy/reclaim/trevor/motion)_
|
|
@@ -32,13 +33,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
|
|
|
32
33
|
This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
|
|
33
34
|
It considers their estimated time to schedule all tasks starting from the most urgent one.
|
|
34
35
|
|
|
36
|
+
#### UDAs
|
|
37
|
+
|
|
35
38
|
Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
|
|
36
39
|
the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
|
|
37
40
|
that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
|
|
38
41
|
The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
|
|
39
42
|
file of taskcheck. For instance:
|
|
40
43
|
|
|
41
|
-
```toml
|
|
42
44
|
```toml
|
|
43
45
|
[time_maps]
|
|
44
46
|
# get an error)
|
|
@@ -48,6 +50,8 @@ tuesday = [[9, 12.30], [14, 17]]
|
|
|
48
50
|
# ...
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
#### They say it's an "AI"
|
|
54
|
+
|
|
51
55
|
Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
|
|
52
56
|
It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
|
|
53
57
|
date of completion and the `scheduled` attribute with the date in which the task is expected to
|
|
@@ -58,6 +62,15 @@ It will also print a red line for every task whose `completion_date` is after it
|
|
|
58
62
|
In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
|
|
59
63
|
of your working day.
|
|
60
64
|
|
|
65
|
+
#### Reports
|
|
66
|
+
|
|
67
|
+
You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
|
|
68
|
+
how much time you have to work on which task in which day. For
|
|
69
|
+
instance:
|
|
70
|
+
|
|
71
|
+
- `taskcheck -r today` will show the tasks planned for today
|
|
72
|
+
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
73
|
+
|
|
61
74
|
## Algorithms
|
|
62
75
|
|
|
63
76
|
Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
|
|
@@ -134,12 +147,11 @@ expiration = 720 # in hours (720 hours = 30 days)
|
|
|
134
147
|
|
|
135
148
|
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
|
|
136
149
|
- 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
150
|
|
|
140
151
|
## CLI Options
|
|
141
152
|
|
|
142
153
|
```
|
|
143
154
|
-v, --verbose: increase output verbosity
|
|
144
155
|
-i, --install: install taskcheck configuration
|
|
156
|
+
-r, --report: show tasks planned until a certain time
|
|
145
157
|
```
|
|
@@ -21,13 +21,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
|
|
|
21
21
|
This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
|
|
22
22
|
It considers their estimated time to schedule all tasks starting from the most urgent one.
|
|
23
23
|
|
|
24
|
+
#### UDAs
|
|
25
|
+
|
|
24
26
|
Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
|
|
25
27
|
the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
|
|
26
28
|
that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
|
|
27
29
|
The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
|
|
28
30
|
file of taskcheck. For instance:
|
|
29
31
|
|
|
30
|
-
```toml
|
|
31
32
|
```toml
|
|
32
33
|
[time_maps]
|
|
33
34
|
# get an error)
|
|
@@ -37,6 +38,8 @@ tuesday = [[9, 12.30], [14, 17]]
|
|
|
37
38
|
# ...
|
|
38
39
|
```
|
|
39
40
|
|
|
41
|
+
#### They say it's an "AI"
|
|
42
|
+
|
|
40
43
|
Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
|
|
41
44
|
It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
|
|
42
45
|
date of completion and the `scheduled` attribute with the date in which the task is expected to
|
|
@@ -47,6 +50,15 @@ It will also print a red line for every task whose `completion_date` is after it
|
|
|
47
50
|
In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
|
|
48
51
|
of your working day.
|
|
49
52
|
|
|
53
|
+
#### Reports
|
|
54
|
+
|
|
55
|
+
You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
|
|
56
|
+
how much time you have to work on which task in which day. For
|
|
57
|
+
instance:
|
|
58
|
+
|
|
59
|
+
- `taskcheck -r today` will show the tasks planned for today
|
|
60
|
+
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
61
|
+
|
|
50
62
|
## Algorithms
|
|
51
63
|
|
|
52
64
|
Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
|
|
@@ -123,12 +135,11 @@ expiration = 720 # in hours (720 hours = 30 days)
|
|
|
123
135
|
|
|
124
136
|
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
|
|
125
137
|
- 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
138
|
|
|
129
139
|
## CLI Options
|
|
130
140
|
|
|
131
141
|
```
|
|
132
142
|
-v, --verbose: increase output verbosity
|
|
133
143
|
-i, --install: install taskcheck configuration
|
|
144
|
+
-r, --report: show tasks planned until a certain time
|
|
134
145
|
```
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "taskcheck"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0-1"
|
|
4
4
|
description = "Automatic scheduler for taskwarrior"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
|
-
dependencies = [
|
|
7
|
+
dependencies = [
|
|
8
|
+
"appdirs>=1.4.4",
|
|
9
|
+
"icalendar>=6.0.1",
|
|
10
|
+
"requests>=2.32.3",
|
|
11
|
+
"rich>=13.9.4",
|
|
12
|
+
]
|
|
8
13
|
|
|
9
14
|
[project.scripts]
|
|
10
15
|
taskcheck = "taskcheck.__main__:main"
|
|
@@ -7,13 +7,19 @@ from taskcheck.common import config_dir
|
|
|
7
7
|
|
|
8
8
|
arg_parser = argparse.ArgumentParser()
|
|
9
9
|
arg_parser.add_argument(
|
|
10
|
-
"-v", "--verbose", action="store_true", help="increase output verbosity"
|
|
10
|
+
"-v", "--verbose", action="store_true", help="increase output verbosity."
|
|
11
11
|
)
|
|
12
12
|
arg_parser.add_argument(
|
|
13
13
|
"-i",
|
|
14
14
|
"--install",
|
|
15
15
|
action="store_true",
|
|
16
|
-
help="install the UDAs, required settings, and default config file",
|
|
16
|
+
help="install the UDAs, required settings, and default config file.",
|
|
17
|
+
)
|
|
18
|
+
arg_parser.add_argument(
|
|
19
|
+
"-r",
|
|
20
|
+
"--report",
|
|
21
|
+
action="store",
|
|
22
|
+
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.",
|
|
17
23
|
)
|
|
18
24
|
|
|
19
25
|
args = arg_parser.parse_args()
|
|
@@ -34,6 +40,13 @@ def main():
|
|
|
34
40
|
install()
|
|
35
41
|
return
|
|
36
42
|
|
|
43
|
+
if args.report:
|
|
44
|
+
from taskcheck.report import generate_report
|
|
45
|
+
|
|
46
|
+
config = load_config()
|
|
47
|
+
generate_report(config, args.report, args.verbose)
|
|
48
|
+
return
|
|
49
|
+
|
|
37
50
|
config = load_config()
|
|
38
51
|
algo = config["scheduler"].get("algorithm", "parallel")
|
|
39
52
|
if algo == "parallel":
|
|
@@ -3,9 +3,12 @@ import json
|
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import appdirs
|
|
6
|
+
from rich.console import Console
|
|
6
7
|
|
|
7
8
|
from taskcheck.ical import ical_to_dict
|
|
8
9
|
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
9
12
|
config_dir = Path(appdirs.user_config_dir("task"))
|
|
10
13
|
|
|
11
14
|
# Taskwarrior status to avoid
|
|
@@ -91,19 +94,36 @@ def pdth_to_hours(duration_str):
|
|
|
91
94
|
# string format is P#DT#H
|
|
92
95
|
# with D and H optional
|
|
93
96
|
duration_str = duration_str[1:] # Remove leading "P"
|
|
94
|
-
days, hours = 0, 0
|
|
97
|
+
days, hours, minutes = 0, 0, 0
|
|
95
98
|
if "D" in duration_str:
|
|
96
99
|
days, duration_str = duration_str.split("D")
|
|
97
100
|
days = int(days)
|
|
98
101
|
if "H" in duration_str:
|
|
99
102
|
hours = int(duration_str.split("T")[1].split("H")[0])
|
|
100
|
-
|
|
103
|
+
if "M" in duration_str:
|
|
104
|
+
minutes = int(duration_str.split("H")[1].split("M")[0])
|
|
105
|
+
return days * 24 + hours + minutes / 60
|
|
101
106
|
|
|
102
107
|
|
|
103
108
|
def hours_to_pdth(hours):
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
days_ = hours / 24
|
|
110
|
+
hours_ = hours % 24
|
|
111
|
+
minutes_ = int(round((hours_ - int(hours_)) * 60))
|
|
112
|
+
days_ = int(days_)
|
|
113
|
+
hours_ = int(hours_)
|
|
114
|
+
minutes_ = int(minutes_)
|
|
115
|
+
ret = "P"
|
|
116
|
+
if days_ == 0:
|
|
117
|
+
ret += "T"
|
|
118
|
+
else:
|
|
119
|
+
ret += f"{days_}DT"
|
|
120
|
+
if hours_ > 0:
|
|
121
|
+
ret += f"{hours_}H"
|
|
122
|
+
if minutes_ > 0:
|
|
123
|
+
ret += f"{minutes_}M"
|
|
124
|
+
if ret == "PT":
|
|
125
|
+
print("Warning: hours_to_pdth() returned 'PT' for hours = ", hours)
|
|
126
|
+
return ret
|
|
107
127
|
|
|
108
128
|
|
|
109
129
|
def get_long_range_time_map(
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from datetime import datetime, timedelta
|
|
6
6
|
from taskcheck.common import (
|
|
7
7
|
AVOID_STATUS,
|
|
8
|
+
console,
|
|
8
9
|
get_calendars,
|
|
9
10
|
get_long_range_time_map,
|
|
10
11
|
get_tasks,
|
|
@@ -57,12 +58,10 @@ def compute_estimated_urgency(remaining_hours, urgency_coefficients):
|
|
|
57
58
|
"""
|
|
58
59
|
Computes the estimated urgency for the given remaining hours using the coefficients.
|
|
59
60
|
"""
|
|
60
|
-
# Convert remaining hours to PDTH format used in the coefficients
|
|
61
|
-
pdth_value = hours_to_pdth(remaining_hours)
|
|
62
61
|
# Find the closest match (e.g., if '2h' is not available, use '1h' or '3h')
|
|
63
62
|
closest_match = min(
|
|
64
63
|
urgency_coefficients.estimated.keys(),
|
|
65
|
-
key=lambda x: abs(
|
|
64
|
+
key=lambda x: abs(pdth_to_hours(x) - remaining_hours),
|
|
66
65
|
)
|
|
67
66
|
coefficient = urgency_coefficients.estimated[closest_match]
|
|
68
67
|
# Compute the urgency
|
|
@@ -246,7 +245,7 @@ def allocate_time_to_task(info, day_offset, day_remaining_hours):
|
|
|
246
245
|
hours_to_decimal(info["task"].get("min_block", 2)),
|
|
247
246
|
)
|
|
248
247
|
|
|
249
|
-
if allocation <= 0:
|
|
248
|
+
if allocation <= 0.05:
|
|
250
249
|
return 0
|
|
251
250
|
|
|
252
251
|
info["remaining_hours"] -= allocation
|
|
@@ -273,7 +272,7 @@ def update_tasks_with_scheduling_info(task_info, verbose):
|
|
|
273
272
|
end_date = scheduled_dates[-1]
|
|
274
273
|
for date_str in scheduled_dates:
|
|
275
274
|
hours = info["scheduling"][date_str]
|
|
276
|
-
scheduling_note += f"{date_str}
|
|
275
|
+
scheduling_note += f"{date_str} - {hours_to_pdth(hours)}\n"
|
|
277
276
|
|
|
278
277
|
subprocess.run(
|
|
279
278
|
[
|
|
@@ -287,6 +286,13 @@ def update_tasks_with_scheduling_info(task_info, verbose):
|
|
|
287
286
|
stdout=subprocess.DEVNULL,
|
|
288
287
|
stderr=subprocess.DEVNULL,
|
|
289
288
|
)
|
|
289
|
+
due = task.get("due")
|
|
290
|
+
end_date = datetime.strptime(end_date, "%Y-%m-%d")
|
|
291
|
+
if due is not None and end_date > datetime.strptime(due, "%Y%m%dT%H%M%SZ"):
|
|
292
|
+
console.print(
|
|
293
|
+
f"[red]Warning: Task {task['id']} ('{task['description']}') is not going to be completed on time.[/red]"
|
|
294
|
+
)
|
|
295
|
+
|
|
290
296
|
if verbose:
|
|
291
297
|
print(
|
|
292
298
|
f"Updated task {task['id']} with scheduled dates {start_date} to {end_date}"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from rich import print
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_tasks(tasks, year, month, day):
|
|
17
|
+
regex = re.compile(rf"{year}-{month}-{day} - ([PDTHM0-9]+)")
|
|
18
|
+
valid_tasks = []
|
|
19
|
+
for task in tasks:
|
|
20
|
+
for line in task["scheduling"].split("\n"):
|
|
21
|
+
m = regex.match(line)
|
|
22
|
+
if m:
|
|
23
|
+
due = task.get("due", "")
|
|
24
|
+
if due:
|
|
25
|
+
due = datetime.strptime(due, "%Y%m%dT%H%M%SZ")
|
|
26
|
+
due = due - datetime.today()
|
|
27
|
+
valid_tasks.append(
|
|
28
|
+
{
|
|
29
|
+
"id": task["id"],
|
|
30
|
+
"project": task.get("project", ""),
|
|
31
|
+
"description": task["description"],
|
|
32
|
+
"scheduling_day": f"{year}-{month}-{day}",
|
|
33
|
+
"scheduling_hours": m.group(1),
|
|
34
|
+
"urgency": task["urgency"],
|
|
35
|
+
"due": due,
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
valid_tasks = sorted(valid_tasks, key=lambda x: x["urgency"], reverse=True)
|
|
39
|
+
return valid_tasks
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_taskwarrior_date(date, _retry=True):
|
|
43
|
+
date = subprocess.run(
|
|
44
|
+
["task", "calc", date],
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
)
|
|
48
|
+
date = date.stdout.strip()
|
|
49
|
+
try:
|
|
50
|
+
date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")
|
|
51
|
+
except Exception as _:
|
|
52
|
+
if _retry:
|
|
53
|
+
return get_taskwarrior_date("today+" + date, False)
|
|
54
|
+
else:
|
|
55
|
+
print(
|
|
56
|
+
"Please provide a valid date. Check with `task calc` that your date is in the format YYYY-MM-DDTHH:MM:SS",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
exit(2)
|
|
60
|
+
return date
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_days_in_constraint(constraint):
|
|
64
|
+
current_date = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
65
|
+
constraint = get_taskwarrior_date(constraint)
|
|
66
|
+
while current_date <= constraint:
|
|
67
|
+
yield current_date.year, current_date.month, current_date.day
|
|
68
|
+
current_date += timedelta(days=1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def generate_report(config, constraint, verbose=False):
|
|
72
|
+
console = Console()
|
|
73
|
+
tasks = subprocess.run(
|
|
74
|
+
["task", "scheduling~.", "export"], capture_output=True, text=True
|
|
75
|
+
)
|
|
76
|
+
tasks = json.loads(tasks.stdout)
|
|
77
|
+
|
|
78
|
+
for year, month, day in get_days_in_constraint(constraint):
|
|
79
|
+
this_day_tasks = get_tasks(tasks, year, month, day)
|
|
80
|
+
|
|
81
|
+
# Create a colorful date header with emojis and a panel
|
|
82
|
+
date_str = f":calendar: [bold cyan]{year}-{month}-{day}[/bold cyan]"
|
|
83
|
+
console.print(Panel(date_str, style="bold magenta", expand=False))
|
|
84
|
+
|
|
85
|
+
if this_day_tasks:
|
|
86
|
+
table = Table(show_header=True, header_style="bold blue")
|
|
87
|
+
table.add_column("Task", style="dim", width=12)
|
|
88
|
+
table.add_column("Project", style="dim", width=12)
|
|
89
|
+
table.add_column("Description")
|
|
90
|
+
table.add_column("Time", justify="right")
|
|
91
|
+
table.add_column("Urgency")
|
|
92
|
+
table.add_column("Due date")
|
|
93
|
+
|
|
94
|
+
for task in this_day_tasks:
|
|
95
|
+
task_id = f"[bold green]#{task['id']}[/bold green]"
|
|
96
|
+
project = task["project"]
|
|
97
|
+
description = Text(task["description"], style="white")
|
|
98
|
+
hours = f"[yellow]{task['scheduling_hours']}h[/yellow]"
|
|
99
|
+
urgency = f"[bold]{task['urgency']}[/bold]"
|
|
100
|
+
due = task["due"]
|
|
101
|
+
if due != "" and due < timedelta(days=5):
|
|
102
|
+
due = f"[red]{due.days}d[/red]"
|
|
103
|
+
elif due != "":
|
|
104
|
+
due = f"[green]{due.days}d[/green]"
|
|
105
|
+
|
|
106
|
+
# Add an emoji based on a keyword in the description
|
|
107
|
+
if "meeting" in task["description"].lower():
|
|
108
|
+
emoji = ":busts_in_silhouette:"
|
|
109
|
+
elif "review" in task["description"].lower():
|
|
110
|
+
emoji = ":mag_right:"
|
|
111
|
+
elif "write" in task["description"].lower():
|
|
112
|
+
emoji = ":pencil:"
|
|
113
|
+
else:
|
|
114
|
+
emoji = ":pushpin:"
|
|
115
|
+
|
|
116
|
+
table.add_row(
|
|
117
|
+
f"{emoji} {task_id}", project, description, hours, urgency, due
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
console.print(table)
|
|
121
|
+
else:
|
|
122
|
+
console.print("[italic dim]No tasks scheduled for this day.[/italic dim]")
|
|
@@ -5,6 +5,7 @@ from taskcheck.common import (
|
|
|
5
5
|
get_tasks,
|
|
6
6
|
mark_end_date,
|
|
7
7
|
pdth_to_hours,
|
|
8
|
+
hours_to_pdth,
|
|
8
9
|
AVOID_STATUS,
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -43,7 +44,7 @@ def schedule_task_on_day(
|
|
|
43
44
|
# consume all the remaining task's hours
|
|
44
45
|
if scheduling_note != "":
|
|
45
46
|
scheduling_note += "\n"
|
|
46
|
-
scheduling_note += f"{current_date}
|
|
47
|
+
scheduling_note += f"{current_date} - {hours_to_pdth(task_remaining_hours)}\n"
|
|
47
48
|
used_hours[day_offset] += task_remaining_hours
|
|
48
49
|
task_remaining_hours = 0
|
|
49
50
|
end_date = current_date
|
|
@@ -54,7 +55,7 @@ def schedule_task_on_day(
|
|
|
54
55
|
# consume all the available hours on this task
|
|
55
56
|
if scheduling_note != "":
|
|
56
57
|
scheduling_note += "\n"
|
|
57
|
-
scheduling_note += f"{current_date}
|
|
58
|
+
scheduling_note += f"{current_date} - {hours_to_pdth(employable_hours)}\n"
|
|
58
59
|
task_remaining_hours -= employable_hours
|
|
59
60
|
used_hours[day_offset] += employable_hours
|
|
60
61
|
if verbose:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0.post1
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,6 +8,7 @@ License-File: LICENSE
|
|
|
8
8
|
Requires-Dist: appdirs>=1.4.4
|
|
9
9
|
Requires-Dist: icalendar>=6.0.1
|
|
10
10
|
Requires-Dist: requests>=2.32.3
|
|
11
|
+
Requires-Dist: rich>=13.9.4
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
> _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal/timehero/flowsavvy/reclaim/trevor/motion)_
|
|
@@ -32,13 +33,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
|
|
|
32
33
|
This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
|
|
33
34
|
It considers their estimated time to schedule all tasks starting from the most urgent one.
|
|
34
35
|
|
|
36
|
+
#### UDAs
|
|
37
|
+
|
|
35
38
|
Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
|
|
36
39
|
the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
|
|
37
40
|
that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
|
|
38
41
|
The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
|
|
39
42
|
file of taskcheck. For instance:
|
|
40
43
|
|
|
41
|
-
```toml
|
|
42
44
|
```toml
|
|
43
45
|
[time_maps]
|
|
44
46
|
# get an error)
|
|
@@ -48,6 +50,8 @@ tuesday = [[9, 12.30], [14, 17]]
|
|
|
48
50
|
# ...
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
#### They say it's an "AI"
|
|
54
|
+
|
|
51
55
|
Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
|
|
52
56
|
It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
|
|
53
57
|
date of completion and the `scheduled` attribute with the date in which the task is expected to
|
|
@@ -58,6 +62,15 @@ It will also print a red line for every task whose `completion_date` is after it
|
|
|
58
62
|
In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
|
|
59
63
|
of your working day.
|
|
60
64
|
|
|
65
|
+
#### Reports
|
|
66
|
+
|
|
67
|
+
You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
|
|
68
|
+
how much time you have to work on which task in which day. For
|
|
69
|
+
instance:
|
|
70
|
+
|
|
71
|
+
- `taskcheck -r today` will show the tasks planned for today
|
|
72
|
+
- `taskcheck -r 1w` will show the tasks planned for the next week,
|
|
73
|
+
|
|
61
74
|
## Algorithms
|
|
62
75
|
|
|
63
76
|
Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
|
|
@@ -134,12 +147,11 @@ expiration = 720 # in hours (720 hours = 30 days)
|
|
|
134
147
|
|
|
135
148
|
- You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
|
|
136
149
|
- 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
150
|
|
|
140
151
|
## CLI Options
|
|
141
152
|
|
|
142
153
|
```
|
|
143
154
|
-v, --verbose: increase output verbosity
|
|
144
155
|
-i, --install: install taskcheck configuration
|
|
156
|
+
-r, --report: show tasks planned until a certain time
|
|
145
157
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|