Church Rosters Literate Program

A.J.Hurst

Version 0.1.7

Table of Contents

1 Introduction
2 The Generic Psalter Roster
2.1 PsalterRoster: main announcement and parameter extraction
2.2 PsalterRoster: imports
2.3 PsalterRoster: define global constants
2.4 PsalterRoster: define the usage routine
2.5 PsalterRoster: define the date parameter routine
2.6 PsalterRoster: define the CLI parameters routine
2.7 PsalterRoster: define the weekday and month name routines
2.8 PsalterRoster: define the PsalterRoster class
2.8.1 PsalterRoster: class PsalterRoster: initialize
2.8.2 PsalterRoster: class PsalterRoster: setDates method
2.8.3 PsalterRoster: class PsalterRoster: setTimes method
2.8.4 PsalterRoster: class PsalterRoster: buildAllocateList
2.8.5 PsalterRoster: class PsalterRoster: dutiesForToday
2.8.6 PsalterRoster: class PsalterRoster: getUnavailables
2.8.7 PsalterRoster: class PsalterRoster: getQuarantines
2.8.8 PsalterRoster: class PsalterRoster: close
2.9 PsalterRoster: class PsalterRoster: Allocate
2.9.1 Allocate: deal with existing entry"
2.9.2 Allocate: deal with no existing entries
2.9.3 Allocate: handle allocation periods
2.10 PsalterRoster: class PsalterRoster: singleAllocate
2.10.1 PsalterRoster: class PsalterRoster: singleAllocate, handle TBAs
2.10.2 PsalterRoster: class PsalterRoster: singleAllocate, get time and number
2.10.3 PsalterRoster: class PsalterRoster: singleAllocate, allocate number required
2.11 PsalterRoster: class PsalterRoster: newAllocation
2.12 PsalterRoster: class PsalterRoster: WhatHappens
3 The Preaching Roster
3.1 PreachingRoster: imports
3.2 PreachingRoster: define global constants
3.3 PreachingRoster: define the PreachingRoster class
3.3.1 PreachingRoster: class PreachingRoster: initialize
3.3.2 PreachingRoster: class PreachingRoster: WhatHappens
3.3.3 PreachingRoster: class PreachingRoster: compute preferences
4 The Greeting Roster
4.1 GreetingRoster: imports
4.2 GreetingRoster: define global constants
4.3 GreetingRoster: define the usage routine
4.4 GreetingRoster: define the GreetingRoster class
4.4.1 GreetingRoster: class GreetingRoster: initialize
4.4.2 GreetingRoster: class GreetingRoster: WhatHappens
4.4.3 GreetingRoster: class GreetingRoster: compute preferences
5 The Vestry Roster
5.1 VestryRoster: imports
5.2 VestryRoster: define global constants
5.3 VestryRoster: define the VestryRoster class
5.3.1 VestryRoster: class VestryRoster: initialize
5.3.2 VestryRoster: class VestryRoster: WhatHappens
5.3.3 VestryRoster: class VestryRoster: compute preferences
6 The Communion Roster
6.1 CommunionRoster: imports
6.2 CommunionRoster: define global constants
6.3 CommunionRoster: define the CommunionRoster class
6.3.1 CommunionRoster: class CommunionRoster: initialize
6.3.2 CommunionRoster: class CommunionRoster: WhatHappens
6.3.3 CommunionRoster: class CommunionRoster: compute preferences
6.4 addLeader
7 The Door Roster
7.1 DoorRoster: imports
7.2 DoorRoster: define global constants
7.3 DoorRoster: define the DoorRoster class
7.3.1 DoorRoster: class DoorRoster: initialize
7.3.2 DoorRoster: class DoorRoster: WhatHappens
7.3.3 DoorRoster: class DoorRoster: compute preferences
8 The Sound Roster
8.1 SoundRoster: imports
8.2 SoundRoster: define global constants
8.3 SoundRoster: define the SoundRoster class
8.3.1 SoundRoster: class SoundRoster: initialize
8.3.2 SoundRoster: class SoundRoster: WhatHappens
8.3.3 SoundRoster: class SoundRoster: compute preferences
9 The Kids Roster
9.1 KidsRoster: imports
9.2 KidsRoster: define global constants
9.3 KidsRoster: define the KidsRoster class
9.3.1 KidsRoster: class KidsRoster: initialize
9.3.2 KidsRoster: class KidsRoster: WhatHappens
9.3.3 KidsRoster: class KidsRoster: compute preferences
10 The Music Roster
10.1 MusicRoster: imports
10.2 MusicRoster: define global constants
10.3 MusicRoster: define the MusicRoster class
10.3.1 MusicRoster: class MusicRoster: initialize
10.3.2 MusicRoster: class MusicRoster: WhatHappens
10.3.3 MusicRoster: class MusicRoster: compute preferences
11 The Bible Roster
11.1 BibleRoster: imports
11.2 BibleRoster: define global constants
11.3 BibleRoster: define the BibleRoster class
11.3.1 BibleRoster: class BibleRoster: initialize
11.3.2 BibleRoster: class BibleRoster: WhatHappens
11.3.3 BibleRoster: class BibleRoster: compute preferences
12 The Hospitality Roster
12.1 HospitalityRoster: imports
12.2 HospitalityRoster: define global constants
12.3 HospitalityRoster: define the HospitalityRoster class
12.3.1 HospitalityRoster: class HospitalityRoster: initialize
12.3.2 HospitalityRoster: class HospitalityRoster: WhatHappens
12.3.3 HospitalityRoster: class HospitalityRoster: compute preferences
13 The Morning Roster
13.1 MorningRoster: imports
13.2 MorningRoster: define global constants
13.3 MorningRoster: define the MorningRoster class
13.3.1 MorningRoster: class MorningRoster: initialize
13.3.2 MorningRoster: class MorningRoster: WhatHappens
13.3.3 MorningRoster: class MorningRoster: compute preferences
14 The Finance Roster
14.1 FinanceRoster: imports
14.2 FinanceRoster: define global constants
14.3 FinanceRoster: define the FinanceRoster class
14.3.1 FinanceRoster: class FinanceRoster: initialize
14.3.2 FinanceRoster: class FinanceRoster: WhatHappens
14.3.3 FinanceRoster: class FinanceRoster: compute preferences
15 The Cleaning Roster
15.1 CleaningRoster: imports
15.2 CleaningRoster: define global constants
15.3 CleaningRoster: define the CleaningRoster class
15.3.1 CleaningRoster: class CleaningRoster: initialize
15.3.2 CleaningRoster: class CleaningRoster: WhatHappens
15.3.3 CleaningRoster: class CleaningRoster: compute preferences
16 The Leisure Roster
16.1 LeisureRoster: imports
16.2 LeisureRoster: define global constants
16.3 LeisureRoster: define the LeisureRoster class
16.3.1 LeisureRoster: class LeisureRoster: initialize
16.3.2 LeisureRoster: class LeisureRoster: WhatHappens
16.3.3 LeisureRoster: class LeisureRoster: compute preferences
17 The Hub Roster
17.1 TheHubRoster: imports
17.2 TheHubRoster: define global constants
17.3 TheHubRoster: define the TheHubRoster class
17.3.1 TheHubRoster: class TheHubRoster: initialize
17.3.2 TheHubRoster: class TheHubRoster: WhatHappens
17.3.3 TheHubRoster: class TheHubRoster: compute preferences
18 The Flowers Roster
18.1 FlowersRoster: imports
18.2 FlowersRoster: define global constants
18.3 FlowersRoster: define the FlowersRoster class
18.3.1 FlowersRoster: class FlowersRoster: initialize
18.3.2 FlowersRoster: class FlowersRoster: WhatHappens
18.3.3 FlowersRoster: class FlowersRoster: compute preferences
19 The Makefile
20 Document History
21 Indices
21.1 Files
21.2 Chunks
21.3 Identifiers


1. Introduction

This is the master literate program for all church roster programs. Currently under development.

2. The Generic Psalter Roster

What the generic psalter roster should look like:

  1. a Preamble, which is preserved on each allocation;
  2. a table of roster Entries, which are mutable;
  3. a Postamble, which is also preserved.
Entries in the roster take the form month day time person* notes? where month and day may be elided if they are the same as the line before. A line consisting of a single entry is assumed to be a year, and marks the start of a new year for the month fields. The person field may contain zero or more person WikiNames, and the notes field is optional.

Entries read in from an existing roster are preserved - the only thing that is changed is during the allocation process, when new names are added to make up any missing. The number of names required for a particular duty in the roster is a attribute of the particular roster and its class specification.

<GlobalDefinitions 1.1> =
REMINDERFILE="/home/ajh/Church/Computers/rosters/rosterReminders.txt" CONSOLIDATEDFILE="/home/ajh/Church/Computers/rosters/consolidatedRoster.txt"
Chunk referenced in 1.5
"PsalterRoster.py" 1.2 =
#!/usr/bin/python ## P s a l t e r R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/psalter.xlp instead * ## *********************************************************** ## # # the generic roster class module # # usage: # (to be defined) # # The -d/--debug flags give debugging output about what is happening. # # version history # 0.1.0 20140227:114913 ajh first version # derived from doorRoster # 0.1.1 20140301:154858 ajh debugging normalization of dates # 0.1.2 20140302:154138 ajh extensive literacy re-structure # 0.1.3 20140310:103216 ajh started on quarantine feature # 0.1.4 20140314:130435 ajh fix bug (temporarily) in alternative # service times allocation # 0.1.5 20140317:124905 ajh add singleAllocation routine # 0.1.6 20140317:170524 ajh add compute preferences routine # 0.1.7 20140322:090553 ajh refine to allow non-preference rosters # 0.1.8 20140626:163644 ajh don't add non-existent entries before allocation date # 0.2.0 20141031:183025 ajh add first and last reminder dates # 0.2.1 20141124:164431 ajh update help # 0.2.2 20141208:203944 ajh move to rosterAccess6 # 0.3.0 20150408:115213 ajh add forward quarantine psalter_version="0.3.0" # ensure this agrees with most recent version above <PsalterRoster: imports 1.4> <PsalterRoster: define global constants 1.5> <PsalterRoster: define the usage routine 1.6> <PsalterRoster: define the date parameter routine 1.7> <PsalterRoster: define the CLI parameters routine 1.8> <PsalterRoster: define the weekday and month name routines 1.9> <PsalterRoster: define the wikiname routines 1.10> <PsalterRoster: define the PsalterRoster class 1.11> <PsalterRoster: main announcement and parameter extraction 1.3> testroster=PsalterRoster('PreachingTest',logMsg,1,5,4,3,WIKI,True) testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

2.1 PsalterRoster: main announcement and parameter extraction

The main routine is responsible for printing identification, initializing some global constants, and handling the CLI parameters. It is used by every roster, although it will need parameterization before using this fragment in these subclasses.

<PsalterRoster: main announcement and parameter extraction 1.3> =
if __name__ == "__main__": print "This is PsalterRoster version %s" % psalter_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version dayDelta=datetime.timedelta(1) weekDelta=datetime.timedelta(7) monthDelta=datetime.timedelta(28) yearDelta=datetime.timedelta(365) (dates,startDate,allocateDate,endDate)=getCLIparms()
Chunk referenced in 1.2

2.2 PsalterRoster: imports

<PsalterRoster: imports 1.4> =
import calendarNames import datetime import getopt import math import re import rosterAccess6 import sys import types import wikiAccess import timeConverter
Chunk referenced in 1.2

The following three subsections are fairly self explanatory, and pass without further comment.

2.3 PsalterRoster: define global constants

<PsalterRoster: define global constants 1.5> =
<GlobalDefinitions 1.1> # various time constants dayDelta=datetime.timedelta(1) weekDelta=datetime.timedelta(7) monthDelta=datetime.timedelta(28) yearDelta=datetime.timedelta(365) today=datetime.date.today() timeStamp=datetime.datetime.now() # the year is assumed to be the current year, unless rolled around # to the next year year=today.year # define logging messages autoLogMsg="auto-update by PsalterRoster.py, version %s" % (psalter_version) logMsg="~- This page automatically generated by !PsalterRoster v%s on %s -~" % (psalter_version,timeStamp) SWITCHES={'allocate':False, 'fulllist':False, 'debug':False, 'local':False, 'names':False, 'nextyear':False, 'verbose':False, 'writeback':False, } WIKI="http://wiki.gwuc.org.au/gwuc" def setDEBUG(d): global DEBUG DEBUG=d rosterAccess6.DEBUG=DEBUG wikiAccess.DEBUG=DEBUG
Chunk referenced in 1.2

Note the ! required in front of PsalterRoster, to avoid it becoming a wiki name.

2.4 PsalterRoster: define the usage routine

<PsalterRoster: define the usage routine 1.6> =
def usage(): print """ PsalterRoster.py [-d|--debug] [-v|--verbose] (Month Day)* This program is the GWUC Roster Master Class. When executed by itself, it runs against a test roster '<name>?'. Normally it is used as a superclass inherited by the various roster specific classes, each defined in its own Python module. The options: -a|--allocate : show all key allocation decisions -f|--fulllist : generate a complete roster, no elisions -d|--debug : show verbose output, and do not write back wiki files -l|--local : use local versions of wiki files -v|--verbose : show comprehensive information about what is happening during the allocation process -w|--writeback : write the update roster back to the wiki """
Chunk referenced in 1.2

2.5 PsalterRoster: define the date parameter routine

There are three key dates to the roster allocation process: the starting date of the new roster (startDate), the end date of allocation (allocateDate), and the end date of the roster table (endDate). They must be in cronological sequence, and years are adjusted to make them so (the start date is assumed to be in the current year).

<PsalterRoster: define the date parameter routine 1.7> =
def computeKeyDates(path): dates=[] startDate=today # handle startDate if len(path)>0: if path[0][0]=='+': val=path[0] # have a compute forward parameter forward=datetime.timedelta(7*int(val)) startDate=startDate+forward path=path[1:] else: month=calendarNames.NameToIndex(path[0]) day=int(path[1]) startDate=datetime.date(year,month,day){Note 1.7.1} path=path[2:] # handle allocateDate if len(path)>0: if path[0][0]=='+': val=path[0] # have a compute forward parameter forward=datetime.timedelta(7*int(val)) allocateDate=startDate+forward path=path[1:] else: month=calendarNames.NameToIndex(path[0]) day=int(path[1]) allocateDate=datetime.date(year,month,day) path=path[2:] if allocateDate<startDate: allocateDate+=yearDelta else: # defaults to 4 weeks ahead allocateDate=startDate+monthDelta # handle endDate if len(path)>0: if path[0][0]=='+': val=path[0] # have a compute forward parameter forward=datetime.timedelta(7*int(val)) endDate=startDate+forward path=path[1:] else: month=calendarNames.NameToIndex(path[0]) day=int(path[1]) endDate=datetime.date(year,month,day) path=path[2:] if endDate<allocateDate: endDate+=yearDelta else: # defaults to 4 weeks ahead endDate=startDate+yearDelta return (dates,startDate,allocateDate,endDate)
Chunk referenced in 1.2
{Note 1.7.1}
Need to make sure this is a Sunday!

