Files
full-focus/scripts/fetch_evening_tasks.py
2026-10-08 22:46:03 -04:00

133 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""
Fetch candidate tasks for evening planning.
Returns JSON with all open (non-Done, non-Blocked) tasks, for local ranking
by blocking status, "Suggested for today", and due date.
"""
import json
import sys
from datetime import datetime
from notion_helpers import notion, get_data_source_id, query_all, TASKS_DB
def get_evening_tasks():
"""Get all tasks that are not Done and not Blocked."""
pages = query_all(
get_data_source_id(TASKS_DB),
filter={
"and": [
{
"property": "Status",
"status": {
"does_not_equal": "Done"
}
},
{
"property": "Status",
"status": {
"does_not_equal": "Blocked"
}
}
]
},
sorts=[
{
"property": "Due date",
"direction": "ascending"
}
]
)
tasks = []
for page in pages:
props = page["properties"]
due_date = None
if props["Due date"]["date"]:
due_date = props["Due date"]["date"]["start"]
suggested_for_today = None
if props["Suggested for today"]["date"]:
suggested_for_today = props["Suggested for today"]["date"]["start"]
project_names = []
is_personal_project = False
if props["Project"]["relation"]:
for rel in props["Project"]["relation"]:
try:
project = notion.pages.retrieve(page_id=rel["id"])
project_name = project["properties"]["Name"]["title"][0]["plain_text"]
project_names.append(project_name)
project_type = project["properties"]["Type"]["select"]
if project_type and project_type["name"] == "Personal":
is_personal_project = True
except:
pass
if is_personal_project:
continue
has_blocking = len(props["Blocking"]["relation"]) > 0 if props["Blocking"]["relation"] else False
tasks.append({
"id": page["id"],
"url": page["url"],
"name": props["Task name"]["title"][0]["plain_text"] if props["Task name"]["title"] else "",
"due_date": due_date,
"suggested_for_today": suggested_for_today,
"status": props["Status"]["status"]["name"] if props["Status"]["status"] else "",
"priority": props["Priority"]["select"]["name"] if props["Priority"]["select"] else "",
"effort_level": props["Effort level"]["select"]["name"] if props["Effort level"]["select"] else "",
"energy_type": props["Energy type"]["select"]["name"] if props["Energy type"]["select"] else "",
"task_type": [t["name"] for t in props["Task type"]["multi_select"]],
"project": project_names[0] if project_names else "",
"has_blocking": has_blocking,
"top_task": props["Top Task"]["checkbox"]
})
return tasks
def get_marked_top_tasks():
"""Get all tasks currently marked Top Task = true, regardless of status.
Queried separately (without the Done/Blocked exclusion) so a stale Top Task
left checked on a now-Done task is still found and can be unmarked.
"""
pages = query_all(
get_data_source_id(TASKS_DB),
filter={
"property": "Top Task",
"checkbox": {
"equals": True
}
}
)
return [
{
"id": page["id"],
"name": page["properties"]["Task name"]["title"][0]["plain_text"] if page["properties"]["Task name"]["title"] else "",
"status": page["properties"]["Status"]["status"]["name"] if page["properties"]["Status"]["status"] else ""
}
for page in pages
]
def main():
try:
data = {
"tasks": get_evening_tasks(),
"marked_top_tasks": get_marked_top_tasks(),
"generated_at": datetime.now().isoformat()
}
print(json.dumps(data, indent=2))
return 0
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())