Moved repo

This commit is contained in:
murraydr 2023-10-11 13:21:26 -04:00
parent 8321655406
commit fa27a9e403
33 changed files with 6838 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*~*
*.pyc
stringOutputs
token.json
*.notes

Binary file not shown.

BIN
TaskManager.docx Normal file

Binary file not shown.

1
archivedSubRequests.csv Normal file
View File

@ -0,0 +1 @@
Timestamp,Requestor,Section,Dates,Replacement,Reason,Result,InstructorReason
1 Timestamp Requestor Section Dates Replacement Reason Result InstructorReason

1
credentials.json Normal file
View File

@ -0,0 +1 @@
{"installed":{"client_id":"737664693809-r1pn3umd94k0ie9nv8ie0h3ikalf9k6v.apps.googleusercontent.com","project_id":"cse102-399617","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}

98
driver.py Normal file
View File

@ -0,0 +1,98 @@
import PySimpleGUI as sg
import pandas as pd
import substitutionApprovalWindow as SWin
import helperFunctions as HF
import scheduledPayrollWindow as PWin
#TODO workflow with git
#TODO search section/date (show curr and orig people assigned)
#TODO code comment
#TODO document how to setup for next semester (staff and section changes)
#TODO document how to get through the cert expiration every week
#TODO do error checking within while loop (update text color / valid status)
#TODO make an error if try to sub in a person if they are already working elsewhere at that time
#TODO delete filename parameters in window functions (only helper functions need it)
#---LOW PRIORITY (due to payroll being entered by ULAs directly)---
#TODO hours database
#TODO section/helproom attendence verification form (filled out by ULAs themselves)
#TODO add to hours database
#TODO staff meeting attendence google form
#TODO add to hours database
#TODO request ad hoc hours google form & approval interface (mirrors sub requests workflow)
#TODO add to hours database
#TODO payroll chart from hours database
#TODO payroll chart click on entry to get detailed breakdown
#set parameters
defaultFont=("Courier",11) #Make sure that the font is evenly spaced or else the tables will be wonky.
sectionDatabaseFilename="sectionsDatabase.csv"
staffDatabaseFilename="staffDatabase.csv"
subRequestsFilename="substitutionRequestForm.csv"
subRequestsArchiveFilename="archivedSubRequests.csv"
RegHoursFilename="regularHoursPayrollForm.csv"
#load databases
HF.getForms(subRequestsFilename)
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
#remove leading/trailing whitespace
secD = secD.apply(lambda x: x.str.strip())
stfD = stfD.apply(lambda x: x.str.strip())
#stfD = stfD.apply(lambda x: x.str.lower())
#rewrite to file (each GUI function reads/writes on its own so that the state on disk matches what the user expects while the program is running)
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)
stfD.to_csv(staffDatabaseFilename,index=False,index_label=False)
#remove timestamp & chosen email (which is NOT necessarily their MSU email), from Google Forms
#this has the side effect of deleting the quotation marks on each entry, which is fine as they are unnecesssary
subs = pd.read_csv(subRequestsFilename,dtype=str)
subs.drop(['Username'],inplace=True,axis=1,errors='ignore')
try:
subs.rename({(subs.loc[:, subs.columns.str.startswith('Section')].columns[0]):"Section"},inplace=True,axis=1)
subs.rename({(subs.loc[:, subs.columns.str.startswith('Substit')].columns[0]):"Requestor"},inplace=True,axis=1)
subs.rename({(subs.loc[:, subs.columns.str.startswith('Dates')].columns[0]):"Dates"},inplace=True,axis=1)
subs.rename({(subs.loc[:, subs.columns.str.startswith('MSU netID of Intend')].columns[0]):"Replacement"},inplace=True,axis=1)
subs.rename({(subs.loc[:, subs.columns.str.startswith('Reason')].columns[0]):"Reason"},inplace=True,axis=1)
except:
pass
subs.to_csv(subRequestsFilename,index=False,index_label=False)
HF.createStrings()
SWin.subAppWin(staffDatabaseFilename,sectionDatabaseFilename,subRequestsFilename,subRequestsArchiveFilename,defaultFont)
#Diff check on original section assignments
df1 = pd.read_csv(sectionDatabaseFilename,dtype=str)
df2 = pd.read_csv(sectionDatabaseFilename[:-4]+"_Orig.csv",dtype=str)
df1.fillna(value="",inplace=True)
df2.fillna(value="",inplace=True)
#diff=df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]
#diff=pd.concat([df1,df2]).drop_duplicates(keep=False)
diff = df1.merge(df2, indicator=True, how='outer')
diff=diff.loc[diff['_merge'] != 'both']
print("\n"+"-"*200+"\nTo-Date difference from default shifts:")
print(diff)
print("-"*200)
#PWin.scheduledPayWin(sectionDatabaseFilename,RegHoursFilename,defaultFont)
#HF.sendEmail("murraydr","CSE102 Substitution","This is a test message")

2
google forms login.txt Normal file
View File

@ -0,0 +1,2 @@
cse102msu@gmail.com
python_1107

445
helperFunctions.py Normal file
View File

@ -0,0 +1,445 @@
#commonly-used helper functions for the other scripts
#By Drew Murray murraydr@msu.edu
import pandas as pd
import copy as cp
import smtplib
import os.path
from datetime import datetime, timedelta
#libraries for google forms
from apiclient import discovery
from httplib2 import Http
from oauth2client import client, file, tools
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.auth.transport.requests import Request
sectionDatabaseFilename="sectionsDatabase.csv"
staffDatabaseFilename="staffDatabase.csv"
#Converts full names to nedIDs and then back again based on the staff database
def nameToID(searchName,getAllMatches=False):
if searchName == "":
return ""
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
try:
ret = stfD[stfD['Name'].str.contains(searchName.strip("*"),case=False,regex=False)]["NetID"].values
except IndexError:
print ('ERROR: name "'+searchName+'" does not appear in the staff database')
ret = -1
if getAllMatches:
return ret
else:
return ret[0]
def IDToName(searchID):
if searchID=="":
return ""
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
name = stfD.loc[stfD['NetID']==searchID].values[0][0]
if stfD.loc[stfD['NetID']==searchID].values[0][3]=="1":#if returning staff
name+="*"#append asterisk to name
return name
def isRequestOverlap(req1,req2):
print(req1)
print(req2)
sections1=req1[2].split(';')
sections2=req2[2].split(';')
dates1=req1[3].split(';')
dates2=req2[3].split(';')
overlap=False
for s1 in sections1:
for s2 in sections2:
for d1 in dates1:
for d2 in dates2:
if s1==s2 and d1==d2:
overlap=True
return overlap
def getTimeFromSection(section):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
return(secD.loc[secD['Section']==section]['Time'].iloc[0])
def getLocationFromSection(section):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
return(secD.loc[secD['Section']==section]['Location'].iloc[0])
def isAssigned(netID,period,date):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
ret= False
filt = (secD.loc[(secD['Section']==period) & (secD['Date']==date)].apply(lambda r: r.astype('string').str.contains(netID).any(), axis=1))
if filt.any(): #if the requestor is not assigned to that section
ret=True
return ret
def reassign(date,period,oldID,newID):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
secD.loc[(secD['Section']==period) & (secD['Date']==date)]=secD.loc[(secD['Section']==period) & (secD['Date']==date)].replace(oldID,newID)
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)
def getSectionTitles():
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
sections=list(secD.drop_duplicates(subset=["Section"])["Section"].values)
return sections
def getDatesFromSection(sec):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
dates = list(secD.loc[secD["Section"]==sec]["Date"])
return dates
def incrementSubCount(netID,category,amount=1):
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
if category==0 or category=="APP":
columnName="Approved Substitutions"
elif category==1 or category=="ACC":
columnName="Accepted Substitutions"
elif category==2 or category=="REJ":
columnName="Rejected Substitutions"
elif category==3 or category=="CAN":
columnName="Cancelled Substitutions"
elif category==4 or category=="FUL":
columnName="Fulfilled Substitutions"
else:
print("ERROR: Invalid category to incrementSubCount")
return
stfD.loc[stfD['NetID']==netID,columnName]=str(float(stfD.loc[stfD['NetID']==netID,columnName])+amount)
stfD.to_csv(staffDatabaseFilename,index=False,index_label=False)
def getSubCount(netID):
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
APP = stfD.loc[(stfD["NetID"]==netID)]["Approved Substitutions"].values[0]
ACC = stfD.loc[(stfD["NetID"]==netID)]["Accepted Substitutions"].values[0]
REJ = stfD.loc[(stfD["NetID"]==netID)]["Rejected Substitutions"].values[0]
CAN = stfD.loc[(stfD["NetID"]==netID)]["Cancelled Substitutions"].values[0]
FUL = stfD.loc[(stfD["NetID"]==netID)]["Fulfilled Substitutions"].values[0]
return (APP,ACC,REJ,CAN,FUL)
def getTopSubs(categoryStr,fewest=False,num=1):
if categoryStr=="":
return ""
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
columns=[]
if "0" in categoryStr:
columns+=["Approved Substitutions"]
if "1" in categoryStr:
columns+=["Accepted Substitutions"]
if "2" in categoryStr:
columns+=["Rejected Substitutions"]
if "3" in categoryStr:
columns+=["Cancelled Substitutions"]
if "4" in categoryStr:
columns+=["Fulfilled Substitutions"]
header=""
for name in columns:
header+=name[:3].upper()+"+"
header=header[:-1]
stfD[header]=0
for name in columns:
stfD[header]+=(stfD[name].astype(float))
return stfD.sort_values(header,axis=0,ascending = fewest).head(num)[["Name",header]].to_string(index=False)
def shiftExists(period,date):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
try:
temp=secD[(secD['Section']==period) & (secD['Date']==date)]
temp.values[0]
except IndexError:
return False
return True
def getAllNamesFromSection(periods,dates):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
others=[]
for period in periods.split(';'):
for date in dates.split(';'):
try:
temp=secD[(secD['Section']==period) & (secD['Date']==date)]
netIDs=list(temp.values[0][5:16])
netIDs = [IDToName(x) for x in netIDs if pd.notna(x)]
others.append(cp.deepcopy(netIDs))
except IndexError:
others.append("NoShift")
return(others)
#finds which date is earlier in string MM/DD format
def dateBeforeOrEqual(date1, date2):
month1,day1 = date1.split('/')
month2,day2 = date2.split('/')
month1=int(month1)
month2=int(month2)
day1=int(day1)
day2=int(day2)
if month1<month2:
return True
elif month1>month2:
return False
elif day1<=day2:
return True
else:
return False
#create string files for easy pasting into google forms
def createStrings():
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
secD = pd.read_csv(sectionDatabaseFilename,dtype=str,index_col=False)
with open("stringOutputs/staffIDs.txt",'w') as f:
netIDs=stfD['NetID'].values
netIDs.sort()
f.write('\n'.join(netIDs))
with open("stringOutputs/emails.txt",'w') as f:
vals=cp.deepcopy(stfD['NetID'].values)
for i in range(len(vals)):
vals[i]=vals[i]+"@msu.edu"
f.write('; '.join(vals))
with open("stringOutputs/sectionTitles.txt",'w') as f:
vals=secD['Section'].values
seen=set()
f.write('\n'.join([x for x in vals if not (x in seen or seen.add(x))]))
with open("stringOutputs/periodDates.txt",'w') as f:
vals=secD['Date'].values
seen=set()
f.write('\n'.join([x for x in vals if not (x in seen or seen.add(x))]))
def generateEmails(requestList):
emails=[]
for req in requestList:
timestamp = req[0]
requestor = IDToName(req[1]).strip("*")
section = req[2]
date = req[3]
replacement = IDToName(req[4]).strip("*")
reason = req[5]
status = req[6]
statusReason = req[7]
if replacement!="":
replaced=True
else:
replaced=False
if ";" in section:
sections = section.split(";")
else:
sections=[section]
if ";" in date:
dates = date.split(";")
else:
dates=[date]
times=[]
locations=[]
if status=="APP" or status=="ACC": #For now, don't tell the ULAs that their acceptance was begrudging. We can change this later if desired.
status="approved"
changed=True
for s in sections:
times.append(getTimeFromSection(s))
locations.append(getLocationFromSection(s))
elif status=="REJ" or status=="CAN":
if status == "REJ":
status="rejected"
else:
status="cancelled"
changed=False
for s in sections:
try:
times.append(getTimeFromSection(s))
except:
times=["INVALID DATE/SECTION"]
try:
locations.append(getLocationFromSection(s))
except:
locations=["INVALID DATE/SECTION"]
else:
print ('ERROR: Status of request is "'+str(status)+'" instead of APP/ACC/REJ/CAN')
return
recipient = req[1]+"@msu.edu"
subject = "Your request for substitution on "+date+" has been "+status+"."
sectionStrings=[]
for i in range(len(sections)):
if "HR" in sections[i]:#helproom
sectionStrings.append(sections[i].replace("_HR"," helproom (hour ")+")")
sectionStrings[i]=sectionStrings[i].replace("M","Monday")
sectionStrings[i]=sectionStrings[i].replace("T","Tuesday")
sectionStrings[i]=sectionStrings[i].replace("W","Wednesday")
sectionStrings[i]=sectionStrings[i].replace("R","Thursday")
sectionStrings[i]=sectionStrings[i].replace("F","Friday")
sectionStrings[i]=sectionStrings[i].replace("S","Sunday")
else:
sectionStrings.append(sections[i].replace("Sec","Section"))
message = "Hello "+requestor+","
message+="\n\nYour request to be substituted out of ["+" and ".join(sectionStrings)+"] at ["+" and ".join(times)+"] on ["+" and ".join(dates)+'] because of "'+reason+'" was recieved at ['+timestamp+"]."
if replaced:
message+="\nYou specified "+replacement+" as your replacement"
if changed:
message+=" and they have also recieved an automatically generated email notifying them of the switch."
else:
message+="."
else:
message+="\nYou did not specify a replacement."
message+="\n\nThis request has been reviewed by the professors and *"+status+"*"
if statusReason != "INST. REASON":
message+=' for the given reason of: "'+statusReason+'"'
if changed:
message+=" and the corresponding changes have been made in our calander.\nIf all of the above information is correct, no further action is necessary. If any of this information is incorrect, please contact us IMMEDIATELY on Slack so we can correct it."
else:
message+= " and no changes have been made in our calander. If you did not submit this request, please contact us on Slack as there may be an error in our system."
message+="\n\nThis email was automatically generated. Do not reply or send email to this address as it will not be seen. All communication should occur in Slack or by emailing cse102@msu.edu"
emails.append([recipient,subject,message])
#Also send email to new ULA if their subbing IN was approved/accepted
if changed and replaced:
recipient = req[4]+"@msu.edu"
subject = "You have been scheduled to substitute on "+date+"."
message = "Hello "+replacement+","
message+="\n\nYou have been scheduled to substitute for "+requestor+" in ["+" and ".join(sectionStrings)+"] at ["+" and ".join(times)+"] on ["+" and ".join(dates)+"] at ["+" and ".join(locations)+"]"
message+="\n\n"+requestor+" has specified that you have already agreed to this by submitting the request with you as a replacement and thus our calander has been changed."
message+="\nIf all of the above information is correct, no further action is necessary. If any of this information is incorrect, please contact us IMMEDIATELY on Slack so we can correct it."
message+="\n\nThis email was automatically generated. Do not reply or send email to this address as it will not be seen. All communication should occur in Slack or by emailing cse102@msu.edu"
emails.append([recipient,subject,message])
#Also send email to GA if their helproom ULAs have been changed.
if changed:
for s in sections:
if s in ["M_HR1","M_HR2","T_HR1","T_HR2","W_HR1","W_HR2","R_HR1","R_HR2","F_HR1","F_HR2","S_HR1","S_HR2"]:
for date in dates:
names=getAllNamesFromSection(s,date)[0]
print(names,names!="NoShift")
if names!="NoShift":
recipient = nameToID(names[0])
subject="Change in helproom staff on "+date
message="There has been a substitution in your helproom section: "+s+".\nYour ULAs on "+date+" are "+", ".join(names[1:])
emails.append([recipient,subject,message])
sendEmails(emails,actuallySend=False)
#duumyParam is to make really sure that the author of the code that calls this functions really intends to actually send emails (by forcing them to use the keyword)
def sendEmails(emails,dummyParam=False,actuallySend=False):
if actuallySend:
#https://www.geeksforgeeks.org/send-mail-gmail-account-using-python/
# creates SMTP session
s = smtplib.SMTP('smtp.gmail.com', 587)
# start TLS for security
s.starttls()
# Authentication
s.login("cse102msu@gmail.com", "whol ydag otqa hxps")
print("BEGINNING EMAIL SENDING!")
for email in emails:
address = email[0]
subject = email[1]
message = email[2]
messageObj = 'Subject: {}\n\n{}'.format(subject, message)
# sending the mail
s.sendmail("cse102msu@gmail.com", address, messageObj)
# terminating the session
print("Emails sent!")
s.quit()
else:
print('\n!!!PRINTING TO TERMINAL INSTEAD TO PREVENT ACCIDENTALY EMAIL SPAM!!!:\n\t(Use the argument "acutallySend=True" in "sendEmails" function to disable this.)')
for email in emails:
print("\n"+"-"*80+"\nEmail would be sent to "+email[0]+":\nSubject: "+email[1]+"\nMessage:\n"+email[2]+"\n"+"-"*80)
def getForms(subRequestsFilename):
#Requires some installation/setup https://developers.google.com/forms/api/quickstart/python
SCOPES = ["https://www.googleapis.com/auth/forms.responses.readonly"]
DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1"
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
store = file.Storage('token.json')
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
#Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
#service = discovery.build('forms', 'v1', http=creds.authorize(Http()), discoveryServiceUrl=DISCOVERY_DOC, static_discovery=False)
service = build('forms', 'v1', credentials=creds)
# gets the responses of your specified form:
form_id = '1x-8fkuMAcQlTl36SdsbCG0tfClKAcvNshnV8L_Hl904'
result = service.forms().responses().list(formId=form_id).execute()
subs = pd.read_csv(subRequestsFilename,dtype=str)
with open("lastUpdatedToken.txt",'r') as f:
line = f.readline()
if line != "":
prevTime=datetime.strptime(line,"%Y-%m-%d %H:%M:%S.%f")
else:
prevTime=datetime.strptime("1975-01-01 01:01:01.000000","%Y-%m-%d %H:%M:%S.%f")
data=result["responses"]
for req in data:
try:
reason=req["answers"]["22a5ae9b"]["textAnswers"]["answers"][0]["value"]
except KeyError:
reason = ""
requestor=req["answers"]["7bb6a9dd"]["textAnswers"]["answers"][0]["value"]
dates=""
for i in req["answers"]["11d3b4f8"]["textAnswers"]["answers"]:
dates+=i["value"]+";"
dates=dates[:-1]
try:
replacement=req["answers"]["30178530"]["textAnswers"]["answers"][0]["value"]
except KeyError:#no replacement specified
replacement=""
sections=""
for i in req["answers"]["5684403f"]["textAnswers"]["answers"]:
sections+=i["value"]+";"
sections=sections[:-1]
timeStr = req["createTime"][:-1].replace("T"," ")+"000"
timeStamp=datetime.strptime(timeStr, '%Y-%m-%d %H:%M:%S.%f')-timedelta(hours=4,minutes=0)
if timeStamp>prevTime:
reqDict={"Timestamp": [timeStr], "Requestor": [requestor], "Section": [sections], "Dates": [dates], "Replacement": [replacement], "Reason": [reason]}
subs=pd.concat([subs,pd.DataFrame(reqDict)],ignore_index=True)
subs.sort_values(by=["Timestamp"],inplace=True)
subs.to_csv(subRequestsFilename,index=False,index_label=False)
with open("lastUpdatedToken.txt",'w') as f:
f.write(str(datetime.now()))

