Initial commits
This commit is contained in:
48
backend/.env
Normal file
48
backend/.env
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\.env
|
||||||
|
|
||||||
|
# .env - Environment variables for the FastAPI Backend
|
||||||
|
|
||||||
|
# --- Frontend URL for CORS configuration ---
|
||||||
|
# Set this to the URL where your React frontend is hosted.
|
||||||
|
# For local development, it's typically http://localhost:3000.
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# --- LLM Provider Configuration ---
|
||||||
|
# Choose which Large Language Model provider to use.
|
||||||
|
# Options:
|
||||||
|
# - GENERIC_API: For any LLM accessible via a standard HTTP API (e.g., OpenAI, Google Gemini, custom APIs)
|
||||||
|
# - HUGGINGFACE: For models loaded locally using the 'transformers' library
|
||||||
|
# - OLLAMA: For local Ollama server deployments
|
||||||
|
LLM_PROVIDER=OLLAMA
|
||||||
|
|
||||||
|
# --- Settings for GENERIC_API Provider (currently commented out as LLM_PROVIDER is OLLAMA) ---
|
||||||
|
# These variables are required if LLM_PROVIDER is set to GENERIC_API.
|
||||||
|
# LLM_API_KEY: Your API key for the chosen generic LLM service.
|
||||||
|
# LLM_API_URL: The API endpoint URL for the chosen generic LLM service.
|
||||||
|
# Examples:
|
||||||
|
# LLM_API_KEY=sk-your_openai_api_key_here
|
||||||
|
# LLM_API_URL=https://api.openai.com/v1/chat/completions
|
||||||
|
|
||||||
|
# For Google Gemini API (if using with a generic setup, though often has specific client libraries)
|
||||||
|
# LLM_API_KEY=your_google_gemini_api_key_here
|
||||||
|
# LLM_API_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent
|
||||||
|
|
||||||
|
# LLM_API_KEY=your_generic_llm_api_key_here
|
||||||
|
# LLM_API_URL=https://api.openai.com/v1/chat/completions
|
||||||
|
|
||||||
|
# --- Settings for HUGGINGFACE Provider (currently commented out as LLM_PROVIDER is OLLAMA) ---
|
||||||
|
# These variables are required if LLM_PROVIDER is set to HUGGINGFACE.
|
||||||
|
# HF_MODEL_NAME: The model identifier from Hugging Face Hub (e.g., google/gemma-2b-it, meta-llama/Llama-2-7b-chat-hf).
|
||||||
|
# Ensure you have sufficient RAM/VRAM for the chosen model.
|
||||||
|
# HUGGING_FACE_HUB_TOKEN: Your Hugging Face access token, often required for private models or higher rate limits.
|
||||||
|
# Uncomment and set this if necessary.
|
||||||
|
# HF_MODEL_NAME=google/gemma-2b-it
|
||||||
|
# HUGGING_FACE_HUB_TOKEN=hf_your_hugging_face_token_here
|
||||||
|
|
||||||
|
# --- Settings for OLLAMA Provider (currently active as LLM_PROVIDER is OLLAMA) ---
|
||||||
|
# These variables are required if LLM_PROVIDER is set to OLLAMA.
|
||||||
|
# OLLAMA_MODEL_NAME: The name of the model downloaded in your local Ollama instance (e.g., llama2, mistral).
|
||||||
|
# OLLAMA_API_URL: The URL of your local Ollama API. Default is typically http://localhost:11434/api/generate.
|
||||||
|
# NOTE: Use "ollama" as the hostname inside Docker Compose, not "localhost"
|
||||||
|
OLLAMA_MODEL_NAME=qwen3:4b # CHANGED: Model updated to deepseek-r1:1.5b
|
||||||
|
OLLAMA_API_URL=http://ollama:11434/api/generate
|
||||||
25
backend/Dockerfile
Normal file
25
backend/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\Dockerfile
|
||||||
|
|
||||||
|
# Use a slim, official Python image to reduce overall image size
|
||||||
|
FROM python:3.10-slim-buster
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the requirements file into the container
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Install dependencies without caching to keep the image lightweight
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy the application source code into the container
|
||||||
|
COPY ./app /app/app
|
||||||
|
COPY main.py /app/main.py
|
||||||
|
|
||||||
|
# Expose the port Uvicorn will use to serve FastAPI
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Define the container startup command to run the FastAPI app via Uvicorn
|
||||||
|
# - main:app -> refers to 'app' inside main.py
|
||||||
|
# - 0.0.0.0 -> binds to all interfaces so it's accessible outside container
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
177
backend/README.md
Normal file
177
backend/README.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# 🧠 AI-Powered Schedule and Task Planning Backend
|
||||||
|
|
||||||
|
This repository contains the backend service for an intelligent schedule and task planning application. It leverages a **Large Language Model (LLM)** (currently using **Qwen1.5–4B via Ollama**) to transform natural language tasks with complex recurrence rules into structured, conflict-free daily schedules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- **Intelligent Scheduling**: Uses a powerful LLM to understand and structure time-bound tasks.
|
||||||
|
- **Flexible Task Input**: Supports daily, weekly, monthly recurrence with specific time rules.
|
||||||
|
- **Advanced Recurrence Handling**: Supports “every 2nd Saturday” or “last Monday of month”.
|
||||||
|
- **Structured JSON Output**: Returns clean machine-readable schedules.
|
||||||
|
- **Pluggable LLM Backend**: Switch between Ollama, HuggingFace, or any generic API.
|
||||||
|
- **FastAPI + Swagger UI**: For live API testing and documentation.
|
||||||
|
- **Dockerized**: Easily run the entire system with Docker Compose.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Technologies Used
|
||||||
|
|
||||||
|
- **FastAPI** – high-performance API framework
|
||||||
|
- **Pydantic** – data validation & schema
|
||||||
|
- **HTTPLX** – async HTTP client for LLM
|
||||||
|
- **Ollama** – running Qwen1.5–4B locally
|
||||||
|
- **Docker + Compose** – containerization
|
||||||
|
- **Python 3.10+**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Project Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
backend/
|
||||||
|
├── main.py # FastAPI entry point
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
├── Dockerfile # Docker image config
|
||||||
|
├── docker-compose.yml # Docker orchestration
|
||||||
|
├── .env # Runtime config
|
||||||
|
└── app/
|
||||||
|
├── models/
|
||||||
|
│ └── task.py # Task schemas
|
||||||
|
├── api/
|
||||||
|
│ └── endpoints.py # FastAPI endpoints
|
||||||
|
└── services/
|
||||||
|
└── llm_service.py # LLM interaction layer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
`.env` example:
|
||||||
|
|
||||||
|
```env
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
LLM_PROVIDER=OLLAMA
|
||||||
|
OLLAMA_MODEL_NAME=qwen:4b
|
||||||
|
OLLAMA_API_URL=http://localhost:11434/api/generate
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Weekly Task Schedule Visualization (Sample Week: July 14–20, 2025)
|
||||||
|
|
||||||
|
| Time | Mon (14) | Tue (15) | Wed (16) | Thu (17) | Fri (18) | Sat (19) | Sun (20) |
|
||||||
|
|--------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|----------------------------------|
|
||||||
|
| 06:00–07:00 | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) |
|
||||||
|
| 07:00–08:00 | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) |
|
||||||
|
| 08:30–09:00 | ☕ Coffee Break | ☕ Coffee Break | ☕ Coffee Break | ☕ Coffee Break | ☕ Coffee Break | | |
|
||||||
|
| 09:00–09:15 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | | |
|
||||||
|
| 09:15–10:00 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | | |
|
||||||
|
| 10:00–11:00 | 🟢 Flex / Board Review | 🔒 🧑💼 Exec Meeting | 🟢 Financial Reporting | 🔒 🧑💼 Deep Code Review | 🟢 🎯 Team Activity | | |
|
||||||
|
| 11:00–12:00 | 🟢 Report Prep ⭐ | 🟢 Report Prep ⭐ | 🟢 Code Review / Reporting | 🔒 Code Finalization 🧑💼 | 🟢 🎯 Team Activity Cont. | 🔒 🧘 HR Audit (2nd Sat) | |
|
||||||
|
| 12:00–13:00 | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch | 🍽 Lunch |
|
||||||
|
| 13:00–14:00 | ⭐ Board Meeting Prep | ⭐ Board Meeting Prep | ⭐ Board Meeting Prep | 🟢 Buffer | 🟢 Buffer | 🟢 Flex Task | 🟢 Flex Task |
|
||||||
|
| 14:00–17:00 | 🟢 Deep Work Block | 🟢 Deep Work | 🟢 Deep Work | 🟢 Deep Work | 🟢 Deep Work | 🟢 Flex Time | 🟢 Flex Time |
|
||||||
|
| 18:00–19:00 | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) |
|
||||||
|
| 21:00–22:00 | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down | 🧘 Wind-down |
|
||||||
|
|
||||||
|
**Legend**:
|
||||||
|
🔒 Fixed • ⚠️ Conflict • ⭐ Preferred • 🟢 Flexible • 🧘 Personal • 🧑💼 Work • 🎯 High Impact
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📬 API Endpoint: `/api/v1/schedule/tasks`
|
||||||
|
|
||||||
|
**POST Request** with list of tasks → returns intelligent JSON schedule.
|
||||||
|
|
||||||
|
Example input:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Daily Stand-up",
|
||||||
|
"task_description": "Critical morning sync.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": "09:00",
|
||||||
|
"end_time": "09:15"
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Every weekday",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 10,
|
||||||
|
"time_unit": "Minutes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scheduled_tasks": [
|
||||||
|
{
|
||||||
|
"task_name": "Daily Stand-up",
|
||||||
|
"date": "2025-07-14",
|
||||||
|
"start_time": "09:00",
|
||||||
|
"end_time": "09:15",
|
||||||
|
"category": "work",
|
||||||
|
"original_task_id": "task1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
### Swagger UI
|
||||||
|
Visit: `http://localhost:8000/docs` → Try `/api/v1/schedule/tasks`
|
||||||
|
|
||||||
|
### Curl
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/api/v1/schedule/tasks \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '[{"type":"daily", "task_name":"Daily Stand-up", "task_description":"Sync","time_settings":{"start_time":"09:00","end_time":"09:15"},"category":"work","repeat_on_1":"Every weekday","repeat_on_2":["M","T","W","T","F"],"reminder_on":{"is_active":true,"numeric_input":10,"time_unit":"Minutes"}}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ LLM Configuration
|
||||||
|
|
||||||
|
**Current Provider**: `OLLAMA`
|
||||||
|
**Model Used**: `qwen:4b`
|
||||||
|
**API URL**: `http://localhost:11434/api/generate`
|
||||||
|
|
||||||
|
Switch to `HUGGINGFACE` or `GENERIC_API` by editing `.env`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Troubleshooting
|
||||||
|
|
||||||
|
- **422 Unprocessable Entity**: Add fallback logic in `llm_service.py` for null times.
|
||||||
|
- **500 Server Error**: LLM may not be running or wrong model name.
|
||||||
|
- **Fix Conflicts**: Use better prompts or larger models for conflict resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Future Enhancements
|
||||||
|
|
||||||
|
- Conflict resolver + dependency chains
|
||||||
|
- User sessions & database storage
|
||||||
|
- Calendar frontend (React)
|
||||||
|
- Notification/reminders
|
||||||
|
- Team role scheduling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT License – See `LICENSE` file.
|
||||||
|
|
||||||
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
BIN
backend/app/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/app/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
BIN
backend/app/api/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/app/api/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/__pycache__/endpoints.cpython-313.pyc
Normal file
BIN
backend/app/api/__pycache__/endpoints.cpython-313.pyc
Normal file
Binary file not shown.
65
backend/app/api/endpoints.py
Normal file
65
backend/app/api/endpoints.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\app\api\endpoints.py
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from app.models.task import TaskCreateRequest, ScheduleResponse
|
||||||
|
from app.services.llm_service import get_scheduled_tasks
|
||||||
|
|
||||||
|
# Initialize the API router
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/schedule/tasks",
|
||||||
|
response_model=ScheduleResponse, # Define the response model for automatic serialization/validation
|
||||||
|
status_code=status.HTTP_200_OK, # Set the default successful status code
|
||||||
|
summary="Create and Schedule Tasks using LLM",
|
||||||
|
description="Receives a list of user-defined tasks, sends them to an LLM for intelligent scheduling, and returns a structured schedule."
|
||||||
|
)
|
||||||
|
async def create_and_schedule_tasks(tasks: List[TaskCreateRequest]):
|
||||||
|
"""
|
||||||
|
Handles the POST request to create and schedule tasks.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tasks (List[TaskCreateRequest]): A list of task objects received from the frontend.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScheduleResponse: A structured response containing the scheduled tasks.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If no tasks are provided, or if there's an error during LLM interaction
|
||||||
|
or internal processing.
|
||||||
|
"""
|
||||||
|
# Validate if any tasks were provided in the request body
|
||||||
|
if not tasks:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="No tasks provided for scheduling. Please submit at least one task."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Call the LLM service to get the scheduled tasks
|
||||||
|
scheduled_response = await get_scheduled_tasks(tasks)
|
||||||
|
return scheduled_response
|
||||||
|
except RuntimeError as e:
|
||||||
|
# Catch errors specifically from LLM interaction issues (e.g., network, API issues)
|
||||||
|
print(f"LLM interaction failed: {e}") # Log the error for debugging
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to schedule tasks due to an issue with the LLM service. Please try again later. Details: {e}"
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
# Catch errors related to invalid data or parsing issues from the LLM's response
|
||||||
|
print(f"Data validation or parsing error: {e}") # Log the error
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, # 422 for semantically erroneous request syntax
|
||||||
|
detail=f"The LLM returned an invalid schedule structure or there was an internal data processing error. Details: {e}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Catch any other unexpected errors
|
||||||
|
print(f"An unexpected error occurred in endpoint: {e}") # Log the error
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"An unexpected error occurred while processing your request. Please contact support. Details: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
BIN
backend/app/models/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/app/models/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/models/__pycache__/task.cpython-313.pyc
Normal file
BIN
backend/app/models/__pycache__/task.cpython-313.pyc
Normal file
Binary file not shown.
69
backend/app/models/task.py
Normal file
69
backend/app/models/task.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\app\models\task.py
|
||||||
|
|
||||||
|
from datetime import time
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
from pydantic import BaseModel, Field, conint, conlist
|
||||||
|
|
||||||
|
# Type aliases for Pydantic constrained types for better readability
|
||||||
|
PositiveInt = conint(gt=0)
|
||||||
|
WeekdayList = conlist(str, min_length=0, max_length=7)
|
||||||
|
|
||||||
|
# Enum for primary recurrence periods
|
||||||
|
class RecurrenceType(str, Enum):
|
||||||
|
daily = "daily"
|
||||||
|
weekly = "weekly"
|
||||||
|
monthly = "monthly"
|
||||||
|
|
||||||
|
# Enum for task categories
|
||||||
|
class TaskCategory(str, Enum):
|
||||||
|
personal = "personal"
|
||||||
|
work = "work"
|
||||||
|
errands = "errands"
|
||||||
|
extra = "extra"
|
||||||
|
|
||||||
|
# Enum for time units used in reminders or durations
|
||||||
|
class TimeUnit(str, Enum):
|
||||||
|
minutes = "Minutes"
|
||||||
|
hours = "Hours"
|
||||||
|
days = "Days"
|
||||||
|
|
||||||
|
# Pydantic model for time settings of a task
|
||||||
|
class TimeSettings(BaseModel):
|
||||||
|
start_time: Optional[time] = Field(None, description="Specific start time (HH:MM)")
|
||||||
|
end_time: Optional[time] = Field(None, description="Specific end time (HH:MM)")
|
||||||
|
duration: Optional[str] = Field(None, description="Duration of the task (e.g., '1 Hour', '30 Minutes'). Used if start_time and end_time are empty or to validate them.")
|
||||||
|
|
||||||
|
# Pydantic model for reminder settings
|
||||||
|
class ReminderOn(BaseModel):
|
||||||
|
is_active: bool = Field(False, description="Whether a reminder is set (true/false)")
|
||||||
|
numeric_input: Optional[PositiveInt] = Field(None, description="Numeric value for reminder (e.g., 10 minutes before)")
|
||||||
|
time_unit: Optional[TimeUnit] = Field(None, description="Unit for reminder time (Minutes, Hours, Days)")
|
||||||
|
|
||||||
|
# Main Pydantic model for the task creation request from the frontend
|
||||||
|
class TaskCreateRequest(BaseModel):
|
||||||
|
type: RecurrenceType = Field(..., description="Defines the primary recurrence period (daily, weekly, monthly)")
|
||||||
|
task_name: str = Field(..., min_length=1, description="A descriptive name for the task")
|
||||||
|
task_description: Optional[str] = Field(None, description="Detailed notes or description of the task")
|
||||||
|
time_settings: TimeSettings = Field(..., description="Time settings for the task, including start, end, and duration")
|
||||||
|
category: TaskCategory = Field(..., description="Categorizes the task (personal, work, errands, extra)")
|
||||||
|
repeat_on_1: Optional[str] = Field(None, description="CRITICAL for complex recurrence patterns. Free-form text field for custom recurrence rules (e.g., 'Every second Monday')")
|
||||||
|
repeat_on_2: Optional[WeekdayList] = Field(
|
||||||
|
None,
|
||||||
|
description="Checkbox selector for days of the week (S, M, T, W, T, F, S). Users can select multiple days."
|
||||||
|
)
|
||||||
|
reminder_on: ReminderOn = Field(..., description="Reminder settings for the task")
|
||||||
|
|
||||||
|
# Pydantic model for a single scheduled task returned by the LLM
|
||||||
|
class ScheduledTask(BaseModel):
|
||||||
|
task_name: str = Field(..., description="Name of the scheduled task")
|
||||||
|
date: str = Field(..., description="Scheduled date in ISO-MM-DD format")
|
||||||
|
start_time: Optional[str] = Field(None, description="Scheduled start time in HH:MM format")
|
||||||
|
end_time: Optional[str] = Field(None, description="Scheduled end time in HH:MM format")
|
||||||
|
category: TaskCategory = Field(..., description="Category of the scheduled task")
|
||||||
|
original_task_id: Optional[str] = Field(None, description="Optional ID to link back to the original task request, useful for updates/tracking")
|
||||||
|
# reminder_on: Optional[ReminderOn] = Field(None, description="Reminder settings for the scheduled task") # Uncomment if LLM output includes this
|
||||||
|
|
||||||
|
# Pydantic model for the overall schedule response from the LLM
|
||||||
|
class ScheduleResponse(BaseModel):
|
||||||
|
scheduled_tasks: List[ScheduledTask] = Field(..., description="List of intelligently assigned scheduled tasks")
|
||||||
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
BIN
backend/app/services/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
backend/app/services/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/services/__pycache__/llm_service.cpython-313.pyc
Normal file
BIN
backend/app/services/__pycache__/llm_service.cpython-313.pyc
Normal file
Binary file not shown.
153
backend/app/services/llm_service.py
Normal file
153
backend/app/services/llm_service.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\app\services\llm_service.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import httpx
|
||||||
|
from typing import List
|
||||||
|
from datetime import date
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from app.models.task import TaskCreateRequest, ScheduleResponse
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
LLM_PROVIDER = os.getenv("LLM_PROVIDER", "OLLAMA")
|
||||||
|
LLM_API_KEY = os.getenv("LLM_API_KEY")
|
||||||
|
LLM_API_URL = os.getenv("LLM_API_URL")
|
||||||
|
HF_MODEL_NAME = os.getenv("HF_MODEL_NAME", "google/gemma-2b-it")
|
||||||
|
OLLAMA_MODEL_NAME = os.getenv("OLLAMA_MODEL_NAME", "deepseek-r1:1.5b")
|
||||||
|
OLLAMA_API_URL = os.getenv("OLLAMA_API_URL", "http://localhost:11434/api/generate")
|
||||||
|
|
||||||
|
tokenizer = None
|
||||||
|
model = None
|
||||||
|
|
||||||
|
if LLM_PROVIDER == "HUGGINGFACE":
|
||||||
|
try:
|
||||||
|
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||||||
|
import torch
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_NAME)
|
||||||
|
model = AutoModelForCausalLM.from_pretrained(HF_MODEL_NAME, torch_dtype=torch.float16)
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
model.to('cuda')
|
||||||
|
print(f"[LLM] Hugging Face model '{HF_MODEL_NAME}' loaded.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Failed to load Hugging Face model: {e}")
|
||||||
|
raise RuntimeError("Hugging Face model loading failed.")
|
||||||
|
|
||||||
|
def infer_priority(task: TaskCreateRequest) -> str:
|
||||||
|
desc = task.task_description.lower()
|
||||||
|
if any(k in desc for k in ["non-negotiable", "critical", "mandatory", "must", "cannot be moved", "very high priority"]):
|
||||||
|
return "high"
|
||||||
|
if any(k in desc for k in ["optional", "nice to have", "if possible", "fun"]):
|
||||||
|
return "low"
|
||||||
|
return "medium"
|
||||||
|
|
||||||
|
async def get_scheduled_tasks(tasks: List[TaskCreateRequest]) -> ScheduleResponse:
|
||||||
|
today_str = date.today().isoformat()
|
||||||
|
prompt_tasks_data = [
|
||||||
|
{
|
||||||
|
**json.loads(task.model_dump_json()),
|
||||||
|
"original_task_id": f"task{index + 1}",
|
||||||
|
"priority": infer_priority(task)
|
||||||
|
}
|
||||||
|
for index, task in enumerate(tasks)
|
||||||
|
]
|
||||||
|
|
||||||
|
system_message = (
|
||||||
|
f"You are an AI-powered schedule and task planning assistant. Your job is to create a conflict-free, "
|
||||||
|
f"intelligent 30-day schedule from today ({today_str}).\n\n"
|
||||||
|
"Parse task details precisely: type, time_settings (start_time, end_time, duration), category, and priority.\n"
|
||||||
|
"Avoid time conflicts. If two fixed-time tasks clash, prioritize the 'high' priority one. Try to shift "
|
||||||
|
"'medium' priority tasks to nearby times. Skip low priority if no space.\n\n"
|
||||||
|
"Respect fixed times first. For flexible tasks, use hints.\n"
|
||||||
|
"Return strictly valid JSON:\n"
|
||||||
|
"{\n"
|
||||||
|
" \"scheduled_tasks\": [\n"
|
||||||
|
" {\n"
|
||||||
|
" \"task_name\": \"string\",\n"
|
||||||
|
" \"date\": \"YYYY-MM-DD\",\n"
|
||||||
|
" \"start_time\": \"HH:MM\",\n"
|
||||||
|
" \"end_time\": \"HH:MM\",\n"
|
||||||
|
" \"category\": \"personal | work | errands | extra\",\n"
|
||||||
|
" \"original_task_id\": \"string\"\n"
|
||||||
|
" }\n"
|
||||||
|
" ]\n"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
user_message = f"Here are the tasks to schedule:\n{json.dumps(prompt_tasks_data, indent=2)}"
|
||||||
|
full_prompt = f"{system_message}\n\n{user_message}"
|
||||||
|
llm_output_content = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if LLM_PROVIDER == "GENERIC_API":
|
||||||
|
if not LLM_API_URL or not LLM_API_KEY:
|
||||||
|
raise ValueError("Missing LLM_API_URL or LLM_API_KEY in .env")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {LLM_API_KEY}"
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": "your-preferred-llm-model-name",
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": system_message},
|
||||||
|
{"role": "user", "content": user_message}
|
||||||
|
],
|
||||||
|
"response_format": {"type": "json_object"}
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=httpx.Timeout(600.0, connect=60.0)) as client:
|
||||||
|
response = await client.post(LLM_API_URL, headers=headers, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
llm_output_content = response.json().get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||||
|
|
||||||
|
elif LLM_PROVIDER == "HUGGINGFACE":
|
||||||
|
import torch
|
||||||
|
input_ids = tokenizer(full_prompt, return_tensors="pt").input_ids
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
input_ids = input_ids.to("cuda")
|
||||||
|
outputs = model.generate(input_ids, max_new_tokens=1024, pad_token_id=tokenizer.eos_token_id)
|
||||||
|
llm_output_content = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
||||||
|
|
||||||
|
elif LLM_PROVIDER == "OLLAMA":
|
||||||
|
payload = {
|
||||||
|
"model": OLLAMA_MODEL_NAME,
|
||||||
|
"prompt": full_prompt,
|
||||||
|
"stream": False,
|
||||||
|
"format": "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=httpx.Timeout(600.0, connect=60.0)) as client:
|
||||||
|
print("[LLM] Sending prompt to Ollama...")
|
||||||
|
response = await client.post(OLLAMA_API_URL, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
llm_output_content = response.json().get("response", "")
|
||||||
|
|
||||||
|
print("[LLM] Received response (first 500 chars):")
|
||||||
|
print(llm_output_content[:500])
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported LLM_PROVIDER: {LLM_PROVIDER}")
|
||||||
|
|
||||||
|
json_start = llm_output_content.find('{')
|
||||||
|
json_end = llm_output_content.rfind('}')
|
||||||
|
if json_start == -1 or json_end == -1:
|
||||||
|
raise ValueError("No valid JSON object found in LLM output.")
|
||||||
|
json_string = llm_output_content[json_start:json_end + 1]
|
||||||
|
parsed_data = json.loads(json_string)
|
||||||
|
return ScheduleResponse(**parsed_data)
|
||||||
|
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
print(f"[ERROR] Ollama returned HTTP {e.response.status_code}: {e.response.text}")
|
||||||
|
raise RuntimeError(f"LLM API returned error {e.response.status_code}: {e.response.text}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
print(f"[ERROR] Request to LLM service failed: {e}")
|
||||||
|
raise RuntimeError(f"Failed to connect to LLM service. Details: {e}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"[ERROR] JSON decoding failed: {e}")
|
||||||
|
print(f"[DEBUG] Raw output: {llm_output_content}")
|
||||||
|
raise ValueError("LLM did not return valid JSON.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Unexpected exception: {e}")
|
||||||
|
raise RuntimeError(f"Failed to schedule tasks due to an issue with the LLM service. Details: {e}")
|
||||||
40
backend/docker-compose.yml
Normal file
40
backend/docker-compose.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./.env:/app/.env
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
ollama:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
volumes:
|
||||||
|
- ollama_data:/root/.ollama
|
||||||
|
container_name: ollama
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Override default entrypoint to allow shell command chaining
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command: >
|
||||||
|
"ollama serve &
|
||||||
|
sleep 5 &&
|
||||||
|
until ollama list > /dev/null 2>&1; do sleep 3; done &&
|
||||||
|
ollama pull qwen3:4b &&
|
||||||
|
wait"
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "ollama", "list"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 6
|
||||||
|
start_period: 120s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ollama_data:
|
||||||
55
backend/main.py
Normal file
55
backend/main.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\main.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from dotenv import load_dotenv # For loading environment variables
|
||||||
|
|
||||||
|
from app.api import endpoints # Import your API router
|
||||||
|
|
||||||
|
# Load environment variables from the .env file.
|
||||||
|
# This should be called early in your application's lifecycle.
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Initialize the FastAPI application instance
|
||||||
|
# Add additional metadata for professional API documentation
|
||||||
|
app = FastAPI(
|
||||||
|
title="AI-Powered Schedule and Task Planning Backend",
|
||||||
|
description="FastAPI backend for ingesting user tasks, using an LLM for intelligent scheduling, and returning structured schedules.",
|
||||||
|
version="1.0.0",
|
||||||
|
docs_url="/docs", # Enable interactive API documentation (Swagger UI)
|
||||||
|
redoc_url="/redoc", # Enable ReDoc documentation
|
||||||
|
openapi_url="/openapi.json" # Specify the OpenAPI schema URL
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure CORS (Cross-Origin Resource Sharing) middleware.
|
||||||
|
# This is crucial for allowing your React frontend (running on a different port/domain)
|
||||||
|
# to make requests to your FastAPI backend.
|
||||||
|
origins = [
|
||||||
|
os.getenv("FRONTEND_URL", "http://localhost:3000"), # Get frontend URL from .env, default to React's development port
|
||||||
|
# Add other origins if your frontend is deployed elsewhere or has multiple access points
|
||||||
|
# e.g., "https://your-production-frontend.com",
|
||||||
|
# "http://localhost:8080" # If testing with another local frontend setup
|
||||||
|
]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins, # List of allowed origins
|
||||||
|
allow_credentials=True, # Allow cookies and authentication headers to be sent
|
||||||
|
allow_methods=["*"], # Allow all HTTP methods (GET, POST, PUT, DELETE, etc.)
|
||||||
|
allow_headers=["*"], # Allow all headers in the request
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include the API router defined in app.api.endpoints.
|
||||||
|
# All routes defined in endpoints.py will be prefixed with "/api/v1".
|
||||||
|
app.include_router(endpoints.router, prefix="/api/v1")
|
||||||
|
|
||||||
|
# Define a root endpoint for basic health check or welcome message
|
||||||
|
@app.get("/", summary="Root Endpoint", description="Provides a welcome message for the API.")
|
||||||
|
async def root():
|
||||||
|
"""
|
||||||
|
Root endpoint for the API.
|
||||||
|
Returns a simple welcome message to indicate the API is running.
|
||||||
|
"""
|
||||||
|
return {"message": "Welcome to the AI-Powered Schedule and Task Planning API! Access docs at /docs"}
|
||||||
|
|
||||||
9
backend/requirements.txt
Normal file
9
backend/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# D:\GNPL-Private\TimeCraft\backend\requirements.txt
|
||||||
|
|
||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
pydantic # Specify a compatible Pydantic version
|
||||||
|
python-dotenv
|
||||||
|
httpx
|
||||||
|
#transformers
|
||||||
|
#torch
|
||||||
275
documentation/README.md
Normal file
275
documentation/README.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# Workflow
|
||||||
|
|
||||||
|
## Input: Json
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX
|
||||||
|
COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX
|
||||||
|
COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX COMPLEX
|
||||||
|
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Critical Daily Stand-up",
|
||||||
|
"task_description": "Non-negotiable daily sync for core project team. Early birds only.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": "09:00",
|
||||||
|
"end_time": "09:15",
|
||||||
|
"duration": null
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Every weekday, but skip public holidays (LLM to infer)",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 5,
|
||||||
|
"time_unit": "Minutes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Daily Team Scrum",
|
||||||
|
"task_description": "New mandatory daily team scrum, might conflict with other meetings. Needs 1 hour.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": "09:00",
|
||||||
|
"end_time": "10:00",
|
||||||
|
"duration": null
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Every Monday to Friday. Very high priority.",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 10,
|
||||||
|
"time_unit": "Minutes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "weekly",
|
||||||
|
"task_name": "Executive Leadership Meeting",
|
||||||
|
"task_description": "Mandatory weekly meeting with executive board. Fixed slot.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": "09:00",
|
||||||
|
"end_time": "10:00",
|
||||||
|
"duration": null
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Every Tuesday, but starting from the 3rd week of the month only. Cannot be moved.",
|
||||||
|
"repeat_on_2": ["T"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 15,
|
||||||
|
"time_unit": "Minutes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "monthly",
|
||||||
|
"task_name": "Monthly HR Check-in",
|
||||||
|
"task_description": "Meet with HR for personal development. Fixed slot, 1 hour.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": "11:00",
|
||||||
|
"end_time": "12:00",
|
||||||
|
"duration": null
|
||||||
|
},
|
||||||
|
"category": "personal",
|
||||||
|
"repeat_on_1": "Every second Saturday of the month.",
|
||||||
|
"repeat_on_2": ["S"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 1,
|
||||||
|
"time_unit": "Days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "monthly",
|
||||||
|
"task_name": "Board Meeting Prep & Review",
|
||||||
|
"task_description": "Extensive preparation for monthly board meeting. Flexible, 8-hour task, needs continuous block if possible.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "8 Hours"
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Any weekday in the first week of every month, as long as it's not Monday or Friday. Prefer early morning. Highly flexible on exact time within that day.",
|
||||||
|
"repeat_on_2": ["T", "W", "T"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 2,
|
||||||
|
"time_unit": "Days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Personal Workout Session",
|
||||||
|
"task_description": "Daily exercise, needs 45 minutes, flexible around work hours. Prioritize morning but accept evening.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "45 Minutes"
|
||||||
|
},
|
||||||
|
"category": "personal",
|
||||||
|
"repeat_on_1": "Daily, prefer 07:00-08:00 AM OR 18:00-19:00 PM, but can be done anytime between 6 AM and 10 PM. Prioritize morning.",
|
||||||
|
"repeat_on_2": ["S", "M", "T", "W", "T", "F", "S"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 30,
|
||||||
|
"time_unit": "Minutes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "weekly",
|
||||||
|
"task_name": "Team Building Activity",
|
||||||
|
"task_description": "Organize a fun team activity. Needs 2 hours, afternoon only.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "2 Hours"
|
||||||
|
},
|
||||||
|
"category": "extra",
|
||||||
|
"repeat_on_1": "Last Friday of every second month (starting from this month), flexible between 2 PM and 5 PM.",
|
||||||
|
"repeat_on_2": ["F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 1,
|
||||||
|
"time_unit": "Days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "monthly",
|
||||||
|
"task_name": "Complex Financial Report",
|
||||||
|
"task_description": "Prepare the complex financial report for audit. Requires 5 hours, ideally split over two consecutive days if necessary.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "5 Hours"
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Any two consecutive weekdays in the third week of the month, prioritizing Tuesday-Wednesday or Wednesday-Thursday. Flexible 09:00-17:00.",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 7,
|
||||||
|
"time_unit": "Days"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Morning Coffee Break",
|
||||||
|
"task_description": "A short 15-minute break. Highly flexible, but must be before 11 AM, avoiding any fixed meetings.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "15 Minutes"
|
||||||
|
},
|
||||||
|
"category": "personal",
|
||||||
|
"repeat_on_1": "Daily, any time between 09:15 and 11:00 AM, avoiding other fixed meetings.",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": false,
|
||||||
|
"numeric_input": null,
|
||||||
|
"time_unit": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "weekly",
|
||||||
|
"task_name": "Deep Dive Code Review",
|
||||||
|
"task_description": "Dedicated slot for reviewing complex PRs. Needs 3 hours, strictly afternoon.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "3 Hours"
|
||||||
|
},
|
||||||
|
"category": "work",
|
||||||
|
"repeat_on_1": "Every Thursday, after 11:00 AM, but before 4:00 PM. Cannot overlap with any other work meetings.",
|
||||||
|
"repeat_on_2": ["T"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 4,
|
||||||
|
"time_unit": "Hours"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "monthly",
|
||||||
|
"task_name": "Personal Finance Audit",
|
||||||
|
"task_description": "Review personal budget and investments. 1 hour, weekday only.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "1 Hour"
|
||||||
|
},
|
||||||
|
"category": "personal",
|
||||||
|
"repeat_on_1": "The 15th of every month, if it's a weekday. Otherwise, the closest preceding weekday.",
|
||||||
|
"repeat_on_2": ["M", "T", "W", "T", "F"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": true,
|
||||||
|
"numeric_input": 24,
|
||||||
|
"time_unit": "Hours"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "daily",
|
||||||
|
"task_name": "Evening Wind-Down",
|
||||||
|
"task_description": "Read a book or meditate. 30 minutes, before bed. No fixed time.",
|
||||||
|
"time_settings": {
|
||||||
|
"start_time": null,
|
||||||
|
"end_time": null,
|
||||||
|
"duration": "30 Minutes"
|
||||||
|
},
|
||||||
|
"category": "personal",
|
||||||
|
"repeat_on_1": "Daily, between 9 PM and 11 PM. Avoid any fixed late-night calls.",
|
||||||
|
"repeat_on_2": ["S", "M", "T", "W", "T", "F", "S"],
|
||||||
|
"reminder_on": {
|
||||||
|
"is_active": false,
|
||||||
|
"numeric_input": null,
|
||||||
|
"time_unit": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# 📅 Weekly Task Schedule Visualization
|
||||||
|
|
||||||
|
**Sample Week: July 14–20, 2025**
|
||||||
|
|
||||||
|
| Time | Mon (14) | Tue (15) | Wed (16) | Thu (17) | Fri (18) | Sat (19) | Sun (20) |
|
||||||
|
|-------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|---------------------------------|
|
||||||
|
| 06:00–07:00 | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) | 🟢 🧘 Workout (Flexible) |
|
||||||
|
| 07:00–08:00 | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) | ⭐ 🧘 Workout (Preferred) |
|
||||||
|
| 08:30–09:00 | 🟢 ☕ Coffee Break | 🟢 ☕ Coffee Break | 🟢 ☕ Coffee Break | 🟢 ☕ Coffee Break | 🟢 ☕ Coffee Break | | |
|
||||||
|
| 09:00–09:15 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | 🔒 ⚠️ Stand-up + Scrum 🎯 | | |
|
||||||
|
| 09:15–10:00 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | ⚠️ Scrum Continues 🎯 | | |
|
||||||
|
| 10:00–11:00 | 🟢 Board Prep / Overflow Slot | 🔒 🧑💼 Exec Leadership Meeting | 🟢 Report Split (Day 2) | 🔒 🧑💼 Deep Code Review | 🟢 🎯 Team Activity Slot | | |
|
||||||
|
| 11:00–12:00 | 🟢 Report Prep ⭐ | 🟢 Report Prep ⭐ | 🟢 Report or Code Review | 🔒 🧑💼 Code Review Ends | 🟢 Team Activity Cont. | 🔒 🧘 HR Check-in (2nd Sat) | |
|
||||||
|
| 12:00–13:00 | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow | 🟢 🍽 Lunch / Overflow |
|
||||||
|
| 13:00–14:00 | 🟢 ⭐ Board Meeting Prep | 🟢 ⭐ Board Meeting Prep | 🟢 ⭐ Board Meeting Prep | 🟢 Board Prep / Flex | 🟢 Team Activity Overflow | 🟢 Free | 🟢 Free |
|
||||||
|
| 14:00–17:00 | 🟢 Deep Work / Report Block | 🟢 Deep Work | 🟢 Deep Work | 🟢 Deep Work | 🟢 Deep Work | 🟢 Flex Time | 🟢 Flex Time |
|
||||||
|
| 18:00–19:00 | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) | 🧘 Workout (Fallback) |
|
||||||
|
| 21:00–22:00 | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down | 🧘 Evening Wind-Down |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔵 Legend
|
||||||
|
- 🔒 = Fixed time slot
|
||||||
|
- ⚠️ = Conflict or overlap
|
||||||
|
- ⭐ = Preferred or high-priority
|
||||||
|
- 🧘 = Personal task
|
||||||
|
- 🧑💼 = Work task
|
||||||
|
- 🎯 = High-impact / urgent
|
||||||
|
- 🟢 = Flexible slot
|
||||||
|
- ☕ = Break
|
||||||
|
- 🍽 = Lunch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧠 Notes & Insights
|
||||||
|
- **09:00 Conflict:** Critical Daily Stand-up and Team Scrum overlap every weekday → consider merging or shifting.
|
||||||
|
- **Board Prep & Financial Reports** are scheduled with max flexibility to avoid blocking.
|
||||||
|
- **Workouts** are morning-preferred with evening fallback for sustainability.
|
||||||
|
- **Personal wellness tasks** are spread across early morning and late evening with no overlaps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Want this in `.csv`, `.ics`, or as a visual HTML calendar? Just let me know.
|
||||||
Reference in New Issue
Block a user