ROAR DocumentationROAR Documentation
  • Databases
  • Workflows
  • Application
  • GitHub Actions
  • Dashboard Components
  • Firebase App Check
  • Cloud Functions
  • Backend Architecture
  • Internationalization
  • Integrating New Apps
  • Optimizing Assets
  • ROAR Redivis Instance
  • Logging and Querying
  • Emulation
  • Data Guidelines
  • Data Organization
  • Data Requests
GitHub
  • Databases
  • Workflows
  • Application
  • GitHub Actions
  • Dashboard Components
  • Firebase App Check
  • Cloud Functions
  • Backend Architecture
  • Internationalization
  • Integrating New Apps
  • Optimizing Assets
  • ROAR Redivis Instance
  • Logging and Querying
  • Emulation
  • Data Guidelines
  • Data Organization
  • Data Requests
GitHub
  • Databases
    • Database Information
    • gse-roar-admin
    • gse-roar-assessment
  • BigQuery
    • Querying Assessment Data
    • BigQuery schema: classes
    • BigQuery schema: districts
    • BigQuery schema: families
    • BigQuery schema: groups
    • BigQuery schema: schools
    • BigQuery schema: user_runs
    • BigQuery schema: user_trials
    • BigQuery schema: users
  • Workflows
    • Workflows
    • Creating an Assignment
    • Authentication
    • Creating new Users
    • User Roster Changes
    • How to Impersonate a Clever User on Localhost
  • Application

    • Auth
  • GitHub Actions
    • ROAR Apps GitHub Actions
      • GitHub Actions in ROAR Apps
      • firebase-deploy-preview.yml
      • firebase-hosting-merge.yml
      • publish-to-npm-create-new-release.yml
      • submit-dashboard-pr.yml
    • ROAR Dashboard GitHub Actions
      • GitHub Actions in the ROAR Dashboard
  • Dashboard Components
    • Dashboard Components
    • Organization Score Reports
  • Firebase App Check
    • Firebase App Check Configuration for roar-firekit and roar-dashboard
  • Backend Architecture
    • Architecture
      • Backend Architecture in ROAR
      • Data Models
      • Database Implementations
      • Error Handling Architecture in ROAR
      • Repository Layer Architecture
      • Service Layer Architecture
    • API
      • Classes
        • Class: AdministrationServiceError
        • Class: FirebaseClientError
        • Class: FirebaseImplementationError
        • Class: FirestoreAdministrationRepository
        • Class: FirestoreAdministrationRepositoryError
        • Class: abstract FirestoreBaseRepository<T>
        • Class: FirestoreFilterAdapter
        • Class: FirestoreIdentityProviderRepository
        • Class: FirestoreIdentityProviderRepositoryError
        • Class: FirestoreOrgRepository
        • Class: FirestoreOrgRepositoryError
        • Class: FirestoreRepositoryError
        • Class: FirestoreUserClaimRepository
        • Class: FirestoreUserClaimRepositoryError
        • Class: FirestoreUserRepository
        • Class: FirestoreUserRepositoryError
        • Class: IdentityProviderServiceError
        • Classes
      • Enumerations
        • Enumeration: CollectionType
        • Enumeration: IdentityProviderType
        • Enumeration: Operator
        • Enumerations
      • Functions
        • Functions
        • Function: chunkOrgs()
        • Function: createAdministrationService()
        • Function: createFirestoreImplementation()
        • Function: createIdentityProviderService()
        • Function: isEmptyOrgs()
      • Interfaces
        • Interface: Administration
        • Interface: AdministrationBaseRepository
        • Interface: AdministrationService
        • Interface: AssentConsent
        • Interface: Assessment
        • Interface: BaseModel
        • Interface: BaseRepository<T>
        • Interface: Claims
        • Interface: CompositeCondition
        • Interface: CompositeFilter
        • Interface: CreateAdministrationServiceParams<AdminRepo, OrgRepo, UserClaimRepo>
        • Interface: CreateParams
        • Interface: DeleteParams
        • Interface: EducationalOrgsList
        • Interface: FieldCondition
        • Interface: FilterAdapter<T>
        • Interface: FirestoreCreateParams
        • Interface: FirestoreDeleteParams
        • Interface: FirestoreFetchDocumentParams
        • Interface: FirestoreGetAllParams
        • Interface: FirestoreGetByIdParams
        • Interface: FirestoreGetByNameParams
        • Interface: FirestoreGetByRoarUidParams
        • Interface: FirestoreGetParams
        • Interface: FirestoreGetWithFiltersParams
        • Interface: FirestoreImplementation
        • Interface: FirestoreRunTransactionParams<T>
        • Interface: FirestoreUpdateParams
        • Interface: FutureParams
        • Interface: GetAdministrationIdsForAdministratorParams
        • Interface: GetAdministrationIdsFromOrgsParams
        • Interface: GetAllParams
        • Interface: GetByNameParams
        • Interface: GetByProviderIdParams
        • Interface: GetByRoarUidParams
        • Interface: GetParams
        • Interface: GetRoarUidParams
        • Interface: IdentityProvider
        • Interface: IdentityProviderBaseRepository
        • Interface: IdentityProviderService
        • Interface: Legal
        • Interface: OrgBase
        • Interface: OrgBaseRepository
        • Interface: OrgsList
        • Interfaces
        • Interface: Result<T>
        • Interface: RunTransactionParams<T>
        • Interface: SingleFilter
        • Interface: UpdateParams
        • Interface: User
        • Interface: UserBaseRepository
        • Interface: UserClaim
        • Interface: UserClaimBaseRepository
        • Interface: createIdentityProviderServiceParams<IDPRepo, UserClaimRepo, UserRepo>
        • Interface: getAdministrationIdsFromOrgsParams
        • Interface: _setAdministrationIdsParams
      • Type Aliases
        • Type Alias: BaseFilter
        • Type Alias: ComparisonOperator
        • Type Alias: Condition
        • Type Alias: DocumentCreatedEvent
        • Type Alias: DocumentDeletedEvent
        • Type Alias: DocumentUpdatedEvent
        • Type Alias: DocumentWrittenEvent
        • Type Alias: ParameterValue
        • Type Aliases
        • Type Alias: SelectAllCondition
      • Variables
        • Variable: FirebaseAppClient
        • Variable: FirebaseAuthClient
        • Variable: FirestoreClient
        • Variable: ORG_NAMES
        • Variables API Documentation
    • Examples
      • Examples
    • Guides
      • Guides
  • Cloud Functions
    • gse-roar-admin
      • Admin Database
      • appendToAdminClaims()
      • associateassessmentuid()
      • createAdministratorAccount()
      • createGuestDocsForGoogleUsers()
      • createLevanteGroup()
      • createLevanteUsers()
      • createnewfamily()
      • createstudentaccount()
      • mirrorClasses()
      • mirrorCustomClaims
      • mirrorDistricts()
      • mirrorFamilies()
      • mirrorGroups()
      • mirrorSchools()
      • removefromadminclaims()
      • saveSurveyResponses()
      • setuidcustomclaims()
      • softDeleteUserAssignment()
      • softDeleteUserExternalData
      • softDeleteUser()
      • syncAssignmentCreated()
      • syncAssignmentDeleted()
      • syncAssignmentUpdated()
      • syncAssignmentsOnAdministrationUpdate()
      • syncAssignmentsOnUserUpdate()
      • syncCleverOrgs()
      • syncCleverUser()
    • gse-roar-assessment
      • Assessment Database
      • organizeBucketLogsByDate()
      • setuidclaims()
      • softDeleteGuestTrial()
      • softDeleteGuest()
      • softDeleteUserRun()
      • softDeleteUserTrial()
      • syncOnRunDocUpdate()
  • Internationalization
    • ROAM Fluency
    • ROAR Letter
    • ROAR Phoneme
    • Internationalization of ROAR Apps
    • ROAR Sentence
    • ROAR Word
  • Integrating New Apps
    • Integrating Roar Apps into the Dashboard
    • Dashboard Integration
    • Monitoring and Testing
    • Preparing the App for Packaging and Deployment
    • Packaging and Publishing to npm
    • Secrets in the GitHub Repository
  • Assets Optimization
    • Optimizing Assets
    • Audio Optimization Guide
    • Image Optimization Guide
  • ROAR Redivis Instance
    • ROAR Redivis Instance
    • ROAR Data Validator Trigger
    • ROAR Data Validator
  • Logging and Querying
    • ROAR Logging
  • Emulation
    • Running the Emulator
      • Commands
    • Emulator Configuration Guide
      • Configuration
      • Cypress Configuration
      • Setup and Dependencies
      • Firebase CLI Configuration
      • Firebase Emulator Configuration
      • GitHub Secrets and Workflows
      • Importing and Exporting Data
      • Local Environment Variables
  • Clowder Implementation
    • Clowder Integration
    • Letter - Clowder
    • Multichoice - Clowder
    • Phoneme - Clowder
    • ARF & CALF - Clowder