2.6 PsalterRoster: define the CLI parameters routine

<PsalterRoster: define the CLI parameters routine 1.8> =
def getCLIparms(SWITCHES): (vals,path)=getopt.getopt(sys.argv[1:], 'adfhlnvw', ['allocate','debug','fulllist','help', 'local','names','nextyear','verbose','writeback']) for (opt,val) in vals: if opt=='-a' or opt=='--allocate': SWITCHES['allocate']=True if opt=='-h' or opt=='--help': usage() sys.exit(1) if opt=='-d' or opt=='--debug': SWITCHES['debug']=True SWITCHES['verbose']=True SWITCHES['allocate']=True if opt=='-f' or opt=='--fulllist': SWITCHES['fulllist']=True if opt=='-l' or opt=='--local': SWITCHES['local']=True if opt=='-v' or opt=='--verbose': SWITCHES['verbose']=True SWITCHES['allocate']=True if opt=='-n' or opt=='--names': SWITCHES['names']=True if opt=='--nextyear': year+=1 if opt=='-w' or opt=='--writeback': SWITCHES['writeback']=True return computeKeyDates(path)
Chunk referenced in 1.2

2.7 PsalterRoster: define the weekday and month name routines

These are routines to handle conversions between internal and external formats of dates and times. whatDay converts a date value into a day of the week (0..6, Mon to Sun) and a weekday within a month (1st/2nd/3rd etc Sunday in the month, etc.).

<PsalterRoster: define the weekday and month name routines 1.9> =
def whatDay(day): dayOfWeek=day.weekday() dayInMonth=((day.day-1) / 7) + 1 return (dayOfWeek,dayInMonth) WeekDays=['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] def weekdayName(d): # d is element of 0 (Monday) to 6 (Sunday) - datetime convention return WeekDays[d] Months=['Jan','Feb','Mar','Apr','May','Jun',\ 'Jul','Aug','Sep','Oct','Nov','Dec'] def monthName(m): # m is element of 1..12, Months is 0-origin indexing return Months[m-1]
Chunk referenced in 1.2
<PsalterRoster: define the wikiname routines 1.10> =
def plainWiki(name): name=re.sub('\[\[','',name) name=re.sub('\]\]','',name) return name def decoratedWiki(name): res=re.match("'",name) if res: name="[[%s]]" % (name) return name
Chunk referenced in 1.2

2.8 PsalterRoster: define the PsalterRoster class

This is the real story. As much as possible is crammed into this generic roster, which is intended to be instantiated with appropriate parameterization for each particular roster.

Initialization of an instance may be done with the following call:

from PsalterRoster import *
PsalterRoster(rosterName,allocCol,prefsCol,[wiki=WIKI,[local=None]])
where:
rosterName
A string giving the base name of the roster. All roster pages are assumed to start with the keyword Roster, so the Salt Vestry roster will have a name RosterSaltVestry. However, only the SaltVestry part should be specified for this parameter.
allocCol
An integer number defining the column number in the roster in which the allocated wiki names are to appear. The first column counts as column 1 (not 0!)
prefsCol
An integer number defining the column number in the preferences roster at which the list of duty times are given. (This may be standardized in future, depending upon decisions about fixed allocation times (see discussion at ???)
wiki
(Optional) The URL of the wiki used for these rosters. It is unlikely to need to be changed, but is here for completeness. It defaults to http://wiki.gwuc.org.au/gwuc/.
local
(Optional) If non-zero (``True''), a local copy of the roster page is used. This is intended for debugging purposes, as it is much faster, and avoids overriding the master wiki page.
The wiki pages used by the instance will be, (for example, in the case of the Salt Vestry roster): RosterSaltVestry (current roster), RosterSaltVestryPrevious (previous roster), and SaltVestryPreferences (preferences page). There is an argument that the latter should be named RosterSaltVestryPrevious, but this was not used in order to keep the names short. It may be changed in the future.

<PsalterRoster: define the PsalterRoster class 1.11> =
class PsalterRoster(): global SWITCHES DECAY=0.01 <PsalterRoster: class PsalterRoster: initialize 1.12,1.13,1.14,1.15> <PsalterRoster: class PsalterRoster: extractCoordinator 1.16> <PsalterRoster: class PsalterRoster: load Preferences 1.17> <PsalterRoster: class PsalterRoster: load Unavailables 1.18> <PsalterRoster: class PsalterRoster: setDates method 1.19> <PsalterRoster: class PsalterRoster: setTimes method 1.20> <PsalterRoster: class PsalterRoster: buildAllocateList 1.21> <PsalterRoster: class PsalterRoster: dutiesForToday 1.22> <PsalterRoster: class PsalterRoster: getUnavailables 1.23> <PsalterRoster: class PsalterRoster: getQuarantines 1.24> <PsalterRoster: class PsalterRoster: futureQuarantines 1.25> <PsalterRoster: class PsalterRoster: Allocate 1.27> <PsalterRoster: class PsalterRoster: singleAllocate 1.35,1.36,1.37,1.38> <PsalterRoster: class PsalterRoster: newAllocation 1.42> <PsalterRoster: class PsalterRoster: close 1.26>
Chunk referenced in 1.2

2.8.1 PsalterRoster: class PsalterRoster: initialize

To initialize a generic roster, we first identify the names of the various roster pages required:

rosterprev
The previous 52 weeks of this roster. This data is needed to compute who is next to be rostered.
rostercurr
The current state of the roster. This roster is to be updated by allocating the next week(s) of names, according to the allocDate variable, which defines the end of allocations. The start of allocations is given by the startDate variable, or the first roster entry, which ever is later in time. The last entry in the roster is given by the endDate variable, and entries not already in the roster are filled in with blank entries from the allocDate to this date.
preferences
Initial entry of preferences data is handled by reading this page as a roster, but it is converted for internal use into a dictionary of preferred duty times, indexed by WikiName.
The startDate, allocDate, and endDate variable are given default values at this point, but they may be overridden by an explicit call to setDates before the actual allocation is made. The defaults are (startDate): the next Sunday after the date of invocation, (allocDate): 4 weeks beyond the start date, and (endDate): 52 weeks beyond the start date.

We also initialize the class variables firstReminder and lastReminder, which define the dates for which reminder messages are to be generated. These normally default to the following Sunday to Saturday, but can be changed if necessary.

<PsalterRoster: class PsalterRoster: initialize 1.12> =
def __init__(self,rosterName,logmsg,wiki=WIKI,local=None): print "Handling the %s Roster" % (rosterName) self.previousName="Roster%sPrevious" % (rosterName) self.currentName="Roster%s" % (rosterName) self.prefsName="%sPreferences" % (rosterName) self.logmsg=logmsg self.version=psalter_version self.emailFileName=REMINDERFILE try: self.emailFile=open(self.emailFileName,'a') except IOError as e: print "Cannot open file %s for appending, reason %s" % (self.emailFileName,e.args) self.wiki=wiki self.local=local # compute the next sunday{Note 1.12.1} startDate=datetime.date.today() diff = 6 - startDate.weekday() if diff > 0: delta=datetime.timedelta(diff) startDate=startDate+delta self.startDate=startDate self.allocDate=startDate+monthDelta self.endDate=startDate+yearDelta # if this is not changed, reminders are sent for duties from # next Sunday to the following Saturday inclusive # We assume that startDate is now a Sunday, and the first date # for which reminders are computed self.firstReminder=self.startDate self.lastReminder=self.firstReminder+datetime.timedelta(days=6) # define all the default parameters. These should be changed in # subclasses after calling this meta-class init method. self.numberReq=1 self.numberOfColumns=5 self.allocCol=4 self.prefsCol=3 self.width=[3,2,7,60,20] # define the times of services self.possibleTimes=['0800','0915','1000','1100'] self.noTimes=False self.normalTimes=['0915','1100']
Chunk referenced in 1.11
Chunk defined in 1.12,1.13,1.14,1.15
{Note 1.12.1}
Note how the next Sunday is computed. The datetime class provides a weekday function, which returns 0 for Mondays, through to 6 for Sundays. Hence we add the difference between 6 and the current weekday number to get to the next Sunday.

Now load and normalize the previous roster. The normalization process fills in elided months and days from the timetable, and computes the appropriate year for each entry. This latter process is assisted by explicit year entries in the roster, but wrap-arounds from December to January will also cause a new year to be assigned.

<PsalterRoster: class PsalterRoster: initialize 1.13> =
def loadPrevious(self,noTimes=False): print "\nLoading %s" % (self.previousName) self.rosterprev=rosterAccess6.roster(self.wiki,self.previousName,self.local,self.numberOfColumns) self.rosterprev.parse() self.rosterprev.fillDates() if SWITCHES['verbose']: print "\nprevious roster after fillDates:" for e in self.rosterprev.entries: print e #self.rosterprev.normalizeDates(noTimes) # now obsolete ! if SWITCHES['verbose']: print "\nprevious roster after normalizing:" for e in self.rosterprev.entries: print e print "previous dateMap after normalizing:\n%s" % (self.rosterprev.dateMap) # sanitize commas from previous roster # this may not be necessary once all previous rosters processed for e in self.rosterprev.entries: names=e[self.allocCol] if names: names=re.sub(","," ",names) names=re.sub(" +"," ",names) e[self.allocCol]=names
Chunk referenced in 1.11
Chunk defined in 1.12,1.13,1.14,1.15

Now load and normalize the current roster. Normalization occurs in a similar way to the previous roster.

<PsalterRoster: class PsalterRoster: initialize 1.14> =
def loadCurrent(self,noTimes=False): print "\nLoading %s" % (self.currentName) self.rostercurr=rosterAccess6.roster(self.wiki,self.currentName,self.local,self.numberOfColumns) self.rostercurr.parse() self.rostercurr.fillDates(noTimes) #self.rostercurr.normalizeDates(noTimes) # now obsolete! coord=self.extractCoordinator() if coord: self.emailFile.write("coordinator(%s)=%s\n" % (self.currentName,coord)) if SWITCHES['verbose']: print "current roster after normalizing:\n" for e in self.rostercurr.entries: print e print "dateMap after loading and normalizing:\n%s" % (self.rostercurr.dateMap) print "merging last entries" self.rostercurr.addEntries(self.rosterprev.entries) self.rostercurr.normalizeDates(noTimes) if SWITCHES['verbose']: print "current roster after merging:\n" for e in self.rostercurr.entries: print e
Chunk referenced in 1.11
Chunk defined in 1.12,1.13,1.14,1.15

The final piece of initialization is to set up the instance variables prefsPage and rosterunavail, since these are not used by non-allocating rosters, and we need something to show their absence.

<PsalterRoster: class PsalterRoster: initialize 1.15> =
self.prefsPage=None self.rosterunavail=None self.allocatetime=None self.coordinator=None
Chunk referenced in 1.11
Chunk defined in 1.12,1.13,1.14,1.15

Extract the coordinator. This is a simple filtering process - just scan the header section for a line with the word "coordinator" in it, and use the wikiname found therein.

<PsalterRoster: class PsalterRoster: extractCoordinator 1.16> =
def extractCoordinator(self): header=self.rostercurr.preamble self.coordinator=None for headline in header: if SWITCHES['verbose']: print "looking for coordinator in header line %s" % (headline) res=re.match(".*coordinator.*([A-Z][a-z]+([A-Z][a-z]+)+)",headline) if res: self.coordinator=res.group(1) if SWITCHES['debug']: print "extractCoordinator finds coordinator %s" % (self.coordinator) break return self.coordinator
Chunk referenced in 1.11

Now load the preferences ``roster''. Note that it is not really a roster, but is treated as such on loading, to employ the general roster parsing mechanisms. Once loaded, it is converted to an internal form, consisting of a dictionary of WikiName (rostered people), indexing to a list of preferences, mainly of duty times.

The "quarantine" idea is to avoid rostering people in too short a time from their most recent allocation. Each individual can express a preference for the number of weeks they are quarantined from allocation. We also keep track of the highest such number, to limit how far back we must search in the previous roster section.

<PsalterRoster: class PsalterRoster: load Preferences 1.17> =
def loadPreferences(self): print "\nLoading %s" % (self.prefsName) noPrefs=[] ncols=2+len(self.possibleTimes) # name, quarantine, number of times self.prefsPage=rosterAccess6.roster(self.wiki,self.prefsName,self.local,ncols) self.prefsPage.parse() self.preferences={} self.allocatetime={} self.quarantine={} self.highestQuarantine=0 self.pairs={} for e in self.prefsPage.entries: if SWITCHES['verbose']: print "initializing Preferences: e=%s" % (e) row=[] noPref=True pair='' for p in e[self.prefsCol:]: # column prefsCol to skip date, name and numbers if SWITCHES['debug']: print "checking preference: p=%s" % (p) # this should be sequentially something like 9.15, 10.00, 11.00 if p=='Yes': row.append(1.0); noPref=False if p=='No' : row.append(0.0) res=re.match(' *pair with ([^ ]+) *$',p) if res: pair=res.group(1) name=e[0] # absolute number - CARE NEEDED! column 0 in prefs must be a WikiName{Note 1.17.1} name=re.sub('\[\[','',name) name=re.sub('\]\]','',name) self.preferences[name]=row # collect allocation period for each person a=int(e[1]) # another absolute column number - the allocation period self.allocatetime[name]=a # collect quarantine period for each person q=int(e[2]) # another absolute column number - the quarantine period self.quarantine[name]=q if q>self.highestQuarantine: self.highestQuarantine=q self.pairs[name]=pair if noPref: noPrefs.append(name) if SWITCHES['verbose']: print "raw preferences for %d volunteers" % (len(self.preferences)) print "%s" % (self.preferences) print "\n\n" # now convert normal times to possible times self.preferences=self.computePreferences(self.preferences) if SWITCHES['verbose']: print "collected preferences for %d volunteers" % (len(self.preferences)) print "%s" % (self.preferences) print "\n\n"
Chunk referenced in 1.11
{Note 1.17.1}
This is one of the places where absolute column numbers are used. It probably should be parameterized in some way, but that might have to wait for a future refinement.

The last stage of initialization is to collect the unavailable lists. Again, this is not a roster, but it has a similar form in that it consists of entries in the form date WikiName*, and so it is convenient to use the general roster parsing mechanisms.

<PsalterRoster: class PsalterRoster: load Unavailables 1.18> =
def loadUnavailables(self): print "Loading the Unavailable list" self.rosterunavail=rosterAccess6.roster(self.wiki,'RosterUnavailable',self.local,2) self.rosterunavail.parse() self.rosterunavail.fillDates() self.rosterunavail.normalizeDates(noTimes=True) if SWITCHES['verbose']: print self.rosterunavail.entries
Chunk referenced in 1.11

2.8.2 PsalterRoster: class PsalterRoster: setDates method

<PsalterRoster: class PsalterRoster: setDates method 1.19> =
def setDates(self,s,a,e): self.startDate=s self.allocateDate=a self.endDate=e if SWITCHES['verbose']: print "\n\n","*"*80 print "set dates to:" print " startDate = %s" % (self.startDate) print " allocateDate = %s" % (self.allocateDate) print " endDate = %s" % (self.endDate) print
Chunk referenced in 1.11

2.8.3 PsalterRoster: class PsalterRoster: setTimes method

Allow for adjusting the sets of normal duty times, and possible duty times. "Normal" implies the regular week-by-week times, while "possible" implies times for special occasions such as combined services, and Easter and Christmas.

<PsalterRoster: class PsalterRoster: setTimes method 1.20> =
def setTimes(self,normal,possible): self.normalTimes=normal self.possibleTimes=possible
Chunk referenced in 1.11

2.8.4 PsalterRoster: class PsalterRoster: buildAllocateList

<PsalterRoster: class PsalterRoster: buildAllocateList 1.21> =
def buildAllocateList(self,thisDate,prefs): '''given an allocation day @thisDate and a list of names given by the keys in @prefs, return a list of (weight,name) tuples sorted from lowest weight to highest weight, where the weight is computed from an exponentially decayed sum of previous duties or allocations.''' global SWITCHES if SWITCHES['verbose']: print "buildAllocateList(%s,%s)" % (thisDate,prefs) namesDict={} for n in prefs.keys(): namesDict[n]=0.0 # scan the roster to compute allocation weights #print len(self.entries),self.entries for e in self.entries: e0=e[0].date() namestr=e[4] # 4 is the column containing a name list # first replace commas with spaces namestr=re.sub(","," ",namestr) # reduce duplicate spaces namestr=re.sub(" +"," ",namestr) # now split the string on spaces namelist=namestr.split(' ') if e0>=thisDate: # if entry thisDate is today or later, exclude from weighting self.date=e0 break if SWITCHES['verbose']: print "have name list %s for date %s" % (namelist,e0) #compute decay factor for each name daysSince=thisDate-e0 days=daysSince.days decayValue=math.exp(-DECAY*days) # build names weights allocated to current date for n in namelist: if namesDict.has_key(n): old=namesDict[n] new=old+decayValue namesDict[n]=new # build a list of names for allocation next=[] for r in namesDict.keys(): d=namesDict[r] if SWITCHES['verbose']: print "%18s: %15.10f" % (r,d) next.append((d,r)) # define how to sort them: lowest weights to top of list # (ascending order of weights). def sortOrder(a,b): x=a y=b if x<y: return -1 elif x>y: return 1 else: return 0 # sort and return the list of allocatees next.sort(sortOrder) self.allocate=next return next
Chunk referenced in 1.11

2.8.5 PsalterRoster: class PsalterRoster: dutiesForToday

It is thought that this code is redundant. It is left in for the moment, pending further analysis.

Compute a list of duties for the date 'date' and return as a tuple of the list of duties and those unavailable on that date. Each individual duty list entry is a list of time of duty, followed by strings of allocated people, one string per welcome/vestry/communion. Lists of people are represented by multiple names in the string (as per the wiki page).

<PsalterRoster: class PsalterRoster: dutiesForToday 1.22> =
def dutiesForToday(self,date): duties=[]; unavailable=[] for e in self.entries: #print e,len(e) thisdate=rosterAccess6.dt2d(e[0]) if thisdate!=date: continue list=[e[3]] duties.extend(list) if len(e)>=8: #print e[7] unavail=rosterAccess6.nameExtract(e[7]) else: unavail=[] for u in unavail: unavailable.append(u) return (duties,unavailable)
Chunk referenced in 1.11

2.8.6 PsalterRoster: class PsalterRoster: getUnavailables

<PsalterRoster: class PsalterRoster: getUnavailables 1.23> =
def getUnavailables(self,date): dateKey=date.strftime("%Y%m%d") unavails=[] if not self.rosterunavail.dateMap.has_key(dateKey): return unavails (st,en)=self.rosterunavail.dateMap[dateKey] for i in range(st,en+1): e = self.rosterunavail.entries[i] people=e[3] # 3 is column containing unavailable people people=people.split(' ') for p in people: if p not in ['bad','12','hour','time']: unavails.append(p) return unavails
Chunk referenced in 1.11

2.8.7 PsalterRoster: class PsalterRoster: getQuarantines

<PsalterRoster: class PsalterRoster: getQuarantines 1.24> =
def getQuarantines(self,nowDate): # Get the list of those still in quarantine from being allocated recently. rosterkey=nowDate.strftime("%Y%m%d") if SWITCHES['verbose']: print "in quarantine, rosterkey=%s, " % (rosterkey), print "self.rostercurr.dateMap=%s" % (self.rostercurr.dateMap) excused=[] quarantine={} # OK, we cannot use the dateMap to find where we are, since there may be new entries in the roster that must be counted if self.rostercurr.dateMap.has_key(rosterkey): (s,e)=self.rostercurr.dateMap[rosterkey] rosterindex=e weeks=0; lastWeek=nowDate while rosterindex>0: e=self.rostercurr.entries[rosterindex] checkDate=e[0].date() weeks=nowDate-checkDate weeks=weeks.days/7 if SWITCHES['verbose']: print "quarantine: checking entry %s" % (e) names=e[self.allocCol] names=re.sub(","," ",names) names=re.sub(" +"," ",names) names=names.split(" ") for n in names: n=plainWiki(n) if self.quarantine.has_key(n): q=self.quarantine[n] if q>weeks: if SWITCHES['verbose']: print "adding %s to excused, date=%s, quarantine=%d, weeks=%d" % \ (n,rosterkey,q,weeks) excused.append(n) if not quarantine.has_key(n): d=e[0].strftime("%Y%m%d:%H%M") quarantine[n]=(d,q,weeks) else: if SWITCHES['verbose']: print "%s (q=%d, w=%d) not in quarantine period" % (n,q,weeks) else: print "NO QUARANTINE PERIOD FOR %s" % (n) if weeks>self.highestQuarantine: break rosterindex-=1 excused=quarantine.keys() def sortOrder(a,b): (d1,q1,w1)=quarantine[a] (d2,q2,w2)=quarantine[b] if w1<w2: return 1 elif w1>w2: return -1 elif q1<q2: return 1 elif q1>q2: return -1 else: return 0 excused.sort(sortOrder) if SWITCHES['verbose']: print "\nquarantine list:" for k in excused: (d,q,w)=quarantine[k] print "%20s: %14s %2d %2d" % (k,d,q,w) return quarantine
Chunk referenced in 1.11
<PsalterRoster: class PsalterRoster: futureQuarantines 1.25> =
def futureQuarantines(self,rosterDate): # find those who have been allocated in the future within their quarantine period futureAllocations={} print "future allocations start" checkDate=rosterDate while checkDate<self.endDate: key=checkDate.strftime("%Y%m%d") if self.rostercurr.dateMap.has_key(key): (s,e)=self.rostercurr.dateMap[key] for i in range(s,e+1): checkEntry=self.rostercurr.entries[i] persons=checkEntry[4] # ALERT! absolute number here persons=persons.split(' ') for p in persons: if not p: continue if SWITCHES['verbose']: print "future allocations looks at roster entry %d = %s for person %s" % (i,checkEntry,p) # remove any wikifying brackets p=re.sub('\[\[','',p) p=re.sub('\]\]','',p) if SWITCHES['verbose']: print "need to check %s for inclusion on exclusion list" % (p) if self.quarantine.has_key(p): q=self.quarantine[p] qDate=rosterDate+q*weekDelta if checkDate<=qDate: r="future quarantine period=%d (%s<=%s)" % (q,checkDate,qDate) futureAllocations[p]=r if SWITCHES['verbose']: print "%s is allocated within quarantine of %d, so exclude" % (p,q) else: if SWITCHES['verbose']: print "%s can be reallocated outside quarantine of %d" % (p,q) else: print "No quarantine period for %s" % (p) checkDate=self.advanceDay(checkDate) print "future allocations end, allocated are %s" % (futureAllocations) return futureAllocations
Chunk referenced in 1.11

Scan forward from the current date being considered for allocation to find those entries already allocated. These will be either previously automatically allocated, or self allocations. If they are within their allotted exclusion (quanrantine) period, they are to be excluded from this allocation on the grounds that a) a previous allocation has been made, and they have been notified of that; or b) they are self allocated, and to allocate them again would violate the multiple allocations within the exclusion period rule.

