DavidD003 commited on
Commit
9cd290a
1 Parent(s): 2f88c89

Upload SchedBuilderUtyModule.py

Browse files
Files changed (1) hide show
  1. SchedBuilderUtyModule.py +450 -0
SchedBuilderUtyModule.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from SchedBuilderClasses2 import *
2
+ import openpyxl as pyxl
3
+ import pandas as pd
4
+ import numpy as np
5
+ import sqlite3
6
+ import functools
7
+ from copy import deepcopy
8
+
9
+
10
+ def debug(func):
11
+ """Print the function signature and return value"""
12
+ @functools.wraps(func)
13
+ def wrapper_debug(*args, **kwargs):
14
+ args_repr = [repr(a) for a in args] # 1
15
+ kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] # 2
16
+ signature = ", ".join(args_repr + kwargs_repr) # 3
17
+ print(f"Calling {func.__name__}({signature})")
18
+ value = func(*args, **kwargs)
19
+ print(f"{func.__name__!r} returned {value!r}") # 4
20
+ return value
21
+ return wrapper_debug
22
+
23
+
24
+
25
+ def addTBL(tblName,fields="",dTypes=None,data=None,addOn=False):
26
+ """Create table if not already existing, optionally with data, optionally clearing out old data if present. Fields as list of strings. Datatypes as list of strings, one must be provided for each field. See sqlite3 docs for mroe info"""
27
+ conn = sqlite3.connect('test17.db')
28
+ c = conn.cursor()
29
+ listedFields=''
30
+ if fields=="": #If none given, make alphabetical
31
+ fields=[chr(65+i) for i in range(len(data[0]))]
32
+ if dTypes==None: #Need not specify dtypes
33
+ for f in fields:
34
+ listedFields=listedFields+', '+ f
35
+ else: #define data types at inception of table
36
+ flds=list(zip(fields,dTypes))
37
+ for pair in flds:
38
+ listedFields=listedFields+', '+pair[0]+' '+pair[1]
39
+ listedFields='('+listedFields[2:]+''')''' #Add leading and closing bracket, remove naively added comma+space from leading field
40
+ c.execute('''CREATE TABLE IF NOT EXISTS '''+tblName+listedFields) # Create table.
41
+ if addOn==False: #Delete if not adding
42
+ c.execute('''DELETE FROM '''+tblName)
43
+ if (data is not None) and len(data)>0:
44
+ stmnt='INSERT INTO '+tblName+' VALUES ('
45
+ for i in range(len(fields)-1):
46
+ stmnt=stmnt+'?,'#Add '?,' equal to num columns less 1
47
+ stmnt=stmnt+'?)' #add closing ?), no final comma
48
+ for subEntry in data:
49
+ c.execute(stmnt, subEntry)
50
+ conn.commit()
51
+
52
+ def isNumeric(n):
53
+ try:
54
+ n=int(n)
55
+ return True
56
+ except ValueError:
57
+ try:
58
+ n=float(n)
59
+ return True
60
+ except:
61
+ return False
62
+
63
+
64
+ def viewTBL(tblName,fields=None,sortBy=None,filterOn=None,returnStatement=0):
65
+ """return np array of table with optional select fields, filtered, sorted. Sort syntax=[(field1,asc/desc),(field2,asc/desc)...] Filter syntax=[(field1,value),(field2,value)...]"""
66
+ conn = sqlite3.connect('test17.db')
67
+ c = conn.cursor()
68
+ stmnt='SELECT '
69
+ if fields!=None:
70
+ flds=''
71
+ for f in fields:
72
+ flds=flds+', '+f
73
+ stmnt=stmnt+flds[2:]+ ' FROM ' +tblName+' '
74
+ else: stmnt=stmnt+'* FROM '+tblName+' ' #unspecified, select all
75
+ if filterOn!=None:
76
+ filt='WHERE '
77
+ for f in filterOn:
78
+ if isNumeric(f[1]): filt=filt+f[0]+' = '+ str(f[1])+' AND '
79
+ else: filt=filt+str(f[0])+' = "'+ str(f[1])+'" AND '
80
+ filt=filt[:-4] #Remove naively added final " and "
81
+ stmnt=stmnt+filt
82
+ if sortBy!=None:
83
+ srt='ORDER BY '
84
+ for s in sortBy:
85
+ srt=srt+s[0]+' '+s[1]+', '
86
+ srt=srt[:-2]
87
+ stmnt=stmnt+srt
88
+ stmnt=stmnt+';'
89
+ if returnStatement==True: # Add option to print out the sql statement for troubleshooting
90
+ return stmnt
91
+ else:
92
+ c.execute(stmnt)
93
+ return [list(x) for x in c.fetchall()] #sqlite3 returns list of tuples.. want sublists for being editable
94
+
95
+ def FTbtRow(ws):
96
+ """Returns the excel row number for the bottom row with data in FT employee sheet"""
97
+ #1st find bottom row with data
98
+ for i in range(5,400):
99
+ ref="C"+str(i) #Referencing EEID column. Failure mode is EEID missing for someone. Thats a bigger problem than the code not working
100
+ if ws[ref].internal_value==None:
101
+ #Condition met when end of data found.
102
+ btmRow=i-1 #backtrack to last data
103
+ break
104
+ return btmRow
105
+
106
+ def getFTinfo(flNm):
107
+ """Returns dataframe with FT employee info (seniority,crew,eeid,name,refusal hours to date, OT hrs worked this week given path to FT refusal sheet"""
108
+ myWb=pyxl.load_workbook(flNm)
109
+ ws=myWb['Hourly OT']
110
+ btmRow=FTbtRow(ws)
111
+ tab=[[x.internal_value for x in sublist] for sublist in ws['A5:I'+str(btmRow)]]
112
+ #for rec in tab: #numeric values getting cast to string on import. cast back
113
+ # for i in [0,2]:
114
+ # rec[i]=int(rec[i])
115
+ # for i in [5,6,7]:
116
+ # rec[i]=float(rec[i])
117
+ #Following to turn into dataframe
118
+ #df_FTinfo=pd.DataFrame(tab)
119
+ #df_FTinfo=df_FTinfo[[0,1,2,3,4,5,8]] #Pull out only required columns
120
+ #df_FTinfo.set_axis(['snrty', 'crew', 'eeid','last','first','yrRef','wkOT'], axis='columns', inplace=True)
121
+ return tab
122
+
123
+
124
+ def FTendCol(ws):
125
+ """Returns column # for last column of skills matrix in excel from FT refusal sheet"""
126
+ for i in range(0,400):
127
+ if ws['A1'].offset(0,i).value=='Start-up':
128
+ #Condition met when end of data found.
129
+ endCol=i-1
130
+ break
131
+ return endCol
132
+
133
+ def getFTskills(flNm):
134
+ """Returns a dataframe containing a table with 2 fields: eeid and job name, with one record for every job an ee is trained in"""
135
+ myWb=pyxl.load_workbook(flNm)
136
+ ws=myWb['Hourly OT']
137
+ endCol=FTendCol(ws) #Get right limit for iteration through skills
138
+ btmRow=FTbtRow(ws) #Get bottom limit for iteration through skills
139
+ skills=[] #Initialize empty skills list
140
+ for i in range(5,btmRow+1): #Data starts on row 5. +1 because range fn not inclusive
141
+ eeid=ws['C'+str(i)].value
142
+ for c in range(10,endCol+1):
143
+ if ws['C'+str(i)].offset(0,c-2).value==1: #subtract 2 from c because the endCol is counted from col A, and we are offsetting from col C, whihc is 2 offset from A
144
+ jobNm=ws['A2'].offset(0,c).value #if 1 indicates trained, pull job name from header row
145
+ skills.append([eeid,jobNm]) #Add new record to skills table
146
+ #idxs=np.array(skills)[:,0]
147
+ #skills=pd.DataFrame(skills,idxs) #Convert to dataframe
148
+ #skills.set_axis(['eeid','skill'], axis='columns', inplace=True)
149
+ return skills
150
+
151
+ def TempbtRow(ws):
152
+ """Returns the excel row number for the bottom row with data in Temp employee sheet"""
153
+ #1st find bottom row with data
154
+ for i in range(4,400):
155
+ ref="C"+str(i) #Referencing EEID column. Failure mode is EEID missing for someone. Thats a bigger problem than the code not working
156
+ if ws[ref].internal_value==None:
157
+ #Condition met when end of data found.
158
+ btmRow=i-1 #backtrack to last data
159
+ break
160
+ return btmRow
161
+
162
+ def getTempinfo(flNm):
163
+ """Returns dataframe with FT employee info (seniority,crew,eeid,name,refusal hours to date, OT hrs worked this week given path to FT refusal sheet"""
164
+ myWb=pyxl.load_workbook(flNm)
165
+ ws=myWb['Temp Refusal']
166
+ btmRow=TempbtRow(ws)
167
+ tab=[[x.internal_value for x in sublist] for sublist in ws['A4:I'+str(btmRow)]]
168
+ #df_Tempinfo=pd.DataFrame(tab)
169
+ #df_Tempinfo=df_Tempinfo[[0,1,2,3,4,5,8]] #Pull out only required columns
170
+ #df_Tempinfo.set_axis(['snrty', 'crew', 'eeid','last','first','yrRef','wkOT'], axis='columns', inplace=True)
171
+ return tab
172
+
173
+ def TempendCol(ws):
174
+ """Returns column # for last column of skills matrix in excel from FT refusal sheet"""
175
+ for i in range(0,400):
176
+ if ws['A2'].offset(0,i).value=='Start Up':
177
+ #Condition met when end of data found.
178
+ endCol=i-1
179
+ break
180
+ return endCol
181
+
182
+ def getTempskills(flNm):
183
+ """Returns a dataframe containing a table with 2 fields: eeid and job name, with one record for every job an ee is trained in"""
184
+ myWb=pyxl.load_workbook(flNm)
185
+ ws=myWb['Temp Refusal']
186
+ endCol=TempendCol(ws) #Get right limit for iteration through skills
187
+ btmRow=TempbtRow(ws) #Get bottom limit for iteration through skills
188
+ skills=[] #Initialize empty skills list
189
+ for i in range(4,btmRow+1): #Data starts on row 5. +1 because range fn not inclusive
190
+ eeid=ws['C'+str(i)].value
191
+ for c in range(11,endCol+1): #First skills column is 11 offset from col A
192
+ if ws['C'+str(i)].offset(0,c-2).value==1: #subtract 2 from c because the endCol is counted from col A, and we are offsetting from col C, which is 2 offset from A
193
+ jobNm=ws['A3'].offset(0,c).value #if 1 indicates trained, pull job name from header row
194
+ skills.append([eeid,jobNm]) #Add new record to skills table
195
+ #idxs=np.array(skills)[:,0]
196
+ #skills=pd.DataFrame(skills,idxs) #Convert to dataframe
197
+ #skills.set_axis(['eeid','skill'], axis='columns', inplace=True)
198
+ return skills
199
+
200
+ def imptXlTbl(XlFl,ShtNm,TblNm):
201
+ myWb=pyxl.load_workbook(XlFl)
202
+ ws=myWb[ShtNm]
203
+ tab=ws.tables[TblNm] #Pull out table
204
+ tab=[[x.value for x in sublist] for sublist in ws[tab.ref]] #Convert to list of lists (each sublist as row of excel table)
205
+ return tab[1:] #Convert nested lists to array, dropping first row which is table headings
206
+
207
+ def imptPolltbl(XlFl,ShtNm,TblNm,tp=None):
208
+ myWb=pyxl.load_workbook(XlFl)
209
+ ws=myWb[ShtNm]
210
+ tab=ws.tables[TblNm] #Pull out table
211
+ tab=[[x.value for x in sublist] for sublist in ws[tab.ref]] #Convert to list of lists (each sublist as row of excel table)
212
+ tab=tab[1:] #Remove header column
213
+ if tp=='FT': #Pull only FT's into FT table
214
+ tab=[rec for rec in tab if rec[3]!=None] #Remove rows without refusal hours
215
+ tab=[rec for rec in tab if rec[3]<10000] #Ft's id'd by less than 10k refusal hours
216
+ if tp=='P': #Pull only probationaries into table
217
+ tab=[rec for rec in tab if rec[3]!=None] #Remove rows without refusal hours
218
+ tab=[rec for rec in tab if rec[3]>=10000] #Probationaries ID'd by 10K or more refusal hours
219
+ return tab
220
+
221
+ def generateMasterPollTbl(pollDict):
222
+ """Given a dictionary containing the polling tables for all crews, generates a master tbl in SQLlite for being able to filter on peoples availabilities, with '1' indicating interest, '0' no interest, and slot seq 1 starting at index 4"""
223
+ mPollTbl=[]
224
+ #the total list of all fields the table has is programmatically generated on these 3 lines
225
+ flds=["eeid",'lastNm','firstNm','ytdRefHrs']
226
+ flds.extend(['slot_'+str(i) for i in range(1,25)])#Note that there is one field for each slot seqID, 1 through 24, for filtering
227
+ flds.append('Comment')
228
+ for crewKey in pollDict:
229
+ tbl=pollDict[crewKey] #Pull the crew specific OT polling table from dictionary
230
+ for rec in tbl:
231
+ cmnt=rec[16]#retrieve comment to tag on later
232
+ slotwise_polling=list(rec[:4])
233
+ for i in range(4,16):
234
+ if rec[i] not in ('n','N',None) :
235
+ slotwise_polling.extend(['y','y']) #Add two entries because 1 entry in polling sheet applies to two slots
236
+ else:
237
+ slotwise_polling.extend(['n','n'])
238
+ slotwise_polling.append(cmnt)
239
+ mPollTbl.append(slotwise_polling)
240
+ addTBL('allPollData',fields=flds, data=mPollTbl,addOn=False)
241
+
242
+
243
+ def pullTbls(FtBook,TempBook,AssnBook,PollBook): #Need to make volunteer shift data puller
244
+ """Take flNm, return ftInfoTbl, ftSkillsMtx, tempInfoTbl, tempSkillsMtx, AssignmentsTbl, slot_Legend, JobTrnCrossRef, pollDict, All_Slots, senList. Uses functions defined previously to return all required tables at once. Function of functions for final script"""
245
+ a=getFTinfo(FtBook) #to sqlite
246
+ b=getFTskills(FtBook) #to sqlite
247
+ b=[[int(d[0]),d[1]] for d in b] #Cast EEid to numeric value
248
+ c=getTempinfo(TempBook) #to sqlite
249
+ d=getTempskills(TempBook) #to sqlite
250
+ d=[[int(data[0]),data[1]] for data in d] #Cast EEid to numeric value
251
+ e=imptXlTbl(AssnBook,'Assignment_List','Assn_List')
252
+ f=imptXlTbl(AssnBook,'Slot_Legend','Slot_Legend')
253
+ g=imptXlTbl(AssnBook,'Job_Training_Crossref','TrainAssnMtx') #to sqlite
254
+ pollDict={} #Generate empty dictionary to store tables of people voluntary overtime
255
+ for crew in ['Blue','Bud','Rock']:
256
+ for eeType in ['FT','P','Temp']:
257
+ if eeType=='Temp': #If type= Temp, proceed to build table
258
+ keyNm='tbl_'+crew+eeType
259
+ tbl=imptXlTbl(PollBook,'Sheet1',keyNm)
260
+ else:
261
+ keyNm='tbl_'+crew+'FT' #If type= FT OR Probationary, will be referring to FT table in excel, so hard code the string
262
+ tbl=imptPolltbl(PollBook,'Sheet1',keyNm,tp=eeType)
263
+ if eeType=='P': #keyNm was made "FT" instead of 'P' so need to manually enter the key when generating dictionry entry
264
+ pollDict['tbl_'+crew+'P']=tbl
265
+ else:
266
+ pollDict[keyNm]=tbl
267
+ pollDict['tbl_wFT']=imptPolltbl(PollBook,'Sheet1','tbl_wFT',tp='FT') #No nice loop to initialize the WWF crew tables in poll sheet
268
+ pollDict['tbl_wP']=imptPolltbl(PollBook,'Sheet1','tbl_wFT',tp='P')
269
+ pollDict['tbl_wT']=imptXlTbl(PollBook,'Sheet1','tbl_wT')
270
+ h=imptXlTbl(AssnBook,'All_Slots','All_Slots')
271
+ #Generate tables in sqlite
272
+ addTBL("sklMtx",fields=["EEID","trnNm"],data=b,addOn=False) #Overwrite all training data and populate FT ops, then append temps for a master table
273
+ addTBL("sklMtx",fields=["EEID","trnNm"],data=d,addOn=True)
274
+ addTBL("xRef",fields=["dispNm","trnNm"],data=g,addOn=False) #Skill name cross ref table for fcn dispToTrn to work
275
+ addTBL("FTinfo",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=a,addOn=False)
276
+ addTBL("TempInfo",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=c,addOn=False)
277
+ #Generate a master seniority table.. following replaces hire date with integers for temps
278
+ senHiLoTemps=viewTBL('TempInfo',sortBy=[('sen','ASC')]) #First retrieve list of temps, most senior to least
279
+ i=100000 #Start new seniority number at arbitrarily high value not to interfere with full timer
280
+ for row in senHiLoTemps:
281
+ row[0]=i
282
+ i+=1
283
+ #Overwrite/make new master sen ref table. Then append the Temp data with integerized values
284
+ addTBL("senRef",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=a,addOn=False)
285
+ addTBL("senRef",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=senHiLoTemps,addOn=True)
286
+ senList=viewTBL('senRef',sortBy=[('sen','ASC')])
287
+ return a,b,c,d,e,f,g,pollDict,h,senList
288
+
289
+ def dispToTrn(dispNm):
290
+ """Returns the trnNm associated with Display name for a given job. assumes popualted sqlite table 'xRef' with dispNm/trnNm pairs"""
291
+ q=viewTBL('xRef',fields=['dispNm','trnNm'],filterOn=[('dispNm',dispNm)])
292
+ if len(q)==0:
293
+ return "Custom func error 'dispToTrn' no entry found in xRef with dispNm="+str(dispNm)
294
+ return q[0][1]
295
+
296
+ def trnToDisp(trnNm):
297
+ """Returns the trnNm associated with Display name for a given job. assumes popualted sqlite table 'xRef' with dispNm/trnNm pairs"""
298
+ q=viewTBL('xRef',fields=['dispNm','trnNm'],filterOn=[('trnNm',trnNm)])
299
+ if len(q)==0:
300
+ return "Custom func error 'trnToDisp' no entry found in xRef with trnNm="+str(trnNm)
301
+ return [e[0] for e in q] #If multiple DispNms for one train name (e.g. L4 Packer -> Packer, Candling) or Bottle Supply -> etc.
302
+ #Then return list of all dispNms
303
+
304
+ def sklChk(eeid,dispNm):
305
+ """Returns True/False if eeid is trained on job with display name or not. Requires skills matrix named sklMtx in sqlite"""
306
+ trnNm=dispToTrn(dispNm)
307
+ if len(viewTBL('sklMtx',filterOn=[('EEID',eeid),('trnNm',trnNm)]))==0:
308
+ return False
309
+ else:
310
+ return True
311
+
312
+
313
+ def makeEEdict(ftInfoTbl,tempInfoTbl,wkHrs=40,tp='id'):
314
+ eeDict={}
315
+ for dtaTbl in [ftInfoTbl,tempInfoTbl]:
316
+ for row in dtaTbl:
317
+ # if row[1].lower().strip() in ['wwf','bud','blue','rock','silver','gold','student']: #Omit people not in packaging, or off, vacation etc
318
+ if row[2] not in list(eeDict.keys()): #Double check ee hasn't already been generated... why Cory would include an ee on temp table with crew reading 'fulltime' is beyond me but there you go
319
+ if tp=='id':
320
+ eeSkills=viewTBL('sklMtx',['trnNm'],filterOn=[('EEID',row[2])])
321
+ eeSkills=[trnToDisp(nm[0]) for nm in eeSkills] #Gather display names for skills trained on, reducing lists within list to spread elements
322
+ sk=[] #Create empty to accumulate all skills present within sublists of eeSkills
323
+ for s in eeSkills:
324
+ sk.extend(s)
325
+ sen=viewTBL('senRef',fields=['sen'],filterOn=[('id',str(row[2]))])[0][0]
326
+ anEE=ee(sen,row[1].lower().strip(),int(row[2]),row[3],row[4],row[5],row[8]+wkHrs,skills=sk) #Pull info from Refusals sheet
327
+ eeDict[anEE.eeID]=anEE
328
+ elif tp=='nm':
329
+ #Just need peoples names and EEID's linked from this.. enter dummy data for seniority, training, etc
330
+ if dtaTbl==tempInfoTbl:sen=100000
331
+ else: sen=1
332
+ anEE=ee(sen,row[1].lower().strip(),int(row[2]),row[3],row[4],row[5],row[8]+wkHrs,skills=[])
333
+ eeDict[anEE.dispNm().lower().replace(' ','-')]=anEE
334
+ return eeDict
335
+
336
+ def makeSlots(eeDict,AllSlots):
337
+ openSlots={} #Open here meaning unassigned.. Will be required when it comes time to force
338
+ for row in AllSlots:
339
+ if row[6]==1: #Check that the slot generation record is labelled as 'active'
340
+ for i in range(row[0],row[1]+1): #Generate a slot for each index over the range indicated... add 1 because python Range fn not inclusive of end point
341
+ sl=Slot(i, row[2],dispToTrn(row[2]))
342
+ #Determine how many eligible volunteers for this slot
343
+ elig=[] #To track how many people trained
344
+ for rec in viewTBL('allPollData',filterOn=[('slot_'+str(sl.seqID),'y')]): # iterate through results (employee info's) of query on who said yes to working at the time of this slot
345
+ if sl.dispNm in eeDict[rec[0]].skills: elig.append(rec[0]) #Append EEID to list 'elig' if the ee is trained on the job
346
+ sl.eligVol=elig # TTake len() to see number of eligible volunteers for the slot.
347
+ openSlots[str(sl.seqID)+'_'+str(sl.dispNm)]=sl #Enter it into the dictionary
348
+ return openSlots
349
+
350
+ def preProcessData(Acrew,wkHrs,FtBook,TempBook,AssnBook,PollBook,pNT=False,assnWWF=False,pVol=True,xtraDays=None,maxI=100):
351
+ """A function to take input data and generate all necessary tables and objects in memory to carry out algorithm. Return Schedule object containing all workSlot objects, and dictioanry fo all employee objects"""
352
+ ftInfoTbl, ftSkillsMtx, tempInfoTbl, tempSkillsMtx, AssignmentsTbl, slot_Legend, JobTrnCrossRef,pollDict,AllSlots,senList=pullTbls(FtBook,TempBook,AssnBook,PollBook)
353
+ #GenerateMasterPollTbl to facilitate making the Slots... require having a table with all employee preferences.
354
+ generateMasterPollTbl(pollDict)
355
+ #Generate Worker Objects, and assign to dictionary keyed by eeID (numeric key, not string keys)
356
+ eeDict=makeEEdict(ftInfoTbl,tempInfoTbl,wkHrs)
357
+ #Generate Schedule Slot objects (all unassigned slots for weekend)
358
+ allSlots=makeSlots(eeDict,AllSlots)
359
+ return Schedule(Acrew,allSlots,eeDict,AssignmentsTbl,senList,pollDict,slot_Legend,pNT=pNT,assnWWF=assnWWF,pVol=pVol,xtraDays=xtraDays,maxI=maxI)
360
+
361
+
362
+ def getEEinfo(FtBook,TempBook): #Need to make volunteer shift data puller
363
+ """Generate employee objects so as to be able to use their names to read pre generated schedule template."""
364
+ a=getFTinfo(FtBook) #to sqlite
365
+ b=getFTskills(FtBook) #to sqlite
366
+ b=[[int(d[0]),d[1]] for d in b] #Cast EEid to numeric value
367
+ c=getTempinfo(TempBook) #to sqlite
368
+ d=getTempskills(TempBook) #to sqlite
369
+ d=[[int(data[0]),data[1]] for data in d] #Cast EEid to numeric value
370
+
371
+ addTBL("FTinfo",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=a,addOn=False)
372
+ addTBL("TempInfo",fields=['sen','crew','id','last','first','ytd','totref','totchrg','wtdOT'],data=c,addOn=False)
373
+
374
+ return a,c
375
+
376
+ def addRecs(flNm,shNm,tblNm,data,otptNm='AutoPrimed_Template.xlsx'):
377
+ """Adds data to existing excel table in new rows. Used to flesh out tables in visual template (blank tables)"""
378
+ wb=pyxl.load_workbook(flNm)
379
+ ws=wb[shNm]
380
+ t=ws.tables[tblNm]
381
+ ref=t.ref
382
+ row,col=ws[ref[ref.index(':')+1:]].row,ws[ref[:ref.index(':')]].column
383
+ records=data
384
+ for rec in records:
385
+ for i in range(len(rec)):
386
+ ws.cell(column=col+i,row=row+records.index(rec)+1).value=rec[i]
387
+ newT=pyxl.worksheet.table.Table(displayName=t.displayName,ref=t.ref[:-len(str(row))]+str(row+len(records)))
388
+ style = pyxl.worksheet.table.TableStyleInfo(name="TableStyleLight1",showRowStripes=True)
389
+ newT.tableStyleInfo = style
390
+ # del ws.tables[tblNm]
391
+ # ws.add_table(newT)
392
+ ws.tables[tblNm]=newT
393
+ wb.save(filename=otptNm)
394
+
395
+
396
+ def translate_Visual_Template(flNm,ftRef=None,tRef=None):
397
+ """Takes in an assignment list file, and composes the All_Slots table and Assignment_List table by reading the Visual Template"""
398
+ ftInf,tInf=getEEinfo(ftRef,tRef)
399
+ eeDict=makeEEdict(ftInf,tInf,tp='nm')
400
+ # return eeDict
401
+ wb=pyxl.load_workbook(flNm)
402
+ ws=wb['Visual_Template']
403
+ #===========================================
404
+ #Print out All_Slots table. Simply observe which jobs are present
405
+ data=[]
406
+ jbNms={}
407
+ maxR=5
408
+ for i in range(5,100): #Based on template job names start at row 5
409
+ if ws['A'+str(i)].value!=None: #Typically just 25 jobs, should be extra, so skip blanks
410
+ data.append([1,24,ws['A'+str(i)].value,'','','',1])
411
+ jbNms[i]=ws['A'+str(i)].value #key job name by row id
412
+ if i>maxR: maxR=i #Track row of last job assigned
413
+ addRecs(flNm,'All_Slots','All_Slots',data)
414
+ #===========================================
415
+ #Print out Assignment_List
416
+ data=[]
417
+ got=[]
418
+ #First, identify all slots that are part of a merged cell, so we know to skip over those coordinates when we get to them when iterating over every single one
419
+ for mRng in ws.merged_cells.ranges:
420
+ rw,cl=next(mRng.cells) #Grab the first cell coordinates within merged range as this is where text is stored
421
+ if rw>4: #Do not capture title etc.
422
+ if ws.cell(row=rw,column=cl).value==None:
423
+ pass #if a blank cell was left merged, skip it. Therefore it will be assigned as normal
424
+ else:
425
+ got.extend(mRng.cells) #Gather up merged coords into got tracker
426
+ if ws.cell(row=rw,column=cl).fill==pyxl.styles.PatternFill(fill_type="solid",start_color='FFCC99FF',end_color='FFCC99FF'):
427
+ tp='F'
428
+ elif ws.cell(row=rw,column=cl).fill==pyxl.styles.PatternFill(fill_type="solid",start_color='FF00B0F0',end_color='FF00B0F0'):
429
+ tp='WWF'
430
+ elif 'N/A' in ws.cell(row=rw,column=cl).value:
431
+ tp='DNS'
432
+ else: tp='V'
433
+ #Active=1, assnType,start slot, end slot, eeid, job
434
+ #slots are -1 because col 1 in excel is job name. Unkn if 2 or 3 cells mrged so use first cell cooridnate already captured, and known that last cell is last entry in got list
435
+ if tp=='DNS':
436
+ data.append([1,tp,cl-1,got[-1][1]-1,"",jbNms[rw]])
437
+ else:
438
+ if ' ' in ws.cell(row=rw,column=cl).value: #Pull name to use as dictionary key as anything before appearance of a space
439
+ nm=ws.cell(row=rw,column=cl).value[:ws.cell(row=rw,column=cl).value.index(' ')].lower()
440
+ else: nm=ws.cell(row=rw,column=cl).value.lower() #No space, use name as appears
441
+ try:
442
+ data.append([1,tp,cl-1,got[-1][1]-1,eeDict[nm].eeID,jbNms[rw]])
443
+ except KeyError:
444
+ print('The name '+nm+' identified in the visual template could not be associated with EE data from the refusal sheets. Check the formatting')
445
+ #Note that, at this time, the output file already exists with previously added rows in first table. therefore refer to this file as the file to write to, in following command.
446
+ addRecs('AutoPrimed_Template.xlsx','Assignment_List','Assn_List',data)
447
+ # wb.save('wow.xlsx')
448
+ # new=deepcopy(wb)
449
+ # new.save('AutoPrimed_Template.xlsx')
450
+ return 'AutoPrimed_Template.xlsx'