<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Atmai</id>
	<title>Expertiza_Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Atmai"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Atmai"/>
	<updated>2026-05-19T22:59:42Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168185</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168185"/>
		<updated>2026-04-30T22:45:45Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively&lt;br /&gt;
developed and maintained by students and faculty at NCSU. As an open-source project built&lt;br /&gt;
on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables&lt;br /&gt;
students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation of the '''E2619 Student Quizzes'''&lt;br /&gt;
project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API).&lt;br /&gt;
The project builds on the E2607 questionnaire rendering code and extends it with&lt;br /&gt;
quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses,&lt;br /&gt;
specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they&lt;br /&gt;
are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;),&lt;br /&gt;
submitting teams create quizzes based on their submissions. Reviewers must complete the&lt;br /&gt;
quizzes before reviewing to demonstrate their understanding. If a reviewer performs&lt;br /&gt;
poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
== Previous Implementation ==&lt;br /&gt;
&lt;br /&gt;
The prior codebase provided the data models, CRUD controllers, and UI components needed for assignments, questionnaires, and peer reviews. However, the quiz workflow was incomplete in several key ways:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; computed scores as &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; using only the numeric answer column, with no correctness check against a stored correct answer. There was no way to distinguish a quiz response from a regular review response.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Item&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column; there was nowhere to persist the expected answer for a quiz item.&lt;br /&gt;
* The &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; model did not carry quiz gateway fields (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;), so the frontend had no structured way to know whether a student must take a quiz before reviewing.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;. Quiz questionnaire ownership was expected to flow through the assignment's questionnaire list, which required instructor configuration and did not support per-team quizzes.&lt;br /&gt;
* The &amp;lt;code&amp;gt;ResponseMapsController&amp;lt;/code&amp;gt; had no index action that filtered out quiz maps, so the frontend could not safely list a student's review assignments without also receiving spurious self-referential quiz entries.&lt;br /&gt;
* No &amp;lt;code&amp;gt;QuizResponseMapsController&amp;lt;/code&amp;gt; existed; there was no endpoint to create a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before a student started a quiz.&lt;br /&gt;
* The questionnaire editor rendered no &amp;quot;Correct answer&amp;quot; field for any item type when the questionnaire type was set to &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
* The student task page had no &amp;quot;Take Quiz&amp;quot; button or any flow to route a student from a quiz gate to the quiz form and back to their review.&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
&lt;br /&gt;
Based on the gaps above, this project aimed to:&lt;br /&gt;
&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to quiz items so submitting teams can specify expected answers at quiz-creation time.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; to detect quiz responses by the &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; invariant and score text/choice items from the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column using case-insensitive equality.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; with four quiz gateway fields so the frontend can render the correct button (Take Quiz / Start Review) without extra API calls.&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column to &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; so each submitting team directly owns its quiz questionnaire, enabling per-team quizzes on the same assignment.&lt;br /&gt;
# Implement &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; so the frontend can create (or reuse) a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before navigating a student to the quiz form.&lt;br /&gt;
# Update &amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; to exclude quiz maps and include per-row quiz state.&lt;br /&gt;
# Add a &amp;quot;Correct answer&amp;quot; row in the questionnaire editor for every quiz item type.&lt;br /&gt;
# Implement an instructor &amp;quot;Create Quiz&amp;quot; button on the reviewer-assignment page that opens the questionnaire editor pre-linked to the team.&lt;br /&gt;
# Implement a &amp;quot;Take Quiz&amp;quot; button in the student task list that calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; and navigates to the existing review form in quiz mode.&lt;br /&gt;
# Add &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; so the frontend can fetch the allowed quiz item types dynamically.&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │&lt;br /&gt;
│       │               (quiz mode)        │                  │&lt;br /&gt;
│       │                                  └──→ [redirect]    │&lt;br /&gt;
│       └──→ [Start/Open Review] ──→ TeammateReview           │&lt;br /&gt;
│                                     (review mode)           │&lt;br /&gt;
│  QuestionnaireEditor ──→ correct_answer fields per type     │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuizResponseMapsController  (POST /quiz_response_maps)     │&lt;br /&gt;
│  ResponsesController         (create / update / submit)     │&lt;br /&gt;
│  ResponseMapsController      (index — filters quiz maps)    │&lt;br /&gt;
│  Response model              (aggregate_questionnaire_score) │&lt;br /&gt;
│  StudentTask model           (quiz gateway fields)          │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│              Assignment               │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +boolean require_quiz                 │&lt;br /&gt;
│ +int num_quiz_questions               │&lt;br /&gt;
│ +has_many questionnaires (via AQ)     │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many (via AQ)&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐     ┌─────────────────────────────┐&lt;br /&gt;
│            Questionnaire              │     │        Team  [UPDATED]      │&lt;br /&gt;
├───────────────────────────────────────┤     ├─────────────────────────────┤&lt;br /&gt;
│ +String name                          │     │ +int quiz_questionnaire_id  │◄─── NEW&lt;br /&gt;
│ +String questionnaire_type            │◄────│ +belongs_to                 │&lt;br /&gt;
│   (&amp;quot;Quiz&amp;quot; for quiz questionnaires)    │     │   quiz_questionnaire        │&lt;br /&gt;
│ +int min_question_score               │     └─────────────────────────────┘&lt;br /&gt;
│ +int max_question_score               │&lt;br /&gt;
│ +int instructor_id                    │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│            Item  [UPDATED]            │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +String txt                           │&lt;br /&gt;
│ +String question_type                 │&lt;br /&gt;
│ +int weight                           │&lt;br /&gt;
│ +String correct_answer               ◄│─── NEW&lt;br /&gt;
│ +QUIZ_ITEM_TYPES : Array             ◄│─── NEW&lt;br /&gt;
│ +is_quiz_item?() : boolean           ◄│─── NEW&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many answers&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│               Answer                  │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int item_id                          │&lt;br /&gt;
│ +int response_id                      │&lt;br /&gt;
│ +int answer                           │&lt;br /&gt;
│ +String comments  (quiz answer here)  │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│          Response  [UPDATED]          │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int map_id                           │&lt;br /&gt;
│ +boolean is_submitted                 │&lt;br /&gt;
│ +int round                            │&lt;br /&gt;
│ +aggregate_questionnaire_score()     ◄│─── quiz-aware: checks&lt;br /&gt;
│                                       │    reviewer_id == reviewee_id&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│      QuizResponseMap  [UPDATED]       │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int reviewer_id                      │&lt;br /&gt;
│ +int reviewee_id                      │&lt;br /&gt;
│   (reviewer_id == reviewee_id        ◄│─── self-referential invariant&lt;br /&gt;
│    identifies quiz maps)              │    distinguishes quiz from review&lt;br /&gt;
│ +int reviewed_object_id              ◄│─── quiz questionnaire id&lt;br /&gt;
│ +mappings_for_reviewer()              │&lt;br /&gt;
└───────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshots ==&lt;br /&gt;
&lt;br /&gt;
=== Take Quiz Button (Assigned Reviews) ===&lt;br /&gt;
[[File:take_quiz_button.png| 500px]]&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Editor – Correct Answer Fields ===&lt;br /&gt;
[[File:correct_answer_fields.png| 500px]]&lt;br /&gt;
&lt;br /&gt;
=== Quiz Submission and Score Display ===&lt;br /&gt;
[[File:quiz_score_display.png| 500px]]&lt;br /&gt;
&lt;br /&gt;
== Changes Implemented ==&lt;br /&gt;
&lt;br /&gt;
=== 1) Database Schema Additions ===&lt;br /&gt;
&lt;br /&gt;
Two new columns support the quiz feature. The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column on &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt; stores the expected answer for each quiz question. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items. The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; lets each submitting team directly own its quiz questionnaire — a key change that makes per-team quizzes possible and replaces a previous approach that required manual wiring at the assignment level.&lt;br /&gt;
&lt;br /&gt;
Two migrations were added to support the quiz features:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# db/migrate/20260423000001_add_correct_answer_to_items.rb&lt;br /&gt;
class AddCorrectAnswerToItems &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :items, :correct_answer, :string&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&lt;br /&gt;
class AddQuizQuestionnaireIdToTeams &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :teams, :quiz_questionnaire_id, :integer, null: true, default: nil&lt;br /&gt;
    add_index  :teams, :quiz_questionnaire_id&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column stores the expected answer for each quiz item. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; allows each submitting team to directly own its quiz questionnaire. This replaces a previous approach that required the instructor to manually wire a questionnaire to the assignment, and is the key change that makes per-team quizzes possible.&lt;br /&gt;
&lt;br /&gt;
=== 2) Quiz-Aware Scoring in Response Model ===&lt;br /&gt;
&lt;br /&gt;
Quiz responses are identified by the invariant &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; on the associated &amp;lt;code&amp;gt;ResponseMap&amp;lt;/code&amp;gt; — no STI &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column is needed. For text and choice quiz items the student's answer is stored in the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column (not the numeric &amp;lt;code&amp;gt;answer&amp;lt;/code&amp;gt; column), so scoring uses case-insensitive string equality. Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions. The final score is returned in the &amp;lt;code&amp;gt;PATCH /responses/:id/submit&amp;lt;/code&amp;gt; response body as &amp;lt;code&amp;gt;total_score&amp;lt;/code&amp;gt; and displayed to the student before the redirect.&lt;br /&gt;
&lt;br /&gt;
The core scoring logic in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; was extended to handle quiz responses:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def aggregate_questionnaire_score&lt;br /&gt;
  sum = 0&lt;br /&gt;
  is_quiz = map.reviewer_id == map.reviewee_id&lt;br /&gt;
  comment_scored_types = %w[&lt;br /&gt;
    TextField MultipleChoiceRadio MultipleChoiceCheckbox&lt;br /&gt;
    Text\ field Multiple\ choice Multiple\ choice\ checkbox&lt;br /&gt;
  ].freeze&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    if is_quiz &amp;amp;&amp;amp; comment_scored_types.include?(s.item.question_type)&lt;br /&gt;
      correct        = s.item.correct_answer.to_s.strip.downcase&lt;br /&gt;
      student_answer = s.comments.to_s.strip.downcase&lt;br /&gt;
      sum += (student_answer == correct &amp;amp;&amp;amp; correct.present? ? 1 : 0) * (s.item.weight || 1)&lt;br /&gt;
    else&lt;br /&gt;
      sum += s.answer * (s.item.weight || 1) unless s.answer.nil?&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  sum&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions.&lt;br /&gt;
&lt;br /&gt;
=== 3) StudentTask Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; so the frontend can make gatekeeping decisions without additional API calls: &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; (true only when a submitted &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; exists against the student's &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;). The frontend &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component uses these fields to gate each review row — if &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; has_quiz_questionnaire &amp;amp;&amp;amp; !quiz_taken&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown instead of the review button.&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
attr_accessor :assignment, :assignment_id, :current_stage, :participant,&lt;br /&gt;
              :stage_deadline, :topic, :permission_granted,&lt;br /&gt;
              :require_quiz, :quiz_taken, :has_quiz_questionnaire, :quiz_questionnaire_id&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; factory method was updated to compute these fields:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def self.from_participant(participant)&lt;br /&gt;
  asgn = participant.assignment&lt;br /&gt;
  return nil unless asgn&lt;br /&gt;
&lt;br /&gt;
  review_maps  = ReviewResponseMap.where(reviewer_id: participant.id)&lt;br /&gt;
  quiz_q_id    = review_maps.filter_map { |m| Team.find_by(id: m.reviewee_id)&amp;amp;.quiz_questionnaire_id }.first&lt;br /&gt;
  quiz_taken   = QuizResponseMap.where(reviewer_id: participant.id, reviewed_object_id: quiz_q_id)&lt;br /&gt;
                                .joins(:responses)&lt;br /&gt;
                                .where(responses: { is_submitted: true })&lt;br /&gt;
                                .exists?  if quiz_q_id&lt;br /&gt;
&lt;br /&gt;
  new(&lt;br /&gt;
    assignment:             asgn.name,&lt;br /&gt;
    assignment_id:          asgn.id,&lt;br /&gt;
    require_quiz:           asgn.require_quiz || false,&lt;br /&gt;
    has_quiz_questionnaire: quiz_q_id.present?,&lt;br /&gt;
    quiz_questionnaire_id:  quiz_q_id,&lt;br /&gt;
    quiz_taken:             quiz_taken || false,&lt;br /&gt;
    # ... other fields&lt;br /&gt;
  )&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4) QuizResponseMapsController ===&lt;br /&gt;
&lt;br /&gt;
A &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; is self-referential: &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt;, both pointing to the reviewer's own &amp;lt;code&amp;gt;AssignmentParticipant&amp;lt;/code&amp;gt; record. This invariant is what distinguishes quiz maps from peer-review maps throughout the system. The endpoint is idempotent — if a map already exists for this student/questionnaire pair it is returned as-is rather than creating a duplicate, making it safe for the frontend to call on every &amp;quot;Take Quiz&amp;quot; click.&lt;br /&gt;
&lt;br /&gt;
A new controller handles quiz map creation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST /quiz_response_maps&lt;br /&gt;
def create&lt;br /&gt;
  # ... validate params, find assignment, resolve quiz questionnaire from team ...&lt;br /&gt;
  map = QuizResponseMap.find_by(&lt;br /&gt;
    reviewed_object_id: quiz_questionnaire.id,&lt;br /&gt;
    reviewer_id: reviewer_participant.id,&lt;br /&gt;
    reviewee_id: reviewer_participant.id&lt;br /&gt;
  ) || QuizResponseMap.new(...)&lt;br /&gt;
&lt;br /&gt;
  render json: {&lt;br /&gt;
    quiz_map_id:             map.id,&lt;br /&gt;
    quiz_questionnaire_id:   quiz_questionnaire.id,&lt;br /&gt;
    reviewer_participant_id: reviewer_participant.id&lt;br /&gt;
  }, status: :created&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5) ResponseMapsController — Quiz Map Filtering ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; action must return only peer-review maps; quiz maps must not appear in the reviewer table. The primary guard &amp;lt;code&amp;gt;next if map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt; is reliable regardless of accidental id coincidences between questionnaire ids and assignment ids. A secondary &amp;lt;code&amp;gt;next unless assignment&amp;lt;/code&amp;gt; check acts as belt-and-suspenders. Each returned row is augmented with &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; so the frontend can render the &amp;quot;Take Quiz&amp;quot; button without a second API call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; was updated to exclude quiz maps and include per-row quiz state for the frontend:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
result = maps.filter_map do |map|&lt;br /&gt;
  # E2619: skip quiz maps (reviewer_id == reviewee_id)&lt;br /&gt;
  next if map.reviewer_id == map.reviewee_id&lt;br /&gt;
  # belt-and-suspenders: reviewed_object_id must reference an assignment&lt;br /&gt;
  assignment = Assignment.find_by(id: map.reviewed_object_id)&lt;br /&gt;
  next unless assignment&lt;br /&gt;
&lt;br /&gt;
  team = Team.find_by(id: map.reviewee_id)&lt;br /&gt;
  quiz_questionnaire_id = team&amp;amp;.quiz_questionnaire_id&lt;br /&gt;
  quiz_taken = quiz_questionnaire_id.present? &amp;amp;&amp;amp;&lt;br /&gt;
    QuizResponseMap.where(reviewer_id: map.reviewer_id, reviewed_object_id: quiz_questionnaire_id)&lt;br /&gt;
                   .joins(&amp;quot;INNER JOIN responses ON responses.map_id = response_maps.id&amp;quot;)&lt;br /&gt;
                   .where(responses: { is_submitted: true }).exists?&lt;br /&gt;
&lt;br /&gt;
  { id: map.id, quiz_questionnaire_id:, quiz_taken:, ... }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6) Instructor &amp;quot;Create Quiz&amp;quot; Button (AssignReviewer) ===&lt;br /&gt;
&lt;br /&gt;
When clicked, the button navigates to the questionnaire editor with URL parameters (&amp;lt;code&amp;gt;type=Quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt;) that cause the editor to link the created questionnaire back to the team automatically via &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;. After saving, the editor redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This is a temporary solution. In the full Expertiza workflow, quiz creation is initiated by a submitting team member from their own task page — not by an instructor from the reviewer-assignment page. The button was placed here because the student-facing entry point was not yet implemented in this codebase and should be removed or relocated once the proper student team submission flow is in place.&lt;br /&gt;
&lt;br /&gt;
An &amp;lt;code&amp;gt;onCreateQuiz&amp;lt;/code&amp;gt; handler was added to the &amp;lt;code&amp;gt;AssignReviewer&amp;lt;/code&amp;gt; page:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const onCreateQuiz = useCallback((teamId: number) =&amp;gt; {&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/questionnaires/new?type=Quiz` +&lt;br /&gt;
    `&amp;amp;team_id=${teamId}` +&lt;br /&gt;
    `&amp;amp;return_to=${encodeURIComponent(location.pathname + location.search)}`&lt;br /&gt;
  );&lt;br /&gt;
}, [navigate, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the questionnaire is saved, &amp;lt;code&amp;gt;QuestionnaireEditor&amp;lt;/code&amp;gt; reads &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt; from the URL, calls &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;, and redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
=== 7) Student &amp;quot;Take Quiz&amp;quot; Button (AssignedReviews) ===&lt;br /&gt;
&lt;br /&gt;
The per-row quiz state (&amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;) is returned directly in the &amp;lt;code&amp;gt;GET /response_maps&amp;lt;/code&amp;gt; response so no extra API call is needed to decide which button to show. When the student clicks &amp;quot;Take Quiz&amp;quot;, the frontend first calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; (idempotent) to obtain or reuse a quiz map id, then navigates to the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page with a &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; parameter encoding the peer-review URL. After the quiz is submitted the page auto-redirects to the actual review.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component now reads per-row quiz state from the response maps API response. When &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; hasQuizQuestionnaire &amp;amp;&amp;amp; !quizCompleted&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const handleTakeQuiz = async (review: AssignedReviewRow, reviewUrl: string) =&amp;gt; {&lt;br /&gt;
  const res = await axiosClient.post('/quiz_response_maps', {&lt;br /&gt;
    assignment_id:    review.assignmentId,&lt;br /&gt;
    reviewer_user_id: currentUser.id,&lt;br /&gt;
    reviewee_team_id: review.revieweeTeamId,&lt;br /&gt;
  });&lt;br /&gt;
  const { quiz_map_id, quiz_questionnaire_id } = res.data;&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/student_teams/teammate_review?` +&lt;br /&gt;
    `map_id=${quiz_map_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_id=${quiz_questionnaire_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_type=Quiz` +&lt;br /&gt;
    `&amp;amp;redirect_after=${encodeURIComponent(reviewUrl)}`&lt;br /&gt;
  );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8) Quiz Mode in TeammateReview ===&lt;br /&gt;
&lt;br /&gt;
Reusing the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page for quiz-taking avoids duplicating the form rendering logic. Quiz mode is activated by &amp;lt;code&amp;gt;questionnaire_type=Quiz&amp;lt;/code&amp;gt; in the URL, which causes the page to fetch quiz questionnaire items, disable the &amp;quot;Save Draft&amp;quot; button (quizzes are single-submission), display the score after submission, and show a &amp;quot;Proceed to Review&amp;quot; button that navigates to the &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; URL.&lt;br /&gt;
&lt;br /&gt;
The existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page was extended with a quiz mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
if (isQuizMode) {&lt;br /&gt;
    setQuizScore(submitRes.data?.total_score ?? null);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 9) Correct Answer Fields in Questionnaire Editor ===&lt;br /&gt;
&lt;br /&gt;
When the questionnaire type is &amp;quot;Quiz&amp;quot;, each item in &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; needs a &amp;quot;Correct answer&amp;quot; input whose control type depends on the question type. A dropdown makes sense for choice questions (the options are already defined), a bounded number input for Scale, a checkbox for Checkbox, and a free-text input for TextField (scored case-insensitively). The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; value is persisted through &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; and serialised via &amp;lt;code&amp;gt;Item#as_json&amp;lt;/code&amp;gt; only for quiz items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; renders a &amp;quot;Correct answer&amp;quot; row for each quiz item. The input type varies by question type:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Question type !! Correct answer input&lt;br /&gt;
|-&lt;br /&gt;
| Text field || Free-text &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;; scored case-insensitively at submission&lt;br /&gt;
|-&lt;br /&gt;
| Multiple choice / Multiple choice checkbox || &amp;lt;code&amp;gt;&amp;lt;select&amp;gt;&amp;lt;/code&amp;gt; pre-populated from the item's alternatives&lt;br /&gt;
|-&lt;br /&gt;
| Scale || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;number&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; bounded by the item's weight range&lt;br /&gt;
|-&lt;br /&gt;
| Checkbox || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; (correct = checked)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 10) GET /questions/quiz_types Endpoint ===&lt;br /&gt;
&lt;br /&gt;
Hard-coding the list of allowed quiz item types in the frontend would create a maintenance hazard every time a new type is added to the backend. The &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; endpoint lets the frontend fetch the authoritative list at runtime. The same constant is used in the &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action to validate incoming item types, returning 422 if an unsupported type is submitted for a quiz questionnaire.&lt;br /&gt;
&lt;br /&gt;
A new endpoint returns the allowed item types for quiz questionnaires so the frontend does not hard-code the list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
QUIZ_ITEM_TYPES = %w[TextField MultipleChoiceRadio MultipleChoiceCheckbox Scale Checkbox].freeze&lt;br /&gt;
&lt;br /&gt;
# GET /questions/quiz_types&lt;br /&gt;
def quiz_types&lt;br /&gt;
  render json: QUIZ_ITEM_TYPES, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action on &amp;lt;code&amp;gt;QuestionsController&amp;lt;/code&amp;gt; also validates that quiz questionnaires only accept items of these types, returning 422 with an error message otherwise.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
