add course dataclass

This commit is contained in:
SowinskiBraeden committed 2022-11-30 19:57:54 -08:00
1 parent 283c5e72ff
commit fe2561a543
4 files changed
+73 -58

No files matched your search

+23 -21
View File
@@ -6,6 +6,7 @@ from string import hexdigits
from app.util.globals import flex, Error from app.util.globals import flex, Error
from app.util.estimateGrade import getGrade from app.util.estimateGrade import getGrade
from app.util.students import Student, studentsToDict from app.util.students import Student, studentsToDict
from app.util.courses import Course, coursesToDict
# Takes in information to create or add a new conflict # Takes in information to create or add a new conflict
# Returns if the particular student has a previous error # Returns if the particular student has a previous error
@@ -45,16 +46,16 @@ def insertConflictSolutions(
# Then it starts to attempt to fit all classes into a timetable, making corretions along # Then it starts to attempt to fit all classes into a timetable, making corretions along
# the way. Corrections being moving a students class # the way. Corrections being moving a students class
def generateScheduleV3( def generateScheduleV3(
students: list[Student], # Refer to /util/students.py to see the students list structure students: list[Student], # Refer to /util/students.py to see the students list structure
courses: dict, # Refer to /util/courses.py to see the courses dictionary structure courses: dict[str: Course], # Refer to /util/courses.py to see the courses dictionary structure
minReq: int = 18, # minimum requests for a class to run minReq: int = 18, # minimum requests for a class to run
classCap: int = 30, # maximum students per class classCap: int = 30, # maximum students per class
blockClassLimit: int = 40, # Block class limit is the number of classrooms available per block. Default 40 classes per block blockClassLimit: int = 40, # Block class limit is the number of classrooms available per block. Default 40 classes per block
totalBlocks: int = 10, # total blocks between two semesters -> default is 10 for 5 per semester... or this can be 8 for 4 blocks per semester totalBlocks: int = 10, # total blocks between two semesters -> default is 10 for 5 per semester... or this can be 8 for 4 blocks per semester
studentsDir: str = "../output/raw/students.json", studentsDir: str = "../output/raw/students.json",
conflictsDir: str = "../output/raw/conflicts.json", conflictsDir: str = "../output/raw/conflicts.json",
coursesDir: str = "../output/raw/courses.json" coursesDir: str = "../output/raw/courses.json"
) -> tuple[dict, Error]: # Returns the completed 'running' dictionary from above ) -> tuple[dict, Error]: # Returns the completed 'running' dictionary
# Return error that totalBlocks is invalid # Return error that totalBlocks is invalid
if totalBlocks not in (10, 8): if totalBlocks not in (10, 8):
@@ -80,9 +81,9 @@ def generateScheduleV3(
# Tally class request # Tally class request
for request in (request for request in student.Requests if not request.Alt and request.CrsNo not in flex): for request in (request for request in student.Requests if not request.Alt and request.CrsNo not in flex):
code = request.CrsNo code = request.CrsNo
courses[code]["Requests"] += 1 courses[code].Requests += 1
# Add course to active list if enough requests # Add course to active list if enough requests
if courses[code]["Requests"] > minReq and courses[code]["CrsNo"] not in activeCourses: if courses[code].Requests > minReq and courses[code].CrsNo not in activeCourses:
activeCourses[code] = courses[code] activeCourses[code] = courses[code]
@@ -94,14 +95,14 @@ def generateScheduleV3(
for i in range(len(activeCourses)): for i in range(len(activeCourses)):
index = list(activeCourses)[i] index = list(activeCourses)[i]
if index not in emptyClasses: emptyClasses[index] = {} if index not in emptyClasses: emptyClasses[index] = {}
classRunCount = activeCourses[index]["Requests"] // median classRunCount = activeCourses[index].Requests // median
remaining = activeCourses[index]["Requests"] % median remaining = activeCourses[index].Requests % median
# Put number of classRunCount classes in emptyClasses # Put number of classRunCount classes in emptyClasses
for j in range(classRunCount): for j in range(classRunCount):
emptyClasses[index][f"{index}-{hexdigits[j]}"] = { emptyClasses[index][f"{index}-{hexdigits[j]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index].Description,
"expectedLen": median # Number of students expected in this class / may be altered later "expectedLen": median # Number of students expected in this class / may be altered later
} }
@@ -118,7 +119,7 @@ def generateScheduleV3(
# Create a class using remaining # Create a class using remaining
emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = { emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index].Description,
"expectedLen": remaining "expectedLen": remaining
} }
@@ -145,7 +146,7 @@ def generateScheduleV3(
# Create a class using remaining + required amount from existing classes # Create a class using remaining + required amount from existing classes
emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = { emptyClasses[index][f"{index}-{hexdigits[classRunCount]}"] = {
"CrsNo": index, "CrsNo": index,
"Description": activeCourses[index]["Description"], "Description": activeCourses[index].Description,
"expectedLen": remaining "expectedLen": remaining
} }
@@ -225,7 +226,7 @@ def generateScheduleV3(
"index": student.StudentIndex "index": student.StudentIndex
}], }],
"CrsNo": course, "CrsNo": course,
"Description": courses[course]["Description"] "Description": courses[course].Description
} }
getAvailableCourse = False getAvailableCourse = False
break break
@@ -550,13 +551,14 @@ def generateScheduleV3(
# Read timetable and collect data on courses # Read timetable and collect data on courses
for block in running: for block in running:
for course in running[block]: for course in running[block]:
sem = "Sem1" if int(block[5:]) <= blockPerSem else "Sem2" c = running[block][course]["CrsNo"]
courses[running[block][course]["CrsNo"]][sem] += 1 if int(block[5:]) <= blockPerSem: courses[c].Sem1 += 1
courses[running[block][course]["CrsNo"]]["Occupied"] += len(running[block][course]["students"]) else: courses[c].Sem2 += 1
courses[c].Occupied += len(running[block][course]["students"])
for course in courses: for course in courses:
courses[course]["Total"] = courses[course]["Sem1"] + courses[course]["Sem2"] courses[course].Total = courses[course].Sem1 + courses[course].Sem2
courses[course]["Seats"] = courses[course]["Total"] * classCap courses[course].Seats = courses[course].Total * classCap
# Update/log new student records # Update/log new student records
with open(studentsDir, "w") as outfile: with open(studentsDir, "w") as outfile:
@@ -564,7 +566,7 @@ def generateScheduleV3(
# Update/log new course records # Update/log new course records
with open(coursesDir, "w") as outfile: with open(coursesDir, "w") as outfile:
json.dump(courses, outfile, indent=2) json.dump(coursesToDict(courses), outfile, indent=2)
# Save timetable to json # Save timetable to json
with open('./output/raw/json/timetable.json', 'w') as outfile: with open('./output/raw/json/timetable.json', 'w') as outfile:
+7 -4
View File
@@ -4,8 +4,11 @@ from app.util.globals import flex
import xlsxwriter import xlsxwriter
import docx import docx
from app.util.courses import Course
from app.util.students import Student
def putMasterTimetable( def putMasterTimetable(
table: dict, table: dict[str: dict],
output_dir: str = './output/final' output_dir: str = './output/final'
) -> None: ) -> None:
@@ -36,8 +39,8 @@ def putMasterTimetable(
workbook.close() workbook.close()
def putScheduleToWord( def putScheduleToWord(
courses: dict, courses: dict[str: Course],
student: dict, student: Student,
output_dir: str = './output/final/student_schedules' output_dir: str = './output/final/student_schedules'
) -> None: ) -> None:
@@ -64,7 +67,7 @@ def putScheduleToWord(
for block in student['schedule']: for block in student['schedule']:
courseCode = student.Schedule[block][0] courseCode = student.Schedule[block][0]
if courseCode in flex: courseName = 'Study' if courseCode in flex: courseName = 'Study'
else: courseName = courses[courseCode[:-2]]["Description"] else: courseName = courses[courseCode[:-2]].Description
blockNum = int(block.split('block')[1]) blockNum = int(block.split('block')[1])
semester = 2 if blockNum > 5 else 1 semester = 2 if blockNum > 5 else 1
if blockNum > (len(student.Schedule)/2): blockNum -= (len(student.Schedule)/2) if blockNum > (len(student.Schedule)/2): blockNum -= (len(student.Schedule)/2)
+35 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3.11 #!/usr/bin/env python3.11
import json import json
import csv import csv
from dataclasses import dataclass from dataclasses import dataclass, asdict
from app.util.estimateGrade import getGrade from app.util.estimateGrade import getGrade
@dataclass @dataclass
@@ -10,45 +10,54 @@ class Request:
Description: str Description: str
Alt: bool Alt: bool
@dataclass
class Course:
CrsNo: str
Description: str
Grade: int
Requests: int = 0 # number of requests for this
Sem1: int = 0 # number of classes running in sem1
Sem2: int = 0 # number of classes running in sem2
Total: int = 0 # total number of classes running
Seats: int = 0 # total number of seats
Occupied: int = 0 # number of occupied seats
def coursesToDict(courses: dict[str: Course]) -> dict[str: dict]:
return [asdict(courses[c]) for c in courses]
# Get all requested courses from data # Get all requested courses from data
def getCourses( def getCourses(
data_dir: str, data_dir: str,
log: int = False, log: int = False,
log_dir: str = './output/raw/courses.json', log_dir: str = './output/raw/courses.json',
) -> dict[dict[str: any]]: ) -> dict[str: Course]:
courses = {} courses: dict[str: Course] = {}
with open(data_dir, newline='') as csvfile: with open(data_dir, newline='') as csvfile:
reader = csv.DictReader(csvfile) reader = csv.DictReader(csvfile)
for row in reader: for row in reader:
exists = False exists = False
for course in courses: for course in courses:
exists = True if courses[course]["CrsNo"] == row["CrsNo"] else False exists = True if courses[course].CrsNo == row["CrsNo"] else False
if exists: break if exists: break
if not exists: if not exists:
grade = getGrade(row["CrsNo"], row["Description"]) grade = getGrade(row["CrsNo"], row["Description"])
courses[row["CrsNo"]] = { courses[row["CrsNo"]] = Course(
"CrsNo": row["CrsNo"], row["CrsNo"],
"Description": row["Description"], row["Description"],
"Grade": grade, grade
"Requests": 0, )
"Sem1": 0, # number of classes running in sem1
"Sem2": 0, # number of classes running in sem2
"Total": 0, # total number of classes running
"Seats": 0, # total number of seats
"Occupied": 0, # number of occupied seats
}
if log: if log:
with open(log_dir, "w") as outfile: with open(log_dir, "w") as outfile:
json.dump(courses, outfile, indent=2) json.dump(coursesToDict(courses), outfile, indent=2)
return courses return courses
# Writes all course data to csv file # Writes all course data to csv file
def writeCoursesToCSV( def writeCoursesToCSV(
courses: dict, courses: dict[str: Course],
output_dir: str = './output/raw/csv/courses.csv' output_dir: str = './output/raw/csv/courses.csv'
) -> None: ) -> None:
with open(output_dir, 'w') as file: with open(output_dir, 'w') as file:
@@ -69,14 +78,14 @@ def writeCoursesToCSV(
for course in courses: for course in courses:
courseData = ( courseData = (
courses[course]["CrsNo"], courses[course].CrsNo,
courses[course]["Description"], courses[course].Description,
courses[course]["Grade"], courses[course].Grade,
courses[course]["Requests"], courses[course].Requests,
courses[course]["Sem1"], courses[course].Sem1,
courses[course]["Sem2"], courses[course].Sem2,
courses[course]["Total"], courses[course].Total,
courses[course]["Seats"], courses[course].Seats,
courses[course]["Occupied"] courses[course].Occupied
) )
writer.writerow(courseData) writer.writerow(courseData)
+8 -7
View File
@@ -6,7 +6,7 @@ import json
from app.util.convertRawData import putScheduleToWord, putMasterTimetable from app.util.convertRawData import putScheduleToWord, putMasterTimetable
from app.generator import generateScheduleV3 from app.generator import generateScheduleV3
from app.util.globals import Error from app.util.globals import Error
from app.util.courses import getCourses, writeCoursesToCSV from app.util.courses import getCourses, writeCoursesToCSV, Course
from app.util.students import getStudents, writeStudentsToCSV, Student from app.util.students import getStudents, writeStudentsToCSV, Student
from app.util.errorCalculator import writeErrorsToCSV from app.util.errorCalculator import writeErrorsToCSV
from app.util.validator import validateInputData from app.util.validator import validateInputData
@@ -41,8 +41,7 @@ def start(
# save raw file data to local file # save raw file data to local file
eel.post_data('Saving raw data to local file...') eel.post_data('Saving raw data to local file...')
with open(raw_data_dir, 'w') as raw_file: with open(raw_data_dir, 'w') as raw_file: raw_file.write(raw_file_data)
raw_file.write(raw_file_data)
# Ensure params are of correct type # Ensure params are of correct type
min_req = int(min_req) min_req = int(min_req)
@@ -56,14 +55,14 @@ def start(
# call pre-algorithm functions read raw data into a processable format # call pre-algorithm functions read raw data into a processable format
eel.post_data('Collecting student information...') eel.post_data('Collecting student information...')
students = getStudents( students: list[Student] = getStudents(
raw_data_dir, raw_data_dir,
log = False, log = False,
totalBlocks = total_blocks, totalBlocks = total_blocks,
log_dir = './output/raw/json/students.json' log_dir = './output/raw/json/students.json'
) )
eel.post_data('Collecting course information...') eel.post_data('Collecting course information...')
courses = getCourses( courses: dict[str: Course] = getCourses(
raw_data_dir, raw_data_dir,
log = True, log = True,
log_dir = f'{raw_json_dir}/courses.json' log_dir = f'{raw_json_dir}/courses.json'
@@ -91,8 +90,10 @@ def start(
eel.post_data('Gathering latest data...') eel.post_data('Gathering latest data...')
with open(f'{raw_json_dir}/students.json', 'r') as studentFile: with open(f'{raw_json_dir}/students.json', 'r') as studentFile:
# Unpack dictionary to Student dataclass # Unpack dictionary to Student dataclass
students = [Student(**s) for s in json.load(studentFile)] students: list[Student] = [Student(**s) for s in json.load(studentFile)]
with open(f'{raw_json_dir}/courses.json', 'r') as cFile: courses = json.load(cFile) with open(f'{raw_json_dir}/courses.json', 'r') as cFile:
# Unpack dictionary to Course dataclass
courses: dict[str: Course] = {c["CrsNo"]: Course(**c) for c in json.load(cFile)}
# call post-algorithm functions to present sorted data # call post-algorithm functions to present sorted data
eel.post_data('Writing data to .csv files...') eel.post_data('Writing data to .csv files...')