fix: require deciders for advancing ties

This commit is contained in:
Daniel Schädler 2026-04-22 15:19:53 +02:00
parent 9547cce982
commit 2912645499
3 changed files with 72 additions and 5 deletions

View File

@ -224,12 +224,11 @@ class GroupStageService
nil nil
end end
def unresolved_tie_crosses_advancing_cutoff?(sorted_group_scores, group_score, advancing_slots) def unresolved_tie_affects_advancing_placements?(sorted_group_scores, group_score, advancing_slots)
return false if advancing_slots.zero? return false if advancing_slots.zero?
cluster = tie_cluster_for(sorted_group_scores, group_score) cluster = tie_cluster_for(sorted_group_scores, group_score)
occupied_slots = (cluster[:start_index] + 1)..(cluster[:end_index] + 1) cluster[:start_index] < advancing_slots
occupied_slots.begin <= advancing_slots && occupied_slots.end > advancing_slots
end end
def tie_cluster_for(sorted_group_scores, group_score) def tie_cluster_for(sorted_group_scores, group_score)
@ -298,7 +297,7 @@ class GroupStageService
tied: comparison_reason[:tied], tied: comparison_reason[:tied],
needs_decider_match: group_ready_for_decider_check && needs_decider_match: group_ready_for_decider_check &&
comparison_reason[:tied] && comparison_reason[:tied] &&
unresolved_tie_crosses_advancing_cutoff?(sorted_group_scores, group_score, advancing_slots) unresolved_tie_affects_advancing_placements?(sorted_group_scores, group_score, advancing_slots)
} }
end end

View File

@ -201,5 +201,41 @@ RSpec.describe StagesController, type: :controller do
expect(body[:blocking_ties].first[:team_names]).to eq(%w[A B]) expect(body[:blocking_ties].first[:team_names]).to eq(%w[A B])
end end
end end
context 'when tied advancing teams still need a decider to fix playoff seeding' do
let(:group_stage) do
create(:group_stage, tournament: create(:tournament, teams_count: 0), group_count: 0)
end
before do
tournament = group_stage.tournament
tournament.update!(playoff_teams_amount: 2, instant_finalists_amount: 2, intermediate_round_participants_amount: 0)
group = create(:group, stage: group_stage, match_count: 0, number: 1)
group_stage.groups = [group]
team_a = create(:team, tournament: tournament, name: 'A')
team_b = create(:team, tournament: tournament, name: 'B')
create(:group_match, group: group, state: :finished).tap do |match|
create(:match_score, match: match, team: team_a, points: 2)
create(:match_score, match: match, team: team_b, points: 2)
end
score_a = create(:group_score, group: group, team: team_a, group_points: 7, scored_points: 4, received_points: 1)
score_b = create(:group_score, group: group, team: team_b, group_points: 7, scored_points: 4, received_points: 1)
GroupStageService.send(:recalculate_position_of_group_scores!, [score_a, score_b]).each(&:save!)
apply_authentication_headers_for group_stage.owner
put :update, params: { id: group_stage.to_param }.merge(finished)
end
it 'returns unprocessable entity' do
expect(response).to have_http_status(:unprocessable_entity)
end
it 'returns a helpful decider error' do
body = deserialize_response(response)
expect(body[:error]).to eq('Playoffs cannot be generated yet because a decider match is required for an advancing place')
expect(body[:blocking_ties].size).to eq(1)
expect(body[:blocking_ties].first[:team_names]).to eq(%w[A B])
end
end
end end
end end

View File

@ -268,7 +268,7 @@ RSpec.describe GroupStageService do
) )
end end
it 'keeps shared positions and requests a decider only when an unresolved tie crosses the cutoff' do it 'keeps shared positions and requests a decider when an unresolved tie affects advancing placements' do
tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0) tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0)
create(:group_match, group: group, state: :finished).tap do |match| create(:group_match, group: group, state: :finished).tap do |match|
@ -300,6 +300,38 @@ RSpec.describe GroupStageService do
) )
end end
it 'requests a decider when tied teams both advance but their seeding stays unresolved' do
tournament.update!(playoff_teams_amount: 2, instant_finalists_amount: 2, intermediate_round_participants_amount: 0)
create(:group_match, group: group, state: :finished).tap do |match|
create(:match_score, match: match, team: team_a, points: 2)
create(:match_score, match: match, team: team_b, points: 2)
end
score_a = create(:group_score, group: group, team: team_a, group_points: 7, scored_points: 4, received_points: 1)
score_b = create(:group_score, group: group, team: team_b, group_points: 7, scored_points: 4, received_points: 1)
score_c = create(:group_score, group: group, team: team_c, group_points: 3, scored_points: 2, received_points: 3)
score_d = create(:group_score, group: group, team: team_d, group_points: 0, scored_points: 1, received_points: 7)
GroupStageService.send(:recalculate_position_of_group_scores!, [score_a, score_b, score_c, score_d]).each(&:save!)
expect([score_a.position, score_b.position, score_c.position, score_d.position]).to eq([1, 1, 3, 4])
expect(GroupStageService.ranking_decision_for(score_a)).to include(
resolved_by: 'tie',
direct_comparison_result: 'draw',
hidden_points_result: 'not_played',
tied: true,
needs_decider_match: true
)
expect(GroupStageService.ranking_decision_for(score_b)).to include(
resolved_by: 'tie',
direct_comparison_result: 'draw',
hidden_points_result: 'not_played',
tied: true,
needs_decider_match: true
)
end
it 'does not expose unresolved tie metadata while regular matches in the group are still unfinished' do it 'does not expose unresolved tie metadata while regular matches in the group are still unfinished' do
tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0) tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0)