def test_has_none(self): _, section, _, _ = get_or_create_course_and_section( "CIS-120-001", TEST_SEMESTER) instructor, _ = Instructor.objects.get_or_create(name="Rajiv Gandhi") section.instructors.add(instructor) recompute_precomputed_fields() self.assertFalse(Section.objects.get(id=section.id).has_reviews)
def setUp(self): _, self.cis_160_001 = create_mock_data( code="CIS-160-001", semester=TEST_SEMESTER, meeting_days="TR" ) _, self.cis_160_201 = create_mock_data( code="CIS-160-201", semester=TEST_SEMESTER, meeting_days="M" ) self.cis_160_201.activity = "REC" self.cis_160_201.save() _, self.cis_160_202 = create_mock_data( code="CIS-160-202", semester=TEST_SEMESTER, meeting_days="W" ) self.cis_160_202.activity = "REC" self.cis_160_202.save() def save_all(): for section in [self.cis_160_001, self.cis_160_201, self.cis_160_202]: section.save() self.save_all = save_all self.all_codes = {"CIS-160"} self.non_open_statuses = [ status[0] for status in Section.STATUS_CHOICES if status[0] not in ["O"] ] recompute_precomputed_fields() self.client = APIClient() set_semester()
def test_two_updates(self): up = record_update(self.section, TEST_SEMESTER, "C", "O", True, "JSON") up.save() up = record_update(self.section, TEST_SEMESTER, "O", "C", True, "JSON") up.save() recompute_precomputed_fields() self.assertTrue( Section.objects.get(full_code="CIS-120-001").has_status_updates)
def setUp(self): _, self.cis_120_001 = create_mock_data( "CIS-120-001", TEST_SEMESTER ) # time 11.0-12.0, days MWF _, self.cis_120_002 = create_mock_data( code="CIS-120-002", semester=TEST_SEMESTER, start=1200, end=1330, meeting_days="TR" ) _, self.cis_160_001 = create_mock_data( code="CIS-160-001", semester=TEST_SEMESTER, start=500, end=630, meeting_days="TR" ) _, self.cis_160_201 = create_mock_data( code="CIS-160-201", semester=TEST_SEMESTER, start=1100, end=1200, meeting_days="M" ) self.cis_160_201.activity = "REC" self.cis_160_201.save() _, self.cis_160_202 = create_mock_data( code="CIS-160-202", semester=TEST_SEMESTER, start=1400, end=1500, meeting_days="W" ) self.cis_160_202.activity = "REC" self.cis_160_202.save() _, self.cis_121_001 = create_mock_data(code="CIS-121-001", semester=TEST_SEMESTER) set_meetings( self.cis_121_001, [ { "building_code": "LLAB", "room_code": "10", "days": "MT", "begin_time_24": 900, "begin_time": "9:00 AM", "end_time_24": 1000, "end_time": "10:00 AM", }, { "building_code": "LLAB", "room_code": "10", "days": "WR", "begin_time_24": 1330, "begin_time": "1:30 PM", "end_time_24": 1430, "end_time": "2:30 PM", }, ], ) _, self.cis_262_001 = create_mock_async_class(code="CIS-262-001", semester=TEST_SEMESTER) recompute_precomputed_fields() self.all_codes = {"CIS-120", "CIS-160", "CIS-121", "CIS-262"} self.client = APIClient() set_semester()
def test_all_semesters(self): recompute_precomputed_fields() self.assertEquals(Course.objects.get(id=self.cis_160.id).num_activities, 2) self.assertEquals(Section.objects.get(id=self.cis_160_001.id).num_meetings, 4) self.assertEquals(Section.objects.get(id=self.cis_160_201.id).num_meetings, 3) self.assertEquals(Section.objects.get(id=self.cis_160_002.id).num_meetings, 3) self.assertEquals(Course.objects.get(id=self.cis_120.id).num_activities, 1) self.assertEquals(Section.objects.get(id=self.cis_120_001.id).num_meetings, 3) self.assertEquals(Course.objects.get(id=self.cis_120_old.id).num_activities, 1) self.assertEquals(Section.objects.get(id=self.cis_120_001_old.id).num_meetings, 3)
def test_has_one(self): _, section, _, _ = get_or_create_course_and_section( "CIS-120-001", TEST_SEMESTER) instructor, _ = Instructor.objects.get_or_create(name="Rajiv Gandhi") section.instructors.add(instructor) import_review(section, instructor, None, 10, None, {"instructor_quality": 4}, lambda x, y=None: None) recompute_precomputed_fields() self.assertTrue(Section.objects.get(id=section.id).has_reviews)
def create_review(section_code, semester, instructor_name, bits, responses=100): _, section, _, _ = get_or_create_course_and_section(section_code, semester) instructor, _ = Instructor.objects.get_or_create(name=instructor_name) section.instructors.add(instructor) import_review(section, instructor, None, responses, None, bits, lambda x, y=None: None) recompute_precomputed_fields()
def get_demand_data(semesters, section_query="", verbose=False): current_semester = get_current_semester() output_dict = dict() recompute_precomputed_fields(verbose=True) if verbose: print(f"Computing demand data for semesters {str(semesters)}...") for semester_num, semester in enumerate(semesters): try: validate_add_drop_semester(semester) except ValidationError: if verbose: print( f"Skipping semester {semester} (unsupported kind for stats)." ) continue add_drop_period = get_or_create_add_drop_period(semester) if verbose: print(f"Processing semester {semester}, " f"{(semester_num+1)}/{len(semesters)}.\n") output_dict[semester] = [] # list of demand data dicts section_id_to_object = dict( ) # maps section id to section object (for this semester) volume_changes_map = dict( ) # maps section id to list of volume changes status_updates_map = dict( ) # maps section id to list of status updates iterator_wrapper = tqdm if verbose else (lambda x: x) if verbose: print("Indexing relevant sections...") for section in iterator_wrapper( Section.objects.filter( extra_metrics_section_filters, full_code__startswith=section_query, course__semester=semester, ).annotate( efficient_semester=F("course__semester"), ).distinct()): section_id_to_object[section.id] = section volume_changes_map[section.id] = [] status_updates_map[section.id] = [] if verbose: print( "Computing registration volume changes over time for each section..." ) for registration in iterator_wrapper( Registration.objects.filter( section_id__in=section_id_to_object.keys()).annotate( section_capacity=F("section__capacity"))): section_id = registration.section_id volume_changes_map[section_id].append({ "date": registration.created_at, "volume_change": 1 }) deactivated_at = registration.deactivated_at if deactivated_at is not None: volume_changes_map[section_id].append({ "date": deactivated_at, "volume_change": -1 }) if verbose: print("Collecting status updates over time for each section...") for status_update in iterator_wrapper( StatusUpdate.objects.filter( section_id__in=section_id_to_object.keys(), in_add_drop_period=True)): section_id = status_update.section_id status_updates_map[section_id].append({ "date": status_update.created_at, "old_status": status_update.old_status, "new_status": status_update.new_status, }) if verbose: print("Joining updates for each section and sorting...") all_changes = sorted( [{ "type": "status_update", "section_id": section_id, **update } for section_id, status_updates_list in status_updates_map.items() for update in status_updates_list] + [{ "type": "volume_change", "section_id": section_id, **change } for section_id, changes_list in volume_changes_map.items() for change in changes_list], key=lambda x: (x["date"], int(x["type"] != "status_update")), # put status updates first on matching dates ) # Initialize variables to be maintained in our main all_changes loop latest_popularity_dist_estimate = None registration_volumes = { section_id: 0 for section_id in section_id_to_object.keys() } demands = {section_id: 0 for section_id in section_id_to_object.keys()} # Initialize section statuses section_status = { section_id: None for section_id in section_id_to_object.keys() } for change in all_changes: section_id = change["section_id"] if change["type"] == "status_update": if section_status[section_id] is None: section_status[section_id] = change["old_status"] percent_through = (add_drop_period.get_percent_through_add_drop( timezone.now()) if semester == current_semester else 1) if percent_through == 0: if verbose: print( f"Skipping semester {semester} because the add/drop period " f"hasn't started yet.") continue distribution_estimate_threshold = sum( len(changes_list) for changes_list in volume_changes_map.values()) // ( ROUGH_MINIMUM_DEMAND_DISTRIBUTION_ESTIMATES * percent_through) num_changes_without_estimate = 0 if verbose: print(f"Compiling demand data for semester {semester}...") for change in iterator_wrapper(all_changes): section_id = change["section_id"] if section_status[section_id] is None: section_status[section_id] = ( "O" if section_id_to_object[section_id].percent_open > 0.5 else "C") if change["type"] == "status_update": section_status[section_id] = change["new_status"] continue date = change["date"] volume_change = change["volume_change"] registration_volumes[section_id] += volume_change demands[section_id] = (registration_volumes[section_id] / section_id_to_object[section_id].capacity) max_id = max(demands.keys(), key=lambda x: demands[x]) min_id = min(demands.keys(), key=lambda x: demands[x]) if (latest_popularity_dist_estimate is None or section_id == latest_popularity_dist_estimate[ "highest_demand_section"].id or section_id == latest_popularity_dist_estimate["lowest_demand_section"].id or latest_popularity_dist_estimate["highest_demand_section"]. id != max_id or latest_popularity_dist_estimate["lowest_demand_section"].id != min_id or num_changes_without_estimate >= distribution_estimate_threshold): num_changes_without_estimate = 0 output_dict[semester].append({ "percent_through": percent_through, "demands": [ val for sec_id, val in demands.items() if section_status[sec_id] == "C" ], }) latest_popularity_dist_estimate = { "created_at": date, "semester": semester, "highest_demand_section": section_id_to_object[max_id], "highest_demand_section_volume": registration_volumes[max_id], "lowest_demand_section": section_id_to_object[min_id], "lowest_demand_section_volume": registration_volumes[min_id], } else: num_changes_without_estimate += 1 return output_dict
def setUp(self): _, self.cis_120_001 = create_mock_data( "CIS-120-001", TEST_SEMESTER ) # time 11.0-12.0, days MWF _, self.cis_120_002 = create_mock_data( code="CIS-120-002", semester=TEST_SEMESTER, start=1200, end=1330, meeting_days="TR" ) _, self.cis_160_001 = create_mock_data( code="CIS-160-001", semester=TEST_SEMESTER, start=500, end=630, meeting_days="TR" ) _, self.cis_160_201 = create_mock_data( code="CIS-160-201", semester=TEST_SEMESTER, start=1100, end=1200, meeting_days="M" ) self.cis_160_201.activity = "REC" self.cis_160_201.save() _, self.cis_160_202 = create_mock_data( code="CIS-160-202", semester=TEST_SEMESTER, start=1400, end=1500, meeting_days="W" ) self.cis_160_202.activity = "REC" self.cis_160_202.save() _, self.cis_121_001 = create_mock_data(code="CIS-121-001", semester=TEST_SEMESTER) set_meetings( self.cis_121_001, [ { "building_code": "LLAB", "room_code": "10", "days": "MT", "begin_time_24": 900, "begin_time": "9:00 AM", "end_time_24": 1000, "end_time": "10:00 AM", }, { "building_code": "LLAB", "room_code": "10", "days": "WR", "begin_time_24": 1330, "begin_time": "1:30 PM", "end_time_24": 1430, "end_time": "2:30 PM", }, ], ) _, self.cis_262_001 = create_mock_async_class(code="CIS-262-001", semester=TEST_SEMESTER) recompute_precomputed_fields() self.all_codes = {"CIS-120", "CIS-160", "CIS-121", "CIS-262"} self.user = User.objects.create_user( username="******", email="*****@*****.**", password="******" ) self.empty_schedule = Schedule( person=self.user, semester=TEST_SEMESTER, name="Empty Schedule", ) self.empty_schedule.save() self.all_available_schedule = Schedule( person=self.user, semester=TEST_SEMESTER, name="All Classes Available Schedule", ) self.all_available_schedule.save() self.all_available_schedule.sections.set([self.cis_120_001]) self.only_120_262_available_schedule = Schedule( person=self.user, semester=TEST_SEMESTER, name="Only CIS-120 and CIS-262 Available Schedule", ) self.only_120_262_available_schedule.save() self.only_120_262_available_schedule.sections.set([self.cis_120_001, self.cis_121_001]) self.only_262_available_schedule = Schedule( person=self.user, semester=TEST_SEMESTER, name="Only CIS-262 Available Schedule", ) self.only_262_available_schedule.save() self.only_262_available_schedule.sections.set( [self.cis_120_001, self.cis_120_002, self.cis_121_001] ) self.client = APIClient() set_semester()
def test_no_updates(self): recompute_precomputed_fields() self.assertFalse( Section.objects.get(full_code="CIS-120-001").has_status_updates)