The system supports hybrid constraint scope, allowing constraints to apply either to an entire requirement category or to specific groups within that category.
Constraints are identified by a 5-tuple: (program_id, category, semester, year, group_name)
Scope Logic:
group_name exists → group-level constraint (applies only to that specific group)group_name is None → category-level constraint (applies to all groups in category)# Determine constraint scope based on group_name presence
constraint_key = (program.id, category, semester, year, group_name)
Constraints use JSON fields for flexible parameter storage:
Constraint types supported:
{"credits_min": X, "credits_max": Y}{"courses_min": X, "courses_max": Y}{"level": 3000, "courses": 2}{"tag": "true", "courses": 2} or {"tag": "lab", "credits": 7}{"tag_field": "has_lab"} or {"tag_field": "course_type"}{
"group_name": "Biology Lab", // Optional: for group-level constraints
"subject_codes": ["BIOS", "CHEM"], // Optional: limit to specific subjects
"tag_field": "has_lab" // Optional: specify which tag field to check
}
During constraint evaluation, the system filters courses based on the scope filter:
Group name filtering (for group-level constraints):
if 'group_name' in scope:
pc_group_name = getattr(pc, 'group_name', None)
if pc_group_name != scope['group_name']:
match = False
Subject codes list filtering:
if 'subject_codes' in scope:
if course.subject_code not in scope['subject_codes']:
match = False
The PlanCourse model includes group name tracking for constraint evaluation:
requirement_group = db.relationship('RequirementGroup', foreign_keys=[requirement_group_id])
@property
def group_name(self):
"""Get the group name from the related RequirementGroup."""
if self.requirement_group:
return self.requirement_group.group_name
return None
CSV Row:
category,semester,year,type,group_name,...,constraint_type,description,min_credits,...
Core Major Requirements,1,1,grouped,,,credits,Minimum 15 credits,15,...
constraint_type but NO group_namescope_filter = {}CSV Row:
category,semester,year,type,group_name,...,constraint_type,description,min_courses,...
Core Major Requirements,1,1,grouped,Biology Lab,min_tag_courses,At least 2 lab courses,,,2,...
constraint_type AND group_namescope_filter = {"group_name": "Biology Lab"}Category: Core Major Requirements (40-45 credits) Groups within category:
Constraints:
group_name in scope_filter clearly indicates group-scoped constraintUses existing scope_filter JSON column in requirement_constraints table:
params (TEXT/JSON): Constraint-specific parametersscope_filter (TEXT/JSON): Optional filtering criteria including group_nameconstraint_type (VARCHAR): Type of constraint (‘credits’, ‘courses’, ‘min_courses_at_level’, etc.)description (TEXT): Human-readable description