From 202e51c8a18398ef7cf3ab813ac2effb9925941a Mon Sep 17 00:00:00 2001 From: Hardik Sankhla Date: Fri, 27 Jun 2025 05:56:40 +0530 Subject: [PATCH] Initial commits --- backend/.env | 48 +++ backend/Dockerfile | 25 ++ backend/README.md | 177 +++++++++++ backend/__pycache__/main.cpython-313.pyc | Bin 0 -> 1571 bytes backend/app/__init__.py | 0 .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 146 bytes backend/app/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 150 bytes .../api/__pycache__/endpoints.cpython-313.pyc | Bin 0 -> 2800 bytes backend/app/api/endpoints.py | 65 +++++ backend/app/models/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 153 bytes .../models/__pycache__/task.cpython-313.pyc | Bin 0 -> 5029 bytes backend/app/models/task.py | 69 +++++ backend/app/services/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes .../__pycache__/llm_service.cpython-313.pyc | Bin 0 -> 9180 bytes backend/app/services/llm_service.py | 153 ++++++++++ backend/docker-compose.yml | 40 +++ backend/main.py | 55 ++++ backend/requirements.txt | 9 + documentation/README.md | 275 ++++++++++++++++++ 22 files changed, 916 insertions(+) create mode 100644 backend/.env create mode 100644 backend/Dockerfile create mode 100644 backend/README.md create mode 100644 backend/__pycache__/main.cpython-313.pyc create mode 100644 backend/app/__init__.py create mode 100644 backend/app/__pycache__/__init__.cpython-313.pyc create mode 100644 backend/app/api/__init__.py create mode 100644 backend/app/api/__pycache__/__init__.cpython-313.pyc create mode 100644 backend/app/api/__pycache__/endpoints.cpython-313.pyc create mode 100644 backend/app/api/endpoints.py create mode 100644 backend/app/models/__init__.py create mode 100644 backend/app/models/__pycache__/__init__.cpython-313.pyc create mode 100644 backend/app/models/__pycache__/task.cpython-313.pyc create mode 100644 backend/app/models/task.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/__pycache__/__init__.cpython-313.pyc create mode 100644 backend/app/services/__pycache__/llm_service.cpython-313.pyc create mode 100644 backend/app/services/llm_service.py create mode 100644 backend/docker-compose.yml create mode 100644 backend/main.py create mode 100644 backend/requirements.txt create mode 100644 documentation/README.md diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..b88eab2 --- /dev/null +++ b/backend/.env @@ -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 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..94a2eab --- /dev/null +++ b/backend/Dockerfile @@ -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"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..2ca1b28 --- /dev/null +++ b/backend/README.md @@ -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. + diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2aa76f557085343885c92bada0e55b304b3b1ac GIT binary patch literal 1571 zcma)6&2Jk;6rb5of5axS)3`~Y9U2fAHMUEYB1Z@*^aD9bQ_CB~#lc#3C-&0yjy1D( z=$=ABYLB__Kfncv|A0Ti6^B|Y^@KR|<|rp_yj^cXfF4+BX5R1Z@4b0%-+OzS%ViKe z!7uy0pVJ8a9)Yt5aQvJuVuXG`1~TwA;?XH><1L9xB$DNAc}w9+EGygUmd3SMR=4%7 z6i*S<)~=)V#1$avIx;jvH&RC0$QW5;uCDQP8?_b_%7p`%IU~O=%~Vy)v+ZN)q3;4TBBeT>yQoDTzk#iMdOmOWR&W%ae3uD6F%R? z<>glqeYcE5{k|#u&1M6{{GEp_H&n4V4cO!0^`UDNX%uS-5Ptw&%KjFMZ(S2G1UV4MJf3O}L^1?9vC zOK}6_NMm|rXXime^Qr4PUF!Kn%&r2)jrcZB{UMKU5kt3j^Cm{0B{)d}8f zayD`x8Df${>RPN%NuLT~c4_=Y{T?My@=93>busMsP5zjtpdYM@Jrp(Xcd$+iu&p8z z4H7n_8h!_TBp=|i9PDS}YtNeEAFea4xH3@$!s!p7IsP4p&!7Qp$1;6Nl7NUK2p`6s zQ^|yS5^}KnMUn%U#LWNIdXV7TWYe-BZxU6Im_CWy;3vc{M|r_QVfpSx`?KBV&RUZ@ zBM7Cv=k)0vZXWpUq*dE})A1^U$DzXEBlBoBYp_HSwpZu~P3KT`%@^O zf1>KRybwxEg!%!@7u1Z)pbtw4s`SsttYR@Pcw`l7vq|G>1axySs6>WZgk(UW)}_%T zhWc6ih3k_*9SDfS)C;6qx{I!#u zu1iPqbOovU)5ep=GjDu#{Ws~2prB&knJ->P{wB#AY&>+I)f&o-%5reCLr%KNa~iMi&c!fUx1Hp zKv8B{Vo7RDNM>%Tb5UYiNla2=a&~H7N=#xwK}>vnW?p7Ve7s&k7%Q6rSDnU)G6}HvMrLHB6f#YDnC_q(4^E%1I2(Pu<{kicnmc?d~|))>)gG z-K2@26gY6;%n5OUOSu&;RNUH1oE=fA(W;e@sDyfR6$D6d;LZNTDT+{6vS;S)d*6HW z=6i2;qoYGc@Qi$UW#OwhLcj7w@WlMd#@8_U0x3ulrqHB7g-MZ$lM&8>&{W%`Ol6Ep!a+3b!_jytUg!y&v_>Udij}0ozLq4IDG?>A#Lk43 zwoREreI`!->g)1_z&M7OITVD*~G2&i|@6RXU8xn8!wh^=-W z2_#26LB9utRa3_S2<8qLwwYnh|zaI zMogVyYk@3cs|JwV7*}c(8>U67Rb!r*7WM-G>;cy+C6-N14$UlTYp?*oAj)(q(u z1wDz-+@|)N=!;Py6wF$qA}Hb+kqCWgE*eN@(XeklffQ*$LTE|&Ldu8RktxgyhfoaV zBL~o2Ku;7!Y8oL^oC5-Tj}n>{6X=8(;Nb1!X%F;UqZ0Nky{Ht)6tiMQm`em2+xUvS zl&`J;p*1SeZGG(wG`8^-dnsSr?W=7&UllYWy@D#jDXAi)Wc33`yLE&_B;f&6%;6k+`{*O;#dQ7)i8PGakwC6zr;AIYB1u5 z{X(b6;E4)0YyMtTN^qUlmJFTf1DKa!g#%LZ8dREz38f&@ZR(e}TcK3dc*}7!!n3mF z@oSixYL&xkgz@H(mRCpffycm`Tkw9|TnEooiOOIr_SHqh!m5S&8hVIH+*s2ok@7Nd zB07PsSM#e$-Zps4@B_jX5EDIvSKoWno$-SWGUM-8w*k%VIR;}eUpA}-H-j7tTGf)F zk9d1cPd+>c|B`l+ax#Yz&3E>omx%PY|5J? z#1BlX*8mTqy7Ire%L|agt-e-jHVq18GchjvH0aTGyCX+vd%fk|^(S&dz`ddO4^f zM<5J(FaiQQC&x3TxAHU7gEQ1vg3~S)A-XZDR;u7WxzP;nN)JP0Gj12~O zBWuZ#_1$)3X}MwFXmqc%fdBqIhT8YEjE~|fO^x+P4|>8df6ym^ zNj}x-L@UrC;7BMx#Vy2(SSxktDU@I4PA8mO+WjP8Y2KA7zm6%tW1S>_vukgRZXj4}bb|Of7t=%F=wEAD0}KEF literal 0 HcmV?d00001 diff --git a/backend/app/api/endpoints.py b/backend/app/api/endpoints.py new file mode 100644 index 0000000..3b4bf46 --- /dev/null +++ b/backend/app/api/endpoints.py @@ -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}" + ) + diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/__pycache__/__init__.cpython-313.pyc b/backend/app/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c36374bd718801cee96c4ccfb782381a113f58d GIT binary patch literal 153 zcmey&%ge<81Y2grW`O9&AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl;;7poX|zW^WI zfTGN@#FEsQkj&gv=c2^4l9;5#K!nE3e2yv&mLc)fzkTO2k( b!P1;myCPPgK_H8YL5z>gjEsy$%s>_ZF{vdA literal 0 HcmV?d00001 diff --git a/backend/app/models/__pycache__/task.cpython-313.pyc b/backend/app/models/__pycache__/task.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5bea916df4da9856428abce8b9bbef31cdff170 GIT binary patch literal 5029 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 LYrjeO7C!tB19AZi literal 0 HcmV?d00001 diff --git a/backend/app/models/task.py b/backend/app/models/task.py new file mode 100644 index 0000000..666f96b --- /dev/null +++ b/backend/app/models/task.py @@ -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") diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/__pycache__/__init__.cpython-313.pyc b/backend/app/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce3bc30f97159918f6a73689b357ef2005b9a872 GIT binary patch literal 155 zcmey&%ge<81Yt8{GeGoX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iAi&c!fUx1Hp zKv8B{Vo7RDNM>%Tb5UYiNla2=a&~H7N=#xwK}>OKQCVhkYH>__d}dx|NqoFsLFFwD eo80`A(wtPgB37VLAghW&jE~HWjEqIhKo$U7AtjFh literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/llm_service.cpython-313.pyc b/backend/app/services/__pycache__/llm_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c8a72d18319565cfb810955e7ced88a2cde96e9 GIT binary patch literal 9180 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 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}") diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..c7d49c3 --- /dev/null +++ b/backend/docker-compose.yml @@ -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: diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..3918c44 --- /dev/null +++ b/backend/main.py @@ -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"} + diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..b133673 --- /dev/null +++ b/backend/requirements.txt @@ -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 diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..35008a4 --- /dev/null +++ b/documentation/README.md @@ -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.