Tests were added for all new and modified backend components. The full suite passes with 0 failures.&lt;br /&gt;
&lt;br /&gt;
=== Pre-Test Setup ===&lt;br /&gt;
&lt;br /&gt;
All request specs use the shared &amp;lt;code&amp;gt;create_roles_hierarchy&amp;lt;/code&amp;gt; helper and JWT-based auth:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
before(:all) { @roles = create_roles_hierarchy }&lt;br /&gt;
let(:token)         { JsonWebToken.encode({ id: instructor.id }) }&lt;br /&gt;
let(:Authorization) { &amp;quot;Bearer #{token}&amp;quot; }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: Response — Quiz Scoring ===&lt;br /&gt;
&lt;br /&gt;
11 new examples in &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; validate the quiz scoring path, covering both spaced and CamelCase question type names, correct/incorrect answers, blank answers, multi-item accumulation, and maximum score calculation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe '#aggregate_questionnaire_score (quiz)' do&lt;br /&gt;
  it 'scores a correct &amp;quot;Text field&amp;quot; answer' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'paris') # case-insensitive&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(2)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'scores an incorrect &amp;quot;Text field&amp;quot; answer as 0' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'London')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'gives 0 when the comments column is blank' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: '')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: StudentTask — Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; verify the quiz gateway fields are populated by &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and serialised by &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Request: QuizResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
6 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| Happy path — creates map and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| Idempotent — reuses existing map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;assignment_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Assignment not found (id = 999999999) || 404&lt;br /&gt;
|-&lt;br /&gt;
| Team has no quiz questionnaire || 422&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: ResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns peer-review maps, excludes quiz maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — each entry includes &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns empty array when reviewer has no maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| POST — creates ReviewResponseMap and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — idempotent, returns same map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — missing required param || 400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: Questions — Quiz Types ===&lt;br /&gt;
&lt;br /&gt;
3 new examples in &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe 'GET /questions/quiz_types' do&lt;br /&gt;
  it 'returns 200 with the allowed quiz item type strings' do&lt;br /&gt;
    get '/questions/quiz_types', headers: { 'Authorization' =&amp;gt; auth_header }&lt;br /&gt;
    types = JSON.parse(response.body)&lt;br /&gt;
    expect(types).to include('TextField', 'MultipleChoiceRadio', 'MultipleChoiceCheckbox')&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Spec file !! Examples before !! Examples after&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || 3 (failing) || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || 11 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || 19 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 6 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| '''Total new examples''' || || '''27'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Files Modified ==&lt;br /&gt;
&lt;br /&gt;
=== Backend (Ruby on Rails) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/response.rb&amp;lt;/code&amp;gt; || Added quiz-aware scoring to &amp;lt;code&amp;gt;aggregate_questionnaire_score&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maximum_score&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/student_task.rb&amp;lt;/code&amp;gt; || Added four quiz gateway fields; updated &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/Item.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;QUIZ_ITEM_TYPES&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;correct_answer_only_for_quiz&amp;lt;/code&amp;gt; validation, and conditional &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/team.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; attribute and &amp;lt;code&amp;gt;quiz_questionnaire&amp;lt;/code&amp;gt; association&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/quiz_response_map.rb&amp;lt;/code&amp;gt; || Minor documentation updates&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questions_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt;; added quiz item type validation in &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/quiz_response_maps_controller.rb&amp;lt;/code&amp;gt; || New controller: &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/response_maps_controller.rb&amp;lt;/code&amp;gt; || Updated &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; to filter quiz maps and return per-row quiz state; added &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/teams_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questionnaires_controller.rb&amp;lt;/code&amp;gt; || Exposed &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; in strong parameters&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added routes for quiz_response_maps, quiz_types, and quiz_questionnaire on teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260423000001_add_correct_answer_to_items.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column + index to &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || Fixed existing test failures; added quiz gateway field assertions&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || Added 11 new quiz scoring tests&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || Added tests for &amp;lt;code&amp;gt;quiz_types&amp;lt;/code&amp;gt; endpoint and quiz item creation/rejection&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 6 tests covering happy path, idempotency, and error cases&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 7 tests covering quiz-map exclusion, quiz state fields, and POST CRUD&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React/TypeScript) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/StudentTasks/AssignedReviews.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Take Quiz&amp;quot; button; per-row quiz state; &amp;lt;code&amp;gt;handleTakeQuiz&amp;lt;/code&amp;gt; calling &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignReviewer.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Create Quiz&amp;quot; button that navigates to the questionnaire editor pre-linked to the team&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx&amp;lt;/code&amp;gt; || Added correct-answer row for each quiz item type&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireEditor.tsx&amp;lt;/code&amp;gt; || Added team-quiz creation flow: links new questionnaire to team via &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Student Teams/TeammateReview.tsx&amp;lt;/code&amp;gt; || Added quiz mode (&amp;lt;code&amp;gt;isQuizMode&amp;lt;/code&amp;gt;), quiz score display, and &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; param support&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentEditor.tsx&amp;lt;/code&amp;gt; || Ensured &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; checkbox is wired to the form state&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentUtil.ts&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IAssignmentFormValues&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireUtils.tsx&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IItem&amp;lt;/code&amp;gt;; updated &amp;lt;code&amp;gt;transformQuestionnaireRequest&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/components/Form/FormSelect.tsx&amp;lt;/code&amp;gt; || Documentation only (JSDoc)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Github Pull Request ===&lt;br /&gt;
&lt;br /&gt;
* Frontend: https://github.com/expertiza/reimplementation-front-end/pull/181&lt;br /&gt;
* Backend: https://github.com/expertiza/reimplementation-back-end/pull/349&lt;br /&gt;
=== Video ===&lt;br /&gt;
&lt;br /&gt;
https://youtu.be/BWh_6bavtbE&lt;br /&gt;
&lt;br /&gt;
=== Mentor ===&lt;br /&gt;
&lt;br /&gt;
Ed Gehringer (efg@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
=== Team Member ===&lt;br /&gt;
&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Mekhi Parker (mrparke4@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168159</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168159"/>
		<updated>2026-04-28T14:55:26Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively&lt;br /&gt;
developed and maintained by students and faculty at NCSU. As an open-source project built&lt;br /&gt;
on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables&lt;br /&gt;
students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation of the '''E2619 Student Quizzes'''&lt;br /&gt;
project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API).&lt;br /&gt;
The project builds on the E2607 questionnaire rendering code and extends it with&lt;br /&gt;
quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses,&lt;br /&gt;
specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they&lt;br /&gt;
are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;),&lt;br /&gt;
submitting teams create quizzes based on their submissions. Reviewers must complete the&lt;br /&gt;
quizzes before reviewing to demonstrate their understanding. If a reviewer performs&lt;br /&gt;
poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
== Previous Implementation ==&lt;br /&gt;
&lt;br /&gt;
The prior codebase provided the data models, CRUD controllers, and UI components needed for assignments, questionnaires, and peer reviews. However, the quiz workflow was incomplete in several key ways:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; computed scores as &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; using only the numeric answer column, with no correctness check against a stored correct answer. There was no way to distinguish a quiz response from a regular review response.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Item&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column; there was nowhere to persist the expected answer for a quiz item.&lt;br /&gt;
* The &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; model did not carry quiz gateway fields (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;), so the frontend had no structured way to know whether a student must take a quiz before reviewing.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;. Quiz questionnaire ownership was expected to flow through the assignment's questionnaire list, which required instructor configuration and did not support per-team quizzes.&lt;br /&gt;
* The &amp;lt;code&amp;gt;ResponseMapsController&amp;lt;/code&amp;gt; had no index action that filtered out quiz maps, so the frontend could not safely list a student's review assignments without also receiving spurious self-referential quiz entries.&lt;br /&gt;
* No &amp;lt;code&amp;gt;QuizResponseMapsController&amp;lt;/code&amp;gt; existed; there was no endpoint to create a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before a student started a quiz.&lt;br /&gt;
* The questionnaire editor rendered no &amp;quot;Correct answer&amp;quot; field for any item type when the questionnaire type was set to &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
* The student task page had no &amp;quot;Take Quiz&amp;quot; button or any flow to route a student from a quiz gate to the quiz form and back to their review.&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
&lt;br /&gt;
Based on the gaps above, this project aimed to:&lt;br /&gt;
&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to quiz items so submitting teams can specify expected answers at quiz-creation time.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; to detect quiz responses by the &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; invariant and score text/choice items from the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column using case-insensitive equality.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; with four quiz gateway fields so the frontend can render the correct button (Take Quiz / Start Review) without extra API calls.&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column to &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; so each submitting team directly owns its quiz questionnaire, enabling per-team quizzes on the same assignment.&lt;br /&gt;
# Implement &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; so the frontend can create (or reuse) a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before navigating a student to the quiz form.&lt;br /&gt;
# Update &amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; to exclude quiz maps and include per-row quiz state.&lt;br /&gt;
# Add a &amp;quot;Correct answer&amp;quot; row in the questionnaire editor for every quiz item type.&lt;br /&gt;
# Implement an instructor &amp;quot;Create Quiz&amp;quot; button on the reviewer-assignment page that opens the questionnaire editor pre-linked to the team.&lt;br /&gt;
# Implement a &amp;quot;Take Quiz&amp;quot; button in the student task list that calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; and navigates to the existing review form in quiz mode.&lt;br /&gt;
# Add &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; so the frontend can fetch the allowed quiz item types dynamically.&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │&lt;br /&gt;
│       │               (quiz mode)        │                  │&lt;br /&gt;
│       │                                  └──→ [redirect]    │&lt;br /&gt;
│       └──→ [Start/Open Review] ──→ TeammateReview           │&lt;br /&gt;
│                                     (review mode)           │&lt;br /&gt;
│  QuestionnaireEditor ──→ correct_answer fields per type     │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuizResponseMapsController  (POST /quiz_response_maps)     │&lt;br /&gt;
│  ResponsesController         (create / update / submit)     │&lt;br /&gt;
│  ResponseMapsController      (index — filters quiz maps)    │&lt;br /&gt;
│  Response model              (aggregate_questionnaire_score) │&lt;br /&gt;
│  StudentTask model           (quiz gateway fields)          │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│              Assignment               │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +boolean require_quiz                 │&lt;br /&gt;
│ +int num_quiz_questions               │&lt;br /&gt;
│ +has_many questionnaires (via AQ)     │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many (via AQ)&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐     ┌─────────────────────────────┐&lt;br /&gt;
│            Questionnaire              │     │        Team  [UPDATED]      │&lt;br /&gt;
├───────────────────────────────────────┤     ├─────────────────────────────┤&lt;br /&gt;
│ +String name                          │     │ +int quiz_questionnaire_id  │◄─── NEW&lt;br /&gt;
│ +String questionnaire_type            │◄────│ +belongs_to                 │&lt;br /&gt;
│   (&amp;quot;Quiz&amp;quot; for quiz questionnaires)    │     │   quiz_questionnaire        │&lt;br /&gt;
│ +int min_question_score               │     └─────────────────────────────┘&lt;br /&gt;
│ +int max_question_score               │&lt;br /&gt;
│ +int instructor_id                    │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│            Item  [UPDATED]            │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +String txt                           │&lt;br /&gt;
│ +String question_type                 │&lt;br /&gt;
│ +int weight                           │&lt;br /&gt;
│ +String correct_answer               ◄│─── NEW&lt;br /&gt;
│ +QUIZ_ITEM_TYPES : Array             ◄│─── NEW&lt;br /&gt;
│ +is_quiz_item?() : boolean           ◄│─── NEW&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many answers&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│               Answer                  │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int item_id                          │&lt;br /&gt;
│ +int response_id                      │&lt;br /&gt;
│ +int answer                           │&lt;br /&gt;
│ +String comments  (quiz answer here)  │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│          Response  [UPDATED]          │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int map_id                           │&lt;br /&gt;
│ +boolean is_submitted                 │&lt;br /&gt;
│ +int round                            │&lt;br /&gt;
│ +aggregate_questionnaire_score()     ◄│─── quiz-aware: checks&lt;br /&gt;
│                                       │    reviewer_id == reviewee_id&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│      QuizResponseMap  [UPDATED]       │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int reviewer_id                      │&lt;br /&gt;
│ +int reviewee_id                      │&lt;br /&gt;
│   (reviewer_id == reviewee_id        ◄│─── self-referential invariant&lt;br /&gt;
│    identifies quiz maps)              │    distinguishes quiz from review&lt;br /&gt;
│ +int reviewed_object_id              ◄│─── quiz questionnaire id&lt;br /&gt;
│ +mappings_for_reviewer()              │&lt;br /&gt;
└───────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Screenshots ===&lt;br /&gt;
[[File:e2619_take_quiz_button.png|Take Quiz button in AssignedReviews]]&lt;br /&gt;
[[File:e2619_correct_answer_fields.png|Correct answer fields in Questionnaire Editor]]&lt;br /&gt;
[[File:e2619_quiz_score.png|Quiz score display after submission]]&lt;br /&gt;
&lt;br /&gt;
== Changes Implemented ==&lt;br /&gt;
&lt;br /&gt;
=== 1) Database Schema Additions ===&lt;br /&gt;
&lt;br /&gt;
Two new columns support the quiz feature. The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column on &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt; stores the expected answer for each quiz question. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items. The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; lets each submitting team directly own its quiz questionnaire — a key change that makes per-team quizzes possible and replaces a previous approach that required manual wiring at the assignment level.&lt;br /&gt;
&lt;br /&gt;
Two migrations were added to support the quiz features:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# db/migrate/20260423000001_add_correct_answer_to_items.rb&lt;br /&gt;
class AddCorrectAnswerToItems &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :items, :correct_answer, :string&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&lt;br /&gt;
class AddQuizQuestionnaireIdToTeams &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :teams, :quiz_questionnaire_id, :integer, null: true, default: nil&lt;br /&gt;
    add_index  :teams, :quiz_questionnaire_id&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column stores the expected answer for each quiz item. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; allows each submitting team to directly own its quiz questionnaire. This replaces a previous approach that required the instructor to manually wire a questionnaire to the assignment, and is the key change that makes per-team quizzes possible.&lt;br /&gt;
&lt;br /&gt;
=== 2) Quiz-Aware Scoring in Response Model ===&lt;br /&gt;
&lt;br /&gt;
Quiz responses are identified by the invariant &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; on the associated &amp;lt;code&amp;gt;ResponseMap&amp;lt;/code&amp;gt; — no STI &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column is needed. For text and choice quiz items the student's answer is stored in the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column (not the numeric &amp;lt;code&amp;gt;answer&amp;lt;/code&amp;gt; column), so scoring uses case-insensitive string equality. Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions. The final score is returned in the &amp;lt;code&amp;gt;PATCH /responses/:id/submit&amp;lt;/code&amp;gt; response body as &amp;lt;code&amp;gt;total_score&amp;lt;/code&amp;gt; and displayed to the student before the redirect.&lt;br /&gt;
&lt;br /&gt;
The core scoring logic in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; was extended to handle quiz responses:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def aggregate_questionnaire_score&lt;br /&gt;
  sum = 0&lt;br /&gt;
  is_quiz = map.reviewer_id == map.reviewee_id&lt;br /&gt;
  comment_scored_types = %w[&lt;br /&gt;
    TextField MultipleChoiceRadio MultipleChoiceCheckbox&lt;br /&gt;
    Text\ field Multiple\ choice Multiple\ choice\ checkbox&lt;br /&gt;
  ].freeze&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    if is_quiz &amp;amp;&amp;amp; comment_scored_types.include?(s.item.question_type)&lt;br /&gt;
      correct        = s.item.correct_answer.to_s.strip.downcase&lt;br /&gt;
      student_answer = s.comments.to_s.strip.downcase&lt;br /&gt;
      sum += (student_answer == correct &amp;amp;&amp;amp; correct.present? ? 1 : 0) * (s.item.weight || 1)&lt;br /&gt;
    else&lt;br /&gt;
      sum += s.answer * (s.item.weight || 1) unless s.answer.nil?&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  sum&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions.&lt;br /&gt;
&lt;br /&gt;
=== 3) StudentTask Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; so the frontend can make gatekeeping decisions without additional API calls: &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; (true only when a submitted &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; exists against the student's &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;). The frontend &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component uses these fields to gate each review row — if &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; has_quiz_questionnaire &amp;amp;&amp;amp; !quiz_taken&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown instead of the review button.&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
attr_accessor :assignment, :assignment_id, :current_stage, :participant,&lt;br /&gt;
              :stage_deadline, :topic, :permission_granted,&lt;br /&gt;
              :require_quiz, :quiz_taken, :has_quiz_questionnaire, :quiz_questionnaire_id&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; factory method was updated to compute these fields:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def self.from_participant(participant)&lt;br /&gt;
  asgn = participant.assignment&lt;br /&gt;
  return nil unless asgn&lt;br /&gt;
&lt;br /&gt;
  review_maps  = ReviewResponseMap.where(reviewer_id: participant.id)&lt;br /&gt;
  quiz_q_id    = review_maps.filter_map { |m| Team.find_by(id: m.reviewee_id)&amp;amp;.quiz_questionnaire_id }.first&lt;br /&gt;
  quiz_taken   = QuizResponseMap.where(reviewer_id: participant.id, reviewed_object_id: quiz_q_id)&lt;br /&gt;
                                .joins(:responses)&lt;br /&gt;
                                .where(responses: { is_submitted: true })&lt;br /&gt;
                                .exists?  if quiz_q_id&lt;br /&gt;
&lt;br /&gt;
  new(&lt;br /&gt;
    assignment:             asgn.name,&lt;br /&gt;
    assignment_id:          asgn.id,&lt;br /&gt;
    require_quiz:           asgn.require_quiz || false,&lt;br /&gt;
    has_quiz_questionnaire: quiz_q_id.present?,&lt;br /&gt;
    quiz_questionnaire_id:  quiz_q_id,&lt;br /&gt;
    quiz_taken:             quiz_taken || false,&lt;br /&gt;
    # ... other fields&lt;br /&gt;
  )&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4) QuizResponseMapsController ===&lt;br /&gt;
&lt;br /&gt;
A &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; is self-referential: &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt;, both pointing to the reviewer's own &amp;lt;code&amp;gt;AssignmentParticipant&amp;lt;/code&amp;gt; record. This invariant is what distinguishes quiz maps from peer-review maps throughout the system. The endpoint is idempotent — if a map already exists for this student/questionnaire pair it is returned as-is rather than creating a duplicate, making it safe for the frontend to call on every &amp;quot;Take Quiz&amp;quot; click.&lt;br /&gt;
&lt;br /&gt;
A new controller handles quiz map creation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST /quiz_response_maps&lt;br /&gt;
def create&lt;br /&gt;
  # ... validate params, find assignment, resolve quiz questionnaire from team ...&lt;br /&gt;
  map = QuizResponseMap.find_by(&lt;br /&gt;
    reviewed_object_id: quiz_questionnaire.id,&lt;br /&gt;
    reviewer_id: reviewer_participant.id,&lt;br /&gt;
    reviewee_id: reviewer_participant.id&lt;br /&gt;
  ) || QuizResponseMap.new(...)&lt;br /&gt;
&lt;br /&gt;
  render json: {&lt;br /&gt;
    quiz_map_id:             map.id,&lt;br /&gt;
    quiz_questionnaire_id:   quiz_questionnaire.id,&lt;br /&gt;
    reviewer_participant_id: reviewer_participant.id&lt;br /&gt;
  }, status: :created&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5) ResponseMapsController — Quiz Map Filtering ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; action must return only peer-review maps; quiz maps must not appear in the reviewer table. The primary guard &amp;lt;code&amp;gt;next if map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt; is reliable regardless of accidental id coincidences between questionnaire ids and assignment ids. A secondary &amp;lt;code&amp;gt;next unless assignment&amp;lt;/code&amp;gt; check acts as belt-and-suspenders. Each returned row is augmented with &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; so the frontend can render the &amp;quot;Take Quiz&amp;quot; button without a second API call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; was updated to exclude quiz maps and include per-row quiz state for the frontend:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
result = maps.filter_map do |map|&lt;br /&gt;
  # E2619: skip quiz maps (reviewer_id == reviewee_id)&lt;br /&gt;
  next if map.reviewer_id == map.reviewee_id&lt;br /&gt;
  # belt-and-suspenders: reviewed_object_id must reference an assignment&lt;br /&gt;
  assignment = Assignment.find_by(id: map.reviewed_object_id)&lt;br /&gt;
  next unless assignment&lt;br /&gt;
&lt;br /&gt;
  team = Team.find_by(id: map.reviewee_id)&lt;br /&gt;
  quiz_questionnaire_id = team&amp;amp;.quiz_questionnaire_id&lt;br /&gt;
  quiz_taken = quiz_questionnaire_id.present? &amp;amp;&amp;amp;&lt;br /&gt;
    QuizResponseMap.where(reviewer_id: map.reviewer_id, reviewed_object_id: quiz_questionnaire_id)&lt;br /&gt;
                   .joins(&amp;quot;INNER JOIN responses ON responses.map_id = response_maps.id&amp;quot;)&lt;br /&gt;
                   .where(responses: { is_submitted: true }).exists?&lt;br /&gt;
&lt;br /&gt;
  { id: map.id, quiz_questionnaire_id:, quiz_taken:, ... }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6) Instructor &amp;quot;Create Quiz&amp;quot; Button (AssignReviewer) ===&lt;br /&gt;
&lt;br /&gt;
When clicked, the button navigates to the questionnaire editor with URL parameters (&amp;lt;code&amp;gt;type=Quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt;) that cause the editor to link the created questionnaire back to the team automatically via &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;. After saving, the editor redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This is a temporary solution. In the full Expertiza workflow, quiz creation is initiated by a submitting team member from their own task page — not by an instructor from the reviewer-assignment page. The button was placed here because the student-facing entry point was not yet implemented in this codebase and should be removed or relocated once the proper student team submission flow is in place.&lt;br /&gt;
&lt;br /&gt;
An &amp;lt;code&amp;gt;onCreateQuiz&amp;lt;/code&amp;gt; handler was added to the &amp;lt;code&amp;gt;AssignReviewer&amp;lt;/code&amp;gt; page:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const onCreateQuiz = useCallback((teamId: number) =&amp;gt; {&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/questionnaires/new?type=Quiz` +&lt;br /&gt;
    `&amp;amp;team_id=${teamId}` +&lt;br /&gt;
    `&amp;amp;return_to=${encodeURIComponent(location.pathname + location.search)}`&lt;br /&gt;
  );&lt;br /&gt;
}, [navigate, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the questionnaire is saved, &amp;lt;code&amp;gt;QuestionnaireEditor&amp;lt;/code&amp;gt; reads &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt; from the URL, calls &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;, and redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
=== 7) Student &amp;quot;Take Quiz&amp;quot; Button (AssignedReviews) ===&lt;br /&gt;
&lt;br /&gt;
The per-row quiz state (&amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;) is returned directly in the &amp;lt;code&amp;gt;GET /response_maps&amp;lt;/code&amp;gt; response so no extra API call is needed to decide which button to show. When the student clicks &amp;quot;Take Quiz&amp;quot;, the frontend first calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; (idempotent) to obtain or reuse a quiz map id, then navigates to the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page with a &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; parameter encoding the peer-review URL. After the quiz is submitted the page auto-redirects to the actual review.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component now reads per-row quiz state from the response maps API response. When &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; hasQuizQuestionnaire &amp;amp;&amp;amp; !quizCompleted&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const handleTakeQuiz = async (review: AssignedReviewRow, reviewUrl: string) =&amp;gt; {&lt;br /&gt;
  const res = await axiosClient.post('/quiz_response_maps', {&lt;br /&gt;
    assignment_id:    review.assignmentId,&lt;br /&gt;
    reviewer_user_id: currentUser.id,&lt;br /&gt;
    reviewee_team_id: review.revieweeTeamId,&lt;br /&gt;
  });&lt;br /&gt;
  const { quiz_map_id, quiz_questionnaire_id } = res.data;&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/student_teams/teammate_review?` +&lt;br /&gt;
    `map_id=${quiz_map_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_id=${quiz_questionnaire_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_type=Quiz` +&lt;br /&gt;
    `&amp;amp;redirect_after=${encodeURIComponent(reviewUrl)}`&lt;br /&gt;
  );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8) Quiz Mode in TeammateReview ===&lt;br /&gt;
&lt;br /&gt;
Reusing the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page for quiz-taking avoids duplicating the form rendering logic. Quiz mode is activated by &amp;lt;code&amp;gt;questionnaire_type=Quiz&amp;lt;/code&amp;gt; in the URL, which causes the page to fetch quiz questionnaire items, disable the &amp;quot;Save Draft&amp;quot; button (quizzes are single-submission), display the score after submission, and show a &amp;quot;Proceed to Review&amp;quot; button that navigates to the &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; URL.&lt;br /&gt;
&lt;br /&gt;
The existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page was extended with a quiz mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
if (isQuizMode) {&lt;br /&gt;
    setQuizScore(submitRes.data?.total_score ?? null);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 9) Correct Answer Fields in Questionnaire Editor ===&lt;br /&gt;
&lt;br /&gt;
When the questionnaire type is &amp;quot;Quiz&amp;quot;, each item in &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; needs a &amp;quot;Correct answer&amp;quot; input whose control type depends on the question type. A dropdown makes sense for choice questions (the options are already defined), a bounded number input for Scale, a checkbox for Checkbox, and a free-text input for TextField (scored case-insensitively). The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; value is persisted through &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; and serialised via &amp;lt;code&amp;gt;Item#as_json&amp;lt;/code&amp;gt; only for quiz items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; renders a &amp;quot;Correct answer&amp;quot; row for each quiz item. The input type varies by question type:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Question type !! Correct answer input&lt;br /&gt;
|-&lt;br /&gt;
| Text field || Free-text &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;; scored case-insensitively at submission&lt;br /&gt;
|-&lt;br /&gt;
| Multiple choice / Multiple choice checkbox || &amp;lt;code&amp;gt;&amp;lt;select&amp;gt;&amp;lt;/code&amp;gt; pre-populated from the item's alternatives&lt;br /&gt;
|-&lt;br /&gt;
| Scale || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;number&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; bounded by the item's weight range&lt;br /&gt;
|-&lt;br /&gt;
| Checkbox || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; (correct = checked)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 10) GET /questions/quiz_types Endpoint ===&lt;br /&gt;
&lt;br /&gt;
Hard-coding the list of allowed quiz item types in the frontend would create a maintenance hazard every time a new type is added to the backend. The &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; endpoint lets the frontend fetch the authoritative list at runtime. The same constant is used in the &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action to validate incoming item types, returning 422 if an unsupported type is submitted for a quiz questionnaire.&lt;br /&gt;
&lt;br /&gt;
A new endpoint returns the allowed item types for quiz questionnaires so the frontend does not hard-code the list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
QUIZ_ITEM_TYPES = %w[TextField MultipleChoiceRadio MultipleChoiceCheckbox Scale Checkbox].freeze&lt;br /&gt;
&lt;br /&gt;
# GET /questions/quiz_types&lt;br /&gt;
def quiz_types&lt;br /&gt;
  render json: QUIZ_ITEM_TYPES, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action on &amp;lt;code&amp;gt;QuestionsController&amp;lt;/code&amp;gt; also validates that quiz questionnaires only accept items of these types, returning 422 with an error message otherwise.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
Tests were added for all new and modified backend components. The full suite passes with 0 failures.&lt;br /&gt;
&lt;br /&gt;
=== Pre-Test Setup ===&lt;br /&gt;
&lt;br /&gt;
All request specs use the shared &amp;lt;code&amp;gt;create_roles_hierarchy&amp;lt;/code&amp;gt; helper and JWT-based auth:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
before(:all) { @roles = create_roles_hierarchy }&lt;br /&gt;
let(:token)         { JsonWebToken.encode({ id: instructor.id }) }&lt;br /&gt;
let(:Authorization) { &amp;quot;Bearer #{token}&amp;quot; }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: Response — Quiz Scoring ===&lt;br /&gt;
&lt;br /&gt;
11 new examples in &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; validate the quiz scoring path, covering both spaced and CamelCase question type names, correct/incorrect answers, blank answers, multi-item accumulation, and maximum score calculation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe '#aggregate_questionnaire_score (quiz)' do&lt;br /&gt;
  it 'scores a correct &amp;quot;Text field&amp;quot; answer' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'paris') # case-insensitive&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(2)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'scores an incorrect &amp;quot;Text field&amp;quot; answer as 0' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'London')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'gives 0 when the comments column is blank' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: '')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: StudentTask — Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; verify the quiz gateway fields are populated by &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and serialised by &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Request: QuizResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
6 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| Happy path — creates map and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| Idempotent — reuses existing map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;assignment_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Assignment not found (id = 999999999) || 404&lt;br /&gt;
|-&lt;br /&gt;
| Team has no quiz questionnaire || 422&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: ResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns peer-review maps, excludes quiz maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — each entry includes &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns empty array when reviewer has no maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| POST — creates ReviewResponseMap and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — idempotent, returns same map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — missing required param || 400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: Questions — Quiz Types ===&lt;br /&gt;
&lt;br /&gt;
3 new examples in &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe 'GET /questions/quiz_types' do&lt;br /&gt;
  it 'returns 200 with the allowed quiz item type strings' do&lt;br /&gt;
    get '/questions/quiz_types', headers: { 'Authorization' =&amp;gt; auth_header }&lt;br /&gt;
    types = JSON.parse(response.body)&lt;br /&gt;
    expect(types).to include('TextField', 'MultipleChoiceRadio', 'MultipleChoiceCheckbox')&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Spec file !! Examples before !! Examples after&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || 3 (failing) || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || 11 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || 19 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 6 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| '''Total new examples''' || || '''27'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Files Modified ==&lt;br /&gt;
