from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) from django.db import models as djmodels from django.db.models import F from . import ret_functions import random author = '' doc = """ multi-round real effort task """ def make_gamble_field(label): return models.IntegerField( choices=[ [1,'Accept'], [0,'Reject'] ], label=label, widget=widgets.RadioSelectHorizontal, ) class Constants(BaseConstants): PLACEMENT_ROUNDS = 1 FIRST_SESSION_ROUNDS = 5 SECOND_SESSION_ROUNDS = 5 name_in_url = 'realefforttask' players_per_group = None num_rounds = FIRST_SESSION_ROUNDS + PLACEMENT_ROUNDS + SECOND_SESSION_ROUNDS # this parameter defines how much time a user will stay on a RET page per round (in seconds) task_time = 10 class Subsession(BaseSubsession): def creating_session(self): # we look for a corresponding Task Generator in our library (ret_functions.py) that contain all task-generating # functions. So the name of the generator in 'task_fun' parameter from settings.py should coincide with an # actual task-generating class from ret_functions. self.session.vars['task_fun'] = getattr(ret_functions, self.session.config['task']) # If a task generator gets some parameters (like a level of difficulty, or number of rows in a matrix etc.) # these parameters should be set in 'task_params' settings of an app, in a form of dictionary. For instance: # 'task_params': {'difficulty': 5} self.session.vars['task_params'] = self.session.config.get('task_params', {}) # for each player we call a function (defined in Player's model) called get_or_create_task # this is done so that when a RET page is shown to a player for the first time they would already have a task # to work on for p in self.get_players(): p.get_or_create_task() class Group(BaseGroup): ... class Player(BasePlayer): age = models.IntegerField(label='What is your age?', min=13, max=125) gender = models.StringField( choices=['Male', 'Female', 'Other'], label='What is your gender?', widget=widgets.RadioSelectHorizontal, ) subject_id = models.StringField(label='What is your ID?') degree = models.StringField(label="What degree are you registered for?") work = models.BooleanField( label="Did you work before you started the degree for which you are currently studying?", widget = widgets.RadioSelectHorizontal,) part_time_work = models.BooleanField( label="Have you undertaken any part-time work during your current degree programme?", widget=widgets.RadioSelectHorizontal,) subject_study = models.StringField(label="What is your subject of study?") nationality = models.StringField(label="What is your nationality?") german = models.BooleanField(label="Is German your mother tongue?") crt_bat = models.IntegerField( # Cognitive Reflection Task field label=''' A bat and a ball cost 22 dollars in total. The bat costs 20 dollars more than the ball. How many dollars does the ball cost?''' ) crt_widget = models.IntegerField( label=''' If it takes 5 machines 5 minutes to make 5 widgets, how many minutes would it take 100 machines to make 100 widgets?''' ) crt_lake = models.IntegerField( label=''' In a lake, there is a patch of lily pads. Every day, the patch doubles in size. If it takes 48 days for the patch to cover the entire lake, how many days would it take for the patch to cover half of the lake?''' ) crt_gamble_four = make_gamble_field('You lose 4 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_gamble_two = make_gamble_field('You lose 2 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_gamble_three = make_gamble_field('You lose 3 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_gamble_five = make_gamble_field('You lose 5 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_gamble_six = make_gamble_field('You lose 6 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_gamble_seven = make_gamble_field('You lose 7 euro with probability 0.5 or you win 6 euro with probability 0.5:') crt_repetitions = make_gamble_field('Six independent repetitions of the gamble above') # here we store all tasks solved in this specific round - for further analysis tasks_dump = models.LongStringField(doc='to store all tasks with answers, diff level and feedback') placement_score = models.FloatField(initial=0) bonus_round = models.IntegerField(initial=0) # this method returns number of correct tasks solved in this round @property def num_tasks_correct(self): return self.tasks.filter(correct_answer=F('answer')).count() # this method returns total number of tasks to which a player provided an answer @property def num_tasks_total(self): return self.tasks.filter(answer__isnull=False).count() @property def get_tasks_score(self): score = 0 if self.num_tasks_total != 0: score = round(self.num_tasks_correct * (self.num_tasks_correct / self.num_tasks_total), 2) return score def evaluate_placement_score(self): a = 0.9 self.placement_score = round(a * self.get_tasks_score + (1-a) * random.randint(0, 10), 2) def is_placement_round(self): return self.round_number == 1 def is_first_session(self): round = self.round_number - 1 return round in range(1, 1+Constants.FIRST_SESSION_ROUNDS) def is_second_session(self): round = self.round_number - 1 return round in range(1+Constants.FIRST_SESSION_ROUNDS, 1+Constants.FIRST_SESSION_ROUNDS+Constants.SECOND_SESSION_ROUNDS) # The following method checks if there are any unfinished (with no answer) tasks. If yes, we return the unfinished # task. If there are no uncompleted tasks we create a new one using a task-generating function from session settings def get_or_create_task(self): unfinished_tasks = self.tasks.filter(answer__isnull=True) if unfinished_tasks.exists(): return unfinished_tasks.first() else: task = Task.create(self, self.session.vars['task_fun'], **self.session.vars['task_params']) task.save() return task # This is a custom model that contains information about individual tasks. In each round, each player can have as many # tasks as they tried to solve (we can call for the set of all tasks solved by a player by calling for instance # player.tasks.all() # Each task has a body field, html_body - actual html code shown at each page, correct answer and an answer provided by # a player. In addition there are two automatically updated/created fields that track time of creation and of an update # These fields can be used to track how long each player works on each task class Task(djmodels.Model): class Meta: ordering = ['-created_at'] player = djmodels.ForeignKey(to=Player, related_name='tasks') body = models.LongStringField() html_body = models.LongStringField() correct_answer = models.StringField() answer = models.StringField(null=True) created_at = djmodels.DateTimeField(auto_now_add=True) updated_at = djmodels.DateTimeField(auto_now=True) # the following method creates a new task, and requires as an input a task-generating function and (if any) some # parameters fed into task-generating function. @classmethod def create(cls, player, fun, **params): proto_task = fun(**params) task = cls(player=player, body=proto_task.body, html_body=proto_task.html_body, correct_answer=proto_task.correct_answer) return task