1
lastUpdatedToken.txt Normal file
View File

@ -0,0 +1 @@
2022-10-04 15:15:51.231447

35
reset.py Normal file
View File

@ -0,0 +1,35 @@
import subprocess
import shutil
import os
import random
import pandas as pd
#Note that this script doesn't work inside protected Windows folders (such as "Documents")
password="Reset{:04d}".format(random.randint(0,10000))
inStr=input('Are you sure? Type "'+password+'" to reset the state of the databases and files to the "start-of-semester-state"\nThis action cannot be undone!\n')
if inStr==password:
subprocess.call(['cmd','/c',"python sectionCSVHelper.py"],shell=True)
shutil.copy("sectionsDatabase.csv", "sectionsDatabase_Orig.csv")
shutil.copy("startOfSemesterInputs\\archivedSubRequests.csv","archivedSubRequests.csv")
with open("lastUpdatedToken.txt", 'w') as f:
f.write("2022-10-04 15:15:51.231447")
with open('substitutionRequestForm.csv', 'r') as fin:
data = fin.read().splitlines(True)
with open('substitutionRequestForm.csv', 'w') as fout:
fout.writelines(data[:1])
stfD = pd.read_csv("staffDatabase.csv",dtype=str,index_col=False)
stfD['Approved Substitutions'].values[:]=0
stfD['Accepted Substitutions'].values[:]=0
stfD['Rejected Substitutions'].values[:]=0
stfD['Cancelled Substitutions'].values[:]=0
stfD['Fulfilled Substitutions'].values[:]=0
stfD.to_csv("staffDatabase.csv",index=False,index_label=False)
else:
print('You typed somethinge other than "Reset'+password+'". Exiting')

75
scheduledPayrollWindow.py Normal file
View File

@ -0,0 +1,75 @@
import PySimpleGUI as sg
import pandas as pd
import helperFunctions as HF
import csv
pd.set_option('display.max_columns', 50)
pd.set_option('display.min_rows', 60)
pd.set_option('display.max_rows', 60)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', 9)
#autoreject hours that aren't same day
#pop up GUI interface for rejects
#name date section reasonWhy
def scheduledPayWin(sectionDatabaseFilename,RegHoursFilename,defaultFont):
secD = pd.read_csv(sectionDatabaseFilename,dtype=str)
currDate=input("Current Date: ")
#Set all hours for sections on elapsed dates to 0 (future dates are set back to blank)
for staffNum in range(0,10):
secD.loc[secD['Date'].apply(HF.dateBeforeOrEqual,args=[currDate])&~secD['Staff'+str(staffNum)].isna(),'Hours'+str(staffNum)]=0
for staffNum in range(0,10):
secD.loc[~secD['Date'].apply(HF.dateBeforeOrEqual,args=[currDate])&~secD['Staff'+str(staffNum)].isna(),'Hours'+str(staffNum)]=''
#read in ALL regularly scheduled hours responses
forms=[]
with open(RegHoursFilename) as f:
headerLine=f.readline()
reader = csv.reader(f)
for row in reader:
forms.append(row)
warningHeader=":\n ID : section : hours date : submitted on\n --------------------------------------------------\n"
diffDayWarning="Submitted on a different day than the hours"+warningHeader
futureDayWarning="Submitted for a future date"+warningHeader
missingWarning="ULA not assigned to the section"+warningHeader
for form in forms:
ID=form[2]
section=form[3]
date=form[4]
vals=form[0].split(' ')[0].split('/')
submissionDate=vals[1]+'/'+vals[2]
errorString=""
#TODO move these warning from terminal to GUI and allow for handling through the interface
if not date == submissionDate:
diffDayWarning+=f" {ID:8} : {section:11} : {date:10} : {submissionDate}\n"
if not HF.dateBeforeOrEqual(date,currDate):
futureDayWarning+=f" {ID:8} : {section:11} : {date:10} : {submissionDate}\n"
staffIDs=secD.loc[(secD['Date']==date) & (secD['Section']==section),'Staff0':'Staff9']
staffNum=staffIDs.columns[staffIDs.eq(ID).any()].values
if len(staffNum)==0: #corresponding section entry does not exist
missingWarning+=f" {ID:8} : {section:11} : {date:10} : {submissionDate}\n"
else:
secD.loc[(secD['Date']==date) & (secD['Section']==section),"Hours"+staffNum[0][-1]]=2
sg.popup("The following hours were rejected due to the following reasons:\n",missingWarning,futureDayWarning,diffDayWarning,title="Automatically Rejected Hours",font=defaultFont,keep_on_top=True)
secD.to_csv(sectionDatabaseFilename,index=False,index_label=False)

159
sectionCSVHelper.py Normal file
View File

