Skip to main content

Comprehension Checks System

Technical documentation for the comprehension check feature in HyperStudy.

Overview​

The comprehension check system allows experimenters to embed quiz questions directly into instruction pages to verify participant understanding before they enter the waiting room. Questions are embedded at the page level and validated client-side with automatic data recording.

Architecture​

Component Structure​

Instructions.svelte (Admin)
├── QuestionBuilder.svelte
│ └── Question configuration UI
│ └── Type-specific forms
│ └── Question management

InstructionsDisplay.svelte (Participant)
├── Question rendering
├── Answer collection
├── Validation logic
└── Data recording integration

Data Flow​

1. Admin creates questions in Instructions.svelte
↓
2. Questions stored in experiment.instructionsPages[].questions
↓
3. Participant views InstructionsDisplay.svelte
↓
4. Questions rendered with appropriate inputs
↓
5. Answers validated using comprehensionCheckValidation.js
↓
6. Results recorded to dataServiceV2
↓
7. Session updated in participantSessionService

Key Files​

Core Implementation​

/frontend/src/lib/utils/comprehensionCheckValidation.js​

Validation logic for all question types.

Key Functions:

  • validateMultipleChoice(question, userAnswer) - Validates MC answers
  • validateTrueFalse(question, userAnswer) - Validates T/F answers
  • validateTextAnswer(question, userAnswer) - Validates text with keywords/phrases
  • validateNumericAnswer(question, userAnswer) - Validates numbers with tolerance
  • validateAnswers(questions, userAnswers, passingScore) - Main validator
  • areAllQuestionsAnswered(questions, userAnswers) - Completeness check
  • randomizeQuestions(questions) - Shuffle question order
  • randomizeQuestionOptions(question) - Shuffle MC options
  • sanitizeInput(input) - XSS prevention

Example Usage:

import { validateAnswers } from './comprehensionCheckValidation.js';

const result = validateAnswers(
questions, // Array of question objects
userAnswers, // { questionId: answer }
80 // Passing score (%)
);

// result = {
// score: 75,
// passed: false,
// correct: 3,
// total: 4,
// feedback: { q1: { correct: true, explanation: null }, ... }
// }

/frontend/src/components/admin/experiment/QuestionBuilder.svelte​

Admin UI for creating and managing questions.

Props:

  • questions - Questions object from instruction page
  • onUpdate - Callback when questions change

State:

  • config - Complete questions configuration
  • editingQuestionIndex - Currently expanded question
  • newQuestionType - Type for new questions

Key Functions:

  • addQuestion() - Creates new question based on type
  • removeQuestion(index) - Deletes a question
  • updateQuestion(index, field, value) - Updates question property
  • moveQuestionUp/Down(index) - Reorders questions

/frontend/src/components/admin/experiment/Instructions.svelte​

Admin interface for instruction pages (modified to include questions).

Added Functions:

  • toggleQuestions() - Enable/disable questions for current page
  • handleQuestionsUpdate(questions) - Save question changes

Integration:

