Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 0 additions & 56 deletions UNUSED_FUNCTIONS_ANALYSIS.md

This file was deleted.

3 changes: 3 additions & 0 deletions generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5365,6 +5365,9 @@ enum e_tournament_stage_types_enum {

"""Single Elimination"""
SingleElimination

"""Swiss"""
Swiss
}

"""
Expand Down
5 changes: 3 additions & 2 deletions generated/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1969,7 +1969,7 @@ export interface e_tournament_stage_types_aggregate_fields {
/** unique or primary key constraints on table "e_tournament_stage_types" */
export type e_tournament_stage_types_constraint = 'e_tournament_stage_types_pkey'

export type e_tournament_stage_types_enum = 'DoubleElimination' | 'RoundRobin' | 'SingleElimination'
export type e_tournament_stage_types_enum = 'DoubleElimination' | 'RoundRobin' | 'SingleElimination' | 'Swiss'


/** aggregate max on columns */
Expand Down Expand Up @@ -49208,7 +49208,8 @@ export const enumETournamentStageTypesConstraint = {
export const enumETournamentStageTypesEnum = {
DoubleElimination: 'DoubleElimination' as const,
RoundRobin: 'RoundRobin' as const,
SingleElimination: 'SingleElimination' as const
SingleElimination: 'SingleElimination' as const,
Swiss: 'Swiss' as const
}

export const enumETournamentStageTypesSelectColumn = {
Expand Down
1 change: 1 addition & 0 deletions hasura/enums/tournament-stage-types.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
insert into e_tournament_stage_types ("value", "description") values
('Swiss', 'Swiss'),
('RoundRobin', 'Round Robin'),
('SingleElimination', 'Single Elimination'),
('DoubleElimination', 'Double Elimination')
Expand Down
76 changes: 76 additions & 0 deletions hasura/functions/tournaments/advance_swiss_teams.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
CREATE OR REPLACE FUNCTION public.advance_swiss_teams(_stage_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
stage_record RECORD;
next_stage_id uuid;
advanced_teams uuid[];
eliminated_count int;
BEGIN
SELECT ts.tournament_id, ts."order"
INTO stage_record
FROM tournament_stages ts
WHERE ts.id = _stage_id;

IF stage_record IS NULL THEN
RAISE EXCEPTION 'Stage % not found', _stage_id;
END IF;

SELECT array_agg(vtsr.tournament_team_id)
INTO advanced_teams
FROM v_team_stage_results vtsr
WHERE vtsr.tournament_stage_id = _stage_id
AND vtsr.wins >= 3;

SELECT COUNT(*)
INTO eliminated_count
FROM v_team_stage_results vtsr
WHERE vtsr.tournament_stage_id = _stage_id
AND vtsr.losses >= 3;

RAISE NOTICE '=== Processing Swiss Advancement ===';
RAISE NOTICE 'Teams with 3+ wins: %', COALESCE(array_length(advanced_teams, 1), 0);
RAISE NOTICE 'Teams with 3+ losses: %', eliminated_count;

DECLARE
remaining_teams int;
stage_complete boolean;
BEGIN
SELECT COUNT(*)
INTO remaining_teams
FROM v_team_stage_results vtsr
WHERE vtsr.tournament_stage_id = _stage_id
AND vtsr.wins < 3
AND vtsr.losses < 3;

stage_complete := (remaining_teams = 0);

IF advanced_teams IS NOT NULL AND array_length(advanced_teams, 1) > 0 THEN
SELECT ts.id INTO next_stage_id
FROM tournament_stages ts
WHERE ts.tournament_id = stage_record.tournament_id
AND ts."order" = stage_record."order" + 1;

IF next_stage_id IS NOT NULL THEN
RAISE NOTICE 'Advancing % teams to next stage', array_length(advanced_teams, 1);

-- Only seed the next stage if the current stage is complete AND we have teams to advance
IF stage_complete THEN
RAISE NOTICE 'Swiss stage complete, advancing teams to next stage';
PERFORM seed_stage(next_stage_id);
END IF;
ELSE
RAISE NOTICE 'No next stage found - teams have won the tournament';
END IF;
ELSIF stage_complete THEN
-- Stage is complete but no teams advanced (shouldn't happen in normal Swiss, but handle gracefully)
RAISE NOTICE 'Swiss stage complete but no teams advanced';
END IF;

END;

RAISE NOTICE '=== Swiss Advancement Complete ===';
END;
$$;

132 changes: 132 additions & 0 deletions hasura/functions/tournaments/assign_teams_to_swiss_pools.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
CREATE OR REPLACE FUNCTION public.assign_teams_to_swiss_pools(_stage_id uuid, _round int)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
pool_record RECORD;
bracket_record RECORD;
team_count int;
matches_needed int;
match_counter int;
bracket_order int[];
i int;
seed_1_idx int;
seed_2_idx int;
team_1_id uuid;
team_2_id uuid;
adjacent_team_id uuid;
used_teams uuid[];
teams_to_pair uuid[];
BEGIN
RAISE NOTICE '=== Assigning Teams to Swiss Pools for Round % ===', _round;

used_teams := ARRAY[]::uuid[];

FOR pool_record IN
SELECT * FROM get_swiss_team_pools(_stage_id, used_teams)
ORDER BY wins DESC, losses ASC
LOOP
team_count := pool_record.team_count;

IF team_count = 0 THEN
CONTINUE;
END IF;

-- Calculate pool group: wins * 100 + losses
DECLARE
pool_group numeric;
BEGIN
pool_group := pool_record.wins * 100 + pool_record.losses;

RAISE NOTICE ' Pool %-% (group %): % teams',
pool_record.wins, pool_record.losses, pool_group, team_count;

-- Handle odd number of teams
adjacent_team_id := NULL;
teams_to_pair := pool_record.team_ids;

IF team_count % 2 != 0 THEN
-- Find a team from an adjacent pool
adjacent_team_id := find_adjacent_swiss_team(_stage_id, pool_record.wins, pool_record.losses, used_teams);

IF adjacent_team_id IS NOT NULL THEN
teams_to_pair := teams_to_pair || adjacent_team_id;
used_teams := used_teams || adjacent_team_id;
RAISE NOTICE ' Borrowed team % from adjacent pool', adjacent_team_id;
ELSE
RAISE EXCEPTION 'Odd number of teams in pool %-% and no adjacent team found',
pool_record.wins, pool_record.losses;
END IF;
END IF;

matches_needed := array_length(teams_to_pair, 1) / 2;

-- For Swiss tournaments, use bracket order for pairing
-- Filter bracket_order to only include valid seed positions (1 to teams_to_pair.length)
bracket_order := generate_bracket_order(array_length(teams_to_pair, 1));
DECLARE
filtered_order int[];
valid_seed int;
BEGIN
filtered_order := ARRAY[]::int[];
FOREACH valid_seed IN ARRAY bracket_order LOOP
IF valid_seed >= 1 AND valid_seed <= array_length(teams_to_pair, 1) THEN
filtered_order := filtered_order || valid_seed;
END IF;
END LOOP;
bracket_order := filtered_order;
END;

-- Validate we have enough valid seed positions
IF array_length(bracket_order, 1) < matches_needed * 2 THEN
RAISE EXCEPTION 'Not enough valid seed positions in bracket order for pool %-% (needed: %, got: %)',
pool_record.wins, pool_record.losses, matches_needed * 2, array_length(bracket_order, 1);
END IF;

match_counter := 1;
FOR i IN 1..matches_needed LOOP
-- Get seed positions from filtered bracket order
seed_1_idx := bracket_order[(i - 1) * 2 + 1];
seed_2_idx := bracket_order[(i - 1) * 2 + 2];

team_1_id := teams_to_pair[seed_1_idx];
team_2_id := teams_to_pair[seed_2_idx];

-- Validate that teams are not NULL
IF team_1_id IS NULL OR team_2_id IS NULL THEN
RAISE EXCEPTION 'NULL team found in pool %-% at match % (seed_1_idx: %, seed_2_idx: %, teams_to_pair length: %)',
pool_record.wins, pool_record.losses, match_counter, seed_1_idx, seed_2_idx, array_length(teams_to_pair, 1);
END IF;

SELECT id INTO bracket_record
FROM tournament_brackets
WHERE tournament_stage_id = _stage_id
AND round = _round
AND "group" = pool_group
AND match_number = match_counter
LIMIT 1;

IF bracket_record IS NULL THEN
RAISE EXCEPTION 'Bracket record not found for match % in pool %-% (group %)',
match_counter, pool_record.wins, pool_record.losses, pool_group;
END IF;

UPDATE tournament_brackets
SET tournament_team_id_1 = team_1_id,
tournament_team_id_2 = team_2_id,
bye = false
WHERE id = bracket_record.id;

-- Mark both teams as used to prevent double-assignment
used_teams := used_teams || team_1_id || team_2_id;

RAISE NOTICE ' Match %: Team % vs Team %', match_counter, team_1_id, team_2_id;
match_counter := match_counter + 1;
END LOOP;
END;
END LOOP;

RAISE NOTICE '=== Team Assignment Complete ===';
END;
$$;

35 changes: 35 additions & 0 deletions hasura/functions/tournaments/binomial_coefficient.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
CREATE OR REPLACE FUNCTION public.binomial_coefficient(n int, k int)
RETURNS numeric
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
result numeric;
i int;
BEGIN
-- Validate inputs
IF n < 0 OR k < 0 OR k > n THEN
RETURN 0;
END IF;

-- C(n, 0) = C(n, n) = 1
IF k = 0 OR k = n THEN
RETURN 1;
END IF;

-- Use symmetry: C(n, k) = C(n, n-k)
-- Choose the smaller k for efficiency
IF k > n - k THEN
k := n - k;
END IF;

-- Calculate iteratively: C(n, k) = (n * (n-1) * ... * (n-k+1)) / (k * (k-1) * ... * 1)
result := 1;
FOR i IN 1..k LOOP
result := result * (n - k + i) / i;
END LOOP;

RETURN result;
END;
$$;

23 changes: 23 additions & 0 deletions hasura/functions/tournaments/check_swiss_round_complete.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE OR REPLACE FUNCTION public.check_swiss_round_complete(_stage_id uuid, _round int)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
unfinished_count int;
total_matches int;
BEGIN
SELECT COUNT(*) INTO unfinished_count
FROM tournament_brackets tb
WHERE tb.tournament_stage_id = _stage_id
AND tb.round = _round
AND tb.finished = false;

SELECT COUNT(*) INTO total_matches
FROM tournament_brackets tb
WHERE tb.tournament_stage_id = _stage_id
AND tb.round = _round;

RETURN unfinished_count = 0 AND total_matches > 0;
END;
$$;

Loading