@ -0,0 +1,159 @@
#sectionCSVHelper.py
#By Drew Murray murraydr@msu.edu
#This script is to assist with the creation of sectionsDatabase.csv
#In theory that CSV can be created manually and might need manual adjustment, but this should make it less tedious
#This code is not made to be especially robust as it is only a tool to assist manual creation/checking and should only need to be used once per semester
#Additionally the goal is to replace the entire function of this code (to convert pasted text from the website) with more direct input to the databases using interfaces yet to be developed
#DO NOT LINK/IMPORT THIS CODE TO THE MAIN SCRIPTS
#We want the headers to be in this order:
# Section,Date,Time,Location,Staff1,Staff2,Staff3,Staff4,Staff5,Staff6,Staff7,Staff8,Staff9
# Section is either "{M/T/W/T/F/S} Helproom (first half)" or "Sec {#}" e.g. "Tu Helproom" or "Sec 14"
# Date is MM/DD
# Time is just for recordkeeping, it is not used by the code
# Location is just for recordkeeping, it is not used by the code
# StaffX is each person associated with that section, order matters only for recordkeeping, the code treats them all the same
import helperFunctions as HF
#-------------------------------------------------------------------------------
#Parameters
leapYear=False
fridayHelproom=False
#-------------------------------------------------------------------------------
#list of dates MM/DD (I filled this out manually based on /admins/once per semester/edit classdays, maybe there's an easier way but it took me only 2 min)
#this code assumes that the first class is always a MW class
with open("startOfSemesterInputs/classDates.txt") as f:
text="".join(f.readlines())
rawDates=text.split('\n')
if '' in rawDates:
rawDates.remove('')
for i in range(len(rawDates)):
rawDates[i]=rawDates[i].split(',')
dates=[rawDates[::2],rawDates[1::2]]
#print("Dates found for MW:\n",dates[0],"\nDates found for TuTh:\n",dates[1])
#-------------------------------------------------------------------------------
#Create a dictionary to relate days of the week to assigned helproom staff
helproomHour1Staff={}
helproomHour2Staff={}
with open("startOfSemesterInputs/helproomHour1Staff.txt") as f:
for line in f:
vals=line.strip('\n').split(',')
helproomHour1Staff[vals[0]]=vals[1:]
with open("startOfSemesterInputs/helproomHour2Staff.txt") as f:
for line in f:
vals=line.strip('\n').split(',')
helproomHour2Staff[vals[0]]=vals[1:]
#-------------------------------------------------------------------------------
#Some code to generate dates and assignments for the helproom sessions
#Assumes that the helproom begins on the first day of class which will be a monday, skips saturdays, and ends 1 day after the last class period (Fri)
#Treats Saturdays as the END of the week (although assigns them to the next week number), and no classes or helprooms on Saturdays
daysOfWeek=['M','T','W','R','F','0','S']
daysPerMonth=[31,28,31,30,31,30,31,31,30,31,30,31]
if leapYear:
daysPerMonth[1]=29
month=int(dates[0][0][0].split('/')[0])
day=int(dates[0][0][0].split('/')[1])
endMonth=int(dates[1][-1][0].split('/')[0])
endDay=int(dates[1][-1][0].split('/')[1])
if month>=10:
endMonth+=12 #wrap around
helprooms=[]
weekday=0 #start with Monday
week=int(dates[0][0][1])
mostRecentDateIdx=0
lastClassElapsed=False
while not lastClassElapsed or weekday!=5: #end on the Friday after the last class
if month>=endMonth and day>=endDay:
lastClassElapsed=True
while month>=int(rawDates[mostRecentDateIdx][0].split('/')[0]) and day>=int(rawDates[mostRecentDateIdx][0].split('/')[1]) and mostRecentDateIdx<len(rawDates)-2:
mostRecentDateIdx+=1
if weekday==5:#if Saturday, update week number
week=int(rawDates[mostRecentDateIdx][1])
else:
if fridayHelproom or weekday != 4:
helprooms.append([daysOfWeek[weekday],str(week),f"{month:02}/{day:02}"])
if weekday==6:#if Sunday, we need to loop back to Monday
weekday=0
else:
weekday+=1
day+=1
if day>daysPerMonth[month-1]:
day=1
month+=1
#-------------------------------------------------------------------------------
#list of section assignments (I pasted the text directly from the web sheet at /admins/once per semester/edit sections)
with open("startOfSemesterInputs/sections.txt") as rf:
with open("sectionsDatabase.csv","w") as wf:
headers = "Section,Week,Date,Time,Location,Staff0,Staff1,Staff2,Staff3,Staff4,Staff5,Staff6,Staff7,Staff8,Staff9,Hours0,Hours1,Hours2,Hours3,Hours4,Hours5,Hours6,Hours7,Hours8,Hours9"
wf.write(str(headers))
for helproom in helprooms:
wf.write("\n"+helproom[0]+"_HR1,"+helproom[1]+","+helproom[2]+","+"07:00 PM-08:00 PM,Zoom,"+','.join(helproomHour1Staff[helproom[0]])+','*(20-len(helproomHour1Staff[helproom[0]]))) #new line BEFORE each line since we already printed the header and don't want one at the end of the file
wf.write("\n"+helproom[0]+"_HR2,"+helproom[1]+","+helproom[2]+","+"08:00 PM-09:00 PM,Zoom,"+','.join(helproomHour2Staff[helproom[0]])+','*(20-len(helproomHour2Staff[helproom[0]]))) #new line BEFORE each line since we already printed the header and don't want one at the end of the file
#this block processes data from the Fall23 spreadsheet on onedrive ("Fall 2023 Staffing and Section Enrollment.xlsx" / "Section Enrollment" tab)
#'''
rf.readline()#skip first line
for line in rf:
vals=line.split('\t')[:-1]
templateLineFront='\n'+'Sec '+vals[0]+',' #new line BEFORE each line since we already printed the header and don't want one at the end of the file
if 'MW' in vals[4]:
dateArrayIdx=0
else:
dateArrayIdx=1
netIDs=[]
for name in vals[10:]:
netIDs.append(HF.nameToID(name.strip(' ')))
if len(vals)>3: #at least 1 staff assigned
templateLineBack=','+vals[5]+','+vals[6].strip(' ')+','+','.join(netIDs)+','*(20-len(netIDs))
for dateIdx in range(len(dates[dateArrayIdx])):#for each date that that section meets
wf.write(templateLineFront+str(dates[dateArrayIdx][dateIdx][1])+','+str(dates[dateArrayIdx][dateIdx][0])+templateLineBack)
#'''
#this block processes data from the website
'''
i=0
for line in rf: #For each section
if "Grader 2" in line or "Grader 3" in line:
continue
else:
line=line[:line.find("Grader")]
vals=line.split('\t')
while '' in vals: vals.remove('') #remove empty string if present
while 'None' in vals: vals.remove('None') #remove empty string if present
vals.remove('QL')
templateLineFront='\n'+vals[0]+',' #new line BEFORE each line since we already printed the header and don't want one at the end of the file
if 'MW' in vals[2]:
dateArrayIdx=0
else:
dateArrayIdx=1
netIDs=[]
for name in vals[3:]:
netIDs.append(HF.nameToID(name.strip(' ')))
if len(vals)>3: #at least 1 staff assigned
templateLineBack=','+vals[2]+','+vals[1]+','+','.join(netIDs)+','*10
for dateIdx in range(len(dates[dateArrayIdx])):#for each date that that section meets
wf.write(templateLineFront+str(dates[dateArrayIdx][dateIdx][1])+','+str(dates[dateArrayIdx][dateIdx][0])+templateLineBack)
i+=1
'''

1717
sectionsDatabase.csv Normal file

File diff suppressed because it is too large Load Diff

1717
sectionsDatabase_Orig.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
"Timestamp","Username","Employee (i.e. your MSU nedID)","Section","Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's ""calendar"" option so use these check boxes instead)"
"2023/04/19 2:12:39 PM AST","cse102msu@gmail.com","alexa521","1","01/18"
"2023/04/19 2:13:28 PM AST","cse102msu@gmail.com","caseale4","1","01/09"
"2023/04/19 2:14:15 PM AST","cse102msu@gmail.com","caseale4","1","01/18"
"2023/04/19 3:59:30 PM AST","cse102msu@gmail.com","manghna1","F helproom","04/14"
"2023/04/19 4:00:19 PM AST","cse102msu@gmail.com","anindhos","F helproom","04/14"
1 Timestamp Username Employee (i.e. your MSU nedID) Section Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's "calendar" option so use these check boxes instead)
2 2023/04/19 2:12:39 PM AST cse102msu@gmail.com alexa521 1 01/18
3 2023/04/19 2:13:28 PM AST cse102msu@gmail.com caseale4 1 01/09
4 2023/04/19 2:14:15 PM AST cse102msu@gmail.com caseale4 1 01/18
5 2023/04/19 3:59:30 PM AST cse102msu@gmail.com manghna1 F helproom 04/14
6 2023/04/19 4:00:19 PM AST cse102msu@gmail.com anindhos F helproom 04/14

View File

@ -0,0 +1,6 @@
"Timestamp","Username","Employee (i.e. your MSU nedID)","Section","Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's ""calendar"" option so use these check boxes instead)"
"2023/04/19 2:12:39 PM AST","cse102msu@gmail.com","alexa521","1","01/18"
"2023/04/19 2:13:28 PM AST","cse102msu@gmail.com","caseale4","1","01/09"
"2023/04/19 2:14:15 PM AST","cse102msu@gmail.com","caseale4","1","01/18"
"2023/04/19 3:59:30 PM AST","cse102msu@gmail.com","manghna1","F helproom","04/14"
"2023/04/19 4:00:19 PM AST","cse102msu@gmail.com","anindhos","F helproom","04/14"
1 Timestamp Username Employee (i.e. your MSU nedID) Section Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's "calendar" option so use these check boxes instead)
2 2023/04/19 2:12:39 PM AST cse102msu@gmail.com alexa521 1 01/18
3 2023/04/19 2:13:28 PM AST cse102msu@gmail.com caseale4 1 01/09
4 2023/04/19 2:14:15 PM AST cse102msu@gmail.com caseale4 1 01/18
5 2023/04/19 3:59:30 PM AST cse102msu@gmail.com manghna1 F helproom 04/14
6 2023/04/19 4:00:19 PM AST cse102msu@gmail.com anindhos F helproom 04/14

View File

@ -0,0 +1,3 @@
"Timestamp","Username","Substitution requestor (i.e. your MSU nedID)","Section","Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's ""calendar"" option so use these check boxes instead)","MSU netID of Intended replacement (if any)","Reason for request (if not already approved)"
"2023/04/18 4:22:45 PM AST","cse102msu@gmail.com","caseale4","1","01/18;01/23","alexa521","sick"
"2023/04/18 4:23:43 PM AST","cse102@gmail.com","sanch489","1","02/08;02/09","","also sick"
1 Timestamp Username Substitution requestor (i.e. your MSU nedID) Section Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's "calendar" option so use these check boxes instead) MSU netID of Intended replacement (if any) Reason for request (if not already approved)
2 2023/04/18 4:22:45 PM AST cse102msu@gmail.com caseale4 1 01/18;01/23 alexa521 sick
3 2023/04/18 4:23:43 PM AST cse102@gmail.com sanch489 1 02/08;02/09 also sick

View File

