@@ -86,43 +86,87 @@ defmodule ClaperWeb.StatController do
86
86
with quiz <-
87
87
Quizzes . get_quiz! ( quiz_id , [
88
88
:quiz_questions ,
89
+ :quiz_responses ,
89
90
quiz_questions: :quiz_question_opts ,
91
+ quiz_responses: [ :quiz_question_opt , :user ] ,
90
92
presentation_file: :event
91
93
] ) ,
92
94
event <- quiz . presentation_file . event ,
93
95
:ok <- authorize_event_access ( current_user , event ) do
94
- # Create headers for the CSV
95
- headers = [ "Question" , "Correct Answers" , "Total Responses" , "Response Distribution (%)" ]
96
-
97
- # Format data rows
98
- data =
99
- quiz . quiz_questions
100
- |> Enum . map ( fn question ->
101
- [
102
- question . content ,
103
- # Correct answers
104
- question . quiz_question_opts
105
- |> Enum . filter ( & & 1 . is_correct )
106
- |> Enum . map_join ( ", " , & & 1 . content ) ,
107
- # Total responses
108
- question . quiz_question_opts
109
- |> Enum . map ( & & 1 . response_count )
110
- |> Enum . sum ( )
111
- |> to_string ( ) ,
112
- # Response distribution
113
- question . quiz_question_opts
114
- |> Enum . map_join ( ", " , fn opt ->
115
- "#{ opt . content } : #{ opt . percentage } %"
116
- end )
117
- ]
118
- end )
96
+ questions = quiz . quiz_questions
97
+ headers = build_quiz_headers ( questions )
119
98
120
- export_as_csv ( conn , headers , data , "quiz-#{ sanitize ( quiz . title ) } " )
121
- else
122
- :unauthorized -> send_resp ( conn , 403 , "Forbidden" )
99
+ # Group responses by user/attendee and question
100
+ responses_by_user =
101
+ Enum . group_by (
102
+ quiz . quiz_responses ,
103
+ fn response -> response . user_id || response . attendee_identifier end
104
+ )
105
+
106
+ # Format data rows - one row per user with their answers and score
107
+ data = Enum . map ( responses_by_user , & process_user_responses ( & 1 , questions ) )
108
+
109
+ csv_content =
110
+ CSV . encode ( [ headers | data ] )
111
+ |> Enum . to_list ( )
112
+ |> to_string ( )
113
+
114
+ send_download ( conn , { :binary , csv_content } ,
115
+ filename: "quiz_#{ quiz . id } _results.csv" ,
116
+ content_type: "text/csv"
117
+ )
123
118
end
124
119
end
125
120
121
+ defp build_quiz_headers ( questions ) do
122
+ question_headers =
123
+ questions
124
+ |> Enum . with_index ( 1 )
125
+ |> Enum . map ( fn { question , _index } -> question . content end )
126
+
127
+ [ "Attendee identifier" , "User email" ] ++ question_headers ++ [ "Total" ]
128
+ end
129
+
130
+ defp process_user_responses ( { _user_id , responses } , questions ) do
131
+ user_identifier = format_attendee_identifier ( List . first ( responses ) . attendee_identifier )
132
+ user_email = Map . get ( List . first ( responses ) . user || % { } , :email , "N/A" )
133
+ responses_by_question = Enum . group_by ( responses , & & 1 . quiz_question_id )
134
+
135
+ answers_with_correctness = process_question_responses ( questions , responses_by_question )
136
+ answers = Enum . map ( answers_with_correctness , fn { answer , _ } -> answer || "" end )
137
+ correct_count = Enum . count ( answers_with_correctness , fn { _ , correct } -> correct end )
138
+ total = "#{ correct_count } /#{ length ( questions ) } "
139
+
140
+ [ user_identifier , user_email ] ++ answers ++ [ total ]
141
+ end
142
+
143
+ defp process_question_responses ( questions , responses_by_question ) do
144
+ Enum . map ( questions , fn question ->
145
+ question_responses = Map . get ( responses_by_question , question . id , [ ] )
146
+
147
+ correct_opt_ids =
148
+ question . quiz_question_opts
149
+ |> Enum . filter ( & & 1 . is_correct )
150
+ |> Enum . map ( & & 1 . id )
151
+ |> MapSet . new ( )
152
+
153
+ format_question_response ( question_responses , correct_opt_ids )
154
+ end )
155
+ end
156
+
157
+ defp format_question_response ( [ ] , _correct_opt_ids ) , do: { nil , false }
158
+
159
+ defp format_question_response ( question_responses , correct_opt_ids ) do
160
+ answers = Enum . map ( question_responses , & & 1 . quiz_question_opt . content )
161
+
162
+ all_correct =
163
+ Enum . all? ( question_responses , fn r ->
164
+ MapSet . member? ( correct_opt_ids , r . quiz_question_opt_id )
165
+ end )
166
+
167
+ { Enum . join ( answers , ", " ) , all_correct }
168
+ end
169
+
126
170
@ doc """
127
171
Exports quiz as QTI format.
128
172
Requires user to be either an event leader or the event owner.
0 commit comments