The returned value futureAllocations is a dictionary of those person found to be allocated within their future quarantine period, indexed by the person wikinames, with a string value stating the reason for exclusion.

2.8.8 PsalterRoster: class PsalterRoster: close

<PsalterRoster: class PsalterRoster: close 1.26> =
def close(self): global SWITCHES print "\nClosing the rosters" # wikify all names for e in self.rostercurr.entries: pps=rosterAccess6.people(e[self.allocCol]) pps=rosterAccess6.wikifyPeople(pps) e[self.allocCol]=pps # done allocations, generate result rosters self.rostercurr.makeRoster(self.width) # should that ^^ go in close rosters? I don't think so ... today=datetime.date.today() year=today.year # only write the updated roster back to the wiki if explicitly required writeBack=SWITCHES['writeback'] if SWITCHES['verbose']: print "generating new Previous Roster" startLast=self.startDate-yearDelta endLast=self.startDate-dayDelta self.rosterprev.copyRoster(self.rostercurr,startLast,endLast) # compute previous roster inserts for years inserts=[] # backwards in time for previous for y in [year-1,year]: dd=datetime.date(y,1,1) inserts.append((dd,"||<-7> '''%4d''' ||" % (y))) if SWITCHES['verbose']: print "previous inserts=%s" % (inserts) # close the previous roster and writeback if necessary result=self.rosterprev.outputRoster(startLast,endLast,inserts) if SWITCHES['verbose']: print "\n\nrosterprev roster lines" for l in result: print l self.rosterprev.page=result print "closing rosterprev ... ", print self.rosterprev.close(writeBack,autoLogMsg) # close the current roster and writeback if necessary # compute roster inserts for years inserts=[] # forwards in time for current for y in [year,year+1]: dd=datetime.date(y,1,1) inserts.append((dd,"||<-7> '''%4d''' ||" % (y))) if SWITCHES['verbose']: print "current inserts=%s" % (inserts) print "Now outputting current roster from %s to %s" % (self.startDate,self.endDate) result=self.rostercurr.outputRoster(self.startDate,self.endDate,inserts,self.logmsg) if SWITCHES['verbose']: print "\n\nrostercurr roster lines" for l in result: print l self.rostercurr.page=result print "closing rosterCurrent, writeBack=%s ... " % (writeBack), #sys.exit(0) print self.rostercurr.close(writeBack,autoLogMsg) if self.prefsPage: print "closing preferences ... ", print self.prefsPage.close(False) if self.rosterunavail: print "closing unavailables ...", print self.rosterunavail.close(False) print print "closing email messages" self.emailFile.close()
Chunk referenced in 1.11

2.9 PsalterRoster: class PsalterRoster: Allocate

This is a revised version of doAllocation, with a more explict code structure. In particular, it scans through all days of the week, picking up entries either from the existing roster, or generating new entries according to a routine yet to be defined.

<PsalterRoster: class PsalterRoster: Allocate 1.27> =
<PsalterRoster: class PsalterRoster: WhatHappens 1.43> <PsalterRoster: class PsalterRoster: advanceDay 1.44> def Allocate(self): print "Start Allocate" <PsalterRoster: class PsalterRoster: Allocate: allocate across all dates 1.28> <PsalterRoster: class PsalterRoster: Allocate: sort allocated entries 1.29> # now rebuild the dateMap self.rostercurr.buildDateMap() if SWITCHES['verbose']: for e in self.rostercurr.entries: print e <PsalterRoster: class PsalterRoster: Allocate: extract consolidated roster 1.30> <PsalterRoster: class PsalterRoster: Allocate: remove tentative allocations 1.31> print "End Allocate"
Chunk referenced in 1.11

The process of allocation takes place in 6 steps:

  1. perform the allocation across the requested range of dates
  2. sort the newly allocated entries by date
  3. rebuild the date map
  4. collect all future allocations
  5. build the consolidated roster (first 4 weeks)
  6. remove the tentative allocations

<PsalterRoster: class PsalterRoster: Allocate: allocate across all dates 1.28> =
rosterDate=self.startDate if SWITCHES['verbose']: print self.rostercurr.dateMap for i in range(len(self.rostercurr.entries)): print "%5d: %s" % (i,self.rostercurr.entries[i]) firstTime=True; firstDate=rosterDate self.newAllocations={} # need these to check elisons later while rosterDate<self.endDate: if SWITCHES['verbose']: print "Allocate: %s" % (rosterDate) (weekday,monthday)=whatDay(rosterDate) key=rosterDate.strftime("%Y%m%d") allocateEntry=None if self.rostercurr.dateMap.has_key(key): <Allocate: deal with existing entry 1.32> else: <Allocate: deal with no existing entries 1.33> rosterDate=self.advanceDay(rosterDate) self.rostercurr.entries.sort()
Chunk referenced in 1.27

Here we proceed to allocate across all dates, from the startDate to the endDate. The outer loop runs the control variable rosterDate across all these dates, using the advanceDate routine to actually advance the date by a single day at each step. This is to ensure that rosters that happen on multiple days of the week are collected and processed.

The main activity in this routine splits into two halves:

<PsalterRoster: class PsalterRoster: Allocate: sort allocated entries 1.29> =
order=self.normalTimes order.append('Outing') def sortOrder(a,b): if a[0]<b[0]: return -1 elif a[0]>b[0]: return 1 else: if a[3].isdigit(): if a[3]>b[3]: return 1 elif a[3]<b[3]: return -1 else: return 0 else: # alphabetic duty, not time ao=bo=-1 try: ao=order.index(a[3]) bo=order.index(b[3]) except: print "oops! bad sort terms against list=%s: %s,%s,%s,%s" % \ (order,a[3],b[3],ao,bo) if ao>bo: return 1 elif ao<bo: return -1 else: return 0 self.rostercurr.entries.sort(sortOrder)
Chunk referenced in 1.27

Sort the entries. The sorting order is defined by the sortOrder routine, which sorts in order of date, then time, bearing in mind that the time may in fact be an alphanumeric string.

<PsalterRoster: class PsalterRoster: Allocate: extract consolidated roster 1.30> =
# throw in a loop here to extract those in new 4 weeks for # consolidated roster. Easier to do this in a separate pass, # than complicate other loops rosterDate=self.startDate fourWeeks=rosterDate+monthDelta consolidatedRoster=open(CONSOLIDATEDFILE,'a') while rosterDate<fourWeeks: key=rosterDate.strftime("%Y%m%d") if self.rostercurr.dateMap.has_key(key): (s,e)=self.rostercurr.dateMap[key] for i in range(s,e+1): checkEntry=self.rostercurr.entries[i] people=checkEntry[self.allocCol] time=checkEntry[3] if people: people=people.split(' ') else: people=[] dateKey=checkEntry[0].strftime("%Y%m%d") for p in people: if p=='TBA': continue p=re.sub('\[\[','',p) p=re.sub('\]\]','',p) alloc="%s:%s@%s=%s()" % (dateKey,time,self.currentName,p) consolidatedRoster.write("%s\n" % (alloc)) rosterDate=self.advanceDay(rosterDate) consolidatedRoster.close()
Chunk referenced in 1.27