@ -0,0 +1,3 @@
Substitution requestor (i.e. your MSU nedID),Section,"Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's ""calendar"" option so use these check boxes instead)",MSU netID of Intended replacement (if any),Reason for request (if not already approved)
caseale4,1,01/18;01/23,alexa521,sick
sanch489,1,02/08;02/09,,also sick
1 Substitution requestor (i.e. your MSU nedID) Section Dates (MM/DD) (Google forms does not allow selecting multiple dates with it's "calendar" option so use these check boxes instead) MSU netID of Intended replacement (if any) Reason for request (if not already approved)
2 caseale4 1 01/18;01/23 alexa521 sick
3 sanch489 1 02/08;02/09 also sick

54
spring23/classDates.txt Normal file
View File

@ -0,0 +1,54 @@
01/09,1
01/10,1
01/11,1
01/12,1
01/18,2
01/19,2
01/23,3
01/24,3
01/25,3
01/26,3
01/30,4
01/31,4
02/01,4
02/02,4
02/06,5
02/07,5
02/08,5
02/09,5
02/20,6
02/21,6
02/22,6
02/23,6
02/27,7
02/28,7
03/01,7
03/02,7
03/13,8
03/14,8
03/15,8
03/16,8
03/20,9
03/21,9
03/22,9
03/23,9
03/27,10
03/28,10
03/29,10
03/30,10
04/03,11
04/04,11
04/05,11
04/06,11
04/10,12
04/11,12
04/12,12
04/13,12
04/17,13
04/18,13
04/19,13
04/20,13
04/24,14
04/25,14
04/26,14
04/27,14

View File

@ -0,0 +1,6 @@
S,rajadit1,tagaychr,veerella,fahimmir,mcgui186,wurachel,greissti
M,xuguang3,haggart3,ramseybe,veerella,schne542,shaikhm8
T,rachama2,ramseybe,tagaychr,buglakda,borekmi1,gautamya
W,jongnara,manghna1,caseale4,sipahiog,wurachel,anindhos
R,wijewar2,dagost37,veerella,borekmi1,fahimmir,bhardw41,john7531
F,echeve11,murraydr,honkalac,leduy2,veerella,fahimmir,shaikhm8,bhardw41,manghna1

207
spring23/sections.txt Normal file
View File

@ -0,0 +1,207 @@
1 QL CC 415 MW 08:00 AM-09:50 AM Luis Sanchez Perez Alexandra Case None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
2 QL CC 415 MW 10:20 AM-12:10 PM Tim Greiss Lance Croy None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
3 QL CC 415 MW 12:40 PM-02:30 PM Campbell Robertson Navya Bhardwaj None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
4 QL CC 415 MW 03:00 PM-04:50 PM Campbell Robertson David Neidecker None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
5 QL CC 415 MW 05:00 PM-06:50 PM Peter Oatney Mila Straskraba None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
6 QL CC 415 MW 07:00 PM-08:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
7 QL EBH 211 MW 08:00 AM-09:50 AM Tyler McDonald Doruk Alp Mutlu None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
8 QL EBH 211 MW 10:20 AM-12:10 PM Yash Gautam Natalia Pittendrigh None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
9 QL EBH 211 MW 12:40 PM-02:30 PM Mir Nayeem Islam Fahim Andrew Dagostino None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
10 QL EBH 211 MW 03:00 PM-04:50 PM Jude Cox Aidan Rankin None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
11 QL EBH 211 MW 05:00 PM-06:50 PM Rei McCreight Duy Le None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
12 QL EBH 211 MW 07:00 PM-08:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
13 QL WH B102 MW 08:00 AM-09:50 AM Niya Patel Shane Patrarungrong None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
14 QL WH B102 MW 10:20 AM-12:10 PM Judy Effendi Gregory Zavalnitskiy None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
15 QL WH B102 MW 12:40 PM-02:30 PM Ryann Seymour Luke Antone None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
16 QL WH B102 MW 03:00 PM-04:50 PM Matthew Sebaly Prasanth Peddireddy None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
17 QL WH B102 MW 05:00 PM-06:50 PM Khang Nguyen Lance Croy None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
18 QL CEM 235 MW 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 1
19 QL CEM 235 MW 10:20 AM-12:10 PM Jake Volek Arnav Deol None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 30
20 QL CEM 235 MW 12:40 PM-02:30 PM Ben Alexander Jaylynn Stefanski None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
21 QL CEM 235 MW 03:00 PM-04:50 PM Luis Sanchez Perez Aryan Verma None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
22 QL CEM 235 MW 05:00 PM-06:50 PM Ethan Alexander Michal Borek None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
23 QL EBH 216 MW 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
24 QL SKH 222 MW 10:20 AM-12:10 PM Anvita Gollu Neeyam Muddappa None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
25 QL EBH 216 MW 12:40 PM-02:30 PM Nicole Schneider Harsh Manghnani None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
26 QL EBH 216 MW 03:00 PM-04:50 PM Mannanjeet Dhillon Phillip Yu None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
27 QL EBH 216 MW 05:00 PM-06:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 1
28 QL EBH 216 MW 07:00 PM-08:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
29 QL CC 403 MW 08:00 AM-09:50 AM Alfredo Sanchez Saatvik Palli None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
30 QL CC 403 MW 10:20 AM-12:10 PM Patrick Bourke Alexander Austin None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
31 QL CC 403 MW 12:40 PM-02:30 PM Nicholas Newcomer Omer Sipahioglu None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
32 QL CC 403 MW 03:00 PM-04:50 PM Nicholas Newcomer Koushik Sai Veerella None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
33 QL CC 403 MW 05:00 PM-06:50 PM Soham Sonar Julian Whittaker None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
34 QL CC 415 TR 08:00 AM-09:50 AM Shane Patrarungrong Adam Martin None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 30
35 QL CC 415 TR 10:20 AM-12:10 PM Trinity Johnson Carson Honkala None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
36 QL CC 415 TR 12:40 PM-02:30 PM Trinity Johnson Patrick Bourke None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
37 QL CC 415 TR 03:00 PM-04:50 PM Sonia Moozhayil Tim Kramer None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
38 QL CC 415 TR 05:00 PM-06:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
39 QL EBH 211 TR 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
40 QL EBH 211 TR 10:20 AM-12:10 PM Palmer McGuire Christina Tagay None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
41 QL EBH 211 TR 12:40 PM-02:30 PM Samantha Sebestyen Christina Tagay None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
42 QL EBH 211 TR 03:00 PM-04:50 PM Mir Nayeem Islam Fahim Jisha Goyal None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
43 QL EBH 211 TR 05:00 PM-06:50 PM Krish Magal Barry Hu None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
44 QL WH B102 TR 08:00 AM-09:50 AM Salma Elsaadany Autumn Mellino None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
45 QL WH B102 TR 10:20 AM-12:10 PM Tim Greiss Danny Buglak None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
46 QL WH B102 TR 12:40 PM-02:30 PM Jack Bailey Benjamin Staebler None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
47 QL WH B102 TR 03:00 PM-04:50 PM Jude Cox Zachary Schultz None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
48 QL WH B102 TR 05:00 PM-06:50 PM Emily Rose Zachary Stebbins None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
49 QL CEM 235 TR 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
50 QL CEM 235 TR 10:20 AM-12:10 PM Josh Ilkka Sifatul Anindho None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 30
51 QL CEM 235 TR 12:40 PM-02:30 PM Morghane McAnelly Anika Raghavendra None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
52 QL CEM 235 TR 03:00 PM-04:50 PM Morghane McAnelly Maria Pacifico None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 30
53 QL CEM 235 TR 05:00 PM-06:50 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
54 QL CC 403 TR 08:00 AM-09:50 AM Aidan Haggarty Lillian Yanke None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
55 QL CC 403 TR 10:20 AM-12:10 PM Aidan Haggarty Milan Mihailovic None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 32
56 QL CC 403 TR 12:40 PM-02:30 PM Krish Magal Elaina Frydel None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 30
57 QL CC 403 TR 03:00 PM-04:50 PM Jaisen Shah Benjamin Kim None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 33
58 QL CC 403 TR 05:00 PM-06:50 PM Alfredo Sanchez Hassan Maklai None Grader 1: None
Grader 2: None
Grader 3: None None Active 38 31
99 QL ANH 1210 MW 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
301 QL WH B102 MWF 08:00 AM-09:50 AM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
302 QL WH B102 MWF 10:20 AM-12:10 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
304 QL WH B102 MWF 12:40 PM-02:30 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
399 QL Anywhere MWF 10:20 AM-12:10 PM None None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 0
730 QL Online 1 TR 08:00 AM-09:50 AM Harsh Manghnani Rachel Wu Jisha Goyal Grader 1: Harsh Manghnani
Grader 2: None
Grader 3: None None Active 38 30
731H QL Anywhere MWF 08:00 AM-09:50 AM Harsh Manghnani None None Grader 1: None
Grader 2: None
Grader 3: None None Disabled 38 1
731 QL Online 2 TR 08:00 AM-09:50 AM Harsh Manghnani Rachel Wu Jisha Goyal Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
732 QL Online 3 TR 08:00 AM-09:50 AM Harsh Manghnani Rachel Wu Jisha Goyal Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
733 QL Online 4 TR 08:00 AM-09:50 AM Harsh Manghnani Rachel Wu Jisha Goyal Grader 1: None
Grader 2: None
Grader 3: None None Active 38 29
830H QL Online 5 TR 08:00 AM-09:50 AM Harsh Manghnani Rachel Wu Jisha Goyal Grader 1: None
Grader 2: None
Grader 3: None None Active 38 10

File diff suppressed because it is too large Load Diff

105
spring23/staffDatabase.csv Normal file
View File

@ -0,0 +1,105 @@
Name,Role,NetID
Ben Alexander,Undergraduate Lead (10),alexa521
Ethan Alexander,Undergraduate Lead (10),alexa571
Yolanda Anderson,Instructor (40),ande1989
Sifatul Anindho,Undergraduate Lead (10),anindhos
Luke Antone,Undergraduate Lead (10),antonelu
Alexander Austin,Undergraduate Lead (10),austi143
Jack Bailey,Undergraduate Lead (10),baile411
Navya Bhardwaj,Undergraduate Lead (10),bhardw41
Michal Borek,Undergraduate Lead (10),borekmi1
Patrick Bourke,Undergraduate Lead (10),bourkep2
Danny Buglak,Undergraduate Lead (10),buglakda
Grant Carr,Undergraduate Lead (10),carrgran
Alexandra Case,Undergraduate Lead (10),caseale4
Jude Cox,Undergraduate Lead (10),coxjude
Lance Croy,Undergraduate Lead (10),croylanc
Andrew Dagostino,Undergraduate Lead (10),dagost37
Arnav Deol,Undergraduate Lead (10),deolarna
Mannanjeet Dhillon,Undergraduate Lead (10),dhillo17
Esteban Echeverri,Graduate Assistant (20),echeve11
Judy Effendi,Undergraduate Lead (10),effendi1
Salma Elsaadany,Undergraduate Lead (10),elsaada1
Mir Nayeem Islam Fahim,Undergraduate Lead (10),fahimmir
Elaina Frydel,Undergraduate Lead (10),frydelel
Yash Gautam,Undergraduate Lead (10),gautamya
Anvita Gollu,Undergraduate Lead (10),golluanv
Jisha Goyal,Head GA (10),goyaljis
Thad Greiner,Undergraduate Lead (10),greine30
Tim Greiss,Undergraduate Lead (10),greissti
Aidan Haggarty,Undergraduate Lead (10),haggart3
Brenda Hodge,Course Coordinator (40),hodgeb
Carson Honkala,Undergraduate Lead (10),honkalac
Barry Hu,Undergraduate Lead (10),hubarry
Josh Ilkka,Undergraduate Lead (10),ilkkajos
Trinity Johnson,Undergraduate Lead (10),john7531
Karn Jongnarangsin,Graduate Assistant (20),jongnara
Benjamin Kim,Undergraduate Lead (10),kimbenj3
Amy King,Course Coordinator (40),kingamyn
Tim Kramer,Undergraduate Lead (10),kramert8
Deborah Kruch,Course Coordinator (40),kruch
Amanda Lamp,Undergraduate Lead (10),lampaman
Duy Le,Undergraduate Lead (10),leduy2
Tyler Lovell,Instructor (20),lovellty
Krish Magal,Undergraduate Lead (10),magalkri
Hassan Maklai,Undergraduate Lead (10),maklaiha
Harsh Manghnani,Undergraduate Lead (10),manghna1
Adam Martin,Undergraduate Lead (10),mart2441
Morghane McAnelly,Undergraduate Lead (10),mcanell6
Rei McCreight,Undergraduate Lead (10),mccreig6
Tyler McDonald,Undergraduate Lead (10),mcdon536
Palmer McGuire,Undergraduate Lead (10),mcgui186
Autumn Mellino,Undergraduate Lead (10),mellinoa
Milan Mihailovic,Undergraduate Lead (10),mihailo8
Sonia Moozhayil,Undergraduate Lead (10),moozhayi
Neeyam Muddappa,Undergraduate Lead (10),muddapp1
Drew Murray,Head GA (20),murraydr
Doruk Alp Mutlu,Undergraduate Lead (10),mutludor
David Neidecker,Undergraduate Lead (10),neidecke
Nicholas Newcomer,Undergraduate Lead (10),newcome4
Khang Nguyen,Undergraduate Lead (10),nguy1055
Stacey Nye,Course Coordinator (40),nyesl
Peter Oatney,Undergraduate Lead (10),oatneype
Kevin Ohl,Instructor (40),ohlk
Maria Pacifico,Undergraduate Lead (10),pacific3
Saatvik Palli,Undergraduate Lead (10),pallisaa
Niya Patel,Undergraduate Lead (10),patelniy
Shane Patrarungrong,Undergraduate (10),patrarun
Prasanth Peddireddy,Undergraduate Lead (10),peddire4
Dennis Phillips,Systems Support (20),dennisp
Natalia Pittendrigh,Undergraduate Lead (10),pittend2
Yashashvini Rachamallu,Graduate Assistant (20),rachama2
Anika Raghavendra,Undergraduate Lead (10),raghave2
Aditya Raj,Head GA (20),rajadit1
Ben Ramsey,Undergraduate Lead (10),ramseybe
Aidan Rankin,Undergraduate Lead (10),rankinai
Campbell Robertson,Undergraduate Lead (10),robe1149
Emily Rose,Undergraduate Lead (10),roseemi5
Alfredo Sanchez,Undergraduate Lead (10),sanch488
Luis Sanchez Perez,Undergraduate Lead (10),sanch489
Philip Sands,Instructor (40),phil
Nicole Schneider,Undergraduate Lead (10),schne542
Zachary Schultz,Undergraduate Lead (10),schul769
Matthew Sebaly,Undergraduate Lead (10),sebalyma
Samantha Sebestyen,Undergraduate Lead (10),sebesty1
Ryann Seymour,Undergraduate Lead (10),seymou82
Jaisen Shah,Undergraduate Lead (10),shahjais
Muhammad Miran Shaikh,Undergraduate Lead (10),shaikhm8
Omer Sipahioglu,Undergraduate Lead (10),sipahiog
Soham Sonar,Undergraduate Lead (10),sonarsoh
Benjamin Staebler,Undergraduate Lead (10),staeble6
Zachary Stebbins,Undergraduate (10),stebbi43
Jaylynn Stefanski,Undergraduate Lead (10),stefan69
Mila Straskraba,Undergraduate Lead (10),straskra
Christina Tagay,Undergraduate Lead (10),tagaychr
Koushik Sai Veerella,Undergraduate Lead (10),veerella
Aryan Verma,Undergraduate Lead (10),vermaar1
Jake Volek,Undergraduate Lead (10),volekjac
Julian Whittaker,Undergraduate Lead (10),whitta49
Kanishka Wijewardena,Head GA (20),wijewar2
Rachel Wu,Undergraduate Lead (10),wurachel
Marilyn Wulfekuhler,Instructor (40),wulfekuh
Guangyue Xu,Head GA (20),xuguang3
Lillian Yanke,Undergraduate Lead (10),yankelil
Phillip Yu,Undergraduate Lead (10),yuphilli
Gregory Zavalnitskiy,Undergraduate Lead (10),zavalni1
1 Name Role NetID
2 Ben Alexander Undergraduate Lead (10) alexa521
3 Ethan Alexander Undergraduate Lead (10) alexa571
4 Yolanda Anderson Instructor (40) ande1989
5 Sifatul Anindho Undergraduate Lead (10) anindhos
6 Luke Antone Undergraduate Lead (10) antonelu
7 Alexander Austin Undergraduate Lead (10) austi143
8 Jack Bailey Undergraduate Lead (10) baile411
9 Navya Bhardwaj Undergraduate Lead (10) bhardw41
10 Michal Borek Undergraduate Lead (10) borekmi1
11 Patrick Bourke Undergraduate Lead (10) bourkep2
12 Danny Buglak Undergraduate Lead (10) buglakda
13 Grant Carr Undergraduate Lead (10) carrgran
14 Alexandra Case Undergraduate Lead (10) caseale4
15 Jude Cox Undergraduate Lead (10) coxjude
16 Lance Croy Undergraduate Lead (10) croylanc
17 Andrew Dagostino Undergraduate Lead (10) dagost37
18 Arnav Deol Undergraduate Lead (10) deolarna
19 Mannanjeet Dhillon Undergraduate Lead (10) dhillo17
20 Esteban Echeverri Graduate Assistant (20) echeve11
21 Judy Effendi Undergraduate Lead (10) effendi1
22 Salma Elsaadany Undergraduate Lead (10) elsaada1
23 Mir Nayeem Islam Fahim Undergraduate Lead (10) fahimmir
24 Elaina Frydel Undergraduate Lead (10) frydelel
25 Yash Gautam Undergraduate Lead (10) gautamya
26 Anvita Gollu Undergraduate Lead (10) golluanv
27 Jisha Goyal Head GA (10) goyaljis
28 Thad Greiner Undergraduate Lead (10) greine30
29 Tim Greiss Undergraduate Lead (10) greissti
30 Aidan Haggarty Undergraduate Lead (10) haggart3
31 Brenda Hodge Course Coordinator (40) hodgeb
32 Carson Honkala Undergraduate Lead (10) honkalac
33 Barry Hu Undergraduate Lead (10) hubarry
34 Josh Ilkka Undergraduate Lead (10) ilkkajos
35 Trinity Johnson Undergraduate Lead (10) john7531
36 Karn Jongnarangsin Graduate Assistant (20) jongnara
37 Benjamin Kim Undergraduate Lead (10) kimbenj3
38 Amy King Course Coordinator (40) kingamyn
39 Tim Kramer Undergraduate Lead (10) kramert8
40 Deborah Kruch Course Coordinator (40) kruch
41 Amanda Lamp Undergraduate Lead (10) lampaman
42 Duy Le Undergraduate Lead (10) leduy2
43 Tyler Lovell Instructor (20) lovellty
44 Krish Magal Undergraduate Lead (10) magalkri
45 Hassan Maklai Undergraduate Lead (10) maklaiha
46 Harsh Manghnani Undergraduate Lead (10) manghna1
47 Adam Martin Undergraduate Lead (10) mart2441
48 Morghane McAnelly Undergraduate Lead (10) mcanell6
49 Rei McCreight Undergraduate Lead (10) mccreig6
50 Tyler McDonald Undergraduate Lead (10) mcdon536
51 Palmer McGuire Undergraduate Lead (10) mcgui186
52 Autumn Mellino Undergraduate Lead (10) mellinoa
53 Milan Mihailovic Undergraduate Lead (10) mihailo8
54 Sonia Moozhayil Undergraduate Lead (10) moozhayi
55 Neeyam Muddappa Undergraduate Lead (10) muddapp1
56 Drew Murray Head GA (20) murraydr
57 Doruk Alp Mutlu Undergraduate Lead (10) mutludor
58 David Neidecker Undergraduate Lead (10) neidecke
59 Nicholas Newcomer Undergraduate Lead (10) newcome4
60 Khang Nguyen Undergraduate Lead (10) nguy1055
61 Stacey Nye Course Coordinator (40) nyesl
62 Peter Oatney Undergraduate Lead (10) oatneype
63 Kevin Ohl Instructor (40) ohlk
64 Maria Pacifico Undergraduate Lead (10) pacific3
65 Saatvik Palli Undergraduate Lead (10) pallisaa
66 Niya Patel Undergraduate Lead (10) patelniy
67 Shane Patrarungrong Undergraduate (10) patrarun
68 Prasanth Peddireddy Undergraduate Lead (10) peddire4
69 Dennis Phillips Systems Support (20) dennisp
70 Natalia Pittendrigh Undergraduate Lead (10) pittend2
71 Yashashvini Rachamallu Graduate Assistant (20) rachama2
72 Anika Raghavendra Undergraduate Lead (10) raghave2
73 Aditya Raj Head GA (20) rajadit1
74 Ben Ramsey Undergraduate Lead (10) ramseybe
75 Aidan Rankin Undergraduate Lead (10) rankinai
76 Campbell Robertson Undergraduate Lead (10) robe1149
77 Emily Rose Undergraduate Lead (10) roseemi5
78 Alfredo Sanchez Undergraduate Lead (10) sanch488
79 Luis Sanchez Perez Undergraduate Lead (10) sanch489
80 Philip Sands Instructor (40) phil
81 Nicole Schneider Undergraduate Lead (10) schne542
82 Zachary Schultz Undergraduate Lead (10) schul769
83 Matthew Sebaly Undergraduate Lead (10) sebalyma
84 Samantha Sebestyen Undergraduate Lead (10) sebesty1
85 Ryann Seymour Undergraduate Lead (10) seymou82
86 Jaisen Shah Undergraduate Lead (10) shahjais
87 Muhammad Miran Shaikh Undergraduate Lead (10) shaikhm8
88 Omer Sipahioglu Undergraduate Lead (10) sipahiog
89 Soham Sonar Undergraduate Lead (10) sonarsoh
90 Benjamin Staebler Undergraduate Lead (10) staeble6
91 Zachary Stebbins Undergraduate (10) stebbi43
92 Jaylynn Stefanski Undergraduate Lead (10) stefan69
93 Mila Straskraba Undergraduate Lead (10) straskra
94 Christina Tagay Undergraduate Lead (10) tagaychr
95 Koushik Sai Veerella Undergraduate Lead (10) veerella
96 Aryan Verma Undergraduate Lead (10) vermaar1
97 Jake Volek Undergraduate Lead (10) volekjac
98 Julian Whittaker Undergraduate Lead (10) whitta49
99 Kanishka Wijewardena Head GA (20) wijewar2
100 Rachel Wu Undergraduate Lead (10) wurachel
101 Marilyn Wulfekuhler Instructor (40) wulfekuh
102 Guangyue Xu Head GA (20) xuguang3
103 Lillian Yanke Undergraduate Lead (10) yankelil
104 Phillip Yu Undergraduate Lead (10) yuphilli
105 Gregory Zavalnitskiy Undergraduate Lead (10) zavalni1

88
staffDatabase.csv Normal file
View File

@ -0,0 +1,88 @@
Name,Role,NetID,Returning,Approved Substitutions,Accepted Substitutions,Rejected Substitutions,Cancelled Substitutions,Fulfilled Substitutions
Aditya Aggarwal,ula,aggarw75,0,0,0,0,0,0
Jonas Ahonen,ula,ahonenj1,0,0,0,0,0,0
Neha Aigalikar,ula,aigalik1,0,0,0,0,0,0
Ethan Alexander,ula,alexa571,1,0,0,0,0,0
Elizabeth Allard,ula,allarde2,0,0,0,0,0,0
Anh Dao,ula,anhdao,0,0,0,0,0,0
Sifatul Anindho,ula,anindhos,1,0,0,0,0,0
Alexander Austin,ula,austi143,1,0,0,0,0,0
Bidhan Bashyal,ga,bashyalb,0,0,0,0,0,0
Navya Bhardwaj,ula,bhardw41,1,0,0,0,0,0
Michal Borek,ula,borekmi1,1,0,0,0,0,0
Jaelin Burge,ula,burgejae,0,0,0,0,0,0
Byungho,ga,byungho,0,0,0,0,0,0
Juan Carlier Blanco,ula,carlier1,0,0,0,0,0,0
Grant Carr,ula,carrgran,0,0,0,0,0,0
Jude Cox,ula,coxjude,1,0,0,0,0,0
Lance Croy,ula,croylanc,1,0,0,0,0,0
Vibu Darshan,ula,darshanv,0,0,0,0,0,0
Mannan Dhillon,ula,dhillo17,1,0,0,0,0,0
Mir Fahim,ula,fahimmir,1,0,0,0,0,0
Yash Gautam,ula,gautamya,1,0,0,0,0,0
Ravi Gangaiahanadoddi,ula,gkravi,0,0,0,0,0,0
Keerthi Gogineni,ga,goginen8,0,0,0,0,0,0
Jisha Goyal,ula,goyaljis,1,0,0,0,0,0
Mayank Gudi,ula,gudimaya,0,0,0,0,0,0
Sammy Guo,ula,guoqian2,0,0,0,0,0,0
Vikram Guruswamy,ula,guruswa1,0,0,0,0,0,0
Aidan Haggarty,ula,haggart3,1,0,0,0,0,0
Mohid Imran,ula,imranmoh,0,0,0,0,0,0
Trinity Johnson,ula,john7531,1,0,0,0,0,0
Eshwar Kanakasabai,ula,kanakas1,0,0,0,0,0,0
Kara Kerzel,ula,kerzelka,0,0,0,0,0,0
Prijam Khanal,ula,khanalpr,0,0,0,0,0,0
Benjamin Kim,ula,kimbenj3,0,0,0,0,0,0
Michael Kleinbriel,ula,kleinb14,0,0,0,0,0,0
Arnas Kumar,ula,kumararn,0,0,0,0,0,0
Duy Le,ula,leduy2,1,0,0,0,0,0
Mehak,ula,lnumehak,0,0,0,0,0,0
Julieta Lopez,ula,lopezj35,0,0,0,0,0,0
Brendan Mackey,ula,mackeyb3,0,0,0,0,0,0
Hassan Maklai,ula,maklaiha,1,0,0,0,0,0
Harsh Rakesh Manghnani,ula,manghna1,1,0,0,0,0,0
Rei Mccreight,ula,mccreig6,1,0,0,0,0,0
Tyler Mcdonald,ula,mcdon536,1,0,0,0,0,0
Palmer Mcguire,ula,mcgui186,1,0,0,0,0,0
Joey Meng,ga,mengjoey,0,0,0,0,0,0
Sophia Mick,ula,micksoph,0,0,0,0,0,0
Sujoy Mondol,ula,mondolsu,0,0,0,0,0,0
Drew Murray,ga,murraydr,1,0,0,0,0,0
Tawsif Imam Nadif,ula,nadiftaw,0,0,0,0,0,0
Achint Nagra,ula,nagraach,0,0,0,0,0,0
David Neidecker,ula,neidecke,1,0,0,0,0,0
Bach Nguyen,ula,nguy1104,0,0,0,0,0,0
Vivian Ng,ula,ngvivian,0,0,0,0,0,0
Peter Oatney,ula,oatneype,1,0,0,0,0,0
Mike Paterala,ula,paterala,1,0,0,0,0,0
Sofia Perez Rodriguez,ula,perezso1,0,0,0,0,0,0
Natalia Pittendrigh,ula,pittend2,1,0,0,0,0,0
Yashashvini,ga,rachama2,1,0,0,0,0,0
Tasmia Rahman,ula,rahman64,0,0,0,0,0,0
Alfredo Sanchez,ula,sanch488,1,0,0,0,0,0
Brixon Schmiesing,ula,schmiesi,0,0,0,0,0,0
Nicole Schneider,ula,schne542,1,0,0,0,0,0
Zachary Schultz,ula,schul769,1,0,0,0,0,0
Jordan Sebagh,ula,sebaghjo,0,0,0,0,0,0
Matthew Sebaly,ula,sebalyma,1,0,0,0,0,0
Tejas Singhal,ula,singhalt,0,0,0,0,0,0
Shashank Singh,ula,singhs65,0,0,0,0,0,0
Omer Salih Sipahioglu,ula,sipahiog,1,0,0,0,0,0
Soham Sonar,ula,sonarsoh,1,0,0,0,0,0
Zach Stebbins,ula,stebbi43,1,0,0,0,0,0
Jaylynn Stefanski,ula,stefan69,1,0,0,0,0,0
Mila Straskraba,ula,straskra,1,0,0,0,0,0
Efim Strumban,ula,strumban,0,0,0,0,0,0
Christina Tagay,ula,tagaychr,0,0,0,0,0,0
Shreyas Tenkayala,ula,tenkayal,0,0,0,0,0,0
Ece Ugurlu,ula,ugurluec,0,0,0,0,0,0
Aanshik Upadhyay,ula,upadhy19,0,0,0,0,0,0
Noel Vazquez,ula,vazque72,0,0,0,0,0,0
Koushik Sai Veerella,ula,veerella,1,0,0,0,0,0
Krish Wadhwani,ula,wadhwan5,0,0,0,0,0,0
Julian Whittaker,ula,whitta49,1,0,0,0,0,0
Sophie Xu,ula,xusophie,0,0,0,0,0,0
Blenda Yan,ula,yanblend,0,0,0,0,0,0
Emma Yaney,ula,yaneyemm,0,0,0,0,0,0
Lilly Yanke,ula,yankelil,1,0,0,0,0,0
Erika Zheng,ga,zhengyil,0,0,0,0,0,0
1 Name Role NetID Returning Approved Substitutions Accepted Substitutions Rejected Substitutions Cancelled Substitutions Fulfilled Substitutions
2 Aditya Aggarwal ula aggarw75 0 0 0 0 0 0
3 Jonas Ahonen ula ahonenj1 0 0 0 0 0 0
4 Neha Aigalikar ula aigalik1 0 0 0 0 0 0
5 Ethan Alexander ula alexa571 1 0 0 0 0 0
6 Elizabeth Allard ula allarde2 0 0 0 0 0 0
7 Anh Dao ula anhdao 0 0 0 0 0 0
8 Sifatul Anindho ula anindhos 1 0 0 0 0 0
9 Alexander Austin ula austi143 1 0 0 0 0 0
10 Bidhan Bashyal ga bashyalb 0 0 0 0 0 0
11 Navya Bhardwaj ula bhardw41 1 0 0 0 0 0
12 Michal Borek ula borekmi1 1 0 0 0 0 0
13 Jaelin Burge ula burgejae 0 0 0 0 0 0
14 Byungho ga byungho 0 0 0 0 0 0
15 Juan Carlier Blanco ula carlier1 0 0 0 0 0 0
16 Grant Carr ula carrgran 0 0 0 0 0 0
17 Jude Cox ula coxjude 1 0 0 0 0 0
18 Lance Croy ula croylanc 1 0 0 0 0 0
19 Vibu Darshan ula darshanv 0 0 0 0 0 0
20 Mannan Dhillon ula dhillo17 1 0 0 0 0 0
21 Mir Fahim ula fahimmir 1 0 0 0 0 0
22 Yash Gautam ula gautamya 1 0 0 0 0 0
23 Ravi Gangaiahanadoddi ula gkravi 0 0 0 0 0 0
24 Keerthi Gogineni ga goginen8 0 0 0 0 0 0
25 Jisha Goyal ula goyaljis 1 0 0 0 0 0
26 Mayank Gudi ula gudimaya 0 0 0 0 0 0
27 Sammy Guo ula guoqian2 0 0 0 0 0 0
28 Vikram Guruswamy ula guruswa1 0 0 0 0 0 0
29 Aidan Haggarty ula haggart3 1 0 0 0 0 0
30 Mohid Imran ula imranmoh 0 0 0 0 0 0
31 Trinity Johnson ula john7531 1 0 0 0 0 0
32 Eshwar Kanakasabai ula kanakas1 0 0 0 0 0 0
33 Kara Kerzel ula kerzelka 0 0 0 0 0 0
34 Prijam Khanal ula khanalpr 0 0 0 0 0 0
35 Benjamin Kim ula kimbenj3 0 0 0 0 0 0
36 Michael Kleinbriel ula kleinb14 0 0 0 0 0 0
37 Arnas Kumar ula kumararn 0 0 0 0 0 0
38 Duy Le ula leduy2 1 0 0 0 0 0
39 Mehak ula lnumehak 0 0 0 0 0 0
40 Julieta Lopez ula lopezj35 0 0 0 0 0 0
41 Brendan Mackey ula mackeyb3 0 0 0 0 0 0
42 Hassan Maklai ula maklaiha 1 0 0 0 0 0
43 Harsh Rakesh Manghnani ula manghna1 1 0 0 0 0 0
44 Rei Mccreight ula mccreig6 1 0 0 0 0 0
45 Tyler Mcdonald ula mcdon536 1 0 0 0 0 0
46 Palmer Mcguire ula mcgui186 1 0 0 0 0 0
47 Joey Meng ga mengjoey 0 0 0 0 0 0
48 Sophia Mick ula micksoph 0 0 0 0 0 0
49 Sujoy Mondol ula mondolsu 0 0 0 0 0 0
50 Drew Murray ga murraydr 1 0 0 0 0 0
51 Tawsif Imam Nadif ula nadiftaw 0 0 0 0 0 0
52 Achint Nagra ula nagraach 0 0 0 0 0 0
53 David Neidecker ula neidecke 1 0 0 0 0 0
54 Bach Nguyen ula nguy1104 0 0 0 0 0 0
55 Vivian Ng ula ngvivian 0 0 0 0 0 0
56 Peter Oatney ula oatneype 1 0 0 0 0 0
57 Mike Paterala ula paterala 1 0 0 0 0 0
58 Sofia Perez Rodriguez ula perezso1 0 0 0 0 0 0
59 Natalia Pittendrigh ula pittend2 1 0 0 0 0 0
60 Yashashvini ga rachama2 1 0 0 0 0 0
61 Tasmia Rahman ula rahman64 0 0 0 0 0 0
62 Alfredo Sanchez ula sanch488 1 0 0 0 0 0
63 Brixon Schmiesing ula schmiesi 0 0 0 0 0 0
64 Nicole Schneider ula schne542 1 0 0 0 0 0
65 Zachary Schultz ula schul769 1 0 0 0 0 0
66 Jordan Sebagh ula sebaghjo 0 0 0 0 0 0
67 Matthew Sebaly ula sebalyma 1 0 0 0 0 0
68 Tejas Singhal ula singhalt 0 0 0 0 0 0
69 Shashank Singh ula singhs65 0 0 0 0 0 0
70 Omer Salih Sipahioglu ula sipahiog 1 0 0 0 0 0
71 Soham Sonar ula sonarsoh 1 0 0 0 0 0
72 Zach Stebbins ula stebbi43 1 0 0 0 0 0
73 Jaylynn Stefanski ula stefan69 1 0 0 0 0 0
74 Mila Straskraba ula straskra 1 0 0 0 0 0
75 Efim Strumban ula strumban 0 0 0 0 0 0
76 Christina Tagay ula tagaychr 0 0 0 0 0 0
77 Shreyas Tenkayala ula tenkayal 0 0 0 0 0 0
78 Ece Ugurlu ula ugurluec 0 0 0 0 0 0
79 Aanshik Upadhyay ula upadhy19 0 0 0 0 0 0
80 Noel Vazquez ula vazque72 0 0 0 0 0 0
81 Koushik Sai Veerella ula veerella 1 0 0 0 0 0
82 Krish Wadhwani ula wadhwan5 0 0 0 0 0 0
83 Julian Whittaker ula whitta49 1 0 0 0 0 0
84 Sophie Xu ula xusophie 0 0 0 0 0 0
85 Blenda Yan ula yanblend 0 0 0 0 0 0
86 Emma Yaney ula yaneyemm 0 0 0 0 0 0
87 Lilly Yanke ula yankelil 1 0 0 0 0 0
88 Erika Zheng ga zhengyil 0 0 0 0 0 0

View File

@ -0,0 +1 @@
Timestamp,Requestor,Section,Dates,Replacement,Reason,Result,InstructorReason
1 Timestamp Requestor Section Dates Replacement Reason Result InstructorReason

View File

@ -0,0 +1,56 @@
08/28,1
08/29,1
08/30,1
08/31,1
09/04,2
09/05,2
09/06,2
09/07,2
09/11,3
09/12,3
09/13,3
09/14,3
09/18,4
09/19,4
09/20,4
09/21,4
09/25,5
09/26,5
09/27,5
09/28,5
10/02,6
10/03,6
10/04,6
10/05,6
10/09,7
10/10,7
10/11,7
10/12,7
10/16,8
10/17,8
10/18,8
10/19,8
10/25,9
10/26,9
10/30,10
10/31,10
11/01,10
11/02,10
11/06,11
11/07,11
11/08,11
11/09,11
11/13,12
11/14,12
11/15,12
11/16,12
11/20,13
11/21,13
11/27,14
11/28,14
11/29,14
11/30,14
12/04,15
12/05,15
12/06,15
12/07,15

View File

@ -0,0 +1,6 @@
S,bashyalb,allarde2,burgejae,carlier1,fahimmir,sipahiog,john7531
M,murraydr,goginen8,anhdao,kumararn,nguy1104,veerella,fahimmir,anindhos
T,mengjoey,rachama2,kumararn,strumban,fahimmir,sipahiog,mcgui186,sonarsoh
W,zhengyil,nguy1104,veerella,fahimmir,sipahiog,mcgui186
R,byungho,leduy2,strumban,fahimmir,mcgui186,veerella
F,

View File

@ -0,0 +1,6 @@
S,bashyalb,allarde2,burgejae,carlier1,fahimmir,sipahiog,john7531
M,murraydr,goginen8,kumararn,veerella,fahimmir,anindhos
T,mengjoey,rachama2,kumararn,strumban,fahimmir,sipahiog,mcgui186,sonarsoh
W,zhengyil,nguy1104,veerella,fahimmir,sipahiog,mcgui186
R,byungho,leduy2,strumban,fahimmir,mcgui186,veerella
F,

View File

@ -0,0 +1,57 @@
Section Status Enrolled Limit Days Times Location Seats in the room extra capacity Staff 1 Staff 2 Section
1 open 30 32 MW 8:00 AM-9:50 AM 415 Computer Center 32 2 Michal B Ethan 1
2 open 30 32 MW 10:20 AM-12:10 PM 415 Computer Center 32 2 Alfredo Vivian 2
3 open 29 32 MW 12:40 PM-2:30 PM 415 Computer Center 32 3 Alfredo Tawsif 3
4 open 29 32 MW 3:00 PM-4:50 PM 415 Computer Center 32 3 Aidan Aanshik 4
5 open 29 32 MW 5:00 PM-6:50 PM 415 Computer Center 32 3 Mir Tasmia 5
6 full 30 30 TR 3:00 PM-4:50 PM 235 Chemistry 30 0 Palmer Vikram 6
7 open 30 32 MW 8:00 AM-9:50 AM 211 Ernst Bessey Hall 32 2 Koushik Sofia P 7
8 open 30 32 MW 10:20 AM-12:10 PM 211 Ernst Bessey Hall 32 2 Koushik Sammy 8
9 open 30 32 MW 12:40 PM-2:30 PM 211 Ernst Bessey Hall 32 2 Jude Eshwar 9
10 open 30 32 MW 3:00 PM-4:50 PM 211 Ernst Bessey Hall 32 2 Noel Julieta 10
11 open 29 32 MW 5:00 PM-6:50 PM 211 Ernst Bessey Hall 32 3 Alexander Austin Aditya 11
12 open 30 32 TR 8:00 AM-9:50 AM B102 Wells Hall 32 2 Peter Kara 12
13 open 30 32 MW 8:00 AM-9:50 AM B102 Wells Hall 32 2 Tyler Emma 13
14 open 30 32 MW 10:20 AM-12:10 PM B102 Wells Hall 32 2 Hassan Blenda 14
15 open 30 32 MW 12:40 PM-2:30 PM B102 Wells Hall 32 2 Harsh Sujoy 15
16 open 29 32 MW 3:00 PM-4:50 PM B102 Wells Hall 32 3 Harsh Ravi 16
17 open 29 32 MW 5:00 PM-6:50 PM B102 Wells Hall 32 3 Soham Efim 17
18 open 29 32 MW 8:00 AM-9:50 AM 403 Computer Center 30 3 Lance Anh 18
19 full 30 30 MW 10:20 AM-12:10 PM 235 Chemistry 30 0 Nicole Mehak 19
20 full 30 30 MW 12:40 PM-2:30 PM 235 Chemistry 30 0 Rei Vibu 20
21 full 30 30 MW 3:00 PM-4:50 PM 235 Chemistry 30 0 David Yash 21
22 open 29 30 MW 5:00 PM-6:50 PM 403 Computer Center 30 1 Mila Krish 22
23 open 30 32 TR 3:00 PM-4:50 PM 403 Computer Center 32 2 Sifat Michael K 23
24 open 30 32 MW 10:20 AM-12:10 PM S222 South Kedzie 32 2 Mila Tejas 24
25 open 30 32 MW 12:40 PM-2:30 PM S222 South Kedzie 32 2 Natalia Brixon 25
26 open 29 32 MW 3:00 PM-4:50 PM S222 South Kedzie 32 3 Lilly Shashank 26
27 open 28 32 MW 5:00 PM-6:50 PM 235 Chemistry 32 4 Duy Sophia M 27
28 closed 0 0 MW 8:00 AM-9:50 AM 403 Computer Center 32 0 28
29 closed 0 0 MW 8:00 AM-9:50 AM S222 South Kedzie 32 0 29
30 open 30 32 MW 10:20 AM-12:10 PM 403 Computer Center 32 2 Omer Juan 30
31 open 30 32 MW 12:40 PM-2:30 PM 403 Computer Center 32 2 Aidan Jordan 31
32 open 30 32 MW 3:00 PM-4:50 PM 403 Computer Center 32 2 Jisha Zach 32
33 closed 0 0 MW 5:00 PM-6:50 PM B102 Wells Hall 32 0 33
34 open 28 32 TR 8:00 AM-9:50 AM 415 Computer Center 32 4 Mike P Elizabeth 34
35 open 30 32 TR 10:20 AM-12:10 PM 415 Computer Center 32 2 Navya Ece 35
36 open 29 32 TR 12:40 PM-2:30 PM 415 Computer Center 32 3 Christina Brendan 36
37 open 30 32 TR 3:00 PM-4:50 PM 415 Computer Center 32 2 Christina Shreyas 37
38 closed 0 0 TR 5:00 PM-6:50 PM 415 Computer Center 32 0 38
39 closed 0 0 TR 8:00 AM-9:50 AM 211 Ernst Bessey Hall 32 0 39
40 open 30 32 TR 10:20 AM-12:10 PM 211 Ernst Bessey Hall 32 2 Matt Neha 40
41 open 30 32 TR 12:40 PM-2:30 PM 211 Ernst Bessey Hall 32 2 Ben Mayank 41
42 open 30 32 TR 3:00 PM-4:50 PM 211 Ernst Bessey Hall 32 2 Jaylynn Sophie X 42
43 closed 0 0 TR 5:00 PM-6:50 PM 211 Ernst Bessey Hall 32 0 43
44 closed 0 0 TR 8:00 AM-9:50 AM B102 Wells Hall 32 0 44
45 open 30 32 TR 10:20 AM-12:10 PM B102 Wells Hall 32 2 Peter Bach 45
46 open 30 32 TR 12:40 PM-2:30 PM B102 Wells Hall 32 2 Omer Jaelin 46
47 open 30 32 TR 3:00 PM-4:50 PM B102 Wells Hall 32 2 Grant Achint 47
48 closed 0 0 TR 5:00 PM-6:50 PM B102 Wells Hall 32 0 48
49 closed 0 0 TR 8:00 AM-9:50 AM 235 Chemistry 30 0 49
50 full 30 30 TR 10:20 AM-12:10 PM 235 Chemistry 30 0 Mannan Arnas 50
51 full 30 30 TR 12:40 PM-2:30 PM 235 Chemistry 30 0 Julian Prijam 51
52 closed 0 0 TR 3:00 PM-4:50 PM 235 Chemistry 30 0 52
53 closed 0 0 TR 5:00 PM-6:50 PM 235 Chemistry 30 0 53
54 closed 0 0 TR 8:00 AM-9:50 AM 403 Computer Center 32 0 54
55 open 30 32 TR 10:20 AM-12:10 PM 403 Computer Center 32 2 Trinity Jonas 55
56 open 30 32 TR 12:40 PM-2:30 PM 403 Computer Center 32 2 Trinity Mohid 56

View File

@ -0,0 +1,256 @@
import PySimpleGUI as sg
import csv
import pandas as pd
import helperFunctions as HF
import substitutionHistoryWindow as SHW
import substitutionManualWindow as SMW
#Printing options for Pandas (for debugging)
pd.set_option('display.min_rows', 20)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', 20)
def subAppWin(staffDatabaseFilename,secDFilename,subRequestsFilename,subRequestsArchiveFilename,defaultFont=("Courier",11)):
#read in sub requests from the provided csv filepath
subRequests=[]
with open(subRequestsFilename) as f:
headerLine=f.readline()
reader = csv.reader(f)
for row in reader:
subRequests.append(row+[""])
#format fields is Requester, Period, Date, Replacement, Reason, Times, CurrAssigned, Errors
columnWidths=[20,16,16,20,30,50,50,20]
#Set up layout object that governs what appears in the window
headerVals=['Requestor','Period(s)','Date(s)','Replacement','Reason','(Frequency) Times','Currently Assigned','Errors']
row=[sg.Text('APP',size=(4,1)),sg.Text('ACC',size=(4,1)),sg.Text('REJ',size=(4,1)),sg.Text('CAN',size=(4,1))]
for i in range(len(headerVals)):
row.append(sg.Text(headerVals[i],expand_x=True,expand_y=True,size=(columnWidths[i],1)))
layout=[row]
#track how many requests are for a specific timeslot
datetimeFreq={}
for i in range(len(subRequests)):
oldID=subRequests[i][1]
periods=subRequests[i][2].split(';')
dates=subRequests[i][3].split(';')
newID=subRequests[i][4]
for period in periods:
for date in dates:
datetime=date+" "+HF.getTimeFromSection(period)
if datetime in datetimeFreq:
datetimeFreq[datetime]+=1
else:
datetimeFreq[datetime]=1
if not HF.shiftExists(period,date):
subRequests[i][6]+="No such shift\n"
else:
if not HF.isAssigned(oldID,period,date):
subRequests[i][6]+=oldID+" not assigned\n"
if newID != "" and HF.isAssigned(newID,period,date):
subRequests[i][6]+=newID+" already assigned\n"
for i in range(len(subRequests)):
request=subRequests[i]
netID=request[1]
periods=request[2]
if request[4]=='':
newSubName="NONE"
else:
newSubName=HF.IDToName(request[4])
others=HF.getAllNamesFromSection(periods,request[3])
times=[]
for period in periods.split(';'):
time=HF.getTimeFromSection(period)
times.append(time)
timeStr="("
periods=request[2].split(';')
dates=request[3].split(';')
for period in periods:
for date in dates:
datetime=date+" "+HF.getTimeFromSection(period)
timeStr+=str(datetimeFreq[datetime])+","
timeStr=timeStr[:-1]+") "
for period in periods:
for date in dates:
timeStr+=date+" "+str(HF.getTimeFromSection(period))+"; "
timeStr=timeStr[:-2]
others=str(others)[1:-1]
others=others.replace("'","")
#others=others.replace("],","]\n"+" "*(sum(columnWidths[:6])+16)+"|")
textVals=[HF.IDToName(netID),*request[2:4],newSubName,request[5],timeStr,others,request[6].strip("\n")]
row=[sg.Column([[sg.Checkbox('',size=(1,1)),sg.Checkbox('',size=(1,1)),sg.Checkbox('',size=(1,1)),sg.Checkbox('',size=(1,1))],[sg.Input("INST. REASON",size=(12,1),key="-INPUT_REASON_"+str(i)+"-")]])]
if request[6]!="":
color='#FF9999'
else:
color='white'
for i in range(len(textVals)):
row.append(sg.Text(textVals[i],expand_x=True,expand_y=True,size=(columnWidths[i],3),text_color=color))
layout.append(row)
layout.append([sg.Text('-'*(sum(columnWidths)+27))])
layout.append([sg.Button('Ok'), sg.Button('Cancel'), sg.Button('See Sub History'), sg.Button('Make Manual Changes')])
layout=[[sg.Column(layout,scrollable=True,expand_y=True,expand_x=True)]]
# Create the Window
window = sg.Window('Substitution Requests', layout, font=defaultFont,resizable=True,size=(1900,1000))
#Event Loop to process "events" and get the "values" of the inputs
#Cease running if they close the window or press OK or Cancel
completed=False
event=-1
while event != sg.WIN_CLOSED and completed==False:
event, values = window.read()
approveValues=list(values.values())[::5]
acceptValues=list(values.values())[1::5]
rejectValues=list(values.values())[2::5]
cancelValues=list(values.values())[3::5]
#Number of approved changes
nApp = sum([1 for d in approveValues if True == d])
nAcc = sum([1 for d in acceptValues if True == d])
nRej = sum([1 for d in rejectValues if True == d])
nCan = sum([1 for d in cancelValues if True == d])
if event == "See Sub History":
SHW.subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont)
if event == "Make Manual Changes":
window['Ok'].Update(disabled=True) #This is to prevent undefined behavior where manual changes are not represented in the error-checking of the main window
SMW.subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont)
event = "Cancel"
if event == 'Ok':
valid=True
dialogShown = False #Do not show multiple error dialogues even if there are multiple problems.
#If two responses are checked in ANY single line, then it is not valid input
if True in [True for i, j, k, l in zip(approveValues, acceptValues, rejectValues, cancelValues) if ((i and j) or (i and k) or (j and k) or (i and l) or (j and l) or (k and l))]:
valid=False
if not dialogShown:
dialogShown=True
multiResponseLayout = [[sg.Text('ERROR: You have two different responses checked for at least one substitution')],[sg.Button('Ok')]]
multiResponseWindow = sg.Window('Multiple Responses Selected',multiResponseLayout,font=defaultFont)
multiResponseEvent = -1
while multiResponseEvent != sg.WIN_CLOSED and multiResponseEvent != 'Ok':
multiResponseEvent,multiResponseValues=multiResponseWindow.read()
multiResponseWindow.close()
allNetIDs = []
otherReqCache={}
for i in range(len(subRequests)):
if approveValues[i] or acceptValues[i]:
if subRequests[i][6] != "":
valid=False
if not dialogShown:
dialogShown=True
errorLayout = [[sg.Text('ERROR: You have approved or accepted a request that has an error.\n(See rightmost colum)')],[sg.Button('Ok')]]
errorWindow = sg.Window('Invalid Request App/Acc',errorLayout,font=defaultFont)
errorEvent = -1
while errorEvent != sg.WIN_CLOSED and errorEvent != 'Ok':
errorEvent,errorValues=errorWindow.read()
errorWindow.close()
#Check that the request doesn't overlap another approved request (as that would create a 'race' condition based on request submission order)
netID=subRequests[i][1]
if netID in allNetIDs:#For performance reasons, only check for section/date overlap between requests from the same person (to avoid n^2 searching)
for j in range(len(otherReqCache[netID])):
if HF.isRequestOverlap(subRequests[i],otherReqCache[netID][j]):#this request overlaps with a another request by the same person
valid=False
if not dialogShown:
dialogShown=True
multiRequestLayout = [[sg.Text('WARNING: You have simultaneously approved multiple overlapping requests from '+HF.IDToName(netID)+'.\nThis can produce ambiguous states and is not supported.\nApprove or accept the requests one at a time.')],[sg.Button('Ok')]]
multiRequestWindow = sg.Window('Multiple Requests Approved',multiRequestLayout,font=defaultFont)
multiRequestEvent = -1
while multiRequestEvent != sg.WIN_CLOSED and multiRequestEvent != 'Ok':
multiRequestEvent,multiRequestValues=multiRequestWindow.read()
multiRequestWindow.close()
else:#This request is by the same person but does not overlap
otherReqCache[netID].append(subRequests[i])
else:#there are no other outstanding requests by this person
allNetIDs.append(netID)
otherReqCache[netID]=[subRequests[i]]
#At least one change was approved, check that they didn't click in error:
if nApp+nAcc+nRej+nCan>0 and valid:
verifyLayout = [[sg.Text('Are you sure?\nThis action will make '+str(nApp+nAcc)+' changes to the sectionsDatabase and delete '+str(nApp+nAcc+nRej+nCan)+' sub requests\n***This cannot be undone!***')],[sg.Button('Yes'), sg.Button('No')]]
verifyWindow = sg.Window('Verify Changes', verifyLayout, font=defaultFont)
verifyEvent=-1
while not (verifyEvent == sg.WIN_CLOSED or verifyEvent == 'Yes' or verifyEvent == 'No'):
verifyEvent, verifyValues = verifyWindow.read()
verifyWindow.close()
#Apply changes to the database if they chose "yes"
if verifyEvent == 'Yes':
trimmedSubrequests = [] #the subRequests after all accepted and rejected have been deleted
resolvedSubrequests = [] #the subRequests which were aproved/accepted/rejected
for i in range(len(subRequests)):
oldID=subRequests[i][1]
periods=subRequests[i][2].split(';')
dates=subRequests[i][3].split(';')
newID=subRequests[i][4]
for date in dates:
for period in periods:
if "HR" in period:
amount = 0.5
else:
amount = 1
if approveValues[i] or acceptValues[i]: #If this request was checked "approved" or "accepted"
HF.reassign(date,period,oldID,newID)
if newID !="":
HF.incrementSubCount(newID,4,amount=amount)
if approveValues[i]:
HF.incrementSubCount(oldID,0,amount=amount)
if acceptValues[i]:
HF.incrementSubCount(oldID,1,amount=amount)
if rejectValues[i]:
HF.incrementSubCount(oldID,2,amount=amount)
if cancelValues[i]:
HF.incrementSubCount(oldID,3,amount=amount)
if (not approveValues[i] and not acceptValues[i] and not rejectValues[i] and not cancelValues[i]):
trimmedSubrequests.append(subRequests[i][:-1])
else:
if approveValues[i]:
result="APP"
elif acceptValues[i]:
result="ACC"
elif rejectValues[i]:
result="REJ"
elif cancelValues[i]:
result="CAN"
resolvedSubrequests.append(subRequests[i][:-1]+[result]+[values["-INPUT_REASON_"+str(i)+"-"]])
# If the program crashes during email sending (such as with invalid login) it will deliberately NOT reach the code where it writes to databases
HF.generateEmails(resolvedSubrequests)
with open(subRequestsFilename,'w', newline='') as f:
f.write(headerLine)
writer = csv.writer(f)
writer.writerows(trimmedSubrequests)
with open(subRequestsArchiveFilename,'a', newline='') as f:
writer = csv.writer(f)
writer.writerows(resolvedSubrequests)
completed=True
elif event == 'Cancel':
completed=True
window.close()

