11from __future__ import annotations
22from enum import Enum
33from glob import glob
4+ from collections import Counter
45
56from pydantic import Json
67
78from typing import Any
89
910import core
10- from major .requirements import AbstractRequirement
11+ from major .requirements import AbstractRequirement , FreeElectiveRequirement
1112from dataclasses import dataclass
1213
13- from major .requirements . map import REQUIREMENTS_MAP
14+ from major .requirements import loader
1415import json
1516
1617from major .requirements .shared import (
1920)
2021from course import Course
2122
23+ LOADER = loader .Loader ()
2224
2325# Read all degree plan JSON files and store their contents in a hashmap
2426# This is so that we can avoid reading all the files each time we want to get the data for a certain course
@@ -144,7 +146,7 @@ def __init__(
144146 requirements : DegreeRequirementsInput ,
145147 bypasses : BypassInput ,
146148 ) -> None :
147- self .courses = set (courses )
149+ self .courses = set ([ Course . from_name ( course ) for course in courses ] )
148150 self .degree_requirements = self .load_requirements (requirements )
149151 self .solved_core : core .store .AssignmentStore | None = None
150152 self .bypasses = bypasses
@@ -187,9 +189,7 @@ def load_requirements(
187189
188190 # Add requirements
189191 for req_data in requirements_data :
190- major_req .requirements .append (
191- REQUIREMENTS_MAP [req_data ["matcher" ]].from_json (req_data )
192- )
192+ major_req .requirements .append (LOADER .requirement_from_json (req_data ))
193193 degree_requirements .append (major_req )
194194 # We don't need to check the other JSON files
195195 break
@@ -201,23 +201,28 @@ def load_requirements(
201201 def solve (self ) -> DegreeRequirementsSolver :
202202 # Run for core
203203 core_solver = self .load_core ()
204- self .solved_core = core_solver .solve (
205- [Course .from_name (course ) for course in self .courses ], []
206- )
207- # Set of the core courses that are fulfilled, so they won't be considered as free electives
208- used_core_courses = set ()
204+ self .solved_core = core_solver .solve (list (self .courses ), [])
205+
206+ # Counter of the core courses and their used hours, so they won't be considered as free electives.
207+ used_core_courses : Counter [Course ] = Counter ()
209208 if self .solved_core is not None :
210209 for req_fill in self .solved_core .reqs_to_courses .values ():
211- used_core_courses .update ([ course . name for course in req_fill . keys ()] )
210+ used_core_courses .update (req_fill )
212211
213212 # Run for major
214213 for degree_req in self .degree_requirements :
215214 for course in self .courses :
216- # Make sure it's not a core course
217- if course in used_core_courses :
218- continue
219215 for requirement in degree_req .requirements :
220- if requirement .attempt_fulfill (course ):
216+ # Free elective requirements are special, since they can take left over hours from core courses.
217+ if type (requirement ) == FreeElectiveRequirement :
218+ if requirement .attempt_fulfill (
219+ course .name ,
220+ available_hours = (
221+ int (course .hours ) - used_core_courses [course ]
222+ ),
223+ ):
224+ break
225+ elif requirement .attempt_fulfill (course .name ):
221226 break
222227
223228 # Handle requirements bypasses for major
0 commit comments