Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion task-launcher/cypress/e2e/math.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ const math_url = 'http://localhost:8080/?task=egma-math';
describe('test math', () => {
it('visits math and plays game', () => {
cy.visit(math_url);
testAfc('alt', '.secondary');
testAfc('alt', '.secondary, .image-medium, .primary');
});
});
153 changes: 82 additions & 71 deletions task-launcher/src/tasks/math/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
taskFinished,
practiceTransition,
feedback,
setupDownex,
} from '../shared/trials';
import { getLayoutConfig } from './helpers/config';
import { taskStore } from '../../taskStore';
Expand Down Expand Up @@ -56,23 +57,17 @@ export default function buildMathTimeline(config: Record<string, any>, mediaAsse
const timeline = [preloadTrials, initialTimeline];

let corpus: StimulusType[] = taskStore().corpora.stimulus;
const downexCorpus: StimulusType[] = taskStore().corpora.downex;
const translations: Record<string, string> = taskStore().translations;
const validationErrorMap: Record<string, string> = {};

const { runCat, heavyInstructions } = taskStore();

// block 3 is only for younger kids
if (!runCat) {
corpus = corpus.filter((trial) => {
return heavyInstructions ? trial.block_index === 3 : trial.block_index !== 3;
});
}

taskStore('totalTrials', corpus.length);

const layoutConfigMap: Record<string, LayoutConfigType> = {};
let i = 0;
for (const c of corpus) {
for (const c of [...downexCorpus, ...corpus]) {
const { itemConfig, errorMessages } = getLayoutConfig(c, translations, mediaAssets, i);
layoutConfigMap[c.itemId] = itemConfig;
if (errorMessages.length) {
Expand Down Expand Up @@ -217,79 +212,96 @@ export default function buildMathTimeline(config: Record<string, any>, mediaAsse

if (runCat) {
// puts the CAT portion of the corpus into taskStore and removes instructions
const fullCorpus = prepareCorpus(corpus);
// until younger-kid version of math is implemented, combine heavy/light instructions
const allInstructionPractice = fullCorpus.ipLight.concat(fullCorpus.ipHeavy);
const instructions = allInstructionPractice.filter((trial) => trial.trialType == 'instructions');

// filter practice trials to only include appropriate trial types if downward extension
const excludedDownexPracticeTypes = [
'Addition',
'Number Comparison',
'Number Identification',
'Counting',
'Counting AFC',
];

const practice = allInstructionPractice.filter((trial) => {
return trial.assessmentStage == 'practice_response' && !(trial.block_index === 3 && excludedDownexPracticeTypes.includes(trial.trialType));
});
const allCorpusParts = prepareCorpus(corpus, true, downexCorpus);
const olderKidInstructionPractice: StimulusType[] = allCorpusParts.ipLight;
const olderKidInstructions: StimulusType[] = olderKidInstructionPractice.filter((trial: StimulusType) => trial.trialType == 'instructions');
let olderKidPractice: StimulusType[] = olderKidInstructionPractice.filter((trial: StimulusType) => trial.assessmentStage == 'practice_response');

let allBlocks: StimulusType[][] = prepareMultiBlockCat(taskStore().corpora.stimulus);
let downexBlock = allBlocks[3];

// remove items from first block that are already in subsequent blocks
const nonDownexIds: string[] = [];
allBlocks
.slice(0, -1)
.flat()
.map((trial) => nonDownexIds.push(trial.itemId as string));

downexBlock = downexBlock.filter((trial: StimulusType) => {
return !nonDownexIds.includes(trial.itemId as string);
});
let olderKidBlocks: StimulusType[][] = prepareMultiBlockCat(taskStore().corpora.stimulus);
taskStore('corpora', { stimulus: olderKidBlocks, downex: taskStore().corpora.downex });
taskStore('totalTestTrials', 0); // add to this while building out each block

// move downex block to the beginning
allBlocks = [downexBlock, ...allBlocks.slice(0, 3)];
// don't repeat instructions
const usedIds: string[] = [];

const newCorpora = {
practice: taskStore().corpora.practice,
stimulus: allBlocks,
};
taskStore('corpora', newCorpora); // puts all blocks into taskStore
taskStore('totalTestTrials', 0); // add to this while building out each block
// first add downex trials to the timeline
if (heavyInstructions) {
const downexInstructionPractice: StimulusType[] = allCorpusParts.ipHeavy;
const downexInstructions: StimulusType[] = downexInstructionPractice.filter((trial) => trial.trialType == 'instructions');
let downexPractice: StimulusType[] = downexInstructionPractice.filter((trial) => trial.assessmentStage == 'practice_response');

let downexBlock: StimulusType[] = allCorpusParts.downexCat;

// remove items from first block that are already in subsequent blocks
const nonDownexIds: string[] = [];
olderKidBlocks.flat().map((trial) => nonDownexIds.push(trial.itemId as string));

downexBlock = downexBlock.filter((trial: StimulusType) => {
return !nonDownexIds.includes(trial.itemId as string);
});

// filter practice trials to only include appropriate trial types if downward extension
const excludedDownexPracticeTypes = [
'Addition',
'Number Comparison',
'Number Identification',
'Counting',
'Counting AFC',
];

downexPractice = downexPractice.filter(
(trial) => !excludedDownexPracticeTypes.includes(trial.trialType),
);

const allowedIds = ['math-instructions1-heavy', 'math-intro1-heavy'];

downexInstructions.forEach((trial) => {
if (allowedIds.includes(trial.itemId)) {
timeline.push({ ...fixationOnly, stimulus: '' });
timeline.push(afcStimulusTemplate(trialConfig, trial));
}
});

downexPractice.forEach((trial) => {
timeline.push({ ...fixationOnly, stimulus: '' });
timeline.push(stimulusBlock(trial));
});

timeline.push(practiceTransition());

const numOfBlocks = allBlocks.length;
const trialProportionsPerBlock = [2, 2, 3, 3]; // divide by these numbers to get trials per block
for (let i = heavyInstructions ? 0 : 1; i < numOfBlocks; i++) {
// skip first block if not heavyInstructions
const numOfTrials = Math.floor(downexBlock.length / 2);
taskStore.transact('totalTestTrials', (oldVal: number) => (oldVal += numOfTrials));
for (let j = 0; j < numOfTrials; j++) {
timeline.push({ ...setupDownex, stimulus: '' }); // select only from the current block
timeline.push(stimulusBlock());
}
}

const numOfBlocks = olderKidBlocks.length;
const trialProportionsPerBlock = [2, 3, 3]; // divide by these numbers to get trials per block
for (let i = 0; i < numOfBlocks; i++) {
// push in block-specific instructions
let usedIDs: string[] = [];
const blockInstructions = instructions.filter((trial) => {
const trialBlock = trial.block_index === 3 ? 0 : Number(trial.block_index) + 1;
const blockInstructions = olderKidInstructions.filter((trial) => {
let allowedIDs: string[]; // CAT only uses particular instructions from corpus

switch (i) {
case 0:
allowedIDs = ['math-instructions1-heavy', 'math-intro1-heavy'];
break;
case 1:
allowedIDs = heavyInstructions ? ['math-intro2'] : ['math-instructions1', 'math-intro1'];
break;
case 2:
case 1:
allowedIDs = ['math-intro2'];
break;
case 3:
case 2:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think at some point we can make this generic enough where this is config driven and hard coded like right now? Ideally, we will be able to switch the blocks around, along with filtering of ids etc via some external config which then this code reads and builds the task. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind've a special case, because the math cat is organized into 3 blocks but the non-CAT version has more blocks. Each block has a generic instruction at the beginning saying "Here's a new kind of question", but because the corpus has one for each block in the non-CAT version, the CAT has to filter some of them out since it has fewer blocks. We could maybe add a corpus column cat_only so that it isn't hard coded like this? I'm not sure what the best solution is

allowedIDs = ['math-intro2', 'number-line-instruct1'];
break;
default:
allowedIDs = [];
}

const include = trialBlock === i && allowedIDs.includes(trial.itemId) && !usedIDs.includes(trial.itemId);
const include = allowedIDs.includes(trial.itemId) && !usedIds.includes(trial.itemId);

if (include) {
usedIDs.push(trial.itemId);
usedIds.push(trial.itemId);
}

return include;
Expand All @@ -301,10 +313,10 @@ export default function buildMathTimeline(config: Record<string, any>, mediaAsse
});

// push in block-specific practice trials
const blockPractice = practice.filter((trial) => {
const trialBlock = trial.block_index === 3 ? 0 : Number(trial.block_index) + 1;
return trialBlock === i;
const blockPractice = olderKidPractice.filter((trial) => {
return i === Number(trial.block_index);
});

blockPractice.forEach((trial) => {
timeline.push({ ...fixationOnly, stimulus: '' });
timeline.push(stimulusBlock(trial));
Expand All @@ -315,28 +327,27 @@ export default function buildMathTimeline(config: Record<string, any>, mediaAsse
});

// final slider block
if (i === 3) {
if (i === 2) {
timeline.push(repeatSliderPracticeBlock());
}

// practice transition screen
timeline.push(practiceTransition());

// push in random items at start of first block (after practice trials)
if (i === 1) {
fullCorpus.start.forEach((trial) => timeline.push(stimulusBlock(trial)));
if (i === 0) {
allCorpusParts.start.forEach((trial) => timeline.push(stimulusBlock(trial)));
}

const numOfTrials = Math.floor(allBlocks[i].length / trialProportionsPerBlock[i]);
const numOfTrials = Math.floor(olderKidBlocks[i].length / trialProportionsPerBlock[i]);
taskStore.transact('totalTestTrials', (oldVal: number) => (oldVal += numOfTrials));
for (let j = 0; j < numOfTrials; j++) {
timeline.push({ ...setupStimulusFromBlock(i), stimulus: '' }); // select only from the current block
timeline.push(stimulusBlock());
}

fullCorpus.unnormed.forEach((trial) => {
const trialBlock = trial.block_index === 3 ? 0 : trial.block_index + 1;
if (trialBlock === i) {
allCorpusParts.unnormed.forEach((trial) => {
if (i === Number(trial.block_index)) {
timeline.push({ ...fixationOnly, stimulus: '' });
timeline.push(stimulusBlock(trial));
}
Expand All @@ -349,7 +360,7 @@ export default function buildMathTimeline(config: Record<string, any>, mediaAsse
corpus.forEach((trial) => (trial.difficulty = NaN));

const newCorpora = {
practice: taskStore().corpora.practice,
downex: taskStore().corpora.downex,
stimulus: corpus,
};
taskStore('corpora', newCorpora);
Expand Down
2 changes: 1 addition & 1 deletion task-launcher/src/tasks/mental-rotation/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default function buildMentalRotationTimeline(config: Record<string, any>,
);

taskStore('corpora', {
practice: taskStore().corpora.practice,
downex: taskStore().corpora.downex,
stimulus: corpus,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
taskFinished,
} from '../shared/trials';
import { setTrialBlock } from './helpers/setTrialBlock';
import { dataQualityScreen } from '../shared/trials/dataQuality';
import { initializeCat, jsPsych } from '../taskSetup';
import { legacyStimulus } from './trials/legacyStimulus';

Expand All @@ -29,12 +28,13 @@ export default function buildSameDifferentTimelineCat(config: Record<string, any
const heavy: boolean = taskStore().heavyInstructions;

const corpus: StimulusType[] = taskStore().corpora.stimulus;
const preparedCorpus = prepareCorpus(corpus, false,true);
const preparedCorpus = prepareCorpus(corpus, false, undefined, true);

const catCorpus = setupSds(taskStore().corpora.stimulus);
const allBlocks = prepareMultiBlockCat(catCorpus);

const newCorpora = {
practice: taskStore().corpora.practice,
downex: taskStore().corpora.downex,
stimulus: allBlocks,
};
taskStore('corpora', newCorpora); // puts all blocks into taskStore
Expand Down Expand Up @@ -173,9 +173,6 @@ export default function buildSameDifferentTimelineCat(config: Record<string, any

initializeCat();

if (heavy) {
timeline.push(dataQualityScreen);
}
timeline.push(taskFinished());
timeline.push(exitFullscreen);
return { jsPsych, timeline };
Expand Down
3 changes: 0 additions & 3 deletions task-launcher/src/tasks/same-different-selection/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,6 @@ export default function buildSameDifferentTimeline(config: Record<string, any>,

initializeCat();

if (heavy) {
timeline.push(dataQualityScreen);
}
timeline.push(taskFinished());
timeline.push(exitFullscreen);
return { jsPsych, timeline };
Expand Down
Loading