This loop scans the now sorted list of entries, checking for those in the next four weeks, and writing these entries into the consolidated roster file for later processing.

<PsalterRoster: class PsalterRoster: Allocate: remove tentative allocations 1.31> =
# allocation proper is now done, but we need to elide those beyond # their allocate periods. Scan through entire roster, computing # weeks beyond rosterDate, and eliding names as appropriate. # none of this is necessary if there are no preferences, or the # allocate option is specified. if self.prefsPage and not SWITCHES['fulllist']: rosterDate=self.startDate while rosterDate<self.endDate: <Allocate: handle allocation periods 1.34> rosterDate=self.advanceDay(rosterDate) pass # if self.prefsPage
Chunk referenced in 1.27

We now remove those entries that are allocated beyond their requested 'fixed' allocation range. This varies from person to person, and is recorded in the preferences table.

2.9.1 Allocate: deal with existing entry"

<Allocate: deal with existing entry 1.32> =
(s,e)=self.rostercurr.dateMap[key] if SWITCHES['verbose']: print "Allocate has duty entries at (%d,%d)" % (s,e) happen=self.WhatHappens(rosterDate,self.possibleTimes) if SWITCHES['verbose']: happenstr='' if happen: keys=happen.keys() keys.sort() for key in keys: happenstr+='\n %s:%s' % (key,happen[key]) print "Allocate to existing entry:WhatHappens=>:%s" % (happenstr) for i in range(s,e+1): allocateEntry=self.rostercurr.entries[i] if SWITCHES['verbose']: datetime=allocateEntry[0].strftime("%Y%m%d:%H%M") print "\n"+50*'='+' Allocate:'+datetime print "Allocate looks at roster entry %d = %s" % (i,allocateEntry) #if self.prefsPage==None: break {Note 1.32.1} time=allocateEntry[0].strftime("%H%M") if time=='0000': time=allocateEntry[3] time=re.sub(' ','',time) # remove any blanks in the 'time' # issue reminders people=allocateEntry[self.allocCol] people=people.split(' ') if rosterDate>=self.firstReminder and rosterDate<=self.lastReminder: for n in people: # only write email msg if non-empty name if n: print "Allocate: issue reminder for %s" % (n) flag='reminder' date=rosterDate.strftime("%Y%m%d") msg="%8s:%4s@%s=%s(%s)" % (date,time,self.currentName,n,flag) self.emailFile.write(msg+'\n') # I don't understand the purpose of the firstTime variable # ajh, 20141029:163330 #if firstTime or rosterDate==firstDate: # firstTime=False; firstDate=rosterDate # for n in people: # self.newAllocation(n,rosterDate,time,self.currentName,'reminder') timekey=time #timeConverter.time12to24(time) if SWITCHES['verbose']: print "Allocate existing entry: time=%s, key=%s" % (time,timekey) if happen and happen.has_key(timekey): shouldhappen=happen[timekey] (date,tm,nreq,possibleEntry)=shouldhappen print "Allocate: shouldhappen=(%s,%s,%d,%s)" % (shouldhappen) else: shouldhappen=None nreq=0 print "Allocate: no happen or no key %s" % (timekey) if SWITCHES['allocate']: print "Allocate: %s:%s %s (nreq=%d)" % (key,timekey,allocateEntry,nreq) if rosterDate<=self.allocateDate: self.singleAllocate(rosterDate,time,allocateEntry,nreq) if SWITCHES['verbose']: print "Allocate computes roster entry %d = %s" % (i,allocateEntry) if SWITCHES['allocate']: print "-"*60
Chunk referenced in 1.28
{Note 1.32.1}
This line has been commented out, because we need to continue checking entries even when there are no preferences, in order to handle manual rosters correctly, and in particular, pick up any reminders for the next week.

2.9.2 Allocate: deal with no existing entries

<Allocate: deal with no existing entries 1.33> =
# no entry, check default behaviours # if no entry, and we are before the allocate date, then # we assume that is intended, and do nothing; # else # we must add a new entry, with all that that entails if rosterDate<self.allocateDate: pass else: happen=self.WhatHappens(rosterDate,self.normalTimes) if happen: if SWITCHES['verbose']: happenstr='' keys=happen.keys() keys.sort() for key in keys: happenstr+='\n %s:%s' % (key,happen[key]) print "Allocate:WhatHappens=>:%s" % (happenstr) times=happen.keys() for t in times: (date,tm,nreq,allocateEntry)=happen[t] date=date.strftime("%Y%m%d") if SWITCHES['allocate']: print "Allocate: %s:%s nreq=%d %s" % (date,tm,nreq,allocateEntry) if rosterDate<=self.allocateDate: self.singleAllocate(rosterDate,t,allocateEntry,nreq) self.rostercurr.entries.append(allocateEntry) i=len(self.rostercurr.entries)-1 new=self.rostercurr.entries[i] # should check here to update dateMap # now add this entry to the dateMap datestr=new[0].strftime("%Y%m%d") thisDateMapEntry=None # until proven otherwise if self.rostercurr.dateMap.has_key(datestr): # already got an entry, advance the end point thisDateMapEntry=self.rostercurr.dateMap[datestr] (st,en)=thisDateMapEntry if SWITCHES['verbose']: print "dateMap has key %s with value (%d,%d) at entry number=%d" % (datestr,st,en,i) if i>en: en=i thisDateMapEntry=(st,en) else: # create single pointer entry if SWITCHES['verbose']: print "dateMap has no key %s, adding entry number=%d" % (datestr,i) thisDateMapEntry=(i,i) self.rostercurr.dateMap[datestr]=thisDateMapEntry if SWITCHES['verbose']: print "Allocate updates dateMap with (%d,%d)" % (thisDateMapEntry) print "Allocate adds roster entry %d = %s" % (i,new)
Chunk referenced in 1.28

2.9.3 Allocate: handle allocation periods

<Allocate: handle allocation periods 1.34> =
key=rosterDate.strftime("%Y%m%d") weeks=rosterDate-self.startDate weeks=weeks.days/7 if self.rostercurr.dateMap.has_key(key): (s,e)=self.rostercurr.dateMap[key] for i in range(s,e+1): checkEntry=self.rostercurr.entries[i] if SWITCHES['verbose']: print "Allocate checking roster entry %d = %s" % (i,checkEntry) people=checkEntry[self.allocCol] if people: people=people.split(' ') else: people=[] newpeople=[] datetimeKey=checkEntry[0].strftime("%Y%m%d:%H%M") print "newAllocations=%s" % (self.newAllocations) for p in people: if p=='TBA': continue newAllocKey=datetimeKey+"@"+p print "Allocate elide checking key=%s" % (newAllocKey) if self.newAllocations.has_key(newAllocKey): (elide,w,a)=self.newAllocations[newAllocKey] print "Allocate elide checking (%s,%s,%s)" % (elide,w,a) if elide: if SWITCHES['verbose']: print "Allocate elide removes %s with allocation=%d and weeks=%d" %\ (p,a,w) else: if SWITCHES['verbose']: print "Allocate elide retains %s with allocation=%d and weeks=%d" %\ (p,a,w) newpeople.append(p) else: newpeople.append(p) people=' '.join(newpeople) checkEntry[self.allocCol]=people if SWITCHES['verbose']: print "Allocate computes new roster entry %d = %s" % (i,checkEntry)
Chunk referenced in 1.31

2.10 PsalterRoster: class PsalterRoster: singleAllocate

singleAllocate makes a single roster allocation into roster entry entry for rosterDate and time. nreq is an interger defining the number of people required to be allocated to this time slot.

<PsalterRoster: class PsalterRoster: singleAllocate 1.35> =
def singleAllocate(self,rosterDate,time,entry,nreq): if not entry: return if not self.prefsPage: return # don't allocate if no preferences if SWITCHES['verbose']: print "normalTimes are %s" % (self.normalTimes) alreadyAllocated=entry[self.allocCol] if SWITCHES['verbose']: print "singleAllocate: nreq=%d, and already allocated are %s" % (nreq,alreadyAllocated)
Chunk referenced in 1.11
Chunk defined in 1.35,1.36,1.37,1.38

If there is no entry to fill, make no allocations.

If there are no preferences, then we cannot make an allocation either.

Before making any allocations, we need to determine whether there are people already allocated. Get those already allocated into the list alreadyAllocated, which is a list of wikinames.

<PsalterRoster: class PsalterRoster: singleAllocate 1.36> =
alreadyAllocated=re.sub(' +',' ',alreadyAllocated) # remove duplicate spaces if alreadyAllocated: # non empty, split it alreadyAllocated=alreadyAllocated.split(' ') else: # empty, make null alreadyAllocated=[]
Chunk referenced in 1.11
Chunk defined in 1.35,1.36,1.37,1.38

Look at the list of those already allocated, and sanitize the entries in list, removing multiple spaces, and making a list of an empty string itself empty.

<PsalterRoster: class PsalterRoster: singleAllocate 1.37> =
<PsalterRoster: class PsalterRoster: singleAllocate, handle TBAs 1.39> if SWITCHES['verbose']: print "alreadyAllocated: got list %s " % (alreadyAllocated), print "and (new) number required=%d, " % (nreq), print "so now allocating an extra %d" % (extraNumber) if extraNumber==0: return unavails=self.getUnavailables(rosterDate) if SWITCHES['verbose']: print "Unavailable are:%s" % (unavails) quarantine=self.getQuarantines(rosterDate) excused=quarantine.keys() forthcoming=self.futureQuarantines(rosterDate) excused2=forthcoming.keys() rawallocates=self.rostercurr.buildAllocateList(rosterDate,self.preferences)
Chunk referenced in 1.11
Chunk defined in 1.35,1.36,1.37,1.38

Firstly, handle the TBAs. The TBAs are themselves obsolete, but this code also determines the extra allocations to be made, and this is returned as the value of the variable extraNumber, which is about to be used.

If that extraNumber is zero, no further allocations are required, so return. Otherwise, we need to determine who is available for allocation.

This takes several parts. Firstly, we get a list of those unavailable on this date. Then, collect who is still quarantined from a previous allocation. This is returned as a dictionary of names to weeks, so extract the keys (names) of those excused. Then scan forward to find those already allocated within the forthcoming quarantine period (there is an argument that this should be twice the quarantine period) and build a list of those forthcoming allocations. Finally, build the list of those who could be considered for allocation, the rawallocates list. These four lists are used in the next stage.

<PsalterRoster: class PsalterRoster: singleAllocate 1.38> =
if SWITCHES['verbose']: print "raw allocation list:" print " %d:%s" % (len(rawallocates),rawallocates) # start allocation proper by filling from the rawallocates list, # skipping those in the unavailable. The order should be preserved. allocates=[] for (w,n) in rawallocates: if n in excused or n in excused2: if SWITCHES['verbose']: if quarantine.has_key(n): (d,q,w)=quarantine[n] reason="in quarantine period %d weeks less than %d" % (w,q) elif forthcoming.has_key(n): reason=forthcoming[n] else: reason="(unknown)" print "Excusing %s from duty, reason=%s" % (n,reason) continue if n in unavails: if SWITCHES['verbose']: print unavails print "Unavailable person %s excluded from duty" % (n) continue else: allocates.append((w,n)) if SWITCHES['verbose']: print "Allocation List:" for (d,n) in allocates: print "%18s: %15.10f" % (n,d) <PsalterRoster: class PsalterRoster: singleAllocate, get time and number 1.40> if SWITCHES['verbose']: print "allocating %d people for time %s (index %d) on date %s" % (numberReq,time,ti,rosterDate) k=0 <PsalterRoster: class PsalterRoster: singleAllocate, allocate number required 1.41> if SWITCHES['verbose']: print "singleAllocate: allocates %s to date %s and time %s" % (alloc,rosterDate,time) #print "="*60 allocStr='' for a in alloc: allocStr += "%s " % (a) entry[self.allocCol]=allocStr.strip()
Chunk referenced in 1.11
Chunk defined in 1.35,1.36,1.37,1.38

2.10.1 PsalterRoster: class PsalterRoster: singleAllocate, handle TBAs

<PsalterRoster: class PsalterRoster: singleAllocate, handle TBAs 1.39> =
extraNumber=0; orgLength=len(alreadyAllocated) new=[] for a in alreadyAllocated: if a=='TBA' or a=='(TBA)': extraNumber+=1 else: new.append(a) alreadyAllocated=new if nreq==0: # use number of TBAs nreq=extraNumber+len(alreadyAllocated) elif extraNumber>0: nreq=orgLength else: extraNumber=nreq-len(alreadyAllocated)
Chunk referenced in 1.37

2.10.2 PsalterRoster: class PsalterRoster: singleAllocate, get time and number

<PsalterRoster: class PsalterRoster: singleAllocate, get time and number 1.40> =
# find what time to allocate to col=4 # standard names column for door roster # get index into preferences for this time try: ti=self.possibleTimes.index(time) except ValueError: print "bad time value %s on day %s" % (time,rosterDate) numberReq=nreq alloc=alreadyAllocated numberReq-=len(alreadyAllocated)
Chunk referenced in 1.38

2.10.3 PsalterRoster: class PsalterRoster: singleAllocate, allocate number required

<PsalterRoster: class PsalterRoster: singleAllocate, allocate number required 1.41> =
if SWITCHES['verbose']: print self.preferences while k<numberReq: try: for (d,n) in allocates: if ti>=0 and ti<len(self.preferences[n]): try: p=self.preferences[n][ti] except: print "Bad Preference deal at d=%s, n=%s, ti=%s:" % (d,n,ti) print self.preferences[n] p=0.0 else: if SWITCHES['verbose']: print "using temporary preferences (there are none!)" p=0.0 pair=self.pairs[n] if SWITCHES['verbose']: print "Checking preference %s for %s at time %s (pair %s)" % (p,n,time,pair) if p>0.0: # check if pairing involved if pair: # must have two spaces left to allocate pair if k<numberReq-1: # allocate both k+=1 allocates.remove((d,n)) alloc.append(n); alloc.append(pair) self.newAllocation(n,rosterDate,time,self.currentName,'alert') self.newAllocation(pair,rosterDate,time,self.currentName,'alert') raise StopIteration() else: # got a pair, but not enough spaces # remove from contention by ignoring continue else: # not a pair, proceed normally pass allocates.remove((d,n)) alloc.append(n) self.newAllocation(n,rosterDate,time,self.currentName,'alert') d=rosterDate.strftime("%Y%m%d")+":%s" % (time) q=self.quarantine[n] quarantine[n]=(d,q,0) if SWITCHES['verbose']: print "allocating %s to current duty %s" % (n,time) # now have made an allocation for this value of k raise StopIteration() # fall through for loop is BAD! print "CANNOT ALLOCATE ENOUGH PEOPLE to duty on %s at %s" % (rosterDate,time) # this doesn't work - should be in notes field #alloc.append("'''NOBODY TO ALLOCATE!'''") entry[5]="'''NOBODY TO ALLOCATE!'''" except StopIteration: pass k+=1 pass # end of allocating number required
Chunk referenced in 1.38

