From 49313e8450da6f47503a739a2ae3ebf3773171a7 Mon Sep 17 00:00:00 2001 From: darkicewolf50 Date: Sat, 7 Dec 2024 20:07:57 -0700 Subject: [PATCH] fix(Packagers): changed to first time run --- OR25-L-Interview Data.xlsx | Bin 6776 -> 6776 bytes ReadDB.py | 85 ++++++------ WriteDB.py | 127 ++++++++---------- __pycache__/ReadDB.cpython-313.pyc | Bin 2542 -> 2897 bytes __pycache__/WriteDB.cpython-313.pyc | Bin 5761 -> 6358 bytes .../interviewPackagers.cpython-313.pyc | Bin 0 -> 1695 bytes __pycache__/main.cpython-313.pyc | Bin 2068 -> 3475 bytes __pycache__/send_email.cpython-313.pyc | Bin 4127 -> 4217 bytes interviewPackagers.py | 62 +++++++++ main.py | 12 +- 10 files changed, 171 insertions(+), 115 deletions(-) create mode 100644 __pycache__/interviewPackagers.cpython-313.pyc diff --git a/OR25-L-Interview Data.xlsx b/OR25-L-Interview Data.xlsx index 6d0e3f3dc831da1b7b693df12e0e6091125a4328..9eba17103d3ad446f478257ce331fd19f08ce64b 100644 GIT binary patch delta 448 zcmexi^23BDz?+#xgn@y9gW=?Yi981dPcCSW+!(rA;1y8pG$R9p;ABP#k@}?*5BeQ8 z;Ax9beYLmr(jv}9I^qKA6DG`Hc_gNKFYMxyz@#hh-g=(C*7)F<;XnKOZK9b?2DL|) z-(c^#k`&du?z88N4Vru|jtnZr4$Fh5a?U>-D?fMHpPp)tTYFyHI-R{CB{ON;EEAtY zMmonHE!mP5*w%mJEK@dvb;%1&#`P94^$~|&T$;~YylFymmUxy#TUJJzUjC7XUq3#q zjN6w}tMryv%qvRS$Hlic4@?URoC494?+QH!2_F-l%n0H}i+%!e{l!AT+-2f69I)`6 u{7T#$q(oW59z>-`xPYit5b6VjGLy6f2^LC%Rj!h>2JxOtx`8NtDNg_x7Q{gS delta 448 zcmexi^23BDz?+#xgn@y9gF%1xM4p3!`m@_3D=u>SzXFP#W@KOxoXjX8QonTKLBGQW zJZ@ zVR9{-64so5Yl*>{&9=n>44mJe_-eT=l0BR&zW3or$#MZ6xiftZT!qZ@ZbTerQ%jT+ zOD+qFikb3I*=+Lxr2yx7QGNkpsxkEu%U)cb&s)4{f^!k~)$g?z!;Q`JLJr-&{P^^q zns>L3EOR|ok{g()aya(NW!HxmCZ;vo*OuRmXWg{Au<23qg3>a-xYfIVpLJfgbV7Kg zW^v(e!Jw1bp`G7PeqH=u>C4>G=>X5wdVi; diff --git a/ReadDB.py b/ReadDB.py index 1e7d54c..e1ef66e 100644 --- a/ReadDB.py +++ b/ReadDB.py @@ -5,12 +5,10 @@ import time from NoSheet import NoSheet import datetime -import os - """ -TODO update to possibly not use pandas and update to use the new template -TODO update name of function to be more clear +TODO change to use new tempate +TODO change names to be more clear """ def ReadDatabase(): @@ -33,54 +31,51 @@ def ReadDatabase(): excel_file_path = f"OR{year_donation}-L-Interview Data.xlsx" lock_file_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" - if not (os.path.isfile(excel_file_path) or os.path.isfile(lock_file_path)): - NoSheet() - else: - # Retry parameters - max_retries = 60 # Maximum number of retries if the file is locked - retry_interval = 0.5 # Wait time (in seconds) between retries + # Retry parameters + max_retries = 60 # Maximum number of retries if the file is locked + retry_interval = 0.5 # Wait time (in seconds) between retries - retries = 0 - while retries < max_retries: - try: - # 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) + retries = 0 + while retries < max_retries: + try: + # 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) - # Initialize the dictionary to store the structured data - interview_data = {} + # Initialize the dictionary to store the structured data + interview_data = {} - # 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']) - slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + # 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']) + 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 + # 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 - # Check if the slot is available for an interviewee to attend - available_slots = interviewee_amount != slot - if available_slots: - # Initialize nested structure if not present - if date not in interview_data: - interview_data[date] = {} - # Add the start time and duration if not present - if start_time not in interview_data[date]: - interview_data[date][start_time] = { - 'Meeting Duration': row['Meeting Duration'], - } - return interview_data # Successfully read the database + # Check if the slot is available for an interviewee to attend + available_slots = interviewee_amount != slot + if available_slots: + # Initialize nested structure if not present + if date not in interview_data: + interview_data[date] = {} + # Add the start time and duration if not present + if start_time not in interview_data[date]: + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + } + return interview_data # Successfully read the database - except Timeout: - # File is locked; wait and retry - retries += 1 - print(f"File is locked, retrying ({retries}/{max_retries})...") - time.sleep(retry_interval) + except Timeout: + # File is locked; wait and retry + retries += 1 + print(f"File is locked, retrying ({retries}/{max_retries})...") + time.sleep(retry_interval) - # If max retries are exceeded, raise an error - raise RuntimeError("Unable to access the database after multiple attempts due to a file lock.") + # 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__": diff --git a/WriteDB.py b/WriteDB.py index c5e9a25..ef5c364 100644 --- a/WriteDB.py +++ b/WriteDB.py @@ -4,15 +4,13 @@ from openpyxl import load_workbook from send_email import send_email from filelock import FileLock -from NoSheet import NoSheet import datetime -import os """ -TODO update to possibly not use pandas and update to use the new template -TODO update name of functions to be more clear +TODO make it work with the new template +TODO update names to be more clear +TODO try to remove pandas """ - def ReadDatabase(): """ Reads the Database to retrieve available interview slots @@ -30,41 +28,37 @@ def ReadDatabase(): # 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']) - # checks for if the file exisits for the year otherwise it will create one - if not (os.path.isfile(file_path) or os.path.isfile(lock_path)): - NoSheet() - else: - # 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 + # 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']) + # 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 + # 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 - } + # 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 + return interview_data def AppendAppointment(date, start_time, interviewee_name, interviewee_email): """ @@ -84,46 +78,41 @@ def AppendAppointment(date, start_time, interviewee_name, interviewee_email): file_path = f"OR{year_donation}-L-Interview Data.xlsx" lock_path = f"OR{year_donation}-L-Interview Data.xlsx.lock" - # checks for if the file exisits for the year otherwise it will create one - if not (os.path.isfile(file_path) or os.path.isfile(lock_path)): - NoSheet() - else: + 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["Interview Timetable"] + df = pd.read_excel(file_path) - 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["Interview Timetable"] - 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']) - # 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 - 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 - # 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 - 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 + # If no slots available, return that the slot is unavailable + return False def run_tests(): diff --git a/__pycache__/ReadDB.cpython-313.pyc b/__pycache__/ReadDB.cpython-313.pyc index 6517ae015e5bdad8f9f4a9df18d6bc76e3e95be1..7cf71530325ea0f3beabfc024645ce12bc730cba 100644 GIT binary patch delta 1551 zcmZ`(PfXip6o38~JBB2r5dH=*3MY0{>($03a#c3BVyCU3{}fHX~LowVDu@1um2X?szgf8YE6p5N!+ zn-5yuFI+AWFm;K_TGF-SZl`|O84C6SV(##_d6Sm_0ix~*rqBb3?F7^Lo`|@e%vHvL z_?<^LhlFF|9vPu+VKJB2mW|Xsn|~))(2R}J2xapT17$U>WX)2op;ZRp0mLcdK`4&T zFDcH}kbI765&)UEs&wa;EYw9cG<6mXQHW;Y{KN#XfsQLyzzv9-V07vXJ20jZuQN!{ zS>)8Y7z$kiQ2zqxfp|kLu0kUx60g%VNDyy4m0II;XERkxp*zMv=M{(SCADRn;np_* z8#KWBhxN7;Cu-oZ^SP6FLDzUe*Kn2x+snA3e^Wo;Xf7*4cjV3oNF=Yuh*G$t#tS*z zuj-K}RK~py;?etR@&DgiZo{n&ZaH;V?3d6Qo~K)J51+ZEai=Q}0Ai0{$JMC#rHw(X zGK96UCyGdN68WjsX8@=PSK36RV^pj{o=O9;UHKNE0Z&-ri!?g@P2sqNBp85rA3+Yn zP>WSzMz`7tCd7j}MvN2=F##7Yjf;1^)L>h$!r_;GNntQ=p5SW3$1l=fvypphsQ$zQ zX6v@S5M?tk!}LOiI$IHD3*aWT62t4hI^wH57iCRN7o}2GlhSHQT~&*klrc=HoXw@O zQZa9oijrz-l6qgw<<-@^CW^_V9DZ9_lEWyO3`xmk#L%^5lE~kZqbp0OCcllMkz`U@ z(o33oKc|(Yc@je;K_o;1TOYH1{1s`z#P`kmk{wE1!8Mroaw)BB$+jLZ}v<8*~^~g7H>A}r>akJ9(-|}Fd`=0h!+h-12X20_uv@C3|oH(GPYjgHmr*}WO`{?TsAED-E zaN@lVF?<>JURwx-JDKnMJcLhrG2iE$Sh3$JhKKq60fyk=Q-1mC;vn;EfLa{1o(Y4L z&-zX1iITN6$*nNSy*Ei)wvb5ZYC%gRY?dsSN!Huu(i?^KqDhw5rnJxD1qNUS$x)at z80n3?Hfv5>?>f7E&!OvQ@SLy=#r`4AQB;~bS){FxoXc%r^vsK}CpzZ)nY|`qzMI+W H<}m&TXj@B# delta 1189 zcmZ`&O>7fK6n?X_e;z06m?#cOiP@kalLN*GB1BRJrAmk>i48klWNAU`C0@Y7S%+Px zu_9H;p~v( z`#bZl*VyUqCcx#z50`KMmfSE7qvspBd>V%ul|!8>gAgXJ6RK`B&tZ+~8^6!4CEkhy z#Zf8NFV^b*Tw~#GtbJ2k^c%~~)q(t}TF~^vZ)^UFTlJdWO)v8Et$Ng2&3<+(U*{YP zg0&+$H!%P0#1g;*peO(;1&HE6x>YvE4*_`p17yKLof8fR?h3_JJ}*gNf+Kf)mfEpC zuE)R;VcIgb#}nWJ1hS2-B&(xAHYX(~t~uQul&w-l;eS4$wF7(Vfj!-k?@LG5RI2TF zpV;H78URjuAMpWHN!&DXfL!0tLKEXFyadNNM{kGY)9CiSt-khcuHfVv4oLaQ_-%k7 zX(vA zt8UAYM3dqh+j|oeM!-4BA<#~T_ORFZ)a8tD2+_bRfH)}8u9Ljjf#eMA?(ksUwqyV` zY~VU|M(LEEKki@$V;x~c%CeT6vUXd8cg`%Duka?lIhtO5L@eK{MrQMlZ%*%ZBq5cG zHFMRzWX;e@W!x+_0$=#9Sf%+(bl3MaEfviZ>_2$`jk11am|akE5+UrR;;O~Bydc0v z)O%NZLeZ$PA$r)o#RtM&@|t&IX{j2MkOy}C)`DM;Q|)CRK(FyJ2^%X>jH6~4%e5%* zV$6q!#p+qq#V%;;a+e75mv%8NUP{bq^c|184%Y(xPRl9 zrw{canw;RUAC2i8Jp5{MI)nclN(o&^bG?NU+`p9}(<9oJiG@AV*12;t!}z6%W`@}) zIgSigBH7YmP89YCB?cfADa5+#2Hv9Yy0I*VJM0%urMkSh6oukFV&p%89d1A;7ao#-5X*zHJqH^XsWs>C@}Rzw&uhMcXaJ-jTWf$Jr?in^>Lt0&gbtsSMuC HDBOMz{w3MWiW#?+DB zJ6Vc>g33ctr7x9zC~6cgkcXl`^HQLG$wPoNXo|K#9wZ2flS^HssEZ;%9~31(ffNWb zbL2^sl)My0SJ2$<%vO=RYxpZX6G z*^?$TAbWudvJYtRN=WvP*<+f)ohmf4lT4mDp92~fJ3^k3(jLh<6d&TvEqE2L9Az=vNB&RdY zxfkYaA?VssSa%N#0Te_Fp>HZdflG}c4Prx0oEVk}A z)|8dN`<-jvt2NEYK|K=Kb#NH{Iv8<}PLO8>gB>6Sdp=7@^|f3ZW+1t39|D`|AVd)w zf;(KW9@GUTC`SjFkwvHtxZnaPG=>s%?K($?@_!zQ6H-DW?|>2CW&SjzQH9K(Ao+=- zunAm_VPN@teNdfpyr0ca;9Bvcbn2gk1R-6Q`6Hx@^b&N-TH#VD;R)_Axy~4@1fJ`{ zb&^fo`m6O@ZrJ)4ch%P`jneAu4QrA=`h2fcQt6HIthQLKTpv2|?YViiT!&I=!7 zDldJEb!D^Dxz7*uJ=Hg zkzi5Lh{Nu+IbGFh3>I0|`P_<}y9rLH*D9JMSE{Cw6>YApn>J^d)b^KDQ!~r+8tkhs z+WbqJN-186&DKh`KsB{g)RtzoitR6hv$Q(8XxJX3Rw1 z#ZpyQ&2m+@1J}zHtyojd7wiB=;7qL>o)>-SxFku|2cEI|37BIH#*g57mdR&nl4`%l z-8=sv)3=`KdypAi&kSy4PCm$-TF;!?XdiyP>-Fxn_9r%yXMPu3PmV56eH2fwUR|AD z>lt2)jx0~U?=H`*Mb9oze&!`Yd^P)SAoEC!{N(D7uihQrB4pqK`w1L-kzI+fn_iO6 ztc3p^BtqwA)Vkt*lf8S!I_Mj(w1Zo3wWr61xVMG2ai90?$C5z*FeHrgzS&kxK@f4! z=%fEL8u5TYd=rg4%{iixcL$AJDAj#=-)Zo3f>B?FN6@Aa%yg5p1W!vO8ZdAuUiWP| z0EWfo48`N}b-&`5eP}=r6rxQr$-xWUI{;92b_wvfQ}F6D^$F%Yyf6pM-e>_0Xai`} zJto(-*Bm_DfGd>+#{qHIeg^?v4LzJ+!?xnU0)(3XTXWS#rbZ#;?OvgTp*Ne zq(^qC?83!{N?ngC5jpJOlMik#0?CLdY?z$nrWu9RV@k9|8g-;xQ_AUaC6GI~KSaPh z2Ad7sR%X96cesUT{XppbD?NlpqCE2p_ z=XzS4YEE|&FOkAyt~~#Eo*8OB9ti+)-sn4&BYsloK2{|ve@8! zeP#wgXl4e$*VK9?cV(C=HH!#tJ3Iis2IOj5QCGiCq9JSx!pzLmx#_7%c}$*}83D7G zs=5ZJC^bCCkjxh}X$-N^dKg@2Z3FeMZvzx6gj}MC7Bqkaei4H8bZJ75TZ7`U2<^r>=9nGK^4s=^*!yVnL<#$Et zQQMJMpWA2~SULZGGPBnC_%Gh6GiJx`e>AZXYrSNfvdDZ%5G3Be`1~=!B0Vfj^xgj=a)(RfW!n6U1~fZQX<@!*I00ovI%2(+*ho212k3%c zgo-f@`aMv;dH+uG8%$KRy){)YsYW9MPg#FT9j!a5auS61;;7sHqEo8H;;#uUAlEVK z|JHo9v{2E`&}+bhyCcSvKsGssVLl{@4@vML>3&GM9+J+#lgK8|GyIk?z@)dXG2dij gTRPLtD9q*shVY@4TN_-*2fX;hi^3*H__TA%mu4R{)&Kwi delta 2374 zcmbVNO>7fa5T3VdukE$h&X4nF5@IKi#3;XNK_pQAlTf0N*2_{?N#rc?5=@=7_0}O# zE(wRWDTlUU>5o(nNJXkRR#DGLrRt@IoPlot|$6x`#`^!`elIzWM2%ZDEpBH|@Rekq3#^^FD*?0fRf8 z1Srg>YVfVG=+-6p;L!#E#pejjF+RBD*L{j#4)&0Nc`S4g!>HQ|!|y)bzCV!ua-a$z z%e?#wkUoA0>!D&DdLbQcM)y0Fb;shdkj*;(;2>F8eoL9CoY_CNwU!HLRz z%vaY0-{PC$A~Co)hOYZALNdH~*W^9@q}x2~nKW;C+KFiH^-e6Er9O0JkvL^#rL68W zzzFMWi*#DNuIe;|ha_iji;?43)LdS@kk{gJzECofAIq?($| zXbaO?-U?jIw@`_@2)xQADUtH_`LB{FMm&{8{~w0i73*adc{*mebbvLKn(zv3>!h0kd`CJ zg{|&Mdk&$}Fi}^rN>K$^CN?=MtIuomZoO@Uu}s@*8(ywl_bOhQFl+2ed!#VM6wCX+ zRtvnw+J_w&!Paby4U^NIn@Id*ywbH*0y1a*!X-4`z0g6XF7D9rvH)BSJ(#}5W|iHZ z_@?ytTc;D))aAZy9J_W76n5*O9V$Cu?Wip2k`j{rHcT)zN+=9+K;gph20um=P7fbJS>uhcI07w%>!=N}Q=u;m~#lS4S@D*m5q zHrytCuklx@gq4as4kTwO5v8*1EE7VkiaB|GCrNu{MsDL1>t+4|qnw2ZF4p0CX!{2dpf zI5qW7YHajvc~G938ZfU)AG6W2BwB)eNz+Pw_Hd!>(Wn^ceCF z;Me%pnLzbW*J^%K(U7>2dmzQ1*6q3d{{6b1mD7)+jbEpKi?*!{{!v-|RnzsRn`;j% z_7QU1Bvl7n*c0y@KQPqF|Jceiy6@y3+N3%-VWw8>t9eLQ&PTs^R2l$ZV@`U$S#%r_j#m zF;us0EPuwXb|!NNsKU6mXaC{ZLUt~%9ibD*W7RQ+k*xECkjGH{7=llrj~w;A^ZjL0jXNGaHv#?o53IZ{B+|@4er= zZZv8j7_-q_<))6%Q?a-la(~iYg~@$nAxqju8Hq|Jvg8>=WsoS_N=BtBNRXv&Ync!Y zWpt{;S_6%6C6v&A7H%F$eq@i}(mLk2h#9ek@Ny_H5r20E2pl9GNS9kH{mE@<0VU*5 zzcqHTUBwl`%x1&nN5rIrH9VIQv+Pk5n@5~C1~S7a6!tdW$!+azScSqJGwZoT$aeR3 z-rll$vO*za?PLputCrvbmrjK4I%$!b*DxK|Y+%aG;)(E-rk-6j_l3fm=W=XwAuVBA zb!>9%)ygZk?^Wv9sV#eU-7x$y$K`~!9C8c^AdHG=AE2%X&R`FQ5Ral9EZy|IbQVyr(b_UOc&S55`cvRWgI}o zojNhGTQaYx1ts`Txb6|&q%Jd^a&S)3^Qw+pF^@5WSHPOKO&A-(FDd;Sg8v78ZBO9$ zg`xWgnAishT?6#l)qIK60Jjtuotnc>d{qSIj|743LtuVv$Tkb1CsaBPrSXkGB`<=% z;y*9)G zX>ooC&gV_$>(D~dUg^Y_2otz7#h@cXW!xe)V)LO#Lgzs)%-KUA@WN)oTDUVYrX?T8 zZ+yLT@!Dp4;jN30itV_4YWz4Fe;i-@X6J0Py|i|*o4+Vj+q<<6Ligky8Q89jj*ebgI(xl6lLk?! zCz?c&sXu<3LXo*HlCUj3`1fZ$Jd9WMQOGNqyKLO{+nBQ>X-$JErc zay}`E`LqT!n;7v!LAOp*@sNVH;_L5Ghm-YmkXk<$z%$=(7#{6qT_ik65f!Z==rz4e thTo`rqEp?aH(*2b5GI;ETuYLkp!r`zG0FJ-I+7NDLU+5N1t}TW{tedM(cl08 literal 0 HcmV?d00001 diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 84981fb7a041d0135ce80c971fda1b983fc9f98d..0daf2455e7aa1fc76f9b6a4973732ec58c3dbe89 100644 GIT binary patch literal 3475 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 H+N1jmgpkHI diff --git a/__pycache__/send_email.cpython-313.pyc b/__pycache__/send_email.cpython-313.pyc index def618d177026d51a08d8a8b588a18addf496a52..9495a50ebb2a8b76aaee5f8b9cb1d03d50b554ee 100644 GIT binary patch delta 368 zcmbQQ@Kb^JGcPX}0}uqUhNe%S$ScX1GEv=@NseJ-N-r~G%;ZnZ=Jh4{3MCn-3Z?mJ z$w`S>i3*9y$@!&uC0twy2|=!bp`Jmm!3hag3JD3tB}EAd3YmE&sYPX(spYAu@u|6q znK?RGrScMUQxP&Li6uz9;*!LolK7I$+*F_?VhI63{=S~UFk4)G9X)*#5+<)^3AP71 z!6mgUH7CCyHANw*666fWjNHT&klUQ|^GXtvOF&|YATd2KY3NXzoS2iISX8N(oVfWf z%N=&G|BE%57#JEDK1gkT##6{7Y-C_$qHCyYXi&vrXkcXkLX)%jYi&({A@zZok&*E> z1K(W+y}Jx5_Zc*9GjM-kVPa(Y!WPKLsPiF>iGhWuqv|rVYo;GE>V_Q{z*?Dm98riZpdlB=Qn- zQx%}%DTyU;HqeHmlK7HLm@LrjfFOTg&tTVJW}xAd53mGp4raa0&dUyTXt5>}149GD z2Z7DjyoF4{h6cKZx<&>@CRH4U237_J3XZ;$_w&~>>QA;65a;;9;={