<?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=Tdesai</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=Tdesai"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Tdesai"/>
	<updated>2026-05-22T00:42:47Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Quiz_score_display.png&amp;diff=168184</id>
		<title>File:Quiz score display.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Quiz_score_display.png&amp;diff=168184"/>
		<updated>2026-04-29T03:11:17Z</updated>

		<summary type="html">&lt;p&gt;Tdesai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tdesai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Correct_answer_fields.png&amp;diff=168183</id>
		<title>File:Correct answer fields.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Correct_answer_fields.png&amp;diff=168183"/>
		<updated>2026-04-29T03:09:45Z</updated>

		<summary type="html">&lt;p&gt;Tdesai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tdesai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Take_quiz_button.png&amp;diff=168182</id>
		<title>File:Take quiz button.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Take_quiz_button.png&amp;diff=168182"/>
		<updated>2026-04-29T03:08:13Z</updated>

		<summary type="html">&lt;p&gt;Tdesai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Tdesai</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2619._Student_Quizzes_Frontend&amp;diff=168181</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=168181"/>
		<updated>2026-04-29T03:03:09Z</updated>

		<summary type="html">&lt;p&gt;Tdesai: /* Screenshots */&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]]&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Editor – Correct Answer Fields ===&lt;br /&gt;
[[File:correct_answer_fields.png]]&lt;br /&gt;
&lt;br /&gt;
=== Quiz Submission and Score Display ===&lt;br /&gt;
[[File:quiz_score_display.png]]&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>Tdesai</name></author>
	</entry>
</feed>