diff --git a/psiturk/default_configs/local_config_defaults.txt b/psiturk/default_configs/local_config_defaults.txt index addd55bdd..ba96a5d24 100644 --- a/psiturk/default_configs/local_config_defaults.txt +++ b/psiturk/default_configs/local_config_defaults.txt @@ -13,6 +13,8 @@ psiturk_keywords = stroop organization_name = New Great University browser_exclude_rule = MSIE, mobile, tablet allow_repeats = false +allow_repeat_conditions = false +always_show_instructions = true [Database Parameters] database_url = sqlite:///participants.db diff --git a/psiturk/example/static/js/task.js b/psiturk/example/static/js/task.js index e7172cfda..56355ddd1 100644 --- a/psiturk/example/static/js/task.js +++ b/psiturk/example/static/js/task.js @@ -9,7 +9,13 @@ var psiTurk = new PsiTurk(uniqueId, adServerLoc, mode); var mycondition = condition; // these two variables are passed by the psiturk server process var mycounterbalance = counterbalance; // they tell you which condition you have been assigned to +var didAlready = didAlready; // if they did the experiment already, this will be true (will be false otherwise) +var num_conds = num_conds; // this is the number of conditions specified in the config.txt file +var num_counters = num_counters; // this is the number of counterbalances specified in the config.txt file +var num_conds_completed = num_conds_completed; // if they did the experiment already, how many conditions they completed (will be 0 by default) // they are not used in the stroop code but may be useful to you +var always_show_instructions = always_show_instructions; // if this is false, then if the participant repeats, it will skip over instructions +// always_show_instructions is used to skip over instructions if the participant repeats (below) // All pages to be loaded var pages = [ @@ -220,8 +226,12 @@ var currentview; * Run Task ******************/ $(window).load( function(){ - psiTurk.doInstructions( - instructionPages, // a list of pages you want to display in sequence - function() { currentview = new StroopExperiment(); } // what you want to do when you are done with instructions + if ( (didAlready == true) & (always_show_instructions == false) ) { + currentview = new StroopExperiment(); + } else { + psiTurk.doInstructions( + instructionPages, // a list of pages you want to display in sequence + function() { currentview = new StroopExperiment(); } // what you want to do when you are done with instructions + }; ); }); diff --git a/psiturk/example/templates/error.html b/psiturk/example/templates/error.html index cc6840082..ed1798114 100644 --- a/psiturk/example/templates/error.html +++ b/psiturk/example/templates/error.html @@ -26,6 +26,13 @@

Sorry, there was an error

Because this is a Psychology experiment, you can only complete this HIT once.

Please release the HIT so someone else can perform the experiment.

+ + {% elif errornum == 1021 %} + +

Sorry, our records indicate that you have already completed (or attempted to complete) all of the possible conditions for this experiment.

+

Because this is a Psychology experiment, you can participate in each condition only once.

+

Please release the HIT so someone else can perform the experiment.

+ {% else %}

Sorry, there was an unexpected error in our processing of your HIT.