&lt;br /&gt;
=== Backend (Ruby on Rails) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/response.rb&amp;lt;/code&amp;gt; || Added quiz-aware scoring to &amp;lt;code&amp;gt;aggregate_questionnaire_score&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maximum_score&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/student_task.rb&amp;lt;/code&amp;gt; || Added four quiz gateway fields; updated &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/Item.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;QUIZ_ITEM_TYPES&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;correct_answer_only_for_quiz&amp;lt;/code&amp;gt; validation, and conditional &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/team.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; attribute and &amp;lt;code&amp;gt;quiz_questionnaire&amp;lt;/code&amp;gt; association&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/quiz_response_map.rb&amp;lt;/code&amp;gt; || Minor documentation updates&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questions_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt;; added quiz item type validation in &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/quiz_response_maps_controller.rb&amp;lt;/code&amp;gt; || New controller: &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/response_maps_controller.rb&amp;lt;/code&amp;gt; || Updated &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; to filter quiz maps and return per-row quiz state; added &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/teams_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questionnaires_controller.rb&amp;lt;/code&amp;gt; || Exposed &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; in strong parameters&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added routes for quiz_response_maps, quiz_types, and quiz_questionnaire on teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260423000001_add_correct_answer_to_items.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column + index to &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || Fixed existing test failures; added quiz gateway field assertions&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || Added 11 new quiz scoring tests&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || Added tests for &amp;lt;code&amp;gt;quiz_types&amp;lt;/code&amp;gt; endpoint and quiz item creation/rejection&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 6 tests covering happy path, idempotency, and error cases&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 7 tests covering quiz-map exclusion, quiz state fields, and POST CRUD&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React/TypeScript) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/StudentTasks/AssignedReviews.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Take Quiz&amp;quot; button; per-row quiz state; &amp;lt;code&amp;gt;handleTakeQuiz&amp;lt;/code&amp;gt; calling &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignReviewer.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Create Quiz&amp;quot; button that navigates to the questionnaire editor pre-linked to the team&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx&amp;lt;/code&amp;gt; || Added correct-answer row for each quiz item type&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireEditor.tsx&amp;lt;/code&amp;gt; || Added team-quiz creation flow: links new questionnaire to team via &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Student Teams/TeammateReview.tsx&amp;lt;/code&amp;gt; || Added quiz mode (&amp;lt;code&amp;gt;isQuizMode&amp;lt;/code&amp;gt;), quiz score display, and &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; param support&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentEditor.tsx&amp;lt;/code&amp;gt; || Ensured &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; checkbox is wired to the form state&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentUtil.ts&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IAssignmentFormValues&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireUtils.tsx&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IItem&amp;lt;/code&amp;gt;; updated &amp;lt;code&amp;gt;transformQuestionnaireRequest&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/components/Form/FormSelect.tsx&amp;lt;/code&amp;gt; || Documentation only (JSDoc)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Github Pull Request ===&lt;br /&gt;
&lt;br /&gt;
* Frontend: https://github.com/expertiza/reimplementation-front-end/pull/179&lt;br /&gt;
* Backend: https://github.com/expertiza/reimplementation-back-end/pull/345&lt;br /&gt;
=== Video ===&lt;br /&gt;
&lt;br /&gt;
https://youtu.be/BWh_6bavtbE&lt;br /&gt;
&lt;br /&gt;
=== Mentor ===&lt;br /&gt;
&lt;br /&gt;
Ed Gehringer (efg@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
=== Team Member ===&lt;br /&gt;
&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Mekhi Parker (mrparke4@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168158</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168158"/>
		<updated>2026-04-28T14:44:09Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively&lt;br /&gt;
developed and maintained by students and faculty at NCSU. As an open-source project built&lt;br /&gt;
on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables&lt;br /&gt;
students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation of the '''E2619 Student Quizzes'''&lt;br /&gt;
project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API).&lt;br /&gt;
The project builds on the E2607 questionnaire rendering code and extends it with&lt;br /&gt;
quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses,&lt;br /&gt;
specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they&lt;br /&gt;
are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;),&lt;br /&gt;
submitting teams create quizzes based on their submissions. Reviewers must complete the&lt;br /&gt;
quizzes before reviewing to demonstrate their understanding. If a reviewer performs&lt;br /&gt;
poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
== Previous Implementation ==&lt;br /&gt;
&lt;br /&gt;
The prior codebase provided the data models, CRUD controllers, and UI components needed for assignments, questionnaires, and peer reviews. However, the quiz workflow was incomplete in several key ways:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; computed scores as &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; using only the numeric answer column, with no correctness check against a stored correct answer. There was no way to distinguish a quiz response from a regular review response.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Item&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column; there was nowhere to persist the expected answer for a quiz item.&lt;br /&gt;
* The &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; model did not carry quiz gateway fields (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;), so the frontend had no structured way to know whether a student must take a quiz before reviewing.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;. Quiz questionnaire ownership was expected to flow through the assignment's questionnaire list, which required instructor configuration and did not support per-team quizzes.&lt;br /&gt;
* The &amp;lt;code&amp;gt;ResponseMapsController&amp;lt;/code&amp;gt; had no index action that filtered out quiz maps, so the frontend could not safely list a student's review assignments without also receiving spurious self-referential quiz entries.&lt;br /&gt;
* No &amp;lt;code&amp;gt;QuizResponseMapsController&amp;lt;/code&amp;gt; existed; there was no endpoint to create a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before a student started a quiz.&lt;br /&gt;
* The questionnaire editor rendered no &amp;quot;Correct answer&amp;quot; field for any item type when the questionnaire type was set to &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
* The student task page had no &amp;quot;Take Quiz&amp;quot; button or any flow to route a student from a quiz gate to the quiz form and back to their review.&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
&lt;br /&gt;
Based on the gaps above, this project aimed to:&lt;br /&gt;
&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to quiz items so submitting teams can specify expected answers at quiz-creation time.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; to detect quiz responses by the &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; invariant and score text/choice items from the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column using case-insensitive equality.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; with four quiz gateway fields so the frontend can render the correct button (Take Quiz / Start Review) without extra API calls.&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column to &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; so each submitting team directly owns its quiz questionnaire, enabling per-team quizzes on the same assignment.&lt;br /&gt;
# Implement &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; so the frontend can create (or reuse) a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before navigating a student to the quiz form.&lt;br /&gt;
# Update &amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; to exclude quiz maps and include per-row quiz state.&lt;br /&gt;
# Add a &amp;quot;Correct answer&amp;quot; row in the questionnaire editor for every quiz item type.&lt;br /&gt;
# Implement an instructor &amp;quot;Create Quiz&amp;quot; button on the reviewer-assignment page that opens the questionnaire editor pre-linked to the team.&lt;br /&gt;
# Implement a &amp;quot;Take Quiz&amp;quot; button in the student task list that calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; and navigates to the existing review form in quiz mode.&lt;br /&gt;
# Add &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; so the frontend can fetch the allowed quiz item types dynamically.&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │&lt;br /&gt;
│       │               (quiz mode)        │                  │&lt;br /&gt;
│       │                                  └──→ [redirect]    │&lt;br /&gt;
│       └──→ [Start/Open Review] ──→ TeammateReview           │&lt;br /&gt;
│                                     (review mode)           │&lt;br /&gt;
│  QuestionnaireEditor ──→ correct_answer fields per type     │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuizResponseMapsController  (POST /quiz_response_maps)     │&lt;br /&gt;
│  ResponsesController         (create / update / submit)     │&lt;br /&gt;
│  ResponseMapsController      (index — filters quiz maps)    │&lt;br /&gt;
│  Response model              (aggregate_questionnaire_score) │&lt;br /&gt;
│  StudentTask model           (quiz gateway fields)          │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│              Assignment               │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +boolean require_quiz                 │&lt;br /&gt;
│ +int num_quiz_questions               │&lt;br /&gt;
│ +has_many questionnaires (via AQ)     │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many (via AQ)&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐     ┌─────────────────────────────┐&lt;br /&gt;
│            Questionnaire              │     │        Team  [UPDATED]      │&lt;br /&gt;
├───────────────────────────────────────┤     ├─────────────────────────────┤&lt;br /&gt;
│ +String name                          │     │ +int quiz_questionnaire_id  │◄─── NEW&lt;br /&gt;
│ +String questionnaire_type            │◄────│ +belongs_to                 │&lt;br /&gt;
│   (&amp;quot;Quiz&amp;quot; for quiz questionnaires)    │     │   quiz_questionnaire        │&lt;br /&gt;
│ +int min_question_score               │     └─────────────────────────────┘&lt;br /&gt;
│ +int max_question_score               │&lt;br /&gt;
│ +int instructor_id                    │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│            Item  [UPDATED]            │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +String txt                           │&lt;br /&gt;
│ +String question_type                 │&lt;br /&gt;
│ +int weight                           │&lt;br /&gt;
│ +String correct_answer               ◄│─── NEW&lt;br /&gt;
│ +QUIZ_ITEM_TYPES : Array             ◄│─── NEW&lt;br /&gt;
│ +is_quiz_item?() : boolean           ◄│─── NEW&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many answers&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│               Answer                  │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int item_id                          │&lt;br /&gt;
│ +int response_id                      │&lt;br /&gt;
│ +int answer                           │&lt;br /&gt;
│ +String comments  (quiz answer here)  │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│          Response  [UPDATED]          │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int map_id                           │&lt;br /&gt;
│ +boolean is_submitted                 │&lt;br /&gt;
│ +int round                            │&lt;br /&gt;
│ +aggregate_questionnaire_score()     ◄│─── quiz-aware: checks&lt;br /&gt;
│                                       │    reviewer_id == reviewee_id&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│      QuizResponseMap  [UPDATED]       │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int reviewer_id                      │&lt;br /&gt;
│ +int reviewee_id                      │&lt;br /&gt;
│   (reviewer_id == reviewee_id        ◄│─── self-referential invariant&lt;br /&gt;
│    identifies quiz maps)              │    distinguishes quiz from review&lt;br /&gt;
│ +int reviewed_object_id              ◄│─── quiz questionnaire id&lt;br /&gt;
│ +mappings_for_reviewer()              │&lt;br /&gt;
└───────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Screenshots ===&lt;br /&gt;
[[File:e2619_take_quiz_button.png|Take Quiz button in AssignedReviews]]&lt;br /&gt;
[[File:e2619_correct_answer_fields.png|Correct answer fields in Questionnaire Editor]]&lt;br /&gt;
[[File:e2619_quiz_score.png|Quiz score display after submission]]&lt;br /&gt;
&lt;br /&gt;
== Changes Implemented ==&lt;br /&gt;
&lt;br /&gt;
=== 1) Database Schema Additions ===&lt;br /&gt;
&lt;br /&gt;
Two new columns support the quiz feature. The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column on &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt; stores the expected answer for each quiz question. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items. The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; lets each submitting team directly own its quiz questionnaire — a key change that makes per-team quizzes possible and replaces a previous approach that required manual wiring at the assignment level.&lt;br /&gt;
&lt;br /&gt;
Two migrations were added to support the quiz features:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# db/migrate/20260423000001_add_correct_answer_to_items.rb&lt;br /&gt;
class AddCorrectAnswerToItems &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :items, :correct_answer, :string&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&lt;br /&gt;
class AddQuizQuestionnaireIdToTeams &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :teams, :quiz_questionnaire_id, :integer, null: true, default: nil&lt;br /&gt;
    add_index  :teams, :quiz_questionnaire_id&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column stores the expected answer for each quiz item. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; allows each submitting team to directly own its quiz questionnaire. This replaces a previous approach that required the instructor to manually wire a questionnaire to the assignment, and is the key change that makes per-team quizzes possible.&lt;br /&gt;
