import math from otree_mturk_utils.views import CustomMturkPage, CustomMturkWaitPage from otree.api import Currency as c, currency_range from ._builtin import Page, WaitPage from .models import Constants, Player, Group import random import config_collat as cc import time debug = True """docstring format Method Description: Input: Output: Class Description: Input: Output: """ """Instructions Pages""" class NotMobilePlayers(WaitPage): group_by_arrival_time = True class WelcomePage(Page): def before_next_page(self): self.participant.vars['timed_out'] = False self.player.role() players = self.group.get_players() p = self.player '''These next four lines of code generate the core variables for the practice game ie the loan, the collateral, the requested repayment(requested return) and the project return''' p.prac_collateral = random.SystemRandom().randrange(1, 11, 2) p.prac_loan_offer = random.SystemRandom().randrange(1, 11, 2) # the reason for this is django models round up where as int() rounds down for 1.5 p.prac_requested_repayment = math.ceil((Constants.rate_of_return * p.prac_loan_offer)) p.prac_project_return = p.prac_loan_offer * Constants.productivity p.prac_collateral_after_cost = max(0, p.prac_collateral - int(Constants.recovery_fee)) '''If contract enforcement doesnt exsists it prac_repayment can be any amount since collateral can be seized even if the terms of the loan are met''' if Constants.contract_enforcement == False: p.prac_repayment = random.SystemRandom().randrange(1, p.prac_project_return + 1, 2) else: if p.prac_project_return > 3: p.prac_repayment = random.SystemRandom().randrange(1, p.prac_requested_repayment, 2) else: p.prac_repayment = 1 '''If contract enforcement isnt true the amount of collateral that can be recovered is equal to any amount up to the collateral the only limitation on this is bounds errors that can emerge if the value of collateral after recovery fee is less than 3 this is caught by if statements. ''' if Constants.contract_enforcement == False: if p.prac_collateral_after_cost > 3: p.prac_recovered_collateral = random.SystemRandom().randrange(1, p.prac_collateral_after_cost + 1, 2) elif p.prac_collateral_after_cost != 0: p.prac_recovered_collateral = random.SystemRandom().choice([1]) else: p.prac_recovered_collateral = 0 else: if p.prac_collateral_after_cost <= p.prac_requested_repayment - p.prac_repayment: p.prac_recovered_collateral = p.prac_collateral_after_cost else: p.prac_recovered_collateral = max(0, p.prac_requested_repayment - p.prac_repayment) if self.player.role() == 'lender': self.player.prac_borrower_money -= self.player.prac_collateral self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class TaskOverview(Page): # Provides general overview of tasks def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class RoleAssignment(Page): # Informs player of the task instructions def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class LoanAgreement(Page): # Explains the loan agreement/package that will be created in the game def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class ScoreSheetOverview(Page): def before_next_page(self): self.player.current_practice_page += 1 def vars_for_template(self): return { 'image_path': 'Instructions_Quiz/example_timer.jpg' } def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class PracCollateralOffer(Page): form_model = 'player' form_fields = ['prac_collateral_input'] def prac_collateral_input_error_message(self, value): '''returns an error message if the player inputs the wrong value''' if value != self.player.prac_collateral: return 'Enter a collateral offer of ' + str(self.player.prac_collateral) + ' to continue.' def before_next_page(self): self.player.current_practice_page += 1 self.player.prac_borrower_money -= self.player.prac_collateral self.player.prac_lender_money -= self.player.prac_loan_offer self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) def is_displayed(self): if self.player.role() == 'borrower' and self.participant.vars['is_mobile'] is False: return True else: return False class PracLoanOffer(Page): form_model = 'player' form_fields = ['prac_loan_offer_input'] def before_next_page(self): self.player.current_practice_page += 1 self.player.prac_lender_money -= self.player.prac_loan_offer self.player.prac_project_return = self.player.prac_loan_offer * Constants.productivity self.player.prac_borrower_money += self.player.prac_project_return self.player.prac_lender_money += self.player.prac_repayment self.player.prac_borrower_money -= self.player.prac_repayment self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) if Constants.contract_enforcement == True: self.player.prac_borrower_money += max(0, self.player.prac_collateral - self.player.prac_recovered_collateral - Constants.recovery_fee) self.player.prac_lender_money += self.player.prac_recovered_collateral self.player.prac_recovered_collateral= self.player.prac_recovered_collateral self.player.prac_returned_collateral = max( 0, self.player.prac_collateral - self.player.prac_recovered_collateral - Constants.recovery_fee) def vars_for_template(self): pass def prac_loan_offer_input_error_message(self, value): '''returns an error message if the player inputs the wrong value''' if value != self.player.prac_loan_offer: return 'Enter a loan offer of ' + str(self.player.prac_loan_offer) + ' points to continue.' def is_displayed(self): if self.player.role() == 'lender' and self.participant.vars['is_mobile'] is False: return True else: return False class PracLoanPackageDecision(Page): form_model = 'player' form_fields = ['prac_loan_package_decision'] def before_next_page(self): self.player.current_practice_page += 1 self.player.prac_borrower_money += self.player.prac_loan_offer * Constants.productivity self.player.prac_project_return = self.player.prac_loan_offer * Constants.productivity self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) def prac_loan_package_decision_error_message(self, value): '''returns an error message if the player inputs the wrong value''' if value != 'Yes': return 'Accept the loan agreement to continue.' def is_displayed(self): if self.player.role() == 'borrower' and self.participant.vars['is_mobile'] is False: return True else: return False class LoanFulfillment(Page): # Explains the process of loan fulfillment def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class PracReturnRealization(Page): form_model = 'player' form_fields = ['prac_repayment_input'] def before_next_page(self): self.player.current_practice_page += 1 self.player.prac_recovered_collateral= self.player.prac_recovered_collateral self.player.prac_returned_collateral = max( 0, self.player.prac_collateral - self.player.prac_recovered_collateral - Constants.recovery_fee) self.player.prac_borrower_money += self.player.prac_returned_collateral self.player.prac_lender_money += self.player.prac_recovered_collateral self.player.prac_borrower_money -= self.player.prac_repayment self.player.prac_lender_money += self.player.prac_repayment self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) def prac_repayment_input_error_message(self, value): if value != self.player.prac_repayment: return 'Enter a return of ' + str(self.player.prac_repayment) + ' to continue.' def is_displayed(self): if self.player.role() == 'borrower' and self.participant.vars['is_mobile'] is False: return True else: return False class LoanRemediation(Page): # Explains loan remediation process def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class PracLoanOffer(Page): form_model = 'player' form_fields = ['prac_loan_offer_input'] def before_next_page(self): self.player.current_practice_page += 1 self.player.prac_lender_money -= self.player.prac_loan_offer self.player.prac_project_return = self.player.prac_loan_offer * Constants.productivity self.player.prac_borrower_money += self.player.prac_project_return self.player.prac_lender_money += self.player.prac_repayment self.player.prac_borrower_money -= self.player.prac_repayment self.player.prac_recovered_collateral= self.player.prac_recovered_collateral self.player.prac_returned_collateral = max( 0, self.player.prac_collateral - self.player.prac_recovered_collateral - Constants.recovery_fee) self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) def vars_for_template(self): pass def prac_loan_offer_input_error_message(self, value): '''returns an error message if the player inputs the wrong value''' if value != self.player.prac_loan_offer: return 'Enter a loan offer of ' + str(self.player.prac_loan_offer) + ' to continue.' def is_displayed(self): if self.player.role() == 'lender' and self.participant.vars['is_mobile'] is False: return True else: return False class PracCollateralDecision(Page): if Constants.contract_enforcement == False: form_model = 'player' form_fields = ['prac_collateral_decision'] def before_next_page(self): self.player.current_practice_page += 1 if Constants.contract_enforcement == True: self.player.prac_borrower_money += self.player.prac_returned_collateral self.player.prac_lender_money += self.player.prac_recovered_collateral self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) def prac_collateral_decision_error_message(self, value): if value != 'No': return "Select 'No' to continue." def is_displayed(self): if self.player.role() == 'lender' and self.participant.vars['is_mobile'] is False: return True else: return False class PracCollateralSeizure(Page): if Constants.contract_enforcement is False: form_model = 'player' form_fields = ['prac_recovered_collateral_input'] def prac_recovered_collateral_input_error_message(self, value): if value != self.player.prac_recovered_collateral: return 'Select ' + str(self.player.prac_recovered_collateral) + ' to continue.' def before_next_page(self): self.player.current_practice_page += 1 if Constants.contract_enforcement == False: self.player.prac_recovered_collateral= self.player.prac_recovered_collateral self.player.prac_returned_collateral = max( 0, self.player.prac_collateral - self.player.prac_recovered_collateral - Constants.recovery_fee) self.player.prac_borrower_money += self.player.prac_returned_collateral self.player.prac_lender_money += self.player.prac_recovered_collateral self.player.prac_lender_dollar_value = str( self.player.prac_lender_money.to_real_world_currency(self.session)) self.player.prac_borrower_dollar_value = str( self.player.prac_borrower_money.to_real_world_currency(self.session)) def is_displayed(self): if self.player.role() == 'lender' and self.participant.vars['is_mobile'] is False: return True else: return False class PracResults(Page): def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False def before_next_page(self): self.player.time_spent_on_instructions += time.time() class TaskRepayment(Page): # Explains how task payoffs are determined def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class InstructionsResults(Page): def before_next_page(self): self.participant.payoff += Constants.instructions_payoff def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class QuizResults(Page): def vars_for_template(self): return {'dollar_amount': self.player.quiz_earnings.to_real_world_currency(self.session) } # Shows subject how much they earned from the quiz def before_next_page(self): # adds quiz earnings to player's payoff self.participant.vars['instruction_earnings'] = Constants.instructions_payoff self.participant.vars['quiz_earnings'] = c(self.player.quiz_earnings) self.participant.vars['quiz_questions_correct'] = self.player.num_correct def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False """ for question in Constants.quiz_questions: counter_question += 1 if counter_question != 6: timeout_questions.append(question) else: if Constants.treatment == 'treatment1' or Constants.treatment == 'treatment2': timeout_questions.extend('If the lender does not indicate satisfaction with the payment ' 'from the borrower, ' + question) else: timeout_questions.extend('If the program does not indicate satisfaction with the payment ' 'from the borrower, ' + question) """ class QuizPage(Page): _allow_custom_attributes = True form_model = 'player' #timeout_seconds = Constants.timer def get_form_fields(self): return [self.player.current_field()] # A dynamic error message is necessary def error_message(self, values): player = self.player current_field = player.current_field() correct_answer = Constants.quiz_fields[current_field] if values[current_field] != correct_answer: player.q_incorrect_attempts += 1 self.player.quiz_incorrect_answer = 'Incorrect. The correct answer is "'\ + str(Constants.quiz_answers[player.quiz_page_counter]) + '"' return self.player.quiz_incorrect_answer def vars_for_template(self): player = self.player quiz_questions = ['True or False: The lending task will only be played for one round', 'In which order is the loan agreement completed? The borrower and lender are both notified of the repayment rate for the loan then, ', 'What is the range of points that can be offered as a loan by the lender?', 'What value is multiplied by the loan to determine the project return?', 'Who determines whether to move on to the loan reconciliation process?', ] if Constants.contract_enforcement == False: quiz_questions.append("If the lender is not satisfied with the borrower's repayment, how can additional compensation be obtained?") else: quiz_questions.append( "If the borrower's repayment is less than the requested loan repayment, how is additional compensation obtained?") index = player.quiz_page_counter return {'question': quiz_questions[index], 'page_number': index + 1, 'incorrect_answer': player.quiz_incorrect_answer} def before_next_page(self): player = self.player player.quiz_page_counter += 1 if self.timeout_happened: player.q_timeout = 1 if player.q_timeout == 1 and player.q_incorrect_attempts == 0: player.q_validation = 1 if player.q_incorrect_attempts == 0 and self.timeout_happened == False: player.num_correct += 1 player.quiz_earnings += c(2) self.participant.vars['quiz_earnings'] = player.quiz_earnings player.error_sequence += str(player.q_incorrect_attempts) player.timeout_sequence += str(player.q_timeout) player.q_timeout = 0 player.q_incorrect_attempts = 0 def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class QuizTimeout(Page): _allow_custom_attributes = True #timeout_seconds = Constants.timer def vars_for_template(self): player = self.player quiz_questions = ['True or False: The lending task will only be played for one round', 'In which order is the loan agreement completed? The borrower and lender are both notified of the repayment ratefor the loan then, ', 'What is the range of points that can be offered as a loan by the lender?', 'What is the requested project return?', 'Who determines loan satisfaction in order to proceed to final payment?', ] if Constants.contract_enforcement == False: quiz_questions.append( "The lender is not satisfied with the borrower's repayment, how can additional compensation be obtained?") else: quiz_questions.append( "If the borrower's repayment is less than the requested loan repayment, how is additional compensation obtained?") index = player.quiz_page_counter - 1 return {'question': quiz_questions[index], 'answer': Constants.quiz_answers[index]} def is_displayed(self): player = self.player if player.q_validation == 1 and self.participant.vars['is_mobile'] is False: return True else: return False def before_next_page(self): self.player.q_validation = 0 # DYNAMIC QUIZ WITH TIMEOUTS page_sequence = [ NotMobilePlayers, WelcomePage, TaskOverview, RoleAssignment, LoanAgreement, LoanFulfillment, LoanRemediation, TaskRepayment, ScoreSheetOverview, PracCollateralOffer, PracLoanOffer, PracLoanPackageDecision, PracReturnRealization, PracCollateralDecision, PracCollateralSeizure, PracResults, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizResults, ] """ # QUIZ PAGE SEQUENCE page_sequence = [ QuizPage, QuizPage, QuizPage, QuizPage, QuizPage, QuizPage, ] # DYNAMIC QUIZ WITH TIMEOUTS page_sequence = [ WelcomePage, TaskOverview, RoleAssignment, LoanAgreement, LoanFulfillment, LoanRemediation, TaskRepayment, PracCollateralOffer, PracLoanOffer, PracLoanPackageDecision, PracReturnRealization, PracCollateralDecision, PracCollateralSeizure, PracResults, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizPage, QuizTimeout, QuizResults ] # QUIZ PAGE SEQUENCE page_sequence = [ QuizPage, QuizPage, QuizPage, QuizPage, QuizPage, QuizPage, ] # FULL PAGE SEQUENCE page_sequence = [ WelcomePage, TaskOverview, RoleAssignment, LoanAgreement, LoanFulfillment, LoanRemediation, TaskRepayment, PracCollateralOffer, PracLoanOffer, PracLoanPackageDecision, PracReturnRealization, PracCollateralDecision, PracCollateralSeizure, PracResults, QuizWaitPage, QuestionOne, QuizTimeout, QuestionTwo, QuizTimeout, QuestionThree, QuizTimeout, QuestionFour, QuizTimeout, QuestionFive, QuizTimeout, QuestionSix, QuizTimeout, QuizResults, QuizResults, ] TaskOverview, RoleAssignment, LoanAgreement, PracCollateralOffer, PracLoanOffer, PracLoanPackageDecision, LoanFulfillment, PracReturnRealization, LoanRemediation, PracCollateralDecision, PracCollateralSeizure, PracResults, TaskRepayment, PracCollateralOffer, PracLoanOffer, PracLoanPackageDecision, PracReturnRealization, PracCollateralDecision, PracCollateralSeizure, PracResults, QuizWaitPage, QuestionOne, QuizTimeout, QuestionTwo, QuizTimeout, QuestionThree, QuizTimeout, QuestionFour, QuizTimeout, QuestionFive, QuizTimeout, QuestionSix, QuizTimeout, QuizResults ] # GAME PAGE SEQUENCE page_sequence= [ Collateral_Offer, WaitForPlayer, Loan_Amount, WaitForPlayer, Loan_Package, WaitForPlayer, Realize_Return, WaitForPlayer, Collateral_Decision, WaitForPlayer, Seize_Collateral, WaitForPlayer, Results ] # QUIZ PAGE SEQUENCE page_sequence= [ QuestionOne, QuizTimeout, QuestionTwo, QuizTimeout, QuestionThree, QuizTimeout, QuestionFour, QuizTimeout, QuestionFive, QuizTimeout, QuestionSix, QuizTimeout, QuizResults ] # removed from game class Determine_Rate_of_Return(Page): Description: Determine_Rate_of_Return inherits the oTree class Page. The purpose of this page is to ask the lender for their desired repayment ratefrom a loan. Input: Page - (oTree Class) Output: Form Field - (to HTML Determine_Rate_of_Return.HTML) form_model= 'group' form_fields= ['rate_of_return'] def is_displayed(self): Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Output: Bool - (to oTree Page class) if self.player.role() == 'lender': return True else: return False """ """ class QuestionSix(Page): _allow_custom_attributes= True timeout_seconds= Constants.timer form_model= 'player' form_fields= ['question_6_response'] def vars_for_template(self): return {'page_number': self.player.quiz_page_counter + 1} def question_six_response_error_message(self, value): if value != 3: self.player.q6_incorrect_attempts += 1 return 'Incorrect. The correct answer is 3' def before_next_page(self): self.player.quiz_page_counter += 1 if self.timeout_happened: self.player.q6_timeout= 1 if self.player.q6_incorrect_attempts > 1: self.player.q6_incorrect= 1 if self.player.q6_incorrect_attempts < 1 and self.timeout_happened == False: self.player.num_correct += 1 self.player.quiz_earnings += c(2) """