def main(): debug = False root = Tk() root.withdraw() run_time = datetime.now().strftime('%Y%m%d%H%M') # used for file extensions, makes sorting easy print("\nMathnasium Scheduler Launched") default_directory = "C:\\ProgramData\\MathnasiumScheduler" FILEOPENOPTIONS = dict(defaultextension='.csv', filetypes=[('XLSX', '*.xlsx'), ('CSV file', '*.csv')]) # Todo-jerry add center name picklist, get center names from configuraton file center_name = simpledialog.askstring("Name prompt", "Enter Center Name") schedule_start_date = simpledialog.askstring("Schedule Start Prompt", "Enter Schedule Start Date (MM/DD/YY)") # center_name = "aaaa.TestRun" #Eliminates need to select files for successive test runs importer = Importer().import_all(run_time, default_directory, center_name, FILEOPENOPTIONS) #Create Schedule Workbook # Create Run Workbook run_wb_path = default_directory + "\\" + center_name + "." + run_time + ".xlsx" run_wb = Workbook() run_wb.save(run_wb_path) schedule_by_name_ws = run_wb.create_sheet("Schedule By Name", index=0) schedule_by_day_ws = run_wb.create_sheet("Schedule By Day", index=1) forecast_summary_ws = run_wb.create_sheet("Summary Forecast", index=2) forecast_detailed_ws = run_wb.create_sheet("Detailed Forecast", index=3) run_log_ws = run_wb.create_sheet("Runlog", index=4) #ToDo Write run_log to run_log_ws run_log = [] instructors = Instructor.create_instructors(run_log) students = Student.initialize_students(importer.attendance_ws, importer.student_data_ws, run_log) #Create Events print("\nCreating events from student arrivals and departures\n") events = [] for each_student in Student.students: events.append(Event('Arrival', each_student.arrival_time, each_student)) events.append(Event('Departure', each_student.departure_time, each_student)) events.sort() #Executing Events print("Executing events and collecting information") # Gather events by week day #ToDo refactor this into Common and use common # define days consistent with datetime.weekday() mon = 0 tue = 1 wed = 2 thu = 3 fri = 4 sat = 5 sun = 6 # Group events by day event_groups = {sun: [], mon: [], tue: [], wed: [], thu: [], fri: [], sat: []} for each_event in events: event_groups[each_event.event_time.weekday()].append(each_event) print("\tDetermining cost of each day") costsOfEventGroups = {} for each_day in event_groups.keys(): cost = 0.0 for each_event in event_groups[each_day]: if each_event.is_arrival_event: cost = cost + each_event.cost() costsOfEventGroups[each_day] = round(cost, 1) print("\t\tDay: ", each_day, "Cost: ", costsOfEventGroups[each_day]) # Sort and process each group of events for each_day in event_groups.keys(): print("\n\tProcessing Day: ", str(each_day)) event_groups[each_day].sort() instructorsMinimum = 2.0 # ToDo remove hard coded variable the minimum staffing level instructorsRequired = 0.0 # actual number of instructors required to meet student demand event_number = 1 # first event number student_count = 0 # start with zero students for each_event in event_groups[each_day]: # Set event number each_event.event_number = event_number # Set event's previous and next events if event_number != 1: each_event.prev = events[events.index(each_event) - 1] if event_number != len(event_groups[each_day]): each_event.next = event_groups[each_day][ event_groups[each_day].index(each_event) + 1] event_number = event_number + 1 # next event number # Maintain student count if (each_event.is_arrival_event): student_count = student_count + 1 elif (each_event.is_departure_event): student_count = student_count - 1 each_event.student_count = student_count # Compute/maintain the actual number of instructors required instructorsRequired = instructorsRequired + each_event.cost() # Compute/maintain the number of instructors to staff (minimum is instructorsMinimum) each_event.instructor_count = max(instructorsMinimum, math.ceil(instructorsRequired)) print("\t\tCollecting Instructor Change Events") instructor_change_events = [] for each_event in event_groups[each_day]: if each_event.is_instructor_change_event(): instructor_change_events.append(each_event) print("\t\tMarking Churn Events") tolerance = 360 # seconds (6 minutes) for i in range(len(instructor_change_events) - 1): event = instructor_change_events[i] next_event = instructor_change_events[i + 1] event.isChurnEvent = event.is_peak_event() and next_event.is_valley_event() \ and (next_event.event_time - event.event_time).seconds < tolerance print("\t\tScheduling Instructors") # instructors = Instructor.instructors instructors.sort() if debug: for eachInstructor in instructors: print(eachInstructor) inactive_instructors = instructors active_instructors = [] dateChangeEvents = 0 for each_event in event_groups[each_day]: if each_event.is_date_change_event(): dateChangeEvents = dateChangeEvents + 1 # Activate minimum number of instructors needed to open if necessary active_instructor_count = 0 inactive_instructors.sort() instructors_changed = [] for this_instructor in inactive_instructors: if this_instructor.isAvailableToOpen(each_event) and (active_instructor_count < instructorsMinimum): active_instructor_count = active_instructor_count + 1 this_instructor.startWorkWhenOpen(each_event) active_instructors.append(this_instructor) # save pointers to instructors for removal from unscheduled list instructors_changed.append(this_instructor) for i in instructors_changed: inactive_instructors.remove(i) # Check for and remove departing Instructors departed_instructors = [] for this_instructor in active_instructors: departed = False if this_instructor.mustDepart(each_event): this_instructor.departWork(each_event) departed_instructors.append(this_instructor) instructor_change_needed = each_event.instructor_count - len(active_instructors) if not each_event.is_churn_event and instructor_change_needed > 0: # Schedule available instructors # print("\t\t\tActivate Instructor") active_instructor_count = 0 inactive_instructors.sort() # print("Inactive Instructors: " + str(len(inactive_instructors))) while (active_instructor_count < instructor_change_needed): # print('while') # Find instructor and schedule instructor instructors_changed = [] #ToDo inside the while loop (inconsistent see line 215) for this_instructor in inactive_instructors: if this_instructor.isAvailable(each_event) and (active_instructor_count < instructor_change_needed): active_instructor_count = active_instructor_count + 1 this_instructor.startWork(each_event) active_instructors.append(this_instructor) # Save pointers to newly scheduled instructors for removal from unscheduled list instructors_changed.append(this_instructor) # Remove newly activated (scheduled) instructors from inactive list for i in instructors_changed: inactive_instructors.remove(i) if not each_event.is_churn_event and instructor_change_needed < 0: # Deactivate instructors # print("\t\t\tDeactivate Instructor") deactivated_instructor_count = 0 instructors_deactivated = [] #ToDo outside the while loop (inconsistent see line 200) active_instructors.sort() # print("Printing Reversed Rank List") # for this in reversed(active_instructors): # print(this.name, this.rank) while deactivated_instructor_count < abs(instructor_change_needed): for this_instructor in reversed(active_instructors): if deactivated_instructor_count < abs(instructor_change_needed): deactivated_instructor_count = deactivated_instructor_count + 1 this_instructor.stopWork(each_event) inactive_instructors.append(this_instructor) instructors_deactivated.append(this_instructor) for i in instructors_deactivated: active_instructors.remove(i) # Finalize schedules after last departure or final event of the day if (each_event == event_groups[each_day][len(event_groups[each_day]) - 1]) or each_event.next.is_date_change_event(): instructors_changed = [] for this_instructor in active_instructors: this_instructor.isScheduled = False inactive_instructors.append(this_instructor) instructors_changed.append(this_instructor) for i in instructors_changed: active_instructors.remove(i) for i in inactive_instructors: i.finalizeSchedule() Reporter().write_all(events, instructors, forecast_detailed_ws, forecast_summary_ws, schedule_by_name_ws) print("\nReview, edit, and approve schedules") # Todo code to review, edit, and approve schedules print("\nFormating and Closing Workbooks") Importer().close_workbooks() Reporter().format_sheets(run_wb) run_wb.save(run_wb_path) run_wb.close() print("\nLaunching Excel") os.system("start excel " + run_wb_path ) # Todo code up individual schedule emails including mapping to email addresses and instructor first names. print("\nEmailing Schedules to Instructors") # Gmailer().send_instructor_schedules(instructors) # Todo code up scheduling individual work events on master schedule calendar and instructor calendars. print("\nAdding Work Events to Instructor Google Calendars") # GoogleEventScheduler().insert_events(instructors) print("\nScheduler Run Completed")