2.11 PsalterRoster: class PsalterRoster: newAllocation

<PsalterRoster: class PsalterRoster: newAllocation 1.42> =
def newAllocation(self,name,date,time,rosterName,flag): # first record this allocation print "making new allocation" allocKey=date.strftime("%Y%m%d")+":"+time+"@"+name # find allocation period for this person if self.allocatetime and self.allocatetime.has_key(name): allocTime=self.allocatetime[name] else: # non-preference person, leave alone allocTime=999 # check if within allocate period for this person weeks = date-self.startDate weeks = weeks.days/7 elide = (weeks>allocTime) # flag entry to keep if elide==False self.newAllocations[allocKey]=(elide,weeks,allocTime) if SWITCHES['allocate']: elideflag=('#' if elide else '') print "new allocation to %s: on %s:%s allocate %s %s" % \ (rosterName,date,time,name,elideflag) # if reminder and more than a week away, forget it if flag=='reminder' and weeks>0: return if SWITCHES['verbose']: print "NewAllocation: checks for allocate time: name=%s, date=%s:%s, weeks=%d, alloc=%d" % (name,date,time,weeks,allocTime) if weeks>allocTime: return msg="%8s:%4s@%s=%s(%s)" % (date.strftime("%Y%m%d"),time,rosterName,name,flag) print "new allocation: %s" % (msg) if name: # only write email msg if non-empty name self.emailFile.write(msg+'\n')
Chunk referenced in 1.11

2.12 PsalterRoster: class PsalterRoster: WhatHappens

WhatHappens is a stub designed to be overridden in a sub-class. It is intended to return a list of tuples, one for each time or duty on the given day, given by the list times. Each tuple contains the date, time, number of people to roster, and a blank roster entry template which may be modified and inserted into the roster as required. This blank entry must follow the standard roster entry format, viz.: [date (in datetime format), month (drawn from the list Months), day of the month, time (a string in the form HHMM in 24 hour notation), a string giving a sequence of wikinames, and a string defining any notes required (not normally used)].

<PsalterRoster: class PsalterRoster: WhatHappens 1.43> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) month=monthName(date.month) day=date.day people="JohnHurst DavidMorgan" notes="example only" nreq=3 retval=[] for t in times: blank=[date,month,day,t,people,notes] retval.append([(date,t,nreq,blank)]) return retval
Chunk referenced in 1.27
<PsalterRoster: class PsalterRoster: advanceDay 1.44> =
def advanceDay(self,day): day += dayDelta return day
Chunk referenced in 1.27

3. The Preaching Roster

This class definition of the preaching roster inherits from PsalterRoster. It is the first sub-classing such roster.

"PreachingRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/preaching.xlp instead * ## *********************************************************** ## # # the preaching roster class module # # version history # 0.1.0 20140313:103840 ajh first version # derived from PsalterRoster # 0.1.1 20140322:131625 ajh bring up to consistency with other versions # 0.1.2 20140421:124215 ajh convert to CLI SWITCHES # 0.1.3 20141208:203630 ajh move to rosterAccess6 # 0.1.4 20150411:092056 ajh version stamping preaching_version="0.1.4" # ensure this agrees with most recent version above <PreachingRoster: imports 1.2> <PreachingRoster: define global constants 1.3> <PreachingRoster: define the PreachingRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is PreachingRoster version %s" % preaching_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=PreachingRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

3.1 PreachingRoster: imports

<PreachingRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

3.2 PreachingRoster: define global constants

<PreachingRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by PreachingRoster.py, version %s" % (preaching_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/PreachingRoster v%s on %s -~" % (psalter_version,preaching_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of PreachingRoster, to avoid it becoming a wiki name.

3.3 PreachingRoster: define the PreachingRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<PreachingRoster: define the PreachingRoster class 1.4> =
class PreachingRoster(PsalterRoster): <PreachingRoster: class PreachingRoster: initialize 1.5> <PreachingRoster: class PreachingRoster: WhatHappens 1.6> <PreachingRoster: class PreachingRoster: compute preferences 1.7>
Chunk referenced in 1.1

3.3.1 PreachingRoster: class PreachingRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<PreachingRoster: class PreachingRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Preaching' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['0800','0915','1100','1630','1900'] self.possibleTimes=['0800','0830','0915','1000','1100','1430','1630','1900'] self.width=[3,2,7,50,30]
Chunk referenced in 1.4

3.3.2 PreachingRoster: class PreachingRoster: WhatHappens

<PreachingRoster: class PreachingRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Preaching, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Preaching sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

3.3.3 PreachingRoster: class PreachingRoster: compute preferences

An empty stup: no preferences.

<PreachingRoster: class PreachingRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

4. The Greeting Roster

This class definition of the Greeting roster inherits from PsalterRoster. It is the first sub-classing such roster.

"GreetingRoster.py" 1.1 =
#!/usr/bin/python ## W e l c o m e R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/Greeting.xlp instead * ## *********************************************************** ## # # the Greeting roster class module # # version history # 0.1.0 20140313:103840 ajh first version # derived from PsalterRoster # 0.1.1 20140313:225311 ajh bug elimination # 0.1.2 20140420:125027 ajh convert to CLI SWITCHES # 0.1.3 20141208:203147 ajh move to rosterAccess6 # 0.1.4 20150410:172746 ajh version stamping greeting_version="0.1.4" # ensure this agrees with most recent version above <GreetingRoster: imports 1.2> <GreetingRoster: define global constants 1.3> <GreetingRoster: define the GreetingRoster class 1.5> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is GreetingRoster version %s" % greeting_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=GreetingRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['0800','0915','1100'],\ ['0800','0830','0915','1000','1100','1430']) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

4.1 GreetingRoster: imports

<GreetingRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

4.2 GreetingRoster: define global constants

<GreetingRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by GreetingRoster.py, version %s" % (greeting_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/GreetingRoster v%s on %s -~" % (psalter_version,greeting_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of GreetingRoster, to avoid it becoming a wiki name.

4.3 GreetingRoster: define the usage routine

<GreetingRoster: define the usage routine 1.4> =
def usage(): print """ GreetingRoster.py [-d|--debug] [-v|--verbose] (Month Day)* This program ... The -v/--verbose flags ... The -d/--debug flags gives debugging output about what is happening. """

4.4 GreetingRoster: define the GreetingRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<GreetingRoster: define the GreetingRoster class 1.5> =
class GreetingRoster(PsalterRoster): <GreetingRoster: class GreetingRoster: initialize 1.6> <GreetingRoster: class GreetingRoster: WhatHappens 1.7> <GreetingRoster: class GreetingRoster: compute preferences 1.8>
Chunk referenced in 1.1

4.4.1 GreetingRoster: class GreetingRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<GreetingRoster: class GreetingRoster: initialize 1.6> =
def __init__(self,wiki=WIKI,local=None): name='Greeting' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=2 # define the times of services self.normalTimes=['0800','0915','1100'] self.possibleTimes=['0800','0830','0915','1000','1100','1430'] self.width=[3,2,7,20,40]
Chunk referenced in 1.5

4.4.2 GreetingRoster: class GreetingRoster: WhatHappens

<GreetingRoster: class GreetingRoster: WhatHappens 1.7> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.5

The purpose of WhatHappens is to define what happens on a roster day. For Greeting, this normally happens at every service on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month. (Only the day of the week is relevant here.)

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

4.4.3 GreetingRoster: class GreetingRoster: compute preferences

This routine is just a stub: all the possible times are taken care of (I think!).

<GreetingRoster: class GreetingRoster: compute preferences 1.8> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0800': newprefs[key].append(preferences[key][0]) # use 0800 value elif t=='0830': pref0800=preferences[key][0] pref0915=preferences[key][1] if pref0800+pref0915 > 0: pref0830=1.0 else: pref0830=0.0 newprefs[key].append(pref0830) # use joint 0800 and 0915 value elif t=='0915': newprefs[key].append(preferences[key][1]) # use 0915 value elif t=='1000': pref0915=preferences[key][1] pref1100=preferences[key][2] if pref0915+pref1100 > 0: pref1000=1.0 else: pref1000=0.0 newprefs[key].append(pref1000) # use joint 0915 and 1100 value elif t=='1100': newprefs[key].append(preferences[key][2]) # use 1100 value elif t=='1430': newprefs[key].append(preferences[key][2]) # use 1100 value else: print "This value of possibleTimes (%s) should NOT happen!" % (t) return newprefs
Chunk referenced in 1.5

5. The Vestry Roster

This class definition of the Vestry roster inherits from PsalterRoster. It has been copied from the CommunionRoster definition, and the names changed.

"VestryRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/Vestry.xlp instead * ## *********************************************************** ## # # the Vestry roster class module # # version history # 0.1.0 20140317:171056 ajh first version # derived from CommunionRoster # 0.1.1 20140420:124950 ajh convert to CLI SWITCHES # 0.1.2 20141208:203851 ajh move to rosterAccess6 # 0.1.3 20150409:174250 ajh remove several bugs # 0.1.4 20150410:172836 ajh version stamping vestry_version="0.1.4" # ensure this agrees with most recent version above <VestryRoster: imports 1.2> <VestryRoster: define global constants 1.3> <VestryRoster: define the VestryRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is VestryRoster version %s" % vestry_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=VestryRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['0915','1100'],['0830','0915','1000','1100','1430']) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

5.1 VestryRoster: imports

<VestryRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

5.2 VestryRoster: define global constants

<VestryRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by VestryRoster.py, version %s" % (vestry_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/VestryRoster v%s on %s -~" % (psalter_version,vestry_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of VestryRoster, to avoid it becoming a wiki name.

5.3 VestryRoster: define the VestryRoster class

This sub-class of PsalterRoster defines the Vestry-specific details of the Vestry roster. These differences are largely confined to the initialization values (see __init__, and what happens on a roster day (see WhatHappens).

<VestryRoster: define the VestryRoster class 1.4> =
class VestryRoster(PsalterRoster): <VestryRoster: class VestryRoster: initialize 1.5> <VestryRoster: class VestryRoster: WhatHappens 1.6> <VestryRoster: class VestryRoster: compute preferences 1.7>
Chunk referenced in 1.1

5.3.1 VestryRoster: class VestryRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<VestryRoster: class VestryRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Vestry' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # define the times of services self.normalTimes=['0915','1100'] self.possibleTimes=['0830','0915','1000','1100'] self.width=[3,2,7,20,30] self.noTimes=False
Chunk referenced in 1.4

5.3.2 VestryRoster: class VestryRoster: WhatHappens

<VestryRoster: class VestryRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Vestry, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Vestry sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

5.3.3 VestryRoster: class VestryRoster: compute preferences

Remap the possible time preferences into the normal time preferences by using the following table:

0830
use only 0915
1000
use both 0915 and 1100

<VestryRoster: class VestryRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0830': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='1000': pref0915=preferences[key][0] pref1100=preferences[key][1] if pref0915+pref1100 > 0: pref1000=1.0 else: pref1000=0.0 newprefs[key].append(pref1000) # use joint 0915 and 1100 value elif t=='1100': newprefs[key].append(preferences[key][1]) # use 1100 value else: print "This value of possibleTimes (%s) should NOT happen!" % (t) return newprefs
Chunk referenced in 1.4

6. The Communion Roster

This class definition of the Communion roster inherits from PsalterRoster. It is the first sub-classing such roster.

"CommunionRoster.py" 1.1 =
#!/usr/bin/python ## C o m m u n i o n R o s t e r . p y ## ## ************************************************************* ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/Communion.xlp instead * ## ************************************************************* ## # # the Communion roster class module # # version history # 0.1.0 20140313:103840 ajh first version # derived from PsalterRoster # 0.1.1 20140313:225311 ajh bug elimination # 0.1.2 20140317:170455 ajh add compute preferences routine # 0.1.3 20140318:183525 ajh first production version # 0.1.4 20140420:125107 ajh convert to CLI SWITCHES # 0.1.5 20141208:183646 ajh move to rosterAccess6 # 0.1.6 20150410:172913 ajh version stamping communion_version="0.1.6" # ensure this agrees with most recent version above <CommunionRoster: imports 1.2> <CommunionRoster: define global constants 1.3> <CommunionRoster: define the CommunionRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is CommunionRoster version %s" % communion_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=CommunionRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['0915','1100'],['0830','0915','1000','1100','1430']) testroster.Allocate() testroster.addLeader() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

6.1 CommunionRoster: imports

<CommunionRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

6.2 CommunionRoster: define global constants

<CommunionRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by CommunionRoster.py, version %s" % (communion_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/CommunionRoster v%s on %s -~" % (psalter_version,communion_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of CommunionRoster, to avoid it becoming a wiki name.

6.3 CommunionRoster: define the CommunionRoster class

This sub-class of PsalterRoster defines the communion-specific details of the communion roster. These differences are largely confined to the initialization values (see __init__, and what happens on a roster day (see WhatHappens).

<CommunionRoster: define the CommunionRoster class 1.4> =
class CommunionRoster(PsalterRoster): <CommunionRoster: class CommunionRoster: initialize 1.5> <CommunionRoster: class CommunionRoster: WhatHappens 1.6> <CommunionRoster: class CommunionRoster: compute preferences 1.7> <CommunionRoster: class CommunionRoster: addLeader 1.8>
Chunk referenced in 1.1

6.3.1 CommunionRoster: class CommunionRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<CommunionRoster: class CommunionRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Communion' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # skip name, fixed, exclusion # define the times of services self.normalTimes=['0915','1100'] self.possibleTimes=['0830','0915','1000','1100','1430']
Chunk referenced in 1.4

6.3.2 CommunionRoster: class CommunionRoster: WhatHappens

<CommunionRoster: class CommunionRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry if m==1 and t=='0915': retval[t]=(date,t,4,blank) elif t=='1000': retval[t]=(date,t,3,blank) elif m==3 and t=='1100': retval[t]=(date,t,3,blank) else: pass pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For communion, this normally happens only on Sundays, at 9.15am on the 1st Sunday of the month, and at 11.00am on the 3rd Sunday of the month. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of communion sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

6.3.3 CommunionRoster: class CommunionRoster: compute preferences

Remap the possible time preferences into the normal time preferences by using the following table:

0830
use only 0915
1000
use both 0915 and 1100
1430
use only 1100

<CommunionRoster: class CommunionRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes if SWITCHES['verbose']: print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0830': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='1000': pref0915=preferences[key][0] pref1100=preferences[key][1] if pref0915+pref1100 > 0: pref1000=1.0 else: pref1000=0.0 newprefs[key].append(pref1000) # use joint 0915 and 1100 value elif t=='1100': newprefs[key].append(preferences[key][1]) # use 1100 value elif t=='1430': newprefs[key].append(preferences[key][1]) # use 1100 value else: print "This value of possibleTimes (%s) should NOT happen!" % (t) return newprefs
Chunk referenced in 1.4

6.4 addLeader

<CommunionRoster: class CommunionRoster: addLeader 1.8> =
def addLeader(self): print "\nChecking Roster Entries" for e in self.rostercurr.entries: print e print "Done Checking Roster Entries\n"
Chunk referenced in 1.4

7. The Door Roster

This class definition of the Door roster inherits from PsalterRoster. It has been copied from the CommunionRoster definition, and the names changed.

"DoorRoster.py" 1.1 =
#!/usr/bin/python ## D o o r R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/Door.xlp instead * ## *********************************************************** ## # # the Door roster class module # # version history # 0.1.0 20140318:195326 ajh first version # derived from VestryRoster # 0.1.1 20140420:174811 ajh convert to CLI SWITCHES # 0.1.2 20141208:183732 ajh move to rosterAccess6 # 0.1.3 20150410:172549 ajh version stamping door_version="0.1.3" # ensure this agrees with most recent version above <DoorRoster: imports 1.2> <DoorRoster: define global constants 1.3> <DoorRoster: define the DoorRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is DoorRoster version %s" % door_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=DoorRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['0915','1100'],['0830','0915','1000','1100','1430']) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

7.1 DoorRoster: imports

<DoorRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

7.2 DoorRoster: define global constants

<DoorRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by DoorRoster.py, version %s" % (door_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/DoorRoster v%s on %s -~" % (psalter_version,door_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of DoorRoster, to avoid it becoming a wiki name.

7.3 DoorRoster: define the DoorRoster class

This sub-class of PsalterRoster defines the Door-specific details of the Door roster. These differences are largely confined to the initialization values (see __init__, and what happens on a roster day (see WhatHappens).

<DoorRoster: define the DoorRoster class 1.4> =
class DoorRoster(PsalterRoster): <DoorRoster: class DoorRoster: initialize 1.5> <DoorRoster: class DoorRoster: WhatHappens 1.6> <DoorRoster: class DoorRoster: compute preferences 1.7>
Chunk referenced in 1.1

7.3.1 DoorRoster: class DoorRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<DoorRoster: class DoorRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Door' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # define the times of services self.normalTimes=['0915','1100'] self.possibleTimes=['0830','0915','1000','1100','1430'] self.width=[3,2,7,30,30]
Chunk referenced in 1.4

7.3.2 DoorRoster: class DoorRoster: WhatHappens

<DoorRoster: class DoorRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry nreq=2 retval[t]=(date,t,nreq,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Door, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Door sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

7.3.3 DoorRoster: class DoorRoster: compute preferences

Remap the possible time preferences into the normal time preferences by using the following table:

0830
use only 0915
1000
use both 0915 and 1100

<DoorRoster: class DoorRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0830': newprefs[key].append(preferences[key][2]) # use wildcard elif t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='1000': pref0915=preferences[key][0] pref1100=preferences[key][1] if pref0915+pref1100 > 0: pref1000=1.0 else: pref1000=0.0 newprefs[key].append(pref1000) # use joint 0915 and 1100 value elif t=='1100': newprefs[key].append(preferences[key][1]) # use 1100 value elif t=='1430': newprefs[key].append(preferences[key][2]) # use wildcard else: print "This value of possibleTimes (%s) should NOT happen!" % (t) if SWITCHES['debug']: print newprefs return newprefs
Chunk referenced in 1.4

8. The Sound Roster

This class definition of the Sound roster inherits from PsalterRoster. It has been copied from the CommunionRoster definition, and the names changed.

"SoundRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/Sound.xlp instead * ## *********************************************************** ## # # the Sound roster class module # # version history # 0.1.0 20140319:115033 ajh first version # derived from VestryRoster, # to which it is almost identical # 0.1.1 20140420:175419 ajh convert to CLI SWITCHES # 0.1.2 20141208:203718 ajh move to rosterAccess6 # 0.1.3 20150410:225045 ajh version stamping sound_version="0.1.3" # ensure this agrees with most recent version above <SoundRoster: imports 1.2> <SoundRoster: define global constants 1.3> <SoundRoster: define the SoundRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is SoundRoster version %s" % sound_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=SoundRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['0915','1100'],['0830','0915','1000','1100','1430']) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

8.1 SoundRoster: imports

<SoundRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

8.2 SoundRoster: define global constants

<SoundRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by SoundRoster.py, version %s" % (sound_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/SoundRoster v%s on %s -~" % (psalter_version,sound_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of SoundRoster, to avoid it becoming a wiki name.

8.3 SoundRoster: define the SoundRoster class

This sub-class of PsalterRoster defines the Sound-specific details of the Sound roster. These differences are largely confined to the initialization values (see __init__, and what happens on a roster day (see WhatHappens).

<SoundRoster: define the SoundRoster class 1.4> =
class SoundRoster(PsalterRoster): <SoundRoster: class SoundRoster: initialize 1.5> <SoundRoster: class SoundRoster: WhatHappens 1.6> <SoundRoster: class SoundRoster: compute preferences 1.7>
Chunk referenced in 1.1

8.3.1 SoundRoster: class SoundRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<SoundRoster: class SoundRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Sound' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # define the times of services self.normalTimes=['0915','1100'] self.possibleTimes=['0830','0915','1000','1100'] self.width=[3,2,7,20,30]
Chunk referenced in 1.4

8.3.2 SoundRoster: class SoundRoster: WhatHappens

<SoundRoster: class SoundRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Sound, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Sound sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

8.3.3 SoundRoster: class SoundRoster: compute preferences

Remap the possible time preferences into the normal time preferences by using the following table:

0830
use only 0915
1000
use both 0915 and 1100

<SoundRoster: class SoundRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0830': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value elif t=='1000': pref0915=preferences[key][0] pref1100=preferences[key][1] if pref0915+pref1100 > 0: pref1000=1.0 else: pref1000=0.0 newprefs[key].append(pref1000) # use joint 0915 and 1100 value elif t=='1100': newprefs[key].append(preferences[key][1]) # use 1100 value else: print "This value of possibleTimes (%s) should NOT happen!" % (t) return newprefs
Chunk referenced in 1.4

9. The Kids Roster

This class definition of the kids roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"KidsRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/kids.xlp instead * ## *********************************************************** ## # # the kids roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from PreachingRoster # 0.1.1 20140421:113543 ajh convert to CLI SWITCHES # 0.1.2 20141208:203336 ajh move to rosterAccess6 # 0.1.3 20150411:084531 ajh version stamping kids_version="0.1.3" # ensure this agrees with most recent version above <KidsRoster: imports 1.2> <KidsRoster: define global constants 1.3> <KidsRoster: define the KidsRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is KidsRoster version %s" % kids_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=KidsRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

9.1 KidsRoster: imports

<KidsRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

9.2 KidsRoster: define global constants

<KidsRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by KidsRoster.py, version %s" % (kids_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/KidsRoster v%s on %s -~" % (psalter_version,kids_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of KidsRoster, to avoid it becoming a wiki name.

9.3 KidsRoster: define the KidsRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<KidsRoster: define the KidsRoster class 1.4> =
class KidsRoster(PsalterRoster): <KidsRoster: class KidsRoster: initialize 1.5> <KidsRoster: class KidsRoster: WhatHappens 1.6> <KidsRoster: class KidsRoster: compute preferences 1.7>
Chunk referenced in 1.1

9.3.1 KidsRoster: class KidsRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<KidsRoster: class KidsRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Kids' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['0915'] self.possibleTimes=['0915','1000'] self.width=[3,2,7,40,30]
Chunk referenced in 1.4

9.3.2 KidsRoster: class KidsRoster: WhatHappens

<KidsRoster: class KidsRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Kids, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Kids sundays (the two if statements).

Central to the idea of this routine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

9.3.3 KidsRoster: class KidsRoster: compute preferences

An empty stup: no preferences.

<KidsRoster: class KidsRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

10. The Music Roster

This class definition of the music roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"MusicRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/music.xlp instead * ## *********************************************************** ## # # the music roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from PreachingRoster # 0.1.1 20140421:124511 ajh convert to CLI SWITCHES # 0.1.2 20141208:203545 ajh move to rosterAccess6 # 0.1.3 20150411:092436 ajh version stamping music_version="0.1.3" # ensure this agrees with most recent version above <MusicRoster: imports 1.2> <MusicRoster: define global constants 1.3> <MusicRoster: define the MusicRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is MusicRoster version %s" % music_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=MusicRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

10.1 MusicRoster: imports

<MusicRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

10.2 MusicRoster: define global constants

<MusicRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by MusicRoster.py, version %s" % (music_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/MusicRoster v%s on %s -~" % (psalter_version,music_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of MusicRoster, to avoid it becoming a wiki name.

10.3 MusicRoster: define the MusicRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<MusicRoster: define the MusicRoster class 1.4> =
class MusicRoster(PsalterRoster): <MusicRoster: class MusicRoster: initialize 1.5> <MusicRoster: class MusicRoster: WhatHappens 1.6> <MusicRoster: class MusicRoster: compute preferences 1.7>
Chunk referenced in 1.1

10.3.1 MusicRoster: class MusicRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<MusicRoster: class MusicRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Music' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['0800','1100'] self.possibleTimes=['0800','1000','1100'] self.width=[3,2,7,25,35]
Chunk referenced in 1.4

10.3.2 MusicRoster: class MusicRoster: WhatHappens

<MusicRoster: class MusicRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Music, this normally happens at every 0800 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Music sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

10.3.3 MusicRoster: class MusicRoster: compute preferences

An empty stup: no preferences.

<MusicRoster: class MusicRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

11. The Bible Roster

This class definition of the bible roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"BibleRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## ********************************************************* ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/bible.xlp instead * ## ********************************************************* ## # # the bible roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from PreachingRoster # 0.1.1 20140423:152651 ajh convert to CLI SWITCHES # 0.1.2 20141124:163503 ajh fix bug with default times # 0.1.3 20141208:183438 ajh move to rosterAccess6 # 0.1.4 20150411:092705 ajh version stamping bible_version="0.1.4" # ensure this agrees with most recent version above <BibleRoster: imports 1.2> <BibleRoster: define global constants 1.3> <BibleRoster: define the BibleRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is BibleRoster version %s" % bible_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=BibleRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

11.1 BibleRoster: imports

<BibleRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

11.2 BibleRoster: define global constants

<BibleRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by BibleRoster.py, version %s" % (bible_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/BibleRoster v%s on %s -~" % (psalter_version,bible_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of BibleRoster, to avoid it becoming a wiki name.

11.3 BibleRoster: define the BibleRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<BibleRoster: define the BibleRoster class 1.4> =
class BibleRoster(PsalterRoster): <BibleRoster: class BibleRoster: initialize 1.5> <BibleRoster: class BibleRoster: WhatHappens 1.6> <BibleRoster: class BibleRoster: compute preferences 1.7>
Chunk referenced in 1.1

11.3.1 BibleRoster: class BibleRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<BibleRoster: class BibleRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Bible' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['0915','1100'] self.possibleTimes=['0915','1000','1100'] self.noTimes=False self.width=[3,2,7,20,20]
Chunk referenced in 1.4

11.3.2 BibleRoster: class BibleRoster: WhatHappens

<BibleRoster: class BibleRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Bible, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Bible sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

11.3.3 BibleRoster: class BibleRoster: compute preferences

An empty stup: no preferences.

<BibleRoster: class BibleRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

12. The Hospitality Roster

This class definition of the hospitality roster inherits from PsalterRoster. It was the first sub-classing roster without a preferences file, but as of version 0.2ff it now does have a preferences page.

"HospitalityRoster.py" 1.1 =
#!/usr/bin/python ## H o s p i t a l i t y R o s t e r . p y ## ## *************************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/hospitality.xlp instead * ## *************************************************************** ## # # the hospitality roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from KidsRoster # 0.1.1 20140423:154037 ajh convert to CLI SWITCHES # 0.2.0 20140629:132412 ajh add preferences page # 0.3.0 20141201:140629 ajh add multiple times # 0.3.1 20141208:203250 ajh move to rosterAccess6 # 0.3.2 20150411:084339 ajh version stamping hospitality_version="0.3.2" # ensure this agrees with most recent version above <HospitalityRoster: imports 1.2> <HospitalityRoster: define global constants 1.3> <HospitalityRoster: define the HospitalityRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is HospitalityRoster version %s" % hospitality_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) theroster=HospitalityRoster(WIKI,SWITCHES['local']) theroster.loadPrevious() theroster.loadCurrent() theroster.loadPreferences() theroster.loadUnavailables() theroster.setDates(startDate,allocateDate,endDate) theroster.Allocate() theroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

12.1 HospitalityRoster: imports

<HospitalityRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

12.2 HospitalityRoster: define global constants

<HospitalityRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by HospitalityRoster.py, version %s" % (hospitality_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/HospitalityRoster v%s on %s -~" % (psalter_version,hospitality_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of HospitalityRoster, to avoid it becoming a wiki name.

12.3 HospitalityRoster: define the HospitalityRoster class

This is the real story. Most of the real work happens within this super class.

<HospitalityRoster: define the HospitalityRoster class 1.4> =
class HospitalityRoster(PsalterRoster): <HospitalityRoster: class HospitalityRoster: initialize 1.5> <HospitalityRoster: class HospitalityRoster: WhatHappens 1.6> <HospitalityRoster: class HospitalityRoster: compute preferences 1.7>
Chunk referenced in 1.1

12.3.1 HospitalityRoster: class HospitalityRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<HospitalityRoster: class HospitalityRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Hospitality' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=2 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # define the times of services self.normalTimes=['0915'] self.possibleTimes=['0915','1000','1100'] self.width=[3,2,7,40,30]
Chunk referenced in 1.4

12.3.2 HospitalityRoster: class HospitalityRoster: WhatHappens

The purpose of WhatHappens is to define what happens on a roster day. For Hospitality, this normally happens at every 0915 and 1100 services on Sundays - however, the same team does both the 9.15 and 11 services, and so the roster only shows a 9.15 duty. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Hospitality sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

<HospitalityRoster: class HospitalityRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The roster only applies to sundays, so if the day is not a sunday (day of the week d==6), return immediately

12.3.3 HospitalityRoster: class HospitalityRoster: compute preferences

<HospitalityRoster: class HospitalityRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value if t=='1000': newprefs[key].append(preferences[key][1]) # use 1000 value if t=='1100': newprefs[key].append(preferences[key][1]) # use 1000 value return newprefs
Chunk referenced in 1.4

13. The Morning Roster

This class definition of the morning roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"MorningRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/morning.xlp instead * ## *********************************************************** ## # # the morning roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from Roster # 0.1.1 20140421:112743 ajh convert to CLI SWITCHES # 0.1.2 20141208:203455 ajh move to rosterAccess6 # 0.1.3 20150411:084045 ajh version stamping morning_version="0.1.3" # ensure this agrees with most recent version above <MorningRoster: imports 1.2> <MorningRoster: define global constants 1.3> <MorningRoster: define the MorningRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is MorningRoster version %s" % morning_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) theroster=MorningRoster(WIKI,SWITCHES['local']) theroster.loadPrevious() theroster.loadCurrent() theroster.loadPreferences() theroster.loadUnavailables() theroster.setDates(startDate,allocateDate,endDate) theroster.Allocate() theroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

13.1 MorningRoster: imports

<MorningRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

13.2 MorningRoster: define global constants

<MorningRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by MorningRoster.py, version %s" % (morning_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/MorningRoster v%s on %s -~" % (psalter_version,morning_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of MorningRoster, to avoid it becoming a wiki name.

13.3 MorningRoster: define the MorningRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<MorningRoster: define the MorningRoster class 1.4> =
class MorningRoster(PsalterRoster): <MorningRoster: class MorningRoster: initialize 1.5> <MorningRoster: class MorningRoster: WhatHappens 1.6> <MorningRoster: class MorningRoster: compute preferences 1.7>
Chunk referenced in 1.1

13.3.1 MorningRoster: class MorningRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<MorningRoster: class MorningRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Morning' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # not used # define the times of services self.normalTimes=['1000'] self.possibleTimes=['1000','1100'] self.width=[3,2,7,50,20]
Chunk referenced in 1.4

13.3.2 MorningRoster: class MorningRoster: WhatHappens

<MorningRoster: class MorningRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,3,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Morning, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Morning sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

13.3.3 MorningRoster: class MorningRoster: compute preferences

<MorningRoster: class MorningRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] newprefs[key].append(preferences[key][0]) # use 1000 value return newprefs
Chunk referenced in 1.4

14. The Finance Roster

This class definition of the finance roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"FinanceRoster.py" 1.1 =
#!/usr/bin/python ## F i n a n c e R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/finance.xlp instead * ## *********************************************************** ## # # the finance roster class module # # version history # 0.1.0 20140326:110741 ajh first version # derived from MorningRoster # 0.1.1 20140405:092821 ajh set to only one counting for # both morning services # 0.1.2 20140423:153213 ajh convert to CLI SWITCHES # 0.1.3 20141208:203027 ajh move to rosterAccess6 # 0.1.4 20150410:173001 ajh version stamping finance_version="0.1.4" # ensure this agrees with most recent version above <FinanceRoster: imports 1.2> <FinanceRoster: define global constants 1.3> <FinanceRoster: define the FinanceRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is FinanceRoster version %s" % finance_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=FinanceRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.loadPreferences() testroster.loadUnavailables() testroster.setDates(startDate,allocateDate,endDate) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

14.1 FinanceRoster: imports

<FinanceRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

14.2 FinanceRoster: define global constants

<FinanceRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by FinanceRoster.py, version %s" % (finance_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/FinanceRoster v%s on %s -~" % (psalter_version,finance_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of FinanceRoster, to avoid it becoming a wiki name.

14.3 FinanceRoster: define the FinanceRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<FinanceRoster: define the FinanceRoster class 1.4> =
class FinanceRoster(PsalterRoster): <FinanceRoster: class FinanceRoster: initialize 1.5> <FinanceRoster: class FinanceRoster: WhatHappens 1.6> <FinanceRoster: class FinanceRoster: compute preferences 1.7>
Chunk referenced in 1.1

14.3.1 FinanceRoster: class FinanceRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<FinanceRoster: class FinanceRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Finance' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=3 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # not used # define the times of services self.normalTimes=['1100'] self.possibleTimes=['1000','1100'] self.width=[3,2,7,50,20]
Chunk referenced in 1.4

14.3.2 FinanceRoster: class FinanceRoster: WhatHappens

<FinanceRoster: class FinanceRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=6: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Finance, this normally happens at every 0915 and 1100 services on Sundays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Finance sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

14.3.3 FinanceRoster: class FinanceRoster: compute preferences

Compy preferences straight through.

<FinanceRoster: class FinanceRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes return preferences
Chunk referenced in 1.4

15. The Cleaning Roster

This class definition of the cleaning roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"CleaningRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/cleaning.xlp instead * ## *********************************************************** ## # # the cleaning roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from PreachingRoster # 0.1.1 20140423:154315 ajh convert to CLI SWITCHES # 0.1.2 20141208:183556 ajh move to rosterAccess6 # 0.1.3 20150411:115413 ajh versionm stamping cleaning_version="0.1.3" # ensure this agrees with most recent version above <CleaningRoster: imports 1.2> <CleaningRoster: define global constants 1.3> <CleaningRoster: define the CleaningRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is CleaningRoster version %s" % cleaning_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=CleaningRoster(WIKI,SWITCHES['local']) testroster.loadPrevious(True) testroster.loadCurrent(True) testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['Area1','Area2','Area3','Mowing'],[]) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

15.1 CleaningRoster: imports

<CleaningRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

15.2 CleaningRoster: define global constants

<CleaningRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by CleaningRoster.py, version %s" % (cleaning_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/CleaningRoster v%s on %s -~" % (psalter_version,cleaning_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of CleaningRoster, to avoid it becoming a wiki name.

15.3 CleaningRoster: define the CleaningRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<CleaningRoster: define the CleaningRoster class 1.4> =
class CleaningRoster(PsalterRoster): <CleaningRoster: class CleaningRoster: initialize 1.5> <CleaningRoster: class CleaningRoster: WhatHappens 1.6> <CleaningRoster: class CleaningRoster: compute preferences 1.7>
Chunk referenced in 1.1

15.3.1 CleaningRoster: class CleaningRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<CleaningRoster: class CleaningRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Cleaning' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['Area1','Area2','Area3','Mowing'] self.possibleTimes=[] self.width=[3,2,7,40,20]
Chunk referenced in 1.4

15.3.2 CleaningRoster: class CleaningRoster: WhatHappens

<CleaningRoster: class CleaningRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=5: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==5: hour=0; minute=0 dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) print "retval=%s (t=%s)" % (retval[t],t) pass # if d==5 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Cleaning, this normally happens on Saturdays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Cleaning sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

15.3.3 CleaningRoster: class CleaningRoster: compute preferences

An empty stup: no preferences.

<CleaningRoster: class CleaningRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

16. The Leisure Roster

This class definition of the leisure roster inherits from PsalterRoster. It is the first sub-classing roster without a preferences file.

"LeisureRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/leisure.xlp instead * ## *********************************************************** ## # # the leisure roster class module # # version history # 0.1.0 20140321:102646 ajh first version # derived from PreachingRoster # 0.1.1 20140423:154315 ajh convert to CLI SWITCHES # 0.1.2 20141208:203418 ajh move to rosterAccess6 # 0.1.3 20150411:115637 ajh version stamping leisure_version="0.1.3" # ensure this agrees with most recent version above <LeisureRoster: imports 1.2> <LeisureRoster: define global constants 1.3> <LeisureRoster: define the LeisureRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is LeisureRoster version %s" % leisure_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=LeisureRoster(WIKI,SWITCHES['local']) testroster.loadPrevious(True) testroster.loadCurrent(True) testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['Drivers','Hosts','Kitchen','Casserole','Sweet','Birthday Cake'],[]) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

16.1 LeisureRoster: imports

<LeisureRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

16.2 LeisureRoster: define global constants

<LeisureRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by LeisureRoster.py, version %s" % (leisure_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/LeisureRoster v%s on %s -~" % (psalter_version,leisure_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of LeisureRoster, to avoid it becoming a wiki name.

16.3 LeisureRoster: define the LeisureRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<LeisureRoster: define the LeisureRoster class 1.4> =
class LeisureRoster(PsalterRoster): <LeisureRoster: class LeisureRoster: initialize 1.5> <LeisureRoster: class LeisureRoster: WhatHappens 1.6> <LeisureRoster: class LeisureRoster: compute preferences 1.7>
Chunk referenced in 1.1

16.3.1 LeisureRoster: class LeisureRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<LeisureRoster: class LeisureRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Leisure' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['Drivers','Hosts','Kitchen','Casserole','Sweet','Birthday Cake'] self.possibleTimes=[] self.width=[3,2,14,70,20]
Chunk referenced in 1.4

16.3.2 LeisureRoster: class LeisureRoster: WhatHappens

<LeisureRoster: class LeisureRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=0: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==0: hour=0; minute=0 dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,1,blank) print "retval=%s (t=%s)" % (retval[t],t) pass # if d==0 pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For Leisure, this normally happens on Saturdays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of Leisure sundays (the two if statements).

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

16.3.3 LeisureRoster: class LeisureRoster: compute preferences

An empty stup: no preferences.

<LeisureRoster: class LeisureRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

17. The Hub Roster

This class definition of the morning roster inherits from PsalterRoster.

"TheHubRoster.py" 1.1 =
#!/usr/bin/python ## P r e a c h i n g R o s t e r . p y ## ## *********************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/thehub.xlp instead * ## *********************************************************** ## # # the thehub roster class module # # version history # 0.1.0 20140625:162517 ajh first version # derived from VestryRoster # 0.2.0 20141105:112858 ajh add first and last reminder dates # 0.2.1 20141208:203809 ajh move to rosterAccess6 # 0.2.2 20150411:115851 ajh version stamping thehub_version="0.2.2" # ensure this agrees with most recent version above <TheHubRoster: imports 1.2> <TheHubRoster: define global constants 1.3> <TheHubRoster: define the TheHubRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is TheHubRoster version %s" % thehub_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) testroster=TheHubRoster(WIKI,SWITCHES['local']) testroster.loadPrevious() testroster.loadCurrent() testroster.setDates(startDate,allocateDate,endDate) testroster.setTimes(['1000','1200'],[]) testroster.Allocate() testroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

17.1 TheHubRoster: imports

<TheHubRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

17.2 TheHubRoster: define global constants

<TheHubRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by TheHubRoster.py, version %s" % (thehub_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/TheHubRoster v%s on %s -~" % (psalter_version,thehub_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of TheHubRoster, to avoid it becoming a wiki name.

17.3 TheHubRoster: define the TheHubRoster class

This is the real story. It is the first example of a PsalterRoster sub-class.

<TheHubRoster: define the TheHubRoster class 1.4> =
class TheHubRoster(PsalterRoster): <TheHubRoster: class TheHubRoster: initialize 1.5> <TheHubRoster: class TheHubRoster: WhatHappens 1.6> <TheHubRoster: class TheHubRoster: compute preferences 1.7>
Chunk referenced in 1.1

17.3.1 TheHubRoster: class TheHubRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<TheHubRoster: class TheHubRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='TheHub' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=1 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=0 # not used # define the times of services self.normalTimes=['1000','1200'] self.possibleTimes=[] self.width=[3,2,10,50,5,20] # make reminder dates Monday to Sunday self.firstReminder=self.startDate+datetime.timedelta(days=1) self.lastReminder=self.firstReminder+datetime.timedelta(days=6) if SWITCHES['debug']: print "start day = %s" % (self.startDate) print "first reminder day = %s" % (self.firstReminder) print "last reminder day = %s" % (self.lastReminder)
Chunk referenced in 1.4

17.3.2 TheHubRoster: class TheHubRoster: WhatHappens

<TheHubRoster: class TheHubRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d<1 or d>3: # day of the week must be Tue,Wed,Thu return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d in [1,2,3]: hour=int(t[0:2]); minute=0 if not (d==2 and hour==12): dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) weekday=dt.strftime("%a") blank=[dt,mon,dy,t,p,weekday,n] # new roster entry retval[t]=(date,t,1,blank) print "d=%d,retval=%s (t=%s)" % (d,retval[t],t) else: print "skipping 1200 for Wednesdays" pass # if d in ... pass # for t return retval
Chunk referenced in 1.4

The purpose of WhatHappens is to define what happens on a roster day. For TheHub, this normally happens at 1000 and 1200 on Tuesdays and Thursday, and only 1000 on Wednesdays. The Hub roster has an extra column to define the day of the week, and this must be filled in at this time. This is computed from whatDay (d,m) are the values day of the month, and weekday in the month, which allow for the calculation of TheHub roster entries.

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

17.3.3 TheHubRoster: class TheHubRoster: compute preferences

An empty stup: no preferences.

<TheHubRoster: class TheHubRoster: compute preferences 1.7> =
def computePreferences(self,preferences): return []
Chunk referenced in 1.4

18. The Flowers Roster

This class definition of the flowers roster inherits from PsalterRoster. It is currently of limited functionality.

"FlowersRoster.py" 1.1 =
#!/usr/bin/python ## F l o w e r s R o s t e r . p y ## ## *************************************************************** ## * DO NOT EDIT THIS FILE! * ## * Use $HOME/Church/Computers/rosters/flowers.xlp instead * ## *************************************************************** ## # # the flowers roster class module # # version history # 0.0.0 20150411:114437 ajh first version, derived from HospitalityRoster 0.3.2 flowers_version="0.0.0" # ensure this agrees with most recent version above <FlowersRoster: imports 1.2> <FlowersRoster: define global constants 1.3> <FlowersRoster: define the FlowersRoster class 1.4> if __name__ == "__main__": rosterAccess6.DEBUG=SWITCHES['debug'] print "This is FlowersRoster version %s" % flowers_version print "Using rosterAccess version %s" % rosterAccess6.rosterAccess_version (dates,startDate,allocateDate,endDate)=getCLIparms(SWITCHES) setDEBUG(SWITCHES['debug']) theroster=FlowersRoster(WIKI,SWITCHES['local']) theroster.loadPrevious(noTimes=True) theroster.loadCurrent(noTimes=True) #theroster.loadPreferences() #theroster.loadUnavailables() theroster.setDates(startDate,allocateDate,endDate) theroster.Allocate() theroster.close() ## ## The End ##

The following three subsections are fairly self explanatory, and pass without further comment.

18.1 FlowersRoster: imports

<FlowersRoster: imports 1.2> =
from PsalterRoster import *
Chunk referenced in 1.1

18.2 FlowersRoster: define global constants

<FlowersRoster: define global constants 1.3> =
timeStamp=datetime.datetime.now() # define logging messages autoLogMsg="auto-update by FlowersRoster.py, version %s" % (flowers_version) logMsg="~- This page automatically generated by !PsalterRoster v%s/FlowersRoster v%s on %s -~" % (psalter_version,flowers_version,timeStamp)
Chunk referenced in 1.1

Note the ! required in front of FlowersRoster, to avoid it becoming a wiki name.

18.3 FlowersRoster: define the FlowersRoster class

This is the real story. Most of the real work happens within this super class.

<FlowersRoster: define the FlowersRoster class 1.4> =
class FlowersRoster(PsalterRoster): <FlowersRoster: class FlowersRoster: initialize 1.5> <FlowersRoster: class FlowersRoster: WhatHappens 1.6> <FlowersRoster: class FlowersRoster: compute preferences 1.7>
Chunk referenced in 1.1

18.3.1 FlowersRoster: class FlowersRoster: initialize

For details of how this class works, please see the meta-class definition PsalterRoster.

<FlowersRoster: class FlowersRoster: initialize 1.5> =
def __init__(self,wiki=WIKI,local=None): name='Flowers' # build the roster instance PsalterRoster.__init__(self,name,logMsg,wiki,local) self.nreq=2 self.ncols=5 # (month, day, time, rosterees, notes) self.allocCol=4 self.prefsCol=3 # define the times of services self.normalTimes=[] self.possibleTimes=[] self.width=[3,2,7,40,30]
Chunk referenced in 1.4

18.3.2 FlowersRoster: class FlowersRoster: WhatHappens

The purpose of WhatHappens is to define what happens on a roster day. For Flowers, this normally happens on Saturdays. The result returned from whatDay (d,m) are the values day of the month, and weekday in the month.

Central to the idea of this rourine is that it can calculate an appropriate blank entry for the roster table blank, which is returned as part of the list of result tuples, one for each time of duty on the day (the parameter times).

<FlowersRoster: class FlowersRoster: WhatHappens 1.6> =
def WhatHappens(self,date,times): (d,m)=whatDay(date) if d!=5: return None mon=monthName(date.month) retval={}; p=''; n='' for t in times: if d==6: hour=int(t[0:2]); minute=int(t[2:4]) dt=datetime.datetime(date.year,date.month,date.day,hour,minute) dy="%s" % (date.day) blank=[dt,mon,dy,t,p,n] # new roster entry retval[t]=(date,t,2,blank) pass # if d==6 pass # for t return retval
Chunk referenced in 1.4

The roster only applies to sundays, so if the day is not a sunday (day of the week d==6), return immediately

18.3.3 FlowersRoster: class FlowersRoster: compute preferences

<FlowersRoster: class FlowersRoster: compute preferences 1.7> =
def computePreferences(self,preferences): normal=self.normalTimes possible=self.possibleTimes print "normal=%s\npossible=%s" % (normal,possible) prefkeys=preferences.keys() newprefs={} for key in prefkeys: newprefs[key]=[] for t in possible: if t=='0915': newprefs[key].append(preferences[key][0]) # use 0915 value if t=='1000': newprefs[key].append(preferences[key][1]) # use 1000 value if t=='1100': newprefs[key].append(preferences[key][1]) # use 1000 value return newprefs
Chunk referenced in 1.4

19. The Makefile

Define your Makefile here.

"Makefile" 2.1 =
default=program Rosters = rosters psalter preaching greeting vestry communion door \ sound kids music bible hospitality morning finance cleaning \ leisure thehub GenFiles = .program install-program include ${HOME}/etc/MakeXLP install-program: program.tangle cp .program ${HOME} touch install-program install: install-program all: rosters.tangle rosters.tangle: $(patsubst rosters.tangle,,$(patsubst %,%.tangle,$(Rosters))) %.tangle: %.xlp xsltproc --xinclude $(XSLLIB)/litprog.xsl $*.xlp >$*.xml touch $*.tangle RosterDependencies=$(patsubst %,%.xlp,$(Rosters)) rosters.xml: $(RosterDependencies) -echo $(RosterDependencies) xsltproc --xinclude $(XSLLIB)/litprog.xsl rosters.xlp >rosters.xml touch $*.tangle %.xml: %.tangle #rosters.exec: .rosters.tangle # chmod a+x $^ # BibleRoster.py: bible.tangle GreetingRoster.py: greeting.tangle HospitalityRoster.py: hospitality.tangle MusicRoster.py: music.tangle PsalterRoster.py: psalter.tangle TheHubRoster.py: thehub.tangle VestryRoster.py: vestry.tangle FlowersRoster.py: flowers.tangle eregnans: $(wildcard *Roster.py) runRosters.sh \ mailConsolidated.py mailReminders.py reminder.py rsync -auv $? eregnans:/home/ajh/Church/Computers/rosters/ touch eregnans clean: litclean -rm $(GenFiles)

20. Document History

20140225:122643 ajh 0.0 first draft
20140227:114657 ajh 0.1 first pass at PsalterRoster
20140301:155012 ajh 0.1.1 debugging of date normalization
20140313:172406 ajh 0.1.2 added preaching and welcome sub-classes
20140317:125010 ajh 0.1.3 added singleAllocate to generic roster, and got communion working with that
20140322:085426 ajh 0.1.4 first production system, with welcome, vestry, communion, door, sound and kids rosters all working
20140429:114101 ajh 0.1.5 add leisure time roster
20140626:163835 ajh 0.1.6 add the hub roster
20140627:124158 ajh 0.1.7 revise Makefile
20141205:121512 ajh 0.1.8 changed all references from roster welcome to roster greeting
20150411:114131 ajh 0.2.0 add flowers roster

21. Indices

21.1 Files

File Name Defined in
BibleRoster.py 1.1
CleaningRoster.py 1.1
CommunionRoster.py 1.1
DoorRoster.py 1.1
FinanceRoster.py 1.1
FlowersRoster.py 1.1
GreetingRoster.py 1.1
HospitalityRoster.py 1.1
KidsRoster.py 1.1
LeisureRoster.py 1.1
Makefile 2.1
MorningRoster.py 1.1
MusicRoster.py 1.1
PreachingRoster.py 1.1
PsalterRoster.py 1.2
SoundRoster.py 1.1
TheHubRoster.py 1.1
VestryRoster.py 1.1

21.2 Chunks

Chunk Name Defined in Used in
Allocate: deal with existing entry 1.32 1.28
Allocate: deal with no existing entries 1.33 1.28
Allocate: handle allocation periods 1.34 1.31
BibleRoster: class BibleRoster: WhatHappens 1.6 1.4
BibleRoster: class BibleRoster: compute preferences 1.7 1.4
BibleRoster: class BibleRoster: initialize 1.5 1.4
BibleRoster: define global constants 1.3 1.1
BibleRoster: define the BibleRoster class 1.4 1.1
BibleRoster: imports 1.2 1.1
CleaningRoster: class CleaningRoster: WhatHappens 1.6 1.4
CleaningRoster: class CleaningRoster: compute preferences 1.7 1.4
CleaningRoster: class CleaningRoster: initialize 1.5 1.4
CleaningRoster: define global constants 1.3 1.1
CleaningRoster: define the CleaningRoster class 1.4 1.1
CleaningRoster: imports 1.2 1.1
CommunionRoster: class CommunionRoster: WhatHappens 1.6 1.4
CommunionRoster: class CommunionRoster: addLeader 1.8 1.4
CommunionRoster: class CommunionRoster: compute preferences 1.7 1.4
CommunionRoster: class CommunionRoster: initialize 1.5 1.4
CommunionRoster: define global constants 1.3 1.1
CommunionRoster: define the CommunionRoster class 1.4 1.1
CommunionRoster: imports 1.2 1.1
DoorRoster: class DoorRoster: WhatHappens 1.6 1.4
DoorRoster: class DoorRoster: compute preferences 1.7 1.4
DoorRoster: class DoorRoster: initialize 1.5 1.4
DoorRoster: define global constants 1.3 1.1
DoorRoster: define the DoorRoster class 1.4 1.1
DoorRoster: imports 1.2 1.1
FinanceRoster: class FinanceRoster: WhatHappens 1.6 1.4
FinanceRoster: class FinanceRoster: compute preferences 1.7 1.4
FinanceRoster: class FinanceRoster: initialize 1.5 1.4
FinanceRoster: define global constants 1.3 1.1
FinanceRoster: define the FinanceRoster class 1.4 1.1
FinanceRoster: imports 1.2 1.1
FlowersRoster: class FlowersRoster: WhatHappens 1.6 1.4
FlowersRoster: class FlowersRoster: compute preferences 1.7 1.4
FlowersRoster: class FlowersRoster: initialize 1.5 1.4
FlowersRoster: define global constants 1.3 1.1
FlowersRoster: define the FlowersRoster class 1.4 1.1
FlowersRoster: imports 1.2 1.1
GreetingRoster: class GreetingRoster: WhatHappens 1.7 1.5
GreetingRoster: class GreetingRoster: compute preferences 1.8 1.5
GreetingRoster: class GreetingRoster: initialize 1.6 1.5
GreetingRoster: define global constants 1.3 1.1
GreetingRoster: define the GreetingRoster class 1.5 1.1
GreetingRoster: define the usage routine 1.4
GreetingRoster: imports 1.2 1.1
HospitalityRoster: class HospitalityRoster: WhatHappens 1.6 1.4
HospitalityRoster: class HospitalityRoster: compute preferences 1.7 1.4
HospitalityRoster: class HospitalityRoster: initialize 1.5 1.4
HospitalityRoster: define global constants 1.3 1.1
HospitalityRoster: define the HospitalityRoster class 1.4 1.1
HospitalityRoster: imports 1.2 1.1
KidsRoster: class KidsRoster: WhatHappens 1.6 1.4
KidsRoster: class KidsRoster: compute preferences 1.7 1.4
KidsRoster: class KidsRoster: initialize 1.5 1.4
KidsRoster: define global constants 1.3 1.1
KidsRoster: define the KidsRoster class 1.4 1.1
KidsRoster: imports 1.2 1.1
LeisureRoster: class LeisureRoster: WhatHappens 1.6 1.4
LeisureRoster: class LeisureRoster: compute preferences 1.7 1.4
LeisureRoster: class LeisureRoster: initialize 1.5 1.4
LeisureRoster: define global constants 1.3 1.1
LeisureRoster: define the LeisureRoster class 1.4 1.1
LeisureRoster: imports 1.2 1.1
MorningRoster: class MorningRoster: WhatHappens 1.6 1.4
MorningRoster: class MorningRoster: compute preferences 1.7 1.4
MorningRoster: class MorningRoster: initialize 1.5 1.4
MorningRoster: define global constants 1.3 1.1
MorningRoster: define the MorningRoster class 1.4 1.1
MorningRoster: imports 1.2 1.1
MusicRoster: class MusicRoster: WhatHappens 1.6 1.4
MusicRoster: class MusicRoster: compute preferences 1.7 1.4
MusicRoster: class MusicRoster: initialize 1.5 1.4
MusicRoster: define global constants 1.3 1.1
MusicRoster: define the MusicRoster class 1.4 1.1
MusicRoster: imports 1.2 1.1
PreachingRoster: class PreachingRoster: WhatHappens 1.6 1.4
PreachingRoster: class PreachingRoster: compute preferences 1.7 1.4
PreachingRoster: class PreachingRoster: initialize 1.5 1.4
PreachingRoster: define global constants 1.3 1.1
PreachingRoster: define the PreachingRoster class 1.4 1.1
PreachingRoster: imports 1.2 1.1
PsalterRoster: class PsalterRoster: Allocate 1.27 1.11
PsalterRoster: class PsalterRoster: Allocate: allocate across all dates 1.28 1.27
PsalterRoster: class PsalterRoster: Allocate: extract consolidated roster 1.30 1.27
PsalterRoster: class PsalterRoster: Allocate: remove tentative allocations 1.31 1.27
PsalterRoster: class PsalterRoster: Allocate: sort allocated entries 1.29 1.27
PsalterRoster: class PsalterRoster: WhatHappens 1.43 1.27
PsalterRoster: class PsalterRoster: advanceDay 1.44 1.27
PsalterRoster: class PsalterRoster: buildAllocateList 1.21 1.11
PsalterRoster: class PsalterRoster: close 1.26 1.11
PsalterRoster: class PsalterRoster: dutiesForToday 1.22 1.11
PsalterRoster: class PsalterRoster: extractCoordinator 1.16 1.11
PsalterRoster: class PsalterRoster: futureQuarantines 1.25 1.11
PsalterRoster: class PsalterRoster: getQuarantines 1.24 1.11
PsalterRoster: class PsalterRoster: getUnavailables 1.23 1.11
PsalterRoster: class PsalterRoster: initialize 1.12, 1.13, 1.14, 1.15 1.11
PsalterRoster: class PsalterRoster: load Preferences 1.17 1.11
PsalterRoster: class PsalterRoster: load Unavailables 1.18 1.11
PsalterRoster: class PsalterRoster: newAllocation 1.42 1.11
PsalterRoster: class PsalterRoster: setDates method 1.19 1.11
PsalterRoster: class PsalterRoster: setTimes method 1.20 1.11
PsalterRoster: class PsalterRoster: singleAllocate 1.35, 1.36, 1.37, 1.38 1.11
PsalterRoster: class PsalterRoster: singleAllocate, allocate number required 1.41 1.38
PsalterRoster: class PsalterRoster: singleAllocate, get time and number 1.40 1.38
PsalterRoster: class PsalterRoster: singleAllocate, handle TBAs 1.39 1.37
PsalterRoster: define global constants 1.5 1.2
PsalterRoster: define the CLI parameters routine 1.8 1.2
PsalterRoster: define the PsalterRoster class 1.11 1.2
PsalterRoster: define the date parameter routine 1.7 1.2
PsalterRoster: define the usage routine 1.6 1.2
PsalterRoster: define the weekday and month name routines 1.9 1.2
PsalterRoster: define the wikiname routines 1.10 1.2
PsalterRoster: imports 1.4 1.2
PsalterRoster: main announcement and parameter extraction 1.3 1.2
SoundRoster: class SoundRoster: WhatHappens 1.6 1.4
SoundRoster: class SoundRoster: compute preferences 1.7 1.4
SoundRoster: class SoundRoster: initialize 1.5 1.4
SoundRoster: define global constants 1.3 1.1
SoundRoster: define the SoundRoster class 1.4 1.1
SoundRoster: imports 1.2 1.1
TheHubRoster: class TheHubRoster: WhatHappens 1.6 1.4
TheHubRoster: class TheHubRoster: compute preferences 1.7 1.4
TheHubRoster: class TheHubRoster: initialize 1.5 1.4
TheHubRoster: define global constants 1.3 1.1
TheHubRoster: define the TheHubRoster class 1.4 1.1
TheHubRoster: imports 1.2 1.1
VestryRoster: class VestryRoster: WhatHappens 1.6 1.4
VestryRoster: class VestryRoster: compute preferences 1.7 1.4
VestryRoster: class VestryRoster: initialize 1.5 1.4
VestryRoster: define global constants 1.3 1.1
VestryRoster: define the VestryRoster class 1.4 1.1
VestryRoster: imports 1.2 1.1

21.3 Identifiers

Identifier Defined in Used in
Months 1.9
entry 1.35 1.35
futureAllocations 1.25
normalTimes 1.12
nreq 1.35
rosterDate 1.35
singleAllocate 1.35 1.32, 1.33
time 1.35

94 accesses since 17 Sep 2016, HTML cache rendered at 20150411:1643