from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) import random #function to generate random numbers author = 'Your name here' doc = """ Your app description """ """Models.py is where you define your app's data models: Subsession, Group and Player. Additionally, in the class "Constants" you can define parameters that do not change throughout the game and can be used in the html-templates. Mainly, in each data model the columns of the database table are defined and (in part) its content is calculated. """ class Constants(BaseConstants): """Defines all constants in game, that do not change throughout the game and are the same for every player These constants remain the same for the entire game and are the same for every subsession, group and player. All calculations of the constants have to be made here or in the following classes, as it is not possible to make any calculations in the html-files (content shown to the players). Changes to the basic parameters of the game can be made here. """ #---General constants of the program--- name_in_url = 'IVAX1' #name gets displayed in URL players_per_group = 6 #sets size of each group num_rounds = 2 #sets number of rounds that get played #---parameters of the I-Vax-Game--- r0 = 3 #R0 (basic reproduction number) endowment = 100 #endowment for each player in each round conversion_multiplier = 0.08 #conversion to real currency (e.g. €) conversion_100 = conversion_multiplier*100 #amount of real currency (e.g. €) for 100 fitnesspoints prob_mild = 0.8 #probability for getting mild side effetcs or illness prob_mild_10 = int(prob_mild * 10) #calculation for html_files prob_medium = 0.1 #probability for getting medium side effetcs or illness prob_medium_10 = int(prob_medium * 10) #calculation for html_files prob_severe = 0.1 #probability for getting severe side effetcs or illness prob_severe_10 = int(prob_severe * 10) #calculation for html_files prob_sideeffects = 0.3 #probability for getting side effects from vaccination prob_sideeffects_100 = int(prob_sideeffects*100) #calculation for html_files costs_disease_mild = 35 #costs for a mild disease costs_disease_medium = 50 #costs for a medium disease costs_disease_severe = 70 #costs for a severe disease costs_disease_avg = int((costs_disease_mild*prob_mild)+(costs_disease_medium*prob_medium)+(costs_disease_severe*prob_severe)) #average costs for a disease (calculation for html_files) costs_sideeffects_mild = 5 #costs for mild side effects costs_sideeffects_medium = 20 #costs for medium side effects costs_sideeffects_severe = 40 #costs for severe side effects costs_sideefects_avg = int((costs_sideeffects_mild*prob_mild) + (costs_sideeffects_medium*prob_medium) + (costs_sideeffects_severe*prob_severe)) #average costs for side effects (calculation for html_files) costs_fixed = 5 #fixed costs for vaccination class Subsession(BaseSubsession): """Creates and defines all parameters and functions in a subsession All parameters and functions in this class are the same for all groups and players, but may vary in each subsession (e.g. every round is a subsession). """ def creating_session(self): """Creates a new subsession Parameters that should be calculated in advance to every round are defined here (e.g. random numbers for players to calculate whether players get ill or side effects. These parameters stay the same for the whole subsession. """ for player in self.get_players(): player.paying_round = random.randint(1, Constants.num_rounds) #generates a random number for every player to calculate relevant round for payoff for player in self.get_players(): player.number1 = random.random() player.number2 = random.random() #generates random numbers for every player to calculate whether players get ill/ side effects (number1) #and the severity of the illness / side effect (number2) pass class Group(BaseGroup): """ Parameters and functions defined for each group Calculations that are relevant for the players in a group, but that vary between the groups (e.g. number of players that are vaccinated in a group and resulting probability to catch the disease). """ sum_vaccination = models.IntegerField() #column for absolute number of vaccinated players in a group rel_vaccination = models.FloatField() #column for relative number of vaccinated players in a group prob_ill = models.FloatField() #column for resulting probability to catch the disease def calc_vaccination(self): """Calculates absolute and relative number of vaccinated players in a group and stores them in columns defined above :return: group.sum_vaccination, group.rel_vaccination """ self.sum_vaccination = sum([p.decision for p in self.get_players()]) #absolute number of vaccinated players self.rel_vaccination = self.sum_vaccination/ Constants.players_per_group #relative number of vaccinated players def calc_prob_ill(self): """Calculates the probability to catch the disease based on number of vaccinated players in a group Calculations are based on parameters in class Constants (r0) and class Group (rel_vaccination) :return: group.prob_ill """ if (Constants.r0*(1-self.rel_vaccination) >= 1): #cases with a probability for catching the disease self.prob_ill = 1 - (1 / (Constants.r0 * (1 - self.rel_vaccination))) else: #cases with safety from catching the disease (herd immunity) self.prob_ill = 0 def illness(self): """Calculates whether player catches disease and the severity of disease for every player in a group Calculations are based on parameters defined in class Constants (prob_mild, prob_medium, prob_severe), class Subsession (number1, number2), class Group ( prob_ill) and class Player (decision) :return: player.severity_disease, player.ill """ for p in self.get_players(): if p.decision == 0 and p.number1<= self.prob_ill: p.ill = True #if player is not vaccinated and their random number1 is smaller than the probability to catch the #disease in this group, the player falls ill if p.number2 <= Constants.prob_mild : p.severity_disease = "mild" #mild disease if p.number2 > Constants.prob_mild and p.number2 <= (Constants.prob_mild + Constants.prob_medium): p.severity_disease = "medium" #medium disease if p.number2 > (Constants.prob_mild + Constants.prob_medium): p.severity_disease = "severe" #severe disease else: p.ill = False #in every other case, player does not catch the disease def sideeffects(self): """Calculates whether player gets side effects and the severity of side effects for every player in a group Calculations are based on parameters defined in class Constants (prob_mild, prob_medium, prob_severe), class Subsession (number1, number2), class Group ( prob_sideeffects) and class Player (decision) :return: player.sideeffect, player.severity_sideeffect """ for p in self.get_players(): if p.decision == 1 and p.number1 <= Constants.prob_sideeffects: p.sideeffect = True #if player is vaccinated and their number1 is smaller than the probability to get side effects #the player gets side effects if p.number2 <= Constants.prob_mild: p.severity_sideeffect = "mild" #mild side effects if p.number2 > Constants.prob_mild and p.number2 <= (Constants.prob_mild + Constants.prob_medium): p.severity_sideeffect = "medium" #medium side effects if p.number2 > (Constants.prob_mild + Constants.prob_medium): p.severity_sideeffect = "severe" #severe side effects else: p.sideeffect = False #in every other case, player does not get side effects def fitnesspoints(self): """Calculates remaining fitnesspoints for every player in a group Calculations are based on parameters defined in class Constants (endowment, costs_disease_mild, costs_disease_medium, costs_disease_sebvere, costs_fixed, costs_sideeffects_mild, costs_sideeffects_medium, costs_sideeffects_severe) and class Player (decision, ill, sideeffect, severity_disease, severity_sideeffect) :return: player.fitness """ for p in self.get_players(): #--if not vaccinated:-- #-if ill:- if p.decision == 0 and p.ill == True: if p.severity_disease == "mild": #mild disease p.fitness = Constants.endowment - Constants.costs_disease_mild if p.severity_disease == "medium": #medium disease p.fitness = Constants.endowment - Constants.costs_disease_medium if p.severity_disease == "severe": #severe disease p.fitness = Constants.endowment - Constants.costs_disease_severe #-if not ill:- if p.decision == 0 and p.ill == False: p.fitness = Constants.endowment #no disease #--if vaccinated:-- #-if side effects:- if p.decision == 1 and p.sideeffect == True: if p.severity_sideeffect == "mild": #mild side effects p.fitness = Constants.endowment - Constants.costs_fixed - Constants.costs_sideeffects_mild if p.severity_sideeffect == "medium": #medium side effects p.fitness = Constants.endowment - Constants.costs_fixed - Constants.costs_sideeffects_medium if p.severity_sideeffect == "severe": #severe side effects p.fitness = Constants.endowment - Constants.costs_fixed - Constants.costs_sideeffects_severe #-if no side effects:- if p.decision == 1 and p.sideeffect == False: p.fitness = Constants.endowment - Constants.costs_fixed #no side effects def calc_payoff(self): """Calculates payoff for every player in a group Calculations are based on parameters defined in class Constants (conversion_multiplier) and Player (paying_round, fitness). :return: player.paying_fitness, player.payoff """ for p in self.get_players(): p.paying_fitness = p.in_round(p.paying_round).fitness #gets relevant round for payoff p.payoff = p.paying_fitness * Constants.conversion_multiplier #conversion from points to currency pass class Player(BasePlayer): """Parameters and functions for every player Parameters defined here stay the same for one player, but vary from player to player, in groups and in subsessions. Most parameters here are calculated in the Group class. Decisions that are made by the players (Consent and Vaccination decision) are defined for the html-files. """ number1 = models.FloatField() #column for random number1 to calculate if player gets disease or side effects number2 = models.FloatField() #column for random number2 to calculate severity of disease or side effects paying_round = models.IntegerField() #column for relevant round for payoff ill = models.BooleanField() #column for whether player catches disease severity_disease = models.StringField() #column for severity of the disease sideeffect = models.BooleanField() #column for whether player gets side effects severity_sideeffect = models.StringField() #column for severity of the side effects fitness = models.IntegerField() #column for remaining fitnesspoints after each round paying_fitness = models.IntegerField() #column for remaining fitnesspoints in relevant round for payoff payoff = models.CurrencyField() #column for payoff in currency consent = models.IntegerField( #column for consent of player choices=[ [1, 'I agree to participate in this study and understand that all my responses will be used for ' 'scientific purposes only. I am aware that the data may be published in an anonymous way to increase ' 'transparency in science.'] #value and label for consent ], widget= widgets.RadioSelect #selects widget that is shown to players in html-files (here radio button) ) decision = models.IntegerField( #column for vaccination decision of player choices=[ [1, 'I want to get vaccinated.'], #value and label for vaccination [0, 'I do not want to get vaccinated.'] #value and label against vaccination ], widget = widgets.RadioSelect #selects widget that is shown to players in html-files (here radio button) ) pass