club3 is a Common Lisp program that schedule a series of meetings of the Toastmaster™ type fairly.
Get club3 from GitHub:
git clone https://github.com/ds26gte/club3
This produces
a directory club3,
which contains a shell script called club3. Put a copy of or soft link to
club3 in your PATH.
The subdirectory lisp contains several Lisp files, the main file
being club3.lisp. If you move club3.lisp to a different
location, you must ensure that all the other .lisp files in the
lisp subdirectory move with it, as code in club3.lisp assumes
that they are in the same directory as itself.
The script club3 sets and uses an environment variable CLUB3DIR to
identify the directory containing club3.lisp. Change it to suit you.
The script club3 also sets and uses an environment variable LISP to identify
your Common Lisp system. Change to suit you.
Optional: You may compile club3.lisp (using the Common Lisp function
compile‑file). Only club3.lisp need be explicitly compiled — it will
take care of compiling the other .lisp files.
In a directory that contains all the data relevant to your club, call
club3 with the argument schedules or profiles.
The data in the current directory serves as input to both calls, and
influences the result. This input data consists of an init file
club3rc.lisp and a subdirectory past‑schedules which contains the
scheduling history of the club. For an example, see
the directory examples/articulators in the distribution.
% club3 schedules
generates schedules. (I.e., type club3 schedules at your Unix
prompt %.)
(For convenience, the argument schedules can be dropped:
calling club3 is equivalent to calling club3 schedules.)
The schedules consist of a .txt file for each date
in the calendar (see below), and an HTML file new‑schedules.html
that lists the schedules as an easy-to-read and -print table.
For a saved example, see
examples/articulators/current-schedules.html in the club3 distro.
The generated .txt files can be inserted into past‑schedules (see
below) to influence the course of future schedules (created by future
calls to club3 schedules). Before putting a .txt file into
past‑schedules, it is a good idea to edit this file by hand to ensure
that it accurately records the members who actually performed the roles
(since club members typically negotiate substitutions up to the very
last minute because of unforeseen circumstances).
% club3 profiles
generates member profiles in a subdirectory called profiles, browsable
via an index.html file.
Example: examples/articulators/profiles/index.html in the distro.
The init file club3rc.lisp is essential. At the very least, it must
use the operators members, lineups, and calendar to respectively list
the members in the club; the type of meeting lineups used; and the
calendar dates for which schedules are desired.
Other operators such as availability, expertise, eagerness,
role‑difficulty, and number‑of‑trials are optional: However it is useful
to invoke some or all of them so that the generated schedules are both
equitable and feasible.
The operators full‑names, role‑names, and club‑name help make the output
schedules more readable.
Example: examples/articulators/club3rc.lisp in the distro.
The operator members lists the members in the club, a symbol for each
member. E.g.,
(members andrew brad charlie dan eileen fran george hilary ian judy
kim lori marcia nelson oliver prasad rick stephanie theresa
ulrich vanessa willy xavier yul zoe)
The operator lineups introduces the various prescribed lineups for the
club’s meetings. Each lineup is a list whose first element is a keyword
describing the type of meeting, and whose second element is a list of
roles.
(lineups (:default
(tm word joke poet speaker speaker speaker
ge eval eval eval topics)))
Roles can repeat: the above lineup allows for three speakers and three
evals.
The first lineup is considered the default lineup (even if it wasn’t
explicitly named :default). lineups can take more arguments if different
lineups are possible (for different meetings).
(lineups (:default ...)
(:topics-tuesday
(tm word joke poet speaker speaker ge eval eval topics)))
This specifies a meeting of type :topics‑tuesday, which differs from the
:default meeting in that its lineup has only two speakers and two evals.
(The purpose of this meeting is plausibly to allow more time for topics
than is possible in a :default meeting.)
A type of meeting called :superset may be optionally added. This
specifies the list of roles that will be specified in the HTML table
generated by club3 schedules for a sequence of meetings, which may be
of different type. The idea is to list all the roles possible in all
the meetings, and have the column for each meeting fill only the row
that is relevant to it.
The operator calendar contains a series of dates for which meetings need
to be scheduled. Each date is given as a dotted integer YYYY.MM.DD; thus,
2009.02.24 is 2009 Feb 24.
(calendar 2009.03.03
2009.03.10)
If the type of meeting to be held on a particular date differs from the
:default type of meeting, then the date is enclosed as the first element
in a list whose second element is the type of meeting. E.g.,
(calendar 2009.03.03
(2009.03.10 :contest)
2009.03.17
2009.03.24
(2009.03.31 :skills-night)
(2009.04.07 :topics-tuesday))
The type of meeting is typically one of the keywords introduced by the
lineups operator (see above). However, it can be anything. If it isn’t one
of the keywords introduced by lineups, then no schedule is presumed
for that date. (This is useful to mark certain dates for special
activities that do not require scheduling, or have been scheduled by
other means.)
The availability operator contains exceptional information about the
temporal availability of the members. By default, members are assumed
to be always available. To override this for any member, that member’s
symbol is associated with an expression denoting
when that member is available.
Availability expressions are given in prefix notation using operators
such as after, always, before, between, from,
on, and until whose arguments
are dates or the results of other operations.
and, not, and or are also allowed, but can only operate on the results of
other operations (i.e., not directly on dates). [Use on if you find
yourself wanting to use and/not/or directly on dates. E.g.,
(not (on 2009.03.17)).]
Dates are either in YYYY.MM.DD format or the keyword
:further‑notice (= ∞).
Examples:
(availabilty (kim (not (between 2009.03.10 2009.03.21))))
states that kim will not be available from March 3 to 21, 2009
(inclusive).
(availability (kim ...)
(theresa (not (until :further-notice))))
says that theresa will not be available until further notice.
(availability (kim ...)
(theresa ...)
(kumaran (and (not (on 2009.02.24 2009.04.28
2009.05.26 2009.06.30))
(not (between 2009.03.10
2009.03.31)))))
states that kumaran will not be available on Feb 24, April 28, May 26, and
June 30 of 2009; and also between March 10 and 31 of 2009.
Using the role‑difficulty operator, you can identify specific roles to
be either :easy, :intermediate, or :hard. By default, all roles are
considered to be :easy. Example entries:
(role-difficulty (speaker :intermediate)
(eval :hard))
By default, every member is considered to be capable of doing all and
only the :easy roles To modify this, use the expertise operator. Each
argument is a list whose first element is the member name and whose
subsequent elements are the additional roles they can tackle beyond the
:easy roles. E.g.,
(expertise (rick topics eval))
If you entered :intermediate or :hard instead of a role name, all roles
that are intermediate or hard, respectively, are intended.
(expertise (rick ...)
(connie :intermediate))
The keyword :all stands for all roles. Thus to encode the fact that
bash can do anything:
(expertise (rick ...)
(connie ...)
(bash :all))
You may also enter a list such as (‑ speaker topics) instead of a role.
This requests that the speaker and topics roles be removed from that
member’s expertise.
(expertise (rick ...)
(connie ...)
(bash ...)
(judyk (- speaker topics)))
By default, all members are supposed to exhibit the same moderate level
of eagerness to be scheduled. However, using the eagerness operator,
you can add an eagerness level to a member. Each argument consists of
the member name followed by a number between 0 and 3 inclusive: 0 for
inactive; 1 for taking it slow; 2 for normal (the default); and 3 for
fast track. E.g.,
(eagerness (joseph 3)
(judyk 1)
(theresa 0))
states that theresa is inactive, judyk wants to take it slow, and joseph
wants the fast track.
club3 uses a system that ascribes demerits to each member for a role on
any day (based on history, availability, etc). Since a lineup is filled
in a certain order, earlier roles may beat out later roles in getting
their best-suited members, leading to overall higher demerits. club3
tries to counter this by picking among eligible members using an
geometrically decaying distribution among them, so a member with higher
demerits could still snag a role. club3 then generates more, different
lineups, to see if another lineup with fewer overall demerits
materializes.
By default, club3 generates 400 trial lineups for each meeting date, and
picks the best among them. club3 output will also indicate the ordinal
n of the eventually picked lineup, so you get an idea if the number of
trials is reasonable. (If n is too close to 400, and the best lineup
was updated many times, perhaps more trials are needed.)
To change the number of trials, use the number‑of‑trials operator, e.g.,
(number-of-trials 200)
The symbol used for each member is typically capitalized when printing
out schedules or profiles; thus, charlie becomes “Charlie” and marcia
becomes “Marcia”. However, in certain cases, naive capitalization may
be inaccurate or unsightly. For instance, if there are several members
with the same (first) name, the members operator may have used an
initial to disambiguate, e.g., judyk and judyp for two different Judys.
To properly capitalize them in the output schedule (so the Judys don’t
judge you), use the full‑names operator:
(full-names (judyk "Judy K")
(judyp "Judy P"))
You may add more complicated names:
(full-names (judyk ...)
(judyp ...)
(kelvin "William Thomson, First Baron Kelvin"))
For routine scheduling, however, a first name, sometimes enhanced with a disambiguating initial, is usually enough.
lineups uses abbreviated symbols for each role. When printing out the
schedule as an HTML file, the roles are capitalized, and if there are
multiple instances of a role in a meeting, each role instance gets a number suffix
(e.g., “Speaker #2”). If you want the printed name for a role to be
different than the mere capitalization of the role symbol, you may use
the role‑names operator, e.g.,
(role-names (tm "Toastmaster")
(eval "Evaluator"))
The name of the club is set using the operator club‑name. This name is
used to title the schedules.
(club-name "The Articulators")
The subdirectory past‑schedules contains the schedules (the .txt files)
generated for previous calendars — i.e., by previous calls to club3 schedules. While optional, past‑schedules is useful to maintain, as
it provides the historical information necessary to ensure that no
member is over- or underscheduled.
club3 runs on any Common Lisp in a Unix-like environment, e.g.,
ABCL,
CLISP,
Clozure CL,
CMUCL,
MKCL,
ECL, and
SBCL.
(On Windows, you can get a Unix
environment via Cygwin, which also
provides CLISP.)