{#if hasQuestions}
<QuestionBuilder
questions={currentPage.questions}
onUpdate={handleQuestionsUpdate}
/>
{/if}

/frontend/src/components/participant/InstructionsDisplay.svelte​

Participant view that renders and validates questions.

Key State:

  • currentAnswers - User's answers { questionId: answer }
  • pageAttempts - All attempts per page { pageIndex: [attempts] }
  • showQuestions - Whether quiz is visible
  • showFeedback - Whether results are shown
  • displayedQuestions - Randomized question list

Key Functions:

  • initializeQuestions() - Sets up questions with randomization
  • startComprehensionCheck() - Shows quiz interface
  • submitAnswers() - Validates and records attempt
  • handleComprehensionFailure() - Handles max attempts reached
  • retryCheck() - Resets for new attempt

Data Recording:

await dataServiceV2.recordEvent({
eventType: 'instructions.comprehension_attempt',
category: 'pre_experiment',
experimentId: experiment.id,
participantId: auth.currentUser.uid,
value: {
pageIndex,
attemptNumber,
answers,
score,
passed,
correctCount,
totalQuestions,
timestamp
},
priority: 'high'
});

Testing​

/frontend/src/lib/utils/__tests__/comprehensionCheckValidation.test.js​

Comprehensive test suite with 43 tests covering:

  • All question type validations
  • Edge cases (null, undefined, empty)
  • Score calculation accuracy
  • Randomization functions
  • Input sanitization

Run Tests:

cd frontend
npm test -- src/lib/utils/__tests__/comprehensionCheckValidation.test.js

Data Model​

Question Object Structure​

Base Question​

{
id: "q_timestamp_random", // Unique identifier
type: "multiple-choice|true-false|short-text|numeric",
question: "Question text",
explanation: "Explanation for incorrect answers"
}

Multiple Choice​

{
...base,
type: "multiple-choice",
options: ["Option 1", "Option 2", "Option 3"],
correctAnswer: 1 // Index of correct option (0-based)
}

True/False​

{
...base,
type: "true-false",
correctAnswer: true // Boolean value
}

Short Text​

{
...base,
type: "short-text",
keywords: ["keyword1", "keyword2"], // All must be present
acceptableAnswers: ["exact phrase 1", "exact phrase 2"],
caseSensitive: false,
partialMatch: false,
minLength: 10
}

Numeric​

{
...base,
type: "numeric",
correctAnswer: 50,
validation: {
exact: false, // Require exact match
tolerance: 5, // Accept ±5
min: 45, // OR range validation
max: 55,
allowDecimals: true,
unit: "trials" // Display unit
}
}

Questions Configuration​

{
title: "Check Your Understanding",
required: true,
passingScore: 80, // Percentage (0-100)
allowRetry: true,
maxAttempts: 3,
showFeedback: true,
showExplanations: true,
randomizeQuestions: false,
randomizeOptions: false,
items: [/* question objects */]
}

Instruction Page with Questions​

{
title: "Study Instructions",
content: "# Instructions markdown...",
questions: {
/* questions configuration */
}
}

Validation Logic​

Text Answer Validation​

Text answers are validated in this priority order:

  1. Exact Match - Check acceptableAnswers array
  2. Keywords - All keywords must be present
  3. Partial Match - Substring matching if enabled
  4. Minimum Length - Character count validation

Case Handling:

const normalized = caseSensitive
? userAnswer.trim()
: userAnswer.toLowerCase().trim();

Numeric Validation​

Numeric answers support three validation modes:

  1. Exact: answer === correctAnswer
  2. Tolerance: Math.abs(answer - correctAnswer) <= tolerance
  3. Range: answer >= min && answer <= max

Decimal Handling:

if (!allowDecimals && !Number.isInteger(answer)) {
return false;
}

Scoring​

score = (correctCount / totalQuestions) * 100;
passed = score >= passingScore;

Event Recording​

Event Types​

Event TypeWhen FiredPriority
instructions.comprehension_attemptEach quiz submissionhigh
instructions.comprehension_passedParticipant passeshigh
instructions.comprehension_failedMax attempts reachedhigh
instructions.completedAll instructions donehigh

Event Data Structure​

{
eventType: 'instructions.comprehension_attempt',
category: 'pre_experiment',
experimentId: string,
participantId: string,
value: {
pageIndex: number,
pageTitle: string,
attemptNumber: number,
answers: { [questionId]: answer },
score: number,
passed: boolean,
correctCount: number,
totalQuestions: number,
timestamp: number
},
timestamp: ISO8601 string,
priority: 'high'
}

Session Updates​

When comprehension check fails:

await sessionService.updateSession(userId, {
comprehensionFailed: true,
failedAt: new Date().toISOString()
});

Security​

XSS Prevention​

All user text inputs are sanitized:

function sanitizeInput(input) {
return input
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
}

Validation Location​

  • Client-side only: Fast feedback, reduces server load
  • Answers not sent to server: Privacy-friendly
  • No retry limits: Configured per experiment

Performance​

Validation Speed​

  • Typical: < 1ms for 5 questions
  • No network calls: Instant feedback
  • Memory efficient: Questions cleared on page change

Bundle Size​

  • Validation utils: ~5KB minified
  • QuestionBuilder: ~8KB minified
  • No external dependencies

Extending the System​

Adding New Question Types​

  1. Add validation function in comprehensionCheckValidation.js:
export function validateNewType(question, userAnswer) {
// Validation logic
return boolean;
}
  1. Add case in validateAnswers:
case 'new-type':
isCorrect = validateNewType(question, userAnswer);
break;
  1. Add UI in QuestionBuilder.svelte:
{:else if question.type === 'new-type'}
<!-- Configuration UI -->
{/if}
  1. Add input in InstructionsDisplay.svelte:
{:else if question.type === 'new-type'}
<!-- Answer input UI -->
{/if}
  1. Add tests in test file

Customizing Validation​

Override or extend validation functions:

import { validateAnswers as baseValidate } from './comprehensionCheckValidation.js';

export function customValidateAnswers(questions, answers, passingScore) {
const result = baseValidate(questions, answers, passingScore);
// Custom logic
return result;
}

Troubleshooting​

Common Issues​

Questions not appearing:

  • Check hasQuestions derived value
  • Verify questions.items array exists and has length
  • Check showQuestions state

Validation not working:

  • Verify question correctAnswer is set
  • Check answer format matches question type
  • Test validation function in isolation

Data not recording:

  • Check auth.currentUser is defined
  • Verify dataServiceV2 is imported
  • Check console for errors
  • Confirm priority: 'high' is set

Randomization issues:

  • Ensure displayedQuestions is used, not questions.items
  • Check initializeQuestions() is called on page change
  • Verify correct answer index updates for MC randomization

Best Practices​

For Developers​

  1. Use Svelte 5 Runes: Follow $state, $derived, $effect patterns
  2. Type Safety: Add JSDoc comments for complex objects
  3. Error Handling: Wrap data recording in try-catch
  4. Validation: Always validate user input
  5. Testing: Update tests when changing validation logic

For Experimenters (in docs)​

  1. Clear Questions: Test with naive users
  2. Appropriate Difficulty: 70-80% passing score
  3. Helpful Feedback: Explain correct answers
  4. Strategic Placement: After critical information
  5. Test First: Pilot test comprehension checks

Migration Guide​

From Old System​

If upgrading from component-based comprehension checks:

  1. Questions are now per-page, not separate states
  2. Validation is automatic, no manual checking needed
  3. Data structure changed: See data model above
  4. Recording is built-in: No custom event creation needed

Backward Compatibility​

  • Instruction pages without questions work unchanged
  • Existing experiments are not affected
  • Feature is opt-in per page
  • No database schema changes required

Support​

For technical questions or issues:

  • Check test suite for examples
  • Review implementation summary
  • See code comments in source files
  • Contact development team