diff --git a/Interviews/OR25-L-Interview Data.xlsx b/Interviews/OR25-L-Interview Data.xlsx new file mode 100644 index 0000000..69195be Binary files /dev/null and b/Interviews/OR25-L-Interview Data.xlsx differ diff --git a/NoSheet.py b/NoSheet.py index 1cb8bab..0d1d118 100644 --- a/NoSheet.py +++ b/NoSheet.py @@ -5,7 +5,7 @@ from openpyxl.styles import Font, Border, Side, PatternFill from openpyxl.formatting.rule import FormulaRule -def NoSheet(): +def NoSheet(file_path): """ Creates the Template for more data to be added @@ -41,7 +41,7 @@ Recruitment Responses: - Where did you hear about us?: Testing - Are you available for team meetings/work days? Saturdays 10 am - 4 pm: "No" #add condiftional formatting for no to make whole line red Interview TimeTable: - - Date: 9/16/2024 + - Date: 2024-09-16 - Meeting Duration: 30 min - Start Time Slot: 10:00:00 AM - Slot: 1 @@ -56,14 +56,15 @@ Data Helper And Info: - Done - No Show - Cancelled/Moved - - First time Startup: Move docker volume pointer to new dirve and start pu container + - First time Startup: Move docker volume pointer to new drive and start up container + - Weird Date: Add more space and it will change from ### to a date - How to Add Dropdown: Go into data, click data validation, select list then select the area you want to get values from in the formula spot """ # uses the base above "yaml file" to create the base template yamlsheet = yaml.safe_load(yamlraw) - year_donation = int(str(datetime.datetime.now().year)[2:]) # gets the last two digits of the current year then adds 1 for the current season - file_name = f"./OR{year_donation + 1}-L-Interview Data.xlsx" # name based off the 2025 naming system + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + file_name = file_path # border style border = Border( # defualt behaviour is thin @@ -100,6 +101,7 @@ Data Helper And Info: example_data = [list(data.values())[0] for data in title_list] for col_num, data in enumerate(example_data, start=1): + # for special case Data Helper where there a list in a dictionary if isinstance(data, list): row_num = 2 @@ -113,9 +115,15 @@ Data Helper And Info: # changes the Dropdown data in status to unknown instead of the other option, only there for prep for a dropdown if data == "Dropdown (Options in datahelp)": cell.value = "Unknown" + + elif isinstance(data, datetime.date): + cell.value = data + # Convert the example date '2024-09-16' to a datetime object + # Set the number format to 'YYYY-MM-DD' + cell.number_format = 'yyyy-mmm-d' else: cell.value = data - + if sheet.title == "Recruitment Responses": sheet.conditional_formatting.add("A2:I2", FormulaRule(formula=['=$I2="No"'], fill=PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid"))) @@ -126,4 +134,8 @@ Data Helper And Info: print(f"Created {file_name} for {year_donation}") if __name__ == "__main__": - NoSheet() \ No newline at end of file + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" + NoSheet(file_name) \ No newline at end of file diff --git a/OR25-L-Interview Data.xlsx b/OR25-L-Interview Data.xlsx deleted file mode 100644 index 9eba171..0000000 Binary files a/OR25-L-Interview Data.xlsx and /dev/null differ diff --git a/ReadDB.py b/ReadDB.py index e1ef66e..fdb1fc3 100644 --- a/ReadDB.py +++ b/ReadDB.py @@ -11,11 +11,11 @@ TODO change to use new tempate TODO change names to be more clear """ -def ReadDatabase(): +def ReadDatabase(file_path): """ Reads the database for which slots are available - ``REQUIRES``: ``None`` + ``REQUIRES``: ``File_Path`` where the file is ``PROMISES``: ``JSON`` Interview Available Slots @@ -25,11 +25,10 @@ def ReadDatabase(): """ - year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season - # name based off the 2025 naming system + # Define the path to the Excel file and the lock file - excel_file_path = f"OR{year_donation}-L-Interview Data.xlsx" - lock_file_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" + excel_file_path = file_path + lock_file_path = file_path + ".lock" # Retry parameters max_retries = 60 # Maximum number of retries if the file is locked @@ -41,7 +40,7 @@ def ReadDatabase(): # Attempt to acquire a shared read (non-blocking) access with FileLock(lock_file_path, timeout=0): # Non-blocking, checks if the lock exists # Load the Excel file into a pandas DataFrame - df = pd.read_excel(excel_file_path) + df = pd.read_excel(excel_file_path, sheet_name="Interview TimeTable") # Initialize the dictionary to store the structured data interview_data = {} @@ -49,11 +48,11 @@ def ReadDatabase(): # Group the DataFrame by Date, Start Time, and Slot for organization for _, row in df.iterrows(): date = str(row['Date']) - start_time = str(row['Start Time']) + start_time = str(row['Start Time Slot']) slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 # Returns the number of interviewees in the slot; returns 0 if empty - interviewee_amount = len(str(row['Interviewee Name']).split()) if str(row['Interviewee Name']) != "nan" else 0 + interviewee_amount = len(str(row['Interviewee Name (What to call them)']).split()) if str(row['Interviewee Name (What to call them)']) != "nan" else 0 # Check if the slot is available for an interviewee to attend available_slots = interviewee_amount != slot @@ -77,10 +76,16 @@ def ReadDatabase(): # If max retries are exceeded, raise an error raise RuntimeError("Unable to access the database after multiple attempts due to a file lock.") + # Example usage of the ReadDatabase function if __name__ == "__main__": + import datetime + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" try: - data = ReadDatabase() + data = ReadDatabase(file_name) print(json.dumps(data, indent=4)) except RuntimeError as e: print(e) diff --git a/WriteDB.py b/WriteDB.py index ef5c364..bd302c4 100644 --- a/WriteDB.py +++ b/WriteDB.py @@ -4,35 +4,29 @@ from openpyxl import load_workbook from send_email import send_email from filelock import FileLock -import datetime """ TODO make it work with the new template TODO update names to be more clear TODO try to remove pandas """ -def ReadDatabase(): +def ReadDatabase(file_path, lock_path): """ Reads the Database to retrieve available interview slots - ``REQUIRES``: None + ``REQUIRES``: ``File_Path`` ``Lock_Path`` where the file and lock are located ``PROMISES``: JSON (Available interview slots) ``Developed by``: Ahmad, Brock ``Contact``: ahmad.ahmad1@ucalgary.ca, darkicewolf50@gmail.com - """ - - year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season - # name based off the 2025 naming system - file_path = f"OR{year_donation}-L-Interview Data.xlsx" - lock_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" + """ # 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']) + df = pd.read_excel(file_path, usecols=['Date', 'Start Time Slot', 'Slot', 'Interviewee Name (What to call them)', 'Interviewee Email', 'Meeting Duration'], sheet_name="Interview TimeTable") # Initialize the dictionary to store structured data for available slots interview_data = {} @@ -41,17 +35,19 @@ def ReadDatabase(): 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']) + # print(date) + start_time = str(row['Start Time Slot']) # 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_names = [name.strip() for name in str(row['Interviewee Name (What to call them)']).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: + # print(date) interview_data[date] = {} interview_data[date][start_time] = { 'Meeting Duration': row['Meeting Duration'], @@ -60,11 +56,11 @@ def ReadDatabase(): return interview_data -def AppendAppointment(date, start_time, interviewee_name, interviewee_email): +def AppendAppointment(file_path, date, start_time, interviewee_name, interviewee_email): """ Appends a new appointment with the interviewee's name and email if the slot is available. - ``REQUIRES``: ``str`` date, ``str`` start_time, ``str`` interviewee_name, ``str`` interviewee_email + ``REQUIRES``: ``File_Path`` ``str`` date, ``str`` start_time, ``str`` interviewee_name, ``str`` interviewee_email ``PROMISES``: ``None`` Updates the Excel file with the new interviewee's name and email if there is an available slot. Returns Bool. @@ -72,45 +68,47 @@ def AppendAppointment(date, start_time, interviewee_name, interviewee_email): ``Contact``: ahmad.ahmad1@ucalgary.ca, darkicewolf50@gmail.com """ + # print(f"{file_path}\n{date}\n{start_time}\n{interviewee_name}\n{interviewee_email}") + lock_path = file_path + ".lock" - year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season - # name based off the 2025 naming system - file_path = f"OR{year_donation}-L-Interview Data.xlsx" - lock_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" - - available_slots = ReadDatabase() - + available_slots = ReadDatabase(file_path, lock_path) + print(date) + print(available_slots) + print(date in available_slots) # 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 + # Load workbook and select "Interview TimeTable" for updating appointments workbook = load_workbook(file_path) - sheet = workbook["Interview Timetable"] - df = pd.read_excel(file_path) + sheet = workbook["Interview TimeTable"] + df = pd.read_excel(file_path, sheet_name="Interview TimeTable") # 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']) + row_start_time = str(row['Start Time Slot']) 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_names = str(row['Interviewee Name (What to call them)']).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) + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name (What to call them)') + 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 + status_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Status') + 1) + status_cell.value = "Unknown" + workbook.save(file_path) send_email(interviewee_email, interviewee_name, date, start_time) return True - + print("False") # If no slots available, return that the slot is unavailable return False @@ -123,33 +121,40 @@ def run_tests(): ``PROMISES``: Prints test outcomes to validate successful booking or slot unavailability. """ + import datetime + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" + lock = file_name + ".lock" + print("Available Slots:") - available_slots = ReadDatabase() + available_slots = ReadDatabase(file_path=file_name, lock_path=lock) 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" + date_1 = "9/16/2024" 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) + AppendAppointment(file_name, 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" + date_2 = "9/17/2024" 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) + AppendAppointment(file_name, 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" + date_3 = "9/16/2024" 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) + AppendAppointment(file_name, date_3, start_time_3, interviewee_name_3, interviewee_email_3) # Run tests if __name__ == "__main__": diff --git a/__pycache__/NoSheet.cpython-313.pyc b/__pycache__/NoSheet.cpython-313.pyc index c77c12d..749dea5 100644 Binary files a/__pycache__/NoSheet.cpython-313.pyc and b/__pycache__/NoSheet.cpython-313.pyc differ diff --git a/__pycache__/ReadDB.cpython-313.pyc b/__pycache__/ReadDB.cpython-313.pyc index 7cf7153..bffd474 100644 Binary files a/__pycache__/ReadDB.cpython-313.pyc and b/__pycache__/ReadDB.cpython-313.pyc differ diff --git a/__pycache__/WriteDB.cpython-313.pyc b/__pycache__/WriteDB.cpython-313.pyc index 57778a9..f749142 100644 Binary files a/__pycache__/WriteDB.cpython-313.pyc and b/__pycache__/WriteDB.cpython-313.pyc differ diff --git a/__pycache__/interviewPackagers.cpython-313.pyc b/__pycache__/interviewPackagers.cpython-313.pyc index bb1131e..6a400f3 100644 Binary files a/__pycache__/interviewPackagers.cpython-313.pyc and b/__pycache__/interviewPackagers.cpython-313.pyc differ diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 0daf245..203f206 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/interviewPackagers.py b/interviewPackagers.py index 3f08274..f036977 100644 --- a/interviewPackagers.py +++ b/interviewPackagers.py @@ -1,11 +1,11 @@ from ReadDB import ReadDatabase -def getSchedulePackager(): +def getSchedulePackager(file_name): """ Packages up the response for a http response - ``REQUIRES``: None + ``REQUIRES``: ``File_Path`` where the file is ``PROMISES``: ``JSON`` http response ready @@ -15,18 +15,18 @@ def getSchedulePackager(): """ return { - "interviewDates": ReadDatabase() + "interviewDates": ReadDatabase(file_path=file_name) } from WriteDB import AppendAppointment from email_validator import validate_email, EmailNotValidError -def SelectAppointment (appointmentJson): +def SelectAppointment (file_name, appointmentJson): """ Packages up a response for a http request - ``REQUIRES``: ``JSON`` with the data of interviewee name, date, starttime and interviewee email + ``REQUIRES``: ``File_Path`` ``JSON`` where the file is, json has the data of interviewee name, date, starttime and interviewee email ``PROMISES``: ``JSON`` Returns if the booking was a success @@ -39,15 +39,15 @@ def SelectAppointment (appointmentJson): Example of an incoming http post body { "intervieweeName": "Brock", - "date": "2024-09-16", - "startTime": "10:30:00", + "date": "9/16/2024", + "startTime": "11:00:00", "intervieweeEmail": "darkicewolf50@gmail.com" } """ try: validEmail = validate_email(appointmentJson["intervieweeEmail"], check_deliverability=True) if validEmail: - status = AppendAppointment(date=appointmentJson["date"], start_time=appointmentJson["startTime"], interviewee_name=appointmentJson["intervieweeName"], interviewee_email=appointmentJson["intervieweeEmail"]) + status = AppendAppointment(file_path=file_name, date=appointmentJson["date"], start_time=appointmentJson["startTime"], interviewee_name=appointmentJson["intervieweeName"], interviewee_email=appointmentJson["intervieweeEmail"]) if status: resBody = {"Success": True, "validEmail": "true"} diff --git a/main.py b/main.py index 666bc9f..3dfa9dc 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,9 @@ import os year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season # name based off the 2025 naming system # Define the path to the Excel file and the lock file -file_name = f"OR{year_donation}-L-Interview Data.xlsx" +file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" if not os.path.isfile(file_name): - NoSheet() + NoSheet(file_name) app = FastAPI() @@ -52,7 +52,7 @@ async def getAppointments(): """ checks for all available slots in the database - ``REQUIRES``: ```None`` Nothing + ``REQUIRES``: ``None`` Nothing ``PROMISES``: ``JSON`` returns all of the avaialbe slots by date then time @@ -62,7 +62,7 @@ async def getAppointments(): """ - res = getSchedulePackager() + res = getSchedulePackager(file_name) return JSONResponse( headers={ @@ -101,7 +101,7 @@ async def postSelectInterview(rawRequest: Appointment): """ Books an interview, first checks if the slot is valid - ``REQUIRES``: ```Appointment`` A specifically formatted request + ``REQUIRES``: ``Appointment`` A specifically formatted request ``PROMISES``: ``JSON`` returns if the booking was successful or not @@ -112,7 +112,7 @@ async def postSelectInterview(rawRequest: Appointment): """ requestDict = {key: str(value) for key, value in rawRequest.dict().items()} - res = SelectAppointment(requestDict) + res = SelectAppointment(file_name, requestDict) return JSONResponse( headers={