From 6f8add4a7203775d9b65d5759404c6dd122f10a4 Mon Sep 17 00:00:00 2001 From: Shiva Menta Date: Fri, 22 Nov 2024 03:59:43 -0600 Subject: [PATCH] Add Status Updates and Cancelled Courses --- .../courses/management/commands/loadstatus.py | 12 +++++ .../management/commands/registrarimport.py | 2 +- .../management/commands/sync_path_status.py | 53 +++++++++++++++---- k8s/main.ts | 2 +- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/backend/courses/management/commands/loadstatus.py b/backend/courses/management/commands/loadstatus.py index 9a6e60da..e4cd3d95 100644 --- a/backend/courses/management/commands/loadstatus.py +++ b/backend/courses/management/commands/loadstatus.py @@ -1,3 +1,4 @@ +import asyncio import json import logging @@ -5,6 +6,10 @@ from tqdm import tqdm from courses import registrar +from courses.management.commands.sync_path_status import ( + get_all_course_status_path, + get_department_codes, +) from courses.models import Course, Section from courses.util import ( get_course_and_section, @@ -25,6 +30,9 @@ def set_all_status(semester=None, add_status_update=False, verbose=False): statuses_out_of_sync = [] status_updates_out_of_sync = [] + department_codes = get_department_codes() + path_course_to_status = asyncio.run(get_all_course_status_path(semester, department_codes)) + for status in tqdm(statuses): section_code = status.get("section_id_normalized") if section_code is None: @@ -45,6 +53,10 @@ def set_all_status(semester=None, add_status_update=False, verbose=False): except (Section.DoesNotExist, Course.DoesNotExist): continue + if section_code in path_course_to_status: + # Defer judgement to Path@Penn status + course_status = path_course_to_status[section_code] + last_status_update = section.last_status_update current_status = section.status diff --git a/backend/courses/management/commands/registrarimport.py b/backend/courses/management/commands/registrarimport.py index 4a698bfe..c90beab9 100644 --- a/backend/courses/management/commands/registrarimport.py +++ b/backend/courses/management/commands/registrarimport.py @@ -36,7 +36,7 @@ def registrar_import(semester=None, query=""): dept.save() print("Loading course statuses from registrar...") - set_all_status(semester=semester) + set_all_status(semester=semester, add_status_update=True) recompute_parent_courses(semesters=[semester], verbose=True) recompute_soft_state(semesters=[semester], verbose=True) diff --git a/backend/courses/management/commands/sync_path_status.py b/backend/courses/management/commands/sync_path_status.py index 8b055278..75f0b8c7 100644 --- a/backend/courses/management/commands/sync_path_status.py +++ b/backend/courses/management/commands/sync_path_status.py @@ -22,8 +22,24 @@ ) -def map_path_to_opendata(course_status): - return "O" if course_status == "A" else "C" +def map_path_to_opendata(course_status: str) -> str: + match course_status: + case "A": + return "O" + case "F": + return "C" + case _: + return "X" + + +def normalize_status(course_status: str) -> str: + match course_status: + case "O": + return "Open" + case "C": + return "Closed" + case _: + return "Cancelled" def denormalize_section_code(section_code: str) -> str: @@ -31,12 +47,12 @@ def denormalize_section_code(section_code: str) -> str: def format_webhook_request_body( - section_code: str, new_course_status: str, semester: str + section_code: str, previous_course_status: str, new_course_status: str, semester: str ) -> Dict[str, str]: return { - "previous_status": "C" if new_course_status == "O" else "O", + "previous_status": previous_course_status, "status": new_course_status, - "status_code_normalized": "Open" if new_course_status == "O" else "Closed", + "status_code_normalized": normalize_status(new_course_status), "section_id": denormalize_section_code(section_code), "section_id_normalized": section_code, "term": semester, @@ -44,24 +60,37 @@ def format_webhook_request_body( async def send_webhook_request( - async_session: aiohttp.ClientSession, semester: str, course: str, course_status: str + async_session: aiohttp.ClientSession, + semester: str, + course: str, + previous_course_status: str, + course_status: str, ) -> None: async with webhook_semaphore: await async_session.post( url="https://penncoursealert.com/webhook", - data=json.dumps(format_webhook_request_body(course, course_status, semester)), + data=json.dumps( + format_webhook_request_body(course, previous_course_status, course_status, semester) + ), headers={"Content-Type": "application/json", "Authorization": f"Basic {auth.decode()}"}, ) async def send_webhook_requests( - semester: str, course_list: List[str], path_course_to_status: Dict[str, str] + semester: str, + course_list: List[str], + db_course_to_status: Dict[str, str], + path_course_to_status: Dict[str, str], ) -> None: async with aiohttp.ClientSession() as async_session: tasks = [ asyncio.create_task( coro=send_webhook_request( - async_session, semester, course, path_course_to_status[course] + async_session, + semester, + course, + db_course_to_status[course], + path_course_to_status[course], ) ) for course in course_list @@ -162,7 +191,11 @@ def resolve_path_differences(send_data_to_slack=False, verbose=False): if verbose: print(f"Inconsistent Courses: {inconsistent_courses}") - asyncio.run(send_webhook_requests(semester, inconsistent_courses, path_course_to_status)) + asyncio.run( + send_webhook_requests( + semester, inconsistent_courses, db_course_to_status, path_course_to_status + ) + ) if verbose and inconsistent_courses: print("Sent updates to webhook.") diff --git a/k8s/main.ts b/k8s/main.ts index 1b2dbb0d..b9442f0f 100644 --- a/k8s/main.ts +++ b/k8s/main.ts @@ -141,7 +141,7 @@ export class MyChart extends PennLabsChart { }) new CronJob(this, 'sync-path-course-statuses', { - schedule: cronTime.everyHour(), + schedule: cronTime.every(30).minutes(), image: backendImage, secret, cmd: ['python', 'manage.py', 'sync_path_status', '--slack'],