1use crate::{
2 chapters::{self, DatabaseChapter},
3 courses::{self, Course},
4 exercise_slide_submissions::ExerciseSlideSubmission,
5 exercises::Exercise,
6 peer_or_self_review_configs::{self, PeerOrSelfReviewConfig},
7 peer_or_self_review_question_submissions::PeerOrSelfReviewQuestionSubmission,
8 peer_or_self_review_questions::{self, PeerOrSelfReviewQuestion, PeerOrSelfReviewQuestionType},
9 peer_or_self_review_submissions::{self, PeerOrSelfReviewSubmission},
10 peer_review_queue_entries::PeerReviewQueueEntry,
11 prelude::*,
12 teacher_grading_decisions::{self, TeacherGradingDecision},
13 user_exercise_slide_states::{self, UserExerciseSlideStateGradingSummary},
14 user_exercise_states::UserExerciseState,
15};
16
17use super::{
18 UserExerciseStateUpdateAlreadyLoadedRequiredData,
19 UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation,
20 UserExerciseStateUpdateRequiredData, UserExerciseStateUpdateRequiredDataPeerReviewInformation,
21};
22
23pub(super) async fn load_required_data(
25 conn: &mut PgConnection,
26 user_exercise_state_id: Uuid,
27 already_loaded_internal_dependencies: UserExerciseStateUpdateAlreadyLoadedRequiredData,
28) -> ModelResult<UserExerciseStateUpdateRequiredData> {
29 info!("Loading required data for user_exercise_state update");
30 let UserExerciseStateUpdateAlreadyLoadedRequiredData {
31 exercise,
32 current_user_exercise_state,
33 peer_or_self_review_information,
34 latest_teacher_grading_decision,
35 user_exercise_slide_state_grading_summary,
36 chapter,
37 course,
38 } = already_loaded_internal_dependencies;
39
40 let loaded_user_exercise_state =
41 load_current_user_exercise_state(conn, current_user_exercise_state, user_exercise_state_id)
42 .await?;
43 let loaded_exercise = load_exercise(conn, exercise, &loaded_user_exercise_state).await?;
44
45 let loaded_chapter = load_chapter(conn, chapter, loaded_exercise.chapter_id).await?;
46
47 let loaded_course = load_course(conn, course, loaded_exercise.course_id).await?;
48
49 Ok(UserExerciseStateUpdateRequiredData {
50 peer_or_self_review_information: load_peer_or_self_review_information(
51 conn,
52 peer_or_self_review_information,
53 &loaded_user_exercise_state,
54 &loaded_exercise,
55 )
56 .await?,
57 exercise: loaded_exercise,
58 latest_teacher_grading_decision: load_latest_teacher_grading_decision(
59 conn,
60 latest_teacher_grading_decision,
61 &loaded_user_exercise_state,
62 )
63 .await?,
64 user_exercise_slide_state_grading_summary: load_user_exercise_slide_state_grading_summary(
65 conn,
66 user_exercise_slide_state_grading_summary,
67 &loaded_user_exercise_state,
68 )
69 .await?,
70 current_user_exercise_state: loaded_user_exercise_state,
71 chapter: loaded_chapter,
72 course: loaded_course,
73 })
74}
75
76async fn load_user_exercise_slide_state_grading_summary(
77 conn: &mut PgConnection,
78 user_exercise_slide_state_grading_summary: Option<UserExerciseSlideStateGradingSummary>,
79 loaded_user_exercise_state: &UserExerciseState,
80) -> ModelResult<UserExerciseSlideStateGradingSummary> {
81 if let Some(user_exercise_slide_state_grading_summary) =
82 user_exercise_slide_state_grading_summary
83 {
84 info!("Using already loaded user exercise slide state grading summary");
85 Ok(user_exercise_slide_state_grading_summary)
86 } else {
87 info!("Loading user exercise slide state grading summary");
88 user_exercise_slide_states::get_grading_summary_by_user_exercise_state_id(
89 conn,
90 loaded_user_exercise_state.id,
91 )
92 .await
93 }
94}
95
96async fn load_latest_teacher_grading_decision(
97 conn: &mut PgConnection,
98 latest_teacher_grading_decision: Option<Option<TeacherGradingDecision>>,
99 loaded_user_exercise_state: &UserExerciseState,
100) -> ModelResult<Option<TeacherGradingDecision>> {
101 if let Some(latest_teacher_grading_decision) = latest_teacher_grading_decision {
102 info!("Using already loaded latest teacher grading decision");
103 Ok(latest_teacher_grading_decision)
104 } else {
105 info!("Loading latest teacher grading decision");
106 Ok(teacher_grading_decisions::try_to_get_latest_grading_decision_by_user_exercise_state_id(conn, loaded_user_exercise_state.id).await?)
107 }
108}
109
110async fn load_current_user_exercise_state(
111 conn: &mut PgConnection,
112 already_loaded_user_exercise_state: Option<UserExerciseState>,
113 user_exercise_state_id: Uuid,
114) -> ModelResult<UserExerciseState> {
115 if let Some(user_exercise_state) = already_loaded_user_exercise_state {
116 info!("Using already loaded user exercise state");
117 Ok(user_exercise_state)
118 } else {
119 info!("Loading user exercise state");
120 Ok(crate::user_exercise_states::get_by_id(conn, user_exercise_state_id).await?)
121 }
122}
123
124async fn load_exercise(
125 conn: &mut PgConnection,
126 already_loaded_exercise: Option<Exercise>,
127 current_user_exercise_state: &UserExerciseState,
128) -> ModelResult<Exercise> {
129 if let Some(exercise) = already_loaded_exercise {
130 info!("Using already loaded exercise");
131 Ok(exercise)
132 } else {
133 info!("Loading exercise");
134 Ok(crate::exercises::get_by_id(conn, current_user_exercise_state.exercise_id).await?)
135 }
136}
137
138async fn load_chapter(
139 conn: &mut PgConnection,
140 already_loaded_chapter: Option<Option<DatabaseChapter>>,
141 chapter_id: Option<Uuid>,
142) -> ModelResult<Option<DatabaseChapter>> {
143 if let Some(chapter_id) = chapter_id {
144 if let Some(already_loaded_chapter) = already_loaded_chapter {
145 info!("Using already loaded chapter");
146 Ok(already_loaded_chapter)
147 } else {
148 info!("Loading chapter");
149 Ok(Some(chapters::get_chapter(conn, chapter_id).await?))
150 }
151 } else {
152 Ok(None)
153 }
154}
155
156async fn load_course(
157 conn: &mut PgConnection,
158 already_loaded_course: Option<Course>,
159 course_id: Option<Uuid>,
160) -> ModelResult<Option<Course>> {
161 if let Some(course_id) = course_id {
162 if let Some(already_loaded_course) = already_loaded_course {
163 info!("Using already loaded course");
164 Ok(Some(already_loaded_course))
165 } else {
166 info!("Loading course");
167 Ok(Some(courses::get_course(conn, course_id).await?))
168 }
169 } else {
170 Ok(None)
171 }
172}
173
174async fn load_peer_or_self_review_information(
175 conn: &mut PgConnection,
176 already_loaded_peer_or_self_review_information: Option<
177 UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation,
178 >,
179 loaded_user_exercise_state: &UserExerciseState,
180 loaded_exercise: &Exercise,
181) -> ModelResult<Option<UserExerciseStateUpdateRequiredDataPeerReviewInformation>> {
182 info!("Loading peer or self review information");
183 if loaded_exercise.needs_peer_review || loaded_exercise.needs_self_review {
184 if loaded_exercise.needs_peer_review {
185 info!("Exercise needs peer review");
186 }
187 if loaded_exercise.needs_self_review {
188 info!("Exercise needs self review");
189 }
190
191 let UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation {
193 given_peer_or_self_review_submissions,
194 given_self_review_submission,
195 latest_exercise_slide_submission,
196 latest_exercise_slide_submission_received_peer_or_self_review_question_submissions,
197 peer_review_queue_entry,
198 peer_or_self_review_config,
199 peer_or_self_review_questions,
200 } = already_loaded_peer_or_self_review_information.unwrap_or_default();
201
202 let loaded_latest_exercise_slide_submission = load_latest_exercise_slide_submission(
203 conn,
204 latest_exercise_slide_submission,
205 loaded_user_exercise_state,
206 )
207 .await?;
208
209 let loaded_peer_or_self_review_config =
210 load_peer_or_self_review_config(conn, peer_or_self_review_config, loaded_exercise)
211 .await?;
212
213 Ok(Some(
214 UserExerciseStateUpdateRequiredDataPeerReviewInformation {
215 given_peer_or_self_review_submissions: load_given_peer_or_self_review_submissions(
216 conn,
217 given_peer_or_self_review_submissions,
218 loaded_user_exercise_state,
219 )
220 .await?,
221 given_self_review_submission: load_given_self_review_submission(
222 conn,
223 given_self_review_submission,
224 loaded_user_exercise_state,
225 )
226 .await?,
227 latest_exercise_slide_submission_received_peer_or_self_review_question_submissions:
228 load_latest_exercise_slide_submission_received_peer_or_self_review_question_submissions(
229 conn,
230 latest_exercise_slide_submission_received_peer_or_self_review_question_submissions,
231 loaded_latest_exercise_slide_submission.id,
232 )
233 .await?,
234 peer_review_queue_entry: load_peer_review_queue_entry(
235 conn,
236 peer_review_queue_entry,
237 loaded_latest_exercise_slide_submission.id,
238 )
239 .await?,
240 peer_or_self_review_config: loaded_peer_or_self_review_config.clone(),
241 peer_or_self_review_questions: load_peer_or_self_review_questions(
242 conn,
243 peer_or_self_review_questions,
244 &loaded_peer_or_self_review_config,
245 )
246 .await?,
247 },
248 ))
249 } else {
250 info!("Exercise does not need peer or self review");
251 Ok(None)
253 }
254}
255
256async fn load_peer_or_self_review_config(
257 conn: &mut PgConnection,
258 already_loaded_peer_or_self_review_config: Option<
259 crate::peer_or_self_review_configs::PeerOrSelfReviewConfig,
260 >,
261 loaded_exercise: &Exercise,
262) -> ModelResult<PeerOrSelfReviewConfig> {
263 if let Some(prc) = already_loaded_peer_or_self_review_config {
264 info!("Using already loaded peer review config");
265 Ok(prc)
266 } else {
267 info!("Loading peer review config");
268 Ok(peer_or_self_review_configs::get_by_exercise_or_course_id(
269 conn,
270 loaded_exercise,
271 loaded_exercise.course_id.ok_or_else(|| {
272 ModelError::new(
273 ModelErrorType::InvalidRequest,
274 "Peer reviews work only on courses (and not, for example, on exams)"
275 .to_string(),
276 None,
277 )
278 })?,
279 )
280 .await?)
281 }
282}
283
284async fn load_peer_or_self_review_questions(
286 conn: &mut PgConnection,
287 already_loaded_peer_or_self_review_questions: Option<Vec<PeerOrSelfReviewQuestion>>,
288 loaded_peer_or_self_review_config: &PeerOrSelfReviewConfig,
289) -> ModelResult<Vec<PeerOrSelfReviewQuestion>> {
290 if let Some(prq) = already_loaded_peer_or_self_review_questions {
291 info!("Using already loaded peer review questions");
292 Ok(prq)
293 } else {
294 info!("Loading peer review questions");
295 let mut questions =
296 peer_or_self_review_questions::get_all_by_peer_or_self_review_config_id(
297 conn,
298 loaded_peer_or_self_review_config.id,
299 )
300 .await?;
301
302 if !loaded_peer_or_self_review_config.points_are_all_or_nothing {
303 questions = normalize_weights(questions);
304 }
305 Ok(questions)
306 }
307}
308
309fn normalize_weights(
310 mut questions: Vec<PeerOrSelfReviewQuestion>,
311) -> Vec<PeerOrSelfReviewQuestion> {
312 info!("Normalizing peer review question weights to sum to 1");
313 questions.sort_by(|a, b| a.order_number.cmp(&b.order_number));
314 info!(
315 "Weights before normalization: {:?}",
316 questions.iter().map(|q| q.weight).collect::<Vec<_>>()
317 );
318 let (mut allowed_to_have_weight, mut not_allowed_to_have_weight) = questions
319 .into_iter()
320 .partition::<Vec<_>, _>(|q| q.question_type == PeerOrSelfReviewQuestionType::Scale);
321 let number_of_questions = allowed_to_have_weight.len();
322 let sum_of_weights = allowed_to_have_weight.iter().map(|q| q.weight).sum::<f32>();
323 if sum_of_weights < 0.000001 {
324 info!(
325 "All weights are zero, setting all weights to 1/number_of_questions so that they sum to 1."
326 );
327 for question in &mut allowed_to_have_weight {
328 question.weight = 1.0 / number_of_questions as f32;
329 }
330 } else {
331 for question in &mut allowed_to_have_weight {
332 question.weight /= sum_of_weights;
333 }
334 }
335 for question in &mut not_allowed_to_have_weight {
336 question.weight = 0.0;
337 }
338 let mut new_vec = not_allowed_to_have_weight;
339 new_vec.append(&mut allowed_to_have_weight);
340 new_vec.sort_by(|a, b| a.order_number.cmp(&b.order_number));
341 questions = new_vec;
342 info!(
343 "Weights after normalization: {:?}",
344 questions.iter().map(|q| q.weight).collect::<Vec<_>>()
345 );
346 questions
347}
348
349async fn load_peer_review_queue_entry(
350 conn: &mut PgConnection,
351 already_loaded_peer_review_queue_entry: Option<Option<PeerReviewQueueEntry>>,
352 latest_exercise_submission_id: Uuid,
353) -> ModelResult<Option<PeerReviewQueueEntry>> {
354 if let Some(prqe) = already_loaded_peer_review_queue_entry {
355 info!("Using already loaded peer review queue entry");
356 Ok(prqe)
357 } else {
358 info!("Loading peer review queue entry");
359 Ok(crate::peer_review_queue_entries::get_by_receiving_peer_reviews_exercise_slide_submission_id(conn, latest_exercise_submission_id ).await.optional()?)
361 }
362}
363
364async fn load_latest_exercise_slide_submission_received_peer_or_self_review_question_submissions(
365 conn: &mut PgConnection,
366 latest_exercise_slide_submission_received_peer_or_self_review_question_submissions: Option<
367 Vec<PeerOrSelfReviewQuestionSubmission>,
368 >,
369 latest_exercise_slide_submission_id: Uuid,
370) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
371 if let Some(
372 latest_exercise_slide_submission_received_peer_or_self_review_question_submissions,
373 ) = latest_exercise_slide_submission_received_peer_or_self_review_question_submissions
374 {
375 info!(
376 "Using already loaded latest exercise slide submission received peer review question submissions"
377 );
378 Ok(latest_exercise_slide_submission_received_peer_or_self_review_question_submissions)
379 } else {
380 info!("Loading latest exercise slide submission received peer review question submissions");
381 Ok(crate::peer_or_self_review_question_submissions::get_received_question_submissions_for_exercise_slide_submission(conn, latest_exercise_slide_submission_id).await?)
382 }
383}
384
385async fn load_latest_exercise_slide_submission(
386 conn: &mut PgConnection,
387 already_loaded_latest_exercise_slide_submission: Option<ExerciseSlideSubmission>,
388 loaded_user_exercise_state: &UserExerciseState,
389) -> ModelResult<ExerciseSlideSubmission> {
390 if let Some(latest_exercise_slide_submission) = already_loaded_latest_exercise_slide_submission
391 {
392 info!("Using already loaded latest exercise slide submission");
393 Ok(latest_exercise_slide_submission)
394 } else {
395 info!("Loading latest exercise slide submission");
396 let selected_exercise_slide_id = loaded_user_exercise_state.selected_exercise_slide_id.ok_or_else(|| ModelError::new(ModelErrorType::PreconditionFailed, "No selected exercise slide id found: presumably the user has not answered the exercise.".to_string(), None))?;
397 let latest_exercise_slide_submission =
399 crate::exercise_slide_submissions::get_users_latest_exercise_slide_submission(
400 conn,
401 selected_exercise_slide_id,
402 loaded_user_exercise_state.user_id,
403 )
404 .await?;
405 Ok(latest_exercise_slide_submission)
406 }
407}
408
409async fn load_given_peer_or_self_review_submissions(
410 conn: &mut PgConnection,
411 already_loaded_given_peer_or_self_review_submissions: Option<Vec<PeerOrSelfReviewSubmission>>,
412 loaded_user_exercise_state: &UserExerciseState,
413) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
414 if let Some(given_peer_or_self_review_submissions) =
415 already_loaded_given_peer_or_self_review_submissions
416 {
417 info!("Using already loaded given peer review submissions");
418 Ok(given_peer_or_self_review_submissions)
419 } else {
420 info!("Loading given peer review submissions");
421 let course_id = loaded_user_exercise_state.course_id.ok_or_else(|| {
422 ModelError::new(
423 ModelErrorType::InvalidRequest,
424 "Peer reviews work only on courses (and not, for example, on exams)".to_string(),
425 None,
426 )
427 })?;
428 Ok(peer_or_self_review_submissions::get_peer_reviews_given_by_user_and_course_instance_and_exercise(conn, loaded_user_exercise_state.user_id, course_id, loaded_user_exercise_state.exercise_id).await?)
429 }
430}
431
432async fn load_given_self_review_submission(
433 conn: &mut PgConnection,
434 already_loaded_given_self_review_submission: Option<Option<PeerOrSelfReviewSubmission>>,
435 loaded_user_exercise_state: &UserExerciseState,
436) -> ModelResult<Option<PeerOrSelfReviewSubmission>> {
437 if let Some(given_self_review_submission) = already_loaded_given_self_review_submission {
438 info!("Using already loaded given self review submission");
439 Ok(given_self_review_submission)
440 } else if let Some(course_id) = loaded_user_exercise_state.course_id {
441 info!("Loading given self review submission");
442 Ok(
443 peer_or_self_review_submissions::get_self_review_submission_by_user_and_exercise(
444 conn,
445 loaded_user_exercise_state.user_id,
446 loaded_user_exercise_state.exercise_id,
447 course_id,
448 )
449 .await?,
450 )
451 } else {
452 Err(ModelError::new(
453 ModelErrorType::PreconditionFailed,
454 "No course instance found: self review is only possible on courses".to_string(),
455 None,
456 ))
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 mod normalize_weights {
465 use super::*;
466
467 #[test]
468 fn test_normalize_weights() {
469 let peer_or_self_review_questions = vec![
470 PeerOrSelfReviewQuestion {
471 id: Uuid::new_v4(),
472 peer_or_self_review_config_id: Uuid::new_v4(),
473 question_type: PeerOrSelfReviewQuestionType::Scale,
474 order_number: 1,
475 weight: 0.3,
476 question: "Scale question".to_string(),
477 created_at: chrono::Utc::now(),
478 updated_at: chrono::Utc::now(),
479 deleted_at: None,
480 answer_required: true,
481 },
482 PeerOrSelfReviewQuestion {
483 id: Uuid::new_v4(),
484 peer_or_self_review_config_id: Uuid::new_v4(),
485 question_type: PeerOrSelfReviewQuestionType::Essay,
486 order_number: 2,
487 weight: 0.1,
488 question: "Text question".to_string(),
489 created_at: chrono::Utc::now(),
490 updated_at: chrono::Utc::now(),
491 deleted_at: None,
492 answer_required: true,
493 },
494 PeerOrSelfReviewQuestion {
495 id: Uuid::new_v4(),
496 peer_or_self_review_config_id: Uuid::new_v4(),
497 question_type: PeerOrSelfReviewQuestionType::Scale,
498 order_number: 3,
499 weight: 0.1,
500 question: "Scale question".to_string(),
501 created_at: chrono::Utc::now(),
502 updated_at: chrono::Utc::now(),
503 deleted_at: None,
504 answer_required: true,
505 },
506 ];
507 let normalized_peer_or_self_review_questions =
508 normalize_weights(peer_or_self_review_questions);
509 assert_eq!(normalized_peer_or_self_review_questions[0].weight, 0.75);
510 assert_eq!(normalized_peer_or_self_review_questions[1].weight, 0.0);
511 assert_eq!(normalized_peer_or_self_review_questions[2].weight, 0.25);
512 }
513 }
514}