Skip Not-Started tasks that are already assigned to someone
A task carrying an "Assigned to" avatar in the Planner grid has been picked up by a person, so its attachments must not be downloaded and its status must not be moved to In Progress. Until now the only filter was percentComplete == 0, so an assigned task with an attachment was downloaded like any other — on the Balboa Nursing and Rehab plan that was one of the two "Facesheets Needed (GG)" tasks assigned to Dr. Mark Lukasik and Adina Smets. - _not_started_tasks() now selects `assignments` as well. - _task_assignee_ids()/_resolve_user_names(): the assignee check runs before the task-details call, so an assigned task costs no extra Graph request; display names are resolved once per user and cached for the run, falling back to the raw ID when the lookup fails. - Summary workbook gains a "Tasks Skipped (Assigned)" column and a second "Tasks" sheet with one row per task — facility, task name, assigned to, attachment count, Downloaded Yes/No and the reason — so a skip can be explained without reading the log. - New "All assigned — skipped" facility status, distinct from "No attachments". scripts/probe_assignments.py is the read-only probe used to confirm the rule against the live plan before changing behaviour. Verified on Balboa Nursing and Rehab: 4 Not Started -> 8.24.26 and 8.21.26 downloaded (unassigned), both Facesheets Needed (GG) skipped. 110 tests pass (tests/test_uipath_pilot.py and tests/test_config.py fail on this checkout for pre-existing, unrelated reasons). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dd864e0b4c
commit
bfd72825b4
3 files changed
+317
-9
No files matched your search
@@ -0,0 +1,121 @@
|
||||
"""Only unassigned Not-Started tasks may be downloaded.
|
||||
|
||||
A task carrying an 'Assigned to' avatar in the Planner grid has been picked up
|
||||
by a person; the bot must leave it — and its status — alone.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "src"
|
||||
for _p in (str(ROOT), str(SRC)):
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
from onedrive_planner_http import _task_assignee_ids, _write_summary # noqa: E402
|
||||
|
||||
|
||||
class TestTaskAssigneeIds:
|
||||
def test_unassigned_task_has_no_assignees(self):
|
||||
assert _task_assignee_ids({"title": "8.24.26"}) == []
|
||||
assert _task_assignee_ids({"title": "8.21.26", "assignments": {}}) == []
|
||||
assert _task_assignee_ids({"assignments": None}) == []
|
||||
|
||||
def test_assigned_task_returns_every_user_id(self):
|
||||
task = {
|
||||
"title": "Facesheets Needed (GG)",
|
||||
"assignments": {
|
||||
"aaaaaaaa-1111-2222-3333-444444444444": {"orderHint": "8"},
|
||||
"bbbbbbbb-5555-6666-7777-888888888888": {"orderHint": "9"},
|
||||
},
|
||||
}
|
||||
assert sorted(_task_assignee_ids(task)) == [
|
||||
"aaaaaaaa-1111-2222-3333-444444444444",
|
||||
"bbbbbbbb-5555-6666-7777-888888888888",
|
||||
]
|
||||
|
||||
def test_blank_user_id_is_ignored(self):
|
||||
assert _task_assignee_ids({"assignments": {"": {}}}) == []
|
||||
|
||||
|
||||
class TestSummaryWorkbook:
|
||||
def _write(self, tmp_path: Path, payload: dict) -> Path:
|
||||
out = tmp_path / "Download_Summary_HTTP_test.xlsx"
|
||||
_write_summary({"Balboa Nursing and Rehab": payload}, [], out, "2026-08-24 18:00")
|
||||
return out
|
||||
|
||||
def _balboa_payload(self) -> dict:
|
||||
"""The real Balboa plan: 2 unassigned with files, 2 assigned."""
|
||||
return {
|
||||
"original": [Path("a.pdf"), Path("b.pdf")],
|
||||
"tasks_total": 4,
|
||||
"tasks_with_attachments": 2,
|
||||
"tasks_with_downloads": 2,
|
||||
"tasks_skipped_no_attachment": 0,
|
||||
"tasks_skipped_assigned": 2,
|
||||
"status_changed": 2,
|
||||
"task_rows": [
|
||||
{"Facility Name": "Balboa Nursing and Rehab", "Task Name": "8.24.26",
|
||||
"Assigned To": "", "Attachments": 1, "Downloaded": "Yes",
|
||||
"Reason": "Downloaded 1/1 attachment(s)"},
|
||||
{"Facility Name": "Balboa Nursing and Rehab", "Task Name": "8.21.26",
|
||||
"Assigned To": "", "Attachments": 1, "Downloaded": "Yes",
|
||||
"Reason": "Downloaded 1/1 attachment(s)"},
|
||||
{"Facility Name": "Balboa Nursing and Rehab",
|
||||
"Task Name": "Facesheets Needed (GG)", "Assigned To": "A S",
|
||||
"Attachments": "", "Downloaded": "No",
|
||||
"Reason": "Skipped — assigned to A S"},
|
||||
{"Facility Name": "Balboa Nursing and Rehab",
|
||||
"Task Name": "Facesheets Needed (GG)", "Assigned To": "D M",
|
||||
"Attachments": "", "Downloaded": "No",
|
||||
"Reason": "Skipped — assigned to D M"},
|
||||
],
|
||||
}
|
||||
|
||||
def test_download_sheet_reports_assigned_skips(self, tmp_path):
|
||||
out = self._write(tmp_path, self._balboa_payload())
|
||||
df = pd.read_excel(out, sheet_name="Download")
|
||||
assert df.loc[0, "Tasks Skipped (Assigned)"] == 2
|
||||
assert df.loc[0, "Files Downloaded"] == 2
|
||||
assert df.loc[0, "Status"] == "Success"
|
||||
|
||||
def test_tasks_sheet_has_a_yes_no_row_per_task(self, tmp_path):
|
||||
out = self._write(tmp_path, self._balboa_payload())
|
||||
df = pd.read_excel(out, sheet_name="Tasks")
|
||||
assert list(df.columns) == [
|
||||
"Facility Name", "Task Name", "Assigned To",
|
||||
"Attachments", "Downloaded", "Reason",
|
||||
]
|
||||
assert list(df["Downloaded"]) == ["Yes", "Yes", "No", "No"]
|
||||
facesheets = df[df["Task Name"] == "Facesheets Needed (GG)"]
|
||||
assert len(facesheets) == 2
|
||||
assert all(facesheets["Downloaded"] == "No")
|
||||
assert all("assigned to" in r for r in facesheets["Reason"])
|
||||
|
||||
def test_facility_whose_tasks_are_all_assigned(self, tmp_path):
|
||||
out = self._write(tmp_path, {
|
||||
"original": [],
|
||||
"tasks_total": 3,
|
||||
"tasks_with_attachments": 0,
|
||||
"tasks_with_downloads": 0,
|
||||
"tasks_skipped_no_attachment": 0,
|
||||
"tasks_skipped_assigned": 3,
|
||||
"status_changed": 0,
|
||||
"task_rows": [],
|
||||
})
|
||||
df = pd.read_excel(out, sheet_name="Download")
|
||||
assert df.loc[0, "Status"] == "All assigned — skipped"
|
||||
|
||||
def test_tasks_sheet_exists_even_with_no_task_rows(self, tmp_path):
|
||||
out = self._write(tmp_path, {
|
||||
"original": [], "tasks_total": 0, "tasks_with_attachments": 0,
|
||||
"tasks_with_downloads": 0, "tasks_skipped_no_attachment": 0,
|
||||
"tasks_skipped_assigned": 0, "status_changed": 0, "task_rows": [],
|
||||
})
|
||||
df = pd.read_excel(out, sheet_name="Tasks")
|
||||
assert df.empty
|
||||
assert "Downloaded" in df.columns
|
||||
Reference in new issue
Block a user