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.
Files changed (34) hide show
  1. {taskcheck-1.6.0/taskcheck.egg-info → taskcheck-1.6.1}/PKG-INFO +6 -1
  2. {taskcheck-1.6.0 → taskcheck-1.6.1}/README.md +5 -0
  3. {taskcheck-1.6.0 → taskcheck-1.6.1}/pyproject.toml +1 -1
  4. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/agent_blocks.py +1 -1
  5. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/parallel.py +11 -1
  6. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/report.py +5 -0
  7. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/timeline.py +5 -1
  8. {taskcheck-1.6.0 → taskcheck-1.6.1/taskcheck.egg-info}/PKG-INFO +6 -1
  9. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_parallel.py +20 -0
  10. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_report.py +9 -0
  11. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_timeline.py +8 -0
  12. {taskcheck-1.6.0 → taskcheck-1.6.1}/LICENSE +0 -0
  13. {taskcheck-1.6.0 → taskcheck-1.6.1}/setup.cfg +0 -0
  14. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/__main__.py +0 -0
  15. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/common.py +0 -0
  16. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/dry_run.py +0 -0
  17. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/google_calendar.py +0 -0
  18. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/ical.py +0 -0
  19. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/install.py +0 -0
  20. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/theme.py +0 -0
  21. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck/undo.py +0 -0
  22. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/SOURCES.txt +0 -0
  23. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/dependency_links.txt +0 -0
  24. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/entry_points.txt +0 -0
  25. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/requires.txt +0 -0
  26. {taskcheck-1.6.0 → taskcheck-1.6.1}/taskcheck.egg-info/top_level.txt +0 -0
  27. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_agent_blocks.py +0 -0
  28. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_agent_scheduler_integration.py +0 -0
  29. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_common.py +0 -0
  30. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_google_calendar.py +0 -0
  31. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_ical.py +0 -0
  32. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_install.py +0 -0
  33. {taskcheck-1.6.0 → taskcheck-1.6.1}/tests/test_main.py +0 -0
  34. {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.0
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`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "taskcheck"
3
- version = "1.6.0"
3
+ version = "1.6.1"
4
4
  description = "Automatic scheduler for taskwarrior"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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 <= datetime.strptime(wait, "%Y%m%dT%H%M%SZ").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 <= datetime.strptime(wait, "%Y%m%dT%H%M%SZ").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
- dates.append(date.fromisoformat(match.group(1)))
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.0
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