&lt;br /&gt;
=== 2) Quiz-Aware Scoring in Response Model ===&lt;br /&gt;
&lt;br /&gt;
Quiz responses are identified by the invariant &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; on the associated &amp;lt;code&amp;gt;ResponseMap&amp;lt;/code&amp;gt; — no STI &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column is needed. For text and choice quiz items the student's answer is stored in the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column (not the numeric &amp;lt;code&amp;gt;answer&amp;lt;/code&amp;gt; column), so scoring uses case-insensitive string equality. Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions. The final score is returned in the &amp;lt;code&amp;gt;PATCH /responses/:id/submit&amp;lt;/code&amp;gt; response body as &amp;lt;code&amp;gt;total_score&amp;lt;/code&amp;gt; and displayed to the student before the redirect.&lt;br /&gt;
&lt;br /&gt;
The core scoring logic in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; was extended to handle quiz responses:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def aggregate_questionnaire_score&lt;br /&gt;
  sum = 0&lt;br /&gt;
  is_quiz = map.reviewer_id == map.reviewee_id&lt;br /&gt;
  comment_scored_types = %w[&lt;br /&gt;
    TextField MultipleChoiceRadio MultipleChoiceCheckbox&lt;br /&gt;
    Text\ field Multiple\ choice Multiple\ choice\ checkbox&lt;br /&gt;
  ].freeze&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    if is_quiz &amp;amp;&amp;amp; comment_scored_types.include?(s.item.question_type)&lt;br /&gt;
      correct        = s.item.correct_answer.to_s.strip.downcase&lt;br /&gt;
      student_answer = s.comments.to_s.strip.downcase&lt;br /&gt;
      sum += (student_answer == correct &amp;amp;&amp;amp; correct.present? ? 1 : 0) * (s.item.weight || 1)&lt;br /&gt;
    else&lt;br /&gt;
      sum += s.answer * (s.item.weight || 1) unless s.answer.nil?&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  sum&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions.&lt;br /&gt;
&lt;br /&gt;
=== 3) StudentTask Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; so the frontend can make gatekeeping decisions without additional API calls: &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; (true only when a submitted &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; exists against the student's &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;). The frontend &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component uses these fields to gate each review row — if &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; has_quiz_questionnaire &amp;amp;&amp;amp; !quiz_taken&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown instead of the review button.&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
attr_accessor :assignment, :assignment_id, :current_stage, :participant,&lt;br /&gt;
              :stage_deadline, :topic, :permission_granted,&lt;br /&gt;
              :require_quiz, :quiz_taken, :has_quiz_questionnaire, :quiz_questionnaire_id&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; factory method was updated to compute these fields:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def self.from_participant(participant)&lt;br /&gt;
  asgn = participant.assignment&lt;br /&gt;
  return nil unless asgn&lt;br /&gt;
&lt;br /&gt;
  review_maps  = ReviewResponseMap.where(reviewer_id: participant.id)&lt;br /&gt;
  quiz_q_id    = review_maps.filter_map { |m| Team.find_by(id: m.reviewee_id)&amp;amp;.quiz_questionnaire_id }.first&lt;br /&gt;
  quiz_taken   = QuizResponseMap.where(reviewer_id: participant.id, reviewed_object_id: quiz_q_id)&lt;br /&gt;
                                .joins(:responses)&lt;br /&gt;
                                .where(responses: { is_submitted: true })&lt;br /&gt;
                                .exists?  if quiz_q_id&lt;br /&gt;
&lt;br /&gt;
  new(&lt;br /&gt;
    assignment:             asgn.name,&lt;br /&gt;
    assignment_id:          asgn.id,&lt;br /&gt;
    require_quiz:           asgn.require_quiz || false,&lt;br /&gt;
    has_quiz_questionnaire: quiz_q_id.present?,&lt;br /&gt;
    quiz_questionnaire_id:  quiz_q_id,&lt;br /&gt;
    quiz_taken:             quiz_taken || false,&lt;br /&gt;
    # ... other fields&lt;br /&gt;
  )&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4) QuizResponseMapsController ===&lt;br /&gt;
&lt;br /&gt;
A &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; is self-referential: &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt;, both pointing to the reviewer's own &amp;lt;code&amp;gt;AssignmentParticipant&amp;lt;/code&amp;gt; record. This invariant is what distinguishes quiz maps from peer-review maps throughout the system. The endpoint is idempotent — if a map already exists for this student/questionnaire pair it is returned as-is rather than creating a duplicate, making it safe for the frontend to call on every &amp;quot;Take Quiz&amp;quot; click.&lt;br /&gt;
&lt;br /&gt;
A new controller handles quiz map creation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST /quiz_response_maps&lt;br /&gt;
def create&lt;br /&gt;
  # ... validate params, find assignment, resolve quiz questionnaire from team ...&lt;br /&gt;
  map = QuizResponseMap.find_by(&lt;br /&gt;
    reviewed_object_id: quiz_questionnaire.id,&lt;br /&gt;
    reviewer_id: reviewer_participant.id,&lt;br /&gt;
    reviewee_id: reviewer_participant.id&lt;br /&gt;
  ) || QuizResponseMap.new(...)&lt;br /&gt;
&lt;br /&gt;
  render json: {&lt;br /&gt;
    quiz_map_id:             map.id,&lt;br /&gt;
    quiz_questionnaire_id:   quiz_questionnaire.id,&lt;br /&gt;
    reviewer_participant_id: reviewer_participant.id&lt;br /&gt;
  }, status: :created&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5) ResponseMapsController — Quiz Map Filtering ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; action must return only peer-review maps; quiz maps must not appear in the reviewer table. The primary guard &amp;lt;code&amp;gt;next if map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt; is reliable regardless of accidental id coincidences between questionnaire ids and assignment ids. A secondary &amp;lt;code&amp;gt;next unless assignment&amp;lt;/code&amp;gt; check acts as belt-and-suspenders. Each returned row is augmented with &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; so the frontend can render the &amp;quot;Take Quiz&amp;quot; button without a second API call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; was updated to exclude quiz maps and include per-row quiz state for the frontend:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
result = maps.filter_map do |map|&lt;br /&gt;
  # E2619: skip quiz maps (reviewer_id == reviewee_id)&lt;br /&gt;
  next if map.reviewer_id == map.reviewee_id&lt;br /&gt;
  # belt-and-suspenders: reviewed_object_id must reference an assignment&lt;br /&gt;
  assignment = Assignment.find_by(id: map.reviewed_object_id)&lt;br /&gt;
  next unless assignment&lt;br /&gt;
&lt;br /&gt;
  team = Team.find_by(id: map.reviewee_id)&lt;br /&gt;
  quiz_questionnaire_id = team&amp;amp;.quiz_questionnaire_id&lt;br /&gt;
  quiz_taken = quiz_questionnaire_id.present? &amp;amp;&amp;amp;&lt;br /&gt;
    QuizResponseMap.where(reviewer_id: map.reviewer_id, reviewed_object_id: quiz_questionnaire_id)&lt;br /&gt;
                   .joins(&amp;quot;INNER JOIN responses ON responses.map_id = response_maps.id&amp;quot;)&lt;br /&gt;
                   .where(responses: { is_submitted: true }).exists?&lt;br /&gt;
&lt;br /&gt;
  { id: map.id, quiz_questionnaire_id:, quiz_taken:, ... }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6) Instructor &amp;quot;Create Quiz&amp;quot; Button (AssignReviewer) ===&lt;br /&gt;
&lt;br /&gt;
When clicked, the button navigates to the questionnaire editor with URL parameters (&amp;lt;code&amp;gt;type=Quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt;) that cause the editor to link the created questionnaire back to the team automatically via &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;. After saving, the editor redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This is a temporary solution. In the full Expertiza workflow, quiz creation is initiated by a submitting team member from their own task page — not by an instructor from the reviewer-assignment page. The button was placed here because the student-facing entry point was not yet implemented in this codebase and should be removed or relocated once the proper student team submission flow is in place.&lt;br /&gt;
&lt;br /&gt;
An &amp;lt;code&amp;gt;onCreateQuiz&amp;lt;/code&amp;gt; handler was added to the &amp;lt;code&amp;gt;AssignReviewer&amp;lt;/code&amp;gt; page:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const onCreateQuiz = useCallback((teamId: number) =&amp;gt; {&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/questionnaires/new?type=Quiz` +&lt;br /&gt;
    `&amp;amp;team_id=${teamId}` +&lt;br /&gt;
    `&amp;amp;return_to=${encodeURIComponent(location.pathname + location.search)}`&lt;br /&gt;
  );&lt;br /&gt;
}, [navigate, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the questionnaire is saved, &amp;lt;code&amp;gt;QuestionnaireEditor&amp;lt;/code&amp;gt; reads &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt; from the URL, calls &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;, and redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
=== 7) Student &amp;quot;Take Quiz&amp;quot; Button (AssignedReviews) ===&lt;br /&gt;
&lt;br /&gt;
The per-row quiz state (&amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;) is returned directly in the &amp;lt;code&amp;gt;GET /response_maps&amp;lt;/code&amp;gt; response so no extra API call is needed to decide which button to show. When the student clicks &amp;quot;Take Quiz&amp;quot;, the frontend first calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; (idempotent) to obtain or reuse a quiz map id, then navigates to the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page with a &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; parameter encoding the peer-review URL. After the quiz is submitted the page auto-redirects to the actual review.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component now reads per-row quiz state from the response maps API response. When &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; hasQuizQuestionnaire &amp;amp;&amp;amp; !quizCompleted&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const handleTakeQuiz = async (review: AssignedReviewRow, reviewUrl: string) =&amp;gt; {&lt;br /&gt;
  const res = await axiosClient.post('/quiz_response_maps', {&lt;br /&gt;
    assignment_id:    review.assignmentId,&lt;br /&gt;
    reviewer_user_id: currentUser.id,&lt;br /&gt;
    reviewee_team_id: review.revieweeTeamId,&lt;br /&gt;
  });&lt;br /&gt;
  const { quiz_map_id, quiz_questionnaire_id } = res.data;&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/student_teams/teammate_review?` +&lt;br /&gt;
    `map_id=${quiz_map_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_id=${quiz_questionnaire_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_type=Quiz` +&lt;br /&gt;
    `&amp;amp;redirect_after=${encodeURIComponent(reviewUrl)}`&lt;br /&gt;
  );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8) Quiz Mode in TeammateReview ===&lt;br /&gt;
&lt;br /&gt;
Reusing the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page for quiz-taking avoids duplicating the form rendering logic. Quiz mode is activated by &amp;lt;code&amp;gt;questionnaire_type=Quiz&amp;lt;/code&amp;gt; in the URL, which causes the page to fetch quiz questionnaire items, disable the &amp;quot;Save Draft&amp;quot; button (quizzes are single-submission), display the score after submission, and show a &amp;quot;Proceed to Review&amp;quot; button that navigates to the &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; URL.&lt;br /&gt;
&lt;br /&gt;
The existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page was extended with a quiz mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
if (isQuizMode) {&lt;br /&gt;
    setQuizScore(submitRes.data?.total_score ?? null);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 9) Correct Answer Fields in Questionnaire Editor ===&lt;br /&gt;
&lt;br /&gt;
When the questionnaire type is &amp;quot;Quiz&amp;quot;, each item in &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; needs a &amp;quot;Correct answer&amp;quot; input whose control type depends on the question type. A dropdown makes sense for choice questions (the options are already defined), a bounded number input for Scale, a checkbox for Checkbox, and a free-text input for TextField (scored case-insensitively). The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; value is persisted through &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; and serialised via &amp;lt;code&amp;gt;Item#as_json&amp;lt;/code&amp;gt; only for quiz items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; renders a &amp;quot;Correct answer&amp;quot; row for each quiz item. The input type varies by question type:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Question type !! Correct answer input&lt;br /&gt;
|-&lt;br /&gt;
| Text field || Free-text &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;; scored case-insensitively at submission&lt;br /&gt;
|-&lt;br /&gt;
| Multiple choice / Multiple choice checkbox || &amp;lt;code&amp;gt;&amp;lt;select&amp;gt;&amp;lt;/code&amp;gt; pre-populated from the item's alternatives&lt;br /&gt;
|-&lt;br /&gt;
| Scale || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;number&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; bounded by the item's weight range&lt;br /&gt;
|-&lt;br /&gt;
| Checkbox || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; (correct = checked)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 10) GET /questions/quiz_types Endpoint ===&lt;br /&gt;
&lt;br /&gt;
Hard-coding the list of allowed quiz item types in the frontend would create a maintenance hazard every time a new type is added to the backend. The &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; endpoint lets the frontend fetch the authoritative list at runtime. The same constant is used in the &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action to validate incoming item types, returning 422 if an unsupported type is submitted for a quiz questionnaire.&lt;br /&gt;
&lt;br /&gt;
A new endpoint returns the allowed item types for quiz questionnaires so the frontend does not hard-code the list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
QUIZ_ITEM_TYPES = %w[TextField MultipleChoiceRadio MultipleChoiceCheckbox Scale Checkbox].freeze&lt;br /&gt;
&lt;br /&gt;
# GET /questions/quiz_types&lt;br /&gt;
def quiz_types&lt;br /&gt;
  render json: QUIZ_ITEM_TYPES, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action on &amp;lt;code&amp;gt;QuestionsController&amp;lt;/code&amp;gt; also validates that quiz questionnaires only accept items of these types, returning 422 with an error message otherwise.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
Tests were added for all new and modified backend components. The full suite passes with 0 failures.&lt;br /&gt;
&lt;br /&gt;
=== Pre-Test Setup ===&lt;br /&gt;
&lt;br /&gt;
All request specs use the shared &amp;lt;code&amp;gt;create_roles_hierarchy&amp;lt;/code&amp;gt; helper and JWT-based auth:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
before(:all) { @roles = create_roles_hierarchy }&lt;br /&gt;
let(:token)         { JsonWebToken.encode({ id: instructor.id }) }&lt;br /&gt;
let(:Authorization) { &amp;quot;Bearer #{token}&amp;quot; }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: Response — Quiz Scoring ===&lt;br /&gt;
&lt;br /&gt;
11 new examples in &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; validate the quiz scoring path, covering both spaced and CamelCase question type names, correct/incorrect answers, blank answers, multi-item accumulation, and maximum score calculation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe '#aggregate_questionnaire_score (quiz)' do&lt;br /&gt;
  it 'scores a correct &amp;quot;Text field&amp;quot; answer' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'paris') # case-insensitive&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(2)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'scores an incorrect &amp;quot;Text field&amp;quot; answer as 0' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'London')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'gives 0 when the comments column is blank' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: '')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: StudentTask — Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; verify the quiz gateway fields are populated by &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and serialised by &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Request: QuizResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
