add course dataclass
This commit is contained in:
4 files changed
+68
-53
No files matched your search
+18
-16
@@ -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
|
||||||
@@ -46,7 +47,7 @@ def insertConflictSolutions(
|
|||||||
# 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
|
||||||
@@ -54,7 +55,7 @@ def generateScheduleV3(
|
|||||||
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:
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
@@ -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...')
|
||||||
|
|||||||
Reference in new issue
Block a user