add student dataclass
This commit is contained in:
6 files changed
+124
-98
No files matched your search
+56
-55
@@ -5,6 +5,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
|
||||||
|
|
||||||
# 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
|
||||||
@@ -44,12 +45,12 @@ 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, # 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, # 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"
|
||||||
@@ -77,8 +78,8 @@ def generateScheduleV3(
|
|||||||
activeCourses = {}
|
activeCourses = {}
|
||||||
for student in students:
|
for student in students:
|
||||||
# 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:
|
||||||
@@ -183,9 +184,9 @@ def generateScheduleV3(
|
|||||||
# bias to students at the top of the list
|
# bias to students at the top of the list
|
||||||
student = tempStudents[random.randint(0, len(tempStudents)-1)]
|
student = tempStudents[random.randint(0, len(tempStudents)-1)]
|
||||||
|
|
||||||
alternates = [request for request in student["requests"] if request["alt"]]
|
alternates = [request for request in student.Requests if request.Alt]
|
||||||
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):
|
||||||
course = request["CrsNo"]
|
course = request.CrsNo
|
||||||
getAvailableCourse = True
|
getAvailableCourse = True
|
||||||
isAlt = False
|
isAlt = False
|
||||||
while getAvailableCourse:
|
while getAvailableCourse:
|
||||||
@@ -198,8 +199,8 @@ def generateScheduleV3(
|
|||||||
if len(selectedCourses[cname]["students"]) < emptyClasses[course][cname]["expectedLen"]:
|
if len(selectedCourses[cname]["students"]) < emptyClasses[course][cname]["expectedLen"]:
|
||||||
# Class exists with room for student
|
# Class exists with room for student
|
||||||
selectedCourses[cname]["students"].append({
|
selectedCourses[cname]["students"].append({
|
||||||
"Pupil #": student["Pupil #"],
|
"Pupil #": student.PupilNum,
|
||||||
"index": student["studentIndex"]
|
"index": student.StudentIndex
|
||||||
})
|
})
|
||||||
getAvailableCourse = False
|
getAvailableCourse = False
|
||||||
break
|
break
|
||||||
@@ -208,7 +209,7 @@ def generateScheduleV3(
|
|||||||
if cname[len(cname)-1] == f"{len(emptyClasses[course])-1}":
|
if cname[len(cname)-1] == f"{len(emptyClasses[course])-1}":
|
||||||
if len(alternates) > 0:
|
if len(alternates) > 0:
|
||||||
# Use alternate
|
# Use alternate
|
||||||
course = alternates[0]["CrsNo"]
|
course = alternates[0].CrsNo
|
||||||
alternates.remove(alternates[0])
|
alternates.remove(alternates[0])
|
||||||
isAlt = True
|
isAlt = True
|
||||||
break
|
break
|
||||||
@@ -220,8 +221,8 @@ def generateScheduleV3(
|
|||||||
elif cname not in selectedCourses:
|
elif cname not in selectedCourses:
|
||||||
selectedCourses[cname] = {
|
selectedCourses[cname] = {
|
||||||
"students": [{
|
"students": [{
|
||||||
"Pupil #": student["Pupil #"],
|
"Pupil #": student.PupilNum,
|
||||||
"index": student["studentIndex"]
|
"index": student.StudentIndex
|
||||||
}],
|
}],
|
||||||
"CrsNo": course,
|
"CrsNo": course,
|
||||||
"Description": courses[course]["Description"]
|
"Description": courses[course]["Description"]
|
||||||
@@ -232,7 +233,7 @@ def generateScheduleV3(
|
|||||||
elif course not in emptyClasses:
|
elif course not in emptyClasses:
|
||||||
if len(alternates) > 0:
|
if len(alternates) > 0:
|
||||||
# Use alternate
|
# Use alternate
|
||||||
course = alternates[0]["CrsNo"]
|
course = alternates[0].CrsNo
|
||||||
alternates.remove(alternates[0])
|
alternates.remove(alternates[0])
|
||||||
isAlt = True
|
isAlt = True
|
||||||
else:
|
else:
|
||||||
@@ -240,7 +241,7 @@ def generateScheduleV3(
|
|||||||
# handle options to solve for missing class
|
# handle options to solve for missing class
|
||||||
getAvailableCourse = False
|
getAvailableCourse = False
|
||||||
|
|
||||||
students[student["studentIndex"]]["remainingAlts"] = alternates
|
students[student.StudentIndex].RemainingAlts = alternates
|
||||||
tempStudents.remove(student)
|
tempStudents.remove(student)
|
||||||
|
|
||||||
|
|
||||||
@@ -323,8 +324,8 @@ def generateScheduleV3(
|
|||||||
for block in running:
|
for block in running:
|
||||||
for cname in running[block]:
|
for cname in running[block]:
|
||||||
for student in running[block][cname]["students"]:
|
for student in running[block][cname]["students"]:
|
||||||
students[student["index"]]["schedule"][block].append(cname)
|
students[student["index"]].Schedule[block].append(cname)
|
||||||
students[student["index"]]["classes"] += 1
|
students[student["index"]].Classes += 1
|
||||||
|
|
||||||
|
|
||||||
# Step 6 - Evaluate, move students to fix conflicts
|
# Step 6 - Evaluate, move students to fix conflicts
|
||||||
@@ -334,7 +335,7 @@ def generateScheduleV3(
|
|||||||
studentsCritical, studentsAcceptable = 0, 0
|
studentsCritical, studentsAcceptable = 0, 0
|
||||||
|
|
||||||
for student in students:
|
for student in students:
|
||||||
blocks = [student["schedule"][block] for block in student["schedule"]]
|
blocks = [student.Schedule[block] for block in student.Schedule]
|
||||||
hasConflicts = True if sum(1 for b in blocks if len(b)>1) > 0 else False
|
hasConflicts = True if sum(1 for b in blocks if len(b)>1) > 0 else False
|
||||||
|
|
||||||
# If there is no conflicts
|
# If there is no conflicts
|
||||||
@@ -342,26 +343,26 @@ def generateScheduleV3(
|
|||||||
# or classes the student is inserted to is missing
|
# or classes the student is inserted to is missing
|
||||||
# no more than two classes:
|
# no more than two classes:
|
||||||
# continue to next student
|
# continue to next student
|
||||||
if not hasConflicts and student["classes"] == student["expectedClasses"]: continue
|
if not hasConflicts and student.Classes == student.ExpectedClasses: continue
|
||||||
elif not hasConflicts and (student["expectedClasses"]-2) <= student["classes"] < student["expectedClasses"]:
|
elif not hasConflicts and (student.ExpectedClasses-2) <= student.Classes < student.ExpectedClasses:
|
||||||
# TODO: Insert alternates if available?
|
# TODO: Insert alternates if available?
|
||||||
a_mc_count += 1
|
a_mc_count += 1
|
||||||
acceptableCount += 1
|
acceptableCount += 1
|
||||||
if not newConflict(student["Pupil #"], "", "Acceptable", "A-MC", "Missing 1-2 Classses", conflictLogs): studentsAcceptable += 1
|
if not newConflict(student.PupilNum, "", "Acceptable", "A-MC", "Missing 1-2 Classses", conflictLogs): studentsAcceptable += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
studentData = {
|
studentData = {
|
||||||
"Pupil #": student["Pupil #"],
|
"Pupil #": student.PupilNum,
|
||||||
"index": student["studentIndex"]
|
"index": student.StudentIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasConflicts:
|
if hasConflicts:
|
||||||
student["classes"] = 0
|
student.Classes = 0
|
||||||
missing = []
|
missing = []
|
||||||
# Clear student schedule to restructure
|
# Clear student schedule to restructure
|
||||||
for block in student["schedule"]:
|
for block in student.Schedule:
|
||||||
[running[block][cname]["students"].remove(studentData) for cname in student["schedule"][block]]
|
[running[block][cname]["students"].remove(studentData) for cname in student.Schedule[block]]
|
||||||
student["schedule"][block] = []
|
student.Schedule[block] = []
|
||||||
|
|
||||||
# Find class in student schedule that's run the least
|
# Find class in student schedule that's run the least
|
||||||
classes, runCounts = [], []
|
classes, runCounts = [], []
|
||||||
@@ -382,9 +383,9 @@ def generateScheduleV3(
|
|||||||
for cname in running[block]:
|
for cname in running[block]:
|
||||||
if cname[:-2] == classes[index] and len(running[block][cname]["students"]) < classCap:
|
if cname[:-2] == classes[index] and len(running[block][cname]["students"]) < classCap:
|
||||||
running[block][cname]["students"].append(studentData)
|
running[block][cname]["students"].append(studentData)
|
||||||
student["schedule"][block].append(cname)
|
student.Schedule[block].append(cname)
|
||||||
availableBlocks.remove(block)
|
availableBlocks.remove(block)
|
||||||
student["classes"] += 1
|
student.Classes += 1
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -402,21 +403,21 @@ def generateScheduleV3(
|
|||||||
if len(existsIn) > 0:
|
if len(existsIn) > 0:
|
||||||
for i, existing in enumerate(existsIn):
|
for i, existing in enumerate(existsIn):
|
||||||
if solution: break
|
if solution: break
|
||||||
classOut = student["schedule"][existing][0]
|
classOut = student.Schedule[existing][0]
|
||||||
for block in running:
|
for block in running:
|
||||||
if solution: break
|
if solution: break
|
||||||
if block == existing or block not in availableBlocks: continue
|
if block == existing or block not in availableBlocks: continue
|
||||||
for cname in running[block]:
|
for cname in running[block]:
|
||||||
if cname[:-2] == classOut[:-2] and len(running[block][cname]["students"]) < classCap:
|
if cname[:-2] == classOut[:-2] and len(running[block][cname]["students"]) < classCap:
|
||||||
student["classes"] += 1
|
student.Classes += 1
|
||||||
|
|
||||||
# Move to existing class elsewhere
|
# Move to existing class elsewhere
|
||||||
student["schedule"][block].append(cname)
|
student.Schedule[block].append(cname)
|
||||||
running[block][cname]["students"].append(studentData)
|
running[block][cname]["students"].append(studentData)
|
||||||
|
|
||||||
# Overwrite old class
|
# Overwrite old class
|
||||||
running[existing][student["schedule"][existing][0]]["students"].remove(studentData)
|
running[existing][student.Schedule[existing][0]]["students"].remove(studentData)
|
||||||
student["schedule"][existing][0] = existingClassNames[i]
|
student.Schedule[existing][0] = existingClassNames[i]
|
||||||
running[existing][existingClassNames[i]]["students"].append(studentData)
|
running[existing][existingClassNames[i]]["students"].append(studentData)
|
||||||
|
|
||||||
solution = True
|
solution = True
|
||||||
@@ -424,7 +425,7 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
if not solution:
|
if not solution:
|
||||||
# Try alternate
|
# Try alternate
|
||||||
alternates = [alt["CrsNo"] for alt in students[student["studentIndex"]]["remainingAlts"] if alt["CrsNo"] not in flex and alt["CrsNo"] in courseRunInfoCopy]
|
alternates = [alt.CrsNo for alt in students[student.StudentIndex].RemainingAlts if alt.CrsNo not in flex and alt.CrsNo in courseRunInfoCopy]
|
||||||
if len(alternates) == 0: # If no alternates, create critical error
|
if len(alternates) == 0: # If no alternates, create critical error
|
||||||
c_cr_count += 1
|
c_cr_count += 1
|
||||||
criticalCount += 1
|
criticalCount += 1
|
||||||
@@ -435,7 +436,7 @@ def generateScheduleV3(
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not newConflict(
|
if not newConflict(
|
||||||
student["Pupil #"],
|
student.PupilNum,
|
||||||
"", # Student Email
|
"", # Student Email
|
||||||
"Critical", # Err type
|
"Critical", # Err type
|
||||||
"C-CR", # Err code
|
"C-CR", # Err code
|
||||||
@@ -453,9 +454,9 @@ def generateScheduleV3(
|
|||||||
runCounts.append(altRunCounts[altIndex])
|
runCounts.append(altRunCounts[altIndex])
|
||||||
|
|
||||||
# Remove alternate from remaining alternates
|
# Remove alternate from remaining alternates
|
||||||
for remaining in students[student["studentIndex"]]["remainingAlts"]:
|
for remaining in students[student.StudentIndex].RemainingAlts:
|
||||||
if remaining["CrsNo"] == alternates[altIndex]:
|
if remaining.CrsNo == alternates[altIndex]:
|
||||||
students[student["studentIndex"]]["remainingAlts"].remove(remaining)
|
students[student.StudentIndex].RemainingAlts.remove(remaining)
|
||||||
|
|
||||||
# Remove class after inserted or failed to insert
|
# Remove class after inserted or failed to insert
|
||||||
classes.remove(classes[index])
|
classes.remove(classes[index])
|
||||||
@@ -466,7 +467,7 @@ def generateScheduleV3(
|
|||||||
if len(missing) > 0:
|
if len(missing) > 0:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
if student["gradelevel"] is None:
|
if student.Gradelevel is None:
|
||||||
for obj in missing:
|
for obj in missing:
|
||||||
data["missing"].append({
|
data["missing"].append({
|
||||||
"CrsNo": obj["CrsNo"],
|
"CrsNo": obj["CrsNo"],
|
||||||
@@ -482,7 +483,7 @@ def generateScheduleV3(
|
|||||||
courseInfo = running[obj['block']][cname]
|
courseInfo = running[obj['block']][cname]
|
||||||
courseGrade = getGrade(courseInfo['CrsNo'], courseInfo['Description'])
|
courseGrade = getGrade(courseInfo['CrsNo'], courseInfo['Description'])
|
||||||
if courseGrade is None: continue
|
if courseGrade is None: continue
|
||||||
if (student["gradelevel"] == courseGrade) or (student["gradelevel"] == 12 and courseGrade == 11):
|
if (student.Gradelevel == courseGrade) or (student.Gradelevel == 12 and courseGrade == 11):
|
||||||
if len(running[obj['block']][cname]["students"]) < classCap:
|
if len(running[obj['block']][cname]["students"]) < classCap:
|
||||||
blockSolution["solutions"].append({
|
blockSolution["solutions"].append({
|
||||||
"CrsNo": cname,
|
"CrsNo": cname,
|
||||||
@@ -491,23 +492,23 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
data.append(blockSolution)
|
data.append(blockSolution)
|
||||||
|
|
||||||
insertConflictSolutions(student["Pupil #"], conflictLogs, data)
|
insertConflictSolutions(student.PupilNum, conflictLogs, data)
|
||||||
|
|
||||||
metSelfRequirements = True if student["classes"] == student["expectedClasses"] else False
|
metSelfRequirements = True if student.Classes == student.ExpectedClasses else False
|
||||||
if not metSelfRequirements:
|
if not metSelfRequirements:
|
||||||
|
|
||||||
if (student["expectedClasses"] - 2) <= student["classes"] < student["expectedClasses"]:
|
if (student.ExpectedClasses - 2) <= student.Classes < student.ExpectedClasses:
|
||||||
a_mc_count += 1
|
a_mc_count += 1
|
||||||
acceptableCount += 1
|
acceptableCount += 1
|
||||||
if not newConflict(student["Pupil #"], "", "Acceptable", "A-MC", "Missing 1-2 Classses", conflictLogs): studentsAcceptable += 1
|
if not newConflict(student.PupilNum, "", "Acceptable", "A-MC", "Missing 1-2 Classses", conflictLogs): studentsAcceptable += 1
|
||||||
|
|
||||||
elif student["classes"] < (student["expectedClasses"] - 2):
|
elif student.Classes < (student.ExpectedClasses - 2):
|
||||||
# Difference between classes inserted to and
|
# Difference between classes inserted to and
|
||||||
# expected classes is too great
|
# expected classes is too great
|
||||||
if student["Pupil #"] in conflictLogs:
|
if student.PupilNum in conflictLogs:
|
||||||
c_mc_count += 1
|
c_mc_count += 1
|
||||||
criticalCount += 1
|
criticalCount += 1
|
||||||
if not newConflict(student["Pupil #"], "", "Critical", "C-MC", "Missing too many Classses", conflictLogs): studentsCritical += 1
|
if not newConflict(student.PupilNum, "", "Critical", "C-MC", "Missing too many Classses", conflictLogs): studentsCritical += 1
|
||||||
|
|
||||||
finalConflictLogs = {
|
finalConflictLogs = {
|
||||||
"Conflicts": conflictLogs,
|
"Conflicts": conflictLogs,
|
||||||
@@ -541,10 +542,10 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
# Insert flex (spare) course codes to empty blocks
|
# Insert flex (spare) course codes to empty blocks
|
||||||
for student in students:
|
for student in students:
|
||||||
for block in student["schedule"]:
|
for block in student.Schedule:
|
||||||
if len(student["schedule"][block]) == 0:
|
if len(student.Schedule[block]) == 0:
|
||||||
i = 0 if int(block[5:]) <= blockPerSem else 1
|
i = 0 if int(block[5:]) <= blockPerSem else 1
|
||||||
student["schedule"][block].append(flex[i])
|
student.Schedule[block].append(flex[i])
|
||||||
|
|
||||||
# Read timetable and collect data on courses
|
# Read timetable and collect data on courses
|
||||||
for block in running:
|
for block in running:
|
||||||
@@ -559,7 +560,7 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
# Update/log new student records
|
# Update/log new student records
|
||||||
with open(studentsDir, "w") as outfile:
|
with open(studentsDir, "w") as outfile:
|
||||||
json.dump(students, outfile, indent=2)
|
json.dump(studentsToDict(students), outfile, indent=2)
|
||||||
|
|
||||||
# Update/log new course records
|
# Update/log new course records
|
||||||
with open(coursesDir, "w") as outfile:
|
with open(coursesDir, "w") as outfile:
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def putScheduleToWord(
|
|||||||
# create an instance of a word doc.
|
# create an instance of a word doc.
|
||||||
doc = docx.Document()
|
doc = docx.Document()
|
||||||
|
|
||||||
doc.add_heading(f'Schedule for Student {student["Pupil #"]}')
|
doc.add_heading(f'Schedule for Student {student.PupilNum}')
|
||||||
|
|
||||||
# Create a table object
|
# Create a table object
|
||||||
table = doc.add_table(rows=1, cols=3)
|
table = doc.add_table(rows=1, cols=3)
|
||||||
@@ -62,12 +62,12 @@ def putScheduleToWord(
|
|||||||
table.columns[2].width = Inches(0.75)
|
table.columns[2].width = Inches(0.75)
|
||||||
|
|
||||||
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)
|
||||||
row = table.add_row().cells
|
row = table.add_row().cells
|
||||||
row[0].text = f'{courseName}\n({courseCode})'
|
row[0].text = f'{courseName}\n({courseCode})'
|
||||||
row[1].text = str(blockNum)
|
row[1].text = str(blockNum)
|
||||||
@@ -76,4 +76,4 @@ def putScheduleToWord(
|
|||||||
for row in table.rows:
|
for row in table.rows:
|
||||||
row.height = Inches(0.75)
|
row.height = Inches(0.75)
|
||||||
|
|
||||||
doc.save(f'{output_dir}/{student["Pupil #"]}_schedule.docx')
|
doc.save(f'{output_dir}/{student.PupilNum}_schedule.docx')
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
#!/usr/bin/env python3.11
|
#!/usr/bin/env python3.11
|
||||||
import json
|
import json
|
||||||
import csv
|
import csv
|
||||||
|
from dataclasses import dataclass
|
||||||
from app.util.estimateGrade import getGrade
|
from app.util.estimateGrade import getGrade
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Request:
|
||||||
|
CrsNo: str
|
||||||
|
Description: str
|
||||||
|
Alt: bool
|
||||||
|
|
||||||
# Get all requested courses from data
|
# Get all requested courses from data
|
||||||
def getCourses(
|
def getCourses(
|
||||||
data_dir: str,
|
data_dir: str,
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ def extractGrade(string: str) -> int:
|
|||||||
return grade if grade is not None and grade >= 8 else None
|
return grade if grade is not None and grade >= 8 else None
|
||||||
|
|
||||||
# Estimage students grade based off requests
|
# Estimage students grade based off requests
|
||||||
def estimateStudentGrade(pupil: dict) -> int:
|
def estimateStudentGrade(pupil) -> int:
|
||||||
grades = [] # List of all possible grades
|
grades = [] # List of all possible grades
|
||||||
for request in (r for r in pupil["requests"] if r not in flex):
|
for request in (r for r in pupil.Requests if r not in flex):
|
||||||
extractedGrade = getGrade(request["CrsNo"], request["Description"])
|
extractedGrade = getGrade(request.CrsNo, request.Description)
|
||||||
if extractedGrade is not None: grades.append(extractedGrade)
|
if extractedGrade is not None: grades.append(extractedGrade)
|
||||||
|
|
||||||
return None if len(grades) == 0 else most_frequent(grades) # Final estimate of grade
|
return None if len(grades) == 0 else most_frequent(grades) # Final estimate of grade
|
||||||
|
|||||||
+48
-30
@@ -3,6 +3,26 @@ import json
|
|||||||
import csv
|
import csv
|
||||||
from app.util.estimateGrade import estimateStudentGrade
|
from app.util.estimateGrade import estimateStudentGrade
|
||||||
from app.util.globals import flex
|
from app.util.globals import flex
|
||||||
|
from app.util.courses import Request
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Student:
|
||||||
|
PupilNum: str
|
||||||
|
Requests: list[Request]
|
||||||
|
Schedule: dict[str: list]
|
||||||
|
ExpectedClasses: int
|
||||||
|
Classes: int
|
||||||
|
RemainingAlts: list[Request]
|
||||||
|
StudentIndex: int
|
||||||
|
Gradelevel: int = None # set to None since don't pass gradelevel
|
||||||
|
|
||||||
|
# This function converst Student.Requests to an array of dictionaries from an array of Requests
|
||||||
|
def RequestsToDict(self) -> None: self.Requests = [asdict(r) for r in self.Requests]
|
||||||
|
|
||||||
|
def studentsToDict(students: list[Student]) -> list[dict]:
|
||||||
|
[s.RequestsToDict for s in students]
|
||||||
|
return [asdict(s) for s in students]
|
||||||
|
|
||||||
# sort data into usable dictionary
|
# sort data into usable dictionary
|
||||||
def getStudents(
|
def getStudents(
|
||||||
@@ -12,56 +32,54 @@ def getStudents(
|
|||||||
log_dir: str = './output/raw/students.json'
|
log_dir: str = './output/raw/students.json'
|
||||||
) -> list[ dict[ str: any ] ]:
|
) -> list[ dict[ str: any ] ]:
|
||||||
|
|
||||||
students: list[ dict[ str: any ] ] = []
|
students: list[Student] = []
|
||||||
|
|
||||||
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 student in students:
|
for student in students:
|
||||||
exists = True if student["Pupil #"] == row["Pupil #"] else False
|
exists = True if student.PupilNum == row["Pupil #"] else False
|
||||||
if exists: break
|
if exists: break
|
||||||
alternate = True if row["Alternate?"] == 'TRUE' else False
|
alternate = True if str(row["Alternate?"]).upper() == 'TRUE' else False
|
||||||
if exists:
|
if exists:
|
||||||
if len(students[student["studentIndex"]]["requests"]) >= totalBlocks and not alternate and row["CrsNo"] not in flex: alternate = True
|
if len(students[student.StudentIndex].Requests) >= totalBlocks and not alternate and row["CrsNo"] not in flex: alternate = True
|
||||||
students[student["studentIndex"]]["requests"].append({
|
students[student.StudentIndex].Requests.append(
|
||||||
"CrsNo": row["CrsNo"],
|
Request(
|
||||||
"Description": row["Description"],
|
row["CrsNo"],
|
||||||
"alt": alternate
|
row["Description"],
|
||||||
})
|
alternate
|
||||||
if row["CrsNo"] not in flex and not alternate and students[student["studentIndex"]]["expectedClasses"] < 10:
|
))
|
||||||
students[student["studentIndex"]]["expectedClasses"] += 1
|
if row["CrsNo"] not in flex and not alternate and students[student.StudentIndex].ExpectedClasses < 10:
|
||||||
|
students[student.StudentIndex].ExpectedClasses += 1
|
||||||
else:
|
else:
|
||||||
newStudent = {
|
newStudent: Student = Student(
|
||||||
"Pupil #": row["Pupil #"],
|
row["Pupil #"],
|
||||||
"requests": [{
|
[Request(
|
||||||
"CrsNo": row["CrsNo"],
|
row["CrsNo"],
|
||||||
"Description": row["Description"],
|
row["Description"],
|
||||||
"alt": alternate
|
alternate
|
||||||
}],
|
)],
|
||||||
"schedule": {},
|
{}, 1, 0, [],
|
||||||
"expectedClasses": 1,
|
len(students)
|
||||||
"classes": 0,
|
)
|
||||||
"remainingAlts": [],
|
newStudent.Gradelevel = row.get("Grade") if row.get("Grade") is not None else row.get("grade")
|
||||||
"studentIndex": len(students)
|
for i in range(1, totalBlocks+1): newStudent.Schedule[f'block{i}'] = []
|
||||||
}
|
|
||||||
newStudent["gradelevel"] = row.get("Grade") if row.get("Grade") is not None else row.get("grade")
|
|
||||||
for i in range(1, totalBlocks+1): newStudent["schedule"][f'block{i}'] = []
|
|
||||||
students.append(newStudent)
|
students.append(newStudent)
|
||||||
|
|
||||||
# Estimate student grades
|
# Estimate student grades
|
||||||
for student in students:
|
for student in students:
|
||||||
if student["gradelevel"] is None: student["gradelevel"] = estimateStudentGrade(student)
|
if student.Gradelevel is None: student.Gradelevel = estimateStudentGrade(student)
|
||||||
|
|
||||||
if log:
|
if log:
|
||||||
with open(log_dir, "w") as outfile:
|
with open(log_dir, "w") as outfile:
|
||||||
json.dump(students, outfile, indent=2)
|
json.dump(studentsToDict(students), outfile, indent=2)
|
||||||
|
|
||||||
return students
|
return students
|
||||||
|
|
||||||
# Writes all students data to csv file
|
# Writes all students data to csv file
|
||||||
def writeStudentsToCSV(
|
def writeStudentsToCSV(
|
||||||
students: dict,
|
students: list[Student],
|
||||||
output_dir: str = './output/raw/csv/students.csv'
|
output_dir: str = './output/raw/csv/students.csv'
|
||||||
) -> None:
|
) -> None:
|
||||||
with open(output_dir, 'w') as file:
|
with open(output_dir, 'w') as file:
|
||||||
@@ -71,5 +89,5 @@ def writeStudentsToCSV(
|
|||||||
writer.writerow(data)
|
writer.writerow(data)
|
||||||
|
|
||||||
for student in students:
|
for student in students:
|
||||||
studentData = (student["Pupil #"], student["classes"], student["gradelevel"])
|
studentData = (student.PupilNum, student.Classes, student.Gradelevel)
|
||||||
writer.writerow(studentData)
|
writer.writerow(studentData)
|
||||||
@@ -7,7 +7,7 @@ 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
|
||||||
from app.util.students import getStudents, writeStudentsToCSV
|
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
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ def start(
|
|||||||
eel.post_data('Collecting student information...')
|
eel.post_data('Collecting student information...')
|
||||||
students = getStudents(
|
students = getStudents(
|
||||||
raw_data_dir,
|
raw_data_dir,
|
||||||
log = True,
|
log = False,
|
||||||
totalBlocks = total_blocks,
|
totalBlocks = total_blocks,
|
||||||
log_dir = './output/raw/json/students.json'
|
log_dir = './output/raw/json/students.json'
|
||||||
)
|
)
|
||||||
@@ -89,13 +89,13 @@ def start(
|
|||||||
|
|
||||||
# Get updated students
|
# Get updated students
|
||||||
eel.post_data('Gathering latest data...')
|
eel.post_data('Gathering latest data...')
|
||||||
with open(f'{raw_json_dir}/students.json', 'r') as studentFile: students = json.load(studentFile)
|
with open(f'{raw_json_dir}/students.json', 'r') as studentFile:
|
||||||
|
# Unpack dictionary to Student dataclass
|
||||||
|
students = [Student(**s) for s in json.load(studentFile)]
|
||||||
|
with open(f'{raw_json_dir}/courses.json', 'r') as cFile: courses = 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...')
|
||||||
# get updated raw data
|
|
||||||
with open(f'{raw_json_dir}/students.json', 'r') as sFile: students = json.load(sFile)
|
|
||||||
with open(f'{raw_json_dir}/courses.json', 'r') as cFile: courses = json.load(cFile)
|
|
||||||
writeStudentsToCSV(students, output_dir='./output/raw/csv/students.csv')
|
writeStudentsToCSV(students, output_dir='./output/raw/csv/students.csv')
|
||||||
writeCoursesToCSV(courses, output_dir='./output/raw/csv/courses.csv')
|
writeCoursesToCSV(courses, output_dir='./output/raw/csv/courses.csv')
|
||||||
writeErrorsToCSV(len(students), conflictsDir='./output/raw/json/conflicts.json', outputDir='./output/raw/csv/error_tracker.csv')
|
writeErrorsToCSV(len(students), conflictsDir='./output/raw/json/conflicts.json', outputDir='./output/raw/csv/error_tracker.csv')
|
||||||
|
|||||||
Reference in new issue
Block a user