6 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| Happy path — creates map and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| Idempotent — reuses existing map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;assignment_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Assignment not found (id = 999999999) || 404&lt;br /&gt;
|-&lt;br /&gt;
| Team has no quiz questionnaire || 422&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: ResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns peer-review maps, excludes quiz maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — each entry includes &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns empty array when reviewer has no maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| POST — creates ReviewResponseMap and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — idempotent, returns same map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — missing required param || 400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: Questions — Quiz Types ===&lt;br /&gt;
&lt;br /&gt;
3 new examples in &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe 'GET /questions/quiz_types' do&lt;br /&gt;
  it 'returns 200 with the allowed quiz item type strings' do&lt;br /&gt;
    get '/questions/quiz_types', headers: { 'Authorization' =&amp;gt; auth_header }&lt;br /&gt;
    types = JSON.parse(response.body)&lt;br /&gt;
    expect(types).to include('TextField', 'MultipleChoiceRadio', 'MultipleChoiceCheckbox')&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Spec file !! Examples before !! Examples after&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || 3 (failing) || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || 11 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || 19 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 6 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| '''Total new examples''' || || '''27'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Files Modified ==&lt;br /&gt;
&lt;br /&gt;
=== Backend (Ruby on Rails) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/response.rb&amp;lt;/code&amp;gt; || Added quiz-aware scoring to &amp;lt;code&amp;gt;aggregate_questionnaire_score&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maximum_score&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/student_task.rb&amp;lt;/code&amp;gt; || Added four quiz gateway fields; updated &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/Item.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;QUIZ_ITEM_TYPES&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;correct_answer_only_for_quiz&amp;lt;/code&amp;gt; validation, and conditional &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/team.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; attribute and &amp;lt;code&amp;gt;quiz_questionnaire&amp;lt;/code&amp;gt; association&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/quiz_response_map.rb&amp;lt;/code&amp;gt; || Minor documentation updates&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questions_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt;; added quiz item type validation in &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/quiz_response_maps_controller.rb&amp;lt;/code&amp;gt; || New controller: &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/response_maps_controller.rb&amp;lt;/code&amp;gt; || Updated &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; to filter quiz maps and return per-row quiz state; added &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/teams_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questionnaires_controller.rb&amp;lt;/code&amp;gt; || Exposed &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; in strong parameters&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added routes for quiz_response_maps, quiz_types, and quiz_questionnaire on teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260423000001_add_correct_answer_to_items.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column + index to &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || Fixed existing test failures; added quiz gateway field assertions&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || Added 11 new quiz scoring tests&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || Added tests for &amp;lt;code&amp;gt;quiz_types&amp;lt;/code&amp;gt; endpoint and quiz item creation/rejection&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 6 tests covering happy path, idempotency, and error cases&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 7 tests covering quiz-map exclusion, quiz state fields, and POST CRUD&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React/TypeScript) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/StudentTasks/AssignedReviews.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Take Quiz&amp;quot; button; per-row quiz state; &amp;lt;code&amp;gt;handleTakeQuiz&amp;lt;/code&amp;gt; calling &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignReviewer.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Create Quiz&amp;quot; button that navigates to the questionnaire editor pre-linked to the team&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx&amp;lt;/code&amp;gt; || Added correct-answer row for each quiz item type&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireEditor.tsx&amp;lt;/code&amp;gt; || Added team-quiz creation flow: links new questionnaire to team via &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Student Teams/TeammateReview.tsx&amp;lt;/code&amp;gt; || Added quiz mode (&amp;lt;code&amp;gt;isQuizMode&amp;lt;/code&amp;gt;), quiz score display, and &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; param support&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentEditor.tsx&amp;lt;/code&amp;gt; || Ensured &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; checkbox is wired to the form state&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentUtil.ts&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IAssignmentFormValues&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireUtils.tsx&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IItem&amp;lt;/code&amp;gt;; updated &amp;lt;code&amp;gt;transformQuestionnaireRequest&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/components/Form/FormSelect.tsx&amp;lt;/code&amp;gt; || Documentation only (JSDoc)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Github Pull Request ===&lt;br /&gt;
&lt;br /&gt;
Frontend: https://github.com/expertiza/reimplementation-front-end/pull/179&lt;br /&gt;
Backend: https://github.com/expertiza/reimplementation-back-end/pull/345&lt;br /&gt;
=== Video ===&lt;br /&gt;
&lt;br /&gt;
https://youtu.be/BWh_6bavtbE&lt;br /&gt;
&lt;br /&gt;
=== Mentor ===&lt;br /&gt;
&lt;br /&gt;
Ed Gehringer (efg@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
=== Team Member ===&lt;br /&gt;
&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Mekhi Parker (mrparke4@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168133</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168133"/>
		<updated>2026-04-27T23:13:26Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively&lt;br /&gt;
developed and maintained by students and faculty at NCSU. As an open-source project built&lt;br /&gt;
on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables&lt;br /&gt;
students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation of the '''E2619 Student Quizzes'''&lt;br /&gt;
project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API).&lt;br /&gt;
The project builds on the E2607 questionnaire rendering code and extends it with&lt;br /&gt;
quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses,&lt;br /&gt;
specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they&lt;br /&gt;
are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;),&lt;br /&gt;
submitting teams create quizzes based on their submissions. Reviewers must complete the&lt;br /&gt;
quizzes before reviewing to demonstrate their understanding. If a reviewer performs&lt;br /&gt;
poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
== Previous Implementation ==&lt;br /&gt;
&lt;br /&gt;
The prior codebase provided the data models, CRUD controllers, and UI components needed for assignments, questionnaires, and peer reviews. However, the quiz workflow was incomplete in several key ways:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; computed scores as &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; using only the numeric answer column, with no correctness check against a stored correct answer. There was no way to distinguish a quiz response from a regular review response.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Item&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column; there was nowhere to persist the expected answer for a quiz item.&lt;br /&gt;
* The &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; model did not carry quiz gateway fields (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;), so the frontend had no structured way to know whether a student must take a quiz before reviewing.&lt;br /&gt;
* The &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; model had no &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;. Quiz questionnaire ownership was expected to flow through the assignment's questionnaire list, which required instructor configuration and did not support per-team quizzes.&lt;br /&gt;
* The &amp;lt;code&amp;gt;ResponseMapsController&amp;lt;/code&amp;gt; had no index action that filtered out quiz maps, so the frontend could not safely list a student's review assignments without also receiving spurious self-referential quiz entries.&lt;br /&gt;
* No &amp;lt;code&amp;gt;QuizResponseMapsController&amp;lt;/code&amp;gt; existed; there was no endpoint to create a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before a student started a quiz.&lt;br /&gt;
* The questionnaire editor rendered no &amp;quot;Correct answer&amp;quot; field for any item type when the questionnaire type was set to &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
* The student task page had no &amp;quot;Take Quiz&amp;quot; button or any flow to route a student from a quiz gate to the quiz form and back to their review.&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
&lt;br /&gt;
Based on the gaps above, this project aimed to:&lt;br /&gt;
&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to quiz items so submitting teams can specify expected answers at quiz-creation time.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; to detect quiz responses by the &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; invariant and score text/choice items from the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column using case-insensitive equality.&lt;br /&gt;
# Extend &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; with four quiz gateway fields so the frontend can render the correct button (Take Quiz / Start Review) without extra API calls.&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column to &amp;lt;code&amp;gt;Team&amp;lt;/code&amp;gt; so each submitting team directly owns its quiz questionnaire, enabling per-team quizzes on the same assignment.&lt;br /&gt;
# Implement &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; so the frontend can create (or reuse) a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; before navigating a student to the quiz form.&lt;br /&gt;
# Update &amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; to exclude quiz maps and include per-row quiz state.&lt;br /&gt;
# Add a &amp;quot;Correct answer&amp;quot; row in the questionnaire editor for every quiz item type.&lt;br /&gt;
# Implement an instructor &amp;quot;Create Quiz&amp;quot; button on the reviewer-assignment page that opens the questionnaire editor pre-linked to the team.&lt;br /&gt;
# Implement a &amp;quot;Take Quiz&amp;quot; button in the student task list that calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; and navigates to the existing review form in quiz mode.&lt;br /&gt;
# Add &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; so the frontend can fetch the allowed quiz item types dynamically.&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │&lt;br /&gt;
│       │               (quiz mode)        │                  │&lt;br /&gt;
│       │                                  └──→ [redirect]    │&lt;br /&gt;
│       └──→ [Start/Open Review] ──→ TeammateReview           │&lt;br /&gt;
│                                     (review mode)           │&lt;br /&gt;
│  QuestionnaireEditor ──→ correct_answer fields per type     │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuizResponseMapsController  (POST /quiz_response_maps)     │&lt;br /&gt;
│  ResponsesController         (create / update / submit)     │&lt;br /&gt;
│  ResponseMapsController      (index — filters quiz maps)    │&lt;br /&gt;
│  Response model              (aggregate_questionnaire_score) │&lt;br /&gt;
│  StudentTask model           (quiz gateway fields)          │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│              Assignment               │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +boolean require_quiz                 │&lt;br /&gt;
│ +int num_quiz_questions               │&lt;br /&gt;
│ +has_many questionnaires (via AQ)     │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many (via AQ)&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐     ┌─────────────────────────────┐&lt;br /&gt;
│            Questionnaire              │     │        Team  [UPDATED]      │&lt;br /&gt;
├───────────────────────────────────────┤     ├─────────────────────────────┤&lt;br /&gt;
│ +String name                          │     │ +int quiz_questionnaire_id  │◄─── NEW&lt;br /&gt;
│ +String questionnaire_type            │◄────│ +belongs_to                 │&lt;br /&gt;
│   (&amp;quot;Quiz&amp;quot; for quiz questionnaires)    │     │   quiz_questionnaire        │&lt;br /&gt;
│ +int min_question_score               │     └─────────────────────────────┘&lt;br /&gt;
│ +int max_question_score               │&lt;br /&gt;
│ +int instructor_id                    │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│            Item  [UPDATED]            │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +String txt                           │&lt;br /&gt;
│ +String question_type                 │&lt;br /&gt;
│ +int weight                           │&lt;br /&gt;
│ +String correct_answer               ◄│─── NEW&lt;br /&gt;
│ +QUIZ_ITEM_TYPES : Array             ◄│─── NEW&lt;br /&gt;
│ +is_quiz_item?() : boolean           ◄│─── NEW&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ has_many answers&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│               Answer                  │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int item_id                          │&lt;br /&gt;
│ +int response_id                      │&lt;br /&gt;
│ +int answer                           │&lt;br /&gt;
│ +String comments  (quiz answer here)  │&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│          Response  [UPDATED]          │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int map_id                           │&lt;br /&gt;
│ +boolean is_submitted                 │&lt;br /&gt;
│ +int round                            │&lt;br /&gt;
│ +aggregate_questionnaire_score()     ◄│─── quiz-aware: checks&lt;br /&gt;
│                                       │    reviewer_id == reviewee_id&lt;br /&gt;
└────────────────────┬──────────────────┘&lt;br /&gt;
                     │ belongs_to&lt;br /&gt;
                     ▼&lt;br /&gt;
┌───────────────────────────────────────┐&lt;br /&gt;
│      QuizResponseMap  [UPDATED]       │&lt;br /&gt;
├───────────────────────────────────────┤&lt;br /&gt;
│ +int reviewer_id                      │&lt;br /&gt;
│ +int reviewee_id                      │&lt;br /&gt;
│   (reviewer_id == reviewee_id        ◄│─── self-referential invariant&lt;br /&gt;
│    identifies quiz maps)              │    distinguishes quiz from review&lt;br /&gt;
│ +int reviewed_object_id              ◄│─── quiz questionnaire id&lt;br /&gt;
│ +mappings_for_reviewer()              │&lt;br /&gt;
└───────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Screenshots ===&lt;br /&gt;
[[File:e2619_take_quiz_button.png|Take Quiz button in AssignedReviews]]&lt;br /&gt;
[[File:e2619_correct_answer_fields.png|Correct answer fields in Questionnaire Editor]]&lt;br /&gt;
[[File:e2619_quiz_score.png|Quiz score display after submission]]&lt;br /&gt;
&lt;br /&gt;
== Changes Implemented ==&lt;br /&gt;
&lt;br /&gt;
=== 1) Database Schema Additions ===&lt;br /&gt;
&lt;br /&gt;
Two new columns support the quiz feature. The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column on &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt; stores the expected answer for each quiz question. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items. The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; lets each submitting team directly own its quiz questionnaire — a key change that makes per-team quizzes possible and replaces a previous approach that required manual wiring at the assignment level.&lt;br /&gt;
&lt;br /&gt;
Two migrations were added to support the quiz features:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# db/migrate/20260423000001_add_correct_answer_to_items.rb&lt;br /&gt;
class AddCorrectAnswerToItems &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :items, :correct_answer, :string&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&lt;br /&gt;
class AddQuizQuestionnaireIdToTeams &amp;lt; ActiveRecord::Migration[8.0]&lt;br /&gt;
  def change&lt;br /&gt;
    add_column :teams, :quiz_questionnaire_id, :integer, null: true, default: nil&lt;br /&gt;
    add_index  :teams, :quiz_questionnaire_id&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; column stores the expected answer for each quiz item. It is only exposed in API responses when &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt; is true, preventing accidental leakage on peer-review rubric items.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; column on &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt; allows each submitting team to directly own its quiz questionnaire. This replaces a previous approach that required the instructor to manually wire a questionnaire to the assignment, and is the key change that makes per-team quizzes possible.&lt;br /&gt;
&lt;br /&gt;
=== 2) Quiz-Aware Scoring in Response Model ===&lt;br /&gt;
&lt;br /&gt;
Quiz responses are identified by the invariant &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; on the associated &amp;lt;code&amp;gt;ResponseMap&amp;lt;/code&amp;gt; — no STI &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column is needed. For text and choice quiz items the student's answer is stored in the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column (not the numeric &amp;lt;code&amp;gt;answer&amp;lt;/code&amp;gt; column), so scoring uses case-insensitive string equality. Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions. The final score is returned in the &amp;lt;code&amp;gt;PATCH /responses/:id/submit&amp;lt;/code&amp;gt; response body as &amp;lt;code&amp;gt;total_score&amp;lt;/code&amp;gt; and displayed to the student before the redirect.&lt;br /&gt;
&lt;br /&gt;
The core scoring logic in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; was extended to handle quiz responses:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def aggregate_questionnaire_score&lt;br /&gt;
  sum = 0&lt;br /&gt;
  is_quiz = map.reviewer_id == map.reviewee_id&lt;br /&gt;
  comment_scored_types = %w[&lt;br /&gt;
    TextField MultipleChoiceRadio MultipleChoiceCheckbox&lt;br /&gt;
    Text\ field Multiple\ choice Multiple\ choice\ checkbox&lt;br /&gt;
  ].freeze&lt;br /&gt;
&lt;br /&gt;
  scores.each do |s|&lt;br /&gt;
    if is_quiz &amp;amp;&amp;amp; comment_scored_types.include?(s.item.question_type)&lt;br /&gt;
      correct        = s.item.correct_answer.to_s.strip.downcase&lt;br /&gt;
      student_answer = s.comments.to_s.strip.downcase&lt;br /&gt;
      sum += (student_answer == correct &amp;amp;&amp;amp; correct.present? ? 1 : 0) * (s.item.weight || 1)&lt;br /&gt;
    else&lt;br /&gt;
      sum += s.answer * (s.item.weight || 1) unless s.answer.nil?&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  sum&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both spaced names (&amp;lt;code&amp;gt;&amp;quot;Text field&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;Multiple choice&amp;quot;&amp;lt;/code&amp;gt;) and CamelCase names (&amp;lt;code&amp;gt;&amp;quot;TextField&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;MultipleChoiceRadio&amp;quot;&amp;lt;/code&amp;gt;) are handled because the frontend historically used both conventions.&lt;br /&gt;
&lt;br /&gt;
=== 3) StudentTask Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; so the frontend can make gatekeeping decisions without additional API calls: &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; (true only when a submitted &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; exists against the student's &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;). The frontend &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component uses these fields to gate each review row — if &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; has_quiz_questionnaire &amp;amp;&amp;amp; !quiz_taken&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown instead of the review button.&lt;br /&gt;
&lt;br /&gt;
Four fields were added to &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
attr_accessor :assignment, :assignment_id, :current_stage, :participant,&lt;br /&gt;
              :stage_deadline, :topic, :permission_granted,&lt;br /&gt;
              :require_quiz, :quiz_taken, :has_quiz_questionnaire, :quiz_questionnaire_id&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; factory method was updated to compute these fields:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def self.from_participant(participant)&lt;br /&gt;
  asgn = participant.assignment&lt;br /&gt;
  return nil unless asgn&lt;br /&gt;
&lt;br /&gt;
  review_maps  = ReviewResponseMap.where(reviewer_id: participant.id)&lt;br /&gt;
  quiz_q_id    = review_maps.filter_map { |m| Team.find_by(id: m.reviewee_id)&amp;amp;.quiz_questionnaire_id }.first&lt;br /&gt;
  quiz_taken   = QuizResponseMap.where(reviewer_id: participant.id, reviewed_object_id: quiz_q_id)&lt;br /&gt;
                                .joins(:responses)&lt;br /&gt;
                                .where(responses: { is_submitted: true })&lt;br /&gt;
                                .exists?  if quiz_q_id&lt;br /&gt;
&lt;br /&gt;
  new(&lt;br /&gt;
    assignment:             asgn.name,&lt;br /&gt;
    assignment_id:          asgn.id,&lt;br /&gt;
    require_quiz:           asgn.require_quiz || false,&lt;br /&gt;
    has_quiz_questionnaire: quiz_q_id.present?,&lt;br /&gt;
    quiz_questionnaire_id:  quiz_q_id,&lt;br /&gt;
    quiz_taken:             quiz_taken || false,&lt;br /&gt;
    # ... other fields&lt;br /&gt;
  )&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4) QuizResponseMapsController ===&lt;br /&gt;
&lt;br /&gt;
A &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; is self-referential: &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt;, both pointing to the reviewer's own &amp;lt;code&amp;gt;AssignmentParticipant&amp;lt;/code&amp;gt; record. This invariant is what distinguishes quiz maps from peer-review maps throughout the system. The endpoint is idempotent — if a map already exists for this student/questionnaire pair it is returned as-is rather than creating a duplicate, making it safe for the frontend to call on every &amp;quot;Take Quiz&amp;quot; click.&lt;br /&gt;
&lt;br /&gt;
A new controller handles quiz map creation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST /quiz_response_maps&lt;br /&gt;
def create&lt;br /&gt;
  # ... validate params, find assignment, resolve quiz questionnaire from team ...&lt;br /&gt;
  map = QuizResponseMap.find_by(&lt;br /&gt;
    reviewed_object_id: quiz_questionnaire.id,&lt;br /&gt;
    reviewer_id: reviewer_participant.id,&lt;br /&gt;
    reviewee_id: reviewer_participant.id&lt;br /&gt;
  ) || QuizResponseMap.new(...)&lt;br /&gt;
&lt;br /&gt;
  render json: {&lt;br /&gt;
    quiz_map_id:             map.id,&lt;br /&gt;
    quiz_questionnaire_id:   quiz_questionnaire.id,&lt;br /&gt;
    reviewer_participant_id: reviewer_participant.id&lt;br /&gt;
  }, status: :created&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5) ResponseMapsController — Quiz Map Filtering ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; action must return only peer-review maps; quiz maps must not appear in the reviewer table. The primary guard &amp;lt;code&amp;gt;next if map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt; is reliable regardless of accidental id coincidences between questionnaire ids and assignment ids. A secondary &amp;lt;code&amp;gt;next unless assignment&amp;lt;/code&amp;gt; check acts as belt-and-suspenders. Each returned row is augmented with &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; so the frontend can render the &amp;quot;Take Quiz&amp;quot; button without a second API call.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; was updated to exclude quiz maps and include per-row quiz state for the frontend:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
result = maps.filter_map do |map|&lt;br /&gt;
  # E2619: skip quiz maps (reviewer_id == reviewee_id)&lt;br /&gt;
  next if map.reviewer_id == map.reviewee_id&lt;br /&gt;
  # belt-and-suspenders: reviewed_object_id must reference an assignment&lt;br /&gt;
  assignment = Assignment.find_by(id: map.reviewed_object_id)&lt;br /&gt;
  next unless assignment&lt;br /&gt;
&lt;br /&gt;
  team = Team.find_by(id: map.reviewee_id)&lt;br /&gt;
  quiz_questionnaire_id = team&amp;amp;.quiz_questionnaire_id&lt;br /&gt;
  quiz_taken = quiz_questionnaire_id.present? &amp;amp;&amp;amp;&lt;br /&gt;
    QuizResponseMap.where(reviewer_id: map.reviewer_id, reviewed_object_id: quiz_questionnaire_id)&lt;br /&gt;
                   .joins(&amp;quot;INNER JOIN responses ON responses.map_id = response_maps.id&amp;quot;)&lt;br /&gt;
                   .where(responses: { is_submitted: true }).exists?&lt;br /&gt;
&lt;br /&gt;
  { id: map.id, quiz_questionnaire_id:, quiz_taken:, ... }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6) Instructor &amp;quot;Create Quiz&amp;quot; Button (AssignReviewer) ===&lt;br /&gt;
&lt;br /&gt;
When clicked, the button navigates to the questionnaire editor with URL parameters (&amp;lt;code&amp;gt;type=Quiz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt;) that cause the editor to link the created questionnaire back to the team automatically via &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;. After saving, the editor redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' This is a temporary solution. In the full Expertiza workflow, quiz creation is initiated by a submitting team member from their own task page — not by an instructor from the reviewer-assignment page. The button was placed here because the student-facing entry point was not yet implemented in this codebase and should be removed or relocated once the proper student team submission flow is in place.&lt;br /&gt;
&lt;br /&gt;
An &amp;lt;code&amp;gt;onCreateQuiz&amp;lt;/code&amp;gt; handler was added to the &amp;lt;code&amp;gt;AssignReviewer&amp;lt;/code&amp;gt; page:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const onCreateQuiz = useCallback((teamId: number) =&amp;gt; {&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/questionnaires/new?type=Quiz` +&lt;br /&gt;
    `&amp;amp;team_id=${teamId}` +&lt;br /&gt;
    `&amp;amp;return_to=${encodeURIComponent(location.pathname + location.search)}`&lt;br /&gt;
  );&lt;br /&gt;
}, [navigate, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the questionnaire is saved, &amp;lt;code&amp;gt;QuestionnaireEditor&amp;lt;/code&amp;gt; reads &amp;lt;code&amp;gt;team_id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;return_to&amp;lt;/code&amp;gt; from the URL, calls &amp;lt;code&amp;gt;PATCH /teams/:team_id/quiz_questionnaire&amp;lt;/code&amp;gt;, and redirects back to the reviewer page.&lt;br /&gt;
&lt;br /&gt;
=== 7) Student &amp;quot;Take Quiz&amp;quot; Button (AssignedReviews) ===&lt;br /&gt;
&lt;br /&gt;
The per-row quiz state (&amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt;) is returned directly in the &amp;lt;code&amp;gt;GET /response_maps&amp;lt;/code&amp;gt; response so no extra API call is needed to decide which button to show. When the student clicks &amp;quot;Take Quiz&amp;quot;, the frontend first calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; (idempotent) to obtain or reuse a quiz map id, then navigates to the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page with a &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; parameter encoding the peer-review URL. After the quiz is submitted the page auto-redirects to the actual review.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component now reads per-row quiz state from the response maps API response. When &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; hasQuizQuestionnaire &amp;amp;&amp;amp; !quizCompleted&amp;lt;/code&amp;gt;, a yellow &amp;quot;Take Quiz&amp;quot; button is shown:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
const handleTakeQuiz = async (review: AssignedReviewRow, reviewUrl: string) =&amp;gt; {&lt;br /&gt;
  const res = await axiosClient.post('/quiz_response_maps', {&lt;br /&gt;
    assignment_id:    review.assignmentId,&lt;br /&gt;
    reviewer_user_id: currentUser.id,&lt;br /&gt;
    reviewee_team_id: review.revieweeTeamId,&lt;br /&gt;
  });&lt;br /&gt;
  const { quiz_map_id, quiz_questionnaire_id } = res.data;&lt;br /&gt;
  navigate(&lt;br /&gt;
    `/student_teams/teammate_review?` +&lt;br /&gt;
    `map_id=${quiz_map_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_id=${quiz_questionnaire_id}` +&lt;br /&gt;
    `&amp;amp;questionnaire_type=Quiz` +&lt;br /&gt;
    `&amp;amp;redirect_after=${encodeURIComponent(reviewUrl)}`&lt;br /&gt;
  );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8) Quiz Mode in TeammateReview ===&lt;br /&gt;
&lt;br /&gt;
Reusing the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page for quiz-taking avoids duplicating the form rendering logic. Quiz mode is activated by &amp;lt;code&amp;gt;questionnaire_type=Quiz&amp;lt;/code&amp;gt; in the URL, which causes the page to fetch quiz questionnaire items, disable the &amp;quot;Save Draft&amp;quot; button (quizzes are single-submission), display the score after submission, and show a &amp;quot;Proceed to Review&amp;quot; button that navigates to the &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; URL.&lt;br /&gt;
&lt;br /&gt;
The existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page was extended with a quiz mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
if (isQuizMode) {&lt;br /&gt;
    setQuizScore(submitRes.data?.total_score ?? null);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 9) Correct Answer Fields in Questionnaire Editor ===&lt;br /&gt;
&lt;br /&gt;
When the questionnaire type is &amp;quot;Quiz&amp;quot;, each item in &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; needs a &amp;quot;Correct answer&amp;quot; input whose control type depends on the question type. A dropdown makes sense for choice questions (the options are already defined), a bounded number input for Scale, a checkbox for Checkbox, and a free-text input for TextField (scored case-insensitively). The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; value is persisted through &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; and serialised via &amp;lt;code&amp;gt;Item#as_json&amp;lt;/code&amp;gt; only for quiz items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; renders a &amp;quot;Correct answer&amp;quot; row for each quiz item. The input type varies by question type:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Question type !! Correct answer input&lt;br /&gt;
|-&lt;br /&gt;
| Text field || Free-text &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot;&amp;gt;&amp;lt;/code&amp;gt;; scored case-insensitively at submission&lt;br /&gt;
|-&lt;br /&gt;
| Multiple choice / Multiple choice checkbox || &amp;lt;code&amp;gt;&amp;lt;select&amp;gt;&amp;lt;/code&amp;gt; pre-populated from the item's alternatives&lt;br /&gt;
|-&lt;br /&gt;
| Scale || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;number&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; bounded by the item's weight range&lt;br /&gt;
|-&lt;br /&gt;
| Checkbox || &amp;lt;code&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;/code&amp;gt; (correct = checked)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 10) GET /questions/quiz_types Endpoint ===&lt;br /&gt;
&lt;br /&gt;
Hard-coding the list of allowed quiz item types in the frontend would create a maintenance hazard every time a new type is added to the backend. The &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt; endpoint lets the frontend fetch the authoritative list at runtime. The same constant is used in the &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action to validate incoming item types, returning 422 if an unsupported type is submitted for a quiz questionnaire.&lt;br /&gt;
&lt;br /&gt;
A new endpoint returns the allowed item types for quiz questionnaires so the frontend does not hard-code the list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
QUIZ_ITEM_TYPES = %w[TextField MultipleChoiceRadio MultipleChoiceCheckbox Scale Checkbox].freeze&lt;br /&gt;
&lt;br /&gt;
# GET /questions/quiz_types&lt;br /&gt;
def quiz_types&lt;br /&gt;
  render json: QUIZ_ITEM_TYPES, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; action on &amp;lt;code&amp;gt;QuestionsController&amp;lt;/code&amp;gt; also validates that quiz questionnaires only accept items of these types, returning 422 with an error message otherwise.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
Tests were added for all new and modified backend components. The full suite passes with 0 failures.&lt;br /&gt;
&lt;br /&gt;
=== Pre-Test Setup ===&lt;br /&gt;
&lt;br /&gt;
All request specs use the shared &amp;lt;code&amp;gt;create_roles_hierarchy&amp;lt;/code&amp;gt; helper and JWT-based auth:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
before(:all) { @roles = create_roles_hierarchy }&lt;br /&gt;
let(:token)         { JsonWebToken.encode({ id: instructor.id }) }&lt;br /&gt;
let(:Authorization) { &amp;quot;Bearer #{token}&amp;quot; }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: Response — Quiz Scoring ===&lt;br /&gt;
&lt;br /&gt;
11 new examples in &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; validate the quiz scoring path, covering both spaced and CamelCase question type names, correct/incorrect answers, blank answers, multi-item accumulation, and maximum score calculation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe '#aggregate_questionnaire_score (quiz)' do&lt;br /&gt;
  it 'scores a correct &amp;quot;Text field&amp;quot; answer' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'paris') # case-insensitive&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(2)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'scores an incorrect &amp;quot;Text field&amp;quot; answer as 0' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: 'London')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  it 'gives 0 when the comments column is blank' do&lt;br /&gt;
    item   = make_quiz_item(question_type: 'Text field', correct_answer: 'Paris', weight: 2)&lt;br /&gt;
    answer = make_quiz_answer(item: item, comments: '')&lt;br /&gt;
    r      = quiz_response([answer])&lt;br /&gt;
    expect(r.aggregate_questionnaire_score).to eq(0)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: StudentTask — Quiz Gateway Fields ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; verify the quiz gateway fields are populated by &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and serialised by &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Request: QuizResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
6 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| Happy path — creates map and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| Idempotent — reuses existing map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;assignment_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| Assignment not found (id = 999999999) || 404&lt;br /&gt;
|-&lt;br /&gt;
| Team has no quiz questionnaire || 422&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: ResponseMaps ===&lt;br /&gt;
&lt;br /&gt;
7 examples in &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Test !! Expected status&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns peer-review maps, excludes quiz maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — each entry includes &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — returns empty array when reviewer has no maps || 200&lt;br /&gt;
|-&lt;br /&gt;
| GET — missing &amp;lt;code&amp;gt;reviewer_user_id&amp;lt;/code&amp;gt; || 400&lt;br /&gt;
|-&lt;br /&gt;
| POST — creates ReviewResponseMap and returns IDs || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — idempotent, returns same map on repeat call || 201&lt;br /&gt;
|-&lt;br /&gt;
| POST — missing required param || 400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Request: Questions — Quiz Types ===&lt;br /&gt;
&lt;br /&gt;
3 new examples in &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
describe 'GET /questions/quiz_types' do&lt;br /&gt;
  it 'returns 200 with the allowed quiz item type strings' do&lt;br /&gt;
    get '/questions/quiz_types', headers: { 'Authorization' =&amp;gt; auth_header }&lt;br /&gt;
    types = JSON.parse(response.body)&lt;br /&gt;
    expect(types).to include('TextField', 'MultipleChoiceRadio', 'MultipleChoiceCheckbox')&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Spec file !! Examples before !! Examples after&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || 3 (failing) || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || 11 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || 19 || 22 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 6 ✅&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || 0 || 7 ✅&lt;br /&gt;
|-&lt;br /&gt;
| '''Total new examples''' || || '''27'''&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Files Modified ==&lt;br /&gt;
&lt;br /&gt;
=== Backend (Ruby on Rails) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/response.rb&amp;lt;/code&amp;gt; || Added quiz-aware scoring to &amp;lt;code&amp;gt;aggregate_questionnaire_score&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maximum_score&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/student_task.rb&amp;lt;/code&amp;gt; || Added four quiz gateway fields; updated &amp;lt;code&amp;gt;from_participant&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/Item.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;QUIZ_ITEM_TYPES&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;is_quiz_item?&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;correct_answer_only_for_quiz&amp;lt;/code&amp;gt; validation, and conditional &amp;lt;code&amp;gt;as_json&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/team.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; attribute and &amp;lt;code&amp;gt;quiz_questionnaire&amp;lt;/code&amp;gt; association&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/quiz_response_map.rb&amp;lt;/code&amp;gt; || Minor documentation updates&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questions_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;GET /questions/quiz_types&amp;lt;/code&amp;gt;; added quiz item type validation in &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/quiz_response_maps_controller.rb&amp;lt;/code&amp;gt; || New controller: &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/response_maps_controller.rb&amp;lt;/code&amp;gt; || Updated &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; to filter quiz maps and return per-row quiz state; added &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/teams_controller.rb&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/questionnaires_controller.rb&amp;lt;/code&amp;gt; || Exposed &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; in strong parameters&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added routes for quiz_response_maps, quiz_types, and quiz_questionnaire on teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260423000001_add_correct_answer_to_items.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column to &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;db/migrate/20260424000001_add_quiz_questionnaire_id_to_teams.rb&amp;lt;/code&amp;gt; || New migration: adds &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; integer column + index to &amp;lt;code&amp;gt;teams&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/student_task_spec.rb&amp;lt;/code&amp;gt; || Fixed existing test failures; added quiz gateway field assertions&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/models/response_spec.rb&amp;lt;/code&amp;gt; || Added 11 new quiz scoring tests&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/questions_spec.rb&amp;lt;/code&amp;gt; || Added tests for &amp;lt;code&amp;gt;quiz_types&amp;lt;/code&amp;gt; endpoint and quiz item creation/rejection&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/quiz_response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 6 tests covering happy path, idempotency, and error cases&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/response_maps_controller_spec.rb&amp;lt;/code&amp;gt; || New spec: 7 tests covering quiz-map exclusion, quiz state fields, and POST CRUD&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React/TypeScript) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! Change&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/StudentTasks/AssignedReviews.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Take Quiz&amp;quot; button; per-row quiz state; &amp;lt;code&amp;gt;handleTakeQuiz&amp;lt;/code&amp;gt; calling &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignReviewer.tsx&amp;lt;/code&amp;gt; || Added &amp;quot;Create Quiz&amp;quot; button that navigates to the questionnaire editor pre-linked to the team&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx&amp;lt;/code&amp;gt; || Added correct-answer row for each quiz item type&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireEditor.tsx&amp;lt;/code&amp;gt; || Added team-quiz creation flow: links new questionnaire to team via &amp;lt;code&amp;gt;PATCH /teams/:id/quiz_questionnaire&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Student Teams/TeammateReview.tsx&amp;lt;/code&amp;gt; || Added quiz mode (&amp;lt;code&amp;gt;isQuizMode&amp;lt;/code&amp;gt;), quiz score display, and &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; param support&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentEditor.tsx&amp;lt;/code&amp;gt; || Ensured &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; checkbox is wired to the form state&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Assignments/AssignmentUtil.ts&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IAssignmentFormValues&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/pages/Questionnaires/QuestionnaireUtils.tsx&amp;lt;/code&amp;gt; || Added &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;IItem&amp;lt;/code&amp;gt;; updated &amp;lt;code&amp;gt;transformQuestionnaireRequest&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;src/components/Form/FormSelect.tsx&amp;lt;/code&amp;gt; || Documentation only (JSDoc)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Github Pull Request ===&lt;br /&gt;
&lt;br /&gt;
https://github.com&lt;br /&gt;
&lt;br /&gt;
=== Video ===&lt;br /&gt;
&lt;br /&gt;
https://youtu.be/BWh_6bavtbE&lt;br /&gt;
&lt;br /&gt;
=== Mentor ===&lt;br /&gt;
&lt;br /&gt;
Ed Gehringer (efg@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
=== Team Member ===&lt;br /&gt;
&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Mekhi Parker (mrparke4@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168060</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168060"/>
		<updated>2026-04-24T14:51:28Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively&lt;br /&gt;
developed and maintained by students and faculty at NCSU. As an open-source project built&lt;br /&gt;
on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables&lt;br /&gt;
students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation of the '''E2619 Student Quizzes'''&lt;br /&gt;
project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API).&lt;br /&gt;
The project builds on the E2607 questionnaire rendering code and extends it with&lt;br /&gt;
quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses,&lt;br /&gt;
specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they&lt;br /&gt;
are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;),&lt;br /&gt;
submitting teams create quizzes based on their submissions. Reviewers must complete the&lt;br /&gt;
quizzes before reviewing to demonstrate their understanding. If a reviewer performs&lt;br /&gt;
poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
=== Prior State of the Codebase ===&lt;br /&gt;
&lt;br /&gt;
The existing implementation provided:&lt;br /&gt;
* '''Backend''': Models for &amp;lt;code&amp;gt;QuizQuestionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizItem&amp;lt;/code&amp;gt;,&lt;br /&gt;
  &amp;lt;code&amp;gt;QuizQuestionChoice&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt;,&lt;br /&gt;
  and &amp;lt;code&amp;gt;Answer&amp;lt;/code&amp;gt;. Basic CRUD for questionnaires and questions existed.&lt;br /&gt;
  &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; calculated scores using&lt;br /&gt;
  &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; only, with no correctness check.&lt;br /&gt;
* '''Frontend''': Questionnaire editor (create/edit/delete), student task list, review&lt;br /&gt;
  tableau display, and assignment configuration. The questionnaire editor supported the&lt;br /&gt;
  &amp;quot;Quiz&amp;quot; type but had no UI to specify correct answers for quiz items.&lt;br /&gt;
&lt;br /&gt;
=== Gaps Addressed ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Area !! Gap !! Status&lt;br /&gt;
|-&lt;br /&gt;
| '''Student Task View''' || No mechanism to direct a student to a quiz, a review, or both based on &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; || ✅ Implemented&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Scoring''' || &amp;lt;code&amp;gt;aggregate_questionnaire_score&amp;lt;/code&amp;gt; did not check correctness || ✅ Implemented&lt;br /&gt;
|-&lt;br /&gt;
| '''Correct Answer Specification''' || No UI to mark correct answers for quiz items || ✅ Implemented&lt;br /&gt;
|-&lt;br /&gt;
| '''Fill-in-the-Blank (TextField)''' || No correct answer support for text questions || ✅ Implemented (single answer, case-insensitive match)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Taking Interface''' || No student-facing page to take a quiz || ✅ Implemented&lt;br /&gt;
  (reuses existing review form in quiz mode)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Response Map Creation''' || No endpoint to create a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; for a student before taking a quiz || ✅ Implemented via&lt;br /&gt;
  &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  AssignedReviews ──→ [Take Quiz] ──→ TeammateReview         │&lt;br /&gt;
│       │               (quiz mode)        │                  │&lt;br /&gt;
│       │                                  └──→ [redirect]    │&lt;br /&gt;
│       └──→ [Start/Open Review] ──→ TeammateReview           │&lt;br /&gt;
│                                     (review mode)           │&lt;br /&gt;
│  QuestionnaireEditor ──→ correct_answer fields per type     │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuizResponseMapsController  (POST /quiz_response_maps)     │&lt;br /&gt;
│  ResponsesController         (create / update / submit)     │&lt;br /&gt;
│  ResponseMapsController      (index — filters quiz maps)    │&lt;br /&gt;
│  Response model              (aggregate_questionnaire_score) │&lt;br /&gt;
│  StudentTask model           (quiz gateway fields)          │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implemented Features ===&lt;br /&gt;
&lt;br /&gt;
==== 1) Student Task View — Quiz/Review Gateway ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;StudentTask&amp;lt;/code&amp;gt; model was extended with four fields computed per participant:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt; — whether the assignment requires a quiz&lt;br /&gt;
* &amp;lt;code&amp;gt;has_quiz_questionnaire&amp;lt;/code&amp;gt; — whether a Quiz-type questionnaire is assigned&lt;br /&gt;
* &amp;lt;code&amp;gt;quiz_questionnaire_id&amp;lt;/code&amp;gt; — the id of that questionnaire&lt;br /&gt;
* &amp;lt;code&amp;gt;quiz_taken&amp;lt;/code&amp;gt; — true only when a submitted &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; exists&lt;br /&gt;
  against the student's &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The frontend &amp;lt;code&amp;gt;AssignedReviews&amp;lt;/code&amp;gt; component uses these fields to gate each review&lt;br /&gt;
row. If &amp;lt;code&amp;gt;require_quiz &amp;amp;&amp;amp; has_quiz_questionnaire &amp;amp;&amp;amp; !quiz_taken&amp;lt;/code&amp;gt;, a yellow&lt;br /&gt;
&amp;quot;Take Quiz&amp;quot; button is shown instead of the review button.&lt;br /&gt;
&lt;br /&gt;
==== 2) Quiz Gateway Flow ====&lt;br /&gt;
&lt;br /&gt;
When a student clicks &amp;quot;Take Quiz&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
# The frontend calls &amp;lt;code&amp;gt;POST /quiz_response_maps&amp;lt;/code&amp;gt; with the assignment id and&lt;br /&gt;
  user id.&lt;br /&gt;
# The backend finds (or creates) a &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt; where&lt;br /&gt;
  &amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt; (self-referential — the student quizzes&lt;br /&gt;
  themselves) and &amp;lt;code&amp;gt;reviewed_object_id&amp;lt;/code&amp;gt; is the quiz questionnaire id.&lt;br /&gt;
# The frontend navigates to the existing &amp;lt;code&amp;gt;TeammateReview&amp;lt;/code&amp;gt; page with&lt;br /&gt;
  &amp;lt;code&amp;gt;questionnaire_type=Quiz&amp;lt;/code&amp;gt; and a &amp;lt;code&amp;gt;redirect_after&amp;lt;/code&amp;gt; parameter&lt;br /&gt;
  encoding the review URL.&lt;br /&gt;
# After the student submits the quiz, the page shows the score and automatically&lt;br /&gt;
  redirects to the actual review after 1.2 seconds.&lt;br /&gt;
&lt;br /&gt;
Quiz maps are distinguished from review maps by the invariant&lt;br /&gt;
&amp;lt;code&amp;gt;reviewer_id == reviewee_id&amp;lt;/code&amp;gt;. No STI &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; column is needed.&lt;br /&gt;
&lt;br /&gt;
==== 3) Quiz Scoring ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; now detects quiz responses by checking&lt;br /&gt;
&amp;lt;code&amp;gt;map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For quiz item types whose student answer is stored in the &amp;lt;code&amp;gt;comments&amp;lt;/code&amp;gt; column&lt;br /&gt;
(because the numeric &amp;lt;code&amp;gt;answer&amp;lt;/code&amp;gt; column is unused for text/choice items):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! question_type !! Student answer location !! Scoring method&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TextField&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;answers.comments&amp;lt;/code&amp;gt; || Case-insensitive exact match&lt;br /&gt;
  vs &amp;lt;code&amp;gt;item.correct_answer&amp;lt;/code&amp;gt; → 1 or 0 × weight&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;MultipleChoiceRadio&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;answers.comments&amp;lt;/code&amp;gt; || Case-insensitive&lt;br /&gt;
  exact match vs &amp;lt;code&amp;gt;item.correct_answer&amp;lt;/code&amp;gt; → 1 or 0 × weight&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;MultipleChoiceCheckbox&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;answers.comments&amp;lt;/code&amp;gt; || Case-insensitive&lt;br /&gt;
  exact match vs &amp;lt;code&amp;gt;item.correct_answer&amp;lt;/code&amp;gt; → 1 or 0 × weight&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Checkbox&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;Scale&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;answers.answer&amp;lt;/code&amp;gt; (integer) ||&lt;br /&gt;
  &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; (unchanged)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The final score is returned in the &amp;lt;code&amp;gt;PATCH /responses/:id/submit&amp;lt;/code&amp;gt; response body&lt;br /&gt;
as &amp;lt;code&amp;gt;total_score&amp;lt;/code&amp;gt; and displayed to the student before the redirect.&lt;br /&gt;
&lt;br /&gt;
==== 4) Correct Answer Specification (Frontend) ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; component renders a &amp;quot;Correct answer&amp;quot; row for&lt;br /&gt;
every item when the questionnaire type is &amp;quot;Quiz&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
* '''Checkbox''' — a checkbox (is correct / is not correct)&lt;br /&gt;
* '''Scale''' — a numeric input bounded to the item's weight range&lt;br /&gt;
* '''Multiple choice / Multiple choice checkbox''' — a dropdown pre-populated from the&lt;br /&gt;
  item's alternatives&lt;br /&gt;
* '''Text field''' — a free-text input (case-insensitive match at scoring time)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;correct_answer&amp;lt;/code&amp;gt; string column was added to the &amp;lt;code&amp;gt;items&amp;lt;/code&amp;gt; table&lt;br /&gt;
and is persisted through &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; and exposed in&lt;br /&gt;
&amp;lt;code&amp;gt;Item#as_json&amp;lt;/code&amp;gt; only for quiz items.&lt;br /&gt;
&lt;br /&gt;
==== 5) Review Map Filtering ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResponseMapsController#index&amp;lt;/code&amp;gt; was updated to skip quiz maps using a two-layer&lt;br /&gt;
guard:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;next if map.reviewer_id == map.reviewee_id&amp;lt;/code&amp;gt; — reliably excludes quiz maps&lt;br /&gt;
   regardless of id coincidences between questionnaire ids and assignment ids.&lt;br /&gt;
# &amp;lt;code&amp;gt;next unless assignment&amp;lt;/code&amp;gt; — belt-and-suspenders fallback.&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
[[File:E2619uml.png]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:2607ss_2.png&amp;diff=168013</id>
		<title>File:2607ss 2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:2607ss_2.png&amp;diff=168013"/>
		<updated>2026-04-20T17:36:04Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:2607ss1.png&amp;diff=168012</id>
		<title>File:2607ss1.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:2607ss1.png&amp;diff=168012"/>
		<updated>2026-04-20T17:35:30Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167820</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167820"/>
		<updated>2026-04-12T18:54:57Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively developed and maintained by students and faculty at NCSU. As an open-source project built on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation plan for the '''E2619 Student Quizzes''' project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API). The project builds on the E2607 questionnaire rendering code and extends it with quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses, specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;), submitting teams create quizzes based on their submissions. Reviewers must complete the quizzes before reviewing to demonstrate their understanding. If a reviewer performs poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
