diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index d8f9bc67f..6bb932160 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -1175,7 +1175,12 @@ defmodule Cadet.Assessments do ) |> Repo.all() - entry_scores = map_eligible_votes_to_entry_score(eligible_votes) + token_divider = + Question + |> select([q], q.question["token_divider"]) + |> Repo.get_by(id: contest_voting_question_id) + + entry_scores = map_eligible_votes_to_entry_score(eligible_votes, token_divider) entry_scores |> Enum.map(fn {ans_id, relative_score} -> @@ -1192,7 +1197,7 @@ defmodule Cadet.Assessments do |> Repo.transaction() end - defp map_eligible_votes_to_entry_score(eligible_votes) do + defp map_eligible_votes_to_entry_score(eligible_votes, token_divider) do # converts eligible votes to the {total cumulative score, number of votes, tokens} entry_vote_data = Enum.reduce(eligible_votes, %{}, fn %{ans_id: ans_id, score: score, ans: ans}, tracker -> @@ -1210,18 +1215,17 @@ defmodule Cadet.Assessments do Enum.map( entry_vote_data, fn {ans_id, {sum_of_scores, number_of_voters, tokens}} -> - {ans_id, calculate_formula_score(sum_of_scores, number_of_voters, tokens)} + {ans_id, calculate_formula_score(sum_of_scores, number_of_voters, tokens, token_divider)} end ) end # Calculate the score based on formula - # score(v,t) = v - 2^(t/n) where v is the normalized_voting_score and n is the modifier - # n = 50 for C3 & C4, n = 80 for C6 + # score(v,t) = v - 2^(t/token_divider) where v is the normalized_voting_score # normalized_voting_score = sum_of_scores / number_of_voters / 10 * 100 - defp calculate_formula_score(sum_of_scores, number_of_voters, tokens) do + defp calculate_formula_score(sum_of_scores, number_of_voters, tokens, token_divider) do normalized_voting_score = sum_of_scores / number_of_voters / 10 * 100 - normalized_voting_score - :math.pow(2, min(1023.5, tokens / 80)) + normalized_voting_score - :math.pow(2, min(1023.5, tokens / token_divider)) end @doc """ diff --git a/lib/cadet/assessments/question_types/voting_question.ex b/lib/cadet/assessments/question_types/voting_question.ex index d76d65912..95c762fcd 100644 --- a/lib/cadet/assessments/question_types/voting_question.ex +++ b/lib/cadet/assessments/question_types/voting_question.ex @@ -11,14 +11,16 @@ defmodule Cadet.Assessments.QuestionTypes.VotingQuestion do field(:template, :string) field(:contest_number, :string) field(:reveal_hours, :integer) + field(:token_divider, :integer) end - @required_fields ~w(content contest_number reveal_hours)a + @required_fields ~w(content contest_number reveal_hours token_divider)a @optional_fields ~w(prepend template)a def changeset(question, params \\ %{}) do question |> cast(params, @required_fields ++ @optional_fields) |> validate_required(@required_fields) + |> validate_number(:token_divider, greater_than: 0) end end diff --git a/lib/cadet/jobs/xml_parser.ex b/lib/cadet/jobs/xml_parser.ex index 003091b84..fb1742571 100644 --- a/lib/cadet/jobs/xml_parser.ex +++ b/lib/cadet/jobs/xml_parser.ex @@ -259,7 +259,8 @@ defmodule Cadet.Updater.XMLParser do |> xpath( ~x"./VOTING"e, contest_number: ~x"./@assessment_number"s, - reveal_hours: ~x"./@reveal_hours"i + reveal_hours: ~x"./@reveal_hours"i, + token_divider: ~x"./@token_divider"i ) ) end diff --git a/test/cadet/assessments/assessments_test.exs b/test/cadet/assessments/assessments_test.exs index 7efd892d9..3575768ff 100644 --- a/test/cadet/assessments/assessments_test.exs +++ b/test/cadet/assessments/assessments_test.exs @@ -77,7 +77,8 @@ defmodule Cadet.AssessmentsTest do question: %{ content: Faker.Pokemon.name(), contest_number: assessment.number, - reveal_hours: 48 + reveal_hours: 48, + token_divider: 50 } }, assessment.id @@ -646,13 +647,13 @@ defmodule Cadet.AssessmentsTest do top_x_ans = Assessments.fetch_top_relative_score_answers(question_id, 5) - assert get_answer_relative_scores(top_x_ans) == expected_top_relative_scores(5) + assert get_answer_relative_scores(top_x_ans) == expected_top_relative_scores(5, 50) x = 3 top_x_ans = Assessments.fetch_top_relative_score_answers(question_id, x) # verify that top x ans are queried correctly - assert get_answer_relative_scores(top_x_ans) == expected_top_relative_scores(3) + assert get_answer_relative_scores(top_x_ans) == expected_top_relative_scores(3, 50) end end @@ -886,7 +887,7 @@ defmodule Cadet.AssessmentsTest do assert get_answer_relative_scores( Assessments.fetch_top_relative_score_answers(yesterday_question.id, 5) - ) == expected_top_relative_scores(5) + ) == expected_top_relative_scores(5, 50) end test "update_rolling_contest_leaderboards correcly updates leaderboards which voting is active", @@ -908,7 +909,7 @@ defmodule Cadet.AssessmentsTest do assert get_answer_relative_scores( Assessments.fetch_top_relative_score_answers(current_question.id, 5) - ) == expected_top_relative_scores(5) + ) == expected_top_relative_scores(5, 50) end end @@ -1707,12 +1708,11 @@ defmodule Cadet.AssessmentsTest do questions |> Enum.map(fn q -> q.id end) |> Enum.sort() end - defp expected_top_relative_scores(top_x) do - # test with token penalty of 80 + defp expected_top_relative_scores(top_x, token_divider) do # "return 0;" in the factory has 3 token 10..0 |> Enum.to_list() - |> Enum.map(fn score -> 10 * score - :math.pow(2, 3 / 80) end) + |> Enum.map(fn score -> 10 * score - :math.pow(2, 3 / token_divider) end) |> Enum.take(top_x) end end diff --git a/test/cadet/assessments/question_test.exs b/test/cadet/assessments/question_test.exs index 7a77f38fb..34381a5f1 100644 --- a/test/cadet/assessments/question_test.exs +++ b/test/cadet/assessments/question_test.exs @@ -39,7 +39,8 @@ defmodule Cadet.Assessments.QuestionTest do question: %{ content: Faker.Pokemon.name(), contest_number: assessment.number, - reveal_hours: 48 + reveal_hours: 48, + token_divider: 50 } } diff --git a/test/cadet/assessments/question_types/voting_question_test.exs b/test/cadet/assessments/question_types/voting_question_test.exs index b3fd8949e..d714b849b 100644 --- a/test/cadet/assessments/question_types/voting_question_test.exs +++ b/test/cadet/assessments/question_types/voting_question_test.exs @@ -9,7 +9,8 @@ defmodule Cadet.Assessments.QuestionTypes.VotingQuestionTest do %{ content: "content", contest_number: "C4", - reveal_hours: 48 + reveal_hours: 48, + token_divider: 50 }, :valid ) @@ -22,6 +23,26 @@ defmodule Cadet.Assessments.QuestionTypes.VotingQuestionTest do }, :invalid ) + + assert_changeset( + %{ + content: "content", + contest_number: "C3", + reveal_hours: 48, + token_divider: -1 + }, + :invalid + ) + + assert_changeset( + %{ + content: "content", + contest_number: "C6", + reveal_hours: 48, + token_divider: 0 + }, + :invalid + ) end end end diff --git a/test/factories/assessments/question_factory.ex b/test/factories/assessments/question_factory.ex index 9836d1047..cfd088dd1 100644 --- a/test/factories/assessments/question_factory.ex +++ b/test/factories/assessments/question_factory.ex @@ -93,7 +93,8 @@ defmodule Cadet.Assessments.QuestionFactory do prepend: Faker.Pokemon.location(), template: Faker.Lorem.Shakespeare.as_you_like_it(), contest_number: contest_assessment.number, - reveal_hours: 48 + reveal_hours: 48, + token_divider: 50 } } end @@ -106,7 +107,8 @@ defmodule Cadet.Assessments.QuestionFactory do prepend: Faker.Pokemon.location(), template: Faker.Lorem.Shakespeare.as_you_like_it(), contest_number: contest_assessment.number, - reveal_hours: 48 + reveal_hours: 48, + token_divider: 50 } end end diff --git a/test/support/xml_generator.ex b/test/support/xml_generator.ex index e028b31b7..8782d32a4 100644 --- a/test/support/xml_generator.ex +++ b/test/support/xml_generator.ex @@ -157,7 +157,8 @@ defmodule Cadet.Test.XMLGenerator do voting_field = voting(%{ reveal_hours: question.question.reveal_hours, - assessment_number: question.question.contest_number + assessment_number: question.question.contest_number, + token_divider: question.question.token_divider }) [ @@ -167,7 +168,7 @@ defmodule Cadet.Test.XMLGenerator do end defp voting(raw_attr) do - {"VOTING", map_permit_keys(raw_attr, ~w(assessment_number reveal_hours)a)} + {"VOTING", map_permit_keys(raw_attr, ~w(assessment_number reveal_hours token_divider)a)} end defp deployment(raw_attrs, children) do