From f0ba933f040a72647cdcaebf86550e9e0c03b9d0 Mon Sep 17 00:00:00 2001 From: Julia Chen Date: Sun, 2 Nov 2025 10:47:44 -0500 Subject: [PATCH 1/9] implement BERT --- .../commands/generate_related_courses.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tcf_website/management/commands/generate_related_courses.py diff --git a/tcf_website/management/commands/generate_related_courses.py b/tcf_website/management/commands/generate_related_courses.py new file mode 100644 index 00000000..743954e2 --- /dev/null +++ b/tcf_website/management/commands/generate_related_courses.py @@ -0,0 +1,39 @@ +from sentence_transformers import SentenceTransformer +import pandas as pd + +####### Phase 1: Content Fingerprint ####### + +#Load current semester course data +df = pd.read_csv('semester_data/csv/2025spring.csv') + +features = ["Mnemonic", "Number", "Title", "Topic", "Description"] +df = df[features] + +#relevant columns: +''' +## Must haves +Description +Mnemonic +Number +Title +Pre-requisites (can we extract this easily?) + +## Optional +Instructor +Topic +Disciplines +''' + +# Load a pre-trained model +model = SentenceTransformer('all-MiniLM-L6-v2') + +# Generate embeddings +# use only description or title + description for embeddings? +embeddings = model.encode(df["Description"], normalize_embeddings=True) + +print(embeddings.shape) + +####### Phase 2: Reranking ####### +#Based on pre-req connection, department (Mnemonic), and level (Number) + +#formula? weighted highest to lowest: pre-req connection, department (Mnemonic), and level (Number) From a684da3837d547055a12895945d6042d2a66ed2e Mon Sep 17 00:00:00 2001 From: Julia Chen Date: Sun, 9 Nov 2025 11:38:05 -0500 Subject: [PATCH 2/9] add prereqs as a field for course model --- .../management/commands/load_semester.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tcf_website/management/commands/load_semester.py b/tcf_website/management/commands/load_semester.py index cd272ccf..00331a9e 100644 --- a/tcf_website/management/commands/load_semester.py +++ b/tcf_website/management/commands/load_semester.py @@ -205,6 +205,25 @@ def load_course( attribute.save() attrs.append(attribute) course.disciplines.set(attrs) + if not pd.isnull(description): #prerequisites + pre_req_format = "Pre-requisite" if "Pre-requisite" in course.description else "Prerequisite" + if pre_req_format in course.description: + # Get pre_req from beginning to end + from_pre_req_to_end = course.description[course.description.find(pre_req_format) :] + # Get rid of title of "Prerequisite" or "Pre-requisite" + pre_req_no_title = from_pre_req_to_end[from_pre_req_to_end.find(":") + 1 :] + + # Check if in-line or not for pre_req + if pre_req_no_title.find(".") > 0: + pre_req_text = pre_req_no_title[: pre_req_no_title.find(".")] + else: + pre_req_text = pre_req_no_title + + import re + # Match only on course mnemonic and code + matches = re.findall(r'([A-Z]{2,4}\s?\d{4})', pre_req_text) + prereq_codes = [m.strip().upper() for m in matches] + course.prerequisites = prereq_codes if not course.description and not pd.isnull(description): course.description = description if not course.title and not pd.isnull(title): From 7e5c52369276d80930724713724cd9e51198804e Mon Sep 17 00:00:00 2001 From: Julia Chen Date: Sun, 9 Nov 2025 11:39:04 -0500 Subject: [PATCH 3/9] add prereqs as a JSON field for course model --- tcf_website/models/models.py | 2 ++ tcf_website/views/team_info.json | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/tcf_website/models/models.py b/tcf_website/models/models.py index 23faa7ad..0f2bf663 100644 --- a/tcf_website/models/models.py +++ b/tcf_website/models/models.py @@ -547,6 +547,8 @@ class Course(models.Model): description = models.TextField(blank=True) # Course disciplines. Optional. disciplines = models.ManyToManyField(Discipline, blank=True) + # Course prerequisites. Optional. + prerequisites = models.JSONField(default=list, blank=True) # Course number. Required. number = models.IntegerField( diff --git a/tcf_website/views/team_info.json b/tcf_website/views/team_info.json index 6c3a1112..fd16a957 100644 --- a/tcf_website/views/team_info.json +++ b/tcf_website/views/team_info.json @@ -259,6 +259,13 @@ "class": "2027", "img_filename": "ENG_Namai_Kureishi.jpg", "github": "namaikureishi" + }, + {6 + "name": "Julia Chen", + "role": "Developer", + "class": "2029", + "img_filename": "ENG_Julia_Chen.jpg", + "github": "juliaxchen" } ], "design_team": [ From e8c1c2fca10132e8826728942723fc800760ab8f Mon Sep 17 00:00:00 2001 From: juliaxchen Date: Sun, 9 Nov 2025 21:05:09 -0500 Subject: [PATCH 4/9] Remove Julia Chen from team_info.json Removed Julia Chen's information from the team info. --- tcf_website/views/team_info.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tcf_website/views/team_info.json b/tcf_website/views/team_info.json index fd16a957..3594210c 100644 --- a/tcf_website/views/team_info.json +++ b/tcf_website/views/team_info.json @@ -260,13 +260,6 @@ "img_filename": "ENG_Namai_Kureishi.jpg", "github": "namaikureishi" }, - {6 - "name": "Julia Chen", - "role": "Developer", - "class": "2029", - "img_filename": "ENG_Julia_Chen.jpg", - "github": "juliaxchen" - } ], "design_team": [ { From dec97e8feaf6c61fa2742b76f25697680f633829 Mon Sep 17 00:00:00 2001 From: juliaxchen Date: Sun, 9 Nov 2025 21:05:56 -0500 Subject: [PATCH 5/9] Delete tcf_website/management/commands/generate_related_courses.py --- .../commands/generate_related_courses.py | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 tcf_website/management/commands/generate_related_courses.py diff --git a/tcf_website/management/commands/generate_related_courses.py b/tcf_website/management/commands/generate_related_courses.py deleted file mode 100644 index 743954e2..00000000 --- a/tcf_website/management/commands/generate_related_courses.py +++ /dev/null @@ -1,39 +0,0 @@ -from sentence_transformers import SentenceTransformer -import pandas as pd - -####### Phase 1: Content Fingerprint ####### - -#Load current semester course data -df = pd.read_csv('semester_data/csv/2025spring.csv') - -features = ["Mnemonic", "Number", "Title", "Topic", "Description"] -df = df[features] - -#relevant columns: -''' -## Must haves -Description -Mnemonic -Number -Title -Pre-requisites (can we extract this easily?) - -## Optional -Instructor -Topic -Disciplines -''' - -# Load a pre-trained model -model = SentenceTransformer('all-MiniLM-L6-v2') - -# Generate embeddings -# use only description or title + description for embeddings? -embeddings = model.encode(df["Description"], normalize_embeddings=True) - -print(embeddings.shape) - -####### Phase 2: Reranking ####### -#Based on pre-req connection, department (Mnemonic), and level (Number) - -#formula? weighted highest to lowest: pre-req connection, department (Mnemonic), and level (Number) From f308d40da8ad34626f7b683f5b1b5b5dd0db5ef3 Mon Sep 17 00:00:00 2001 From: juliaxchen Date: Sun, 9 Nov 2025 21:07:03 -0500 Subject: [PATCH 6/9] Fix JSON formatting in team_info.json --- tcf_website/views/team_info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcf_website/views/team_info.json b/tcf_website/views/team_info.json index 3594210c..6c3a1112 100644 --- a/tcf_website/views/team_info.json +++ b/tcf_website/views/team_info.json @@ -259,7 +259,7 @@ "class": "2027", "img_filename": "ENG_Namai_Kureishi.jpg", "github": "namaikureishi" - }, + } ], "design_team": [ { From bf27bbf082b97492dfcf70f929a3bf6b715f0de8 Mon Sep 17 00:00:00 2001 From: juliaxchen Date: Mon, 10 Nov 2025 11:29:25 -0500 Subject: [PATCH 7/9] Refactor prerequisite extraction logic Refactor prerequisite handling to use description variable instead of course.description for consistency. --- tcf_website/management/commands/load_semester.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tcf_website/management/commands/load_semester.py b/tcf_website/management/commands/load_semester.py index 00331a9e..6d852461 100644 --- a/tcf_website/management/commands/load_semester.py +++ b/tcf_website/management/commands/load_semester.py @@ -206,10 +206,10 @@ def load_course( attrs.append(attribute) course.disciplines.set(attrs) if not pd.isnull(description): #prerequisites - pre_req_format = "Pre-requisite" if "Pre-requisite" in course.description else "Prerequisite" - if pre_req_format in course.description: + pre_req_format = "Pre-requisite" if "Pre-requisite" in description else "Prerequisite" + if pre_req_format in description: # Get pre_req from beginning to end - from_pre_req_to_end = course.description[course.description.find(pre_req_format) :] + from_pre_req_to_end = description[description.find(pre_req_format) :] # Get rid of title of "Prerequisite" or "Pre-requisite" pre_req_no_title = from_pre_req_to_end[from_pre_req_to_end.find(":") + 1 :] @@ -219,7 +219,6 @@ def load_course( else: pre_req_text = pre_req_no_title - import re # Match only on course mnemonic and code matches = re.findall(r'([A-Z]{2,4}\s?\d{4})', pre_req_text) prereq_codes = [m.strip().upper() for m in matches] From 3f79aad12cfe013fd69738c7c36f07c37c4e75df Mon Sep 17 00:00:00 2001 From: Nahduma26 Date: Sun, 16 Nov 2025 14:26:43 -0500 Subject: [PATCH 8/9] Make migration for prerequisites --- .../migrations/0023_course_prerequisites.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tcf_website/migrations/0023_course_prerequisites.py diff --git a/tcf_website/migrations/0023_course_prerequisites.py b/tcf_website/migrations/0023_course_prerequisites.py new file mode 100644 index 00000000..dfa0af49 --- /dev/null +++ b/tcf_website/migrations/0023_course_prerequisites.py @@ -0,0 +1,18 @@ +# Generated manually for prerequisites field + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tcf_website", "0022_coursegrade_tcf_website_course__76e103_idx_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="course", + name="prerequisites", + field=models.JSONField(blank=True, default=list), + ), + ] From a9550aa93a963582ffd7bfe812d05d68ffd366e7 Mon Sep 17 00:00:00 2001 From: Nahduma26 Date: Sun, 16 Nov 2025 14:38:50 -0500 Subject: [PATCH 9/9] Make course prereq migration and add prereq tests --- ...rerequisites.py => 0024_course_prerequisites.py} | 2 +- tcf_website/tests/test_course.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) rename tcf_website/migrations/{0023_course_prerequisites.py => 0024_course_prerequisites.py} (80%) diff --git a/tcf_website/migrations/0023_course_prerequisites.py b/tcf_website/migrations/0024_course_prerequisites.py similarity index 80% rename from tcf_website/migrations/0023_course_prerequisites.py rename to tcf_website/migrations/0024_course_prerequisites.py index dfa0af49..5d6844d3 100644 --- a/tcf_website/migrations/0023_course_prerequisites.py +++ b/tcf_website/migrations/0024_course_prerequisites.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("tcf_website", "0022_coursegrade_tcf_website_course__76e103_idx_and_more"), + ("tcf_website", "0023_remove_sectionenrollment_section_and_more"), ] operations = [ diff --git a/tcf_website/tests/test_course.py b/tcf_website/tests/test_course.py index 3ef46e02..3f31c00a 100644 --- a/tcf_website/tests/test_course.py +++ b/tcf_website/tests/test_course.py @@ -120,3 +120,16 @@ def test_student_eval_link(self): # need to break into 2 lines because otherwise pylint gets mad # this link doesn't actually work because CS 420 is not a real class self.assertEqual(eval_link, self.course.eval_link()) + + def test_prerequisites_default(self): + """Test that prerequisites field defaults to empty list.""" + self.assertEqual(self.course.prerequisites, []) + + def test_prerequisites_set_and_get(self): + """Test that prerequisites can be set and retrieved.""" + prerequisites = ["CS 1110", "CS 2100"] + self.course.prerequisites = prerequisites + self.course.save() + # Refresh from database + self.course.refresh_from_db() + self.assertEqual(self.course.prerequisites, prerequisites)