Letter - Clowder

1. Parameter Extraction from URL (serve/serve.js)

  • Extract Clowder-specific parameters such as earlyStopping, tolerance, and logicalOperation.
  • DO NOT USE userMode as it is no longer required for Clowder-based logic.
  • Add these parameters to the gameParams array.
// Parameters for Clowder

const earlyStopping = urlParams.get('earlyStopping')?.toLowerCase() ?? null;
const tolerance = urlParams.get('tolerance') ?? null;
const logicalOperation = urlParams.get('logicalOperation')?.toLowerCase() ?? null;

// Other useful parameters

const threshold = urlParams.get('threshold') ?? null;
const patience = urlParams.get('patience') ?? null;
const nItems = urlParams.get('nItems') ? parseInt(urlParams.get('nItems'), 10) : null;
const randomSeed = urlParams.get('random') ?? null;
const catsToUpdate = urlParams.get('catsToUpdate')?.split(',') ?? [];

// Add more parameters for Clowder if needed

2. Initialize Clowder in the Store Session (experiment/config/config.js)

  • Inside the initStore function, set the following:
store.session.set('itemSelect', 'mfi'); // Maximum Fisher Information algorithm (modifiable if needed)
store.session.set('currentCatIndex', -1); // Ensures the session starts with the first category if the page is refreshed
  • Initialize Clowder before returning the session:
