from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) from otree.db.models import ForeignKey from slider_task.models import BaseSlider, SliderPlayer from decimal import Decimal import random, csv, math, decimal author = 'Frauke Stehr' doc = """ Treatment 1: Individual; Treatment 2: Risk; Treatment 3: Harm; Treatment 4: Responsibility Experiment with Compensation for Externalities. For SLIDER TASK (See https://github.com/chkgk/slider_task for more info) """ ##################################### Defining global functions (for all classes) ################################### # define rounding as ceiling function for decimal objects decimal.getcontext().rounding = decimal.ROUND_CEILING ## define functions to create fields with some characteristics more efficiently ## def make_integer_field(): # create an integer field with minimum value 0 return models.IntegerField( min=0, label='', ) def make_control_field(): # create field for second attempt of answering the control questions return models.IntegerField( doc="if the answer to control question qi was wrong, second attempt", blank=True, label='', ) def make_likert_field(label): # create a Likert type field with a 10 point scale form 0 to 10, with the argument 'label' choices = range(0, 11) return models.IntegerField( choices=choices, widget=widgets.RadioSelect, label=label, doc="Likert-scale field going from 0 (Completely disagree) to 10 (Completely agree). " "Market fairness items taken from Bartling et al. 2018." "Risk preference and altruism item from Preference Survey Module (Falk et al. 2016)" ) ################################################# CONSTANTS CLASS ##################################################### class Constants(BaseConstants): name_in_url = 'CompensationBase' players_per_group = None # set to None as groups are manually coded in create_session() ### Parameters which determine the game structure num_rounds_BDM = 4 # number of rounds in the BDM task (with one WTP elicited in each round) num_rounds_part1 = 12 # number of rounds in the buying game without compensation possibility num_rounds_BDM_part_1 = num_rounds_BDM + num_rounds_part1 # number of rounds for BDM and part 1 num_rounds_part2 = 12 # number of rounds in the buying game with compensation possibility num_rounds = num_rounds_BDM + num_rounds_part1 + num_rounds_part2 # total number of rounds ### Passwords needed to continue to next round after reading instructions: password1 = 2971 password2 = 1942 password3 = 6076 password4 = 4577 password5 = 7903 ### Game Parameters for the experiment wage_trialsliders = c(100) # wage for the sliders that have to be solved in the beginning of the experiment maxWTP = 100 # upper bound for the stated willingness to pay in the BDM task endowment = c(75) # endowment every player gets every round value = c(100) # value of the product if bought maxsliders = 240 # maximum number of sliders that have to be solved at the end of the experiment (externality) minsliders = int(maxsliders/2) # lower number of sliders in the risk treatment trialsliders = int(maxsliders/2) # number of sliders that have to be solved in the beginning of the experiment BDMsliders = [int(minsliders/2), minsliders, int(1.5*minsliders), maxsliders] # list of different slider numbers for which BDM is done minprice = c(1) # minimum possible price of the product maxprice = c(100) # maximum possible price of the product SlidersPerPoint = 8 # price of compensation: 1 point invested reduces number of sliders by SlidersPerPoint sliders num_harmed = 2 # number of harmed people in the diffusion of harm treatment # slider_columns = 3 # uncomment this if you want sliders in the slider task to be displayed in multiple columns ### Get control questions, answers, scenarions and helptexts from the csv file with open('compensation_beliefs/ControlQuestions.csv') as questions_file: csv_questions = list(csv.DictReader(questions_file, delimiter=';')) def bycol_decl(lod, keylist): # define a function that converts a list of dictionaries to a list of lists return [[row[key] for row in lod] for key in keylist] keylist = ['ID', 'scenario_ind', 'question_ind', 'solution_ind', 'helptext_ind', 'scenario_risk', 'question_risk', 'solution_risk', 'helptext_risk', 'scenario_harm', 'question_harm', 'solution_harm', 'helptext_harm', 'scenario_resp', 'question_resp', 'solution_resp', 'helptext_resp'] question_file = bycol_decl(csv_questions, keylist) # using the specified keylist convert dictionairy into list of lists scenario_ind = question_file[1] # store each column of csv file (list of lists) in the Constants class as separate lists question_ind = question_file[2] solution_ind = question_file[3] helptext_ind = question_file[4] scenario_risk = question_file[5] question_risk = question_file[6] solution_risk = question_file[7] helptext_risk = question_file[8] scenario_harm = question_file[9] question_harm = question_file[10] solution_harm = question_file[11] helptext_harm = question_file[12] scenario_resp = question_file[13] question_resp = question_file[14] solution_resp = question_file[15] helptext_resp = question_file[16] ################################################# SUBSESSION CLASS ##################################################### class Subsession(BaseSubsession): ### create columns in the data set for parameters that are constant throughout the game payment_round = models.IntegerField(min=1, max=Constants.num_rounds, doc="round that is randomly selected for payment") part = models.IntegerField(choices=[[0, 'BDM rounds'], [1, 'Baseline without Compensation'], [2, 'Game with Compensation Stage']], doc='Experimental Parts') euro_per_point = models.FloatField() value_of_product = models.CurrencyField() endowment = models.CurrencyField() def creating_session(self): # store Constants parameters in dataset self.euro_per_point = self.session.config['real_world_currency_per_point'] self.value_of_product = Constants.value self.endowment = Constants.endowment if self.round_number <= Constants.num_rounds_BDM: self.part = 0 elif self.round_number <= Constants.num_rounds_BDM_part_1: self.part = 1 elif self.round_number <= Constants.num_rounds: self.part = 2 # assign group matrices depending on experimental mode (pilot, experiment or testing) # In the group matrices, every tuple within square brackets constitutes one group (e.g. [1,4] is group 1 if the group matrix structure1 is used. mode = self.session.config['mode'] if mode == 'pilot': # set group matrix with only groups of 2 for 'Individual' treatment that is run in the pilot structure1 = [[1, 4], [2, 5], [3, 6], [7, 10], [8, 11], [9, 12], [13, 16], [14, 17], [15, 18], [19, 22], [20, 23], [21, 24], [25, 28], [26, 29], [27, 30]] structure2 = [[1, 6], [2, 4], [3, 5], [7, 12], [8, 10], [9, 11], [13, 18], [14, 16], [15, 17], [19, 24], [20, 22], [21, 23], [25, 30], [26, 28], [27, 29]] structure3 = [[1, 5], [2, 6], [3, 4], [7, 11], [8, 12], [9, 10], [13, 17], [14, 18], [15, 16], [19, 23], [20, 24], [21, 22], [25, 29], [26, 30], [27, 28]] if self.session.num_participants == 24: # remove the last 3 items from the group matrix del structure1[12:15] del structure2[12:15] del structure3[12:15] if self.session.num_participants == 18: # remove the last 6 items from the group matrix del structure1[9:15] del structure2[9:15] del structure3[9:15] # assign different group matrices to different rounds if self.round_number == 1 or (self.round_number - 1) % 3 == 0: # if round_number-1 is divisible by 3 without remainder use structure1 self.set_group_matrix(structure1) if self.round_number == 2 or (self.round_number + 1) % 3 == 0: # if round_number+1 is divisible by 3 without remainder use structure2 self.set_group_matrix(structure2) if self.round_number == 3 or self.round_number % 3 == 0: # if round_number is divisible by 3 without remainder use structure3 self.set_group_matrix(structure3) if mode == 'experiment': # set group matrix with different group sizes to accommodate all treatments treatments_played = self.session.config['treatments_played'] # get the code 'treatments_played' from the session configuration (possible values are 1234, 123, 124, 234, 134) structure1 = [[1, 4], [2, 5], [3, 6], [7, 10], [8, 11], [9, 12], [13, 16, 17], [14, 18, 19], [15, 20, 21], [22, 23, 28], [24, 25, 29], [26, 27, 30]] structure2 = [[1, 6], [2, 4], [3, 5], [7, 12], [8, 10], [9, 11], [14, 16, 21], [15, 18, 17], [13, 20, 19], [24, 23, 30], [26, 25, 28], [22, 27, 29]] structure3 = [[1, 5], [2, 6], [3, 4], [7, 11], [8, 12], [9, 10], [15, 16, 19], [13, 18, 21], [14, 20, 17], [26, 23, 29], [22, 25, 30], [24, 27, 28]] ### depending on which treatments are run, the subject number needs to be different and the group matrices are adjusted accordingly if treatments_played == "134" or treatments_played == "234": assert self.session.num_participants == 24 del structure1[3:13] del structure2[3:13] del structure3[3:13] append1 = [[7, 10, 11], [8, 12, 13], [9, 14, 15], [16, 17, 22], [18, 19, 23], [20, 21, 24]] append2 = [[8, 10, 15], [9, 12, 11], [7, 14, 13], [18, 17, 24], [20, 19, 22], [16, 21, 23]] append3 = [[9, 10, 13], [7, 12, 15], [8, 14, 11], [20, 17, 23], [16, 19, 24], [18, 21, 22]] for i in append1: structure1.append(i) for i in append2: structure2.append(i) for i in append3: structure3.append(i) elif treatments_played == "123" or treatments_played == "124": # remove the last 3 items from the group matrix assert self.session.num_participants == 21 del structure1[9:13] del structure2[9:13] del structure3[9:13] elif treatments_played == "34": # replace old group matrix with new one, based on two cohorts of 9 participants each assert self.session.num_participants == 18 del structure1[0:13] del structure2[0:13] del structure3[0:13] append1 = [[1, 4, 5], [2, 6, 7], [3, 8, 9], [10, 11, 16], [12, 13, 17], [14, 15, 18]] append2 = [[2, 4, 9], [3, 6, 5], [1, 8, 7], [12, 11, 18], [14, 13, 16], [10, 15, 17]] append3 = [[3, 4, 7], [1, 6, 9], [2, 8, 5], [14, 11, 17], [10, 13, 18], [12, 15, 16]] for i in append1: structure1.append(i) for i in append2: structure2.append(i) for i in append3: structure3.append(i) elif treatments_played == "13" or treatments_played == "14": # replace old group matrix with new one, based on two cohorts of 9 and participants respectively assert self.session.num_participants == 15 del structure1[3:13] del structure2[3:13] del structure3[3:13] append1 = [[7, 10, 11], [8, 12, 13], [9, 14, 15]] append2 = [[8, 10, 15], [9, 12, 11], [7, 14, 13]] append3 = [[9, 10, 13], [7, 12, 15], [8, 14, 11]] for i in append1: structure1.append(i) for i in append2: structure2.append(i) for i in append3: structure3.append(i) ### change the group matrices in every round if self.round_number == 1 or (self.round_number - 1) % 3 == 0: # if round_number-1 is divisible by 3 without remainder use structure1 self.set_group_matrix(structure1) if self.round_number == 2 or (self.round_number + 1) % 3 == 0: # if round_number+1 is divisible by 3 without remainder use structure1 self.set_group_matrix(structure2) if self.round_number == 3 or self.round_number % 3 == 0: # if round_number is divisible by 3 without remainder use structure1 self.set_group_matrix(structure3) if mode == 'follow-up': num_groups = self.session.config['num_groups'] structure1 = [[1, 2, 7], [3, 4, 8], [5, 6, 9], [10, 11, 16], [12, 13, 17], [14, 15, 18], [19, 20, 25], [21, 22, 26], [23, 24, 27]] structure2 = [[3, 2, 9], [5, 4, 7], [1, 6, 8], [12, 11, 18], [14, 13, 16], [10, 15, 17], [21, 20, 27], [23, 22, 25], [19, 24, 26]] structure3 = [[5, 2, 8], [1, 4, 9], [3, 6, 7], [14, 11, 17], [10, 13, 18], [12, 15, 16], [23, 20, 26], [19, 22, 27], [21, 24, 25]] if num_groups == 3: assert self.session.num_participants == 27 if num_groups == 2: assert self.session.num_participants == 18 del structure1[6:9] del structure2[6:9] del structure3[6:9] if num_groups == 1: assert self.session.num_participants == 9 del structure1[3:9] del structure2[3:9] del structure3[3:9] ### change the group matrices in every round if self.round_number == 1 or ( self.round_number - 1) % 3 == 0: # if round_number-1 is divisible by 3 without remainder use structure1 self.set_group_matrix(structure1) if self.round_number == 2 or ( self.round_number + 1) % 3 == 0: # if round_number+1 is divisible by 3 without remainder use structure1 self.set_group_matrix(structure2) if self.round_number == 3 or self.round_number % 3 == 0: # if round_number is divisible by 3 without remainder use structure1 self.set_group_matrix(structure3) ### Assign the treatments and the sliders per point to each player depending on the mode that is played and the treatments you want to run for p in self.get_players(): if mode == 'pilot' or 'testing': # if mode is pilot or testing Individual treatment is run p.treatment = 'Individual' if mode == 'experiment': # assign to treatments using p.participant.id_in_session treatments_played = self.session.config['treatments_played'] # get the code 'treatments_played' from the session configuration (possible values are 1234, 123, 124, 234, 134) if treatments_played == '1234': # assign to all 4 treatments: Individual, Risk, Harm and Responsibility if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 13: p.treatment = "Risk" elif p.participant.id_in_session < 22: p.treatment = "Harm" elif p.participant.id_in_session < 31: p.treatment = "Responsibility" elif treatments_played == '123': # assign to treatments Individual, Risk, and Harm if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 13: p.treatment = "Risk" elif p.participant.id_in_session < 22: p.treatment = "Harm" elif treatments_played == '124': # assign to treatments Individual, Risk, and Responsibility if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 13: p.treatment = "Risk" elif p.participant.id_in_session < 22: p.treatment = "Responsibility" elif treatments_played == '134': # assign to treatments Individual, Harm, and Responsibility if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 16: p.treatment = "Harm" elif p.participant.id_in_session < 25: p.treatment = "Responsibility" elif treatments_played == '234': # assign to treatments Risk, Harm, and Responsibility if p.participant.id_in_session < 7: p.treatment = "Risk" elif p.participant.id_in_session < 16: p.treatment = "Harm" elif p.participant.id_in_session < 25: p.treatment = "Responsibility" elif treatments_played == '34': # assign to treatments Harm and Responsibility if p.participant.id_in_session < 10: p.treatment = "Harm" elif p.participant.id_in_session < 19: p.treatment = "Responsibility" elif treatments_played == '13': # assign to treatments Individual and Harm if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 16: p.treatment = "Harm" elif treatments_played == '14': # assign to treatments Individual and Responsibility if p.participant.id_in_session < 7: p.treatment = "Individual" elif p.participant.id_in_session < 16: p.treatment = "Responsibility" if mode == 'follow-up': p.treatment = 'Responsibility' ### store sliders per point in data set if p.treatment == 'Harm': p.sliders_per_point = math.ceil(Constants.SlidersPerPoint / Constants.num_harmed) # for harm treatment the sliders per point spent are split equally among all harmed people else: p.sliders_per_point = Constants.SlidersPerPoint ### store player type in data set if p.id_in_group == 1: p.type = 'Buyer' elif p.id_in_group == 2 and p.treatment == "Responsibility": p.type = 'Buyer2' elif p.id_in_group == 3 and p.treatment == 'Harm': p.type = 'Harmed' else: p.type = 'Harmed' if self.round_number == 1: # assign a random payment period paying_round = random.randint(1, Constants.num_rounds) self.session.vars['paying_round'] = paying_round #print('set the paying round to', paying_round) # Randomize prices on participant level (assigning one price for entire group is done in group class below) possible_BDM_prices = list(range(int(0), int(Constants.maxWTP))) possible_slider_numbers = [Constants.minsliders, Constants.maxsliders] possible_prices = list(range(int(Constants.minprice), int(Constants.maxprice))) for p in self.get_players(): p.participant.vars['final_sliders'] = 0 # initialize participant variables p.participant.vars['rounded_final_euro_payoff'] = 0.00 BDM_price = random.choices(possible_BDM_prices, k=Constants.num_rounds_BDM) # draw BDM prices with replacement p.participant.vars['BDM_price'] = BDM_price #print('set the BDM prices to', BDM_price) # randomized_prices = random.sample(possible_prices, Constants.num_rounds) # draw prices without replacement randomized_prices = random.choices(possible_prices, k=Constants.num_rounds) # draw prices with replacement p.participant.vars['prices'] = randomized_prices if p.treatment == 'Risk': # define a list of slider numbers for each player for each round for risk treatment randomized_slider_numbers = random.choices(possible_slider_numbers, k=Constants.num_rounds) # draw slider numbers with replacement print(randomized_slider_numbers) p.participant.vars['random_sliders'] = randomized_slider_numbers self.payment_round = self.session.vars['paying_round'] # save payment round to data set - creates a record in every round def vars_for_admin_report(self): # define variables to use in report sheet participants = [p for p in self.session.get_participants()] return { 'participants': participants, 'participation_fee': self.session.config['participation_fee'], } ################################################# GROUP CLASS ####################################################### class Group(BaseGroup): ### Set fields in Group Table buy_decision = models.BooleanField(choices=((True, 'Yes'), (False, 'No')), widget=widgets.RadioSelectHorizontal, doc="True, if product is bought", label="Would you like to buy the product?" ) buy_decision_buyer2 = models.BooleanField(choices=((True, 'Yes'), (False, 'No')), widget=widgets.RadioSelectHorizontal, doc="True, if product is bought", label="Would you like to buy the product?" ) buy_belief = models.IntegerField(min=0, max=100, doc="Buyer's belief of buyer 2's probability of buying") buy_belief_buyer2 = models.IntegerField(min=0, max=100, doc="Buyer's belief of buyer 2's probability of buying") buy_belief_harmed = models.BooleanField(choices=((True, 'Yes'), (False, 'No')), widget=widgets.RadioSelectHorizontal, doc="True, if Harmed player believes product is bought", label="Do you believe the buyer is going to buy the product at this price?" ) buy_belief_harmed2 = models.BooleanField(choices=((True, 'Yes'), (False, 'No')), widget=widgets.RadioSelectHorizontal, doc="True, if Harmed player believes product is bought", label="Do you believe the buyer is going to buy the product at this price?" ) compensation_decision = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders/Constants.SlidersPerPoint), doc="Buyer's compensation choice") compensation_decision_buyer2 = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders/Constants.SlidersPerPoint), doc="Buyer 2's compensation choice") compensation_belief = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders / Constants.SlidersPerPoint), blank=True, doc="Buyer 1's belief of buyer's compensation choice") compensation_belief_buyer2 = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders / Constants.SlidersPerPoint), blank=True, doc="Buyer 2's belief of buyer's compensation choice") compensation_belief_harmed = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders/Constants.SlidersPerPoint), doc="Harmed player's belief of buyer's compensation choice") compensation_belief_harmed2 = models.IntegerField(min=0, max=math.ceil(Constants.maxsliders / Constants.SlidersPerPoint), doc="Second harmed player's belief of buyer's compensation choice") information_taken = models.BooleanField(doc='True if in risk treatment, ' 'Buyer clicks on info box to reveal number of sliders', blank=True) ### Get current random price for buyers and assign same price to harmed in the same group def current_price(self): players = self.get_players() buyer = self.get_player_by_role('Person A') harmed = self.get_player_by_role('Person B') for p in players: if self.round_number == 1 and p.treatment == 'Responsibility': # intitialize participant variable for responsibility treatment buyer2 = self.get_player_by_role('Person A2') buyer2.participant.vars['buying_decision_buyer2'] = 0 buyer2.participant.vars['compensation_decision_buyer2'] = 0 if self.round_number <= Constants.num_rounds_BDM: # if current round is a BDM round, set slider number and BDM price on player level p.BDM_slider_number = Constants.BDMsliders[self.round_number - 1] p.BDM_price = p.participant.vars['BDM_price'][self.round_number - 1] if self.round_number > Constants.num_rounds_BDM: # if current round is a game round set, ex ante slider number and price on group level buyer.price = p.participant.vars['prices'][self.round_number - 1] harmed.price = buyer.price # set price for harmed person equal to the buyer's price within a group ### set ex ante number of sliders for all treatments buyer.num_sliders_ex_ante = 0 if p.treatment == "Individual": # set ex ante sliders for harmed in Individual treatment to maxsliders harmed.num_sliders_ex_ante = Constants.maxsliders if p.treatment == 'Risk': # set ex ante sliders for harmed in Risk treatment to the element in the list of randomly drawn sliders numbers harmed.num_sliders_ex_ante = p.participant.vars['random_sliders'][self.round_number - 1] if p.treatment == "Harm": # set ex ante sliders for harmed in Harm treatment to maxsliders/num_harmed to keep aggregate externality size constant harmed2 = self.get_player_by_role('Person B2') harmed2.price = buyer.price # set price for the second harmed player equal to the price of the buyer harmed.num_sliders_ex_ante = math.ceil(Constants.maxsliders / Constants.num_harmed) # The ceiling function is necessary in case the maximum number of sliders is not divisible by the number of harmed players in a group harmed2.num_sliders_ex_ante = harmed.num_sliders_ex_ante if p.treatment == "Responsibility": # set ex ante sliders for harmed (buyer2) in Responsibility treatment to maxsliders (zero) buyer2 = self.get_player_by_role('Person A2') buyer2.price = buyer.price buyer2.num_sliders_ex_ante = 0 harmed.num_sliders_ex_ante = Constants.maxsliders ### Set payoffs for all players and all treatments def set_payoffs(self): buyer = self.get_player_by_role('Person A') harmed = self.get_player_by_role('Person B') buyer.num_sliders_ex_post = 0 for p in self.get_players(): if self.subsession.round_number <= Constants.num_rounds_BDM: # Defining the BDM payoffs if p.sliders_WTP < p.participant.vars['BDM_price'][self.round_number - 1]: p.current_payoff = Constants.wage_trialsliders p.num_sliders_ex_post = p.BDM_slider_number if p.sliders_WTP >= p.participant.vars['BDM_price'][self.round_number - 1]: p.current_payoff = Constants.wage_trialsliders - p.BDM_price p.num_sliders_ex_post = 0 if self.subsession.round_number > Constants.num_rounds_BDM: # Defining the payoffs for the buying game harmed.current_payoff = Constants.endowment # Defining the harmed player's payoff for all treatments and all subsequent rounds if p.treatment == 'Individual': # Defining the payoffs for the Individual treatment if self.subsession.round_number <= (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 2 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante if self.subsession.round_number > (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 3 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price - self.compensation_decision sliders_after_comp = harmed.num_sliders_ex_ante - self.compensation_decision * Constants.SlidersPerPoint # Defining the number of sliders after compensation if sliders_after_comp < 0: # define a rule for the case if there is surplus compensation harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp if not self.buy_decision: # Defining the payoffs if we are either in Part 2 or 3 and the buyer did not buy buyer.current_payoff = Constants.endowment harmed.num_sliders_ex_post = 0 if p.treatment == 'Risk': # Payoffs for the risk treatment are the same as in Individual but the number of sliders depends on random draw if self.subsession.round_number <= (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 2 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante if self.subsession.round_number > (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 3 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price - self.compensation_decision sliders_after_comp = harmed.num_sliders_ex_ante - self.compensation_decision * Constants.SlidersPerPoint # Defining the number of sliders after compensation if sliders_after_comp < 0: harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp if not self.buy_decision: # Defining the payoffs if we are either in Part 2 or 3 and the buyer did not buy buyer.current_payoff = Constants.endowment harmed.num_sliders_ex_post = 0 if p.treatment == 'Harm': # Defining the payoffs for the Diffused Harm treatment harmed2 = self.get_player_by_role('Person B2') if self.subsession.round_number <= (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 2 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante if self.subsession.round_number > (Constants.num_rounds_part1 + Constants.num_rounds_BDM) and self.buy_decision: # Defining the payoffs if we are in Part 3 and the buyer bought buyer.current_payoff = Constants.endowment + Constants.value - buyer.price - self.compensation_decision sliders_after_comp = harmed.num_sliders_ex_ante - math.ceil((self.compensation_decision * Constants.SlidersPerPoint) / Constants.num_harmed) # Defining the number of sliders after compensation if sliders_after_comp < 0: harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp if not self.buy_decision: # Defining the payoffs if we are either in Part 2 or 3 and the buyer did not buy buyer.current_payoff = Constants.endowment harmed.num_sliders_ex_post = 0 harmed2.current_payoff = harmed.current_payoff # Copying current_payoff and number of sliders to the second harmed player harmed2.num_sliders_ex_post = harmed.num_sliders_ex_post if p.treatment == 'Responsibility': # Defining the payoffs for the Diffused Responsibility treatment buyer2 = self.get_player_by_role('Person A2') buyer2.num_sliders_ex_post = 0 if self.subsession.round_number <= (Constants.num_rounds_part1 + Constants.num_rounds_BDM): # Defining the payoffs if we are in Part 2 if self.buy_decision and self.buy_decision_buyer2: # set payoffs and sliders if both buyers buy buyer.current_payoff = Constants.endowment + Constants.value - buyer.price buyer2.current_payoff = Constants.endowment + Constants.value - buyer2.price harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante elif self.buy_decision and not self.buy_decision_buyer2: # set payoffs and sliders if only buyer 1 buys buyer.current_payoff = Constants.endowment + Constants.value - buyer.price buyer2.current_payoff = Constants.endowment harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante elif not self.buy_decision and self.buy_decision_buyer2: # set payoffs and sliders if only buyer 2 buys buyer.current_payoff = Constants.endowment buyer2.current_payoff = Constants.endowment + Constants.value - buyer2.price harmed.num_sliders_ex_post = harmed.num_sliders_ex_ante elif not self.buy_decision and not self.buy_decision_buyer2: # set payoffs and sliders if no buyer buys buyer.current_payoff = Constants.endowment buyer2.current_payoff = Constants.endowment harmed.num_sliders_ex_post = 0 if self.subsession.round_number > (Constants.num_rounds_part1 + Constants.num_rounds_BDM): # Defining the payoffs if we are in Part 3 if self.buy_decision and self.buy_decision_buyer2: # set payoffs and sliders if both buyers buy buyer.current_payoff = Constants.endowment + Constants.value - buyer.price - self.compensation_decision buyer2.current_payoff = Constants.endowment + Constants.value - buyer2.price - self.compensation_decision_buyer2 sliders_after_comp = harmed.num_sliders_ex_ante - ( self.compensation_decision + self.compensation_decision_buyer2) * Constants.SlidersPerPoint if sliders_after_comp < 0: harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp elif self.buy_decision and not self.buy_decision_buyer2: # set payoffs and sliders if only buyer 1 buys buyer.current_payoff = Constants.endowment + Constants.value - buyer.price - self.compensation_decision buyer2.current_payoff = Constants.endowment sliders_after_comp = harmed.num_sliders_ex_ante - self.compensation_decision * Constants.SlidersPerPoint if sliders_after_comp < 0: harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp elif not self.buy_decision and self.buy_decision_buyer2: # set payoffs and sliders if only buyer 2 buys buyer.current_payoff = Constants.endowment buyer2.current_payoff = Constants.endowment + Constants.value - buyer2.price - self.compensation_decision_buyer2 sliders_after_comp = harmed.num_sliders_ex_ante - self.compensation_decision_buyer2 * Constants.SlidersPerPoint if sliders_after_comp < 0: harmed.num_sliders_ex_post = 0 else: harmed.num_sliders_ex_post = sliders_after_comp elif not self.buy_decision and not self.buy_decision_buyer2: # set payoffs and sliders if no buyer buys buyer.current_payoff = Constants.endowment buyer2.current_payoff = Constants.endowment harmed.num_sliders_ex_post = 0 ### Defining payoff variable for payment file based on behavior in random paying_round and store all decision relevant variables in the participant variables if self.subsession.round_number == self.session.vars['paying_round']: p.payoff = p.current_payoff p.final_sliders = p.num_sliders_ex_post p.participant.vars['final_sliders'] = p.final_sliders p.participant.vars['final_payoff'] = p.payoff euro_payoff = p.payoff.to_real_world_currency(self.session) p.participant.vars['rounded_final_euro_payoff'] = '%.2f' % Decimal(euro_payoff).quantize(Decimal("1.0")) # convert final euro payoff in Decimal with 2 decimals print('buyer final sliders are', buyer.num_sliders_ex_post) print('harmed final sliders are', harmed.num_sliders_ex_post) ### storing payoff relevant variables in participant vars to have access to them on the payment info screen if self.session.vars['paying_round'] <= Constants.num_rounds_BDM: # saving the variables if the payment round is a BDM round p.participant.vars['BDM_final_price'] = p.BDM_price p.participant.vars['BDM_final_sliders'] = p.BDM_slider_number # store the ex ante number of BDM sliders in the payment round in participant vars to display it later p.participant.vars['sliders_WTP'] = p.sliders_WTP p.participant.vars['buying_decision'] = None p.participant.vars['buying_price'] = None if self.session.vars['paying_round'] > Constants.num_rounds_BDM: # saving the variables if the payment round is a game round p.participant.vars['BDM_final_sliders'] = None p.participant.vars['BDM_final_price'] = None p.participant.vars['sliders_WTP'] = None p.participant.vars['buying_decision'] = self.buy_decision p.participant.vars['buying_price'] = p.price if self.session.vars['paying_round'] > (Constants.num_rounds_BDM + Constants.num_rounds_part1): p.participant.vars['compensation'] = self.compensation_decision if self.session.vars['paying_round'] <= (Constants.num_rounds_BDM + Constants.num_rounds_part1): p.participant.vars['compensation'] = None if p.treatment == "Responsibility": p.participant.vars['buying_decision_buyer2'] = self.buy_decision_buyer2 # save decisions of buyer 2 in participant variables for payment screen p.participant.vars['compensation_decision_buyer2'] = self.compensation_decision_buyer2 if self.session.vars['paying_round'] > (Constants.num_rounds_BDM + Constants.num_rounds_part1): # calculate total compensation and save in participant variables for payment screen if self.buy_decision and self.buy_decision_buyer2: p.participant.vars['total_compensation'] = self.compensation_decision + self.compensation_decision_buyer2 if self.buy_decision and not self.buy_decision_buyer2: p.participant.vars['total_compensation'] = self.compensation_decision if not self.buy_decision and self.buy_decision_buyer2: p.participant.vars['total_compensation'] = self.compensation_decision_buyer2 if not self.buy_decision and not self.buy_decision_buyer2: p.participant.vars['total_compensation'] = 0 else: p.participant.vars['total_compensation'] = None else: # if current round is not paying round, set the payoff and final sliders of the player to 0 p.payoff = c(0) p.final_sliders = 0 ################################################# PLAYER CLASS ####################################################### class Player(SliderPlayer): # Set fields in Player Table: BACKGROUND VARIABLES FIELD password = models.IntegerField(doc='password that is typed in, in order to continue after the instructions') BDM_slider_number = models.IntegerField(doc='number of sliders that are displayed in the current BDM version') BDM_price = models.CurrencyField(doc='randomly generated price for the BDM') sliders_WTP = models.IntegerField(min=0, max=Constants.maxWTP, doc='BDM elicited WTP to avoid another 100 or 200 sliders') # Set fields in Player Table: GAME FIELDS type = models.StringField(doc="records if a player is a buyer or a harmed person") sliders_per_point = models.IntegerField() price = models.CurrencyField() num_sliders_ex_ante = models.IntegerField(min=0, max=Constants.maxsliders, doc="ex ante number of sliders a player is assigned in the respective round") num_sliders_ex_post = models.IntegerField(min=0, max=Constants.maxsliders, doc="records the effective number of " "sliders after the buying and compensation " "decision.") final_sliders = models.IntegerField(min=0, max=Constants.maxsliders, doc= "final number of sliders a player has to solve") current_payoff = models.CurrencyField(doc="Player's payoff in the current round") treatment = models.StringField() slider_solving_time = models.FloatField(doc="time in seconds needed to solve the slider task") # Set fields in Player Table: CONTROL QUESTION FIELDS. q1_answer_1 = make_integer_field() q2_answer_1 = make_integer_field() q3_answer_1 = make_integer_field() q4_answer_1 = make_integer_field() q5_answer_1 = make_integer_field() q6_answer_1 = make_integer_field() q7_answer_1 = make_integer_field() q8_answer_1 = make_integer_field() q1_answer_2 = make_control_field() q2_answer_2 = make_control_field() q3_answer_2 = make_control_field() q4_answer_2 = make_control_field() q5_answer_2 = make_control_field() q6_answer_2 = make_control_field() q7_answer_2 = make_control_field() q8_answer_2 = make_control_field() q1_correct = models.BooleanField() q2_correct = models.BooleanField() q3_correct = models.BooleanField() q4_correct = models.BooleanField() q5_correct = models.BooleanField() q6_correct = models.BooleanField() q7_correct = models.BooleanField() q8_correct = models.BooleanField() time_on_first_control_question_screen = models.FloatField(doc="time in seconds spent on screen ControlQuestions") time_on_second_control_question_screen = models.FloatField(doc="time in seconds spent on screen ControlQuestionsCorrected") sum_correct = models.IntegerField() # Set fields in Player Table: QUESTIONNAIRE Fields. gender = models.IntegerField(choices=[[1, 'Female'], [2, 'Male'], [3, 'Other'], [4, 'No answer']], widget=widgets.RadioSelectHorizontal, doc='Gender, 1 if female, 2 if male, 3 if other, 4 if no answer') age = models.IntegerField(min=17, doc='age in years') # economics_student = models.BooleanField(choices=((True, 'Economics or Business'), (False, 'Other')), # widget=widgets.RadioSelectHorizontal, # doc='true if field of study is business or economics') risk_preference = make_likert_field('How do you see yourself: are you a person who is generally willing to take ' 'risks, or do you try to avoid taking risks? Please use a scale from 0 to 10, ' 'where a 0 means you are “completely unwilling to take risks” and a 10 means ' 'you are “very willing to take risks”. You can also use the values in between ' 'to indicate where you fall on the scale.') altruism = make_likert_field('How do you assess your willingness to share with others without expecting anything in ' 'return when it comes to charity? Please use a scale from 0 to 10, where 0 means you ' 'are “completely unwilling to share” and a 10 means you are “very willing to share”. ' 'You can also use the values in between to indicate where you fall on the scale.') responsible_consumption = models.IntegerField(choices=[[0, 'don\'t know, no answer'], [1, 'Never'], [2, 'Sometimes'], [3, 'Often'], [4, 'Always']], widget=widgets.RadioSelectHorizontal, doc='Responsible consumption item from Bartling et al. 2018') responsible_purchase = models.IntegerField(min=100, doc='WTP for responsible purchase item from Bartling et al. 2018') offsetting_behavior = models.IntegerField(choices=[[0, 'don\'t know, no answer'], [1, 'Never'], [2, 'Sometimes'], [3, 'Often'], [4, 'Always']], widget=widgets.RadioSelectHorizontal, doc='from 0 (no answer) to 4 (always) frequency of past carbon offsetting behavior') donation_behavior = models.IntegerField(min=0, doc='donation to charitable causes per year, Bartling et al. 2018') # political_orientation = make_likert_field('Where would you classify yourself on the left/right political spectrum? ' # 'Please use a scale from 0 to 10, where 0 means “left” and a 10 means “right”.') market_fairness_1 = make_likert_field('The free market system is a fair system.') market_fairness_2 = make_likert_field('Common or “normal” business practices must be fair, or they would not survive.') market_fairness_3 = make_likert_field('Acting in response to market forces is not always a fair way to conduct business.') market_fairness_4 = make_likert_field('In free market systems, people tend to get the outcomes they deserve.') market_fairness_5 = make_likert_field('Profitable businesses tend to be more morally responsible than unprofitable businesses.') market_fairness_6 = make_likert_field('Economic markets do not fairly reward people.') ### Define a function check_correct which compares the entered answer in the control questions with the correct answer and counts the number of correct answers def check_correct(self): submitted_answers = [self.q1_answer_1, self.q2_answer_1, self.q3_answer_1, self.q4_answer_1, self.q5_answer_1, self.q6_answer_1, self.q7_answer_1, self.q8_answer_1] answers_correct = [] for i in range(0,len(submitted_answers)): # Check whether answers are correct for all treatments (part 1 and part2), # OBS!!! this is assuming there are 8 control questions for all treatments for part 1, otherwise adjust in iterator if self.treatment == 'Individual': # Creates a list of ones (zeros) if the answer is correct (false) if self.round_number == Constants.num_rounds_BDM + 1: if int(submitted_answers[i]) == int(Constants.solution_ind[i]): # comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.round_number == Constants.num_rounds_BDM + Constants.num_rounds_part1 + 1: if int(submitted_answers[i]) == int(Constants.solution_ind[8+i]): # the control questions for part 3 start in row 9; comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.treatment == 'Risk': if self.round_number == Constants.num_rounds_BDM + 1: if int(submitted_answers[i]) == int(Constants.solution_risk[i]): # comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.round_number == Constants.num_rounds_BDM + Constants.num_rounds_part1 + 1: if int(submitted_answers[i]) == int(Constants.solution_risk[8+i]): # the control questions for part 3 start in row 9; comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.treatment == 'Harm': if self.round_number == Constants.num_rounds_BDM + 1: if int(submitted_answers[i]) == int(Constants.solution_harm[i]): # comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.round_number == Constants.num_rounds_BDM + Constants.num_rounds_part1 + 1: if int(submitted_answers[i]) == int(Constants.solution_harm[8+i]): # the control questions for part 3 start in row 9; comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.treatment == 'Responsibility': if self.round_number == Constants.num_rounds_BDM + 1: if int(submitted_answers[i]) == int(Constants.solution_resp[i]): # comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) if self.round_number == Constants.num_rounds_BDM + Constants.num_rounds_part1 + 1: if int(submitted_answers[i]) == int(Constants.solution_resp[8+i]): # the control questions for part 3 start in row 9; comparing the submitted answer to the respective element from the csv file answers_correct.append(1) else: answers_correct.append(0) # stores whether the answer is correct or not (correct = 1, false = 0) for each player on the respective field by taking the element from the created list answers_correct self.q1_correct = answers_correct[0] self.q2_correct = answers_correct[1] self.q3_correct = answers_correct[2] self.q4_correct = answers_correct[3] self.q5_correct = answers_correct[4] self.q6_correct = answers_correct[5] self.q7_correct = answers_correct[6] self.q8_correct = answers_correct[7] self.sum_correct = sum(answers_correct) # counts the number of correct answers and stores them in the data set def role(self): # Define roles for players based on the id in group. if self.id_in_group == 1: return 'Person A' elif self.id_in_group == 2 and self.treatment == "Responsibility": return 'Person A2' elif self.id_in_group == 3 and self.treatment == 'Harm': return 'Person B2' else: return 'Person B' class Slider(BaseSlider): # Class that is needed for the slider task player = ForeignKey(Player, on_delete=models.CASCADE, )