Documentation
¶
Overview ¶
Package-level note: excel.go implements the page-layout algorithm that writes pool match and elimination match data into an Excel workbook.
Layout model:
"Pool Matches" sheet: courts are placed side-by-side (8 columns each). Pools are rendered top-to-bottom within each court column. A soft page-break is inserted whenever the next pool block would overflow PoolMatchesRowsPerPage rows. Vertical page breaks separate courts so the sheet prints as distinct pages.
"Elimination Matches" section: elimination rounds are laid out top-to-bottom with all courts side-by-side. A new page break is inserted when the next match block would overflow EliminationRowsPerPage.
Tree sheets ("Tree 1", "Tree 2", …): one sheet per bracket segment. Leaf values reference pool-match winner cells via CONCATENATE formulas so the bracket updates automatically when scores are entered.
Row-count thresholds and layout constants are defined in constants.go.
CHK037 — Kachinuki Excel rendering decision (T160 + T195–T203):
The main Pool Matches / Elimination Matches sheets continue to use the 8-column-per-court layout invariant (CourtsColumnsPerCourt = 8 — see constants.go and CLAUDE.md). Variable-bout kachinuki grids would either overflow that budget or force a layout-mode switch the rest of the workbook can't accommodate, so the main sheets carry the team-match row only.
Bout-by-bout detail is rendered on a separate "Kachinuki Detail" sheet (helper.SheetKachinukiDetail). See internal/helper/excel_kachinuki.go — the sheet uses a flexible 8-column layout (NOT bound by CourtsColumnsPerCourt) and is opt-in: the engine export path (internal/engine/export.go → collectKachinukiMatches) emits it only when comp.TeamMatchType == kachinuki AND at least one match carries bouts. CLI export paths (cmd/create-pools.go, create-playoffs.go) are kachinuki-agnostic and produce zero changes to existing example files.
Index ¶
- Constants
- Variables
- func AddPlayerDataToSheet(f *excelize.File, players []Player, sanitize bool, titlePrefix string) map[string]playerCellCoord
- func AddPoolDataToSheet(f *excelize.File, pools []Pool, sanitize bool, titlePrefix string) (map[string]cellCoord, map[string]playerCellCoord)
- func AddPoolsToSheet(f *excelize.File, pools []Pool, poolCoords map[string]cellCoord, ...) error
- func AddPoolsToTree(f *excelize.File, sheetName string, pools []Pool, ...)
- func ApplySeeds(players []Player, assignments []domain.SeedAssignment) error
- func AssignPlayerNumbers(players []Player, prefix string, start int) int
- func AssignPoolsToCourts(numPools, numCourts int) ([]int, error)
- func CalculateDepth(node *Node) int
- func CheckDuplicateEntries(input []string) []string
- func ConvertPlayersToWinners(players []Player, sanitized bool, pCoords map[string]playerCellCoord) map[string]MatchWinner
- func CourtLabel(i int) string
- func CreateNamesToPrint(f *excelize.File, players []Player, sanitized bool, numCourts int, ...)
- func CreateNamesWithPoolToPrint(f *excelize.File, pools []Pool, sanitized bool, numCourts int, ...)
- func CreatePartialPoolMatches(pools []Pool)
- func CreatePoolMatches(pools []Pool)
- func CreatePoolRoundRobinMatches(pools []Pool)
- func CreateTagsSheet(f *excelize.File, pools []Pool) error
- func CreateTreeBracket(f *excelize.File, sheet string, col int, startRow int, size int) string
- func FillEstimations(f *excelize.File, numPools int64, totalPoolMatches int64, teamSize int64, ...)
- func FillInMatches(f *excelize.File, eliminationMatchRounds [][]*Node)
- func GenerateFinals(pools []Pool, poolWinners int) []string
- func GetBorderStyleBottomLeft(f *excelize.File) int
- func GetBorderStyleLeft(f *excelize.File) int
- func IsUUIDv4(s string) bool
- func MatchHeader(f *excelize.File, sheetName string, startColName string, poolRow int, ...)
- func NewUUID4() string
- func NextPow2(n int) int
- func ParseSeedsFile(filePath string) ([]domain.SeedAssignment, error)
- func PrintLeafNodes(node *Node, f *excelize.File, sheetName string, startCol int, startRow int, ...)
- func PrintPoolMatches(f *excelize.File, pools []Pool, teamMatches int, numWinners int, numCourts int, ...) map[string]MatchWinner
- func PrintTeamEliminationMatches(f *excelize.File, poolMatchWinners map[string]MatchWinner, ...)
- func ProtectAllSheets(f *excelize.File)
- func ProtectSheets(f *excelize.File, sheetNames []string)
- func ReadEntriesFromFile(filePath string) ([]string, error)
- func RemoveDuplicates(input []string) []string
- func RoundToPowerOf2(x, y float64) (int, error)
- func SanitizeName(name string) string
- func SetSheetLayoutLandscapeA3(f *excelize.File, sheetName string)
- func SetSheetLayoutPortraitA4(f *excelize.File, sheetName string)
- func SetSheetLayoutPortraitA4Centered(f *excelize.File, sheetName string)
- func SetSheetLayoutPortraitA4DownThenOver(f *excelize.File, sheetName string, numCourts int)
- func SetTreeSheetTitle(f *excelize.File, sheetName string, title string)
- func SubtreeCourtIndex(numSubtrees, numCourts, idx int) int
- func TreePageLayout(numPlayers, numCourts int, singleTree bool) (int, error)
- func ValidateCourts(n int) error
- func WriteKachinukiDetailSheet(f *excelize.File, matches []KachinukiMatchDetail) error
- type KachinukiBout
- type KachinukiMatchDetail
- type Match
- type MatchWinner
- type Node
- type Player
- type Pool
- type RowStack
- type Stack
Constants ¶
const ( // PoolMatchesRowsPerPage is the soft row budget before inserting a page // break on the Pool Matches sheet. PoolMatchesRowsPerPage = 45 // PoolSpaceLines is the number of blank rows added after the pool header // before the first match block. PoolSpaceLines = 3 // PoolDrawRowsPerPage is the soft row budget before inserting a page // break on the Pool Draw sheet. PoolDrawRowsPerPage = 42 )
Pool match sheet layout constants.
const ( // EliminationRowsPerPage is the soft row budget before inserting a page // break in the elimination-match section. EliminationRowsPerPage = 44 // EliminationSpaceLines is the number of blank rows printed between // elimination match rounds. EliminationSpaceLines = 5 // EliminationMatchHeight is the row-height of a single individual // elimination match block. EliminationMatchHeight = 8 // EliminationTeamMatchHeightBase is added to the team-match count to get // the row-height of a team elimination match block. EliminationTeamMatchHeightBase = 11 )
Elimination match layout constants.
const ( DefaultPort = 8080 DefaultWinners = 2 DefaultPoolSize = 3 DefaultCourts = 2 )
Default flag values used by CLI commands and the web handler.
const ( MinDateYear = 1900 MaxDateYear = 2100 )
MinDateYear / MaxDateYear are the inclusive bounds on the year component of tournament + competition dates. The mobile-app HTTP handlers (validateDateDMY in handlers_tournament.go) enforce these on every write path so a value the API accepts is also one the admin UI can edit. Without matching bounds, a direct API/import write landing an out-of-range date would block every subsequent admin Settings save — the JS validator re-validates the stored date on every PUT and surfaces an inline error before reaching the wire.
Mirrored client-side as `MIN_YEAR` / `MAX_YEAR` in web-mobile/js/admin_helpers.jsx — keep all four in lockstep. Pin tests on both sides assert the literal values so cross-language drift fails CI rather than waiting for a date-related UX bug.
const ( SheetData = "data" SheetTimeEstimator = "Time Estimator" SheetPoolDraw = "Pool Draw" SheetPoolMatches = "Pool Matches" SheetEliminationMatches = "Elimination Matches" SheetNamesToPrint = "Names to Print" SheetTags = "Tags" SheetTree = "Tree" SheetKachinukiDetail = "Kachinuki Detail" )
Sheet names for every tab in the workbook. Use these constants wherever a sheet name is needed so that a rename only requires one edit here.
SheetKachinukiDetail is opt-in: only emitted by the engine export path when a competition has teamMatchType=kachinuki AND at least one kachinuki match has bout data to display. See excel_kachinuki.go (T199–T203).
const CourtsColumnsPerCourt = 8
CourtsColumnsPerCourt is the number of Excel columns allocated to each court (Shiaijo) on the Pool Matches and Elimination Matches sheets. Layout: Name | V | P | vs | P | V | Name | Spacer = 8 columns.
const MaxCourts = 26
MaxCourts is the upper bound for the number of Shiaijo (courts). It comes from the single-letter A–Z labelling used on Shiaijo headers throughout the workbook; values above this are rejected up front by ValidateCourts so we never silently truncate a user-requested layout.
Mirrored client-side as `MAX_COURTS` in web-mobile/js/admin_helpers.jsx — keep the two in lockstep. The JS side is anchored by a comment back here so changes are visible at both edit points.
const MaxPlayersPerTree = 16
MaxPlayersPerTree is the maximum leaf count on a single tree sheet. 16 players form a balanced bracket that fits on one A4 landscape page.
const MaxRankOverride = 1000
MaxRankOverride is the absolute upper bound for a manual rank override submitted via PUT /api/competitions/:id/pools/:poolId/override-rank. The override-rank handler ALSO validates against the actual pool size (the real semantic constraint — rank within a pool must be in [1..N] where N is the number of players in that pool). This cap is a defense-in-depth overflow guard for the rare case where pools have not been generated yet or LoadPools returns stale/unexpected data.
Mirrored client-side as `MAX_RANK` in web-mobile/js/admin_helpers.jsx — keep the two in lockstep. 1000 is arbitrary; no real pool has 1000+ participants.
const TreeTitleRows = 3
TreeTitleRows is the number of rows reserved at the top of every tree sheet for the user to add a title. Content starts below this offset.
Variables ¶
var MobileWebFs embed.FS
var WebFs embed.FS
Functions ¶
func AddPlayerDataToSheet ¶ added in v0.3.0
func AddPoolDataToSheet ¶ added in v0.3.0
func AddPoolsToSheet ¶
func AddPoolsToTree ¶
func ApplySeeds ¶ added in v0.11.0
func ApplySeeds(players []Player, assignments []domain.SeedAssignment) error
ApplySeeds assigns seeds to the helper players, handling swaps if needed Returns an error if an assigned name could not be matched
func AssignPlayerNumbers ¶ added in v0.16.0
AssignPlayerNumbers sets Number on each player to prefix+counter, where counter starts at start and increments by one. Returns the next counter value so callers can chain across multiple slices (e.g. pools).
func AssignPoolsToCourts ¶ added in v0.14.0
AssignPoolsToCourts distributes numPools pools across numCourts courts using contiguous blocks that match the tree sheet grouping. The first court gets ceil(numPools/numCourts) pools, subsequent courts get the remainder. Returns an error when numCourts exceeds numPools.
func CalculateDepth ¶
func CheckDuplicateEntries ¶ added in v0.14.0
CheckDuplicateEntries scans the raw entry list (one CSV row per element) for duplicates and returns a list of entries that appear more than once. Empty strings are ignored. The returned slice preserves first-seen order of the offending entries; an empty result means the list is unique.
func ConvertPlayersToWinners ¶ added in v0.3.0
func ConvertPlayersToWinners(players []Player, sanitized bool, pCoords map[string]playerCellCoord) map[string]MatchWinner
func CourtLabel ¶ added in v0.16.0
CourtLabel returns the letter label (A–Z) for a zero-based court index.
func CreateNamesToPrint ¶
func CreateNamesWithPoolToPrint ¶ added in v0.3.0
func CreatePartialPoolMatches ¶ added in v0.16.0
func CreatePartialPoolMatches(pools []Pool)
CreatePartialPoolMatches fills each pool's Matches slice with adjacent-neighbour pairings only — for N players it produces N-1 matches in the sequence (0,1), (1,2), ..., (N-2,N-1). This is the "partial round-robin" / league format selected via state.Competition.PoolFormat == "partial".
FR-052 / R7: full round-robin grows O(N²) so even modest pools become untenably long; partial pools give every player exactly two bouts (one with the neighbour above, one with the neighbour below) except the two endpoints who get one each.
Pre-condition: pools[i].Players must be sorted in the order the pairings should follow (typically seed/rank order — the caller in engine/pools.go applies PoolSeeding before passing them in).
func CreatePoolMatches ¶
func CreatePoolMatches(pools []Pool)
func CreatePoolRoundRobinMatches ¶
func CreatePoolRoundRobinMatches(pools []Pool)
func CreateTreeBracket ¶
func FillEstimations ¶ added in v0.6.0
func FillInMatches ¶
func GenerateFinals ¶
GenerateFinals interleaves pool finalists so that when CreateBalancedTree distributes them into bracket slots, the first-place finisher of one pool is paired against the second-place finisher of another pool.
The algorithm cycles through all pools, advancing a round counter every time a full set of pools has been placed. The position rank for each slot is computed as (i + round) % poolWinners, which shifts the rank picked for successive pools so that adjacent bracket slots always contain different finishing positions.
Example with 4 pools and 2 winners per pool:
result = [Pool_A-1st, Pool_B-2nd, Pool_C-1st, Pool_D-2nd,
Pool_A-2nd, Pool_B-1st, Pool_C-2nd, Pool_D-1st]
func GetBorderStyleLeft ¶
func MatchHeader ¶
func NextPow2 ¶ added in v0.14.0
NextPow2 returns the smallest power of 2 that is >= n. Returns 1 for n <= 1.
func ParseSeedsFile ¶ added in v0.11.0
func ParseSeedsFile(filePath string) ([]domain.SeedAssignment, error)
ParseSeedsFile reads a CSV file mapping names to seed positions
func PrintLeafNodes ¶
func PrintPoolMatches ¶
func PrintTeamEliminationMatches ¶ added in v0.2.0
func ProtectAllSheets ¶ added in v0.16.0
ProtectAllSheets applies protection to the Tree sheets, Names to Print, Tags, Pool Matches, and Elimination Matches. The score-entry sheets have explicitly unlocked cells for data entry.
func ProtectSheets ¶ added in v0.16.0
ProtectSheets applies sheet-level protection.
func ReadEntriesFromFile ¶ added in v0.9.0
func RemoveDuplicates ¶
RemoveDuplicates removes duplicate strings from the input slice and returns a new slice without duplicates.
The function takes a parameter named input, which is a slice of strings. It represents the input slice from which duplicates and empty strings will be removed.
func RoundToPowerOf2 ¶ added in v0.4.0
func SanitizeName ¶ added in v0.16.0
SanitizeName returns the canonical display form derived from a participant name: a single token uppercased ("KAZUKI") or "F. LAST" for multi-token names. Exported so state.SaveParticipants can detect display names that match the auto-derived form and avoid round-trip data corruption (a 3-column row whose DisplayName equals SanitizeName(Name) carries no extra information and must not be written for non-zekken competitions — see internal/state/participants.go).
func SetSheetLayoutLandscapeA3 ¶ added in v0.11.0
func SetSheetLayoutPortraitA4 ¶ added in v0.11.0
func SetSheetLayoutPortraitA4Centered ¶ added in v0.14.0
SetSheetLayoutPortraitA4Centered configures portrait A4 with print-centering. Unlike SetSheetLayoutPortraitA4 it does not enable FitToPage, so smaller content keeps its natural size and the horizontal/vertical centering print option actually has room to take effect.
func SetSheetLayoutPortraitA4DownThenOver ¶ added in v0.14.0
func SetTreeSheetTitle ¶ added in v0.14.0
SetTreeSheetTitle writes a title formula into the first row of a tree sheet, spanning a wide range of columns to cover the bracket layout. The formula prepends the value of data!$B$1 (the user-supplied title prefix) to the given title string, so editing that single cell updates all tree sheets.
func SubtreeCourtIndex ¶ added in v0.16.0
SubtreeCourtIndex returns the zero-based court index for tree subtree idx when numSubtrees are spread across numCourts. Mirrors the grouping used by poolBoundsForSubtree so that court labels are always consistent.
func TreePageLayout ¶ added in v0.16.0
TreePageLayout computes the number of tree sheet pages needed for numPlayers competitors assigned to numCourts Shiaijo. When singleTree is true the result is always 1 (unless court expansion requires more, which singleTree suppresses). numCourts must be clamped by the caller before calling if caller-specific rules apply (e.g. capping at numPools).
func ValidateCourts ¶ added in v0.14.0
ValidateCourts returns an error when n is outside the supported court range. Courts are labelled A–Z, so MaxCourts (26) is the hard upper bound. n < 1 is also rejected so the caller does not have to guess what "0 courts" should mean.
func WriteKachinukiDetailSheet ¶ added in v0.16.0
func WriteKachinukiDetailSheet(f *excelize.File, matches []KachinukiMatchDetail) error
WriteKachinukiDetailSheet creates the SheetKachinukiDetail sheet and writes one section per match. When matches is empty the sheet is NOT created — the caller is responsible for checking the input length AND the renderer guards against accidental emission of an empty sheet.
Types ¶
type KachinukiBout ¶ added in v0.16.0
type KachinukiBout struct {
Position int // 1-based bout index within the team match
SideAName string // player name for Side A
SideAPos string // lineup position (Senpo, Jiho, Chuken, Fukusho, Taisho) — may be empty
ScoreA string // accumulated ippon string (e.g. "MK", "MMK") or empty
SideBName string
SideBPos string
ScoreB string
Winner string // player name of the winner; empty for hikiwake
Decision string // canonical decision wire value (fought / hikiwake / kiken / fusenpai / fusensho / daihyosen / kachinuki-exhaustion)
}
KachinukiBout is one bout in a kachinuki team match.
type KachinukiMatchDetail ¶ added in v0.16.0
type KachinukiMatchDetail struct {
Label string // human-readable match identifier (e.g. "Pool A - Match 1")
SideATeam string // team name on Side A
SideBTeam string // team name on Side B
Bouts []KachinukiBout
Winner string // winning team name; empty when the match did not end with a winner
Decision string // canonical wire decision for the parent match (typically "kachinuki-exhaustion")
EliminationA int // count of Side A players retired by the end of the match
EliminationB int // count of Side B players retired by the end of the match
}
KachinukiMatchDetail is a single team match's bout log with team-level summary metadata. One section is rendered per entry in WriteKachinukiDetailSheet.
type MatchWinner ¶
type MatchWinner struct {
// contains filtered or unexported fields
}
MatchWinner records the Excel cell that contains a pool or elimination match winner's name; used to build cross-sheet formula references in bracket trees.
type Node ¶
type Node struct {
LeafNode bool
// sheet for Cell Values
SheetName string
// Pool Number or Cell value
LeafVal string
Val int64
Left *Node
Right *Node
// contains filtered or unexported fields
}
func CreateBalancedTree ¶
func SubdivideTree ¶ added in v0.4.0
function that subdivides a tree into a specified number of subtrees
type Player ¶
Player is a type alias for domain.Player. The helper package used to own a parallel struct during the NFR-007 migration; it was collapsed to an alias once the two were proven field-identical (the converters were copying fields 1:1 with no translation). The helper name is kept for rendering-side ergonomics inside this package.
func PoolSeeding ¶ added in v0.14.0
PoolSeeding reorders players for pool distribution so that top seeds land in pools that are appropriately spread across the given number of courts.
It assigns seeds to courts in a round-robin fashion and uses a per-court priority to ensure correct bracket placement (e.g., top and bottom of the court's bracket) after the pools are deinterleaved by ReorderPoolsForCourts.
func StandardSeeding ¶ added in v0.11.0
StandardSeeding reorders players into bracket positions such that seeded participants (Seed > 0) are spaced according to tournament standards (e.g., #1 and #2 on opposite halves). Unseeded players fill the remaining slots.
type Pool ¶
type Pool struct {
PoolName string `json:"poolName"`
Players []Player `json:"players"`
Matches []Match `json:"matches,omitempty"`
}
func ReorderPoolsForCourts ¶ added in v0.14.0
ReorderPoolsForCourts deinterleaves pools so that when divided into contiguous court blocks, each block has balanced pool sizes and seeds are spread across courts. Original pool i goes to court block (i % numCourts). Pool names are re-assigned alphabetically after reordering.
type RowStack ¶ added in v0.2.0
type RowStack struct {
// contains filtered or unexported fields
}
func (*RowStack) PushHighest ¶ added in v0.2.1
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package bracket will hold bracket/tree algorithms extracted from internal/helper.
|
Package bracket will hold bracket/tree algorithms extracted from internal/helper. |
|
Package csv will hold CSV parsing extracted from internal/helper.
|
Package csv will hold CSV parsing extracted from internal/helper. |
|
Package pool will hold pool-generation algorithms extracted from internal/helper.
|
Package pool will hold pool-generation algorithms extracted from internal/helper. |
|
Package seeding will hold seeding algorithms extracted from internal/helper.
|
Package seeding will hold seeding algorithms extracted from internal/helper. |