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;