allow more variable changes
This commit is contained in:
3 files changed
+110
-71
No files matched your search
@@ -3,6 +3,7 @@ import json
|
|||||||
import random
|
import random
|
||||||
from inspect import currentframe
|
from inspect import currentframe
|
||||||
from string import hexdigits
|
from string import hexdigits
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
# Import from custom utilities
|
# Import from custom utilities
|
||||||
from util.mockStudents import getSampleStudents
|
from util.mockStudents import getSampleStudents
|
||||||
@@ -44,6 +45,15 @@ from util.debug import debug
|
|||||||
exists = lambda n : True if n not in ('', None) else False
|
exists = lambda n : True if n not in ('', None) else False
|
||||||
getLineNumber = lambda : currentframe().f_back.f_lineno
|
getLineNumber = lambda : currentframe().f_back.f_lineno
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Error:
|
||||||
|
Title: str
|
||||||
|
Description: str
|
||||||
|
|
||||||
|
def __init__(self, title: str, description: str):
|
||||||
|
self.Title = title
|
||||||
|
self.Description = description
|
||||||
|
|
||||||
# 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
|
||||||
def newConflict(pupilNum: str, email: str, conflictType: str, code: str, description: str, logs: dict) -> bool:
|
def newConflict(pupilNum: str, email: str, conflictType: str, code: str, description: str, logs: dict) -> bool:
|
||||||
@@ -66,20 +76,6 @@ def insertConflictSolutions(pupilNum: str, logs: dict, data: dict) -> None:
|
|||||||
conflict["Missing"] = data
|
conflict["Missing"] = data
|
||||||
break
|
break
|
||||||
|
|
||||||
minReq, median, classCap = 18, 24, 30
|
|
||||||
running = {
|
|
||||||
"block1": {},
|
|
||||||
"block2": {},
|
|
||||||
"block3": {},
|
|
||||||
"block4": {},
|
|
||||||
"block5": {},
|
|
||||||
"block6": {},
|
|
||||||
"block7": {},
|
|
||||||
"block8": {},
|
|
||||||
"block9": {},
|
|
||||||
"block10": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# These are the codes for Flex (spare) blocks
|
# These are the codes for Flex (spare) blocks
|
||||||
# Semester 1 and 2
|
# Semester 1 and 2
|
||||||
flex = ("XAT--12A-S", "XAT--12B-S")
|
flex = ("XAT--12A-S", "XAT--12B-S")
|
||||||
@@ -90,13 +86,28 @@ flex = ("XAT--12A-S", "XAT--12B-S")
|
|||||||
# 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/mockStudents.py to see the students list structure
|
students: list, # Refer to /util/mockStudents.py to see the students list structure
|
||||||
courses: dict, # Reger to ../util/generateCourses.py to see the courses dictionary structure
|
courses: dict, # Reger to /util/generateCourses.py to see the courses dictionary structure
|
||||||
|
minReq: int=18, # minimum requests for a class to run
|
||||||
|
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
|
||||||
studentsDir: str="../output/students.json",
|
studentsDir: str="../output/students.json",
|
||||||
conflictsDir: str="../output/conflicts.json"
|
conflictsDir: str="../output/conflicts.json"
|
||||||
) -> dict[str, dict]: # Returns the completed 'running' dictionary from above
|
) -> tuple[dict, Error]: # Returns the completed 'running' dictionary from above
|
||||||
|
|
||||||
|
# Return error that totalBlocks is invalid
|
||||||
|
if totalBlocks not in (10, 8):
|
||||||
|
totalBlockError = Error('Invalid totalBlocks', 'An invalid \'totalBlocks\' value was provided -> must be 10 or 8')
|
||||||
|
return (None, totalBlockError) # return none an signal failure
|
||||||
|
|
||||||
|
# First we need to setup some values
|
||||||
|
median = (minReq + classCap) // 2
|
||||||
|
blockPerSem = int(totalBlocks / 2)
|
||||||
|
running = {}
|
||||||
|
for i in range(1, totalBlocks + 1):
|
||||||
|
running[f'block{i}'] = {}
|
||||||
|
|
||||||
|
|
||||||
def equal(l: list) -> list: # Used to equalize list of numbers
|
def equal(l: list) -> list: # Used to equalize list of numbers
|
||||||
q,r = divmod(sum(l),len(l))
|
q,r = divmod(sum(l),len(l))
|
||||||
@@ -276,14 +287,19 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
# Step 4 - Attempt to fit classes into timetable
|
# Step 4 - Attempt to fit classes into timetable
|
||||||
def stepIndex(offset: int, stepType: int) -> int:
|
def stepIndex(offset: int, stepType: int) -> int:
|
||||||
|
# ALl this logic handles taking the # of blocks per semester, 4 or 5 and calculating
|
||||||
|
# the number used to step between the index of the first or second semester
|
||||||
|
|
||||||
# stepType 0 is for stepping between first and second semester
|
# stepType 0 is for stepping between first and second semester
|
||||||
if stepType == 0: return 5 if offset in (0, -4) else -4
|
if stepType == 0: return blockPerSem if offset in (0, (-1 * (blockPerSem - 1))) else (-1 * (blockPerSem - 1))
|
||||||
|
|
||||||
# stepType 1 is for stepping between second and first semester
|
# stepType 1 is for stepping between second and first semester
|
||||||
elif stepType == 1: return -5 if offset in (0, 6) else 6
|
elif stepType == 1: return (-1 * blockPerSem) if offset in (0, (blockPerSem + 1)) else (blockPerSem + 1)
|
||||||
|
|
||||||
# Return Error if code is altered to cause error
|
# Return Error if code is altered to cause error
|
||||||
else: raise SystemExit(f"Invalid 'stepType' in func 'stepIndex' line {getLineNumber()}")
|
else:
|
||||||
|
invalidStepTypeError = Error('Invalid stepType', 'An invalid \'stepType\' was passed to func \'stepIndex\'')
|
||||||
|
return (None, invalidStepTypeError)
|
||||||
|
|
||||||
# Create copy for step 6
|
# Create copy for step 6
|
||||||
courseRunInfoCopy = dict(courseRunInfo)
|
courseRunInfoCopy = dict(courseRunInfo)
|
||||||
@@ -294,15 +310,12 @@ def generateScheduleV3(
|
|||||||
course = list(courseRunInfo)[index]
|
course = list(courseRunInfo)[index]
|
||||||
|
|
||||||
# Tally first and second semester
|
# Tally first and second semester
|
||||||
sem1 = sum(len(running[f'block{i}']) for i in range(1, 6))
|
allSemBlockLens = [len(running[f'block{i}']) for i in range(1, totalBlocks + 1)]
|
||||||
sem2 = sum(len(running[f'block{i}']) for i in range(6, 11))
|
|
||||||
allSemBlockLens = [len(running[f'block{i}']) for i in range(1, 11)]
|
|
||||||
|
|
||||||
# If there is more than one class Running
|
# If there is more than one class Running
|
||||||
if allClassRunCounts[index] > 1:
|
if allClassRunCounts[index] > 1:
|
||||||
blockIndex = allSemBlockLens.index(min(allSemBlockLens))
|
blockIndex = allSemBlockLens.index(min(allSemBlockLens))
|
||||||
startSem = 1 if blockIndex < 5 else 2
|
stepType = 0 if blockIndex < blockPerSem else 1
|
||||||
stepType = 0 if blockIndex < 5 else 1
|
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
# Spread classes throughout both semesters
|
# Spread classes throughout both semesters
|
||||||
@@ -310,7 +323,6 @@ def generateScheduleV3(
|
|||||||
cname = f"{course}-{hexdigits[i]}"
|
cname = f"{course}-{hexdigits[i]}"
|
||||||
classInserted = False
|
classInserted = False
|
||||||
while not classInserted:
|
while not classInserted:
|
||||||
|
|
||||||
blockIndex += offset
|
blockIndex += offset
|
||||||
if len(running[f'block{blockIndex+1}']) < blockClassLimit:
|
if len(running[f'block{blockIndex+1}']) < blockClassLimit:
|
||||||
running[list(running)[blockIndex]][cname] = {
|
running[list(running)[blockIndex]][cname] = {
|
||||||
@@ -323,8 +335,8 @@ def generateScheduleV3(
|
|||||||
|
|
||||||
offset = stepIndex(offset, stepType)
|
offset = stepIndex(offset, stepType)
|
||||||
|
|
||||||
if blockIndex >= 9:
|
if blockIndex >= (totalBlocks - 1):
|
||||||
blockIndex = 0 if stepType == 0 else 5
|
blockIndex = 0 if stepType == 0 else blockPerSem
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
# If the class only runs once, place in semester with least classes
|
# If the class only runs once, place in semester with least classes
|
||||||
@@ -348,10 +360,6 @@ def generateScheduleV3(
|
|||||||
allClassRunCounts.remove(allClassRunCounts[index])
|
allClassRunCounts.remove(allClassRunCounts[index])
|
||||||
courseRunInfo.pop(list(courseRunInfo)[index])
|
courseRunInfo.pop(list(courseRunInfo)[index])
|
||||||
|
|
||||||
# # Debug step 4 output
|
|
||||||
# for i in range(1, 6):
|
|
||||||
# debug(f'block{i} - {len(running[f"block{i}"])} | block{i+5} - {len(running[f"block{i+5}"])}')
|
|
||||||
|
|
||||||
# Step 5 - Fill student schedule
|
# Step 5 - Fill student schedule
|
||||||
for block in running:
|
for block in running:
|
||||||
for cname in running[block]:
|
for cname in running[block]:
|
||||||
@@ -367,12 +375,9 @@ def generateScheduleV3(
|
|||||||
studentsCritical, studentsAcceptable = 0, 0
|
studentsCritical, studentsAcceptable = 0, 0
|
||||||
|
|
||||||
for student in students:
|
for student in students:
|
||||||
# if student["Pupil #"] == "772554": debug("772554")
|
|
||||||
blocks = [student["schedule"][block] for block in student["schedule"]]
|
blocks = [student["schedule"][block] for block in student["schedule"]]
|
||||||
# if student["Pupil #"] == "772554": debug(blocks)
|
|
||||||
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 student["Pupil #"] == "772554": debug(f'Has conflict? {hasConflicts}')
|
|
||||||
|
|
||||||
# If there is no conflicts
|
# If there is no conflicts
|
||||||
# and classes inserted to is equal to expectedClasses
|
# and classes inserted to is equal to expectedClasses
|
||||||
# or classes the student is inserted to is missing
|
# or classes the student is inserted to is missing
|
||||||
@@ -407,7 +412,7 @@ def generateScheduleV3(
|
|||||||
runCounts.append(courseRunInfoCopy[cname[:-2]]["Total"])
|
runCounts.append(courseRunInfoCopy[cname[:-2]]["Total"])
|
||||||
|
|
||||||
# Rebuild student schedule
|
# Rebuild student schedule
|
||||||
availableBlocks = [f'block{i}' for i in range(1, 11)]
|
availableBlocks = [f'block{i}' for i in range(1, totalBlocks + 1)]
|
||||||
while len(classes) > 0:
|
while len(classes) > 0:
|
||||||
index = runCounts.index(min(runCounts)) # Get class least run
|
index = runCounts.index(min(runCounts)) # Get class least run
|
||||||
found = False
|
found = False
|
||||||
@@ -583,11 +588,12 @@ def generateScheduleV3(
|
|||||||
if len(student["schedule"][block]) == 0:
|
if len(student["schedule"][block]) == 0:
|
||||||
student["schedule"][block].append(flex[0]) if int(block[5:]) <= 5 else student["schedule"][block].append(flex[1])
|
student["schedule"][block].append(flex[0]) if int(block[5:]) <= 5 else student["schedule"][block].append(flex[1])
|
||||||
|
|
||||||
# Update Student records
|
# Update/log Student records
|
||||||
with open(studentsDir, "w") as outfile:
|
with open(studentsDir, "w") as outfile:
|
||||||
json.dump(students, outfile, indent=2)
|
json.dump(students, outfile, indent=2)
|
||||||
|
|
||||||
return running
|
return (running, None)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("Processing...")
|
print("Processing...")
|
||||||
@@ -596,7 +602,9 @@ def main():
|
|||||||
samplemockCourses = getSampleCourses(True)
|
samplemockCourses = getSampleCourses(True)
|
||||||
timetable = {}
|
timetable = {}
|
||||||
timetable["Version"] = 3
|
timetable["Version"] = 3
|
||||||
timetable["timetable"] = generateScheduleV3(sampleStudents, samplemockCourses)
|
timetable["timetable"], err = generateScheduleV3(sampleStudents, samplemockCourses)
|
||||||
|
|
||||||
|
if err is not None: raise SystemExit(f'{err.Title} : {err.Description}')
|
||||||
|
|
||||||
with open("../output/timetable.json", "w") as outfile:
|
with open("../output/timetable.json", "w") as outfile:
|
||||||
json.dump(timetable, outfile, indent=2)
|
json.dump(timetable, outfile, indent=2)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3.113.11
|
#!/usr/bin/env python3.11
|
||||||
from os.path import exists as file_exists
|
from os.path import exists as file_exists
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
@@ -20,12 +20,12 @@ from scheduleGenerator.generator import generateScheduleV3
|
|||||||
|
|
||||||
done = False
|
done = False
|
||||||
|
|
||||||
# consts
|
# constants
|
||||||
blockClassLimit = 40
|
TOTAL_BLOCKS = 10 # this can also be 8 for 4 blocks per semester
|
||||||
|
BLOCK_CLASS_LIMIT = 40 # this is number of classrooms available per block
|
||||||
|
MIN_REQ = 18 # this is minumum number of requests to allow a course to run
|
||||||
|
CLASS_CAP = 30 # this is the max number of students per class
|
||||||
|
|
||||||
# print()
|
|
||||||
# debug(f'Current blockClassLimit = {blockClassLimit}')
|
|
||||||
|
|
||||||
def processing(msg: str):
|
def processing(msg: str):
|
||||||
for c in itertools.cycle(['|', '/', '-', '\\']):
|
for c in itertools.cycle(['|', '/', '-', '\\']):
|
||||||
if done: break
|
if done: break
|
||||||
@@ -66,15 +66,26 @@ def main():
|
|||||||
st = time() # Start time
|
st = time() # Start time
|
||||||
|
|
||||||
if noAnim:
|
if noAnim:
|
||||||
sampleStudents = getSampleStudents("./sample_data/course_selection_data.csv", True)
|
sampleStudents = getSampleStudents("./sample_data/course_selection_data.csv", log=True, totalBlocks=TOTAL_BLOCKS)
|
||||||
sampleCourses = getSampleCourses("./sample_data/course_selection_data.csv", True)
|
sampleCourses = getSampleCourses("./sample_data/course_selection_data.csv", log=True)
|
||||||
timetable = {}
|
timetable = {}
|
||||||
timetable["Version"] = 3
|
timetable["Version"] = 3
|
||||||
timetable["timetable"] = generateScheduleV3(sampleStudents, sampleCourses, blockClassLimit, "./output/students.json", "./output/conflicts.json")
|
timetable["timetable"], err = generateScheduleV3(
|
||||||
|
sampleStudents,
|
||||||
|
sampleCourses,
|
||||||
|
minReq=MIN_REQ,
|
||||||
|
blockClassLimit=BLOCK_CLASS_LIMIT,
|
||||||
|
classCap=CLASS_CAP,
|
||||||
|
totalBlocks=TOTAL_BLOCKS,
|
||||||
|
studentsDir="./output/students.json",
|
||||||
|
conflictsDir="./output/conflicts.json")
|
||||||
|
|
||||||
|
if err is not None: raise SystemExit(f'{err.Title} : {err.Description}')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
t = threading.Thread(target=processing, args=('Collection student requests',))
|
t = threading.Thread(target=processing, args=('Collection student requests',))
|
||||||
t.start() # Start animation
|
t.start() # Start animation
|
||||||
sampleStudents = getSampleStudents("./sample_data/course_selection_data.csv", True)
|
sampleStudents = getSampleStudents("./sample_data/course_selection_data.csv", log=True, totalBlocks=TOTAL_BLOCKS)
|
||||||
done = True # End Animation
|
done = True # End Animation
|
||||||
print('\nStudent list generated.\n')
|
print('\nStudent list generated.\n')
|
||||||
|
|
||||||
@@ -82,7 +93,7 @@ def main():
|
|||||||
t = threading.Thread(target=processing, args=('Collection Course Information',))
|
t = threading.Thread(target=processing, args=('Collection Course Information',))
|
||||||
done = False # reset animation
|
done = False # reset animation
|
||||||
t.start() # Start animation
|
t.start() # Start animation
|
||||||
sampleCourses = getSampleCourses("./sample_data/course_selection_data.csv", True)
|
sampleCourses = getSampleCourses("./sample_data/course_selection_data.csv", log=True)
|
||||||
done = True # End Animation
|
done = True # End Animation
|
||||||
print('\nCourse Information Collected.\n')
|
print('\nCourse Information Collected.\n')
|
||||||
|
|
||||||
@@ -92,9 +103,18 @@ def main():
|
|||||||
t.start() # Start animation
|
t.start() # Start animation
|
||||||
timetable = {}
|
timetable = {}
|
||||||
timetable["Version"] = 3
|
timetable["Version"] = 3
|
||||||
timetable["timetable"] = generateScheduleV3(sampleStudents, sampleCourses, blockClassLimit, "./output/students.json", "./output/conflicts.json")
|
timetable["timetable"], err = generateScheduleV3(
|
||||||
|
sampleStudents,
|
||||||
|
sampleCourses,
|
||||||
|
minReq=MIN_REQ,
|
||||||
|
blockClassLimit=BLOCK_CLASS_LIMIT,
|
||||||
|
classCap=CLASS_CAP,
|
||||||
|
totalBlocks=TOTAL_BLOCKS,
|
||||||
|
studentsDir="./output/students.json",
|
||||||
|
conflictsDir="./output/conflicts.json")
|
||||||
|
|
||||||
done = True # End Animation
|
done = True # End Animation
|
||||||
|
if err is not None: raise SystemExit(f'{err.Title} : {err.Description}')
|
||||||
|
|
||||||
et = time() # End time
|
et = time() # End time
|
||||||
elapsed_time = round((et - st), 3) # Execution time
|
elapsed_time = round((et - st), 3) # Execution time
|
||||||
@@ -127,15 +147,36 @@ def main():
|
|||||||
if noAnim:
|
if noAnim:
|
||||||
timetable = {}
|
timetable = {}
|
||||||
timetable["Version"] = 3
|
timetable["Version"] = 3
|
||||||
timetable["timetable"] = generateScheduleV3(sampleStudents, sampleCourses, blockClassLimit, "./output/students.json", "./output/conflicts.json")
|
timetable["timetable"], err = generateScheduleV3(
|
||||||
else :
|
sampleStudents,
|
||||||
|
sampleCourses,
|
||||||
|
minReq=MIN_REQ,
|
||||||
|
blockClassLimit=BLOCK_CLASS_LIMIT,
|
||||||
|
classCap=CLASS_CAP,
|
||||||
|
totalBlocks=TOTAL_BLOCKS,
|
||||||
|
studentsDir="./output/students.json",
|
||||||
|
conflictsDir="./output/conflicts.json")
|
||||||
|
|
||||||
|
if err is not None: raise SystemExit(f'{err.Title} : {err.Description}')
|
||||||
|
|
||||||
|
else:
|
||||||
t = threading.Thread(target=processing, args=('Processing',))
|
t = threading.Thread(target=processing, args=('Processing',))
|
||||||
t.start() # Start animation
|
t.start() # Start animation
|
||||||
timetable = {}
|
timetable = {}
|
||||||
timetable["Version"] = 3
|
timetable["Version"] = 3
|
||||||
timetable["timetable"] = generateScheduleV3(sampleStudents, sampleCourses, blockClassLimit, "./output/students.json", "./output/conflicts.json")
|
timetable["timetable"], err = generateScheduleV3(
|
||||||
done = True # End Animation
|
sampleStudents,
|
||||||
|
sampleCourses,
|
||||||
|
minReq=MIN_REQ,
|
||||||
|
blockClassLimit=BLOCK_CLASS_LIMIT,
|
||||||
|
classCap=CLASS_CAP,
|
||||||
|
totalBlocks=TOTAL_BLOCKS,
|
||||||
|
studentsDir="./output/students.json",
|
||||||
|
conflictsDir="./output/conflicts.json")
|
||||||
|
|
||||||
|
done = True # End Animation
|
||||||
|
if err is not None: raise SystemExit(f'{err.Title} : {err.Description}')
|
||||||
|
|
||||||
et = time() # End time
|
et = time() # End time
|
||||||
elapsed_time = round((et - st), 3) # Execution time
|
elapsed_time = round((et - st), 3) # Execution time
|
||||||
print(f'\n\nDone - Finished in {elapsed_time} seconds\n')
|
print(f'\n\nDone - Finished in {elapsed_time} seconds\n')
|
||||||
|
|||||||
+4
-14
@@ -8,7 +8,7 @@ flex= ("XAT--12A-S", "XAT--12B-S")
|
|||||||
mockStudents: list[dict] = []
|
mockStudents: list[dict] = []
|
||||||
|
|
||||||
# sort real sample data into usable dictionary
|
# sort real sample data into usable dictionary
|
||||||
def getSampleStudents(data_dir: str, log: bool = False) -> list[dict]:
|
def getSampleStudents(data_dir: str, log: bool = False, totalBlocks: int = 10) -> list[dict]:
|
||||||
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:
|
||||||
@@ -18,7 +18,7 @@ def getSampleStudents(data_dir: str, log: bool = False) -> list[dict]:
|
|||||||
if exists: break
|
if exists: break
|
||||||
alternate = True if row["Alternate?"] == 'TRUE' else False
|
alternate = True if row["Alternate?"] == 'TRUE' else False
|
||||||
if exists:
|
if exists:
|
||||||
if len(mockStudents[student["studentIndex"]]["requests"]) >= 10 and not alternate and row["CrsNo"] not in flex: alternate = True
|
if len(mockStudents[student["studentIndex"]]["requests"]) >= totalBlocks and not alternate and row["CrsNo"] not in flex: alternate = True
|
||||||
mockStudents[student["studentIndex"]]["requests"].append({
|
mockStudents[student["studentIndex"]]["requests"].append({
|
||||||
"CrsNo": row["CrsNo"],
|
"CrsNo": row["CrsNo"],
|
||||||
"Description": row["Description"],
|
"Description": row["Description"],
|
||||||
@@ -34,23 +34,13 @@ def getSampleStudents(data_dir: str, log: bool = False) -> list[dict]:
|
|||||||
"Description": row["Description"],
|
"Description": row["Description"],
|
||||||
"alt": alternate
|
"alt": alternate
|
||||||
}],
|
}],
|
||||||
"schedule": {
|
"schedule": {},
|
||||||
"block1": [],
|
|
||||||
"block2": [],
|
|
||||||
"block3": [],
|
|
||||||
"block4": [],
|
|
||||||
"block5": [],
|
|
||||||
"block6": [],
|
|
||||||
"block7": [],
|
|
||||||
"block8": [],
|
|
||||||
"block9": [],
|
|
||||||
"block10": []
|
|
||||||
},
|
|
||||||
"expectedClasses": 1,
|
"expectedClasses": 1,
|
||||||
"classes": 0,
|
"classes": 0,
|
||||||
"remainingAlts": [],
|
"remainingAlts": [],
|
||||||
"studentIndex": len(mockStudents)
|
"studentIndex": len(mockStudents)
|
||||||
}
|
}
|
||||||
|
for i in range(1, totalBlocks+1): newStudent["schedule"][f'block{i}'] = []
|
||||||
mockStudents.append(newStudent)
|
mockStudents.append(newStudent)
|
||||||
|
|
||||||
# Estimate student grades
|
# Estimate student grades
|
||||||
|
|||||||
Reference in new issue
Block a user