initializeClowder();

3. Create CATs and Clowder (experiment/experimentSetup.js)

  • Implement the initializeClowder function to set up Clowder instances for different letter-related trials.
  • Define catsConfig for each Clowder instance, specifying method, itemSelect, minTheta, maxTheta, and randomSeed.
  • Initialize Clowder’s corpus using prepareClowderCorpus.
  • Select the next stimulus using the Clowder function updateCatAndGetNextItem.
import { Cat, Clowder, StopAfterNItems, prepareClowderCorpus } from '@bdelab/jscat';

// English
import enLetterCatCorpus from '../stimuli/en/letterCatCorpus.csv';

const catOrderMap = { // This order map corresponds to the order we want to give for the timeline and the selection of the cats
  0: 'letterNamePractice',
  1: 'letterNameLower',
  2: 'letterNameUpper',
  3: 'letterPhonemePractice',
  4: 'letterPhoneme',
};

const catToSubTaskMap = { // This map is the match the name of the cats with the subtasks on this app
  letterNamePractice: 'LetterPractice',
  letterNameLower: 'LowercaseNames',
  letterNameUpper: 'UppercaseNames',
  letterPhonemePractice: 'PhonemePractice',
  letterPhoneme: 'Phonemes',
};

export let clowder;

export const initializeClowder = () => {
  // Define the `cats` configuration
  const catsConfig = {
    letterNamePractice: {
      method: 'EAP', // MLE or other offered clowder algorithm
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-lower-practice',
    },
    letterNameLower: {
      method: 'EAP',
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-lower',
    },
    letterNameUpper: {
      method: 'EAP',
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-upper',
    },
    letterPhonemePractice: {
      method: 'EAP',
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-phoneme-practice',
    },
    letterPhoneme: {
      method: 'EAP',
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-phoneme',
    },
    total: {
      method: 'EAP',
      itemSelect: store.session('itemSelect'),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: 'seed-lower-practice',
    },
  };

  let earlyStoppingCats = null;

  // store.session.get('config').earlyStopping) // if any
  earlyStoppingCats = new StopAfterNItems({ // Clowder method to early stop at specific number of items
    requiredItems: {
      letterNameLower: store.session.get('config').nItems ?? 5,
      letterNameUpper: store.session.get('config').nItems ?? 5,
      letterPhoneme: store.session.get('config').nItems ?? 15,
    },
    logicalOperation: store.session.get('config').logicalOperation ?? 'only',
  });

  const clowderCorpus = prepareClowderCorpus( // function to grab cat names and irt parameters from a given csv file
    enLetterCatCorpus, //corpus
    ['letterNamePractice', 'letterNameLower', 'letterNameUpper', 'letterPhonemePractice', 'letterPhoneme', 'total'], // cat names
    '.', // separator
  );

  clowder = new Clowder({ // new instance of Clowder
    cats: catsConfig,
    corpus: clowderCorpus,
    randomSeed: store.session.get('config').randomSeed ?? 'random-seed',
    earlyStopping: earlyStoppingCats,
  });
};

