diff --git a/ConversionToExcel.py b/ConversionToExcel.py new file mode 100644 index 0000000..2b6e9e9 --- /dev/null +++ b/ConversionToExcel.py @@ -0,0 +1,44 @@ +import pandas as pd +import yaml + +# Load the YAML file +with open("./interview_database.yaml", 'r') as file: + interview_data = yaml.safe_load(file) + +# Access the nested structure within 'Data' +flattened_data = [] +for date, date_info in interview_data['Data'].items(): + meeting_duration = date_info.get('Meeting Duration') + + for start_time_info in date_info.get('Meeting Start Times', []): + for start_time, meeting_info in start_time_info.items(): + interviewer_name = meeting_info['Interviewer'][0].get('Name') + interviewer_email = meeting_info['Interviewer'][1].get('Email') + interviewee_name = meeting_info['Interviewee'][0].get('Name') + interviewee_email = meeting_info['Interviewee'][1].get('Email') + category = meeting_info.get('Category') + status = meeting_info.get('Status') + slot = meeting_info.get('Slot') + + # Add flattened row to list + flattened_data.append({ + 'Date': date, + 'Meeting Duration': meeting_duration, + 'Start Time': start_time, + 'Interviewer Name': interviewer_name, + 'Interviewer Email': interviewer_email, + 'Interviewee Name': interviewee_name, + 'Interviewee Email': interviewee_email, + 'Category': category, + 'Status': status, + 'Slot': slot + }) + +# Convert to DataFrame if flattened data is not empty +if flattened_data: + df = pd.DataFrame(flattened_data) + # Write the DataFrame to an Excel file + df.to_excel("interview_database.xlsx", index=False) + print("Data has been written to interview_database.xlsx") +else: + print("No data found to write.") diff --git a/TestBookAppointment.py b/TestBookAppointment.py new file mode 100644 index 0000000..f076d51 --- /dev/null +++ b/TestBookAppointment.py @@ -0,0 +1,11 @@ +from WriteDB import AppendAppointment + +def TestBookAppointment(): + date_1 = "2024-09-16" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) + +TestBookAppointment() \ No newline at end of file diff --git a/WriteDB.py b/WriteDB.py new file mode 100644 index 0000000..cf9ad46 --- /dev/null +++ b/WriteDB.py @@ -0,0 +1,141 @@ +import pandas as pd +import json +from openpyxl import load_workbook +from send_email import send_email +from filelock import FileLock + +# Define the path to the Excel file and the lock file +file_path = "./interview_database.xlsx" +lock_path = "./interview_database.xlsx.lock" # Lock file for synchronization + +def ReadDatabase(): + """ + Reads the Database to retrieve available interview slots. + + ``REQUIRES``: None + + ``PROMISES``: JSON (Available interview slots) + + ``Developed by``: Ahmad + + ``Contact``: ahmad.ahmad1@ucalgary.ca + """ + # Use a file-based lock for thread-safe and process-safe access + with FileLock(lock_path): + # Load the Excel file into a pandas DataFrame with specific columns + df = pd.read_excel(file_path, usecols=['Date', 'Start Time', 'Slot', 'Interviewee Name', 'Interviewee Email', 'Meeting Duration']) + + # Initialize the dictionary to store structured data for available slots + interview_data = {} + + # Process each row in the DataFrame to structure data by date and time + for _, row in df.iterrows(): + # Convert Date and Start Time to string format for easier comparison + date = str(row['Date']).split(" ")[0] # Format date to YYYY-MM-DD + start_time = str(row['Start Time']) + + # Calculate the slot capacity and current number of interviewees + slot_capacity = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + interviewee_names = [name.strip() for name in str(row['Interviewee Name']).split(',') if name.strip()] + interviewee_count = len(interviewee_names) if interviewee_names != ["nan"] else 0 + + # Check if there are available slots for more interviewees + if interviewee_count < slot_capacity: + # Organize data by date and time, keeping track of available slots and meeting duration + if date not in interview_data: + interview_data[date] = {} + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + 'Available Slots': slot_capacity - interviewee_count + } + + return interview_data + +def AppendAppointment(date, start_time, interviewee_name, interviewee_email): + """ + Appends a new appointment with the interviewee's name and email if the slot is available. + + ``REQUIRES``: date (str), start_time (str), interviewee_name (str), interviewee_email (str) + + ``PROMISES``: Updates the Excel file with the new interviewee's name and email if there is an available slot. Returns Bool. + + ``Developed by``: Ahmad + + ``Contact``: ahmad.ahmad1@ucalgary.ca + """ + available_slots = ReadDatabase() + + # Check if the requested slot is available in the `available_slots` structure + if date in available_slots and start_time in available_slots[date]: + with FileLock(lock_path): # Ensure process-safe access to the file + # Load workbook and select "Sheet1" for updating appointments + workbook = load_workbook(file_path) + sheet = workbook["Sheet1"] + df = pd.read_excel(file_path) + + # Find and update the row that matches the provided date and start time + for index, row in df.iterrows(): + row_date = str(row['Date']).split(" ")[0] + row_start_time = str(row['Start Time']) + + if row_date == date and row_start_time == start_time: + # Current entries for names and emails, and append new data with comma and space + current_names = str(row['Interviewee Name']).strip() + current_emails = str(row['Interviewee Email']).strip() + + updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name + updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email + + # Update the cells with new names and emails + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name') + 1) + email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) + name_cell.value = updated_names + email_cell.value = updated_emails + + workbook.save(file_path) + send_email(interviewee_email, interviewee_name, date, start_time) + return True + + # If no slots available, return that the slot is unavailable + return False + + +def run_tests(): + """ + Executes test cases to verify appointment scheduling and slot availability. + + ``REQUIRES``: None + + ``PROMISES``: Prints test outcomes to validate successful booking or slot unavailability. + """ + print("Available Slots:") + available_slots = ReadDatabase() + print(json.dumps(available_slots, indent=4)) + + # Test Case 1: Append to an available slot on 2024-09-16 at 10:30:00 + date_1 = "2024-09-16" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) + + # Test Case 2: Append to an available slot on 2024-09-17 at 13:30:00 + date_2 = "2024-09-17" + start_time_2 = "13:30:00" + interviewee_name_2 = "Bob Smith" + interviewee_email_2 = "bob.smith@example.com" + print(f"\nTest Case 2: Trying to book {date_2} at {start_time_2} for {interviewee_name_2} ({interviewee_email_2})") + AppendAppointment(date_2, start_time_2, interviewee_name_2, interviewee_email_2) + + # Test Case 3: Attempting to book at 10:30:00 on 2024-09-16 for a different interviewee + date_3 = "2024-09-16" + start_time_3 = "10:30:00" + interviewee_name_3 = "Charlie Brown" + interviewee_email_3 = "charlie.brown@example.com" + print(f"\nTest Case 3: Trying to book {date_3} at {start_time_3} for {interviewee_name_3} ({interviewee_email_3})") + AppendAppointment(date_3, start_time_3, interviewee_name_3, interviewee_email_3) + +# Run tests +if __name__ == "__main__": + run_tests() diff --git a/__pycache__/WriteDB.cpython-312.pyc b/__pycache__/WriteDB.cpython-312.pyc new file mode 100644 index 0000000..401e4ac Binary files /dev/null and b/__pycache__/WriteDB.cpython-312.pyc differ diff --git a/__pycache__/send_email.cpython-312.pyc b/__pycache__/send_email.cpython-312.pyc new file mode 100644 index 0000000..1b561ac Binary files /dev/null and b/__pycache__/send_email.cpython-312.pyc differ diff --git a/composetest/Dockerfile b/composetest/Dockerfile new file mode 100644 index 0000000..8505363 --- /dev/null +++ b/composetest/Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 +FROM python:3.10-slim +WORKDIR /code +COPY requirements.txt requirements.txt +RUN pip install -r requirements.txt +EXPOSE 5000 +COPY . . +CMD ["python", "app.py"] \ No newline at end of file diff --git a/composetest/app.py b/composetest/app.py new file mode 100644 index 0000000..69e5c83 --- /dev/null +++ b/composetest/app.py @@ -0,0 +1,33 @@ +import time +from http.server import BaseHTTPRequestHandler, HTTPServer + +hit_count = 0 # In-memory counter + +def get_hit_count(): + global hit_count + hit_count += 1 + return hit_count + +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/': + count = get_hit_count() + response = f'Hello World! I have been seen {count} times.\n' + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(response.encode('utf-8')) + else: + self.send_response(404) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'Not Found\n') + +def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f'Starting server on port {port}...') + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/composetest/docker-compose.yaml b/composetest/docker-compose.yaml new file mode 100644 index 0000000..c24417b --- /dev/null +++ b/composetest/docker-compose.yaml @@ -0,0 +1,8 @@ +services: + web: + container_name: test + build: . + ports: + - "8000:8000" + volumes: + - ./:/code diff --git a/composetest/requirements.txt b/composetest/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/interview_database.yaml b/interview_database.yaml new file mode 100644 index 0000000..9a677ee --- /dev/null +++ b/interview_database.yaml @@ -0,0 +1,64 @@ +Data: + September 16: + Meeting Duration: 30 min + Meeting Start Times: + - 10:00 AM: + Interviewer: + - Name: John + - Email: john@example.com + Interviewee: + - Name: Jane + - Email: jane@example.com + Category: PowerDrive + Status: Done + Slot: 2 + - 10:30 AM: + Interviewer: + - Name: Jay + - Email: jay@example.com + Interviewee: + - Name: Zoz + - Email: Zoz@example.com + Category: PowerTrain + Status: Pending + Slot: 3 + - 11:00 AM: + Interviewer: + - Name: Ali + - Email: Ali@example.com + Interviewee: + - Name: Bob + - Email: Bob@example.com + Category: Software + Status: Rescheduled + Slot: 1 + - 11:30 AM: + Interviewer: + - Name: + - Email: + Interviewee: + - Name: + - Email: + Category: + Status: + Slot: 1 + - 12:00 PM: + Interviewer: + - Name: Ali, John + - Email: Ali@example.com, Jhon@example.com + Interviewee: + - Name: Bob + - Email: Bob@example.com + Category: Software + Status: Cancelled + Slot: 1 + - 12:30 PM: + Interviewer: + - Name: Ali + - Email: Ali@example.com + Interviewee: + - Name: Bob, Jish + - Email: Bob@example.com, jish@example.com + Category: Software + Status: Cancelled + Slot: 2 diff --git a/send_email.py b/send_email.py new file mode 100644 index 0000000..736b75d --- /dev/null +++ b/send_email.py @@ -0,0 +1,82 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from datetime import datetime, timedelta +import pytz # For timezone handling + +def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="brock", date="10-1-2024", start_time="10:00 AM", location ="ENC25"): + """ + Sends an email notification to the interviewee and a static Gmail account. + + ``REQUIRES``: interviewee_email (str), interviewee_name (str), date (str), start_time (str) + ``PROMISES``: Sends an email to interviewee and static email on successful appointment booking. + """ + # Define static email for notifications and Gmail credentials + static_email = "uofcbaja.noreply@gmail.com" + gmail_user = "uofcbaja.noreply@gmail.com" + gmail_apppassword = "pver lpnt upjd zvld" + + # Define Mountain Standard Time + mst = pytz.timezone("America/Edmonton") # MST with Daylight Savings considered + + # Parse the input date and time, localize to MST + start_datetime = mst.localize(datetime.strptime(f"{date} {start_time}", "%Y-%m-%d %H:%M:%S")) + end_datetime = start_datetime + timedelta(minutes=30) + + # Format date and time for the calendar URLs + start_time_google = start_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + end_time_google = end_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + outlook_start = start_datetime.isoformat() # Outlook Calendar (ISO local time) + outlook_end = end_datetime.isoformat() # Outlook Calendar (ISO local time) + + # Create message object + msg = MIMEMultipart() + msg['From'] = gmail_user + msg['To'] = f"{interviewee_email}, {static_email}" + msg['Subject'] = "Interview Appointment Confirmation" + + # Message body + body = f''' + +
+Dear {interviewee_name},
+Your interview has been scheduled on {date} at {start_time} MST.
+Your interview location is at {location} or will be emailed to you.
+Please ensure to be available at the designated time.
+ + + + + + +Best regards,
+UCalgary Baja Interview Team
+Dear {interviewee_name},
+Your interview has been scheduled on {date} at {start_time}
+Please ensure to be available at the designated time.
+ + + + + + +Best regards,
+UCalgary Baja Interview Team
+