Refactors weekly-template's Notion client setup into a shared helper (with .env/dotenv support and full pagination), and adds a dedicated evening-prep script so both commands can fall back to script-based fetching instead of the MCP search/fetch pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
126 lines
4.0 KiB
Python
126 lines
4.0 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 = []
|
|
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)
|
|
except:
|
|
pass
|
|
|
|
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())
|