diff --git a/psiturk/example/templates/exp.html b/psiturk/example/templates/exp.html index 98b9b07f7..6d9a0511b 100644 --- a/psiturk/example/templates/exp.html +++ b/psiturk/example/templates/exp.html @@ -26,6 +26,11 @@ var counterbalance = {{ counterbalance }}; // a number indexing counterbalancing conditions var adServerLoc = "{{ adServerLoc }}"; // the location of your ad (so you can send user back at end of experiment) var mode = "{{ mode }}"; + var didAlready = {{ didAlready }}; // this will indicate whether they did already (for repeats, to skip instructions, etc.) + var always_show_instructions = {{ always_show_instructions }}; // if this is false, then if the participant repeats, it will skip over instructions + var num_conds = {{ num_conds }}; // this is the number of conditions specified in the config.txt file + var num_counters = {{ num_counters }}; // this is the number of counterbalances specified in the config.txt file + var num_conds_completed = {{ num_conds_completed }}; // this is the number of counterbalances specified in the config.txt file diff --git a/psiturk/experiment.py b/psiturk/experiment.py index 4a3b4d80d..54d06db31 100644 --- a/psiturk/experiment.py +++ b/psiturk/experiment.py @@ -125,7 +125,7 @@ def shutdown_session(_=None): # Experiment counterbalancing code # ================================ -def get_random_condcount(): +def get_random_condcount(condsAlreadyDone): """ HITs can be in one of three states: - jobs that are finished @@ -160,13 +160,22 @@ def get_random_condcount(): condcount = (participant.cond, participant.counterbalance) if condcount in counts: counts[condcount] += 1 - mincount = min(counts.values()) - minima = [hsh for hsh, count in counts.iteritems() if count == mincount] - chosen = choice(minima) - #conds += [ 0 for _ in range(1000) ] - #conds += [ 1 for _ in range(1000) ] - app.logger.info("given %(a)s chose %(b)s" % {'a': counts, 'b': chosen}) - + # below is to exclude the cond/counterbalance combos + # that this participant has already done + allow_repeat_conditions = CONFIG.getboolean('HIT Configuration', 'allow_repeat_conditions') + if not allow_repeat_conditions: + unwanted = [x for x in counts if x in condsAlreadyDone] + for unwanted_key in unwanted: del counts[unwanted_key] + if bool(counts): + mincount = min(counts.values()) + minima = [hsh for hsh, count in counts.iteritems() if count == mincount] + chosen = choice(minima) + #conds += [ 0 for _ in range(1000) ] + #conds += [ 1 for _ in range(1000) ] + app.logger.info("given %(a)s chose %(b)s" % {'a': counts, 'b': chosen}) + else: + # there are no more conditions for this subject to do, so raise error + raise ExperimentError('already_did_all_conds') return chosen @@ -402,16 +411,36 @@ def start_exp(): filter(Participant.workerid == worker_id).\ filter(Participant.assignmentid == assignment_id).\ all() + # added for repeats + matchesConds = Participant.query.\ + filter(Participant.workerid == worker_id).\ + all() + # get all the conditions they have done and submitted or completed already + condsAlreadyDone = [(record.cond, record.counterbalance) \ + for record in matchesConds \ + if (record.status in [COMPLETED,SUBMITTED,CREDITED,BONUSED])] else: matches = Participant.query.\ filter(Participant.workerid == worker_id).\ all() - + # added for repeats + condsAlreadyDone = [] + + # record number of conditions completed (if repeats are allowed) in order to prevent + # a subject from repeating again if they have completed all possible conditions + # and also if we want to inform the participant how many they have completed) + num_conds_completed = len(condsAlreadyDone) + # this is so we can skip instructions, certain questionnaires, etc. fif we want + didAlready = 'false' + if num_conds_completed > 0: + didAlready = 'true' + numrecs = len(matches) if numrecs == 0: # Choose condition and counterbalance - subj_cond, subj_counter = get_random_condcount() - + # (Will exclude conditions the subject has already done) + subj_cond, subj_counter = get_random_condcount(condsAlreadyDone) + worker_ip = "UNKNOWN" if not request.remote_addr else \ request.remote_addr browser = "UNKNOWN" if not request.user_agent.browser else \ @@ -492,6 +521,11 @@ def start_exp(): counterbalance=part.counterbalance, adServerLoc=ad_server_location, mode = mode, + didAlready = didAlready, + always_show_instructions = CONFIG.get('HIT Configuration', 'always_show_instructions'), + num_conds = CONFIG.get('Task Parameters', 'num_conds'), + num_counters = CONFIG.get('Task Parameters', 'num_counters'), + num_conds_completed = num_conds_completed, contact_address=CONFIG.get('HIT Configuration', 'contact_email_on_error') ) diff --git a/psiturk/experiment_errors.py b/psiturk/experiment_errors.py index a13c14e28..756995ad4 100644 --- a/psiturk/experiment_errors.py +++ b/psiturk/experiment_errors.py @@ -25,6 +25,7 @@ class ExperimentError(Exception): already_started_exp=1008, already_started_exp_mturk=1009, already_did_exp_hit=1010, + already_did_all_conds=1021, tried_to_quit=1011, intermediate_save=1012, improper_inputs=1013, @@ -87,6 +88,11 @@ class ExperimentError(Exception): The experiment has already been completed. """) + error_descriptions['already_did_all_conds'] = unwrap( + """ + The participant has already completed all conditions. + """) + error_descriptions['tried_to_quit'] = unwrap( """ The experiment was ended prematurely and something went wrong while diff --git a/psiturk/psiturk_js/psiturk.js b/psiturk/psiturk_js/psiturk.js index 1b207e2be..f03f14758 100644 --- a/psiturk/psiturk_js/psiturk.js +++ b/psiturk/psiturk_js/psiturk.js @@ -44,7 +44,12 @@ var PsiTurk = function(uniqueId, adServerLoc, mode) { questiondata: {}, eventdata: [], useragent: "", - mode: "" + mode: "", + didAlready: false, + always_show_instructions: true, + num_conds: 1, + num_counters: 1, + num_conds_completed: 0 }, initialize: function() {