Skip to content

Commit

Permalink
Account for incomplete results when calculating uncertain advancement
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatanklosko committed Sep 28, 2024
1 parent ab868fa commit 5be51c7
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 6 deletions.
28 changes: 22 additions & 6 deletions lib/wca_live/scoretaking/advancing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ defmodule WcaLive.Scoretaking.Advancing do

{result_ids, result_ids}
else
round = Repo.preload(round, [:results, :competition_event])
{qualifying_result_ids(round), clinched_qualifying_result_ids(round)}
end
end
Expand Down Expand Up @@ -204,7 +205,7 @@ defmodule WcaLive.Scoretaking.Advancing do
hypothetical_results =
Enum.map(round.results, fn result ->
if result.id in ignored_result_ids do
%{result | attempts: [-1], best: -1, average: -1}
%{result | attempts: [%Scoretaking.Attempt{result: -1}], best: -1, average: -1}
else
result
end
Expand All @@ -221,15 +222,30 @@ defmodule WcaLive.Scoretaking.Advancing do
end

defp clinched_qualifying_result_ids(round) do
# Assume best possible attempts for empty results and see who of
# the currently entered results would still qualify.
# Assume best possible attempts for empty (or incomplete) results
# and see who of the currently entered results would still qualify.

format = Format.get_by_id!(round.format_id)

hypothetical_results =
Enum.map(round.results, fn result ->
if result.attempts == [] do
%{result | attempts: [1, 1, 1, 1, 1], best: 1, average: 1}
else
max_expected_attempts =
Result.max_expected_attempts(result, format.number_of_attempts, round.cutoff)

max_missing_attempts = max_expected_attempts - length(result.attempts)

if max_missing_attempts == 0 do
result
else
attempts =
Enum.map(result.attempts, &Map.from_struct/1) ++
List.duplicate(%{result: 1}, max_missing_attempts)

attrs = %{attempts: attempts}
event_id = round.competition_event.event_id

Result.changeset(result, attrs, event_id, format, round.time_limit, round.cutoff)
|> Ecto.Changeset.apply_changes()
end
end)

Expand Down
19 changes: 19 additions & 0 deletions lib/wca_live/scoretaking/result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,25 @@ defmodule WcaLive.Scoretaking.Result do
end
end

@doc """
Returns the maximum number of attempts expected for the given result.
If the result is incomplete, we assume the best-case scenario.
"""
@spec max_expected_attempts(%Result{}, pos_integer(), %Cutoff{}) :: pos_integer()
def max_expected_attempts(result, max_attempts, cutoff) do
if meets_cutoff?(result, cutoff) do
max_attempts
else
if length(result.attempts) == cutoff.number_of_attempts do
cutoff.number_of_attempts
else
# They can still satisfy the cutoff and get all attempts
max_attempts
end
end
end

@doc """
Returns `true` if `result` has no attempts.
"""
Expand Down
68 changes: 68 additions & 0 deletions test/wca_live/scoretaking/advancing_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,74 @@ defmodule WcaLive.Scoretaking.AdvancingTest do
] = updated_results
end

test "compute_advancing/1 sets questionable advancing if the round has incomplete results" do
competition_event = insert(:competition_event)

round1 =
insert(:round,
competition_event: competition_event,
number: 1,
advancement_condition: %{type: "ranking", level: 3}
)

%{id: id1} =
insert(:result, round: round1, ranking: 1, best: 1000, average: 1000, advancing: false)

%{id: id2} =
insert(:result, round: round1, ranking: 2, best: 1100, average: 1100, advancing: false)

%{id: id3} =
insert(:result, round: round1, ranking: 3, best: 1200, average: 1200, advancing: false)

# We have two incomplete results, both with BPA 1150, so id2 is
# already guaranteed to advance either way, but id3 is questionable

%{id: id4} =
insert(:result,
round: round1,
ranking: 4,
best: 1100,
average: 1250,
attempts: [
build(:attempt, result: 1400),
build(:attempt, result: 1200),
build(:attempt, result: 1150),
build(:attempt, result: 1100)
],
advancing: false
)

%{id: id5} =
insert(:result,
round: round1,
ranking: 4,
best: 1100,
average: 1250,
attempts: [
build(:attempt, result: 1400),
build(:attempt, result: 1200),
build(:attempt, result: 1150),
build(:attempt, result: 1100)
],
advancing: false
)

_round2 =
insert(:round, competition_event: competition_event, number: 2, advancement_condition: nil)

changeset = Advancing.compute_advancing(round1)

updated_results = apply_changes(changeset).results

assert [
%{id: ^id1, advancing: true, advancing_questionable: false},
%{id: ^id2, advancing: true, advancing_questionable: false},
%{id: ^id3, advancing: true, advancing_questionable: true},
%{id: ^id4, advancing: false, advancing_questionable: false},
%{id: ^id5, advancing: false, advancing_questionable: false}
] = updated_results
end

test "qualifying_result_ids/1 returns an empty list if the round has no results" do
round = insert(:round, number: 1)

Expand Down

0 comments on commit 5be51c7

Please sign in to comment.