aboutsummaryrefslogtreecommitdiff
path: root/comp3311/2
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
commit98cef5e9a772602d42acfcf233838c760424db9a (patch)
tree5277fa1d7cc0a69a0f166fcbf10fd320f345f049 /comp3311/2
initial commit
Diffstat (limited to 'comp3311/2')
-rwxr-xr-xcomp3311/2/prog328
-rwxr-xr-xcomp3311/2/rules107
-rwxr-xr-xcomp3311/2/trans63
3 files changed, 498 insertions, 0 deletions
diff --git a/comp3311/2/prog b/comp3311/2/prog
new file mode 100755
index 0000000..130b82b
--- /dev/null
+++ b/comp3311/2/prog
@@ -0,0 +1,328 @@
+#!/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()
diff --git a/comp3311/2/rules b/comp3311/2/rules
new file mode 100755
index 0000000..7f32a99
--- /dev/null
+++ b/comp3311/2/rules
@@ -0,0 +1,107 @@
+#!/usr/bin/python3
+# COMP3311 21T3 Ass2 ... print list of rules for a program or stream
+
+import sys
+import psycopg2
+import re
+from helpers import *
+
+# define any local helper functions here
+
+# Requires an array with with: name, min_req, max_req, type, definition, defby
+def printAcademicRequirements(rules, db):
+ print("Academic Requirements:")
+ for (name, min_req, max_req, type, definition, defby) in rules:
+ def_count = str.count(definition, ",")
+
+ # There is some weirdly specific behaviour to emulate when printing. While
+ # this might pass the tests, it is unlikely to continue doing so.
+ # According to our lecturer we won't be tested on edge cases, so I can only
+ # hope this is enough.
+ if type == "DS":
+ print(f"{min_req} stream(s) from {name}")
+ printStreams(definition, db)
+
+ elif type == "CC" or type == "PE":
+ print(getCountString(def_count, min_req, max_req, name))
+ printSubjects(definition, defby, db)
+
+ elif type == "GE":
+ print(f"{min_req} UOC of {name}")
+
+ elif type == "FE":
+ print(f"at least {min_req} UOC of Free Electives")
+
+ else:
+ print(f"UNEXPECTED {name}, {min_req}, {max_req}, {type}, {defby}")
+
+
+### set up some globals
+
+usage = f"Usage: {sys.argv[0]} (ProgramCode|StreamCode)"
+db = None
+
+### process command-line args
+
+argc = len(sys.argv)
+if argc < 2:
+ print(usage)
+ exit(1)
+code = sys.argv[1]
+if len(code) == 4:
+ codeOf = "program"
+elif len(code) == 6:
+ codeOf = "stream"
+
+try:
+ db = psycopg2.connect("dbname=mymyunsw")
+ if codeOf == "program":
+ progInfo = getProgram(db,code)
+ if not progInfo:
+ print(f"Invalid program code {code}")
+ exit()
+
+ print(f"{progInfo[0]} {progInfo[1]}, {progInfo[2]} UOC, {progInfo[3] / 12.0} years")
+ print(f"- offered by {progInfo[4]}");
+
+ cur = db.cursor()
+ query = """
+ SELECT R.name, R.min_req, R.max_req, R.type, AOG.definition, AOG.defby
+ 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
+ """
+ cur.execute(query, [code])
+ printAcademicRequirements(cur.fetchall(), db)
+ cur.close()
+
+
+ elif codeOf == "stream":
+ strmInfo = getStream(db,code)
+ if not strmInfo:
+ print(f"Invalid stream code {code}")
+ exit()
+
+ print(f"{code} {strmInfo[0]}");
+ print(f"- offered by {strmInfo[1]}")
+
+ cur = db.cursor()
+ query = """
+ SELECT R.name, R.min_req, R.max_req, R.type, AOG.definition, AOG.defby
+ 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
+ """
+
+ cur.execute(query, [code])
+ printAcademicRequirements(cur.fetchall(), db)
+ cur.close();
+
+except Exception as err:
+ print(err)
+finally:
+ if db:
+ db.close()
diff --git a/comp3311/2/trans b/comp3311/2/trans
new file mode 100755
index 0000000..f530676
--- /dev/null
+++ b/comp3311/2/trans
@@ -0,0 +1,63 @@
+#!/usr/bin/python3
+# COMP3311 21T3 Ass2 ... print a transcript for a given student
+
+import sys
+import psycopg2
+import re
+from helpers import getStudent, getStudentCourses, doesGradeAddRule
+from helpers import doesGradeAddUOC, doesGradeAddWAM, getGradeString
+
+# define any local helper functions here
+
+### set up some globals
+
+usage = f"Usage: {sys.argv[0]} zID"
+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(f"Invalid student ID {zid}")
+ exit(1)
+
+# manipulate database
+
+try:
+ db = psycopg2.connect("dbname=mymyunsw")
+ stuInfo = getStudent(db,zid)
+ if not stuInfo:
+ print(f"Invalid student ID {zid}")
+ exit()
+
+ student = getStudent(db, zid)
+ print(f"{zid} {student[1]}, {student[2]}")
+
+ total_uoc = 0;
+ total_wam_num = 0;
+ total_wam_den = 0;
+ for (CourseCode, Term, SubjectTitle, Mark, Grade, UOC) in getStudentCourses(db, zid):
+ total_uoc += UOC if doesGradeAddUOC(Grade) else 0
+ total_wam_num += UOC * (Mark if Mark != None else 0)
+ total_wam_den += UOC if doesGradeAddWAM(Grade) else 0
+
+ mark_str = Mark if Mark != None else "-"
+ uoc_str = " " + getGradeString(Grade) if getGradeString(Grade) != "Xuoc" else f"{UOC:2d}uoc"
+ print(f"{CourseCode} {Term} {SubjectTitle :<32s}{mark_str :>3} {Grade :>2s} {uoc_str}")
+
+ # Avoid division by zero.
+ print(f"UOC = {total_uoc}, WAM = {total_wam_num / total_wam_den if total_wam_den != 0 else 0 :.1f}");
+
+except Exception as err:
+ print("DB error: ", err)
+finally:
+ if db:
+ db.close()
+