Moved repo
This commit is contained in:
parent
8321655406
commit
fa27a9e403
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*~*
|
||||
*.pyc
|
||||
stringOutputs
|
||||
token.json
|
||||
*.notes
|
BIN
GoogleCloudDocumentation.docx
Normal file
BIN
GoogleCloudDocumentation.docx
Normal file
Binary file not shown.
BIN
TaskManager.docx
Normal file
BIN
TaskManager.docx
Normal file
Binary file not shown.
1
archivedSubRequests.csv
Normal file
1
archivedSubRequests.csv
Normal file
@ -0,0 +1 @@
|
||||
Timestamp,Requestor,Section,Dates,Replacement,Reason,Result,InstructorReason
|
|
1
credentials.json
Normal file
1
credentials.json
Normal 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
98
driver.py
Normal 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
2
google forms login.txt
Normal file
@ -0,0 +1,2 @@
|
||||
cse102msu@gmail.com
|
||||
python_1107
|
445
helperFunctions.py
Normal file
445
helperFunctions.py
Normal 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
1
lastUpdatedToken.txt
Normal file
@ -0,0 +1 @@
|
||||
2022-10-04 15:15:51.231447
|
35
reset.py
Normal file
35
reset.py
Normal 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
75
scheduledPayrollWindow.py
Normal 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
159
sectionCSVHelper.py
Normal 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
1717
sectionsDatabase.csv
Normal file
File diff suppressed because it is too large
Load Diff
1717
sectionsDatabase_Orig.csv
Normal file
1717
sectionsDatabase_Orig.csv
Normal file
File diff suppressed because it is too large
Load Diff
6
spring23/Regular Hours Payroll Form - Copy.csv
Normal file
6
spring23/Regular Hours Payroll Form - Copy.csv
Normal 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"
|
|
6
spring23/Regular Hours Payroll Form.csv
Normal file
6
spring23/Regular Hours Payroll Form.csv
Normal 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"
|
|
3
spring23/Substitution Request Form - Copy.csv
Normal file
3
spring23/Substitution Request Form - Copy.csv
Normal 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"
|
|
3
spring23/Substitution Request Form.csv
Normal file
3
spring23/Substitution Request Form.csv
Normal 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
|
|
54
spring23/classDates.txt
Normal file
54
spring23/classDates.txt
Normal 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
|
6
spring23/helproomStaff.txt
Normal file
6
spring23/helproomStaff.txt
Normal 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
207
spring23/sections.txt
Normal 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
|
1555
spring23/sectionsDatabase.csv
Normal file
1555
spring23/sectionsDatabase.csv
Normal file
File diff suppressed because it is too large
Load Diff
105
spring23/staffDatabase.csv
Normal file
105
spring23/staffDatabase.csv
Normal 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
|
|
88
staffDatabase.csv
Normal file
88
staffDatabase.csv
Normal 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
startOfSemesterInputs/archivedSubRequests.csv
Normal file
1
startOfSemesterInputs/archivedSubRequests.csv
Normal file
@ -0,0 +1 @@
|
||||
Timestamp,Requestor,Section,Dates,Replacement,Reason,Result,InstructorReason
|
|
56
startOfSemesterInputs/classDates.txt
Normal file
56
startOfSemesterInputs/classDates.txt
Normal 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
|
6
startOfSemesterInputs/helproomHour1Staff.txt
Normal file
6
startOfSemesterInputs/helproomHour1Staff.txt
Normal 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,
|
6
startOfSemesterInputs/helproomHour2Staff.txt
Normal file
6
startOfSemesterInputs/helproomHour2Staff.txt
Normal 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,
|
57
startOfSemesterInputs/sections.txt
Normal file
57
startOfSemesterInputs/sections.txt
Normal 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
|
256
substitutionApprovalWindow.py
Normal file
256
substitutionApprovalWindow.py
Normal 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()
|
100
substitutionHistoryWindow.py
Normal file
100
substitutionHistoryWindow.py
Normal 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()
|
||||
|
66
substitutionManualWindow.py
Normal file
66
substitutionManualWindow.py
Normal 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()
|
1
substitutionRequestForm.csv
Normal file
1
substitutionRequestForm.csv
Normal file
@ -0,0 +1 @@
|
||||
Timestamp,Requestor,Section,Dates,Replacement,Reason
|
|
Loading…
x
Reference in New Issue
Block a user