View File

@ -0,0 +1,100 @@
import PySimpleGUI as sg
import csv
import pandas as pd
import helperFunctions as HF
def subHisWin(staffDatabaseFilename,subRequestsArchiveFilename,defaultFont):
history = pd.read_csv(subRequestsArchiveFilename,dtype=str)
layout=[[sg.Text("Name of TA:")],
[sg.Input()],[sg.Button("Find History"),sg.Button("Close Window"),sg.Button("Hall of Fame")],
[sg.Button("+APP"),sg.Button("-APP"),sg.Button("+ACC"),sg.Button("-ACC"),sg.Button("+REJ"),sg.Button("-REJ"),sg.Button("+CAN"),sg.Button("-CAN"),sg.Button("+FUL"),sg.Button("-FUL")]
,[sg.Text("",key="OUTPUT",expand_x=True,expand_y=True,size=(200, 20))]]
# Create the Window
window = sg.Window('Substitution History', layout, font=defaultFont,resizable=True)
#Event Loop to process "events" and get the "values" of the inputs
#Cease running if they close the window
event=-1
netID=-1
while event != sg.WIN_CLOSED and event != "Close Window":
event, values = window.read()
if event in ["+APP","-APP","+ACC","-ACC","+REJ","-REJ","+CAN","-CAN","+FUL","-FUL"] and netID != -1 and len(netIDs)<=1:
if event[0]=="+":
change = 1
elif event[0]=="-":
change = -1
else:
print("ERROR: button names in sub history window do not match!")
HF.incrementSubCount(netID,event[1:],change)
event = "Find History"
if event=="Find History":
window['+APP'].Update(disabled=False)
window['-APP'].Update(disabled=False)
window['+ACC'].Update(disabled=False)
window['-ACC'].Update(disabled=False)
window['+REJ'].Update(disabled=False)
window['-REJ'].Update(disabled=False)
window['+CAN'].Update(disabled=False)
window['-CAN'].Update(disabled=False)
window['+FUL'].Update(disabled=False)
window['-FUL'].Update(disabled=False)
name = values[0]
netIDs=HF.nameToID(name,getAllMatches=True)
if not hasattr(netIDs,'__len__') or len(netIDs)==0:
netID=-1
if name=="":
name="_BLANK_"
window['OUTPUT'].update(value=name+" was not found in any names in the staff database.")
else:
netID=netIDs[0]
name=HF.IDToName(netID)#convert ID back to get full name
extraNamesStr=""
if len(netIDs)>1:
names = [HF.IDToName(i) for i in netIDs[:]]
window['OUTPUT'].update(value="Multiple matches found: ["+", ".join(names)+"]")
else:
APP,ACC,REJ,CAN,FUL=HF.getSubCount(netID)
#Printing options for Pandas
pd.set_option('display.min_rows', 20)
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', 30)
output=history[history['Requestor']==netID].fillna("NONE")
if output.empty:
output="No history"
else:
output=str(output.to_string(index=False))
window['OUTPUT'].update(value=name+"; "+netID+"\n"+"APP:"+APP+" ACC:"+ACC+" REJ:"+REJ+" CAN:"+CAN+" FUL:"+FUL+"\n"+output)
if event=="Hall of Fame":
window['+APP'].Update(disabled=True)
window['-APP'].Update(disabled=True)
window['+ACC'].Update(disabled=True)
window['-ACC'].Update(disabled=True)
window['+REJ'].Update(disabled=True)
window['-REJ'].Update(disabled=True)
window['+CAN'].Update(disabled=True)
window['-CAN'].Update(disabled=True)
window['+FUL'].Update(disabled=True)
window['-FUL'].Update(disabled=True)
#Printing options for Pandas
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', 30)
output='To customize which categories to add (App,Acc,Rej,Can,Ful):\nuse 0,1,2,3,4 or any combination thereof in the text box then click "Hall of Fame" again\n(e.g. "12" yields Accepted plus Rejected)\n'
inputStr=values[0]
if inputStr=="" or not inputStr.isnumeric():
inputStr=""
output+=HF.getTopSubs(inputStr,num=15)
window['OUTPUT'].update(value=output)
window.close()

