From 58f913d3ed3b70900a31755bfe270cdaace2e3a0 Mon Sep 17 00:00:00 2001 From: HamodiGit Date: Sat, 9 Nov 2024 16:00:06 -0700 Subject: [PATCH] feat(Send_Email): Added feature to send emails --- ConversionToExcel.py | 44 ++++++++ README.md | 1 + WriteDB.py | 134 +++++++++++++++++++++++++ __pycache__/send_email.cpython-312.pyc | Bin 0 -> 2065 bytes interview_database.xlsx | Bin 0 -> 5586 bytes interview_database.yaml | 64 ++++++++++++ send_email.py | 41 ++++++++ 7 files changed, 284 insertions(+) create mode 100644 ConversionToExcel.py create mode 100644 README.md create mode 100644 WriteDB.py create mode 100644 __pycache__/send_email.cpython-312.pyc create mode 100644 interview_database.xlsx create mode 100644 interview_database.yaml create mode 100644 send_email.py 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/README.md b/README.md new file mode 100644 index 0000000..c40a406 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Interview-Backend \ No newline at end of file diff --git a/WriteDB.py b/WriteDB.py new file mode 100644 index 0000000..6cff7e0 --- /dev/null +++ b/WriteDB.py @@ -0,0 +1,134 @@ +import pandas as pd +import json +from openpyxl import load_workbook +from send_email import send_email + +# Define the path to the Excel file +file_path = "./interview_database.xlsx" + +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 + """ + # 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]: + # 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 = "alice.johnson@example.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 +run_tests() diff --git a/__pycache__/send_email.cpython-312.pyc b/__pycache__/send_email.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cab579e27d0da1d8f4af79c05bf197ee0b4b311 GIT binary patch literal 2065 zcmZ8iU2NM#96u*^;y6juF4^eVG~Gt`5t?Qjj0vi3Vrx5Sw~nnXLaQpZj(tttI(BAX z+9payD}mU=B;aY2rb&GS^<|HJLE6*@9(XZY5tZ3^0SWOIC=aN-@INPR%1-j#@BSb6 z|Ge|}@wkLw-2VM!{@V~jzp{-#0!^^h3*bIdk;>_4oUH+!D+I;^9K%69Sm4Kbz=Fzu z!i@_7M1<9*Du>EkI`jwIT}yLre0XqpV7R1HtteX*2qGhlkwvQTO%1k$;S0jI^$X`2 z=4!mk)dV$A3sImnhHs)4t_sz#8axN&Dtd@izREvDkAgrG7)=cU3VTSba=?#N129X~ zAk5K6;hI}^AX6PDxVKP6dgXTwfYuTn-O}aBwKtR`EnehdwrUk@bcNt z@~L(_5?ZmBldmDV&zI{$R%cDxrMIUlK?MRW`I|^h)}-4~IoVhjxCOKryonaLbV@!1 zDa8_oF-i>8#*uO2t_fSLSuqBl{ULTi`kvXwJUHZD+dO=!rLrqtz7D5X1^kMJ+jSZEBh{kAIzqSK~!yY zSTDYoj#=zxxFMNRS(&leGvtPuAzjP4{OIu5byxHXPj%ZBbTh9RK!78dSvMlvlh8m= zDVM)h(kQqJ8|p>|78O!tjhl|T9YFD$w$XKN`)(5JH<4n zSZ+rHL%$Sd+g>m&)eRPGa3N=^Wj6#O&=M|3Z1&`^i`5+4J$=0sm!OSszopHSiJdg} zT#}VJh-rca)-aeYmcg5yt#ufVVlSedgzLJ5>g_0XYU%1`N6*sGW@nGX@BKT3qMe_g zT)6U|r~NDt`Q^dQz3%{sEMKdKP$K1|x}4!M8yn)AO~wPsIeK<2=5!4_vA?T5 zt*w8&A%4Vo(Yw)m@#kXPY43J2=bi2gYvjq*Cl{O>BTn1skK))r&!efo5GQ0jnPV%r zRurf0^`FGp@^_+xsCoPvaNQ_eIfqG}4y|uBY1ijtAF|3xLyn^rT%(-0IDEv9Smi5Z>4cY=- zWMP=L#d8Id-O&qH8n*1OWgmbEoaMNmQQHfYeSx~R_#<5A&-XdRML&&MCxHJS=@>f} literal 0 HcmV?d00001 diff --git a/interview_database.xlsx b/interview_database.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..97233833ab09eebf643dd0669e9b1acd5f50fbfd GIT binary patch literal 5586 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 e^iLcp!+&ioJxyYgUw