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 = 'f' 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) # 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 ## Real Interest Rate constants INFLATION_1 = 0 INFLATION_2 = 0.43/12 INFLATION_3 = 0.22773/12 REAL_INTEREST_1 = INTEREST_RATE - INFLATION_1 REAL_INTEREST_2 = INTEREST_RATE - INFLATION_2 REAL_INTEREST_3 = INTEREST_RATE - INFLATION_3 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 you face the current situation below. In this month, what happens to your purchasing power?', widget=widgets.RadioSelect ) q2 = models.IntegerField( label='Can you afford more, less, or the same amount of Food as before?', widget=widgets.RadioSelect ) q3 = models.IntegerField( label='Suppose you face the current situation below. In this month, what happens to your purchasing power?', widget=widgets.RadioSelect ) q4 = models.IntegerField( label='Can you afford more, less, or the same amount of Food as before?', widget=widgets.RadioSelect ) q5 = models.IntegerField( label='Suppose you face the current situation below. In this month, what happens to your purchasing power?', widget=widgets.RadioSelect ) q6 = models.IntegerField( label='Can you afford more, less, or the same amount of Food as before?', 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']) realInterest_1 = models.FloatField() realInterest_2 = models.FloatField() realInterest_3 = models.FloatField() def q1_choices(player): import random choices = [[1,'It increases.'],[2,'It decreases.'],[3,'It stays the same.'],[4,'There is not enough information to determine the effect.']] random.shuffle(choices) return choices def q2_choices(player): import random choices = [[1,'More'],[2,'Less'],[3,'The same amount'],[4,'There is not enough information to determine the effect.']] random.shuffle(choices) return choices def q3_choices(player): import random choices = [[1,'It increases.'],[2,'It decreases.'],[3,'It stays the same.'],[4,'There is not enough information to determine the effect.']] random.shuffle(choices) return choices def q4_choices(player): import random choices = [[1,'More'],[2,'Less'],[3,'The same amount'],[4,'There is not enough information to determine the effect.']] random.shuffle(choices) return choices def q5_choices(player): import random choices = [[1,'It increases.'],[2,'It decreases.'],[3,'It stays the same.'],[4,'There is not enough information to determine the effect.']] random.shuffle(choices) return choices def q6_choices(player): import random choices = [[1,'More'],[2,'Less'],[3,'The same amount'],[4,'There is not enough information to determine the effect.']] 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 player.realInterest_1 = round(100 * (C.REAL_INTEREST_1), 2) player.realInterest_2 = round(100 * (C.REAL_INTEREST_2), 2) player.realInterest_3 = round(100 * (C.REAL_INTEREST_3), 2) 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, realInterest_1=player.realInterest_1, realInterest_2=player.realInterest_2, realInterest_3=player.realInterest_3 ) } # PAGES class F_1(Page): counter_questions = 0 class F_2(Page): live_method = live_method counter_questions = F_1.counter_questions + 1 ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(F_2.counter_questions / C.NUM_QUESTIONS * 100,) ) class F_3(Page): live_method = live_method form_model = 'player' form_fields = ['q1','q2'] counter_questions = F_2.counter_questions + 1 ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(F_3.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q1=1, q2=1 ) error_messages = dict( q1='The Real Interest Rate is positive. This implies an increase in purchasing power.', q2='The Real Interest Rate is positive. This implies you can afford more Food than before.', ) for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class F_4(Page): live_method = live_method form_model = 'player' form_fields = ['q3','q4'] counter_questions = F_3.counter_questions + len(F_3.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(F_4.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q3=2, q4=2 ) error_messages = dict( q3='The Real Interest Rate is negative. This implies a decrease in purchasing power.', q4='The Real Interest Rate is negative. This implies you can afford less Food than before.', ) for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class F_5(Page): live_method = live_method form_model = 'player' form_fields = ['q5','q6'] counter_questions = F_4.counter_questions + len(F_4.form_fields) ## For progress bars @staticmethod def vars_for_template(player: Player): return dict( percentage=round(F_5.counter_questions / C.NUM_QUESTIONS * 100,) ) @staticmethod def error_message(player, values): solutions = dict( q5=3, q6=3 ) error_messages = dict( q5='The Real Interest Rate is zero. This implies your purchasing power stays the same.', q6='The Real Interest Rate is zero. This implies you can afford the same amount of Food as before.', ) for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages = error_messages[field_name] return error_messages class Results(Page): counter_questions = F_5.counter_questions + len(F_5.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 = [ F_1, F_2, F_3, F_4, F_5, Results ]