diff --git a/HTML/Assets/Detectors/Lumilo/Critical_Struggle/critical_struggle.js b/HTML/Assets/Detectors/Lumilo/Critical_Struggle/critical_struggle.js
index 7a16951..c2ac14e 100644
--- a/HTML/Assets/Detectors/Lumilo/Critical_Struggle/critical_struggle.js
+++ b/HTML/Assets/Detectors/Lumilo/Critical_Struggle/critical_struggle.js
@@ -18,50 +18,269 @@ var mailer;
//declare any custom global variables that will be initialized
//based on "remembered" values across problem boundaries, here
// (initialize these at the bottom of this file, inside of self.onmessage)
+var attemptWindow;
+var skillLevelsAttempts;
+
+//declare and/or initialize any other custom global variables for this detector here...
+var stepCounter;
+var help_model_output;
+var help_variables = {"lastAction": "null",
+ "lastActionTime": "",
+ "seenAllHints": {},
+ "lastHintLength": "",
+ "lastSenseOfWhatToDo": false
+ };
+var timerId; var timerId2; var timerId3; var timerId4; var timerId5;
//
-//
-//
-//
-//
+//[optional] single out TUNABLE PARAMETERS below
+var windowSize = 7;
+var threshold = 1;
+var wheelSpinningAttemptThreshold = 10; //following Beck and Gong's wheel-spinning work
+var errorThreshold = 3; //currently arbitrary
+var newStepThreshold = 3; //currently arbitrary
+var familiarityThreshold = 0.4;
+var senseOfWhatToDoThreshold = 0.6;
+var hintIsHelpfulPlaceholder = true; //currently a dummy value (assumption that hint is always helpful...)
-//declare and/or initialize any other custom global variables for this detector here
-//
-//
-//
//
+//###############################
+//##### Help model ######
+//###############################
+//###############################
//
+//non-controversial
+function lastActionIsHint(e){
+ if (help_variables.lastAction == "hint"){return true;}
+ else{return false;}
+}
+function lastActionIsError(e){
+ if (help_variables.lastAction == "error"){return true;}
+ else{return false;}
+}
+function seenAllHintLevels(e){
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "hint"){
+ if (e.data.tutor_data.selection in help_variables.seenAllHints){
+ return help_variables.seenAllHints[e.data.tutor_data.selection];
+ }
+ else{return false;}
+ }
+ else{
+ if (e.data.tool_data.selection in help_variables.seenAllHints){
+ return help_variables.seenAllHints[e.data.tool_data.selection];
+ }
+ else{return false;}
+ }
+}
+function isCorrect(e){
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "correct"){return true;}
+ else{return false;}
+}
+
+function secondsSinceLastAction(e){
+ var currTime = new Date();
+ diff = currTime.getTime() - help_variables.lastActionTime.getTime();
+ console.log("time elapsed: ", diff/1000)
+ return (diff / 1000);
+}
-function frustrated_for_a_while(){
+//less controversial
+function isDeliberate(e){
+ var hintThreshold = (help_variables.lastHintLength/600)*60;
+ if (lastActionIsError(e)){
+ return (secondsSinceLastAction(e) > errorThreshold);
+ }
+ else if (lastActionIsHint(e)){
+ return (secondsSinceLastAction(e) > hintThreshold);
+ }
+ else{
+ return (secondsSinceLastAction(e) > newStepThreshold);
+ }
}
-function confused_for_a_while(){
+//more controversial...
+function isFamiliar(e){
+ var rawSkills = e.data.tutor_data.skills;
+ for (var property in rawSkills) {
+ if (rawSkills.hasOwnProperty(property)) {
+ if (parseFloat(rawSkills[property].pKnown)<=familiarityThreshold){
+ return false;
+ }
+ }
+ }
+ return true;
+}
+function isLowSkillStep_All(e){
+ var rawSkills = e.data.tutor_data.skills;
+ for (var property in rawSkills) {
+ if (rawSkills.hasOwnProperty(property)) {
+ if (parseFloat(rawSkills[property].pKnown)>=familiarityThreshold){
+ return false;
+ }
+ }
+ }
+ return true;
}
-function many_incorrects_on_current_problem(){
+function isLowSkillStep_Some(e){
+ var rawSkills = e.data.tutor_data.skills;
+ for (var property in rawSkills) {
+ if (rawSkills.hasOwnProperty(property)) {
+ if (parseFloat(rawSkills[property].pKnown)<=familiarityThreshold){
+ return true;
+ }
+ }
+ }
+ return false;
+}
+function hintIsHelpful(e){
+ return hintIsHelpfulPlaceholder;
+}
+function lastActionUnclearFix(e){
+ if (help_variables.lastSenseOfWhatToDo == false){return true;}
+ else{return false;}
+}
+function senseOfWhatToDo(e){
+ var sel = e.data.tutor_data.selection;
+ var rawSkills = e.data.tutor_data.skills;
+ for (var property in rawSkills) {
+ if (rawSkills.hasOwnProperty(property)) {
+ if (parseFloat(rawSkills[property].pKnown)<=senseOfWhatToDoThreshold){
+ return false;
+ }
+ }
+ }
+ return true;
}
-//no frustration detector currently included
-function detect_frustration(){
+//evaluation of each step
+function evaluateAction(e){
+ var sel = e.data.tutor_data.selection;
+ var outcome = e.data.tutor_data.action_evaluation.toLowerCase();
+
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "hint"){
+ console.log("isHint")
+ if (isDeliberate(e)){
+ console.log("isDeliberate")
+ if (!seenAllHintLevels(e) &&
+ (!isFamiliar(e)
+ || (lastActionIsError(e) && lastActionUnclearFix(e))
+ || (lastActionIsHint(e) && !hintIsHelpful(e))) ){
+ return "preferred/ask hint";
+ }
+ else if ( (isFamiliar(e) && !senseOfWhatToDo(e) )
+ || (lastActionIsHint(e)) ){
+ return "acceptable/ask hint";
+ }
+ else{
+ return "not acceptable/hint abuse";
+ }
+
+ }
+ else{
+ console.log("not deliberate")
+ return "not acceptable/hint abuse";
+ }
+ }
+ else{
+ if (isDeliberate(e)){
+ if ( (isFamiliar(e) && (!(lastActionIsError(e) && lastActionUnclearFix(e))) )
+ || (lastActionIsHint(e) && hintIsHelpful(e))
+ ){
+ return "preferred/try step";
+ }
+ else if (seenAllHintLevels(e) &&
+ (!(lastActionIsError(e) && lastActionUnclearFix(e))) ){
+ return "preferred/try step";
+ }
+ else if (isCorrect(e)){
+ return "acceptable/try step";
+ }
+ else if (seenAllHintLevels(e)){
+ if (lastActionIsError(e) && lastActionUnclearFix(e)){
+ return "ask teacher for help/try step";
+ }
+ }
+ else{
+ return "not acceptable/hint avoidance";
+ }
+ }
+ else{
+ return "not acceptable/not deliberate";
+ }
+ }
- return false;
}
+function updateHistory(e){
+ help_variables.lastActionTime = new Date();
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "hint"){
+ help_variables.lastAction = "hint";
+ help_variables.lastHintLength = e.data.tutor_data.tutor_advice.split(' ').length;
+ if (help_variables.seenAllHints[e.data.tutor_data.selection] != true){
+ help_variables.seenAllHints[e.data.tutor_data.selection] = (e.data.tutor_data.current_hint_number == e.data.tutor_data.total_hints_available);
+ }
+ }
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "incorrect"){
+ help_variables.lastAction = "error";
+ }
+ if (e.data.tutor_data.action_evaluation.toLowerCase() == "correct"){
+ help_variables.lastAction = "correct";
+ }
+
+ help_variables.lastSenseOfWhatToDo = senseOfWhatToDo(e);
+
+}
+
+
+//
+//###############################
+//###############################
+//###############################
+//###############################
+//
-function detect_confusion(){
+function updateSkillLevelsAttempts(e, rawSkills, currStepCount){
+ for (var skill in rawSkills) {
+ if( (rawSkills[skill].name in skillLevelsAttempts) && (currStepCount==1) ){
+ skillLevelsAttempts[rawSkills[skill].name][0] += 1;
+ skillLevelsAttempts[rawSkills[skill].name][1] = parseFloat(rawSkills[skill].pKnown);
+ }
+ else{
+ skillLevelsAttempts[rawSkills[skill].name] = [1, parseFloat(rawSkills[skill].pKnown)];
+ }
+ }
}
-function detect_wheel_spinning(){
+function detect_wheel_spinning(e, rawSkills, currStepCount){
+
+ updateSkillLevelsAttempts(e, rawSkills, currStepCount);
+
+ for (var skill in skillLevelsAttempts) {
+ if ((skillLevelsAttempts[skill][0] >= 10) && (skillLevelsAttempts[skill][1] < 0.95)){
+ return true;
+ }
+ }
+ return false;
}
+//
+//###############################
+//###############################
+//###############################
+//###############################
+//
+
+
+
function receive_transaction( e ){
//e is the data of the transaction from mailer from transaction assembler
@@ -81,11 +300,35 @@ function receive_transaction( e ){
detector_output.step_id = e.data.tutor_data.step_id;
//custom processing (insert code here)
- //
- //
- var booleanValues = [0, 1];
- var timeValues = ["> 25 s", "> 45 s", "> 1 min", "> 2 min", "> 5 min"];
- detector_output.value = String(booleanValues[Math.floor(Math.random() * booleanValues.length)]) + "," + String(timeValues[Math.floor(Math.random() * timeValues.length)]) ;
+
+ if (help_variables.lastAction!="null"){
+ help_model_output = evaluateAction(e);
+ }
+ else{
+ help_model_output = "preferred"; //first action in whole tutor is set to "preferred" by default
+ }
+
+ //keep track of num attempts on each step
+ currStep = e.data.tool_data.selection;
+ if(currStep in stepCounter){
+ stepCounter[currStep] += 1;
+ }
+ else{
+ stepCounter[currStep] = 1;
+ }
+
+
+ var isWheelSpinning = detect_wheel_spinning(e, rawSkills, stepCounter[currStep]);
+
+ attemptWindow.shift();
+ attemptWindow.push( (help_model_output == "ask teacher for help/try step" || isWheelSpinning) ? 1 : 0 );
+ var sumAskTeacherForHelp = attemptWindow.reduce(function(pv, cv) { return pv + cv; }, 0);
+ console.log(attemptWindow);
+ console.log(help_model_output);
+ console.log(isWheelSpinning);
+ console.log(skillLevelsAttempts);
+
+ updateHistory(e);
}
@@ -96,11 +339,60 @@ function receive_transaction( e ){
detector_output.transaction_id = e.data.transaction_id;
//custom processing (insert code here)
- //
- //
- //
- //
- //
+ if (detector_output.value=="0, > 0 s" && (sumAskTeacherForHelp >= threshold)){
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.value = "1, > 25 s"
+ detector_output.time = new Date();
+
+ timerId = setTimeout(function() {
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.value = "1, > 45 s"
+ detector_output.time = new Date();
+ mailer.postMessage(detector_output);
+ postMessage(detector_output);
+ console.log("output_data = ", detector_output); },
+ 20000)
+ timerId2 = setTimeout(function() {
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.value = "1, > 1 min"
+ detector_output.time = new Date();
+ mailer.postMessage(detector_output);
+ postMessage(detector_output);
+ console.log("output_data = ", detector_output); },
+ 35000)
+ timerId3 = setTimeout(function() {
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.value = "1, > 2 min"
+ detector_output.time = new Date();
+ mailer.postMessage(detector_output);
+ postMessage(detector_output);
+ console.log("output_data = ", detector_output); },
+ 95000)
+ timerId4 = setTimeout(function() {
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.value = "1, > 5 min"
+ detector_output.time = new Date();
+ mailer.postMessage(detector_output);
+ postMessage(detector_output);
+ console.log("output_data = ", detector_output); },
+ 275000)
+
+ }
+ else if (detector_output.value!="0, > 0 s" && (sumAskTeacherForHelp >= threshold)){
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.time = new Date();
+ }
+ else{
+ detector_output.value = "0, > 0 s";
+ detector_output.history = JSON.stringify([attemptWindow, skillLevelsAttempts]);
+ detector_output.time = new Date();
+
+ clearTimeout(timerId);
+ clearTimeout(timerId2);
+ clearTimeout(timerId3);
+ clearTimeout(timerId4);
+ }
+
mailer.postMessage(detector_output);
postMessage(detector_output);
@@ -125,7 +417,7 @@ self.onmessage = function ( e ) {
}
}
- //optional: In "detectorForget", specify conditions under which a detector
+ //optional: In "detectorForget", specify conditions under which a detector
//should NOT remember their most recent value and history (using the variable "detectorForget").
//(e.g., setting the condition to "true" will mean that the detector
// will always be reset between problems... and setting the condition to "false"
@@ -149,7 +441,8 @@ self.onmessage = function ( e ) {
//in the event that the detector history is empty,
//initialize variables to your desired 'default' values
//
- //
+ attemptWindow = Array.apply(null, Array(windowSize)).map(Number.prototype.valueOf,0);
+ skillLevelsAttempts = {};
}
else{
//if the detector history is not empty, you can access it via:
@@ -157,7 +450,9 @@ self.onmessage = function ( e ) {
//...and initialize your variables to your desired values, based on
//this history
//
- //
+ var all_history = JSON.parse(detector_output.history);
+ attemptWindow = all_history[0];
+ skillLevelsAttempts = all_history[1];
}
break;
diff --git a/HTML/Assets/Detectors/Lumilo/Struggle/struggle__moving_average.js b/HTML/Assets/Detectors/Lumilo/Struggle/struggle__moving_average.js
index c3c9bf0..8501388 100644
--- a/HTML/Assets/Detectors/Lumilo/Struggle/struggle__moving_average.js
+++ b/HTML/Assets/Detectors/Lumilo/Struggle/struggle__moving_average.js
@@ -64,12 +64,12 @@ function receive_transaction( e ){
//custom processing (insert code here)
if (detector_output.value=="0, > 0 s" && (sumCorrect <= threshold)){
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.value = "1, > 25 s"
detector_output.time = new Date();
timerId = setTimeout(function() {
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.value = "1, > 45 s"
detector_output.time = new Date();
mailer.postMessage(detector_output);
@@ -77,7 +77,7 @@ function receive_transaction( e ){
console.log("output_data = ", detector_output); },
20000)
timerId2 = setTimeout(function() {
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.value = "1, > 1 min"
detector_output.time = new Date();
mailer.postMessage(detector_output);
@@ -85,7 +85,7 @@ function receive_transaction( e ){
console.log("output_data = ", detector_output); },
35000)
timerId3 = setTimeout(function() {
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.value = "1, > 2 min"
detector_output.time = new Date();
mailer.postMessage(detector_output);
@@ -93,7 +93,7 @@ function receive_transaction( e ){
console.log("output_data = ", detector_output); },
95000)
timerId4 = setTimeout(function() {
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.value = "1, > 5 min"
detector_output.time = new Date();
mailer.postMessage(detector_output);
@@ -103,12 +103,12 @@ function receive_transaction( e ){
}
else if (detector_output.value!="0, > 0 s" && (sumCorrect <= threshold)){
- detector_output.history = e.data.tool_data.tool_event_time;
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.time = new Date();
}
else{
detector_output.value = "0, > 0 s";
- detector_output.history = e.data.tool_data.tool_event_time
+ detector_output.history = JSON.stringify(attemptWindow);
detector_output.time = new Date();
clearTimeout(timerId);
diff --git a/HTML/Assets/Detectors/help_models/help_model_try_if_low.js b/HTML/Assets/Detectors/help_models/help_model_try_if_low.js
index 41da796..7fe86de 100644
--- a/HTML/Assets/Detectors/help_models/help_model_try_if_low.js
+++ b/HTML/Assets/Detectors/help_models/help_model_try_if_low.js
@@ -234,20 +234,11 @@ function updateHistory(e){
}
-//TO-DO:
-// detector initialiization, and leave comment
-// showing user how not to initialize (or, if we decide to
-// initialize all detector variables by default, at startup...
-// I suppose this would mean showing user how to clear initialized
-// values upon the first transaction received?)
function receive_transaction( e ){
//e is the data of the transaction from mailer from transaction assembler
- //TEST CODE
- //console.log("in detector1 with data:");
- //console.log(e.data);
//set conditions under which transaction should be processed
//(i.e., to update internal state and history, without
@@ -269,7 +260,7 @@ function receive_transaction( e ){
detector_output.value = evaluateAction(e);
}
else{
- detector_output.value = "preferred"; //may want to change this... currently means that the first action in any problem is always preferred
+ detector_output.value = "preferred";
}
updateHistory(e);
detector_output.history = help_variables;