""" Assignment 0 Solution Code CSC148, Winter 2023 This code is provided solely for the personal and private use of students taking CSC148 at the University of Toronto. Copying for purposes other than this use is expressly prohibited. All forms of distribution of this code, whether as given or with any changes, are expressly prohibited. Authors: Mario Badr, Jonathan Calver, Tom Ginsberg, Diane Horton, Sophia Huynh, Christine Murad, Misha Schwartz, Jaisie Sin, and Jacqueline Smith. All of the files in this directory and all subdirectories are: Copyright (c) 2022 Mario Badr, Jonathan Calver, Tom Ginsberg, Diane Horton, Sophia Huynh, Christine Murad, Misha Schwartz, Jaisie Sin, and Jacqueline Smith. """ from __future__ import annotations import os import webbrowser from datetime import datetime from typing import Any import pandas as pd import yaml from gym_utilities import in_week, create_offering_dict, \ write_schedule_to_html # The additional pay per hour that instructors receive for each certificate they # hold. BONUS_RATE = 1.50 BASE_RATE = 25.0 class Instructor: """ An instructor that teaches workout classes at a gym. === Public Attributes === name: The name of the instructor === Private Attributes === _inst_id: id of the instructor _certificates: a list of certificates earned by the instructor """ name: str _inst_id: int _certificates: list[str] def __init__(self, _inst_id: int, name: str) -> None: """Initialize a new instructor with <_inst_id>, and the <_certificates> they have. >>> diane = Instructor(1, 'Diane') >>> diane.name 'Diane' >>> diane._inst_id 1 >>> diane._certificates [] """ self.name = name self._inst_id = _inst_id self._certificates = [] def get_id(self) -> int: """ Return instructor's <_inst_id> when called. >>> diane = Instructor(1, 'Diane') >>> diane.get_id() 1 >>> jacqueline = Instructor(2, 'Jacqueline') >>> jacqueline.get_id() 2 """ return self._inst_id def get_certificates(self) -> list[str]: """Return instructor's <_certificates> when called. >>> diane = Instructor(1, 'Diane') >>> diane.get_certificates() [] """ return self._certificates[:] def add_certificate(self, cert_name: str) -> bool: """ Add certificates to Instructors' list of certificates. Return True iff certificate with cert_name does not preexist in <_certificates>. >>> diane = Instructor(1, 'Diane') >>> diane.add_certificate('Cardio 1') True >>> diane.get_certificates() ['Cardio 1'] """ if cert_name not in self._certificates: self._certificates.append(cert_name) return True else: return False class WorkoutClass: """A workout class that can be offered at a gym. === Public Attributes === name: The name of the workout class. === Private Attributes === _required_certificates: The certificates that an instructor must hold to teach this WorkoutClass. """ name: str _required_certificates: list[str] def __init__(self, name: str, required_certificates: list[str]) -> None: """Initialize a new WorkoutClass called and with the . >>> workout_class = WorkoutClass('Kickboxing', ['Strength Training']) >>> workout_class.name 'Kickboxing' """ self.name = name self._required_certificates = required_certificates[:] def get_required_certificates(self) -> list[str]: """Return all the certificates required to teach this WorkoutClass. >>> workout_class = WorkoutClass('Kickboxing', ['Strength Training']) >>> needed = workout_class.get_required_certificates() >>> needed ['Strength Training'] >>> needed.append('haha') >>> try_again = workout_class.get_required_certificates() >>> try_again ['Strength Training'] """ # Make a copy of the list to avoid aliasing return self._required_certificates[:] def __eq__(self, other: Any) -> bool: """Return True iff this WorkoutClass is equal to . Two WorkoutClasses are considered equal if they have the same name and the same required certificates. >>> workout_class = WorkoutClass('Kickboxing', ['Strength Training']) >>> workout_class2 = WorkoutClass('Kickboxing', ['Strength Training']) >>> workout_class == workout_class2 True >>> d = {1: 17} >>> workout_class == d False """ if not isinstance(other, WorkoutClass): return False return (self.name == other.name and self._required_certificates == other._required_certificates) class Gym: """A gym that hosts workout classes taught by instructors. All offerings of workout classes start on the hour and are 1 hour long. If a class starts at 7:00 pm, for example, we say that the class is "at" the timepoint 7:00, or just at 7:00. === Public Attributes === name: The name of the gym. === Private Attributes === _instructors: The instructors who work at this Gym. Each key is an instructor's ID and its value is the Instructor object representing them. _workouts: The workout classes that are taught at this Gym. Each key is the name of a workout class and its value is the WorkoutClass object representing it. _room_capacities: The rooms and capacities in this Gym. Each key is the name of a room and its value is the room's capacity, that is, the number of people who can register for a class in the room. _schedule: The schedule of classes offered at this gym. Each key is a date and time and its value is a nested dictionary describing all offerings that start then. In the nested dictionary, each key is the name of a room that has an offering scheduled then, and its value is a tuple describing the offering. The tuple elements record, in order: - the instructor teaching the class, - the workout class itself, and - a list of registered clients. Each client is represented in the list by a unique string. === Representation Invariants === - All instructors in _schedule are in _instructors (the reverse is not necessarily true). - All workout classes in _schedule are in _workouts (the reverse is not necessarily true). - All rooms recorded in _schedule are also recorded in _room_capacities (the reverse is not necessarily true). - Two workout classes cannot be scheduled at the same time in the same room. - No instructor is scheduled to teach two workout classes at the same time. I.e., there does not exist timepoint t, and rooms r1 and r2 such that _schedule[t][r1][0] == _schedule[t][r2][0] - No client can take two workout classes at the same time. I.e., there does not exist timepoint t, and rooms r1 and r2 such that c in _schedule[t][r1][2] and c in _schedule[t][r2][2] - If an instructor is scheduled to teach a workout class, they have the necessary qualifications. - If there are no offerings scheduled at date and time , then does not occur as a key in _schedule. - If there are no offerings scheduled at date and time in room then does not occur as a key in _schedule[d] - Each list of registered clients for an offering is ordered with the most recently registered client at the end of the list. """ name: str _instructors: dict[int, Instructor] _workouts: dict[str, WorkoutClass] _room_capacities: dict[str, int] _schedule: dict[datetime, dict[str, tuple[Instructor, WorkoutClass, list[str]]]] def __init__(self, gym_name: str) -> None: """Initialize a new Gym with . Initially, this gym has no instructors, workout classes, rooms, or offerings. >>> ac = Gym('Athletic Centre') >>> ac.name 'Athletic Centre' """ self.name = gym_name self._instructors = {} self._workouts = {} self._room_capacities = {} self._schedule = {} def add_instructor(self, instructor: Instructor) -> bool: """Add a new to this Gym's roster iff the does not have the same id as another instructor at this Gym. Return True iff the id has not already been added to this Gym's roster. >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> ac.add_instructor(diane) True """ if instructor.get_id() not in self._instructors: self._instructors[instructor.get_id()] = instructor return True else: return False def add_workout_class(self, workout_class: WorkoutClass) -> bool: """Add a to this Gym iff the does not have the same name as another WorkoutClass at this Gym. Return True iff the workout class has not already been added this Gym. >>> ac = Gym('Athletic Centre') >>> kickboxing = WorkoutClass('Kickboxing', ['Strength Training']) >>> ac.add_workout_class(kickboxing) True """ if workout_class.name not in self._workouts: self._workouts[workout_class.name] = workout_class return True else: return False def add_room(self, name: str, capacity: int) -> bool: """Add a room with and to this Gym iff there is not already a room with at this Gym. Return True iff the room has not already been added to this Gym. >>> ac = Gym('Athletic Centre') >>> ac.add_room('Dance Studio', 50) True """ if name not in self._room_capacities: self._room_capacities[name] = capacity return True else: return False def _helper_is_instructor_qualified(self, instr_id: int, workout_name: str) -> bool: """ Return True iff instructor is qualified to teach the workout class with Preconditions: - The Instructor has already been added to this Gym. >>> ac = Gym('Athletic Centre') >>> jacqueline = Instructor(1, 'Jacqueline Smith') >>> ac.add_instructor(jacqueline) True >>> jacqueline.add_certificate('Cardio 1') True >>> diane = Instructor(2, 'Diane Horton') >>> ac.add_instructor(diane) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> tap = WorkoutClass('Intro Tap', []) >>> ac.add_workout_class(tap) True >>> ballet = WorkoutClass('Intro Ballet', ['Ballet 1']) >>> ac.add_workout_class(ballet) True >>> ac._helper_is_instructor_qualified(1, 'Boot Camp') True >>> ac._helper_is_instructor_qualified(2, 'Intro Tap') True >>> ac._helper_is_instructor_qualified(1, 'Intro Ballet') False """ required_certs = self._workouts[workout_name].\ get_required_certificates() instr_certs = self._instructors[instr_id].get_certificates() if required_certs != []: for cert in required_certs: if cert in instr_certs: return True return False else: return True def _helper_is_instr_available(self, time_point: datetime, instr_id: int) -> bool: """Return True iff the instructor with is not teaching another workout class at the same . Preconditions: - The Instructor has already been added to this Gym. - An offering has already been added to this Gym >>> ac = Gym('Athletic Centre') >>> jacqueline = Instructor(1, 'Jacqueline Smith') >>> ac.add_instructor(jacqueline) True >>> jacqueline.add_certificate('Cardio 1') True >>> diane = Instructor(2, 'Diane Horton') >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 18) True >>> ac.add_room('lower gym', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> tap = WorkoutClass('Intro Tap', []) >>> ac.add_workout_class(tap) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'lower gym',\ boot_camp.name, jacqueline.get_id()) True >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ tap.name, diane.get_id()) True >>> ac._helper_is_instr_available(sep_9_2022_12_00, 2) False """ for room in self._schedule[time_point]: if self._schedule[time_point][room][0].get_id() == instr_id: return False return True def schedule_workout_class(self, time_point: datetime, room_name: str, workout_name: str, instr_id: int) -> bool: """Add an offering to this Gym at iff: the room with is available, the instructor with is qualified to teach the workout class with , and the instructor is not teaching another workout class at the same . A room is available iff it does not already have another workout class scheduled at that day and time. The added offering starts with no registered clients. Return True iff the offering was added. Preconditions: - The room has already been added to this Gym. - The Instructor has already been added to this Gym. - The WorkoutClass has already been added to this Gym. >>> ac = Gym('Athletic Centre') >>> jacqueline = Instructor(1, 'Jacqueline Smith') >>> ac.add_instructor(jacqueline) True >>> jacqueline.add_certificate('Cardio 1') True >>> diane = Instructor(2, 'Diane Horton') >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 18) True >>> ac.add_room('lower gym', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> tap = WorkoutClass('Intro Tap', []) >>> ac.add_workout_class(tap) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'lower gym',\ boot_camp.name, jacqueline.get_id()) True >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ tap.name, diane.get_id()) True """ if time_point in self._schedule: if room_name in self._schedule[time_point]: return False else: if self._helper_is_instructor_qualified(instr_id, workout_name)\ and self._helper_is_instr_available(time_point, instr_id): self._schedule[time_point][room_name] \ = (self._instructors[instr_id], self._workouts[workout_name], []) return True else: return False else: if self._helper_is_instructor_qualified(instr_id, workout_name): self._schedule[time_point] = {} self._schedule[time_point][room_name] \ = (self._instructors[instr_id], self._workouts[workout_name], []) return True else: return False def _helper_is_room_full(self, time_point: datetime, room_name: str) -> bool: """Return True iff a room in the Gym is not full at . A room is only full if the number of registered clients is equal to the room's capacity. Preconditions: - At least one offering has been added to the Gym - At least one room has been added to the Gym - At least one client has been registered in a WorkoutClass >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> diane.add_certificate('Cardio 1') True >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ boot_camp.name, diane.get_id()) True >>> ac._helper_is_room_full(sep_9_2022_12_00, 'Dance Studio') False """ if len(self._schedule[time_point][room_name][2]) ==\ self._room_capacities[room_name]: return True else: return False def _helper_find_superior_room(self, time_point: datetime, room_1: str, room_2: str = None) -> str: """Return the room that has the most clients already registered but still has available space. Preconditions: - At least one offering has been added to the Gym. - At least two rooms have been added to the Gym. >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> diane.add_certificate('Cardio 1') True >>> jacqueline = Instructor(2, 'Jacqueline') >>> jacqueline.add_certificate('Cardio 1') True >>> ac.add_instructor(diane) True >>> ac.add_instructor(jacqueline) True >>> ac.add_room('Dance Studio', 50) True >>> ac.add_room('Lower Gym', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ boot_camp.name, diane.get_id()) True >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Lower Gym',\ boot_camp.name, jacqueline.get_id()) True >>> ac.register(sep_9_2022_12_00, 'Philip', 'Boot Camp') True >>> ac._helper_find_superior_room(sep_9_2022_12_00, 'Dance Studio',\ 'Lower Gym') 'Lower Gym' """ if room_2 is not None: if self._helper_is_room_full(time_point, room_1): return room_2 elif len(self._schedule[time_point][room_1][2]) >= \ len(self._schedule[time_point][room_2][2]): return room_1 else: return room_2 else: return room_1 def _helper_is_client_new(self, time_point: datetime, client: str) -> bool: """Return True iff client has not already been registered in any course in the Gym at . Preconditions: - At least one offering has been added to the Gym >>> ac = Gym('Athletic Centre') >>> jacqueline = Instructor(1, 'Jacqueline Smith') >>> ac.add_instructor(jacqueline) True >>> jacqueline.add_certificate('Cardio 1') True >>> diane = Instructor(2, 'Diane Horton') >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 18) True >>> ac.add_room('lower gym', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> tap = WorkoutClass('Intro Tap', []) >>> ac.add_workout_class(tap) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'lower gym',\ boot_camp.name, jacqueline.get_id()) True >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ tap.name, diane.get_id()) True >>> ac._helper_is_client_new(sep_9_2022_12_00, 'Felix') True """ for room in self._schedule[time_point]: if client in self._schedule[time_point][room][2]: return False return True def register(self, time_point: datetime, client: str, workout_name: str) \ -> bool: """Add to the WorkoutClass with that is being offered at iff the client has not already been registered in any course (including ) at , and the room is not full. If the WorkoutClass is being offered in more than one room at , then add the client to the room that has the most clients already registered but still has available space. In the case of a tie, register in any of the tied classes. Return True iff the client was added. Precondition: the WorkoutClass with is being offered in at least one room at . >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> diane.add_certificate('Cardio 1') True >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> sep_9_2022_12_00 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(sep_9_2022_12_00, 'Dance Studio',\ boot_camp.name, diane.get_id()) True >>> ac.register(sep_9_2022_12_00, 'Philip', 'Boot Camp') True >>> ac.register(sep_9_2022_12_00, 'Philip', 'Boot Camp') False """ sup_room = '' same_class = [] for location in self._schedule[time_point]: if self._schedule[time_point][location][1].name == workout_name: same_class.append(location) if self._helper_is_client_new(time_point, client): for room1 in same_class: for room2 in same_class: sup_room = self._helper_find_superior_room(time_point, room1, room2) self._schedule[time_point][sup_room][2].append(client) return True else: return False def instructor_hours(self, time1: datetime, time2: datetime) -> \ dict[int, int]: """Return a dictionary reporting the hours worked by instructors teaching classes that start at any time between and , inclusive. Each key is an instructor ID and its value is the total number of hours worked by that instructor between and . Both and specify the start time for an hour when an instructor may have taught. Precondition: time1 <= time2 >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> david = Instructor(2, 'David') >>> diane.add_certificate('Cardio 1') True >>> ac.add_instructor(diane) True >>> ac.add_instructor(david) True >>> ac.add_room('Dance Studio', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> t1 = datetime(2019, 9, 9, 12, 0) >>> ac.schedule_workout_class(t1, 'Dance Studio', boot_camp.name, 1) True >>> t2 = datetime(2019, 9, 10, 12, 0) >>> ac.instructor_hours(t1, t2) == {1: 1, 2: 0} True >>> ac.schedule_workout_class(t2, 'Dance Studio', boot_camp.name, 1) True >>> ac.instructor_hours(t1, t2) == {1: 2, 2: 0} True """ inst_to_hours = {} relevant_dates = [] date_to_offering = {} for date in self._schedule: for room in self._schedule[date]: date_to_offering[date] = (room, self._schedule[date][room][0], self._schedule[date][room][1], self._schedule[date][room][2]) for date in self._schedule: if time1 <= date <= time2: relevant_dates.append(date) for inst_id in self._instructors: inst_to_hours[inst_id] = 0 for date in relevant_dates: for inst in inst_to_hours: if date_to_offering[date][1].get_id() == inst: inst_to_hours[inst] += 1 return inst_to_hours def payroll(self, time1: datetime, time2: datetime, base_rate: float) \ -> list[tuple[int, str, int, float]]: """Return a sorted list of tuples reporting pay earned by each instructor teaching classes that start any time between and , inclusive. The list should be sorted in ascending order of instructor ids. Each tuple contains 4 elements, in this order: - an instructor's ID, - the instructor's name, - the number of hours worked by the instructor between and , and - the instructor's total wages earned between and . The returned list is sorted by instructor ID. Both and specify the start time for an hour when an instructor may have taught. Each instructor earns a per hour plus an additional BONUS_RATE per hour for each certificate they hold. Precondition: time1 <= time2 >>> ac = Gym('Athletic Centre') >>> diane = Instructor(1, 'Diane') >>> david = Instructor(2, 'David') >>> diane.add_certificate('Cardio 1') True >>> ac.add_instructor(david) True >>> ac.add_instructor(diane) True >>> ac.add_room('Dance Studio', 50) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> t1 = datetime(2019, 9, 9, 12, 0) >>> ac.schedule_workout_class(t1, 'Dance Studio', boot_camp.name, ... 1) True >>> t2 = datetime(2019, 9, 10, 12, 0) >>> ac.payroll(t1, t2, 25.0) [(1, 'Diane', 1, 26.5), (2, 'David', 0, 0.0)] """ payroll = [] inst_to_hours = self.instructor_hours(time1, time2) for inst in inst_to_hours: name = self._instructors[inst].name hours = inst_to_hours[inst] total_wages = hours * (len(self._instructors[inst] .get_certificates()) * BONUS_RATE + base_rate) payroll.append((inst, name, hours, total_wages)) payroll.sort() return payroll def _is_instructor_name_unique(self, instructor: Instructor) -> bool: """Return True iff the name of is used by <= 1 instructor in the Gym. >>> ac = Gym('Athletic Centre') >>> first_hire = Instructor(1, 'Diane') >>> ac.add_instructor(first_hire) True >>> ac._is_instructor_name_unique(first_hire) True >>> second_hire = Instructor(2, 'Diane') >>> ac.add_instructor(second_hire) True >>> ac._is_instructor_name_unique(first_hire) False >>> ac._is_instructor_name_unique(second_hire) False >>> third_hire = Instructor(3, 'Tom') >>> ac._is_instructor_name_unique(third_hire) True """ name_count = 0 for inst in self._instructors: if self._instructors[inst].name == instructor.name: name_count += 1 if name_count <= 1: return True else: return False def offerings_at(self, time_point: datetime) -> list[dict[str, str | int]]: """Return a list of dictionaries, each representing a workout offered at this Gym at . The offerings should be sorted by room name, in alphabetical ascending order. Each dictionary must have the following keys and values: 'Date': the weekday and date of the class as a string, in the format 'Weekday, year-month-day' (e.g., 'Monday, 2022-11-07') 'Time': the time of the class, in the format 'HH:MM' where HH uses 24-hour time (e.g., '15:00') 'Class': the name of the class 'Room': the name of the room 'Registered': the number of people already registered for the class 'Available': the number of spots still available in the class 'Instructor': the name of the instructor If there are multiple instructors with the same name, the name should be followed by the instructor ID in parentheses e.g., "Diane (1)" If there are no offerings at , return an empty list. NOTE: - You MUST use the helper function create_offering_dict from gym_utilities to create the dictionaries, in order to make sure you match the format specified above. - You MUST use the helper method _is_instructor_name_unique when deciding how to format the instructor name. >>> ac = Gym('Athletic Centre') >>> diane1 = Instructor(1, 'Diane') >>> diane1.add_certificate('Cardio 1') True >>> diane2 = Instructor(2, 'Diane') >>> david = Instructor(3, 'David') >>> david.add_certificate('Strength Training') True >>> ac.add_instructor(diane1) True >>> ac.add_instructor(diane2) True >>> ac.add_instructor(david) True >>> ac.add_room('Dance Studio', 50) True >>> ac.add_room('Room A', 20) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> kickboxing = WorkoutClass('KickBoxing', ['Strength Training']) >>> ac.add_workout_class(kickboxing) True >>> t1 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(t1, 'Dance Studio', boot_camp.name, 1) True >>> ac.schedule_workout_class(t1, 'Room A', kickboxing.name, 3) True >>> ac.offerings_at(t1) == [ ... { 'Date': 'Friday, 2022-09-09', 'Time': '12:00', ... 'Class': 'Boot Camp', 'Room': 'Dance Studio', 'Registered': 0, ... 'Available': 50, 'Instructor': 'Diane (1)' }, ... { 'Date': 'Friday, 2022-09-09', 'Time': '12:00', ... 'Class': 'KickBoxing', 'Room': 'Room A', 'Registered': 0, ... 'Available': 20, 'Instructor': 'David' } ... ] True """ total_offerings = [] date = f"{time_point.strftime('%A')}, {time_point.strftime('%Y-%m-%d')}" time = time_point.strftime('%H:%M') for location in self._schedule[time_point]: room_name = location workout_class = self._schedule[time_point][location][1].name registered = len(self._schedule[time_point][location][2]) available = self._room_capacities[location] - registered inst = self._schedule[time_point][location][0] if not self._is_instructor_name_unique(inst): inst_name = f"{inst.name} ({inst.get_id()})" else: inst_name = inst.name offering_dict = create_offering_dict(date, time, workout_class, room_name, registered, available, inst_name) total_offerings.append(offering_dict) end = len(total_offerings) - 1 while end != 0: for i in range(end): if total_offerings[i]['Room'] > total_offerings[i + 1]['Room']: total_offerings[i]['Room'], total_offerings[i + 1]['Room'] \ = total_offerings[i + 1]['Room'], \ total_offerings[i]['Room'] end = end - 1 return total_offerings def to_schedule_list(self, week: datetime = None) \ -> list[dict[str, str | int]]: """Return a list of dictionaries for the Gym's entire schedule, with each dictionary representing a workout offered (in the format specified by the docstring for offerings_at). The dictionaries should be in the list in ascending order by their date and time (not the string representation of the date and time). Offerings occurring at exactly the same date and time should be in alphabetical order based on their room names. If is specified, only return the events that occur between the date interval (between a Monday 0:00 and Sunday 23:59) that contains . Hint: The helper function can be used to determine if one datetime object is in the same week as another. >>> ac = Gym('Athletic Centre') >>> diane1 = Instructor(1, 'Diane') >>> diane1.add_certificate('Cardio 1') True >>> diane2 = Instructor(2, 'Diane') >>> david = Instructor(3, 'David') >>> david.add_certificate('Strength Training') True >>> ac.add_instructor(diane1) True >>> ac.add_instructor(diane2) True >>> ac.add_instructor(david) True >>> ac.add_room('Studio 1', 20) True >>> boot_camp = WorkoutClass('Boot Camp', ['Cardio 1']) >>> ac.add_workout_class(boot_camp) True >>> kickboxing = WorkoutClass('KickBoxing', ['Strength Training']) >>> ac.add_workout_class(kickboxing) True >>> t1 = datetime(2022, 9, 9, 12, 0) >>> ac.schedule_workout_class(t1, 'Studio 1', boot_camp.name, 1) True >>> t2 = datetime(2022, 9, 8, 13, 0) >>> ac.schedule_workout_class(t2, 'Studio 1', kickboxing.name, 3) True >>> ac.to_schedule_list() == [ ... { 'Date': 'Thursday, 2022-09-08', 'Time': '13:00', ... 'Class': 'KickBoxing', 'Room': 'Studio 1', 'Registered': 0, ... 'Available': 20, 'Instructor': 'David' }, ... { 'Date': 'Friday, 2022-09-09', 'Time': '12:00', ... 'Class': 'Boot Camp', 'Room': 'Studio 1', 'Registered': 0, ... 'Available': 20, 'Instructor': 'Diane (1)' }, ... ] True """ schedule_list = [] schedule_copy = {} for date in self._schedule: schedule_copy[date] = self._schedule[date] for date in sorted(schedule_copy): if week is not None and in_week(date, week): offerings = self.offerings_at(date) for offering in offerings: schedule_list.append(offering) else: offerings = self.offerings_at(date) for offering in offerings: schedule_list.append(offering) return schedule_list def __eq__(self, other: Any) -> bool: """Return True iff this Gym is equal to . Two gyms are considered equal if they have name the same name, instructors, workouts, room capacities, and schedule. >>> ac = Gym('Athletic Centre') >>> ac2 = Gym('Athletic Centre') >>> ac == ac2 True """ if isinstance(other, Gym): if (self.name == other.name) and\ (self._instructors == other._instructors) and\ (self._workouts == other._workouts) and \ (self._room_capacities == other._room_capacities) and \ (self._schedule == other._schedule): return True else: return False else: return False def to_webpage(self, filename: str = 'schedule.html') -> None: """Create a simple html webpage from data exported by gym.to_schedule_list and save it to the file . The webpage can be viewed by opening it in a web browser. Precondition: ends in .html """ df = pd.DataFrame(self.to_schedule_list()) write_schedule_to_html(df, filename) def gym_from_yaml(filename: str) -> Gym: """Return a Gym object build from the data in a YAML file with . Precondition: uses the following format: name: instructors: - id: name: certificates: - - ... rooms: - name: capacity: workout_classes: - name: certificates: - - ... schedule: - time: