taskcheck 1.6.0__tar.gz → 1.6.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.
- {taskcheck-1.6.0/taskcheck.egg-info → taskcheck-1.6.1}/PKG-INFO +6 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1}/README.md +5 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/pyproject.toml +1 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/agent_blocks.py +1 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/parallel.py +11 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/report.py +5 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/timeline.py +5 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1/taskcheck.egg-info}/PKG-INFO +6 -1
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_parallel.py +20 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_report.py +9 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_timeline.py +8 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/LICENSE +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/setup.cfg +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/__main__.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/common.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/dry_run.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/google_calendar.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/ical.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/install.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/theme.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/undo.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/SOURCES.txt +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/dependency_links.txt +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/entry_points.txt +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/requires.txt +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/top_level.txt +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_agent_blocks.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_agent_scheduler_integration.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_common.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_google_calendar.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_ical.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_install.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_main.py +0 -0
- {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_undo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.1
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -60,6 +60,11 @@ It updates Taskwarrior with:
|
|
|
60
60
|
- `completion_date` → when work is expected to finish
|
|
61
61
|
- a warning when a task will miss its due date
|
|
62
62
|
|
|
63
|
+
Taskcheck does not allocate work before a task's `wait` date.
|
|
64
|
+
It can allocate work on that date because it plans whole days, not exact start times.
|
|
65
|
+
Reports and timelines ignore saved work dates before `wait`, even when you change `wait` after scheduling.
|
|
66
|
+
If no work fits within the planning period, the next scheduling run clears the task's old schedule fields.
|
|
67
|
+
|
|
63
68
|
## Quick start
|
|
64
69
|
|
|
65
70
|
1. `pipx install taskcheck`
|
|
@@ -45,6 +45,11 @@ It updates Taskwarrior with:
|
|
|
45
45
|
- `completion_date` → when work is expected to finish
|
|
46
46
|
- a warning when a task will miss its due date
|
|
47
47
|
|
|
48
|
+
Taskcheck does not allocate work before a task's `wait` date.
|
|
49
|
+
It can allocate work on that date because it plans whole days, not exact start times.
|
|
50
|
+
Reports and timelines ignore saved work dates before `wait`, even when you change `wait` after scheduling.
|
|
51
|
+
If no work fits within the planning period, the next scheduling run clears the task's old schedule fields.
|
|
52
|
+
|
|
48
53
|
## Quick start
|
|
49
54
|
|
|
50
55
|
1. `pipx install taskcheck`
|
|
@@ -39,7 +39,7 @@ def _eligible(info, pending, date):
|
|
|
39
39
|
if any(dep in pending for dep in info["task"].get("depends", [])):
|
|
40
40
|
return False
|
|
41
41
|
wait = info["task"].get("wait")
|
|
42
|
-
if wait and date
|
|
42
|
+
if wait and date < datetime.strptime(wait, "%Y%m%dT%H%M%SZ").date():
|
|
43
43
|
return False
|
|
44
44
|
return True
|
|
45
45
|
|
|
@@ -416,7 +416,7 @@ def allocate_time_for_day(
|
|
|
416
416
|
continue
|
|
417
417
|
|
|
418
418
|
wait = info["task"].get("wait")
|
|
419
|
-
if wait and date
|
|
419
|
+
if wait and date < datetime.strptime(wait, "%Y%m%dT%H%M%SZ").date():
|
|
420
420
|
if verbose:
|
|
421
421
|
print(f"Skipping task {info['task']['id']} due to wait date {wait}")
|
|
422
422
|
continue
|
|
@@ -647,6 +647,16 @@ def update_tasks_with_scheduling_info(task_info, verbose, taskrc):
|
|
|
647
647
|
scheduling_note = ""
|
|
648
648
|
scheduled_dates = sorted(info["scheduling"].keys())
|
|
649
649
|
if not scheduled_dates:
|
|
650
|
+
if any(task.get(field) for field in ("scheduled", "completion_date", "scheduling")):
|
|
651
|
+
subprocess.run(
|
|
652
|
+
[
|
|
653
|
+
"task", str(task["id"]), "modify",
|
|
654
|
+
"scheduled:", "completion_date:", "scheduling:",
|
|
655
|
+
],
|
|
656
|
+
stdout=subprocess.DEVNULL,
|
|
657
|
+
stderr=subprocess.DEVNULL,
|
|
658
|
+
env=get_task_env(taskrc),
|
|
659
|
+
)
|
|
650
660
|
continue
|
|
651
661
|
start_date = scheduled_dates[0]
|
|
652
662
|
end_date = scheduled_dates[-1]
|
|
@@ -184,6 +184,11 @@ def get_tasks(config, tasks, year, month, day):
|
|
|
184
184
|
regex = re.compile(rf"{year:04d}-{month:02d}-{day:02d} - ([PDTHM0-9]+)")
|
|
185
185
|
valid_tasks = []
|
|
186
186
|
for task in tasks:
|
|
187
|
+
wait = task.get("wait")
|
|
188
|
+
if wait and datetime(year, month, day).date() < datetime.strptime(
|
|
189
|
+
wait, "%Y%m%dT%H%M%SZ"
|
|
190
|
+
).date():
|
|
191
|
+
continue
|
|
187
192
|
for line in task.get("scheduling", "").split("\n"):
|
|
188
193
|
m = regex.match(line)
|
|
189
194
|
if m:
|
|
@@ -37,10 +37,14 @@ def project_color(project, theme):
|
|
|
37
37
|
|
|
38
38
|
def parse_scheduling_dates(task):
|
|
39
39
|
dates = []
|
|
40
|
+
wait = task.get("wait")
|
|
41
|
+
wait_date = datetime.strptime(wait, "%Y%m%dT%H%M%SZ").date() if wait else None
|
|
40
42
|
for line in task.get("scheduling", "").splitlines():
|
|
41
43
|
match = SCHEDULING_DATE.match(line)
|
|
42
44
|
if match:
|
|
43
|
-
|
|
45
|
+
scheduled_date = date.fromisoformat(match.group(1))
|
|
46
|
+
if wait_date is None or scheduled_date >= wait_date:
|
|
47
|
+
dates.append(scheduled_date)
|
|
44
48
|
return sorted(set(dates))
|
|
45
49
|
|
|
46
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.1
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -60,6 +60,11 @@ It updates Taskwarrior with:
|
|
|
60
60
|
- `completion_date` → when work is expected to finish
|
|
61
61
|
- a warning when a task will miss its due date
|
|
62
62
|
|
|
63
|
+
Taskcheck does not allocate work before a task's `wait` date.
|
|
64
|
+
It can allocate work on that date because it plans whole days, not exact start times.
|
|
65
|
+
Reports and timelines ignore saved work dates before `wait`, even when you change `wait` after scheduling.
|
|
66
|
+
If no work fits within the planning period, the next scheduling run clears the task's old schedule fields.
|
|
67
|
+
|
|
63
68
|
## Quick start
|
|
64
69
|
|
|
65
70
|
1. `pipx install taskcheck`
|
|
@@ -105,6 +105,26 @@ class TestTaskInitialization:
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
class TestTimeAllocation:
|
|
108
|
+
@pytest.mark.parametrize("agent", [False, True])
|
|
109
|
+
def test_task_starts_on_wait_date(self, agent):
|
|
110
|
+
from freezegun import freeze_time
|
|
111
|
+
|
|
112
|
+
task = {
|
|
113
|
+
"id": 1, "uuid": "waiting", "estimated": "PT2H", "time_map": "work",
|
|
114
|
+
"wait": "20231206T000000Z", "urgency": 10.0,
|
|
115
|
+
}
|
|
116
|
+
if agent:
|
|
117
|
+
task.update(tags=["agent"], agent_weight=25)
|
|
118
|
+
coeffs = UrgencyCoefficients({"PT2H": 0}, False, 0, 365, 12, 2)
|
|
119
|
+
with freeze_time("2023-12-05"):
|
|
120
|
+
with patch("taskcheck.parallel.get_long_range_time_map", return_value=([8.0] * 3, 0.0)):
|
|
121
|
+
info = initialize_task_info([task], {}, 3, coeffs, [])
|
|
122
|
+
allocate_time_for_day(info, 0, coeffs, False, 1.0)
|
|
123
|
+
assert info["waiting"]["scheduling"] == {}
|
|
124
|
+
allocate_time_for_day(info, 1, coeffs, False, 1.0)
|
|
125
|
+
assert info["waiting"]["scheduling"] == {"2023-12-06": 2.0}
|
|
126
|
+
assert info["waiting"]["remaining_hours"] == 0
|
|
127
|
+
|
|
108
128
|
def test_allocate_time_for_day_single_task(self, sample_config):
|
|
109
129
|
task_info = {
|
|
110
130
|
"task-1": {
|
|
@@ -67,6 +67,15 @@ class TestDateUtilities:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class TestTaskFiltering:
|
|
70
|
+
@pytest.mark.parametrize("day, expected", [(5, []), (6, [1]), (7, [1])])
|
|
71
|
+
def test_wait_date_overrides_saved_schedule(self, day, expected):
|
|
72
|
+
task = {
|
|
73
|
+
"id": 1, "description": "Waiting task", "urgency": 10.0,
|
|
74
|
+
"wait": "20231206T000000Z",
|
|
75
|
+
"scheduling": "\n".join(f"2023-12-{d:02d} - PT2H" for d in (5, 6, 7)),
|
|
76
|
+
}
|
|
77
|
+
assert [row["id"] for row in get_tasks({}, [task], 2023, 12, day)] == expected
|
|
78
|
+
|
|
70
79
|
def test_get_tasks_with_scheduling(self, sample_config):
|
|
71
80
|
tasks_with_scheduling = [
|
|
72
81
|
{
|
|
@@ -23,6 +23,14 @@ def test_parse_scheduling_dates_ignores_invalid_lines():
|
|
|
23
23
|
assert parse_scheduling_dates(task) == [date(2024, 1, 2), date(2024, 1, 3)]
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def test_wait_date_overrides_saved_schedule():
|
|
27
|
+
task = {
|
|
28
|
+
"wait": "20240103T000000Z",
|
|
29
|
+
"scheduling": "2024-01-02 - PT2H\n2024-01-03 - PT2H\n2024-01-04 - PT2H",
|
|
30
|
+
}
|
|
31
|
+
assert parse_scheduling_dates(task) == [date(2024, 1, 3), date(2024, 1, 4)]
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
def test_day_timeline_groups_projects_and_marks_events():
|
|
27
35
|
tasks = [
|
|
28
36
|
{
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|