progress on degree checking
This commit is contained in:
parent
055a561e18
commit
2a15748d5a
|
|
@ -850,8 +850,8 @@ def enroll_id_list_to_shell(id_list, shell_id, v=0):
|
||||||
|
|
||||||
|
|
||||||
def enroll_stem_students_live():
|
def enroll_stem_students_live():
|
||||||
the_term = '178'
|
the_term = '179' # su23 fa23 = 180
|
||||||
do_removes = 0
|
do_removes = 1
|
||||||
depts = "MATH BIO CHEM CSIS PHYS PSCI GEOG ASTR ECOL ENVS ENGR".split(" ")
|
depts = "MATH BIO CHEM CSIS PHYS PSCI GEOG ASTR ECOL ENVS ENGR".split(" ")
|
||||||
users_to_enroll = users_in_depts_live(depts, the_term) # term id
|
users_to_enroll = users_in_depts_live(depts, the_term) # term id
|
||||||
|
|
||||||
|
|
@ -863,7 +863,6 @@ def enroll_stem_students_live():
|
||||||
print("\n\nALREADY IN STEM SHELL %s" % str(users_in_stem_shell))
|
print("\n\nALREADY IN STEM SHELL %s" % str(users_in_stem_shell))
|
||||||
|
|
||||||
enroll_us = users_to_enroll.difference(users_in_stem_shell)
|
enroll_us = users_to_enroll.difference(users_in_stem_shell)
|
||||||
#enroll_us = users_to_enroll
|
|
||||||
remove_us = users_in_stem_shell.difference(users_to_enroll)
|
remove_us = users_in_stem_shell.difference(users_to_enroll)
|
||||||
|
|
||||||
print("\n\nTO ENROLL %s" % str(enroll_us))
|
print("\n\nTO ENROLL %s" % str(enroll_us))
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
402
degrees.py
402
degrees.py
|
|
@ -1,5 +1,10 @@
|
||||||
from lark import Lark, Transformer, v_args
|
from lark import Lark, Transformer, v_args
|
||||||
import json, sys, re, codecs
|
import json, sys, re, codecs, os
|
||||||
|
from stats import grades_to_vectors, stu_record_to_vector, courses_to_vector, course_main_record, num
|
||||||
|
from minizinc import Instance, Model, Solver
|
||||||
|
from datetime import datetime
|
||||||
|
from collections import defaultdict
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
debug_out = codecs.open("cache/degrees_debug.txt", "w", "utf-8")
|
debug_out = codecs.open("cache/degrees_debug.txt", "w", "utf-8")
|
||||||
|
|
@ -314,6 +319,8 @@ def is_noncourse_new_section(noncourse_line):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# take at least additional units from CSIS28
|
||||||
|
|
||||||
rule_lookup = {
|
rule_lookup = {
|
||||||
'take_all_prereq': ['RN PROGRAM PREREQUISITES', 'PREREQUISITES', ],
|
'take_all_prereq': ['RN PROGRAM PREREQUISITES', 'PREREQUISITES', ],
|
||||||
'take at least n courses': ['(\d+) courses total', 'SELECT (ONE|TWO) OF THE FOLLOWING', 'Select (one|two|three)', 'Select (\d+) courses',
|
'take at least n courses': ['(\d+) courses total', 'SELECT (ONE|TWO) OF THE FOLLOWING', 'Select (one|two|three)', 'Select (\d+) courses',
|
||||||
|
|
@ -321,9 +328,10 @@ rule_lookup = {
|
||||||
'Choose (\w+) of the following','Choose (one|two|three)', 'Choose ([\d\w]+) courses from',
|
'Choose (\w+) of the following','Choose (one|two|three)', 'Choose ([\d\w]+) courses from',
|
||||||
'ANY (COURSE) NOT USED IN', 'Select (1)', 'Select (one) of the following REQUIRED CORE',
|
'ANY (COURSE) NOT USED IN', 'Select (1)', 'Select (one) of the following REQUIRED CORE',
|
||||||
'(One) of the following:', 'LIST [AB]: Select (\d)', 'Choose (One) Course:', ],
|
'(One) of the following:', 'LIST [AB]: Select (\d)', 'Choose (One) Course:', ],
|
||||||
'take at least n units': ['LIST A \((\d+) units\)', 'LIST B \((\d+) units\)', 'LIST C \- Any course .*\((\d+) units\)', '(\d+) units total', 'Select (\d+) units', 'Any combination totaling (\d+) units',
|
'take at least n units': ['LIST A \((\d+) units\)', 'LIST B \((\d+) units\)', 'LIST C \- Any course .*\((\d+) units\)', '(\d+) units total',
|
||||||
|
'Select (\d+) units', 'Any combination totaling (\d+) units',
|
||||||
'Choose (\w+) units from classes listed', 'Choose a minimum of ([\w\d]+) units from',
|
'Choose (\w+) units from classes listed', 'Choose a minimum of ([\w\d]+) units from',
|
||||||
'Choose any combination of courses for a minimum of ([\w\d]+) units',
|
'Choose any combination of courses for a minimum of ([\w\d]+) units\:?',
|
||||||
'Choose ([\w\d]+) units', 'Choose courses for at least ([\w\d]+) units',
|
'Choose ([\w\d]+) units', 'Choose courses for at least ([\w\d]+) units',
|
||||||
'Choose a minimum of (\d+) units', 'Select any (\d+)\-\d+ units from the following'],
|
'Choose a minimum of (\d+) units', 'Select any (\d+)\-\d+ units from the following'],
|
||||||
'electives': ['Electives', 'Recommended electives?:', ],
|
'electives': ['Electives', 'Recommended electives?:', ],
|
||||||
|
|
@ -335,6 +343,8 @@ rule_lookup = {
|
||||||
|
|
||||||
def lookup_rule(line):
|
def lookup_rule(line):
|
||||||
verbose = 0
|
verbose = 0
|
||||||
|
if re.search(rule_lookup['take at least n units'][8], line):
|
||||||
|
print(f"line: {line} matched: {rule_lookup['take at least n units'][8]}")
|
||||||
for key in rule_lookup.keys():
|
for key in rule_lookup.keys():
|
||||||
for each in rule_lookup[key]:
|
for each in rule_lookup[key]:
|
||||||
m = re.search(each, line)
|
m = re.search(each, line)
|
||||||
|
|
@ -349,6 +359,25 @@ def lookup_rule(line):
|
||||||
return key,num
|
return key,num
|
||||||
return None,None
|
return None,None
|
||||||
|
|
||||||
|
|
||||||
|
def num_units(s):
|
||||||
|
if s == '': return 0
|
||||||
|
m = re.match(r'^(\d+)\.?(\d+)?\s?(units)?$',s)
|
||||||
|
if m:
|
||||||
|
if m.group(1) and m.group(2):
|
||||||
|
if m.group(2) == '0':
|
||||||
|
return int(m.group(1))
|
||||||
|
return float(f"{m.group(1)}.{m.group(2)}")
|
||||||
|
elif m.group(1):
|
||||||
|
return int(m.group(1))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return int(s)
|
||||||
|
except ValueError:
|
||||||
|
return float(s)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def examine(li,award, verbose=0):
|
def examine(li,award, verbose=0):
|
||||||
summary = [x[0] for x in li]
|
summary = [x[0] for x in li]
|
||||||
if summary[1] in ['and','or']:
|
if summary[1] in ['and','or']:
|
||||||
|
|
@ -368,7 +397,7 @@ def check_ands_ors_pbd(award, pbd, verbose=0):
|
||||||
if verbose: print()
|
if verbose: print()
|
||||||
|
|
||||||
|
|
||||||
def build_program_rules():
|
def build_program_rules(verbose=0):
|
||||||
cfile = "cache/courses/courses_active_built.json"
|
cfile = "cache/courses/courses_active_built.json"
|
||||||
pfile = "cache/programs/programs_built.json"
|
pfile = "cache/programs/programs_built.json"
|
||||||
|
|
||||||
|
|
@ -410,7 +439,7 @@ def build_program_rules():
|
||||||
# Each award (degree or certificate)
|
# Each award (degree or certificate)
|
||||||
award = p['award'] + " " + p['program_title']
|
award = p['award'] + " " + p['program_title']
|
||||||
d("\n" + p['award'] + " " + p['program_title'])
|
d("\n" + p['award'] + " " + p['program_title'])
|
||||||
print("\n" + award)
|
#print("\n" + award)
|
||||||
this_program = p['award'] + " " + p['program_title']
|
this_program = p['award'] + " " + p['program_title']
|
||||||
this_rule = ""
|
this_rule = ""
|
||||||
r = p['requirements']
|
r = p['requirements']
|
||||||
|
|
@ -455,7 +484,7 @@ def build_program_rules():
|
||||||
elif each_r[0] == 'course': # course in a rule
|
elif each_r[0] == 'course': # course in a rule
|
||||||
if not this_rule:
|
if not this_rule:
|
||||||
this_rule = "take_all from "
|
this_rule = "take_all from "
|
||||||
is_lab = '[L]' if is_lab_class(each_r[2]['code']) else ''
|
is_lab = '' # '[L]' if is_lab_class(each_r[2]['code']) else ''
|
||||||
#this_rule += f"{each_r[2]['code']}{is_lab}({each_r[1]}), "
|
#this_rule += f"{each_r[2]['code']}{is_lab}({each_r[1]}), "
|
||||||
this_rule += f"{each_r[2]['code']}{is_lab}, "
|
this_rule += f"{each_r[2]['code']}{is_lab}, "
|
||||||
if v2: print(f"\t\tthis rule is now: {this_rule}")
|
if v2: print(f"\t\tthis rule is now: {this_rule}")
|
||||||
|
|
@ -488,32 +517,367 @@ def build_program_rules():
|
||||||
if ok:
|
if ok:
|
||||||
okay.append(p)
|
okay.append(p)
|
||||||
|
|
||||||
print("\n\n\n\nThese programs are okay:")
|
if verbose:
|
||||||
for p in okay:
|
print("\n\n\n\nThese programs are okay:")
|
||||||
for l in p:
|
for p in okay:
|
||||||
print(l)
|
for l in p:
|
||||||
print()
|
print(l)
|
||||||
|
print()
|
||||||
|
|
||||||
print("\n\nThese programs are not okay:")
|
print("\n\nThese programs are not okay:")
|
||||||
for p in notokay:
|
for p in notokay:
|
||||||
for l in p:
|
for l in p:
|
||||||
print(l)
|
print(l)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
date_string = datetime.now().strftime("%Y%m%d")
|
||||||
|
codecs.open(f'cache/programs/ruleprogress_{date_string}.json','w','utf-8').write( json.dumps(
|
||||||
|
{'num_okay': len(okay), 'num_notokay': len(notokay),
|
||||||
|
'okay': okay, 'notokay': notokay}, indent=2)
|
||||||
|
)
|
||||||
print(f"okay: {len(okay)}")
|
print(f"okay: {len(okay)}")
|
||||||
print(f"not okay: {len(notokay)}")
|
print(f"not okay: {len(notokay)}")
|
||||||
return
|
return okay, notokay
|
||||||
do_parse('\n'.join(course_spec))
|
#do_parse('\n'.join(course_spec))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def deg_name_to_filename(deg_name):
|
||||||
|
deg_name = re.sub(r'[\-\s]+', '_', deg_name)
|
||||||
|
deg_name = re.sub(r'[\.\:,]','',deg_name)
|
||||||
|
return deg_name.lower()
|
||||||
|
|
||||||
|
def substitute_template_var(template, var, value):
|
||||||
|
out = []
|
||||||
|
for L in template:
|
||||||
|
j = re.sub(r'\[\[' + var + r'\]\]', str(value), L)
|
||||||
|
out.append(j)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
all_courses_dict = {}
|
||||||
|
|
||||||
|
def course_units(course):
|
||||||
|
#print(course)
|
||||||
|
global all_courses_dict
|
||||||
|
if not all_courses_dict:
|
||||||
|
ac = json.loads(codecs.open('cache/courses/courses_built.json','r','utf-8').read())
|
||||||
|
for c in ac.values():
|
||||||
|
if 'min_units' in c:
|
||||||
|
c['units'] = num(c['min_units'])
|
||||||
|
# ignoring max units for degree calculation for now...
|
||||||
|
#if 'max_units' in c:
|
||||||
|
# c['units'] += '-' + num(c['max_units'])
|
||||||
|
all_courses_dict[c['dept'] + c['number']] = c
|
||||||
|
elif 'max_units' in c:
|
||||||
|
c['units'] = num(c['max_units'])
|
||||||
|
all_courses_dict[c['dept'] + c['number']] = c
|
||||||
|
|
||||||
|
return all_courses_dict[course]['units']
|
||||||
|
|
||||||
|
|
||||||
|
def create_degree_mzn():
|
||||||
|
|
||||||
|
okay_courses, notokay_courses = build_program_rules(0)
|
||||||
|
|
||||||
|
for this_course in okay_courses:
|
||||||
|
fn = 'cache/program_check/' + deg_name_to_filename(this_course[0]) + '.mzn'
|
||||||
|
|
||||||
|
# don't overwrite
|
||||||
|
if os.path.exists(fn):
|
||||||
|
continue
|
||||||
|
|
||||||
|
out_file = codecs.open(fn, 'w', 'utf-8')
|
||||||
|
print(this_course)
|
||||||
|
print("\t" + fn)
|
||||||
|
#print(okay_courses)
|
||||||
|
#continue
|
||||||
|
|
||||||
|
mzn_starter = """%%
|
||||||
|
%% [[degreename]]
|
||||||
|
%%
|
||||||
|
%% Check if a student has attained the degree / program
|
||||||
|
%%
|
||||||
|
%% or what courses they'd still need for it
|
||||||
|
|
||||||
|
|
||||||
|
%%
|
||||||
|
%% Set up all courses
|
||||||
|
int: numcourses = [[numcourses]];
|
||||||
|
array[1..numcourses] of string:
|
||||||
|
all_courses = [ [[allcourses]] ];
|
||||||
|
|
||||||
|
array[1..numcourses] of float:
|
||||||
|
all_units = [ [[allunits]] ];
|
||||||
|
|
||||||
|
%% Courses student took
|
||||||
|
array[1..numcourses] of bool: student_taken;
|
||||||
|
""".split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
all_names = ",".join([ f"\"{str(x)}\"" for x in course_main_record() ])
|
||||||
|
all_units = ",".join([ str(course_units(x)) for x in course_main_record() ])
|
||||||
|
output = substitute_template_var(mzn_starter, 'allcourses', all_names)
|
||||||
|
output = substitute_template_var(output, 'allunits', all_units)
|
||||||
|
output = substitute_template_var(output, 'numcourses', len(course_main_record()))
|
||||||
|
output = substitute_template_var(output, 'degreename', this_course[0])
|
||||||
|
out_file.write("\n".join(output))
|
||||||
|
|
||||||
|
for n,rule in enumerate(this_course[1:]):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
m1 = re.search(r'take at least (\d+) courses? from (.*)$', rule)
|
||||||
|
if m1:
|
||||||
|
num = m1.group(1)
|
||||||
|
crs = m1.group(2).strip(',').split(",")
|
||||||
|
crs = [ re.sub(r'\s+','',x) for x in crs ]
|
||||||
|
print(f"\t{num} courses from {crs}")
|
||||||
|
# take n courses
|
||||||
|
n_courses = """%%
|
||||||
|
%%
|
||||||
|
%% Rule [[n]]: Take [[num]] courses
|
||||||
|
%% [[originalrule]]
|
||||||
|
array[1..numcourses] of bool: rule_[[n]]_courses = [ [[courseboollist]] ];
|
||||||
|
array[1..numcourses] of var bool: rule_[[n]]_selected;
|
||||||
|
|
||||||
|
% needed to complete
|
||||||
|
array[1..numcourses] of var bool:
|
||||||
|
rule_[[n]]_needed = [ rule_[[n]]_selected[j] /\ rule_[[n]]_courses[j] /\ (not student_taken[j]) | j in 1..numcourses ];
|
||||||
|
|
||||||
|
% Limit selections to this rule
|
||||||
|
constraint forall(j in 1..numcourses)( if rule_[[n]]_courses[j] == false then rule_[[n]]_selected[j] = false endif );
|
||||||
|
|
||||||
|
%% The Rule: Take n courses
|
||||||
|
constraint sum(j in 1..numcourses)( bool2int(rule_[[n]]_selected[j])) >= [[num]]; %% n courses
|
||||||
|
|
||||||
|
|
||||||
|
""".split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
rule_output = substitute_template_var(n_courses, 'n', n)
|
||||||
|
rule_output = substitute_template_var(rule_output, 'num', num)
|
||||||
|
rule_output = substitute_template_var(rule_output, 'originalrule', rule)
|
||||||
|
courselist = ",".join(courses_to_vector(crs))
|
||||||
|
rule_output = substitute_template_var(rule_output, 'courseboollist', courselist)
|
||||||
|
out_file.write("\n".join(rule_output))
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
m1 = re.search(r'take at least (\d+) units? from (.*)$', rule)
|
||||||
|
if m1:
|
||||||
|
num = m1.group(1)
|
||||||
|
crs = m1.group(2).strip(',').split(",")
|
||||||
|
crs = [ re.sub(r'\s+','',x) for x in crs ]
|
||||||
|
print(f"\t{num} units from {crs}")
|
||||||
|
# take n courses
|
||||||
|
n_courses = """%%
|
||||||
|
%%
|
||||||
|
%% Rule [[n]]: Take [[num]] units
|
||||||
|
%% [[originalrule]]
|
||||||
|
array[1..numcourses] of bool: rule_[[n]]_courses = [ [[courseboollist]] ];
|
||||||
|
array[1..numcourses] of var bool: rule_[[n]]_selected;
|
||||||
|
|
||||||
|
% needed to complete
|
||||||
|
array[1..numcourses] of var bool:
|
||||||
|
rule_[[n]]_needed = [ rule_[[n]]_selected[j] /\ rule_[[n]]_courses[j] /\ (not student_taken[j]) | j in 1..numcourses ];
|
||||||
|
|
||||||
|
% Limit selections to this rule
|
||||||
|
constraint forall(j in 1..numcourses)( if rule_[[n]]_courses[j] == false then rule_[[n]]_selected[j] = false endif );
|
||||||
|
|
||||||
|
%% Rule - take n units
|
||||||
|
constraint sum(j in 1..numcourses)(bool2float(rule_[[n]]_selected[j]) * all_units[j])>=[[num]]; %% n units
|
||||||
|
|
||||||
|
|
||||||
|
""".split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
rule_output = substitute_template_var(n_courses, 'n', n)
|
||||||
|
rule_output = substitute_template_var(rule_output, 'num', num)
|
||||||
|
rule_output = substitute_template_var(rule_output, 'originalrule', rule)
|
||||||
|
courselist = ",".join(courses_to_vector(crs))
|
||||||
|
rule_output = substitute_template_var(rule_output, 'courseboollist', courselist)
|
||||||
|
out_file.write("\n".join(rule_output))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
m1 = re.search(r'take_all from (.*)$', rule)
|
||||||
|
if m1:
|
||||||
|
crs = m1.group(1).strip(',').split(",")
|
||||||
|
crs = [ re.sub(r'\s+','',x) for x in crs ]
|
||||||
|
print(f"\ttake all from {crs}")
|
||||||
|
# take n courses
|
||||||
|
n_courses = """%%
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
%% Rule [[n]]: Take all courses
|
||||||
|
%% [[originalrule]]
|
||||||
|
array[1..numcourses] of bool: rule_[[n]]_courses = [ [[courseboollist]] ];
|
||||||
|
array[1..numcourses] of var bool: rule_[[n]]_selected;
|
||||||
|
|
||||||
|
% needed to complete
|
||||||
|
array[1..numcourses] of var bool:
|
||||||
|
rule_[[n]]_needed = [ rule_[[n]]_selected[j] /\ rule_[[n]]_courses[j] /\ (not student_taken[j]) | j in 1..numcourses ];
|
||||||
|
|
||||||
|
% Limit selections to this rule
|
||||||
|
constraint forall(j in 1..numcourses)( if rule_[[n]]_courses[j] == false then rule_[[n]]_selected[j] = false endif );
|
||||||
|
|
||||||
|
%% Rule - take all
|
||||||
|
constraint forall(j in 1..numcourses)( if rule_[[n]]_courses[j] == true then rule_[[n]]_selected[j] = true endif );
|
||||||
|
|
||||||
|
|
||||||
|
""".split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
rule_output = substitute_template_var(n_courses, 'n', n)
|
||||||
|
rule_output = substitute_template_var(rule_output, 'originalrule', rule)
|
||||||
|
courselist = ",".join(courses_to_vector(crs))
|
||||||
|
rule_output = substitute_template_var(rule_output, 'courseboollist', courselist)
|
||||||
|
out_file.write("\n".join(rule_output))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
#
|
||||||
|
# finish out the file
|
||||||
|
finish = """
|
||||||
|
|
||||||
|
%% All Rules: No double dipping
|
||||||
|
constraint forall(j in 1..numcourses)(count([ [[double_dip]] ],true) <= 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var int: total_courses;
|
||||||
|
var float: total_units;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% minimize courses
|
||||||
|
total_courses = sum(j in 1..numcourses)( [[coursesum]] );
|
||||||
|
|
||||||
|
% minimize units
|
||||||
|
total_units = sum(j in 1..numcourses)( [[units_sum]] );
|
||||||
|
solve minimize total_units + total_courses;
|
||||||
|
|
||||||
|
output [ "{" ]
|
||||||
|
|
||||||
|
""".split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
num_rules = len(this_course[1:])
|
||||||
|
course_sum = " + ".join( [ f"bool2int(rule_{x}_selected[j])" for x in range(num_rules) ] )
|
||||||
|
rule_output = substitute_template_var(finish, 'coursesum', course_sum)
|
||||||
|
|
||||||
|
double_dip = ", ".join( [ f"bool2int(rule_{x}_selected[j])" for x in range(num_rules) ] )
|
||||||
|
rule_output = substitute_template_var(rule_output, 'double_dip', double_dip)
|
||||||
|
|
||||||
|
units_sum = " + ".join( [ f"all_units[j] * bool2float(rule_{x}_needed[j])" for x in range(num_rules) ] )
|
||||||
|
rule_output = substitute_template_var(rule_output, 'units_sum', units_sum)
|
||||||
|
|
||||||
|
out_file.write("\n".join(rule_output))
|
||||||
|
|
||||||
|
display = [ f"""++ [ "\\n\\"rule {x} selected\\": [" ]
|
||||||
|
++ [ if fix(rule_{x}_selected[j])=true then " \(all_courses[j]), " else "" endif | j in 1..numcourses ]
|
||||||
|
++ ["],\\n\\"rule {x} need to take\\": [" ]
|
||||||
|
++ [ if fix(rule_{x}_needed[j])=true then " \(all_courses[j]) ," else "" endif | j in 1..numcourses ]
|
||||||
|
++ [ "]," ]
|
||||||
|
""" for x in range(num_rules) ]
|
||||||
|
|
||||||
|
out_file.write("\n".join(display))
|
||||||
|
out_file.write("""++ ["\\n\\"total units\\": \(total_units)" ]
|
||||||
|
++ [",\\n\\"total courses\\": \(total_courses) \\n}" ]
|
||||||
|
;
|
||||||
|
""")
|
||||||
|
|
||||||
|
out_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
def check_student_degrees():
|
||||||
|
records = grades_to_vectors(boolean=1)
|
||||||
|
i = 0
|
||||||
|
needed = defaultdict(dict)
|
||||||
|
|
||||||
|
# took no classes?
|
||||||
|
empty_student = {}
|
||||||
|
|
||||||
|
for r in records:
|
||||||
|
i += 1
|
||||||
|
if i>100: break
|
||||||
|
|
||||||
|
lowest = 1000
|
||||||
|
lowest_deg = ""
|
||||||
|
recommendation = {}
|
||||||
|
crs = ", ".join([x[0] for x in r[2] ])
|
||||||
|
|
||||||
|
if r[0] != "0" and len(r[2]) < 3:
|
||||||
|
print(f"skipping student: {r[0]} taken {crs}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
#print(f"student: {r[0]}, taken {crs}", end="", flush=True)
|
||||||
|
print(f"student: {r[0]}, taken {crs}")
|
||||||
|
for degree in os.listdir('cache/program_check'):
|
||||||
|
m1 = re.match(r'(.*)\.mzn', degree)
|
||||||
|
if m1:
|
||||||
|
try:
|
||||||
|
d = m1.group(1)
|
||||||
|
|
||||||
|
#print(f"checking student: {r[0]} with degree: {d}, taken {crs}")
|
||||||
|
deg_model = Model(f"cache/program_check/{degree}")
|
||||||
|
#print(".", end="", flush=True)
|
||||||
|
print(f" {d}")
|
||||||
|
|
||||||
|
# Find the MiniZinc solver configuration for Gecode
|
||||||
|
gecode = Solver.lookup("gecode")
|
||||||
|
# Create an Instance of the n-Queens model for Gecode
|
||||||
|
instance = Instance(gecode, deg_model)
|
||||||
|
|
||||||
|
# Assign student courses taken
|
||||||
|
s = f"student_taken = [" + ','.join( [ str(x) for x in r[1]] ) + "];"
|
||||||
|
instance.add_string(s)
|
||||||
|
|
||||||
|
result = instance.solve()
|
||||||
|
r_str = str(result).split("\n")
|
||||||
|
r_str = [ re.sub(r'\s?,\s?\]', ']', x) for x in r_str ]
|
||||||
|
|
||||||
|
r1 = json.loads("\n".join(r_str))
|
||||||
|
#print(json.dumps(r1,indent=2))
|
||||||
|
#print()
|
||||||
|
|
||||||
|
if str(r[0]) == "0":
|
||||||
|
empty_student[d] = r1['total units']
|
||||||
|
needed[r[0]][d] = r1['total units']
|
||||||
|
if r1['total units']>0 and r1['total units'] < lowest:
|
||||||
|
lowest = r1['total units']
|
||||||
|
lowest_deg = d
|
||||||
|
recommendation = r1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"error with student {r[0]} and degree {d}")
|
||||||
|
print(e)
|
||||||
|
print()
|
||||||
|
# What shall we recommend to this student?
|
||||||
|
remaining_courses = []
|
||||||
|
for k,v in recommendation.items():
|
||||||
|
if re.search(r'need to take', k):
|
||||||
|
remaining_courses.extend(v)
|
||||||
|
rc = ", ".join(remaining_courses)
|
||||||
|
codecs.open('cache/recommend_program.txt','a','utf-8').write( \
|
||||||
|
f"student {r[0]} should take {lowest_deg} ({lowest} units)\nAlready taken: {crs}\n" + \
|
||||||
|
f"needs to take: {rc} \n\n")
|
||||||
|
|
||||||
|
df = pd.DataFrame.from_dict(needed, orient='index')
|
||||||
|
df.to_csv('cache/program_check/needed.csv')
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
options = { 1: ['parsing example',parser] ,
|
options = { 1: ['parsing example',parser] ,
|
||||||
2: ['build program rules', build_program_rules],
|
2: ['build program rules', build_program_rules],
|
||||||
|
3: ['create degree logic files', create_degree_mzn],
|
||||||
|
4: ['check student degrees', check_student_degrees]
|
||||||
}
|
}
|
||||||
print ('')
|
print ('')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,6 @@ def num(s):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return float(s)
|
return float(s)
|
||||||
|
|
||||||
def num_units(s):
|
|
||||||
if s == '': return 0
|
|
||||||
m = re.match(r'^(\d+)\.?(\d+)?\s?(units)?$',s)
|
|
||||||
if m:
|
|
||||||
if m.group(1) and m.group(2):
|
|
||||||
if m.group(2) == '0':
|
|
||||||
return int(m.group(1))
|
|
||||||
return float(f"{m.group(1)}.{m.group(2)}")
|
|
||||||
elif m.group(1):
|
|
||||||
return int(m.group(1))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return int(s)
|
|
||||||
except ValueError:
|
|
||||||
return float(s)
|
|
||||||
|
|
||||||
|
|
||||||
class CourseOr:
|
class CourseOr:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import heapq, re, csv, os, shutil, datetime, urllib
|
import heapq, re, csv, os, shutil, datetime, urllib, sys
|
||||||
import itertools, time, markdown, csv, json, os.path, webbrowser, threading
|
import itertools, time, markdown, csv, json, os.path, webbrowser, threading
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import Flask, request, send_from_directory, Response, render_template
|
from flask import Flask, request, send_from_directory, Response, render_template
|
||||||
|
|
@ -6,7 +6,9 @@ from flask import send_file
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
from werkzeug.routing import PathConverter
|
from werkzeug.routing import PathConverter
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import ScrollableContainer
|
||||||
|
from textual.widgets import Button, Footer, Header, Static
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
|
||||||
import server
|
import server
|
||||||
|
|
@ -355,9 +357,48 @@ def serve():
|
||||||
y.start()
|
y.start()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
serve()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasApp(App):
|
||||||
|
"""A Textual app to manage canvas."""
|
||||||
|
|
||||||
|
BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
"""Create child widgets for the app."""
|
||||||
|
yield Header()
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
|
def action_toggle_dark(self) -> None:
|
||||||
|
"""An action to toggle dark mode."""
|
||||||
|
self.dark = not self.dark
|
||||||
|
|
||||||
|
|
||||||
|
def text_app():
|
||||||
|
app = CanvasApp()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
options = { 1: ['start web server',serve] ,
|
||||||
|
2: ['start text app', text_app],
|
||||||
|
}
|
||||||
|
print ('')
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]):
|
||||||
|
resp = int(sys.argv[1])
|
||||||
|
print("\n\nPerforming: %s\n\n" % options[resp][0])
|
||||||
|
|
||||||
|
else:
|
||||||
|
print ('')
|
||||||
|
for key in options:
|
||||||
|
print(str(key) + '.\t' + options[key][0])
|
||||||
|
|
||||||
|
print('')
|
||||||
|
resp = input('Choose: ')
|
||||||
|
|
||||||
|
# Call the function in the options dict
|
||||||
|
options[ int(resp)][1]()
|
||||||
|
|
||||||
|
|
|
||||||
81
stats.py
81
stats.py
|
|
@ -75,6 +75,7 @@ student_orientation_participation = f'cache/participation_orientation_courses.js
|
||||||
|
|
||||||
def num(s):
|
def num(s):
|
||||||
if s == '': return 0
|
if s == '': return 0
|
||||||
|
s = re.sub(r'\.0','',s)
|
||||||
try:
|
try:
|
||||||
return int(s)
|
return int(s)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -487,6 +488,8 @@ def reorganize_grades_student():
|
||||||
output_s.write("student,courses\n")
|
output_s.write("student,courses\n")
|
||||||
output = csv.writer(output_f)
|
output = csv.writer(output_f)
|
||||||
output.writerow("course_code course pocr_status orientation_status teacher_code mode student_id scaled_score".split(" "))
|
output.writerow("course_code course pocr_status orientation_status teacher_code mode student_id scaled_score".split(" "))
|
||||||
|
# student id 0 has no courses
|
||||||
|
output.writerow([0,])
|
||||||
for st in students:
|
for st in students:
|
||||||
courses = [r[1] for r in bystudent[st]]
|
courses = [r[1] for r in bystudent[st]]
|
||||||
scores = [r[7] for r in bystudent[st]]
|
scores = [r[7] for r in bystudent[st]]
|
||||||
|
|
@ -558,6 +561,7 @@ def shell2course(shell):
|
||||||
|
|
||||||
def stu_record_line(line):
|
def stu_record_line(line):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
line = line.strip(',')
|
||||||
parts = line.split(',')
|
parts = line.split(',')
|
||||||
stu_id = parts[0]
|
stu_id = parts[0]
|
||||||
courses = []
|
courses = []
|
||||||
|
|
@ -565,40 +569,50 @@ def stu_record_line(line):
|
||||||
courses.append(C.split('|'))
|
courses.append(C.split('|'))
|
||||||
return stu_id, courses
|
return stu_id, courses
|
||||||
|
|
||||||
def stu_record_to_vector(line):
|
def stu_record_to_vector(line, boolean=0):
|
||||||
id, courses = stu_record_line(line)
|
id, courses = stu_record_line(line)
|
||||||
|
|
||||||
|
yesval = "true" if boolean else 1
|
||||||
|
noval = "false" if boolean else 0
|
||||||
|
|
||||||
template = json.loads(codecs.open('cache/courses/course_main_record.json','r','utf-8').read())
|
template = json.loads(codecs.open('cache/courses/course_main_record.json','r','utf-8').read())
|
||||||
lookup = {}
|
lookup = {}
|
||||||
for i,c in enumerate(template):
|
for i,c in enumerate(template):
|
||||||
lookup[c] = i
|
lookup[c] = i
|
||||||
vector = [0 for x in range(len(template))]
|
vector = [noval for x in range(len(template))]
|
||||||
for C in courses:
|
for C in courses:
|
||||||
goodname = shell2course(C[0])
|
goodname = shell2course(C[0])
|
||||||
if goodname:
|
if goodname:
|
||||||
vector[lookup[goodname]] = 1 # C[1] # score
|
vector[lookup[goodname]] = yesval # C[1] # score
|
||||||
return id,vector
|
return id,vector,courses
|
||||||
|
|
||||||
|
|
||||||
def grades_to_vectors(verbose=1):
|
def grades_to_vectors(boolean=0, verbose=0):
|
||||||
grades = codecs.open('cache/courses_student_scores.csv','r','utf-8').readlines()
|
grades = codecs.open('cache/courses_student_scores.csv','r','utf-8').readlines()
|
||||||
for L in grades:
|
for L in grades:
|
||||||
id, vector = stu_record_to_vector(L)
|
id, vector, courses = stu_record_to_vector(L,boolean)
|
||||||
if verbose: print(id, vector)
|
if verbose: print(id, vector)
|
||||||
|
yield id, vector, courses
|
||||||
|
|
||||||
def course_main_record():
|
def course_main_record():
|
||||||
return json.loads(codecs.open('cache/courses/course_main_record.json','r','utf-8').read())
|
return json.loads(codecs.open('cache/courses/course_main_record.json','r','utf-8').read())
|
||||||
|
|
||||||
def courses_to_vector(course_list):
|
def courses_to_vector(course_list, boolean=1):
|
||||||
|
#print(course_list)
|
||||||
|
yesval = "true" if boolean else 1
|
||||||
|
noval = "false" if boolean else 0
|
||||||
template = course_main_record()
|
template = course_main_record()
|
||||||
lookup = {}
|
lookup = {}
|
||||||
for i,c in enumerate(template):
|
for i,c in enumerate(template):
|
||||||
lookup[c] = i
|
lookup[c] = i
|
||||||
vector = [0 for x in range(len(template))]
|
vector = [noval for x in range(len(template))]
|
||||||
for C in course_list:
|
for C in course_list:
|
||||||
goodname = shell2course(C[0])
|
C = C.strip()
|
||||||
if goodname:
|
#goodname = shell2course(C[0])
|
||||||
vector[lookup[goodname]] = 1 # C[1] # score
|
#if goodname:
|
||||||
|
#print(C)
|
||||||
|
vector[lookup[C]] = yesval # C[1] # score
|
||||||
|
#print(vector)
|
||||||
return vector
|
return vector
|
||||||
|
|
||||||
def course_vector_to_names(vector):
|
def course_vector_to_names(vector):
|
||||||
|
|
@ -611,22 +625,35 @@ def course_vector_to_names(vector):
|
||||||
|
|
||||||
|
|
||||||
def all_course_names():
|
def all_course_names():
|
||||||
complete_list = {}
|
ac = json.loads(codecs.open('cache/courses/courses_built.json','r','utf-8').read())
|
||||||
missing_names = {}
|
master_record = []
|
||||||
with open(student_courses_scores,'r') as input_f:
|
for C in ac.values():
|
||||||
for L in input_f:
|
if C['status'] == 'Draft':
|
||||||
stu_id, courses = stu_record_line(L)
|
continue
|
||||||
for C in courses:
|
name = C['dept'] + C['number']
|
||||||
real_name = shell2course(C[0])
|
master_record.append(name)
|
||||||
if real_name:
|
master_record = set(master_record)
|
||||||
complete_list[real_name] = 1
|
master_record = list(master_record)
|
||||||
else:
|
master_record = sorted(master_record)
|
||||||
missing_names[C[0]] = 1
|
|
||||||
master_record = sorted(complete_list.keys())
|
## Extract from all 'accomplished courses'...
|
||||||
print(f"Found {len(master_record)} courses")
|
if 0:
|
||||||
print(master_record)
|
complete_list = {}
|
||||||
print(f"Missing {len(missing_names)} courses")
|
missing_names = {}
|
||||||
print(missing_names)
|
with open(student_courses_scores,'r') as input_f:
|
||||||
|
for L in input_f:
|
||||||
|
stu_id, courses = stu_record_line(L)
|
||||||
|
for C in courses:
|
||||||
|
real_name = shell2course(C[0])
|
||||||
|
if real_name:
|
||||||
|
complete_list[real_name] = 1
|
||||||
|
else:
|
||||||
|
missing_names[C[0]] = 1
|
||||||
|
master_record = sorted(complete_list.keys())
|
||||||
|
print(f"Found {len(master_record)} courses")
|
||||||
|
print(master_record)
|
||||||
|
print(f"Missing {len(missing_names)} courses")
|
||||||
|
print(missing_names)
|
||||||
mr = codecs.open('cache/courses/course_main_record.json','w','utf-8')
|
mr = codecs.open('cache/courses/course_main_record.json','w','utf-8')
|
||||||
mr.write(json.dumps(master_record,indent=2))
|
mr.write(json.dumps(master_record,indent=2))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue