ARF & CALF - Clowder
📝 NOTE: ROAM apps will use the URL param
mode=catto switch between adaptive and non-adaptive modes.
1. Fetch and Parse Corpus for Clowder (tasks/fluency/helpers/fetchAndParseCorpus.js)
- Add the needed rows to the corpus handler
 
// Add CAT corpus-specific columns if in CAT mode
    if (store.session.get("config").userMode === "cat") {
      ["sum", "minus", "mult", "div", "total"].forEach((op) => {
        ["a", "b", "c", "d"].forEach((suffix) => {
          newRow[`${op}.${suffix}`] = row[`${op}.${suffix}`];
        });
      });
    }
- Select the corpus for clowder
 
if (store.session.get("config").userMode === "cat") {
    block0: `link to block 0 order`,	    
    corpusLocation = {
        fluencyArf: {
            order: {
            block0: `block 0 link for arf`,
            },
            stimulus: `link for items-all-cat.csv`,
            practice: `link for items-practice-cat.csv`,
        },	      
    // more locations
        };
    };
    // more apps location
};
- Initialize Clowder
 
if (store.session.get("config").userMode === "cat") {
    initializeClowder();
}
export const initializeClowder = () => {
// Define the `cats` configuration
const catsConfig = {
    sum: {
        method: "MLE", // EAP
        itemSelect: store.session("itemSelect"),
        minTheta: -8,
        maxTheta: 8,
        randomSeed: "seed-sum",
    },
    minus: {
        method: "MLE",
        itemSelect: store.session("itemSelect"),
        minTheta: -8,
        maxTheta: 8,
        randomSeed: "seed-minus",
    },
    mult: {
        method: "MLE",
        itemSelect: store.session("itemSelect"),
        minTheta: -8,
        maxTheta: 8,
        randomSeed: "seed-mult",
    },
    div: {
        method: "MLE",
        itemSelect: store.session("itemSelect"),
        minTheta: -8,
        maxTheta: 8,
        randomSeed: "seed-div",
    },
    total: {
        method: "MLE",
        itemSelect: store.session("itemSelect"),
        minTheta: -8,
        maxTheta: 8,
        randomSeed: "seed-total",
    },
};
- Prepare corpus and create a new instance of Clowder
 
// USE THIS EXAMPLE TO GET EARLY STOPPING CATS -- REMEMBER TO IMPORT FUNCTIONS FROM CLOWDER AS NEEDED
  // if (store.session.get('config').earlyStopping) {
  // earlyStoppingCats = new StopAfterNItems({
  //   requiredItems: {
  //     sum:  5,
  //     minus: 5,
  //     div: 15,
  //     mult: 15,
  //   },
  //   logicalOperation: 'only',
  // });
  const corpusClowder = store.session.get("corpusAll")["stimulus"]["block0"];
  const clowderCorpus = prepareClowderCorpus(
    corpusClowder,
    ["sum", "minus", "mult", "div", "total"],
    ".",
  );
  /* In the case of ROAM there are 2 separate stimulus files, 
    one for practice and the other one for stimuli test, we need to add the zetas
    in the corresponding block -- we are not using a cat for practice */
  const corpusWithClowder = store.session.get("corpusAll");
  corpusWithClowder.stimulus.block0 = clowderCorpus;
  store.session.set("corpusAll", corpusWithClowder);
  clowder = new Clowder({
    cats: catsConfig,
    corpus: corpusWithClowder.stimulus.block0,
    randomSeed: store.session.get("config").randomSeed ?? "random-seed",
    // earlyStopping: earlyStoppingCats, --- use this if early stopping is needed
  });
2. Update the stimulus for Clowder (tasks/shared/helpers/updateStimulus.js)
import { clowder } from "../../fluency/helpers/fetchAndParseCorpus";
const catOrderMap = {
  0: "sum",
  1: "minus",
  2: "mult",
  3: "div",
};
const getNextStimulus = (corpusName) => {
  let corpus, nextStimulus, remainingStimuli;
  // read the current version of the corpus
  corpus = store.session.get("currentCorpus");
  if (
    store.session.get("config").userMode === "cat" &&
    corpusName === "stimulus"
  ) {
    let catIndex = store.session.get("currentCatIndex");
    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({
      catToSelect: catName,
      catsToUpdate: ["total", "sum", "minus", "mult", "div"],
      items: previousItem ?? undefined,
      answers: previousAnswer ?? undefined,
      randomlySelectUnvalidated: false,
    });
    if (nextStimulus === undefined) {
      store.session.remove("nextStimulus");
      const catIndex = (store.session.get("currentCatIndex") ?? -1) + 1;
      store.session.set("currentCatIndex", catIndex);
      if (catIndex < 4) {
        getNextStimulus(corpusName);
      }
    } else {
      store.session.set("nextStimulus", nextStimulus);
    }
  } else {
    nextStimulus = corpus[0];
    // get the remaining stimuli
    remainingStimuli = corpus.slice(1);
    // store the item for use in the trial
    store.session.set("nextStimulus", nextStimulus);
    // update the corpus with the remaining unused items
    corpus = remainingStimuli;
    store.session.set("currentCorpus", corpus);
  }
};
3. Save previous items and responses (tasks/responseModalityStudy/trials/responseTimeBlock.js)
- Since we are only updating from the stimulus corpus names, we need to save the previous seen items and answers from the stimulus block
 
if (corpusName === "stimulus") {
    store.session.set("previousItem", stimulus);
    store.session.set("previousAnswer", store.session.get("dataCorrect"));
}