from decimal import Decimal from otree.api import * NATIONALITIES = ['Afghan', 'Albanian', 'Algerian', 'American', 'Andorran', 'Angolan', 'Antiguans', 'Argentinean', 'Armenian', 'Australian', 'Austrian', 'Azerbaijani', 'Bahamian', 'Bahraini', 'Bangladeshi', 'Barbadian', 'Barbudans', 'Batswana', 'Belarusian', 'Belgian', 'Belizean', 'Beninese', 'Bhutanese', 'Bolivian', 'Bosnian', 'Brazilian', 'British', 'Bruneian', 'Bulgarian', 'Burkinabe', 'Burmese', 'Burundian', 'Cambodian', 'Cameroonian', 'Canadian', 'Cape Verdean', 'Central African', 'Chadian', 'Chilean', 'Chinese', 'Colombian', 'Comoran', 'Congolese', 'Costa Rican', 'Croatian', 'Cuban', 'Cypriot', 'Czech', 'Danish', 'Djibouti', 'Dominican', 'Dutch', 'Dutchman', 'Dutchwoman', 'East Timorese', 'Ecuadorean', 'Egyptian', 'Emirian', 'Equatorial Guinean', 'Eritrean', 'Estonian', 'Ethiopian', 'Fijian', 'Filipino', 'Finnish', 'French', 'Gabonese', 'Gambian', 'Georgian', 'German', 'Ghanaian', 'Greek', 'Grenadian', 'Guatemalan', 'Guinea-Bissauan', 'Guinean', 'Guyanese', 'Haitian', 'Herzegovinian', 'Honduran', 'Hungarian', 'I-Kiribati', 'Icelander', 'Indian', 'Indonesian', 'Iranian', 'Iraqi', 'Irish', 'Israeli', 'Italian', 'Ivorian', 'Jamaican', 'Japanese', 'Jordanian', 'Kazakhstani', 'Kenyan', 'Kittian and Nevisian', 'Kuwaiti', 'Kyrgyz', 'Laotian', 'Latvian', 'Lebanese', 'Liberian', 'Libyan', 'Liechtensteiner', 'Lithuanian', 'Luxembourger', 'Macedonian', 'Malagasy', 'Malawian', 'Malaysian', 'Maldivan', 'Malian', 'Maltese', 'Marshallese', 'Mauritanian', 'Mauritian', 'Mexican', 'Micronesian', 'Moldovan', 'Monacan', 'Mongolian', 'Moroccan', 'Mosotho', 'Motswana', 'Mozambican', 'Namibian', 'Nauruan', 'Nepalese', 'Netherlander', 'New Zealander', 'Ni-Vanuatu', 'Nicaraguan', 'Nigerian', 'Nigerien', 'North Korean', 'Northern Irish', 'Norwegian', 'Omani', 'Pakistani', 'Palauan', 'Panamanian', 'Papua New Guinean', 'Paraguayan', 'Peruvian', 'Polish', 'Portuguese', 'Qatari', 'Romanian', 'Russian', 'Rwandan', 'Saint Lucian', 'Salvadoran', 'Samoan', 'San Marinese', 'Sao Tomean', 'Saudi', 'Scottish', 'Senegalese', 'Serbian', 'Seychellois', 'Sierra Leonean', 'Singaporean', 'Slovakian', 'Slovenian', 'Solomon Islander', 'Somali', 'South African', 'South Korean', 'Spanish', 'Sri Lankan', 'Sudanese', 'Surinamer', 'Swazi', 'Swedish', 'Swiss', 'Syrian', 'Taiwanese', 'Tajik', 'Tanzanian', 'Thai', 'Togolese', 'Tongan', 'Trinidadian or Tobagonian', 'Tunisian', 'Turkish', 'Tuvaluan', 'Ugandan', 'Ukrainian', 'Uruguayan', 'Uzbekistani', 'Venezuelan', 'Vietnamese', 'Welsh', 'Yemenite', 'Zambian', 'Zimbabwean'] class Constants(BaseConstants): name_in_url = 'public_goods' players_per_group = 4 num_rounds = 12 endowment = cu(30) instruction_template = 'public_goods/instruction.html' conversion_rate = 1000 class Subsession(BaseSubsession): multiplier = models.FloatField() real_world_endowment = models.CurrencyField() class Group(BaseGroup): total_contribution = models.CurrencyField() individual_share = models.CurrencyField() class Player(BasePlayer): contribution = models.CurrencyField( min=0, max=Constants.endowment, label=f"How much do you decide to invest into the Group Exchange account? Please enter an amount from 0 to {Constants.endowment}" ) gender = models.StringField( choices=['Male', 'Female', 'Others'], label='What is your gender?' ) age = models.IntegerField( label='How many years old are you?' ) nationality = models.StringField( choices=NATIONALITIES, label='What is your nationality?' ) province = models.StringField( blank=True, label='If you are in Vietnam, which province/city are you from? (Leave it blank if you are non-Vietnamese)' ) area = models.StringField( choices=['Urban area', 'Suburban area', 'Rural area'], label='Which type of area do you spend most of your lifetime living in?' ) education = models.IntegerField( label='How many years have you spent joining the education system?' ) # FUNCTIONS def creating_session(subsession: Subsession): MPCR = [0.3, 0.75, 0.75, 0.3] subsession.multiplier = MPCR[(subsession.round_number - 1) % 4] * 4 subsession.real_world_endowment = Constants.endowment.to_real_world_currency(subsession.session) def set_payoffs(group: Group): players = group.get_players() contributions = [p.contribution for p in players] group.total_contribution = sum(contributions) multiplier = group.subsession.multiplier group.individual_share = ( group.total_contribution * multiplier / Constants.players_per_group ) for p in players: p.payoff = Constants.endowment - p.contribution + group.individual_share # PAGES class Introduction(Page): template_name = 'public_goods/Introduction.html' @staticmethod def is_displayed(player: Player): try: return player.participant.trial except: player.participant.trial = True return True # @staticmethod # def before_next_page(player: Player, timeout_happened): # player.participant.trial = True class Contribute(Page): template_name = 'public_goods/Contribute.html' form_model = 'player' form_fields = ['contribution'] @staticmethod def vars_for_template(player: Player): return { 'num_other_players': Constants.players_per_group - 1, 'real_endowment': f'{Decimal(Constants.endowment) * Constants.conversion_rate} VND' } class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs class Results(Page): template_name = 'public_goods/Results.html' @staticmethod def vars_for_template(player: Player): return { 'keep': Constants.endowment - player.contribution, 'real_payoff': f'{Decimal(player.payoff) * Constants.conversion_rate} VND' } @staticmethod def app_after_this_page(player: Player, upcoming_apps): if player.participant.trial: # Finish trial round player.participant.trial = False return upcoming_apps[0] class Demographic(Page): form_fields = ['gender', 'age', 'nationality', 'province', 'area', 'education'] form_model = 'player' @staticmethod def is_displayed(player: Player): return player.subsession.round_number == Constants.num_rounds page_sequence = [Introduction, Contribute, ResultsWaitPage, Results, Demographic]