Standard Tournament Generator¶
sage-tools-api/apps-script/standard-tournament-generator.gs — bound Apps Script that builds
one facility-day's standard-tournament workbook from a Tournament Calculator
plan CSV. Implements
standard-tournament-master-spec.md;
its §18 records where the code departs from it.
It is bound to the SAGE Standard Tournament Master workbook (spec §14),
whose hidden _CATEGORY_TEMPLATE is an unchanged copy of the Pickle for
Sight Annex's HIMD tab: every category tab takes its formats from fixed
cells of it (TEMPLATE_PROTO).
Same arrangement as the Dual Meet Sheet Generator:
it lives in the API repo for versioning, runs inside a workbook, and ships by
being pasted into the master's script project beside sheets-sync.gs.
Changing it is not a deploy. It needs a master for the same reason — the tabs
run on the workbook-level Named Function library,
here in its 22-function standard form (spec §11), with the row-5-label
MATCHCOURT.
A separate file, not a mode¶
The two generators share no code at runtime: each master carries only its
own. standard-tournament-generator.gs copies dual-meet-generator.gs's pure helpers
verbatim (CSV parsing, colLetter_, the progress log, assertTabsPristine_)
and rewrites everything shaped around clubs. The onOpen block is the one
piece duplicated verbatim across all three .gs files, because a workbook
carries sheets-sync.gs plus one generator and the last-loaded onOpen
wins.
Pipeline¶
parsePlanCsv ─► validatePlan ─► planCategory (per ticked category)
│
computeTabLayout_ ─► packSchedule_ (in memory: sizes SCHEDULE)
│
category tabs → Variables → Title → Reference for Players → MATCHES → SCHEDULE
→ CSV → STANDINGSCSV → Court Control → Timeline → ATTENDANCE
ATTENDANCE is created empty, in the format event attendance
fills (buildAttendanceTab_); a tab of that name already there is left alone.
Everything left of the Sheets writes is pure. planCategory is where every
playoff shape lives, and the only part with real logic.
planCategory¶
Ports the calculator's groupSizes, reduceToTarget, solveForTarget,
tieredRounds and playoffPlan (keep them in step with
tools/tournament-calculator.html), then:
- Round robin from
RR_GUIDE_, SAGE's bracket guide (the RR tab of the BRACKET GUIDE workbook): per bracket size, which pairs meet in each round and in what order. A size the guide lacks (8, 11-13) falls back to the circle method, with a warning. Brackets are uneven, so each carries its own first-pair index, and the guide's pair numbers are offset from it. Codes run<KEY>_1 … <KEY>_<teams>unbroken, bracket 1 first. - Seat numbering (spec §7.6.1). Stages are walked from the final back, with one counter shared by every stage's fresh seats, so a drawn slot number identifies exactly one seat. Fed seats take the other half of a fresh partner's pair, or the lowest free number. Each stage's fed seats are wired to the previous stage's winners in block-label order.
- Shapes without a ladder: best-of-3 (2 pairs), twice-to-beat (one
bracket, the default) with a bronze walkover against
BYE, and round-robin only.
It also reports the MATCHES rows and band width, every team code the tab
holds (which sizes STANDINGSCSV), and any validation error: a ladder
deeper than R32, more than 32 entrants, or a code or label that would
repeat.
Formats come from the template¶
Nothing in the script sets a colour or a font. buildCategoryTab copies the
template tab, lays its all-black row 34 over everything below the chrome,
then copies each block's format from its fixed prototype cell in
TEMPLATE_PROTO: pair rows, grid cells, banners, match blocks, the roster,
the draw and the slot table. checkTemplate_ refuses to run unless
_CATEGORY_TEMPLATE shows the reference HIMD tab's banner labels where
TEMPLATE_PROTO expects them. Merges are broken in two pieces that cut
through none of the template's — A:AA from row 6, AB onward from row 4 —
since Sheets refuses to unmerge a range that splits a merge.
applyTemplateRules_ stretches the template's own conditional formats to
each tab's height and drops the two pinned to HIMD's cells.
Forward propagation¶
A standard ladder can't pull its entrants from a sorted group block the way
a dual meet's playoff does — there is a draw and a chain of results in the
way. So each block pushes its winner forward: column K evaluates to the
code of the seat the winner moves to, and a fed seat finds its names with
FILTER over the previous stage's B/K rows. Fresh seats read the
qualifier scaffold (AO/AP), which resolves a drawn slot number through
the slot table (AT:AV). A two-bracket category's slot numbers are written
in rather than drawn (fixedSeating_), because its semifinals are a fixed
crossover.
MATCHES and the packer¶
MATCHES is a copy of the pristine SCHEDULE prototype, tiled to one
band per category, so pasting from it into SCHEDULE is a plain copy.
Nothing reads it. Row labels sit in the separator column left of each band,
out of the way of a paste. Block headers come from blockLabels: a bracket's
round-robin matches always sit under that bracket's floor(n_b / 2) blocks,
because every round of a bracket fills exactly that many and brackets run
largest first.
SCHEDULE and MATCHES both lift their conditional formats off before
tiling and restore them afterwards with one range per block column; tiled
with copyTo, each rule would list every cell separately and
setConditionalFormatRules fails outright past a few hundred.
packSchedule_ places every match in waves, taking turns between categories,
never reordering a category's queue, holding each playoff stage until the one
before it has finished, and keeping one slot clear after a round robin for
the qualifier draw. Its only outputs are SCHEDULE's height (slots + 4
spare) and the suggested layout in the execution log. It reproduces the
Pickle for Sight days at 33 and 29 slots.
In place, once¶
SCHEDULE, Court Control, Timeline, CSV, STANDINGSCSV and
Reference for Players are grown from prototypes already in the master
(REBUILT_IN_PLACE_TABS), because the sync watches SCHEDULE's and
Court Control's GIDs. A second run is refused before anything is written —
by the pristine check, and by the category tabs and MATCHES colliding by
name.
After every tab is built, workbookName_ renames the spreadsheet to
<date> <title> - <FACILITY> from the plan and the facility label; a failed
or rejected run leaves the copy's name alone. dual-meet-generator.gs carries
the same helper, with the venue label.
CSV gets its header and one formula row with A2 blank. SAGE → Fill match
numbers (in sheets-sync.gs) copies that row down to one row per match.
Slot times on SCHEDULE and Timeline's headers both chain off
Variables!C4 + Variables!$C$6, never literals, so COUNTPAIRAT's exact
match holds.
Importing bracket draws¶
SAGE → Import bracket draws runs after generation, shares nothing with
generateEventTabs beyond the trace and progress-log helpers, and lives in
the same file. It sits outside the PROP_TABS_GENERATED_FOR guard, so unlike
Generate event tabs it stays in the menu — a re-draw is imported over the
top with Replace existing names.
The pipeline is pure up to the last step. parseDrawText_ reads one of the
Bracket Generator's .txt exports, anchored on the Drawn … line in the
first six lines: the category is the line above it, brackets and pairs are
read in file order, and parsing stops at TO CHECK THIS DRAW YOURSELF. It
never throws — an unreadable file comes back carrying its own errors.
splitPairNames_ splits a pair line on the earliest of /, &, +, , or
and, and reports when it found no separator rather than failing.
readPlanBlock_ reads Variables!I11:L<last> — key, display value, teams,
brackets — and flags the rows that have a real tab, since Variables lists
every category in the plan CSV including the other venue's.
resolveDrawCategory_ matches a file's category line against the key, then
the display name, both normalised (trim, upper-case, collapse whitespace); an
operator-picked assignment beats both.
validateDraws_ runs every check across every file before anything is
written and returns all failures at once, including the emptiness of
AD5:AD<4 + 2 × teams> unless replace is set, and bracket sizes against
groupSizes_(teams, brackets) — the same function the tab was built with, so
the comparison is against the tab as built. Only then does writeDrawNames_
run, three setValues calls per category and no formatting: AD (names),
AI (codes) and AB2:AB4 (seed, draw, import stamp).
AI is written in identity order, and only ever together with AD from the
same file. That is safe because the rows arrive in the order a seeded,
published draw produced — the randomising already happened, and shuffling
again would break the bracket assignment. There is deliberately no code path
that fills AI on its own.
The fingerprints in the file are not re-verified; they are what an outsider
checks the draw with. AB2:AB4's provenance is the useful half — it ties the
workbook to that file.
Progress and version¶
The sidebar's progress bar and timed log work as the
dual meet generator's
do, from the same copied log block. A standard run counts each category tab,
Variables, Title, Reference for Players, MATCHES, SCHEDULE and the five readout
tabs. The file carries STANDARD_TOURNAMENT_GENERATOR_VERSION, shown at the
foot of the sidebar and in SAGE → Help, and bumped with every change to
the file.
Verifying a change¶
Runs the real generateEventTabs against apps-script/mock-apps-script.mjs,
fed the two Pickle for Sight plan CSVs in apps-script/fixtures/, plus a
reconstructed BKL Day 2 plan. It checks the spec's §16 figures, the ladder
cells of spec §7.5 in the tab, every one of the 105 multi-bracket
combinations §9 allows, the packer against the waves, and the packed
schedule through sheets-sync.gs's real planMatchNumbers_.
sheets-sync.gs is loaded into the same context first, so a name clash
between the two files fails there. The mock does not evaluate formulas.
Change a prototype shape in REBUILT_IN_PLACE_TABS and the fixture in the
script has to change with it.
The import scenario covers the draw import end to end against files built
byte-shaped like the tool's real exportAsText() output: parsing, the five
separators, the written cells, and each refusal in turn. Because the mock
evaluates no formulas it proves the values and the geometry, not that the tab
recalculates — that the B column resolves names through the AE/AI link
needs a real workbook.
Features: Standard Tournament Generator usage
Spec: standard-tournament-master-spec.md