export const setNextStimulus = () => { // function on letter to get the next stimulus
  let catIndex = store.session.get('currentCatIndex');
  // eslint-disable-next-line eqeqeq
  if (catIndex == undefined) {
    store.session.set('currentCatIndex', 0);
    catIndex = 0;
  }

  const catName = catOrderMap[catIndex];
  const previousItem = store.session.get('previousItem');
  const previousAnswer = store.session.get('previousAnswer');

  const nextStimulus = clowder.updateCatAndGetNextItem({ // clowder function to select the next stimulus
    catToSelect: catName,
    catsToUpdate: ['total', 'letterNameLower', 'letterNameUpper', 'letterPhoneme'], // notice here that we did not add all the created cats, only the ones we want to updatw
    items: previousItem ?? undefined,
    answers: previousAnswer ?? undefined,
    randomlySelectUnvalidated: false,
  });

  if (nextStimulus === undefined) { // returning undefined from clowder.updateCatAndGetNextItem would indicate early stopping and moving to next block
    store.session.remove('nextStimulus');
  } else {
    store.session.set('nextStimulus', nextStimulus);
  }
};

export const moveToNextBlock = () => { // move to next cat - timeline for the task
  const catIndex = (store.session.get('currentCatIndex') ?? -1) + 1;
  store.session.set('subTaskName', catToSubTaskMap[catOrderMap[catIndex]]);
  store.session.set('currentCatIndex', catIndex);
  store.session.set('correctItems', []);
  store.session.set('incorrectItems', []);
  store.session.set('trialNumSubtask', 0); // counter for trials in subtask
};

4. Stimulus Control (experiment/trials/stimulus.js)

  • Define buildBlock, which organizes the letter-based timeline using Clowder.
import store from 'store2';
import { ifLetterNameTest, ifRealTrialResponse } from './stimulusLetterName'; // story breaks during stimulus
import { ifPracticeCorrect, ifPracticeIncorrect } from './practice'; // story breaks during practice
import { setNextStimulus, moveToNextBlock } from '../experimentSetup';

export const buildBlock = (preInstructions) => {
  const stimulusLoop = {
    timeline: [ifLetterNameTest, ifPracticeCorrect, ifPracticeIncorrect, ifRealTrialResponse],
    loop_function: () => {
      setNextStimulus();
      return store.session.get('nextStimulus') !== undefined;
    },
  };

  return {
    timeline: [preInstructions, stimulusLoop],
    on_timeline_start: () => {
      moveToNextBlock();
      setNextStimulus();
    },
  };
};

5. Experiment Execution Updates (experiment/experiment.js)

  • Incorporate buildBlock, ensuring Clowder-managed stimulus presentation.
import { buildBlock } from './trials/stimulus';
  • Define the timeline for transitions:
timeline.push(buildBlock(letterIntroAndInstructions));
timeline.push(buildBlock(letterPracticeDone));
timeline.push(buildBlock(letterTransition));
timeline.push(buildBlock(soundIntroAndInstructions));
timeline.push(buildBlock(soundPracticeDone));

6. Scoring and Progress Tracking Updates (experiment/scores.js)

  • Compute Clowder-based scores.
import { clowder } from './experimentSetup';

const subTaskToCatMap = {
  LetterPractice: 'letterNamePractice',
  LowercaseNames: 'letterNameLower',
  UppercaseNames: 'letterNameUpper',
  PhonemePractice: 'letterPhonemePractice',
  Phonemes: 'letterPhoneme',
};

export const computedScoreCallback = (rawScores) => {
  return {
    // otrher scores
    thetaEstimate: clowder.theta[subTaskToCatMap[store.session.get('subTaskName')]], // check names
    thetaSE: clowder.seMeasurement[subTaskToCatMap[store.session.get('subTaskName')]], // check names
    thetaEstimateTotal: clowder.theta.total,
    thetaSETotal: clowder.seMeasurement.total,
  };
};
Edit this page
Last Updated:
Contributors: Elijah Kelly, emily-ejag
Prev
Clowder Integration
Next
Multichoice - Clowder