View File

@ -0,0 +1,66 @@
import PySimpleGUI as sg
import csv
import pandas as pd
import helperFunctions as HF
from datetime import datetime, timedelta
def subManualWin(staffDatabaseFilename,secDFilename,subRequestsArchiveFilename,defaultFont):
stfD = pd.read_csv(staffDatabaseFilename,dtype=str,index_col=False)
allnames=list(stfD["Name"].values)
sections=HF.getSectionTitles()
layout=[[sg.Text("Email?| Sec | Date | Replacee | Replacement")],
[sg.Checkbox('',size=(3,1),key='-EMAILCHECK-'),sg.Combo(sections,size=(6,1),key="-SECTION-",readonly=True, enable_events=True),sg.Combo([""],size=(6,1),key='-DATE-',readonly=True, enable_events=True),sg.Combo([""],size=(30,1),key="-REPLACEE-", enable_events=True,readonly=True),sg.Combo(allnames,size=(30,1),key="-REPLACEMENT-", enable_events=True,readonly=True)],
[sg.Text("Reason for manual change: "),sg.Input(key='-REASON-')],
[sg.Button("Close Window"),sg.Button("Make Change")]]
# Create the Window
window = sg.Window('Manual Substitution', layout, font=defaultFont,resizable=True)
#Event Loop to process "events" and get the "values" of the inputs
#Cease running if they close the window
event=-1
while event != sg.WIN_CLOSED and event != "Close Window":
event, values = window.read()
if event=="-SECTION-" and values['-SECTION-']!="":
sec=values['-SECTION-']
dates=HF.getDatesFromSection(sec)
window["-DATE-"].update(values=dates)
window["-REPLACEE-"].update(values=[""])
if event=="-DATE-" and values['-DATE-']!="":
date=values["-DATE-"]
names=list(HF.getAllNamesFromSection(sec,date))[0]
window["-REPLACEE-"].update(values=names)
if event == "Make Change":
sec=values['-SECTION-']
date=values["-DATE-"]
replacee=HF.nameToID(values["-REPLACEE-"])
replacement=HF.nameToID(values["-REPLACEMENT-"])
timeStamp=str(datetime.now())
request=[timeStamp,replacee,sec,date,replacement,values["-REASON-"],"APP","MANUALLY CHANGED BY COURSE ADMINS"]
HF.reassign(date,sec,replacee,replacement)
with open(subRequestsArchiveFilename,'a', newline='') as f:
writer = csv.writer(f)
writer.writerows([request])
window["-SECTION-"].update(values=sections)
window["-DATE-"].update(values=[""])
window["-REPLACEE-"].update(values=[""])
window["-REPLACEMENT-"].update(values=allnames)
if values['-EMAILCHECK-']:
HF.generateEmails([request])
window.close()

View File

@ -0,0 +1 @@
Timestamp,Requestor,Section,Dates,Replacement,Reason
1 Timestamp Requestor Section Dates Replacement Reason