// Selected state:
```
---
## π Data Recording Requirements
### Trial Data Structure
Every trial must record:
```javascript
{
// Identification
trialNumber: number, // 1-105 (35 encoding + 35 immediate + 35 delayed + 35 recognition)
phase: 'encoding' | 'immediate_recall' | 'delayed_recall' | 'recognition',
trialInPhase: number, // 1-35 per phase
// Stimulus information
cueItem: string, // 'Kite', 'Spectacles', etc.
targetItem: string, // 'Pen', 'Mixer', etc.
distractorRelated: string | null, // 'Parachute' (recognition only)
distractorUnrelated: string | null, // 'Elephant' (recognition only)
// Response data
participantResponse: string | null, // Typed text or selected option
responseTime: number | null, // milliseconds from stimulus onset
responseAccuracy: 0 | 1, // 1 = correct, 0 = incorrect/timeout
responseType: 'typed' | 'selected' | 'timeout' | 'no-response',
// Recognition specific
selectedOption: 'correct' | 'related' | 'unrelated' | null,
optionPositions: array | null, // [0, 1, 2] randomized order
// Timing
stimulusOnsetTime: number, // timestamp
responseSubmitTime: number | null, // timestamp
encodingDuration: number | null, // encoding phase only
// Metadata
ageGroup: 'adolescent' | 'adult',
timestamp: number // Date.now()
}
```
### Summary Data Structure
After test completion:
```javascript
{
// Participant info
participantInfo: {
ageGroup: 'adolescent' | 'adult',
testDate: string,
testStartTime: string,
testEndTime: string,
totalDuration: number, // minutes
delayDurationActual: number // should be ~15 minutes
},
// Accuracy metrics (%)
accuracy: {
immediateRecall: number,
delayedRecall: number,
recognition: number,
overall: number
},
// Response counts
responseCounts: {
totalTrials: 105,
totalResponses: number,
totalTimeouts: number,
correctResponses: number,
incorrectResponses: number,
// By phase
immediateRecallAnswered: number,
delayedRecallAnswered: number,
recognitionAnswered: number
},
// Reaction time statistics (ms)
reactionTimes: {
immediateRecallMean: number,
delayedRecallMean: number,
recognitionMean: number,
overallMean: number,
// Additional stats
immediateRecallSD: number,
delayedRecallSD: number,
recognitionSD: number
},
// Memory consolidation
consolidation: {
absoluteChange: number, // Delayed - Immediate
percentageChange: number, // (Delayed - Immediate) / Immediate Γ 100
forgettingRate: number, // Items forgotten
itemsRetained: number // Correct in both phases
},
// Phase-specific accuracy rates
phaseAccuracy: {
immediateRecall: {
correct: number,
incorrect: number,
timeout: number,
accuracy: number // %
},
delayedRecall: {
correct: number,
incorrect: number,
timeout: number,
accuracy: number // %
},
recognition: {
correct: number,
relatedDistractorChosen: number,
unrelatedDistractorChosen: number,
timeout: number,
accuracy: number // %
}
},
// Raw trial data
trialData: [/* array of all trial records */]
}
```
### Data Export Formats
**JSON Export:**
```javascript
// Filename: vpam_results_[ageGroup]_[timestamp].json
// Full data structure with all nested objects
```
**CSV Export:**
```javascript
// Filename: vpam_results_[ageGroup]_[timestamp].csv
// Flattened trial data with column headers:
// trial_number, phase, trial_in_phase, cue_item, target_item,
// participant_response, response_time, accuracy, ...
```
---
## π§ Key Constants to Define
### vpamConfig.js
```javascript
export const VPAM_CONFIG = {
TOTAL_PAIRS: 35,
PHASES: ['encoding', 'immediate_recall', 'delayed_recall', 'recognition'],
TIMING: {
// Encoding
ENCODING_DURATION_ADOLESCENT: 3500, // ms
ENCODING_DURATION_ADULT: 2500, // ms
INTER_PAIR_INTERVAL: 300, // ms blank screen between pairs
// Recall phases
RECALL_RESPONSE_WINDOW: 15000, // ms (both age groups)
TIMEOUT_MESSAGE_DURATION: 1000, // ms
// Recognition phase
RECOGNITION_RESPONSE_WINDOW: 4000, // ms (both age groups)
TOO_LATE_MESSAGE_DURATION: 1000, // ms
// Delay interval
DELAY_DURATION: 900000, // ms (15 minutes)
},
FILLER_TASKS: [
'response_inhibition',
'cognitive_flexibility',
'stroop' // if available
],
VALIDATION: {
MAX_INPUT_LENGTH: 50,
CASE_SENSITIVE: false,
TRIM_WHITESPACE: true,
ALLOW_TYPOS: false // set to true for lenient matching
}
};
```
### vpamInstructions.js
```javascript
export const VPAM_INSTRUCTIONS = {
adolescent: {
intro: {
title: "Memory Game: Picture Pairs",
description: `In this game, you will see pictures of everyday things.
Some of these pictures will be shown together as pairs. Your task is
to pay close attention and try to remember which ones are being
presented as pairs, because later we will test your memory in different
ways. Sometimes you will have to write the missing item, and sometimes
you will choose it from a few options. Let's get started!`,
buttonText: "Let's Go!"
},
encoding: {
title: "Learning Phase",
description: `You will see two pictures appear inside a box on the screen.
Just look carefully and try to remember which pictures are being
presented as pairs. You don't need to press anything. Each pair will
disappear after a few seconds, so focus while it is on the screen.`,
buttonText: "Let's Go!"
},
immediateRecall: {
title: "Memory Test: Part 1",
description: `Great job learning the pairs! Now, one item from each pair
will appear by itself. Your task is to write the item that goes with it.
Try to answer quickly β you'll have a short time for each one.`,
reminder: `Remember: write the exact item that was paired. Don't just
write a general category, color, or size β focus on the specific item.`,
example: `Example: If you see π Apple and it was paired with πͺ Chair,
write 'Chair', not 'furniture' or 'brown chair' or 'big chair'.`,
buttonText: "Let's Go!"
},
delayTransition: {
title: "Brain Break!",
description: `Great job looking at all the pairs! Before we test your
memory, you'll play a few short brain-teaser games. These games are
like a quick workout for your mind. Once you're done, we'll bring you
back to check how many pairs you can remember!`,
buttonText: "Let's Go!"
},
delayedRecall: {
title: "Memory Test: Part 2",
description: `Welcome back to the memory game you played earlier! Remember,
you saw pairs of items and tried to learn which ones go together. Now,
again, one item from each pair will appear alone. Your task is to write
the item that goes with it. You'll have a short time for each one, so
try to answer quickly.`,
reminder: `Write the exact item that goes with the one you see. Don't just
write a general category, color, or size of the object β try to remember
the specific pair.`,
buttonText: "Let's Go!"
},
recognition: {
title: "Memory Test: Final Part",
description: `Next, you'll see one picture at the top of the screen. Below
it, you'll see three choices. Your job is to pick the picture that was
originally paired with the one on top. Be quickβyou'll only have a few
seconds for each choice.`,
buttonText: "Let's Go!"
},
completion: {
title: "Awesome Work!",
message: "That's it! You've finished this memory game. Great work!",
buttonText: "View Results"
}
},
adult: {
intro: {
title: "Visual Paired Associates Memory Test",
description: `In this task, you will see images of everyday objects.
Some of these images will be presented together as pairs. Your task is
to observe carefully and remember which items are paired together, as
your memory will be tested in different ways. You will sometimes need
to type the missing item, and other times select it from multiple options.`,
buttonText: "Begin Task"
},
encoding: {
title: "Learning Phase",
description: `You will see two items presented together inside a box. Your
task is to observe carefully and remember which items are being presented
as pairs. You don't need to press anything. Each pair will only remain on
screen briefly, so focus while it is shown.`,
buttonText: "Start Learning"
},
immediateRecall: {
title: "Immediate Recall Test",
description: `You've just seen all the pairs. Now, a single item from each
pair will appear alone. Your task is to type or write the item that was
originally paired with it. Respond quickly β each trial has a time limit.`,
reminder: `Type the exact item that was paired. Do not write a general
category, color, or size β focus on the specific item.`,
example: `Example: If the cue is ποΈ Pen and it was paired with π Elephant,
type 'Elephant', not 'animal', 'creature', or 'grey elephant'.`,
buttonText: "Begin Recall"
},
delayTransition: {
title: "Interim Activities",
description: `Well doneβyou've completed the first part. Next, you'll do a
few short thinking activities designed to keep your mind active while your
memory continues to work in the background. After that, we'll return to
test how well you remember the pairs.`,
buttonText: "Continue"
},
delayedRecall: {
title: "Delayed Recall Test",
description: `We're returning to the memory task you did before. Earlier,
you learned pairs of items and tried to remember which ones belonged
together. In this part, again, a single item from each pair will appear
alone. Your task is to type or write the item that was originally paired
with it.`,
reminder: `Type the exact item that was paired with the cue you see. Do not
write a general category, color, or size of the object β focus on the
specific paired item.`,
buttonText: "Begin Test"
},
recognition: {
title: "Recognition Test",
description: `In this final part, you'll again see one item at the top of
the screen. Below it, three choices will be shown. Select the item that
was originally paired with the one on top. Be readyβeach trial only lasts
a few seconds.`,
buttonText: "Begin Recognition"
},
completion: {
title: "Task Complete",
message: "This concludes the memory task. Thank you for your participation.",
buttonText: "View Results"
}
}
};
```
### vpamPairs.js
```javascript
// EXACT sequences from PDF
export const VPAM_PAIRS = {
adolescent: [
{ id: 1, first: 'Pen', second: 'Kite', firstImg: 'pen.png', secondImg: 'kite.png' },
{ id: 2, first: 'Butterfly', second: 'Spoon', firstImg: 'butterfly.png', secondImg: 'spoon.png' },
// ... all 35 pairs
],
adult: [
{ id: 1, first: 'Mixer', second: 'Spectacles', firstImg: 'mixer.png', secondImg: 'spectacles.png' },
{ id: 2, first: 'Mobile Phone', second: 'Water Tap', firstImg: 'mobile-phone.png', secondImg: 'water-tap.png' },
// ... all 35 pairs
]
};
// Immediate recall cues (show SECOND, ask for FIRST)
export const IMMEDIATE_RECALL_CUES = {
adolescent: [
{ id: 1, cue: 'Kite', cueImg: 'kite.png', correctAnswer: 'Pen', correctImg: 'pen.png' },
{ id: 2, cue: 'Butterfly', cueImg: 'butterfly.png', correctAnswer: 'Spoon', correctImg: 'spoon.png' },
// ... all 35 trials
],
adult: [
{ id: 1, cue: 'Spectacles', cueImg: 'spectacles.png', correctAnswer: 'Mixer', correctImg: 'mixer.png' },
// ... all 35 trials
]
};
// Delayed recall cues (show FIRST, ask for SECOND)
export const DELAYED_RECALL_CUES = {
adolescent: [
{ id: 1, cue: 'Pen', cueImg: 'pen.png', correctAnswer: 'Kite', correctImg: 'kite.png' },
// ... all 35 trials
],
adult: [
{ id: 1, cue: 'Mixer', cueImg: 'mixer.png', correctAnswer: 'Spectacles', correctImg: 'spectacles.png' },
// ... all 35 trials
]
};
// Recognition trials with distractors
export const RECOGNITION_TRIALS = {
adolescent: [
{
id: 1,
cue: 'Pen',
cueImg: 'pen.png',
correctTarget: 'Kite',
correctImg: 'kite.png',
relatedDistractor: 'Parachute',
relatedImg: 'parachute.png',
unrelatedDistractor: 'Elephant',
unrelatedImg: 'elephant.png'
},
// ... all 35 trials with distractors
],
adult: [
{
id: 1,
cue: 'Mixer',
cueImg: 'mixer.png',
correctTarget: 'Spectacles',
correctImg: 'spectacles.png',
relatedDistractor: 'Sunglasses',
relatedImg: 'sunglasses.png',
unrelatedDistractor: 'Apple',
unrelatedImg: 'apple.png'
},
// ... all 35 trials
]
};
```
---
## π File Structure Requirements
```
src/
βββ components/
β βββ screens/
β β βββ VPAM/
β β β βββ IntroScreen.jsx
β β β βββ EncodingInstructionScreen.jsx
β β β βββ EncodingScreen.jsx
β β β βββ ImmediateRecallInstructionScreen.jsx
β β β βββ ImmediateRecallScreen.jsx
β β β βββ DelayIntervalScreen.jsx
β β β βββ DelayedRecallInstructionScreen.jsx
β β β βββ DelayedRecallScreen.jsx
β β β βββ RecognitionInstructionScreen.jsx
β β β βββ RecognitionScreen.jsx
β β β βββ ResultsScreen.jsx
β β β βββ VisualPairedAssociatesTest.jsx (main orchestrator)
β β β
β β βββ CFT/ # Existing
β β βββ RIT/ # Existing
β β βββ TestSelectionScreen.jsx
β β
β βββ shared/
β βββ VPAM/
β βββ PairDisplay.jsx
β βββ RecallInput.jsx
β βββ RecognitionOptions.jsx
β βββ TrialCounter.jsx
β βββ Timer.jsx
β βββ MetricsCard.jsx
β βββ PhaseComparison.jsx
β
βββ hooks/
β βββ vpam/
β βββ useEncodingScreen.js
β βββ useRecallScreen.js
β βββ useRecognitionScreen.js
β βββ useDelayInterval.js
β
βββ utils/
β βββ vpamTimer.js
β βββ vpamValidation.js
β βββ vpamCalculations.js
β βββ vpamDataExport.js
β βββ vpamAnimations.js
β
βββ constants/
β βββ vpamConfig.js
β βββ vpamInstructions.js
β βββ vpamPairs.js
β βββ testsConfig.js (update to include VPAM)
β
βββ public/
βββ images/
βββ vpam/
βββ adolescent/
β βββ pen.png
β βββ kite.png
β βββ ... (70 images)
βββ adult/
β βββ mixer.png
β βββ spectacles.png
β βββ ... (70 images)
βββ distractors/
βββ parachute.png
βββ knife.png
βββ ... (70 images)
```
---
## π« Important Constraints
### NEVER Do This
1. β Change pair order (must follow PDF exactly)
2. β Show feedback on correctness during test
3. β Allow skipping trials
4. β Let participants replay encoding phase
5. β Accept category answers (require specific items)
6. β Use emojis as actual stimuli (use real images)
7. β Skip or abbreviate delay interval
8. β Randomize recall cue order (follow PDF)
9. β Show scores during test (only at end)
10. β Use localStorage or sessionStorage
### ALWAYS Do This
1. β Follow exact pair sequences from PDF
2. β Use age-specific timing (3.5s vs 2.5s encoding)
3. β Show "Time is up!" / "Too Late" on timeout
4. β Validate exact item names only
5. β Randomize recognition option positions
6. β Track all metrics silently during test
7. β Implement 15-minute delay with filler tasks
8. β Use opposite cues for delayed vs immediate recall
9. β Display clear, high-contrast images
10. β Export complete data at end
---
## π§ Development Workflow
### Step-by-Step Process
1. **Current Phase:** Developer specifies which screen to create
2. **AI Creates:** Complete screen with all required files
3. **AI Shows:** Code for that screen only
4. **AI Asks:** "Screen [Name] complete. Proceed to [NextScreen]?"
5. **Developer:** Approves or requests changes
6. **Repeat:** Until all 11 screens are complete
7. **Integration:** Connect VPAM to TestSelectionScreen
8. **Testing:** Verify timing, data collection, export
### Code Quality Standards
- Use functional components with hooks
- Separate concerns (logic in hooks, UI in components)
- Add JSDoc comments for complex functions
- Follow consistent naming conventions (camelCase)
- Use Tailwind for ALL styling (no inline styles)
- Handle edge cases (timeout, rapid input, empty responses)
- Create reusable components (don't duplicate code)
- Optimize re-renders with useMemo/useCallback
- Implement proper cleanup in useEffect
### State Management Pattern
```javascript
// Main VPAM Test State (Context or prop drilling)
const [testState, setTestState] = useState({
currentScreen: 'intro', // 'intro', 'encoding', 'immediateRecall', etc.
ageGroup: null, // 'adolescent' or 'adult'
phase: null, // 'encoding', 'immediate_recall', 'delayed_recall', 'recognition'
currentTrial: 0, // 0-34 per phase
trialData: [], // All recorded trials
startTime: null,
delayStartTime: null,
delayEndTime: null
});
// Per-Screen Local State
const [screenState, setScreenState] = useState({
showStimulus: false,
currentPair: null,
userResponse: '',
isSubmitting: false,
showTimeout: false,
reactionStartTime: null
});
```
---
## π Animation Specifications
### Framer Motion Animations (utils/vpamAnimations.js)
```javascript
// Minimal, non-distracting animations
export const fadeIn = {
initial: { opacity: 0 },
animate: { opacity: 1 },
transition: { duration: 0.3 }
};
export const slideUp = {
initial: { y: 20, opacity: 0 },
animate: { y: 0, opacity: 1 },
transition: { duration: 0.4, ease: 'easeOut' }
};
export const pairTransition = {
initial: { opacity: 0, scale: 0.95 },
animate: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0.95 },
transition: { duration: 0.2 }
};
export const timeoutPulse = {
initial: { scale: 1 },
animate: {
scale: [1, 1.1, 1],
opacity: [1, 0.8, 0]
},
transition: { duration: 1 }
};
// NO complex animations during encoding/recall
// Keep cognitive load minimal
```
---
## π― Timing Flow (CRITICAL)
### Encoding Phase Timing
```javascript
// For each of 35 pairs:
1. Display pair immediately (no fade-in delay)
- Both images visible simultaneously
- Centered in white box
2. Hold for exposure duration
- Adolescent: 3.5 seconds
- Adult: 2.5 seconds
- No user interaction
3. Brief blank screen (0.3 seconds)
- Inter-pair interval
- Prevents visual overlap
4. Next pair appears
- Repeat steps 1-3
5. After pair 35 β transition to Immediate Recall
- No delay, automatic progression
// Total encoding time:
// Adolescents: (3.5s + 0.3s) Γ 35 = 133 seconds (~2.2 min)
// Adults: (2.5s + 0.3s) Γ 35 = 98 seconds (~1.6 min)
```
### Recall Phase Timing
```javascript
// For each of 35 trials:
1. Display cue image immediately
- Start reaction timer
- Show text input field (empty)
2. Wait for response OR timeout (15 seconds)
- Listen for:
* Text input changes
* Submit button click
* Enter key press
* Timer expiration
3. On Submit (before timeout):
- Record response text
- Calculate reaction time
- Validate accuracy
- Clear input field
- Proceed to next trial immediately
4. On Timeout (no submit):
- Show "Time is up!" (1 second)
- Record as no response (accuracy = 0)
- Clear input field
- Proceed to next trial
5. Brief blank screen (optional, 0.3s)
- Or immediately show next cue
// Total recall time (worst case - all timeouts):
// (15s + 1s) Γ 35 = 560 seconds (~9.3 min per phase)
```
### Recognition Phase Timing
```javascript
// For each of 35 trials:
1. Display cue at top
2. Display 3 randomized options below
3. Start 4-second timer
4. Wait for selection OR timeout
5. On Selection (before timeout):
- Highlight selected option briefly (0.2s)
- Record choice and RT
- Validate accuracy
- Proceed to next trial immediately
6. On Timeout (no selection):
- Show "Too Late" (1 second)
- Record as no response (accuracy = 0)
- Proceed to next trial
// Total recognition time (worst case):
// (4s + 1s) Γ 35 = 175 seconds (~2.9 min)
```
### Delay Interval Timing
```javascript
// 15-minute delay with filler tasks
1. Show transition message (5 seconds)
2. Launch Response Inhibition Test (~7 min)
3. Launch Cognitive Flexibility Test (~6 min)
4. Launch Stroop Test (~2 min) [if available]
5. Auto-trigger Delayed Recall at 15:00
// Critical: Track elapsed time precisely
// Use Date.now() or performance.now()
// Don't rely on setTimeout for long durations
```
---
## π Scoring Logic (CRITICAL)
### Response Validation
```javascript
/**
* Validates user response against correct answer
* @param {string} userInput - Raw user input
* @param {string} correctAnswer - Expected answer
* @returns {boolean} - True if correct
*/
function validateResponse(userInput, correctAnswer) {
// Normalize both strings
const normalized = userInput
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s]/g, ''); // Remove punctuation
const correct = correctAnswer
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s]/g, '');
// Exact match required
if (normalized === correct) {
return true;
}
// Optional: Handle common variations
// "lady's finger" vs "ladys finger" vs "ladies finger"
const variations = generateVariations(correct);
return variations.includes(normalized);
}
/**
* Generate acceptable variations for hyphenated/possessive words
*/
function generateVariations(word) {
return [
word,
word.replace(/[\s-']/g, ''), // Remove spaces, hyphens, apostrophes
word.replace(/'/g, ''), // Remove apostrophes only
word.replace(/-/g, ' '), // Replace hyphens with spaces
word.replace(/\s/g, '') // Remove all spaces
];
}
```
### Accuracy Calculation
```javascript
/**
* Calculate accuracy metrics for each phase
*/
function calculateAccuracy(trialData, phase) {
const phaseTrials = trialData.filter(t => t.phase === phase);
const totalTrials = phaseTrials.length;
const answered = phaseTrials.filter(t => t.responseType !== 'timeout').length;
const correct = phaseTrials.filter(t => t.responseAccuracy === 1).length;
return {
totalTrials,
answered,
notAnswered: totalTrials - answered,
correct,
incorrect: answered - correct,
accuracyRate: totalTrials > 0 ? (correct / totalTrials) * 100 : 0,
answeredAccuracyRate: answered > 0 ? (correct / answered) * 100 : 0
};
}
/**
* Calculate overall task metrics
*/
function calculateOverallMetrics(trialData) {
const immediateRecall = calculateAccuracy(trialData, 'immediate_recall');
const delayedRecall = calculateAccuracy(trialData, 'delayed_recall');
const recognition = calculateAccuracy(trialData, 'recognition');
// Consolidation slope
const absoluteChange = delayedRecall.accuracyRate - immediateRecall.accuracyRate;
const percentageChange = immediateRecall.accuracyRate > 0
? (absoluteChange / immediateRecall.accuracyRate) * 100
: 0;
// Overall accuracy
const totalCorrect = immediateRecall.correct + delayedRecall.correct + recognition.correct;
const totalTrials = immediateRecall.totalTrials + delayedRecall.totalTrials + recognition.totalTrials;
const overallAccuracy = totalTrials > 0 ? (totalCorrect / totalTrials) * 100 : 0;
return {
immediateRecall,
delayedRecall,
recognition,
consolidation: {
absoluteChange,
percentageChange,
forgettingRate: Math.max(0, -absoluteChange), // Only if negative
itemsRetained: Math.min(immediateRecall.correct, delayedRecall.correct)
},
overall: {
accuracy: overallAccuracy,
totalCorrect,
totalTrials,
totalAnswered: immediateRecall.answered + delayedRecall.answered + recognition.answered
}
};
}
```
### Reaction Time Calculation
```javascript
/**
* Calculate reaction time statistics
*/
function calculateReactionTimes(trialData, phase) {
const phaseTrials = trialData
.filter(t => t.phase === phase && t.responseTime !== null);
if (phaseTrials.length === 0) {
return { mean: null, median: null, sd: null, min: null, max: null };
}
const times = phaseTrials.map(t => t.responseTime);
const mean = times.reduce((a, b) => a + b, 0) / times.length;
// Standard deviation
const variance = times.reduce((sum, time) => {
return sum + Math.pow(time - mean, 2);
}, 0) / times.length;
const sd = Math.sqrt(variance);
// Median
const sorted = [...times].sort((a, b) => a - b);
const median = sorted.length % 2 === 0
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
: sorted[Math.floor(sorted.length / 2)];
return {
mean: Math.round(mean),
median: Math.round(median),
sd: Math.round(sd),
min: Math.min(...times),
max: Math.max(...times),
count: times.length
};
}
```
---
## π¨ Component Examples
### PairDisplay Component
```javascript
// components/shared/VPAM/PairDisplay.jsx
import React from 'react';
import { motion } from 'framer-motion';
/**
* Displays a pair of images during encoding phase
*/
export default function PairDisplay({ firstImage, secondImage, ageGroup }) {
const containerBg = ageGroup === 'adolescent'
? 'bg-gradient-to-br from-purple-100 to-blue-100'
: 'bg-gray-50';
return (
);
}
```
---
## π Data Export Implementation
### vpamDataExport.js
```javascript
/**
* Export VPAM test results in multiple formats
*/
/**
* Export as JSON file
*/
export function exportJSON(data, ageGroup) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `vpam_results_${ageGroup}_${timestamp}.json`;
const jsonStr = JSON.stringify(data, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
downloadBlob(blob, filename);
}
/**
* Export as CSV file
*/
export function exportCSV(data, ageGroup) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `vpam_results_${ageGroup}_${timestamp}.csv`;
// CSV Headers
const headers = [
'Trial_Number',
'Phase',
'Trial_In_Phase',
'Cue_Item',
'Target_Item',
'Participant_Response',
'Response_Time_ms',
'Accuracy',
'Response_Type',
'Selected_Option',
'Timestamp'
];
// Convert trial data to CSV rows
const rows = data.trialData.map(trial => [
trial.trialNumber,
trial.phase,
trial.trialInPhase,
trial.cueItem || 'N/A',
trial.targetItem,
trial.participantResponse || '',
trial.responseTime || '',
trial.responseAccuracy,
trial.responseType,
trial.selectedOption || 'N/A',
trial.timestamp
]);
// Combine headers and rows
const csvContent = [
headers.join(','),
...rows.map(row => row.map(escapeCSV).join(','))
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
downloadBlob(blob, filename);
}
/**
* Escape CSV values
*/
function escapeCSV(value) {
if (value === null || value === undefined) return '';
const str = String(value);
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}
/**
* Trigger browser download
*/
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* Export summary report (human-readable)
*/
export function exportSummaryReport(data, ageGroup) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `vpam_summary_${ageGroup}_${timestamp}.txt`;
const report = `
VISUAL PAIRED ASSOCIATES MEMORY TEST - SUMMARY REPORT
====================================================
Test Information:
-----------------
Age Group: ${ageGroup === 'adolescent' ? 'Adolescent (14-18)' : 'Adult (18-22)'}
Test Date: ${data.participantInfo.testDate}
Total Duration: ${data.participantInfo.totalDuration} minutes
Delay Duration: ${data.participantInfo.delayDurationActual} minutes
Overall Performance:
--------------------
Total Trials: ${data.responseCounts.totalTrials}
Total Responses: ${data.responseCounts.totalResponses}
Correct Responses: ${data.responseCounts.correctResponses}
Overall Accuracy: ${data.accuracy.overall.toFixed(2)}%
Phase-Specific Accuracy:
------------------------
Immediate Recall: ${data.accuracy.immediateRecall.toFixed(2)}%
Delayed Recall: ${data.accuracy.delayedRecall.toFixed(2)}%
Recognition: ${data.accuracy.recognition.toFixed(2)}%
Reaction Times (Mean):
----------------------
Immediate Recall: ${data.reactionTimes.immediateRecallMean}ms
Delayed Recall: ${data.reactionTimes.delayedRecallMean}ms
Recognition: ${data.reactionTimes.recognitionMean}ms
Memory Consolidation:
---------------------
Absolute Change: ${data.consolidation.absoluteChange.toFixed(2)}%
Percentage Change: ${data.consolidation.percentageChange.toFixed(2)}%
Items Retained: ${data.consolidation.itemsRetained} / 35
====================================================
`.trim();
const blob = new Blob([report], { type: 'text/plain' });
downloadBlob(blob, filename);
}
```
---
## π Testing Checklist
### Before Declaring Complete
- [ ] **All 11 screens functional** and transition properly
- [ ] **Timing accuracy** verified with stopwatch
- [ ] Encoding: 3.5s (adolescent) / 2.5s (adult) per pair
- [ ] Recall: 15s response window
- [ ] Recognition: 4s response window
- [ ] Delay: Exactly 15 minutes
- [ ] **Image loading** works for all 210+ images
- [ ] **Response validation** correctly identifies exact matches
- [ ] **Data recording** captures all required fields
- [ ] **Timeout messages** display correctly
- [ ] **Recognition randomization** works (options in different positions)
- [ ] **Calculations correct** for all metrics
- [ ] **Export functions** produce valid JSON/CSV files
- [ ] **Age-specific content** displays properly for both groups
- [ ] **Responsive design** works on mobile (345px) to desktop
- [ ] **No console errors** or warnings
- [ ] **Memory leaks** prevented (proper cleanup in useEffect)
- [ ] **Keyboard navigation** works (Enter to submit, Tab through options)
- [ ] **Accessibility** basics (alt text, ARIA labels, focus indicators)
---
## π Integration with Existing Project
### Update TestSelectionScreen
```javascript
// components/screens/TestSelectionScreen.jsx
const tests = [
{
id: 'rit',
name: 'Response Inhibition Task',
description: 'Go/No-Go test measuring impulse control',
component: ResponseInhibitionTest,
estimatedTime: '8-10 minutes'
},
{
id: 'cft',
name: 'Cognitive Flexibility Test',
description: 'Tests ability to switch between rules',
component: CognitiveFlexibilityTest,
estimatedTime: '10-12 minutes'
},
{
id: 'vpam', // NEW
name: 'Visual Paired Associates Memory',
description: 'Tests associative memory and retention',
component: VisualPairedAssociatesTest,
estimatedTime: '30-35 minutes'
}
];
```
### Update testsConfig.js
```javascript
// constants/testsConfig.js
export const TESTS_CONFIG = {
rit: {
name: 'Response Inhibition Task',
shortName: 'RIT',
route: '/rit',
color: 'blue'
},
cft: {
name: 'Cognitive Flexibility Test',
shortName: 'CFT',
route: '/cft',
color: 'purple'
},
vpam: { // NEW
name: 'Visual Paired Associates Memory',
shortName: 'VPAM',
route: '/vpam',
color: 'green'
}
};
```
---
## π Final Notes
### Research Validity Reminders
- This is a **standardized memory test** - ANY deviation from specifications invalidates research data
- Timing must be **precise to the millisecond** where possible
- Trial order is **predetermined, not randomized** - follow PDF exactly
- Response validation must be **strict** (exact item names only)
- Data export must include **ALL metrics** for statistical analysis
### Performance Optimization
- Preload ALL images before starting encoding phase
- Use `React.memo()` for heavy components
- Debounce text input validation (but record raw input)
- Clear timers and intervals properly to prevent memory leaks
- Consider using Web Workers for data calculations if needed
### Accessibility Considerations
- Keyboard navigation for all inputs
- Screen reader compatibility (ARIA labels)
- High contrast mode support
- Focus indicators clearly visible
- Text size adjustable (relative units)
### Future Enhancements (Optional)
- Practice trials for recall/recognition phases
- Audio cues (optional mode)
- Multi-language support
- Adaptive difficulty (not standard, research variant)
- Real-time data sync to server
- Progress save/resume functionality
---
## β Development Completion Criteria
The VPAM test is considered complete when:
1. β All 11 screens implemented and functional
2. β All timing specifications met precisely
3. β All 210+ images loaded and displayed correctly
4. β Data recording captures all required metrics
5. β Export functions produce valid, complete data files
6. β Age-specific content displays correctly
7. β Responsive design works across all screen sizes
8. β No bugs, errors, or console warnings
9. β Code is clean, commented, and maintainable
10. β Integration with existing project seamless
11. β Testing checklist 100% verified
12. β Documentation updated
---
**IMPORTANT REMINDERS:**
- **ONE SCREEN AT A TIME** - wait for:**
- `components/screens/VPAM/IntroScreen.jsx`
- `constants/vpamInstructions.js`
**Content:**
```javascript
// Generic welcome message (shown to all)
"In this game, you will see pictures of everyday things. Some of these
pictures will be shown together as pairs. Your task is to pay close
attention and try to remember which ones are being presented as pairs,
because later we will test your memory in different ways. Sometimes you
will have to write the missing item, and sometimes you will choose it
from a few options. Let's get started!"
```
**State to initialize:**
- `ageGroup` (null β 'adolescent' or 'adult')
- `currentScreen` ('intro')
---
#### Screen 2: EncodingInstructionScreen
**Purpose:** Explain the learning phase before showing pairs
**Components needed:**
- Instruction text display
- Example pair visualization
- Duration explanation
- "Let's Go!" button
**Required files:**
- `components/screens/VPAM/EncodingInstructionScreen.jsx`
**Instructions:**
Adolescents:
```
"You will see two pictures appear inside a box on the screen. Just look
carefully and try to remember which pictures are being presented as pairs.
You don't need to press anything. Each pair will disappear after a few
seconds, so focus while it is on the screen."
```
Adults:
```
"You will see two items presented together inside a box. Your task is to
observe carefully and remember which items are being presented as pairs.
You don't need to press anything. Each pair will only remain on screen
briefly, so focus while it is shown."
```
---
#### Screen 3: EncodingScreen
**Purpose:** Display all 35 pairs for learning
**Components needed:**
- White box container (centered)
- Two image displays (side by side)
- Automatic timer
- Progress indicator (optional: "Pair 12 of 35")
- Auto-advance logic
**Required files:**
- `components/screens/VPAM/EncodingScreen.jsx`
- `components/shared/VPAM/PairDisplay.jsx`
- `hooks/vpam/useEncodingScreen.js`
- `utils/vpamTimer.js`
**Critical logic:**
```javascript
// For each pair in sequence:
1. Display both images together
2. Start timer (3.5s adolescent / 2.5s adult)
3. Wait for timer completion (NO user input)
4. Clear screen briefly
5. Load next pair
6. After pair 35 β transition to Immediate Recall
```
**Timing precision:**
- Use `setTimeout` or `setInterval`
- Track exposure time per pair
- Record timestamp for each pair shown
---
#### Screen 4: ImmediateRecallInstructionScreen
**Purpose:** Explain the typing recall task
**Components needed:**
- Instruction text display
- Example with visual
- Response format explanation
- "Let's Go!" button
**Required files:**
- `components/screens/VPAM/ImmediateRecallInstructionScreen.jsx`
**Instructions:**
Adolescents:
```
"Great job learning the pairs! Now, one item from each pair will appear
by itself. Your task is to write the item that goes with it. Try to answer
quickly β you'll have a short time for each one.
Remember: write the exact item that was paired. Don't just write a general
category, color, or size β focus on the specific item.
Example: If you see π Apple and it was paired with πͺ Chair, write 'Chair',
not 'furniture' or 'brown chair' or 'big chair'."
```
Adults:
```
"You've just seen all the pairs. Now, a single item from each pair will
appear alone. Your task is to type or write the item that was originally
paired with it. Respond quickly β each trial has a time limit.
Type the exact item that was paired. Do not write a general category, color,
or size β focus on the specific item.
Example: If the cue is ποΈ Pen and it was paired with π Elephant, type
'Elephant', not 'animal', 'creature', or 'grey elephant'."
```
---
#### Screen 5: ImmediateRecallScreen
**Purpose:** Test immediate memory with typed responses
**Components needed:**
- Cue image display (white box, centered)
- Text input field (below cue)
- Submit button
- Timer countdown (optional visual)
- "Time is up!" message overlay
- Trial counter
**Required files:**
- `components/screens/VPAM/ImmediateRecallScreen.jsx`
- `components/shared/VPAM/RecallInput.jsx`
- `hooks/vpam/useRecallScreen.js`
**Critical logic:**
```javascript
// For each trial:
1. Display cue image
2. Start 15-second timer
3. Show text input field
4. Listen for:
- Submit button click
- Enter key press
- Timer expiration
5. Validate response against correct answer
6. Record:
- Response text
- Reaction time (ms from cue to submit)
- Accuracy (1/0)
7. If timeout β show "Time is up!" for 1s
8. Clear input and load next trial
9. After trial 35 β transition to Delay Interval
```
**Response validation:**
```javascript
function validateResponse(userInput, correctAnswer) {
// Normalize both strings
const normalized = userInput.toLowerCase().trim();
const correct = correctAnswer.toLowerCase().trim();
// Exact match required
return normalized === correct;
// Optional: Allow minor typos (Levenshtein distance β€ 1)
// But be careful - too lenient affects data validity
}
```
---
#### Screen 6: DelayIntervalScreen
**Purpose:** Transition message and launch filler tasks
**Components needed:**
- Transition message display
- Automatic task chaining
- Timer tracking (15 minutes total)
- Progress indicator (optional)
**Required files:**
- `components/screens/VPAM/DelayIntervalScreen.jsx`
- `hooks/vpam/useDelayInterval.js`
**Transition messages:**
Adolescents:
```
"Great job looking at all the pairs! Before we test your memory, you'll
play a few short brain-teaser games. These games are like a quick workout
for your mind. Once you're done, we'll bring you back to check how many
pairs you can remember!"
```
Adults:
```
"Well doneβyou've completed the first part. Next, you'll do a few short
thinking activities designed to keep your mind active while your memory
continues to work in the background. After that, we'll return to test how
well you remember the pairs."
```
**Filler task sequence:**
```javascript
// Import and chain existing components:
1. (Go/No-Go)
2. (CFT)
3. (Attention - if available)
// Track elapsed time
// At 15:00 β auto-launch Delayed Recall
```
---
#### Screen 7: DelayedRecallInstructionScreen
**Purpose:** Re-explain recall task after delay
**Components needed:**
- "Welcome back" message
- Reminder of task rules
- "Let's Go!" button
**Required files