Quizzes can also be used to test what students have learned from assigned readings on book chapters written for Expertiza assignments. In this case, the quiz-taker does not necessarily need to also review the submission.&lt;br /&gt;
&lt;br /&gt;
=== Current State of the Codebase ===&lt;br /&gt;
&lt;br /&gt;
The existing implementation provides:&lt;br /&gt;
* '''Backend''': Models for &amp;lt;code&amp;gt;QuizQuestionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizItem&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizQuestionChoice&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Answer&amp;lt;/code&amp;gt;. Basic CRUD for questionnaires and questions exists. The &amp;lt;code&amp;gt;ScorableHelper&amp;lt;/code&amp;gt; module calculates scores using &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt;.&lt;br /&gt;
* '''Frontend''': Questionnaire editor (create/edit/delete), student task list, review tableau display, and assignment configuration. The questionnaire editor supports the &amp;quot;Quiz&amp;quot; type but does '''not''' support specifying correct answers for quiz items.&lt;br /&gt;
&lt;br /&gt;
=== What Is Missing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Area !! Gap&lt;br /&gt;
|-&lt;br /&gt;
| '''Student Task View''' || No mechanism to direct a student to a quiz, a review, or both based on the assignment configuration (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Scoring''' || &amp;lt;code&amp;gt;Response.aggregate_questionnaire_score&amp;lt;/code&amp;gt; performs &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; but does not check whether the answer is ''correct'' against &amp;lt;code&amp;gt;QuizQuestionChoice.iscorrect&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| '''Correct Answer Specification''' || The frontend &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; / questionnaire editor has no UI to mark correct answers for quiz item types (Multiple Choice, Checkbox, TextField)&lt;br /&gt;
|-&lt;br /&gt;
| '''Fill-in-the-Blank (TextField)''' || No support for multiple correct answers on a TextField quiz question&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Taking Interface''' || No student-facing page to take a quiz (render questions, collect answers, submit)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Assignment''' || The &amp;lt;code&amp;gt;assign_quiz&amp;lt;/code&amp;gt; route exists in &amp;lt;code&amp;gt;routes.rb&amp;lt;/code&amp;gt; but the controller method is not implemented&lt;br /&gt;
|-&lt;br /&gt;
| '''Backend Endpoints''' || Missing endpoints: submit quiz answers, fetch quiz for student, get quiz score&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  StudentTasks ──→ QuizGateway ──→ QuizTaker ──→ QuizResult  │&lt;br /&gt;
│       │                              │                      │&lt;br /&gt;
│       └──→ ReviewForm                └──→ API Services      │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuestionnaireEditor ──→ QuizCorrectAnswerEditor            │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  StudentQuizzesController  ←── QuizScoringService           │&lt;br /&gt;
│  QuestionnairesController  ←── QuizQuestionChoice model     │&lt;br /&gt;
│  StudentTasksController    ←── StudentTask model            │&lt;br /&gt;
│  Response model            ←── ScorableHelper (enhanced)    │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Key Features ===&lt;br /&gt;
&lt;br /&gt;
==== 1) Student Task View — Quiz/Review Gateway ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' Students currently see their assigned tasks but have no way to be directed to a quiz, a review, or both.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Enhance &amp;lt;code&amp;gt;StudentTasksController&amp;lt;/code&amp;gt; and the frontend &amp;lt;code&amp;gt;StudentTasks&amp;lt;/code&amp;gt; page to determine what action a student needs to perform based on the assignment configuration:&lt;br /&gt;
&lt;br /&gt;
* If &amp;lt;code&amp;gt;assignment.require_quiz == true&amp;lt;/code&amp;gt; and the student has not yet taken the quiz for a submission → show &amp;quot;Take Quiz&amp;quot; action&lt;br /&gt;
* If the student has taken the quiz (or quiz is not required) → show &amp;quot;Write Review&amp;quot; action&lt;br /&gt;
* If both are required and the quiz is completed → show &amp;quot;Write Review&amp;quot; action&lt;br /&gt;
&lt;br /&gt;
==== 2) Quiz Scoring Logic ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' The current scoring in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; multiplies &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; for all items. For quiz questionnaires, it should check whether the student's answer is ''correct'' (1 point) or ''incorrect'' (0 points).&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Enhance the &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; model's scoring to detect &amp;lt;code&amp;gt;QuizQuestionnaire&amp;lt;/code&amp;gt; type and score differently.&lt;br /&gt;
&lt;br /&gt;
==== 3) Specifying Correct Answers in the Questionnaire Editor (Frontend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' When creating a Quiz questionnaire, the instructor needs a way to mark which answers are correct. For TextField items (fill-in-the-blank), the instructor needs to specify multiple acceptable correct answers.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Extend the &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; component to render answer-choice editors when the questionnaire type is &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== 4) Student Quiz-Taking Controller (Backend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' There is no endpoint for students to fetch a quiz, submit answers, or view their score.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Create a new &amp;lt;code&amp;gt;StudentQuizzesController&amp;lt;/code&amp;gt; with the following actions:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GET    /student_quizzes                          → index (all quizzes assigned to current user)&lt;br /&gt;
GET    /student_quizzes/:id                      → show (fetch quiz with questions for taking)&lt;br /&gt;
POST   /student_quizzes/assign                   → assign_quiz (instructor assigns quiz to student)&lt;br /&gt;
POST   /student_quizzes/submit_answers           → submit_quiz (student submits quiz answers)&lt;br /&gt;
GET    /student_quizzes/:id/score                → view_score (student views their quiz score)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5) Quiz-Taking Interface (Frontend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' There is no student-facing page where a student can take a quiz.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Create a &amp;lt;code&amp;gt;QuizTaker&amp;lt;/code&amp;gt; page component.&lt;br /&gt;
&lt;br /&gt;
'''QuizTaker Flow:'''&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. Student clicks &amp;quot;Take Quiz&amp;quot; from StudentTasks&lt;br /&gt;
2. Frontend fetches GET /student_quizzes/:id (quiz items without correct answers)&lt;br /&gt;
3. Student fills in answers for each question&lt;br /&gt;
4. Student clicks &amp;quot;Submit&amp;quot;&lt;br /&gt;
5. Frontend sends POST /student_quizzes/submit_answers&lt;br /&gt;
6. Backend scores answers via QuizScoringService&lt;br /&gt;
7. Frontend receives score and displays QuizResult&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== UML Design ===&lt;br /&gt;
[[File:E2619uml.png]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:E2619uml.png&amp;diff=167819</id>
		<title>File:E2619uml.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:E2619uml.png&amp;diff=167819"/>
		<updated>2026-04-12T18:53:56Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167816</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167816"/>
		<updated>2026-04-12T18:42:42Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2619: Student Quizzes — Design Document =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is an educational web application collaboratively developed and maintained by students and faculty at NCSU. As an open-source project built on the Ruby on Rails platform, its code is accessible on GitHub. The platform enables students to provide peer reviews and refine their work based on feedback.&lt;br /&gt;
&lt;br /&gt;
This design document describes the implementation plan for the '''E2619 Student Quizzes''' project, covering both the frontend (React/TypeScript) and backend (Ruby on Rails API). The project builds on the E2607 questionnaire rendering code and extends it with quiz-specific capabilities: directing students to quizzes/reviews, scoring quiz responses, specifying correct answers, and handling fill-in-the-blank questions.&lt;br /&gt;
&lt;br /&gt;
== Project Overview ==&lt;br /&gt;
&lt;br /&gt;
Quizzes in Expertiza are designed to ensure that reviewers comprehend the material they are evaluating. When an assignment includes quizzes (&amp;lt;code&amp;gt;require_quiz = true&amp;lt;/code&amp;gt;), submitting teams create quizzes based on their submissions. Reviewers must complete the quizzes before reviewing to demonstrate their understanding. If a reviewer performs poorly, their review can be discounted to maintain quality.&lt;br /&gt;
&lt;br /&gt;
Quizzes can also be used to test what students have learned from assigned readings on book chapters written for Expertiza assignments. In this case, the quiz-taker does not necessarily need to also review the submission.&lt;br /&gt;
&lt;br /&gt;
=== Current State of the Codebase ===&lt;br /&gt;
&lt;br /&gt;
The existing implementation provides:&lt;br /&gt;
* '''Backend''': Models for &amp;lt;code&amp;gt;QuizQuestionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizItem&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizQuestionChoice&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuizResponseMap&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Answer&amp;lt;/code&amp;gt;. Basic CRUD for questionnaires and questions exists. The &amp;lt;code&amp;gt;ScorableHelper&amp;lt;/code&amp;gt; module calculates scores using &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt;.&lt;br /&gt;
* '''Frontend''': Questionnaire editor (create/edit/delete), student task list, review tableau display, and assignment configuration. The questionnaire editor supports the &amp;quot;Quiz&amp;quot; type but does '''not''' support specifying correct answers for quiz items.&lt;br /&gt;
&lt;br /&gt;
=== What Is Missing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Area !! Gap&lt;br /&gt;
|-&lt;br /&gt;
| '''Student Task View''' || No mechanism to direct a student to a quiz, a review, or both based on the assignment configuration (&amp;lt;code&amp;gt;require_quiz&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Scoring''' || &amp;lt;code&amp;gt;Response.aggregate_questionnaire_score&amp;lt;/code&amp;gt; performs &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; but does not check whether the answer is ''correct'' against &amp;lt;code&amp;gt;QuizQuestionChoice.iscorrect&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| '''Correct Answer Specification''' || The frontend &amp;lt;code&amp;gt;QuestionnairesController&amp;lt;/code&amp;gt; / questionnaire editor has no UI to mark correct answers for quiz item types (Multiple Choice, Checkbox, TextField)&lt;br /&gt;
|-&lt;br /&gt;
| '''Fill-in-the-Blank (TextField)''' || No support for multiple correct answers on a TextField quiz question&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Taking Interface''' || No student-facing page to take a quiz (render questions, collect answers, submit)&lt;br /&gt;
|-&lt;br /&gt;
| '''Quiz Assignment''' || The &amp;lt;code&amp;gt;assign_quiz&amp;lt;/code&amp;gt; route exists in &amp;lt;code&amp;gt;routes.rb&amp;lt;/code&amp;gt; but the controller method is not implemented&lt;br /&gt;
|-&lt;br /&gt;
| '''Backend Endpoints''' || Missing endpoints: submit quiz answers, fetch quiz for student, get quiz score&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
┌─────────────────────────────────────────────────────────────┐&lt;br /&gt;
│                     Frontend (React/TS)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  StudentTasks ──→ QuizGateway ──→ QuizTaker ──→ QuizResult  │&lt;br /&gt;
│       │                              │                      │&lt;br /&gt;
│       └──→ ReviewForm                └──→ API Services      │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  QuestionnaireEditor ──→ QuizCorrectAnswerEditor            │&lt;br /&gt;
└──────────────────────────┬──────────────────────────────────┘&lt;br /&gt;
                           │ REST API&lt;br /&gt;
┌──────────────────────────┴──────────────────────────────────┐&lt;br /&gt;
│                     Backend (Rails API)                     │&lt;br /&gt;
│                                                             │&lt;br /&gt;
│  StudentQuizzesController  ←── QuizScoringService           │&lt;br /&gt;
│  QuestionnairesController  ←── QuizQuestionChoice model     │&lt;br /&gt;
│  StudentTasksController    ←── StudentTask model            │&lt;br /&gt;
│  Response model            ←── ScorableHelper (enhanced)    │&lt;br /&gt;
└─────────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Key Features ===&lt;br /&gt;
&lt;br /&gt;
==== 1) Student Task View — Quiz/Review Gateway ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' Students currently see their assigned tasks but have no way to be directed to a quiz, a review, or both.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Enhance &amp;lt;code&amp;gt;StudentTasksController&amp;lt;/code&amp;gt; and the frontend &amp;lt;code&amp;gt;StudentTasks&amp;lt;/code&amp;gt; page to determine what action a student needs to perform based on the assignment configuration:&lt;br /&gt;
&lt;br /&gt;
* If &amp;lt;code&amp;gt;assignment.require_quiz == true&amp;lt;/code&amp;gt; and the student has not yet taken the quiz for a submission → show &amp;quot;Take Quiz&amp;quot; action&lt;br /&gt;
* If the student has taken the quiz (or quiz is not required) → show &amp;quot;Write Review&amp;quot; action&lt;br /&gt;
* If both are required and the quiz is completed → show &amp;quot;Write Review&amp;quot; action&lt;br /&gt;
&lt;br /&gt;
==== 2) Quiz Scoring Logic ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' The current scoring in &amp;lt;code&amp;gt;Response#aggregate_questionnaire_score&amp;lt;/code&amp;gt; multiplies &amp;lt;code&amp;gt;answer * weight&amp;lt;/code&amp;gt; for all items. For quiz questionnaires, it should check whether the student's answer is ''correct'' (1 point) or ''incorrect'' (0 points).&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Enhance the &amp;lt;code&amp;gt;Response&amp;lt;/code&amp;gt; model's scoring to detect &amp;lt;code&amp;gt;QuizQuestionnaire&amp;lt;/code&amp;gt; type and score differently.&lt;br /&gt;
&lt;br /&gt;
==== 3) Specifying Correct Answers in the Questionnaire Editor (Frontend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' When creating a Quiz questionnaire, the instructor needs a way to mark which answers are correct. For TextField items (fill-in-the-blank), the instructor needs to specify multiple acceptable correct answers.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Extend the &amp;lt;code&amp;gt;QuestionnaireItemsFieldArray&amp;lt;/code&amp;gt; component to render answer-choice editors when the questionnaire type is &amp;quot;Quiz&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== 4) Student Quiz-Taking Controller (Backend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' There is no endpoint for students to fetch a quiz, submit answers, or view their score.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Create a new &amp;lt;code&amp;gt;StudentQuizzesController&amp;lt;/code&amp;gt; with the following actions:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
GET    /student_quizzes                          → index (all quizzes assigned to current user)&lt;br /&gt;
GET    /student_quizzes/:id                      → show (fetch quiz with questions for taking)&lt;br /&gt;
POST   /student_quizzes/assign                   → assign_quiz (instructor assigns quiz to student)&lt;br /&gt;
POST   /student_quizzes/submit_answers           → submit_quiz (student submits quiz answers)&lt;br /&gt;
GET    /student_quizzes/:id/score                → view_score (student views their quiz score)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5) Quiz-Taking Interface (Frontend) ====&lt;br /&gt;
&lt;br /&gt;
'''Problem:''' There is no student-facing page where a student can take a quiz.&lt;br /&gt;
&lt;br /&gt;
'''Solution:''' Create a &amp;lt;code&amp;gt;QuizTaker&amp;lt;/code&amp;gt; page component.&lt;br /&gt;
&lt;br /&gt;
'''QuizTaker Flow:'''&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. Student clicks &amp;quot;Take Quiz&amp;quot; from StudentTasks&lt;br /&gt;
2. Frontend fetches GET /student_quizzes/:id (quiz items without correct answers)&lt;br /&gt;
3. Student fills in answers for each question&lt;br /&gt;
4. Student clicks &amp;quot;Submit&amp;quot;&lt;br /&gt;
5. Frontend sends POST /student_quizzes/submit_answers&lt;br /&gt;
6. Backend scores answers via QuizScoringService&lt;br /&gt;
7. Frontend receives score and displays QuizResult&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167811</id>
		<title>CSC/ECE 517 Spring 2026 - E2619. Student Quizzes Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=167811"/>
		<updated>2026-04-11T05:41:28Z</updated>

		<summary type="html">&lt;p&gt;Atmai: Created page with &amp;quot;Start&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Start&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167622</id>
		<title>CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167622"/>
		<updated>2026-03-30T23:13:50Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend =&lt;br /&gt;
&lt;br /&gt;
== Important Links ==&lt;br /&gt;
* Git PR (front-end): https://github.com/expertiza/reimplementation-front-end/pull/163&lt;br /&gt;
* GitHub Repository (front-end): https://github.com/EFHassler/ResponsesController_Frontend_CSC517&lt;br /&gt;
* Demo Video: https://drive.google.com/file/d/1KaY0lCwc8ppLmhtatW7d38o6kAbKP_TQ/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
== Expertiza ==&lt;br /&gt;
Expertiza is an open-source web application for peer assessment and collaborative learning. It allows instructors to create assignments where students can submit work, review peers, and receive feedback. Expertiza supports team and individual assignments, metareviews, topic sign-ups, staggered deadlines, and detailed rubrics, promoting active learning and continuous improvement.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The objective of this project is to reimplement the review and response workflow UI for Expertiza. This includes:&lt;br /&gt;
* A modern student dashboard for assigned reviews and topic sign-up.&lt;br /&gt;
* Dynamic review/teammate review forms supporting all questionnaire item types.&lt;br /&gt;
* Instructor tools for assigning reviewers and monitoring review progress.&lt;br /&gt;
* Integration with the backend API for seamless data flow.&lt;br /&gt;
&lt;br /&gt;
== Problem ==&lt;br /&gt;
The previous review UI was limited in usability and flexibility. Students could not easily track or resume reviews, and instructors lacked intuitive tools for managing reviewer assignments. The interface did not fully support all questionnaire types or provide clear feedback/status indicators.&lt;br /&gt;
&lt;br /&gt;
== Solution Approach ==&lt;br /&gt;
* Student dashboard displays all assigned reviews, their status (Not saved, Saved, Submitted), and topic/team info.&lt;br /&gt;
* Dynamic review forms render all item types (Criterion, Scale, TextField, TextArea, Dropdown, MultipleChoice, Checkbox, Grid, UploadFile, SectionHeader).&lt;br /&gt;
* Drafts can be saved and resumed; submissions are locked for editing.&lt;br /&gt;
* Instructor “Assign Reviewer” page allows adding/removing reviewers per team.&lt;br /&gt;
* Review Tableau visualizes all reviews for a participant in a matrix format.&lt;br /&gt;
* Uses React 18, TypeScript, Redux Toolkit, React-Bootstrap, Formik, and axios for API integration.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
This project focuses on the frontend React application. Backend API endpoints are assumed to be available and compatible. The scope includes:&lt;br /&gt;
* StudentTasks dashboard&lt;br /&gt;
* TeammateReview form&lt;br /&gt;
* ResponseMappings (Assign Reviewer)&lt;br /&gt;
* ReviewTableau visualization&lt;br /&gt;
* Alert system for user feedback&lt;br /&gt;
&lt;br /&gt;
== Key Components ==&lt;br /&gt;
; StudentTasks&lt;br /&gt;
: Displays assigned reviews, their status, and topic/team info. Allows navigation to review forms.&lt;br /&gt;
&lt;br /&gt;
; TeammateReview&lt;br /&gt;
: Renders dynamic review forms for all questionnaire item types. Supports draft saving, submission, and read-only mode.&lt;br /&gt;
&lt;br /&gt;
; ResponseMappings (Assign Reviewer)&lt;br /&gt;
: Instructor tool for assigning/removing reviewers to teams. Uses backend API and localStorage.&lt;br /&gt;
&lt;br /&gt;
; ReviewTableau&lt;br /&gt;
: Visualizes all reviews for a participant in a matrix format, showing rubric items, scores, and feedback.&lt;br /&gt;
&lt;br /&gt;
; Alert System&lt;br /&gt;
: Provides contextual feedback for user actions (success, error, info), with auto-dismiss and manual close.&lt;br /&gt;
&lt;br /&gt;
; ResponseController (Backend Integration)&lt;br /&gt;
: Main backend API controller for managing review responses. Handles creating, updating, submitting, and fetching responses, with proper authorization.&lt;br /&gt;
&lt;br /&gt;
== Screenshots ==&lt;br /&gt;
* [[File:Ss1_e2607.png|700px]]&lt;br /&gt;
* [[File:Ss2_e2607.png|700px]]&lt;br /&gt;
&lt;br /&gt;
== Demo ==&lt;br /&gt;
* Demo Video: https://drive.google.com/file/d/1KaY0lCwc8ppLmhtatW7d38o6kAbKP_TQ/view?usp=sharing&lt;br /&gt;
&lt;br /&gt;
== Setup &amp;amp; Run ==&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
* Docker and Docker Compose&lt;br /&gt;
* Node.js (v18+) and npm&lt;br /&gt;
&lt;br /&gt;
=== Backend (Rails API) ===&lt;br /&gt;
# In backend directory:&lt;br /&gt;
  cd E2550-reimplementation-back-end&lt;br /&gt;
  docker compose build --no-cache&lt;br /&gt;
  docker compose up&lt;br /&gt;
# (First time) In a new terminal:&lt;br /&gt;
  docker compose exec app bash&lt;br /&gt;
  bundle install&lt;br /&gt;
  bundle exec rails db:migrate:reset&lt;br /&gt;
  bundle exec rails db:seed&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React) ===&lt;br /&gt;
# In frontend directory:&lt;br /&gt;
  cd ResponsesController_Frontend_CSC517&lt;br /&gt;
  npm install&lt;br /&gt;
  npm run dev&lt;br /&gt;
&lt;br /&gt;
# Frontend: http://localhost:3000&lt;br /&gt;
# Backend API: http://localhost:3002&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
* Manual testing of all review workflows (dashboard, form, assign reviewer, tableau).&lt;br /&gt;
* API error and loading state handling.&lt;br /&gt;
* Demo credentials: username: student1@example.com, password: password&lt;br /&gt;
* Admin credentials: username: admin, password: password123&lt;br /&gt;
&lt;br /&gt;
== Team ==&lt;br /&gt;
'''Members'''&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;br /&gt;
* Emma Hassler (efhassle@ncsu.edu)&lt;br /&gt;
'''Mentor'''&lt;br /&gt;
* Vishal Reddy Devireddy (vdevire2@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
== Future Scope ==&lt;br /&gt;
* Add more comprehensive test coverage.&lt;br /&gt;
* Enhance accessibility and mobile responsiveness.&lt;br /&gt;
* Integrate real-time updates for review assignments.&lt;br /&gt;
* Add instructor dashboards for review analytics.&lt;br /&gt;
&lt;br /&gt;
== Navigation ==&lt;br /&gt;
* [[#StudentTasks|StudentTasks Component]]&lt;br /&gt;
* [[#TeammateReview|TeammateReview Component]]&lt;br /&gt;
* [[#ResponseMappings|ResponseMappings Component]]&lt;br /&gt;
* [[#ReviewTableau|ReviewTableau Component]]&lt;br /&gt;
* [[#Demo|Demo]]&lt;br /&gt;
* [[#Future Scope|Future Scope]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167594</id>
		<title>CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167594"/>
		<updated>2026-03-30T19:59:36Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend =&lt;br /&gt;
&lt;br /&gt;
== Important Links ==&lt;br /&gt;
* Git PR (front-end): https://github.com/expertiza/reimplementation-front-end/pull/XX&lt;br /&gt;
* GitHub Repository (front-end): https://github.com/your-username/reimplementation-front-end&lt;br /&gt;
* Demo Video: https://drive.google.com/your-demo-link&lt;br /&gt;
&lt;br /&gt;
== Expertiza ==&lt;br /&gt;
Expertiza is an open-source web application for peer assessment and collaborative learning. It allows instructors to create assignments where students can submit work, review peers, and receive feedback. Expertiza supports team and individual assignments, metareviews, topic sign-ups, staggered deadlines, and detailed rubrics, promoting active learning and continuous improvement.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The objective of this project is to reimplement the review and response workflow UI for Expertiza. This includes:&lt;br /&gt;
* A modern student dashboard for assigned reviews and topic sign-up.&lt;br /&gt;
* Dynamic review/teammate review forms supporting all questionnaire item types.&lt;br /&gt;
* Instructor tools for assigning reviewers and monitoring review progress.&lt;br /&gt;
* Integration with the backend API for seamless data flow.&lt;br /&gt;
&lt;br /&gt;
== Problem ==&lt;br /&gt;
The previous review UI was limited in usability and flexibility. Students could not easily track or resume reviews, and instructors lacked intuitive tools for managing reviewer assignments. The interface did not fully support all questionnaire types or provide clear feedback/status indicators.&lt;br /&gt;
&lt;br /&gt;
== Solution Approach ==&lt;br /&gt;
* Student dashboard displays all assigned reviews, their status (Not saved, Saved, Submitted), and topic/team info.&lt;br /&gt;
* Dynamic review forms render all item types (Criterion, Scale, TextField, TextArea, Dropdown, MultipleChoice, Checkbox, Grid, UploadFile, SectionHeader).&lt;br /&gt;
* Drafts can be saved and resumed; submissions are locked for editing.&lt;br /&gt;
* Instructor “Assign Reviewer” page allows adding/removing reviewers per team.&lt;br /&gt;
* Review Tableau visualizes all reviews for a participant in a matrix format.&lt;br /&gt;
* Uses React 18, TypeScript, Redux Toolkit, React-Bootstrap, Formik, and axios for API integration.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
This project focuses on the frontend React application. Backend API endpoints are assumed to be available and compatible. The scope includes:&lt;br /&gt;
* StudentTasks dashboard&lt;br /&gt;
* TeammateReview form&lt;br /&gt;
* ResponseMappings (Assign Reviewer)&lt;br /&gt;
* ReviewTableau visualization&lt;br /&gt;
* Alert system for user feedback&lt;br /&gt;
&lt;br /&gt;
== Key Components ==&lt;br /&gt;
; StudentTasks&lt;br /&gt;
: Displays assigned reviews, their status, and topic/team info. Allows navigation to review forms.&lt;br /&gt;
&lt;br /&gt;
; TeammateReview&lt;br /&gt;
: Renders dynamic review forms for all questionnaire item types. Supports draft saving, submission, and read-only mode.&lt;br /&gt;
&lt;br /&gt;
; ResponseMappings (Assign Reviewer)&lt;br /&gt;
: Instructor tool for assigning/removing reviewers to teams. Uses backend API and localStorage.&lt;br /&gt;
&lt;br /&gt;
; ReviewTableau&lt;br /&gt;
: Visualizes all reviews for a participant in a matrix format, showing rubric items, scores, and feedback.&lt;br /&gt;
&lt;br /&gt;
; Alert System&lt;br /&gt;
: Provides contextual feedback for user actions (success, error, info), with auto-dismiss and manual close.&lt;br /&gt;
&lt;br /&gt;
; ResponseController (Backend Integration)&lt;br /&gt;
: Main backend API controller for managing review responses. Handles creating, updating, submitting, and fetching responses, with proper authorization.&lt;br /&gt;
&lt;br /&gt;
== Screenshots ==&lt;br /&gt;
* [[File:Ss1_e2607.png|700px]]&lt;br /&gt;
* [[File:Ss2_e2607.png|700px]]&lt;br /&gt;
&lt;br /&gt;
== Demo ==&lt;br /&gt;
* Demo Video: https://drive.google.com/your-demo-link&lt;br /&gt;
&lt;br /&gt;
== Setup &amp;amp; Run ==&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
* Docker and Docker Compose&lt;br /&gt;
* Node.js (v18+) and npm&lt;br /&gt;
&lt;br /&gt;
=== Backend (Rails API) ===&lt;br /&gt;
# In backend directory:&lt;br /&gt;
  cd E2550-reimplementation-back-end&lt;br /&gt;
  docker compose build --no-cache&lt;br /&gt;
  docker compose up&lt;br /&gt;
# (First time) In a new terminal:&lt;br /&gt;
  docker compose exec app bash&lt;br /&gt;
  bundle install&lt;br /&gt;
  bundle exec rails db:migrate:reset&lt;br /&gt;
  bundle exec rails db:seed&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React) ===&lt;br /&gt;
# In frontend directory:&lt;br /&gt;
  cd ResponsesController_Frontend_CSC517&lt;br /&gt;
  npm install&lt;br /&gt;
  npm run dev&lt;br /&gt;
&lt;br /&gt;
# Frontend: http://localhost:3000&lt;br /&gt;
# Backend API: http://localhost:3002&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
* Manual testing of all review workflows (dashboard, form, assign reviewer, tableau).&lt;br /&gt;
* API error and loading state handling.&lt;br /&gt;
* Demo credentials: username: student1@example.com, password: password&lt;br /&gt;
* Admin credentials: username: admin, password: password123&lt;br /&gt;
&lt;br /&gt;
== Team ==&lt;br /&gt;
'''Members'''&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;br /&gt;
* Emma Hassler (efhassle@ncsu.edu)&lt;br /&gt;
'''Mentor'''&lt;br /&gt;
* Vishal Reddy Devireddy (vdevire2@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
== Future Scope ==&lt;br /&gt;
* Add more comprehensive test coverage.&lt;br /&gt;
* Enhance accessibility and mobile responsiveness.&lt;br /&gt;
* Integrate real-time updates for review assignments.&lt;br /&gt;
* Add instructor dashboards for review analytics.&lt;br /&gt;
&lt;br /&gt;
== Navigation ==&lt;br /&gt;
* [[#StudentTasks|StudentTasks Component]]&lt;br /&gt;
* [[#TeammateReview|TeammateReview Component]]&lt;br /&gt;
* [[#ResponseMappings|ResponseMappings Component]]&lt;br /&gt;
* [[#ReviewTableau|ReviewTableau Component]]&lt;br /&gt;
* [[#Demo|Demo]]&lt;br /&gt;
* [[#Future Scope|Future Scope]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Ss2_e2607.png&amp;diff=167593</id>
		<title>File:Ss2 e2607.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Ss2_e2607.png&amp;diff=167593"/>
		<updated>2026-03-30T19:56:23Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Ss1_e2607.png&amp;diff=167592</id>
		<title>File:Ss1 e2607.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Ss1_e2607.png&amp;diff=167592"/>
		<updated>2026-03-30T19:55:51Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167591</id>
		<title>CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167591"/>
		<updated>2026-03-30T17:55:04Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend =&lt;br /&gt;
&lt;br /&gt;
== Important Links ==&lt;br /&gt;
* Git PR (front-end): https://github.com/expertiza/reimplementation-front-end/pull/XX&lt;br /&gt;
* GitHub Repository (front-end): https://github.com/your-username/reimplementation-front-end&lt;br /&gt;
* Demo Video: https://drive.google.com/your-demo-link&lt;br /&gt;
&lt;br /&gt;
== Expertiza ==&lt;br /&gt;
Expertiza is an open-source web application for peer assessment and collaborative learning. It allows instructors to create assignments where students can submit work, review peers, and receive feedback. Expertiza supports team and individual assignments, metareviews, topic sign-ups, staggered deadlines, and detailed rubrics, promoting active learning and continuous improvement.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The objective of this project is to reimplement the review and response workflow UI for Expertiza. This includes:&lt;br /&gt;
* A modern student dashboard for assigned reviews and topic sign-up.&lt;br /&gt;
* Dynamic review/teammate review forms supporting all questionnaire item types.&lt;br /&gt;
* Instructor tools for assigning reviewers and monitoring review progress.&lt;br /&gt;
* Integration with the backend API for seamless data flow.&lt;br /&gt;
&lt;br /&gt;
== Problem ==&lt;br /&gt;
The previous review UI was limited in usability and flexibility. Students could not easily track or resume reviews, and instructors lacked intuitive tools for managing reviewer assignments. The interface did not fully support all questionnaire types or provide clear feedback/status indicators.&lt;br /&gt;
&lt;br /&gt;
== Solution Approach ==&lt;br /&gt;
* Student dashboard displays all assigned reviews, their status (Not saved, Saved, Submitted), and topic/team info.&lt;br /&gt;
* Dynamic review forms render all item types (Criterion, Scale, TextField, TextArea, Dropdown, MultipleChoice, Checkbox, Grid, UploadFile, SectionHeader).&lt;br /&gt;
* Drafts can be saved and resumed; submissions are locked for editing.&lt;br /&gt;
* Instructor “Assign Reviewer” page allows adding/removing reviewers per team.&lt;br /&gt;
* Review Tableau visualizes all reviews for a participant in a matrix format.&lt;br /&gt;
* Uses React 18, TypeScript, Redux Toolkit, React-Bootstrap, Formik, and axios for API integration.&lt;br /&gt;
&lt;br /&gt;
== Scope ==&lt;br /&gt;
This project focuses on the frontend React application. Backend API endpoints are assumed to be available and compatible. The scope includes:&lt;br /&gt;
* StudentTasks dashboard&lt;br /&gt;
* TeammateReview form&lt;br /&gt;
* ResponseMappings (Assign Reviewer)&lt;br /&gt;
* ReviewTableau visualization&lt;br /&gt;
* Alert system for user feedback&lt;br /&gt;
&lt;br /&gt;
== Key Components ==&lt;br /&gt;
; StudentTasks&lt;br /&gt;
: Displays assigned reviews, their status, and topic/team info. Allows navigation to review forms.&lt;br /&gt;
&lt;br /&gt;
; TeammateReview&lt;br /&gt;
: Renders dynamic review forms for all questionnaire item types. Supports draft saving, submission, and read-only mode.&lt;br /&gt;
&lt;br /&gt;
; ResponseMappings (Assign Reviewer)&lt;br /&gt;
: Instructor tool for assigning/removing reviewers to teams. Uses backend API and localStorage.&lt;br /&gt;
&lt;br /&gt;
; ReviewTableau&lt;br /&gt;
: Visualizes all reviews for a participant in a matrix format, showing rubric items, scores, and feedback.&lt;br /&gt;
&lt;br /&gt;
; Alert System&lt;br /&gt;
: Provides contextual feedback for user actions (success, error, info), with auto-dismiss and manual close.&lt;br /&gt;
&lt;br /&gt;
; ResponseController (Backend Integration)&lt;br /&gt;
: Main backend API controller for managing review responses. Handles creating, updating, submitting, and fetching responses, with proper authorization.&lt;br /&gt;
&lt;br /&gt;
== Demo ==&lt;br /&gt;
* Demo Video: https://drive.google.com/your-demo-link&lt;br /&gt;
&lt;br /&gt;
== Setup &amp;amp; Run ==&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
* Docker and Docker Compose&lt;br /&gt;
* Node.js (v18+) and npm&lt;br /&gt;
&lt;br /&gt;
=== Backend (Rails API) ===&lt;br /&gt;
# In backend directory:&lt;br /&gt;
  cd E2550-reimplementation-back-end&lt;br /&gt;
  docker compose build --no-cache&lt;br /&gt;
  docker compose up&lt;br /&gt;
# (First time) In a new terminal:&lt;br /&gt;
  docker compose exec app bash&lt;br /&gt;
  bundle install&lt;br /&gt;
  bundle exec rails db:migrate:reset&lt;br /&gt;
  bundle exec rails db:seed&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React) ===&lt;br /&gt;
# In frontend directory:&lt;br /&gt;
  cd ResponsesController_Frontend_CSC517&lt;br /&gt;
  npm install&lt;br /&gt;
  npm run dev&lt;br /&gt;
&lt;br /&gt;
# Frontend: http://localhost:3000&lt;br /&gt;
# Backend API: http://localhost:3002&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
* Manual testing of all review workflows (dashboard, form, assign reviewer, tableau).&lt;br /&gt;
* API error and loading state handling.&lt;br /&gt;
* Demo credentials: username: student1@example.com, password: password&lt;br /&gt;
* Admin credentials: username: admin, password: password123&lt;br /&gt;
&lt;br /&gt;
== Team ==&lt;br /&gt;
'''Members'''&lt;br /&gt;
* An Mai (atmai@ncsu.edu)&lt;br /&gt;
* Tejas Manoj Desai (tdesai@ncsu.edu)&lt;br /&gt;
* Emma Hassler (efhassle@ncsu.edu)&lt;br /&gt;
'''Mentor'''&lt;br /&gt;
* Vishal Reddy Devireddy (vdevire2@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
== Future Scope ==&lt;br /&gt;
* Add more comprehensive test coverage.&lt;br /&gt;
* Enhance accessibility and mobile responsiveness.&lt;br /&gt;
* Integrate real-time updates for review assignments.&lt;br /&gt;
* Add instructor dashboards for review analytics.&lt;br /&gt;
&lt;br /&gt;
== Navigation ==&lt;br /&gt;
* [[#StudentTasks|StudentTasks Component]]&lt;br /&gt;
* [[#TeammateReview|TeammateReview Component]]&lt;br /&gt;
* [[#ResponseMappings|ResponseMappings Component]]&lt;br /&gt;
* [[#ReviewTableau|ReviewTableau Component]]&lt;br /&gt;
* [[#Demo|Demo]]&lt;br /&gt;
* [[#Future Scope|Future Scope]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026&amp;diff=167590</id>
		<title>CSC/ECE 517 Spring 2026</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026&amp;diff=167590"/>
		<updated>2026-03-30T17:42:29Z</updated>

		<summary type="html">&lt;p&gt;Atmai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[CSC/ECE 517 Spring 2026 - E2602. Reimplement student task view]]&lt;br /&gt;
* [[CSC/ECE 517 Spring 2026 - E2604. Finish Password Resets]]&lt;br /&gt;
* [[CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend]]&lt;br /&gt;
* [[CSC/ECE 517 Spring 2026 - E2610. Teams hierarchy testing]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167589</id>
		<title>CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2607._ResponseController_Frontend&amp;diff=167589"/>
		<updated>2026-03-30T17:40:36Z</updated>

		<summary type="html">&lt;p&gt;Atmai: Created page with &amp;quot;= CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend =  == Introduction ==  === Background === The ResponseController Frontend provides the user interface for students and instructors to manage peer reviews, teammate reviews, and feedback in the Expertiza platform. It enables students to view assigned review tasks, complete and submit reviews, and instructors to assign reviewers and monitor review progress.  === Motivation === * Modernize the review and respons...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= CSC/ECE 517 Spring 2026 - E2607. ResponseController Frontend =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
The ResponseController Frontend provides the user interface for students and instructors to manage peer reviews, teammate reviews, and feedback in the Expertiza platform. It enables students to view assigned review tasks, complete and submit reviews, and instructors to assign reviewers and monitor review progress.&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
* Modernize the review and response UI for clarity and usability.&lt;br /&gt;
* Streamline the process of submitting, saving, and resuming reviews.&lt;br /&gt;
* Improve assignment and review management for instructors.&lt;br /&gt;
* Ensure maintainability and scalability using React, TypeScript, and modular design.&lt;br /&gt;
&lt;br /&gt;
=== Tasks Identified ===&lt;br /&gt;
; Student Dashboard (StudentTasks)&lt;br /&gt;
: Display assigned reviews and their statuses (Not saved, Saved, Submitted).&lt;br /&gt;
: Show topic signup and team information.&lt;br /&gt;
: Allow navigation to the review form for each assignment.&lt;br /&gt;
&lt;br /&gt;
; Review/Teammate Review Form (TeammateReview)&lt;br /&gt;
: Render dynamic forms for all questionnaire item types (Criterion, Scale, TextField, TextArea, Dropdown, MultipleChoice, Checkbox, Grid, UploadFile, SectionHeader).&lt;br /&gt;
: Support saving drafts, resuming, and submitting reviews.&lt;br /&gt;
: Display read-only mode for submitted reviews.&lt;br /&gt;
: Auto-create ResponseMap if not found.&lt;br /&gt;
&lt;br /&gt;
; Assign Reviewer (Instructor)&lt;br /&gt;
: List teams, members, and current reviewer assignments.&lt;br /&gt;
: Add or remove reviewers for each team.&lt;br /&gt;
: Persist assignments using backend API and localStorage.&lt;br /&gt;
&lt;br /&gt;
; Review Tableau&lt;br /&gt;
: Visualize all reviews done by a participant for an assignment.&lt;br /&gt;
: Show rubric items, scores, and feedback in a tabular format.&lt;br /&gt;
&lt;br /&gt;
; General Improvements&lt;br /&gt;
: Use React-Bootstrap for consistent UI.&lt;br /&gt;
: Integrate alert system for feedback (success, error, info).&lt;br /&gt;
: Modularize code with hooks, services, and reusable components.&lt;br /&gt;
&lt;br /&gt;
== Demo ==&lt;br /&gt;
&lt;br /&gt;
=== Demo Video ===&lt;br /&gt;
''To be added by the team.''&lt;br /&gt;
&lt;br /&gt;
=== Demo Link ===&lt;br /&gt;
''To be added by the team.''&lt;br /&gt;
&lt;br /&gt;
== Key Components ==&lt;br /&gt;
&lt;br /&gt;
=== ResponseController ===&lt;br /&gt;
The ResponseController is the main backend API controller for managing peer review and teammate review responses. It provides endpoints for:&lt;br /&gt;
* Creating a new response (draft) or reusing an existing unsubmitted draft for a given review assignment.&lt;br /&gt;
* Fetching an existing response and its associated answers (scores/comments).&lt;br /&gt;
* Updating a draft response with new answers.&lt;br /&gt;
* Submitting a response, which locks it from further editing.&lt;br /&gt;
* Unsubmitting or deleting a response (instructor/admin only).&lt;br /&gt;
* Ensuring that only the assigned reviewer or authorized instructor can access or modify each response.&lt;br /&gt;
&lt;br /&gt;
The frontend integrates with these endpoints to support saving drafts, resuming work, and submitting reviews, ensuring a smooth and secure review workflow for both students and instructors.&lt;br /&gt;
&lt;br /&gt;
=== StudentTasks ===&lt;br /&gt;
* Fetches assigned reviews from backend and localStorage.&lt;br /&gt;
* Renders a table of assignments, teams, review types, statuses, and action buttons.&lt;br /&gt;
* Integrates with topic signup and team info.&lt;br /&gt;
&lt;br /&gt;
=== TeammateReview ===&lt;br /&gt;
* Fetches questionnaire items and existing responses.&lt;br /&gt;
* Renders a dynamic review form based on item types.&lt;br /&gt;
* Handles draft saving, submission, and read-only display.&lt;br /&gt;
* Integrates with backend for response and response map management.&lt;br /&gt;
&lt;br /&gt;
=== ResponseMappings (Assign Reviewer) ===&lt;br /&gt;
* Displays teams, members, and reviewer assignments.&lt;br /&gt;
* Allows instructors to add or remove reviewers.&lt;br /&gt;
* Uses localStorage for persistence and demo data.&lt;br /&gt;
&lt;br /&gt;
=== ReviewTableau ===&lt;br /&gt;
* Fetches and displays all reviews for a participant.&lt;br /&gt;
* Shows rubric items, scores, and feedback in a matrix.&lt;br /&gt;
* Uses color coding and tooltips for clarity.&lt;br /&gt;
&lt;br /&gt;
=== Alert System ===&lt;br /&gt;
* Provides contextual feedback for user actions.&lt;br /&gt;
* Alerts auto-dismiss after a timeout or can be closed manually.&lt;br /&gt;
&lt;br /&gt;
== Architecture ==&lt;br /&gt;
* '''Frontend''': React 18, TypeScript, Redux Toolkit, React-Bootstrap, Formik, TanStack Table.&lt;br /&gt;
* '''Backend''': Ruby on Rails API (see E2550-reimplementation-back-end).&lt;br /&gt;
* '''API Integration''': Uses axios for HTTP requests to backend endpoints for responses, response maps, questionnaires, grades, and assignments.&lt;br /&gt;
&lt;br /&gt;
== Setup &amp;amp; Run ==&lt;br /&gt;
&lt;br /&gt;
=== Prerequisites ===&lt;br /&gt;
* Docker and Docker Compose installed&lt;br /&gt;
* Node.js (v18+) and npm (for frontend development)&lt;br /&gt;
* (Optional) Ruby and Rails for direct backend development&lt;br /&gt;
&lt;br /&gt;
=== Backend (Rails API) ===&lt;br /&gt;
# Open a terminal in the backend directory:&lt;br /&gt;
  cd E2550-reimplementation-back-end&lt;br /&gt;
&lt;br /&gt;
# Build and start the backend services:&lt;br /&gt;
  docker compose build --no-cache&lt;br /&gt;
  docker compose up&lt;br /&gt;
&lt;br /&gt;
# (First time only) In a new terminal, run setup commands inside the app container:&lt;br /&gt;
  docker compose exec app bash&lt;br /&gt;
  bundle install&lt;br /&gt;
  bundle exec rails db:migrate:reset&lt;br /&gt;
  bundle exec rails db:seed&lt;br /&gt;
&lt;br /&gt;
# Run backend tests (optional):&lt;br /&gt;
  bundle exec rspec&lt;br /&gt;
&lt;br /&gt;
=== Frontend (React) ===&lt;br /&gt;
# Open a new terminal in the frontend directory:&lt;br /&gt;
  cd ResponsesController_Frontend_CSC517&lt;br /&gt;
&lt;br /&gt;
# Install dependencies:&lt;br /&gt;
  npm install&lt;br /&gt;
&lt;br /&gt;
# Start the development server:&lt;br /&gt;
  npm run dev&lt;br /&gt;
&lt;br /&gt;
# The frontend will be available at http://localhost:3000 (or as shown in the terminal).&lt;br /&gt;
# The backend API runs at http://localhost:3002 by default.&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
* Make sure both backend and frontend servers are running for full functionality.&lt;br /&gt;
* Update environment variables or API URLs in the frontend if your backend runs on a different port.&lt;br /&gt;
* For production deployment, follow additional build and configuration steps as needed.&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Add more comprehensive test coverage for all components.&lt;br /&gt;
* Enhance accessibility and mobile responsiveness.&lt;br /&gt;
* Integrate real-time updates for review assignments.&lt;br /&gt;
* Add instructor dashboards for review analytics.&lt;br /&gt;
&lt;br /&gt;
== Navigation ==&lt;br /&gt;
* [[#StudentTasks|StudentTasks Component]]&lt;br /&gt;
* [[#TeammateReview|TeammateReview Component]]&lt;br /&gt;
* [[#ResponseMappings|ResponseMappings Component]]&lt;br /&gt;
* [[#ReviewTableau|ReviewTableau Component]]&lt;br /&gt;
* [[#Demo|Demo]]&lt;br /&gt;
* [[#Future Work|Future Work]]&lt;/div&gt;</summary>
		<author><name>Atmai</name></author>
	</entry>
</feed>