@@ -1006,6 +1006,18 @@ defmodule Cadet.Assessments do
1006
1006
# fetch top 10 contest voting entries with the contest question id
1007
1007
question_id = fetch_associated_contest_question_id ( course_id , q )
1008
1008
1009
+ # fetch top 10 contest coting entries with contest question id based on popular score
1010
+ popular_results =
1011
+ if is_nil ( question_id ) do
1012
+ [ ]
1013
+ else
1014
+ if leaderboard_open? ( assessment , q ) or role in @ open_all_assessment_roles do
1015
+ fetch_top_popular_score_answers ( question_id , 10 )
1016
+ else
1017
+ [ ]
1018
+ end
1019
+ end
1020
+
1009
1021
leaderboard_results =
1010
1022
if is_nil ( question_id ) do
1011
1023
[ ]
@@ -1025,6 +1037,10 @@ defmodule Cadet.Assessments do
1025
1037
:contest_leaderboard ,
1026
1038
leaderboard_results
1027
1039
)
1040
+ |> Map . put (
1041
+ :popular_leaderboard ,
1042
+ popular_results
1043
+ )
1028
1044
1029
1045
Map . put ( q , :question , voting_question )
1030
1046
else
@@ -1097,6 +1113,37 @@ defmodule Cadet.Assessments do
1097
1113
|> Repo . all ( )
1098
1114
end
1099
1115
1116
+ @ doc """
1117
+ Fetches top answers for the given question, based on the contest popular_score
1118
+
1119
+ Used for contest leaderboard fetching
1120
+ """
1121
+ def fetch_top_popular_score_answers ( question_id , number_of_answers ) do
1122
+ Answer
1123
+ |> where ( question_id: ^ question_id )
1124
+ |> where (
1125
+ [ a ] ,
1126
+ fragment (
1127
+ "?->>'code' like ?" ,
1128
+ a . answer ,
1129
+ "%return%"
1130
+ )
1131
+ )
1132
+ |> order_by ( desc: :popular_score )
1133
+ |> join ( :left , [ a ] , s in assoc ( a , :submission ) )
1134
+ |> join ( :left , [ a , s ] , student in assoc ( s , :student ) )
1135
+ |> join ( :inner , [ a , s , student ] , student_user in assoc ( student , :user ) )
1136
+ |> where ( [ a , s , student ] , student . role == "student" )
1137
+ |> select ( [ a , s , student , student_user ] , % {
1138
+ submission_id: a . submission_id ,
1139
+ answer: a . answer ,
1140
+ popular_score: a . popular_score ,
1141
+ student_name: student_user . name
1142
+ } )
1143
+ |> limit ( ^ number_of_answers )
1144
+ |> Repo . all ( )
1145
+ end
1146
+
1100
1147
@ doc """
1101
1148
Computes rolling leaderboard for contest votes that are still open.
1102
1149
"""
@@ -1181,6 +1228,7 @@ defmodule Cadet.Assessments do
1181
1228
|> Repo . get_by ( id: contest_voting_question_id )
1182
1229
1183
1230
entry_scores = map_eligible_votes_to_entry_score ( eligible_votes , token_divider )
1231
+ normalized_scores = map_eligible_votes_to_popular_score ( eligible_votes , token_divider )
1184
1232
1185
1233
entry_scores
1186
1234
|> Enum . map ( fn { ans_id , relative_score } ->
@@ -1195,6 +1243,20 @@ defmodule Cadet.Assessments do
1195
1243
end )
1196
1244
|> Enum . reduce ( Multi . new ( ) , & Multi . append / 2 )
1197
1245
|> Repo . transaction ( )
1246
+
1247
+ normalized_scores
1248
+ |> Enum . map ( fn { ans_id , popular_score } ->
1249
+ % Answer { id: ans_id }
1250
+ |> Answer . popular_score_update_changeset ( % {
1251
+ popular_score: popular_score
1252
+ } )
1253
+ end )
1254
+ |> Enum . map ( fn changeset ->
1255
+ op_key = "answer_#{ changeset . data . id } "
1256
+ Multi . update ( Multi . new ( ) , op_key , changeset )
1257
+ end )
1258
+ |> Enum . reduce ( Multi . new ( ) , & Multi . append / 2 )
1259
+ |> Repo . transaction ( )
1198
1260
end
1199
1261
1200
1262
defp map_eligible_votes_to_entry_score ( eligible_votes , token_divider ) do
@@ -1220,14 +1282,46 @@ defmodule Cadet.Assessments do
1220
1282
)
1221
1283
end
1222
1284
1285
+ defp map_eligible_votes_to_popular_score ( eligible_votes , token_divider ) do
1286
+ # converts eligible votes to the {total cumulative score, number of votes, tokens}
1287
+ entry_vote_data =
1288
+ Enum . reduce ( eligible_votes , % { } , fn % { ans_id: ans_id , score: score , ans: ans } , tracker ->
1289
+ { prev_score , prev_count , _ans_tokens } = Map . get ( tracker , ans_id , { 0 , 0 , 0 } )
1290
+
1291
+ Map . put (
1292
+ tracker ,
1293
+ ans_id ,
1294
+ # assume each voter is assigned 10 entries which will make it fair.
1295
+ { prev_score + score , prev_count + 1 , Lexer . count_tokens ( ans ) }
1296
+ )
1297
+ end )
1298
+
1299
+ # calculate the score based on formula {ans_id, score}
1300
+ Enum . map (
1301
+ entry_vote_data ,
1302
+ fn { ans_id , { sum_of_scores , number_of_voters , tokens } } ->
1303
+ { ans_id ,
1304
+ calculate_normalized_score ( sum_of_scores , number_of_voters , tokens , token_divider ) }
1305
+ end
1306
+ )
1307
+ end
1308
+
1223
1309
# Calculate the score based on formula
1224
1310
# score(v,t) = v - 2^(t/token_divider) where v is the normalized_voting_score
1225
1311
# normalized_voting_score = sum_of_scores / number_of_voters / 10 * 100
1226
1312
defp calculate_formula_score ( sum_of_scores , number_of_voters , tokens , token_divider ) do
1227
- normalized_voting_score = sum_of_scores / number_of_voters / 10 * 100
1313
+ normalized_voting_score =
1314
+ calculate_normalized_score ( sum_of_scores , number_of_voters , tokens , token_divider )
1315
+
1228
1316
normalized_voting_score - :math . pow ( 2 , min ( 1023.5 , tokens / token_divider ) )
1229
1317
end
1230
1318
1319
+ # Calculate the normalized score based on formula
1320
+ # normalized_voting_score = sum_of_scores / number_of_voters / 10 * 100
1321
+ defp calculate_normalized_score ( sum_of_scores , number_of_voters , _tokens , _token_divider ) do
1322
+ sum_of_scores / number_of_voters / 10 * 100
1323
+ end
1324
+
1231
1325
@ doc """
1232
1326
Function returning submissions under a grader. This function returns only the
1233
1327
fields that are exposed in the /grading endpoint.
@@ -1509,7 +1603,8 @@ defmodule Cadet.Assessments do
1509
1603
|> Enum . map ( fn ans ->
1510
1604
if ans . question . type == :voting do
1511
1605
empty_contest_entries = Map . put ( ans . question . question , :contest_entries , [ ] )
1512
- empty_contest_leaderboard = Map . put ( empty_contest_entries , :contest_leaderboard , [ ] )
1606
+ empty_popular_leaderboard = Map . put ( empty_contest_entries , :popular_leaderboard , [ ] )
1607
+ empty_contest_leaderboard = Map . put ( empty_popular_leaderboard , :contest_leaderboard , [ ] )
1513
1608
question = Map . put ( ans . question , :question , empty_contest_leaderboard )
1514
1609
Map . put ( ans , :question , question )
1515
1610
else
0 commit comments