Skip to content

Commit efd5fc8

Browse files
DIodideclaude
andauthored
Fix stale settled paths after hoagie data update (#515)
The hoagie requirements data update (#514) changed category names in the YAML files, breaking the verifier's settled path matching for existing users. This adds a management command to clear stale settled paths and also: - Adds POR/SPA to AB_CONCENTRATIONS (new local YAML files) - Cleans up verifier imports (removes unused `requests`, fixes `pathlib` placement) - Management command also creates Major DB records for POR/SPA Run on production after deploy: docker compose -f docker-compose.prod.yml exec -T web python manage.py clear_settled_paths --apply Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4e7891e commit efd5fc8

3 files changed

Lines changed: 102 additions & 5 deletions

File tree

tigerpath/majors_and_certificates/scripts/university_info.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,12 @@
172172
"PHI": "Philosophy",
173173
"PHY": "Physics",
174174
"POL": "Politics",
175+
"POR": "Portuguese",
175176
"PSY": "Psychology",
176177
"REL": "Religion",
177178
"SLA": "Slavic Languages and Literatures",
178179
"SOC": "Sociology",
180+
"SPA": "Spanish",
179181
"SPI": "Public and International Affairs",
180182
"SPO": "Spanish and Portuguese",
181183
}

tigerpath/majors_and_certificates/scripts/verifier.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@
22
import collections
33
import copy
44
import os
5+
import pathlib
56
from functools import lru_cache
67

7-
import requests
88
import yaml
99

1010
from . import university_info
1111

12-
# Allow overriding the data repo base via env var for easy testing
13-
# Must end with a trailing slash and point to a raw.githubusercontent.com base
14-
import pathlib
15-
1612
_LOCAL_DATA_DIR = pathlib.Path(__file__).resolve().parent.parent.parent / "requirements_data"
1713

14+
1815
@lru_cache(maxsize=256)
1916
def _load_yaml(path: str):
2017
"""Load and parse YAML from the local requirements data directory."""
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
Clear stale 'settled' paths from all users' schedules and add new Major records.
3+
4+
When the requirement YAML data is updated with new category names, existing users'
5+
settled paths (stored in user_schedule JSON) no longer match the new requirement
6+
tree structure. This command clears those stale paths and adds Major records for
7+
newly available concentrations.
8+
9+
The verifier's auto-settle feature will re-assign courses that can only satisfy
10+
one requirement; users only need to manually re-settle ambiguous courses.
11+
12+
Usage:
13+
python manage.py clear_settled_paths # dry-run (default)
14+
python manage.py clear_settled_paths --apply # actually write changes
15+
"""
16+
17+
from django.core.management.base import BaseCommand
18+
19+
from tigerpath.models import Major, UserProfile
20+
21+
NEW_MAJORS = [
22+
{"name": "Portuguese", "code": "POR", "degree": "AB"},
23+
{"name": "Spanish", "code": "SPA", "degree": "AB"},
24+
]
25+
26+
27+
class Command(BaseCommand):
28+
help = "Clear stale settled paths and add new Major records for updated requirement data"
29+
30+
def add_arguments(self, parser):
31+
parser.add_argument(
32+
"--apply",
33+
action="store_true",
34+
default=False,
35+
help="Actually write changes to the database (default is dry-run)",
36+
)
37+
38+
def handle(self, *args, **options):
39+
apply = options["apply"]
40+
mode = "APPLIED" if apply else "DRY-RUN"
41+
42+
# --- Step 1: Add new Major records ---
43+
self.stdout.write(f"\n[{mode}] Step 1: Adding new Major records")
44+
for m in NEW_MAJORS:
45+
exists = Major.objects.filter(code=m["code"]).exists()
46+
if exists:
47+
self.stdout.write(f" {m['code']} already exists, skipping")
48+
else:
49+
self.stdout.write(f" {m['code']} ({m['name']}) - will create")
50+
if apply:
51+
Major.objects.create(
52+
name=m["name"],
53+
code=m["code"],
54+
degree=m["degree"],
55+
supported=True,
56+
)
57+
58+
# --- Step 2: Clear settled paths ---
59+
self.stdout.write(f"\n[{mode}] Step 2: Clearing settled paths from user schedules")
60+
profiles = UserProfile.objects.exclude(user_schedule__isnull=True)
61+
total = profiles.count()
62+
affected = 0
63+
courses_cleared = 0
64+
65+
for profile in profiles.iterator():
66+
schedule = profile.user_schedule
67+
if not schedule:
68+
continue
69+
70+
modified = False
71+
for semester in schedule:
72+
if not isinstance(semester, list):
73+
continue
74+
for course in semester:
75+
if not isinstance(course, dict):
76+
continue
77+
settled = course.get("settled")
78+
if settled and isinstance(settled, list) and len(settled) > 0:
79+
course["settled"] = []
80+
modified = True
81+
courses_cleared += 1
82+
83+
if modified:
84+
affected += 1
85+
if apply:
86+
profile.user_schedule = schedule
87+
profile.save(update_fields=["user_schedule"])
88+
89+
self.stdout.write(f"\n[{mode}] Results:")
90+
self.stdout.write(f" Scanned {total} profiles")
91+
self.stdout.write(f" {affected} profiles had settled paths")
92+
self.stdout.write(f" {courses_cleared} course-requirement bindings cleared")
93+
if not apply:
94+
self.stdout.write(
95+
self.style.WARNING("\nNo changes written. Pass --apply to commit changes.")
96+
)
97+
else:
98+
self.stdout.write(self.style.SUCCESS("\nDone. Migration complete."))

0 commit comments

Comments
 (0)