from otree.api import * from decimal import * from settings import SESSION_CONFIG_DEFAULTS, SESSION_CONFIGS, PARTICIPANT_FIELDS author = "Nathaniel Archer Lawrence, LEMMA, Université Panthéon-Assas - Paris II" doc = """ Consumption simulation with interface based off of 'Shopping app (online grocery store)' from oTree demos, see: . """ def read_csv(): import csv f = open(__name__ + '/catalog.csv', encoding='utf-8-sig') rows = [row for row in csv.DictReader(f)] for row in rows: # all values in CSV are string unless you convert them row['unit_price'] = float(row['unit_price']) return rows class C(BaseConstants): NAME_IN_URL = 's' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 NUM_QUESTIONS = 6 + 2 # task constants NUM_PERIODS = 120 INITIAL_ENDOWMENT = SESSION_CONFIG_DEFAULTS['initial_endowment'] INCOME = SESSION_CONFIG_DEFAULTS['income'] INTEREST_RATE = SESSION_CONFIG_DEFAULTS['interest_rate'] INTEREST_PERCENT = round(INTEREST_RATE*100,2) INTEREST_ROUNDED = round(INTEREST_RATE,4) INTEREST_EARNED = cu((INITIAL_ENDOWMENT + INCOME) * INTEREST_RATE ) CONSUMPTION_RATE = 1 # amount of good consumed from stock balance each period MONETARY_POLICY = SESSION_CONFIG_DEFAULTS['monetary_policy'] TOTAL_CASH = cu(INITIAL_ENDOWMENT + INCOME) ## Simple strategy quantity STRATEGY_QUANTITY = 10 # list of products taken from csv file PRODUCTS = read_csv() # SKU = 'stock keeping unit' = product ID PRODUCTS_DICT = {row['sku']: row for row in PRODUCTS} ## For practice questions QUANTITY = 4 QUANTITY_SAVINGS = QUANTITY - 2 NEW_CASH = TOTAL_CASH - (PRODUCTS_DICT['1']['unit_price'] * QUANTITY_SAVINGS) NEW_STOCK = 0 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): ## Session indices day = models.IntegerField() app_sequence = models.IntegerField() inf_sequence = models.IntegerField() intervention = models.IntegerField() ### choices = [[value,label],[value,label],...] q1 = models.IntegerField( label='Suppose the Inflation Rate is greater than the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) q2 = models.IntegerField( label='Suppose the Inflation Rate is less than the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) q3 = models.IntegerField( label='Suppose the Inflation Rate is greater than the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) q4 = models.IntegerField( label='Suppose you are in Month 117. The Inflation Rate is greater than the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) q5 = models.IntegerField( label='Suppose the Inflation Rate is less than the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) q6 = models.IntegerField( label='Suppose the Inflation Rate is equal to the interest rate paid on your Savings Account. What should you do in this situation?', widget=widgets.RadioSelect ) total_price = models.CurrencyField(initial=0) initial_savings = models.CurrencyField(initial=0) cashOnHand = models.CurrencyField(initial=0) finalSavings = models.CurrencyField(initial=0) finalStock = models.IntegerField(initial=0) interestEarned = models.CurrencyField(initial=0) newPrice = models.FloatField(initial=C.PRODUCTS_DICT['1']['unit_price']) def q1_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices def q2_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices def q3_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices def q4_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices def q5_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices def q6_choices(player): import random choices = [[1,'Buy {} units'.format(C.STRATEGY_QUANTITY)],[2,'Save (Buy 0 units)'],[3,'Buy 1 unit'],[4,'Buy 4 units']] random.shuffle(choices) return choices class Item(ExtraModel): player = models.Link(Player) sku = models.StringField() name = models.StringField() quantity = models.IntegerField() unit_price = models.CurrencyField() newPrice = models.FloatField(initial=C.PRODUCTS_DICT['1']['unit_price']) # Calculate total price of item with quantity selected def total_price(item: Item): return item.quantity * item.unit_price # Convert information about items into a dictionary def to_dict(item: Item): return dict( sku=item.sku, name=item.name, quantity=item.quantity, total_price=total_price(item) ) # Send info to HTML def live_method(player: Player, data): if player.round_number == 1: player.newPrice = float(C.PRODUCTS_DICT['1']['unit_price']) if 'sku' in data: sku = data['sku'] delta = data['delta'] product = C.PRODUCTS_DICT[sku] matches = Item.filter(player=player, sku=sku) if matches: [item] = matches item.quantity += delta if item.quantity <= 0: item.delete() else: if delta > 0: Item.create( player=player, quantity=delta, sku=sku, name=product['name'], unit_price=player.newPrice ) items = Item.filter(player=player) item_dicts = [to_dict(item) for item in items] player.total_price = sum([total_price(item) for item in items]) player.initial_savings = C.INITIAL_ENDOWMENT player.cashOnHand = player.initial_savings + C.INCOME player.finalSavings = player.cashOnHand - player.total_price player.finalStock = sum([item.quantity for item in items]) player.interestEarned = 0 player.newPrice = player.newPrice return { player.id_in_group: dict( items=item_dicts, total_price=player.total_price, initial_savings=player.initial_savings, interestEarned=player.interestEarned, cashOnHand=player.cashOnHand, finalSavings=player.finalSavings, finalStock=player.finalStock, newPrice=player.newPrice, ) } # PAGES class S_1(Page): counter_questions = 0 class S_2(Page): counter_questions = S_1.counter_questions + 1 ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_2.counter_questions / C.NUM_QUESTIONS * 100,) ) class S_3(Page): live_method = live_method form_model = 'player' form_fields = ['q1'] counter_questions = S_2.counter_questions + 1 ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_3.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q1=1, ) error_messages = dict( a1='That is incorrect. You have 0 Food in Stock.', q1='That is incorrect. When the Inflation Rate is greater than the Interest Rate and you have 0 Food in Stock, you should buy {} units.'.format(C.STRATEGY_QUANTITY) ) for field_name in solutions: if values[field_name] == 2: error_messages = error_messages['a1'] return error_messages elif values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class S_4(Page): live_method = live_method form_model = 'player' form_fields = ['q2'] counter_questions = S_3.counter_questions + len(S_3.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_4.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q2=2, ) error_messages = dict( q2='That is incorrect. Whenever you have Food in Stock, you should save (buy 0 units of Food).' ) for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class S_5(Page): live_method = live_method form_model = 'player' form_fields = ['q3'] counter_questions = S_4.counter_questions + len(S_4.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_5.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q3=2 ) error_messages = dict( q3='That is incorrect. Whenever you have Food in Stock, you should save (buy 0 units of Food).' ) for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class S_6(Page): live_method = live_method form_model = 'player' form_fields = ['q4'] counter_questions = S_5.counter_questions + len(S_5.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_6.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q4=4 ) error_messages = dict( a1='That is incorrect. You should not buy more Food than there are months remaining in the Savings Game (Month 117 of 120).', q4='That is incorrect. Here, you have 0 Food in Stock, and the Inflation Rate is greater than the Interest Rate.' ) for field_name in solutions: if values[field_name] == 1: error_messages = error_messages['a1'] return error_messages elif values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class S_7(Page): live_method = live_method form_model = 'player' form_fields = ['q5'] counter_questions = S_6.counter_questions + len(S_6.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_7.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q5=3, ) error_messages = dict( a1='That is incorrect. You have 0 Food in Stock.', q5='That is incorrect. When the Inflation Rate is less than the Interest Rate, you should not buy more than 1 unit of Food.' ) for field_name in solutions: if values[field_name] == 2: error_messages = error_messages['a1'] return error_messages elif values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class S_8(Page): live_method = live_method form_model = 'player' form_fields = ['q6'] counter_questions = S_7.counter_questions + len(S_7.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(S_8.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q6=3, ) error_messages = dict( a1='That is incorrect. You have 0 Food in Stock.', q6='That is incorrect. When the Inflation Rate is equal to the Interest Rate, you should not buy more than 1 unit of Food.' ) for field_name in solutions: if values[field_name] == 2: error_messages = error_messages['a1'] return error_messages elif values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class Results(Page): counter_questions = S_8.counter_questions + len(S_8.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(Results.counter_questions / C.NUM_QUESTIONS * 100,) ) page_sequence = [ S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_8, Results ]