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

Multichoice - Clowder

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

  • Extract Clowder-specific parameters such as earlyStopping, tolerance, and logicalOperation.
  • 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('previousItem', null);
store.session.set('previousAnswer', null);
  • Select the corpus in advance
const config = {
  practiceCorpus:
    task === "cva"
      ? practiceCorpus || "cva-practice-catv2"
      : practiceCorpus || "morphology-practice-catv2",

  stimulusCorpus:
    task === "cva"
      ? stimulusCorpus || "cva-stimulus-catv2"
      : stimulusCorpus || "morphology-catv2",
};
  • 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 phoneme-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, prepareClowderCorpus } from '@bdelab/jscat';

const catOrderMap = {
  0: 'practiceFSM',
  1: 'fsm',
  2: 'practiceLSM',
  3: 'lsm',
  4: 'practiceDEL',
  5: 'del',
};

// eslint-disable-next-line import/no-mutable-exports
export let clowder;

// TODO: Update values accordingly

export const initializeClowder = () => {
  // Define the `cats` configuration
  const catsConfig = {
    practice: {
      method: "EAP", // MLE
      itemSelect: store.session("itemSelect"),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: "seed-cat",
    },
    total: {
      method: "EAP", // MLE
      itemSelect: store.session("itemSelect"),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: "seed-cat",
    },
    core: {
      method: "EAP", // MLE
      itemSelect: store.session("itemSelect"),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: "seed-cat",
    },
    new: {
      method: "EAP", // MLE
      itemSelect: store.session("itemSelect"),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: "seed-cat",
    },
    spare: {
      method: "EAP", // MLE
      itemSelect: store.session("itemSelect"),
      minTheta: -8,
      maxTheta: 8,
      randomSeed: "seed-cat",
    },
  };

  // if (store.session.get('config').earlyStopping) {

  // USE EXAMPLE IN CASE OF EARLY STOPPING

  // let earlyStoppingCats = null;

  // earlyStoppingCats = new StopAfterNItems({
  //   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',
  // });

  // USE IN CASE OF SEPARATE CATS

  // const corpusLetterNameLower = encorpusLetterNameLower.map((row) => ({
  //   stimulus: row.target,
  //   zetas: [
  //     {
  //       cats: ['letterNameLower'],
  //       zeta: {
  //         a: row.a,
  //         b: row.b,
  //         c: row.c,
  //         d: row.d,
  //       },
  //     },
  //   ],
  //   ..._omit(row, ['a', 'b', 'c', 'd']),
  // }));

  const corpus = store.session.get("corpora");

  const combinedCorpus = [...corpus.practice, ...corpus.stimulus];

  const clowderCorpus = prepareClowderCorpus(
    combinedCorpus,
    ["total", "core", "new", "spare", "practice"],
    ".",
  );

  clowder = new Clowder({
    cats: catsConfig, // [spare, new, core, total] cats
    corpus: clowderCorpus,
    randomSeed: store.session.get("config").randomSeed ?? "random-seed",
    // earlyStopping: earlyStoppingCats,
  });

  store.session.set("clowder", clowder);
};

export const setNextStimulus = () => {
  const itemGroupCounter = store.session.get("itemGroupCounter");
  const coreRemaining = store.session.get("coreRemaining");
  const newRemaining = store.session.get("newRemaining");
  const spareRemaining = store.session.get("spareRemaining");
  const practiceRemaining = store.session.get("practiceRemaining");

  let catToSelect;

  if (practiceRemaining > 0) {
    catToSelect = "practice";
    store.session.set("practiceRemaining", practiceRemaining - 1);
  } else {
    // Your existing logic
    catToSelect =
      coreRemaining > 0
        ? itemGroupCounter % 4 === 0 && newRemaining > 0 && itemGroupCounter > 0
          ? "new"
          : "core"
        : newRemaining > 0
        ? itemGroupCounter % 4 === 0 &&
          spareRemaining > 0 &&
          itemGroupCounter > 0
          ? "spare"
          : "new"
        : spareRemaining > 0
        ? "spare"
        : undefined;

    if (catToSelect) {
      if (catToSelect === "core") {
        store.session.set("coreRemaining", coreRemaining - 1);
        store.session.set("itemGroupCounter", itemGroupCounter + 1);
      } else if (catToSelect === "new") {
        store.session.set("newRemaining", newRemaining - 1);
        store.session.set("itemGroupCounter", 0);
      } else if (catToSelect === "spare") {
        store.session.set("spareRemaining", spareRemaining - 1);
        store.session.set("itemGroupCounter", 0);
      }
      // Update remaining items and reset counters
      store.session.set("itemGroupCounter", itemGroupCounter + 1);
    }
  }

  store.session.set("catName", catToSelect);

  const previousItem = store.session.get("previousItem");
  const previousAnswer = store.session.get("previousAnswer");

  const nextStimulus = clowder.updateCatAndGetNextItem({
    catToSelect,
    catsToUpdate: catToSelect ? [catToSelect] : [],
    items: previousItem ?? undefined,
    answers: previousAnswer ?? undefined,
    randomlySelectUnvalidated: false,
  });

  if (nextStimulus === undefined) {
    store.session.remove("nextStimulus");
  } else {
    store.session.set("nextStimulus", nextStimulus);
  }
};

// USE THIS IN CASE OF MULTIPLE BLOCKS

// export const moveToNextBlock = () => {
//   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/setup.js) **

  • import setNextStimulus from experiment/experimentHelpers
import { setNextStimulus } from '../experimentHelpers';
  • Call setNextStimulus on setSurveyData
const setupSurveyData = [
  {
    onFinish: () => {
      setNextStimulus();
    },
  },
  {
    onFinish: () => {
      setNextStimulus();
    },
  },
];

5. Fetch and Parse Corpus for Clowder (config/corpus.js)

  • Add the needed rows to the corpus handler
// Add CAT corpus-specific columns if in CAT mode
const transformCSV = (csvInput) => {
  const accum = [];
  csvInput.forEach((row) => {
    const newRow = { ...row };
    ["total", "core", "new", "spare", "practice"].forEach((op) => {
      ["a", "b", "c", "d"].forEach((suffix) => {
        const key = `${op}.${suffix}`;
        newRow[key] = row[key];
      });
    });
    accum.push(newRow);
    return accum;
  }, []);

📝 NOTE: Multichoice has two transformCSV functions one for Morphology and the other for CVA. So remember to add these to both functions.


Edit this page
Last Updated:
Contributors: emily-ejag
Prev
Letter - Clowder
Next
Phoneme - Clowder