#!/usr/bin/python3 # COMP3311 21T3 Ass2 ... progression check for a given student import sys import psycopg2 import re from helpers import * # define any local helper functions here ### set up some globals def getMostRecentProgStrm(zid, db): cur = db.cursor(); cur.execute(""" SELECT program, ST.code FROM Students AS S, Program_enrolments AS PE, Stream_Enrolments AS SE, Programs AS P, Streams AS ST WHERE S.id = %s AND PE.student = S.id AND SE.partof = PE.id AND P.id = PE.program AND ST.id = SE.stream ORDER BY term DESC """, [zid]); info = cur.fetchone(); cur.close(); if not info: return None else: return info def getRequirements(prog_code, strm_code, db): cur = db.cursor(); cur.execute(""" SELECT R.name, R.min_req, R.max_req, R.type, AOG.definition, AOG.defby, 1 AS IS_PROG, R.id FROM Streams AS S, Stream_Rules AS SR, Rules AS R, Academic_Object_groups AS AOG WHERE S.code = %s AND SR.stream = S.id AND SR.rule = R.id AND AOG.id = R.ao_group UNION SELECT R.name, R.min_req, R.max_req, R.type, AOG.definition, AOG.defby, 0 AS IS_PROG, R.id FROM Program_Rules as PR, Rules as R, Academic_Object_Groups AS AOG WHERE PR.program = %s AND R.id = PR.rule AND AOG.id = R.ao_group AND AOG.type != 'stream' ORDER BY IS_PROG DESC, id ASC """, [strm_code, prog_code]); info = cur.fetchall(); cur.close(); if not info: return None else: return list(info) def maybeRemovePatternRequirement(requirement, course_code, UOC): (name, min_req, max_req, type, definition, defby, stream, aogtype) = requirement if type == "FE": if requirement[1] != None: requirement[1] = requirement[1] - UOC if requirement[2] != None: requirement[2] = requirement[2] - UOC return True for subject in definition.split(","): match = True if len(subject) < 2: continue #print(f"testing {subject} against {course_code}") for i in range(0, len(subject)): if subject[i] != '#' and course_code[i] != subject[i]: match = False break if match == True: if requirement[1] != None: requirement[1] = requirement[1] - UOC if requirement[2] != None: requirement[2] = requirement[2] - UOC if not '#' in subject: requirement[4] = requirement[4].replace(subject, "") return True; return False def maybeRemoveEnumRequirement(requirement, course_code): removeStr = None; (name, min_req, max_req, type, definition, defby, stream, aogtype) = requirement for subject in definition.split(","): subject = subject.replace("{", "") subject = subject.replace("}", "") for code in subject.split(";"): if code == course_code: removeStr = subject break if removeStr != None: break if removeStr == None: return False requirement[4] = requirement[4].replace(removeStr, "") return True def maybeRemoveInnerRequirement(requirement, course_code, UOC): (name, min_req, max_req, type, definition, defby, stream, aogtype) = requirement if defby == "pattern": return maybeRemovePatternRequirement(requirement, course_code, UOC) elif defby == "enumerated": return maybeRemoveEnumRequirement(requirement, course_code) def shouldPopPattern(requirement): (name, min_req, max_req, type, definition, defby, stream, aogtype) = requirement if min_req != None and min_req <= 0: return True if max_req != None and max_req <= 0: return True return any(l.isalnum() for l in definition) == False def maybeRemoveRequirement(requirements, course_code, UOC): removeIndex = None if removeIndex == None: for count, requirement in enumerate(requirements): (name, min_req, max_req, type, definition, defby, stream, _) = requirement if stream == 1: continue if type == "FE": continue # handled in next loop #print(f"checking against {name} : { maybeRemoveInnerRequirement(requirement, course_code, UOC)}") if maybeRemoveInnerRequirement(requirement, course_code, UOC): removeIndex = count break if removeIndex == None: for count, requirement in enumerate(requirements): (name, min_req, max_req, type, definition, defby, stream, _) = requirement if stream == 0: continue if type == "FE": continue # handled in next loop if maybeRemoveInnerRequirement(requirement, course_code, UOC) == True: removeIndex = count break # Special FE case, they should be prioritised last. isFreeElective = False # For some reason we only return Free Electives here if removeIndex == None: for count, requirement in enumerate(requirements): (name, min_req, max_req, type, definition, defby, stream, _) = requirement if type != "FE": continue # handled in next loop if maybeRemoveInnerRequirement(requirement, course_code, UOC) == True: removeIndex = count isFreeElective = True break if removeIndex == None: return None # Remove if our min_req is zero for pattern, or if we have nothing else to # enumerate in requirements. (name, min_req, max_req, type, definition, defby, stream, _) = requirements[removeIndex]; if defby == "pattern": if shouldPopPattern(requirement): requirements.pop(removeIndex) elif defby == "enumerated": if any(l.isalnum() for l in definition) == False: requirements.pop(removeIndex) return name if isFreeElective == False else "Free Electives" def printAcademicRequirementsType(requirements, expected_type, db): for requirement in requirements: (name, min_req, max_req, type, definition, defby, stream, aogtype) = requirement if type != expected_type: continue def_count = str.count(definition, ",") if type == "DS": print(f"{min_req} stream(s) from {name}") printStreams(definition, db) elif type == "CC" or type == "PE" and min_req: if defby == "pattern": print(getCountString(def_count, min_req, max_req, name)) continue printSubjects(definition, defby, db) elif type == "GE" and min_req: print(f"{min_req} UOC of {name}") elif type == "FE" and min_req: print(f"at least {min_req} UOC of Free Electives") #else: # print(f"UNEXPECTED {name}, {min_req}, {max_req}, {type}, {defby}") def printAcademicRequirements(requirements, db): printAcademicRequirementsType(requirements, "CC", db) printAcademicRequirementsType(requirements, "PE", db) printAcademicRequirementsType(requirements, "GE", db) printAcademicRequirementsType(requirements, "FE", db) usage = f"Usage: {sys.argv[0]} zID [Program Stream]" db = None ### process command-line args argc = len(sys.argv) if argc < 2: print(usage) exit(1) zid = sys.argv[1] if zid[0] == 'z': zid = zid[1:8] digits = re.compile("^\d{7}$") if not digits.match(zid): print("Invalid student ID") exit(1) progCode = None strmCode = None if argc == 4: progCode = sys.argv[2] strmCode = sys.argv[3] # manipulate database try: db = psycopg2.connect("dbname=mymyunsw") stuInfo = getStudent(db,zid) if not stuInfo: print(f"Invalid student id {zid}") exit() if progCode == None or strmCode == None: (pc, sc) = getMostRecentProgStrm(zid, db) progCode = str(pc) strmCode = str(sc) progInfo = getProgram(db,progCode) if progCode: if not progInfo: print(f"Invalid program code {progCode}") exit() strmInfo = getStream(db,strmCode) if strmCode: if not strmInfo: print(f"Invalid program code {strmCode}") exit() print(f"{stuInfo[0]} {stuInfo[1]}, {stuInfo[2]}") print(f" {progInfo[0]} {progInfo[1]}") print(f" {strmCode} {strmInfo[0]}"); # Our general strategy is threefold: # 1. Generate a data structure containing all the requirements of the degree # 2. Iterate and print all completed subjects, while removing requirements # from (1) if fulfilled. # 3. Print the remainder of (1), or ready to graduate if empty. requirements = list(getRequirements(progCode, strmCode, db)) requirements = [list(a) for a in requirements] print("\nCompleted:") total_uoc = 0 for (CourseCode, Term, SubjectTitle, Mark, Grade, UOC) in getStudentCourses(db, zid): remove_requirement_str = maybeRemoveRequirement(requirements, CourseCode, UOC) if doesGradeAddUOC(Grade) else None mark_str = Mark if Mark != None else "-" total_uoc += UOC if doesGradeAddUOC(Grade) and remove_requirement_str != None else 0 uoc_str = " " + getGradeString(Grade) if getGradeString(Grade) != "Xuoc" else f"{UOC:2d}uoc" if remove_requirement_str != None: uoc_str = uoc_str + f" towards {remove_requirement_str}" elif doesGradeAddUOC(Grade) == False: uoc_str = uoc_str + " does not count" else: uoc_str = " 0uoc does not satisfy any rule" print(f"{CourseCode} {Term} {SubjectTitle :<32s}{mark_str :>3} {Grade :>2s} {uoc_str}") print(f"UOC = {total_uoc} so far") if len(requirements) > 0: print("\nRemaining to complete degree:") printAcademicRequirements(requirements, db) else: print("Eligible to graduate") except Exception as err: print("DB error: ", err) finally: if db: db.close()