Smart To-Do List¶
Difficulty: Intermediate
Time: 60-75 minutes
Learning Focus: Data structures, file I/O, date handling, AI assistance
Module: chat
Overview¶
Build a smart to-do list application that helps users organise tasks with categories, priorities, and due dates. The application provides AI-assisted recommendations for task management and organisation.
Instructions¶
import os
import json
from datetime import datetime, timedelta
from hands_on_ai.chat import get_response
class SmartTodoList:
"""
A smart to-do list that can categorize tasks, set priorities,
track due dates, and provide AI-assisted task management.
"""
def __init__(self):
self.tasks = []
self.categories = ["Work", "School", "Personal", "Shopping", "Health", "Other"]
self.priorities = ["High", "Medium", "Low"]
self.data_dir = "todo_data"
self.data_file = os.path.join(self.data_dir, "tasks.json")
# Create data directory if it doesn't exist
os.makedirs(self.data_dir, exist_ok=True)
# Load existing tasks if available
self.load_tasks()
def load_tasks(self):
"""Load tasks from the data file."""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r') as f:
self.tasks = json.load(f)
print(f"Loaded {len(self.tasks)} tasks from {self.data_file}")
except json.JSONDecodeError:
print("Error reading tasks file. Starting with empty task list.")
self.tasks = []
else:
print("No existing tasks file found. Starting with empty task list.")
self.tasks = []
def save_tasks(self):
"""Save tasks to the data file."""
with open(self.data_file, 'w') as f:
json.dump(self.tasks, f, indent=2)
print(f"Saved {len(self.tasks)} tasks to {self.data_file}")
def add_task(self):
"""Add a new task to the list."""
print("\n=== Add New Task ===")
# Get task details
title = input("Task title: ")
# Select category
print("\nCategories:")
for i, category in enumerate(self.categories, 1):
print(f"{i}. {category}")
category_choice = input(f"Select category (1-{len(self.categories)}): ")
try:
category_idx = int(category_choice) - 1
category = self.categories[category_idx]
except (ValueError, IndexError):
print("Invalid category selection. Using 'Other'.")
category = "Other"
# Select priority
print("\nPriorities:")
for i, priority in enumerate(self.priorities, 1):
print(f"{i}. {priority}")
priority_choice = input(f"Select priority (1-{len(self.priorities)}): ")
try:
priority_idx = int(priority_choice) - 1
priority = self.priorities[priority_idx]
except (ValueError, IndexError):
print("Invalid priority selection. Using 'Medium'.")
priority = "Medium"
# Set due date
due_date = None
has_due_date = input("\nDoes this task have a due date? (y/n): ").lower() == 'y'
if has_due_date:
date_format = "%Y-%m-%d"
date_input = input("Enter due date (YYYY-MM-DD) or relative (e.g., 'tomorrow', '3 days'): ")
try:
# Parse relative dates
if date_input.lower() == 'today':
due_date = datetime.now().strftime(date_format)
elif date_input.lower() == 'tomorrow':
due_date = (datetime.now() + timedelta(days=1)).strftime(date_format)
elif 'days' in date_input.lower():
# Parse "X days" format
try:
days = int(date_input.split()[0])
due_date = (datetime.now() + timedelta(days=days)).strftime(date_format)
except (ValueError, IndexError):
print("Could not parse relative date. Please enter a specific date.")
else:
# Try to parse as YYYY-MM-DD
due_date = datetime.strptime(date_input, date_format).strftime(date_format)
except ValueError:
print("Invalid date format. Due date will not be set.")
# Add notes
notes = input("\nAdd any notes (optional): ")
# Create task object
task = {
"id": len(self.tasks) + 1,
"title": title,
"category": category,
"priority": priority,
"due_date": due_date,
"notes": notes,
"completed": False,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# Add to task list
self.tasks.append(task)
print(f"\nTask '{title}' added successfully!")
# Save updated tasks
self.save_tasks()
def view_tasks(self, show_completed=False):
"""Display tasks based on filters."""
if not self.tasks:
print("\nNo tasks found.")
return
filtered_tasks = [t for t in self.tasks if t["completed"] == show_completed]
if not filtered_tasks:
status = "completed" if show_completed else "pending"
print(f"\nNo {status} tasks found.")
return
# Sort tasks: first by due date (None at the end), then by priority
def sort_key(task):
# Priority order: High, Medium, Low
priority_order = {"High": 0, "Medium": 1, "Low": 2}
# Sort by due date first (None/null dates come last)
if task["due_date"]:
return (0, task["due_date"], priority_order.get(task["priority"], 1))
else:
return (1, "9999-99-99", priority_order.get(task["priority"], 1))
sorted_tasks = sorted(filtered_tasks, key=sort_key)
# Display tasks
status = "Completed" if show_completed else "Pending"
print(f"\n=== {status} Tasks ===")
for i, task in enumerate(sorted_tasks, 1):
due_str = f"Due: {task['due_date']}" if task['due_date'] else "No due date"
# Add warning for tasks due today or overdue
warning = ""
if task['due_date']:
try:
due_date = datetime.strptime(task['due_date'], "%Y-%m-%d").date()
today = datetime.now().date()
if due_date < today and not task['completed']:
warning = " [OVERDUE!]"
elif due_date == today and not task['completed']:
warning = " [DUE TODAY!]"
except ValueError:
pass
print(f"{i}. [{task['priority']}] {task['title']}{warning} - {due_str} ({task['category']})")
# Return the sorted tasks for selection
return sorted_tasks
def toggle_task_status(self):
"""Mark a task as completed or pending."""
print("\n=== Toggle Task Status ===")
# Show pending tasks first
pending_tasks = self.view_tasks(show_completed=False)
if pending_tasks:
# Show completed tasks
print("\n=== Completed Tasks ===")
completed_tasks = self.view_tasks(show_completed=True)
# Ask which list to toggle from
toggle_from = input("\nToggle task from (p)ending or (c)ompleted list? ").lower()
if toggle_from == 'p' and pending_tasks:
task_list = pending_tasks
current_status = False
elif toggle_from == 'c' and completed_tasks:
task_list = completed_tasks
current_status = True
else:
print("Invalid selection or no tasks in that category.")
return
# Get task number
task_num = input(f"Enter task number to toggle (1-{len(task_list)}): ")
try:
idx = int(task_num) - 1
selected_task = task_list[idx]
# Find this task in the main task list and toggle its status
for task in self.tasks:
if task["id"] == selected_task["id"]:
task["completed"] = not current_status
status = "completed" if task["completed"] else "pending"
print(f"\nTask '{task['title']}' marked as {status}.")
break
# Save updated tasks
self.save_tasks()
except (ValueError, IndexError):
print("Invalid task number.")
def edit_task(self):
"""Edit an existing task."""
print("\n=== Edit Task ===")
# Show all tasks for selection
print("\nAll Tasks:")
all_tasks = self.tasks.copy()
# Sort tasks by completion status, then by other criteria
def sort_key(task):
return (task["completed"], task.get("due_date", "9999-99-99"), task["priority"])
sorted_tasks = sorted(all_tasks, key=sort_key)
for i, task in enumerate(sorted_tasks, 1):
status = "✓" if task["completed"] else "☐"
due_str = f"Due: {task['due_date']}" if task['due_date'] else "No due date"
print(f"{i}. {status} [{task['priority']}] {task['title']} - {due_str} ({task['category']})")
# Get task to edit
task_num = input(f"\nEnter task number to edit (1-{len(sorted_tasks)}): ")
try:
idx = int(task_num) - 1
selected_task = sorted_tasks[idx]
print(f"\nEditing task: {selected_task['title']}")
# Get updated values
title = input(f"Title [{selected_task['title']}]: ") or selected_task['title']
# Select category
print("\nCategories:")
for i, category in enumerate(self.categories, 1):
print(f"{i}. {category}")
category_choice = input(f"Select category [current: {selected_task['category']}]: ")
if category_choice:
try:
category_idx = int(category_choice) - 1
category = self.categories[category_idx]
except (ValueError, IndexError):
print("Invalid category selection. Keeping current category.")
category = selected_task['category']
else:
category = selected_task['category']
# Select priority
print("\nPriorities:")
for i, priority in enumerate(self.priorities, 1):
print(f"{i}. {priority}")
priority_choice = input(f"Select priority [current: {selected_task['priority']}]: ")
if priority_choice:
try:
priority_idx = int(priority_choice) - 1
priority = self.priorities[priority_idx]
except (ValueError, IndexError):
print("Invalid priority selection. Keeping current priority.")
priority = selected_task['priority']
else:
priority = selected_task['priority']
# Update due date
current_due = selected_task['due_date'] or "None"
due_choice = input(f"Update due date? Current: {current_due} (y/n): ").lower()
if due_choice == 'y':
date_format = "%Y-%m-%d"
date_input = input("Enter due date (YYYY-MM-DD) or relative (e.g., 'tomorrow', '3 days'): ")
try:
# Parse relative dates
if date_input.lower() == 'today':
due_date = datetime.now().strftime(date_format)
elif date_input.lower() == 'tomorrow':
due_date = (datetime.now() + timedelta(days=1)).strftime(date_format)
elif 'days' in date_input.lower():
# Parse "X days" format
try:
days = int(date_input.split()[0])
due_date = (datetime.now() + timedelta(days=days)).strftime(date_format)
except (ValueError, IndexError):
print("Could not parse relative date. Keeping current due date.")
due_date = selected_task['due_date']
elif date_input.lower() in ('none', 'remove', 'clear'):
due_date = None
else:
# Try to parse as YYYY-MM-DD
due_date = datetime.strptime(date_input, date_format).strftime(date_format)
except ValueError:
print("Invalid date format. Keeping current due date.")
due_date = selected_task['due_date']
else:
due_date = selected_task['due_date']
# Update notes
current_notes = selected_task['notes'] or "None"
notes_choice = input(f"Update notes? Current: {current_notes} (y/n): ").lower()
if notes_choice == 'y':
notes = input("Enter new notes: ")
else:
notes = selected_task['notes']
# Find this task in the main task list and update it
for task in self.tasks:
if task["id"] == selected_task["id"]:
task["title"] = title
task["category"] = category
task["priority"] = priority
task["due_date"] = due_date
task["notes"] = notes
print(f"\nTask '{title}' updated successfully!")
break
# Save updated tasks
self.save_tasks()
except (ValueError, IndexError):
print("Invalid task number.")
def delete_task(self):
"""Delete a task from the list."""
print("\n=== Delete Task ===")
# Show all tasks for selection
print("\nAll Tasks:")
all_tasks = self.tasks.copy()
# Sort tasks by completion status, then by other criteria
def sort_key(task):
return (task["completed"], task.get("due_date", "9999-99-99"), task["priority"])
sorted_tasks = sorted(all_tasks, key=sort_key)
for i, task in enumerate(sorted_tasks, 1):
status = "✓" if task["completed"] else "☐"
due_str = f"Due: {task['due_date']}" if task['due_date'] else "No due date"
print(f"{i}. {status} [{task['priority']}] {task['title']} - {due_str} ({task['category']})")
# Get task to delete
task_num = input(f"\nEnter task number to delete (1-{len(sorted_tasks)}): ")
try:
idx = int(task_num) - 1
selected_task = sorted_tasks[idx]
# Confirm deletion
confirm = input(f"Are you sure you want to delete '{selected_task['title']}'? (y/n): ").lower()
if confirm == 'y':
# Remove task from list
self.tasks = [t for t in self.tasks if t["id"] != selected_task["id"]]
print(f"\nTask '{selected_task['title']}' deleted successfully!")
# Save updated tasks
self.save_tasks()
else:
print("Deletion cancelled.")
except (ValueError, IndexError):
print("Invalid task number.")
def get_ai_recommendations(self):
"""Get AI-assisted recommendations for task management."""
if not self.tasks:
print("\nNo tasks found. Please add some tasks first.")
return
print("\n=== AI Task Management Recommendations ===")
print("Analyzing your tasks...")
try:
# Prepare task data for AI
today = datetime.now().date()
# Count tasks by category
category_counts = {}
for task in self.tasks:
cat = task["category"]
if cat in category_counts:
category_counts[cat] += 1
else:
category_counts[cat] = 1
# Count overdue tasks
overdue_tasks = []
for task in self.tasks:
if task["due_date"] and not task["completed"]:
try:
due_date = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
if due_date < today:
overdue_tasks.append({
"title": task["title"],
"due_date": task["due_date"],
"days_overdue": (today - due_date).days,
"priority": task["priority"]
})
except ValueError:
pass
# Get tasks due today
today_tasks = []
for task in self.tasks:
if task["due_date"] and not task["completed"]:
try:
due_date = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
if due_date == today:
today_tasks.append({
"title": task["title"],
"priority": task["priority"],
"category": task["category"]
})
except ValueError:
pass
# Get high priority tasks
high_priority = []
for task in self.tasks:
if task["priority"] == "High" and not task["completed"]:
high_priority.append({
"title": task["title"],
"due_date": task["due_date"],
"category": task["category"]
})
# Create prompt for AI
prompt = f"""
Based on the following task data, please provide helpful task management recommendations:
Task summary:
- Total tasks: {len(self.tasks)}
- Completed tasks: {sum(1 for t in self.tasks if t["completed"])}
- Pending tasks: {sum(1 for t in self.tasks if not t["completed"])}
Category breakdown: {category_counts}
Overdue tasks ({len(overdue_tasks)}):
{overdue_tasks if overdue_tasks else "None"}
Tasks due today ({len(today_tasks)}):
{today_tasks if today_tasks else "None"}
High priority pending tasks ({len(high_priority)}):
{high_priority if high_priority else "None"}
Please provide:
1. A prioritized action plan for the next 24 hours
2. Task management tips based on the current workload
3. Suggestions for which tasks to focus on first
Keep your response friendly, practical and under 300 words.
"""
# Get AI recommendations
recommendations = get_response(prompt)
print("\n" + recommendations)
except Exception as e:
print(f"Error getting AI recommendations: {e}")
print("Unable to generate AI recommendations at this time.")
def run(self):
"""Run the main to-do list interface."""
print("=== Smart To-Do List ===")
while True:
print("\nOptions:")
print("1. Add new task")
print("2. View pending tasks")
print("3. View completed tasks")
print("4. Toggle task status")
print("5. Edit task")
print("6. Delete task")
print("7. Get AI recommendations")
print("8. Exit")
choice = input("\nSelect an option (1-8): ")
if choice == '1':
self.add_task()
elif choice == '2':
self.view_tasks(show_completed=False)
elif choice == '3':
self.view_tasks(show_completed=True)
elif choice == '4':
self.toggle_task_status()
elif choice == '5':
self.edit_task()
elif choice == '6':
self.delete_task()
elif choice == '7':
self.get_ai_recommendations()
elif choice == '8':
print("\nExiting Smart To-Do List. Goodbye!")
break
else:
print("Invalid choice. Please select a number between 1 and 8.")
# Run the to-do list
if __name__ == "__main__":
todo_list = SmartTodoList()
todo_list.run()
Extension Ideas¶
- Add recurring tasks (daily, weekly, monthly)
- Implement task dependencies (tasks that require other tasks to be completed first)
- Create a calendar view to visualise task distribution
- Add a Pomodoro timer feature for focused work sessions
- Implement task sharing or collaboration features
- Create a mobile-friendly web interface using a framework like Flask