from otree.api import * from otree.database import db import random import json import csv import os import time doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'bargain' PLAYERS_PER_GROUP = 4 NUM_ROUNDS = 4 # 2604 test: 4 rounds and 4 people; weak outside option MULTIPLIER_OPTIONS_B = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3] BTN_COOLDOWN = 2000 # Miliseconds TIMEOUT_GENERIC_1 = 60 # Bargaining Result Page TIMEOUT_GENERIC_2 = 60 # Seconds used to be 45 TIMEOUT_BOT = 30 # used to be 45 Seconds TIMEOUT_BOT_SUBMISSION = 25 # Bot submits after 25 seconds in selected ODR1 treatments TIMEOUT_SUBMISSION = 80 # Submission Page TIMEOUT_MECHANISM = 80 # Mechanism Page LOBBY_HEARTBEAT_SECONDS = 2 LOBBY_STALE_SECONDS = 10 LOBBY_CONFIRM_SECONDS = 40 # new matching design BARGAINING_BREAKDOWN_NAME = "BargainingBreakdown.csv" OUTSIDE_OPTIONS_NAME = "OutsideOptions.csv" MULTIPLIERS_NAME = "Multipliers.csv" TREATMENTS_1 = ["ODR1", "SeqODR1", "OpaODR1", "AIODR1", "AIODRsoph1"] ### newnew 250224 AIODR1; 250606 AIODRsoph1 TREATMENTS_2 = ["ODR2", "SeqODR2"] TREATMENTS_M = TREATMENTS_1 + TREATMENTS_2 TREATMENTS_O = ["ODR1", "OpaODR1", "ODR2"] TREATMENTS_S = ["SeqODR1", "SeqODR2"] TREATMENTS_A = ["AIODR1", "AIODRsoph1"] ### newnew 250224 AIODR1; 250606 AIODRsoph1 class Subsession(BaseSubsession): treatment_name = models.StringField(initial="") treatment_rep = models.IntegerField(initial=0) exchange_rate = models.FloatField(initial=0.0) show_up_fee = models.FloatField(initial=0.0) objects_A = models.IntegerField(initial=0) objects_B = models.IntegerField(initial=0) bargaining_breakdown = models.IntegerField(initial=0) group_order = models.StringField(initial="") outside_type = models.StringField(initial="") lobby_batch_counter = models.IntegerField(initial=0) lobby_final_group_counter = models.IntegerField(initial=0) class Group(BaseGroup): pass class Player(BasePlayer): matching = models.IntegerField(initial=0) id_in_match = models.IntegerField(initial=0) player_matched = models.IntegerField(initial=0) player_code = models.StringField(initial="") outside_option_A = models.IntegerField(initial=0) objects_B_multiplier = models.FloatField(initial=0.0) matched_participant_code = models.StringField(initial="") ##newnew 0705 lobby_status = models.StringField(initial="waiting") lobby_queue_entered_at = models.FloatField(initial=0) lobby_last_seen_at = models.FloatField(initial=0) lobby_tab_visible = models.BooleanField(initial=True) lobby_batch_id = models.IntegerField(initial=0) lobby_invited_at = models.FloatField(initial=0) lobby_invite_deadline = models.FloatField(initial=0) lobby_confirmed_at = models.FloatField(initial=0) lobby_ready_deadline = models.FloatField(initial=0) lobby_final_group_id = models.IntegerField(initial=0) lobby_last_ready_confirmed_at = models.FloatField(initial=0) lobby_reconfirm_count = models.IntegerField(initial=0) lobby_reset_count = models.IntegerField(initial=0) lobby_matched_at = models.FloatField(initial=0) # Barg Treatments offer_1_objects_A = models.IntegerField(initial=-1, min=0) offer_1_objects_B = models.IntegerField(initial=-1, min=0) offer_2_objects_A = models.IntegerField(initial=-1, min=0) offer_2_objects_B = models.IntegerField(initial=-1, min=0) offer_3_objects_A = models.IntegerField(initial=-1, min=0) offer_3_objects_B = models.IntegerField(initial=-1, min=0) offer_4_objects_A = models.IntegerField(initial=-1, min=0) offer_4_objects_B = models.IntegerField(initial=-1, min=0) accepted_offer = models.IntegerField(initial=0) all_offers_submitted_A = models.LongStringField(initial="") # newnew 1102 all_offers_submitted_B = models.LongStringField(initial="") # newnew 1102 # ODR and SeqODR treatments submission_1_objects_A = models.IntegerField(initial=-1, min=0) suggestion_1_objects_A = models.FloatField(initial=-1.0, min=0.0) suggestion_1_objects_B = models.FloatField(initial=-1.0, min=0.0) accepted_suggestion_1 = models.IntegerField(initial=-1) mutually_accepted_1 = models.IntegerField(initial=-1) second_chance = models.IntegerField(initial=0) submission_2_objects_A = models.IntegerField(initial=-1, min=0) suggestion_2_objects_A = models.FloatField(initial=-1.0, min=0.0) suggestion_2_objects_B = models.FloatField(initial=-1.0, min=0.0) accepted_suggestion_2 = models.IntegerField(initial=-1) mutually_accepted_2 = models.IntegerField(initial=-1) # AIODR treatments add predicted_outside_option = models.FloatField(initial=-1, min=0) ## newnew 250224 AIODR1 predicted_outside_options_sum = models.FloatField(initial=-1, min=0) ## newnew 250224 AIODR1 # All treatments accepted_objects_A = models.FloatField(initial=-1.0) accepted_objects_B = models.FloatField(initial=-1.0) breakdown_happened = models.IntegerField(initial=0) timed_out = models.IntegerField(initial=0) match_timed_out = models.IntegerField(initial=0) ecu_earned_this_round = models.FloatField(initial=0.0) round_to_pay = models.IntegerField(initial=0) ecu_earned = models.FloatField(initial=0.0) money_earned = models.FloatField(initial=0.0) money_to_pay = models.FloatField(initial=0.0) # Online vars timeout_page = models.StringField(initial="") replaced_by_bot = models.IntegerField(initial=0) # time count ## newnew 1028 enter_time = models.FloatField() time_on_pages = models.StringField(initial="") class Offer(ExtraModel): player = models.Link(Player) session = models.StringField(initial="") round_number = models.IntegerField(initial=0) group = models.IntegerField(initial=0) sender = models.IntegerField(initial=0) receiver = models.IntegerField(initial=0) action = models.StringField(initial="") offer_slot = models.IntegerField(initial=0) offer_objects_A = models.IntegerField(initial=-1) offer_objects_B = models.IntegerField(initial=-1) time = models.IntegerField(initial=0) # PAGES # P01 class P00_Welcome(Page): def is_displayed(player): return player.round_number == 1 def vars_for_template(player): return dict( summary=get_summary_name(player), ) def before_next_page(player, timeout_happened): if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P00_Welcome" player.replaced_by_bot = 1 class P01_LobbyMatching(Page): @staticmethod def is_displayed(player): return ( player.round_number == 1 and player.lobby_status not in ["done", "matched"] and player.lobby_final_group_id == 0 ) @staticmethod def vars_for_template(player): return dict( heartbeat_seconds=C.LOBBY_HEARTBEAT_SECONDS, confirm_seconds=C.LOBBY_CONFIRM_SECONDS, ) @staticmethod def live_method(player, data): now = time.time() root_player = player.in_round(1) if root_player.lobby_queue_entered_at == 0: root_player.lobby_status = "waiting" root_player.lobby_queue_entered_at = now root_player.lobby_last_seen_at = now root_player.lobby_tab_visible = bool(data.get("tab_visible", True)) message_type = data.get("type") if message_type == "confirm": if ( root_player.lobby_status == "invited" and root_player.lobby_invite_deadline > now ): root_player.lobby_status = "confirmed" root_player.lobby_confirmed_at = now root_player.lobby_last_ready_confirmed_at = now root_player.lobby_ready_deadline = now + C.LOBBY_CONFIRM_SECONDS elif message_type == "reconfirm": if ( root_player.lobby_status == "confirmed" and root_player.lobby_ready_deadline > now ): root_player.lobby_last_ready_confirmed_at = now root_player.lobby_reconfirm_count += 1 root_player.lobby_ready_deadline = now + C.LOBBY_CONFIRM_SECONDS process_lobby(root_player.subsession) root_player = player.in_round(1) # Players start in singleton groups and are regrouped into a 4-player group # inside the lobby. Broadcasting to the current live page avoids relying on an # id_in_group value that can change while the page is still open. return {0: lobby_payload(root_player)} @staticmethod def before_next_page(player, timeout_happened): if player.lobby_status == "matched" or player.lobby_final_group_id > 0: player.lobby_status = "done" def lobby_payload(player): now = time.time() root_subsession = player.subsession.in_round(1) batch_players = [ p for p in root_subsession.get_players() if p.lobby_batch_id == player.lobby_batch_id and p.lobby_batch_id > 0 and p.lobby_final_group_id == 0 ] confirmed_count = len([p for p in batch_players if p.lobby_status == "confirmed"]) if player.lobby_final_group_id > 0: return dict(state="matched") if player.lobby_status == "invited": return dict( state="invited", seconds_left=max(0, int(player.lobby_invite_deadline - now)), confirmed_count=confirmed_count, ) if player.lobby_status == "confirmed": return dict( state="confirmed", seconds_left=max(0, int(player.lobby_ready_deadline - now)), confirmed_count=confirmed_count, ) waiting_count = len(get_waiting_candidates(root_subsession, now)) return dict( state="waiting", waiting_count=waiting_count, ) def process_lobby(root_subsession): now = time.time() players = root_subsession.get_players() for p in players: if p.lobby_final_group_id > 0: continue if p.lobby_status == "invited": if p.lobby_invite_deadline <= now or not is_lobby_available(p, now): reset_to_waiting(p, now, send_to_back=True) elif p.lobby_status == "confirmed": if p.lobby_ready_deadline <= now or not is_lobby_available(p, now): reset_to_waiting(p, now, send_to_back=True) batch_ids = sorted({ p.lobby_batch_id for p in players if p.lobby_batch_id > 0 and p.lobby_final_group_id == 0 }) for batch_id in batch_ids: batch_players = [ p for p in root_subsession.get_players() if p.lobby_batch_id == batch_id and p.lobby_final_group_id == 0 ] confirmed_players = [p for p in batch_players if p.lobby_status == "confirmed"] if len(confirmed_players) == C.PLAYERS_PER_GROUP: finalize_lobby_group(root_subsession, confirmed_players) continue vacancies = C.PLAYERS_PER_GROUP - len(batch_players) if vacancies > 0: candidates = get_waiting_candidates(root_subsession, now) for candidate in candidates[:vacancies]: invite_player(candidate, batch_id, now) while True: available_waiters = get_waiting_candidates(root_subsession, now) if len(available_waiters) < C.PLAYERS_PER_GROUP: break batch_id = next_batch_id(root_subsession) for p in available_waiters[:C.PLAYERS_PER_GROUP]: invite_player(p, batch_id, now) def is_lobby_available(player, now): return ( player.lobby_final_group_id == 0 and player.lobby_tab_visible and player.lobby_last_seen_at >= now - C.LOBBY_STALE_SECONDS ) def get_waiting_candidates(root_subsession, now): candidates = [ p for p in root_subsession.get_players() if p.lobby_final_group_id == 0 and p.lobby_status == "waiting" and is_lobby_available(p, now) ] return sorted(candidates, key=lambda p: (p.lobby_queue_entered_at, p.id_in_subsession)) def reset_to_waiting(player, now, send_to_back): player.lobby_status = "waiting" player.lobby_batch_id = 0 player.lobby_invited_at = 0 player.lobby_invite_deadline = 0 player.lobby_confirmed_at = 0 player.lobby_ready_deadline = 0 player.lobby_last_ready_confirmed_at = 0 player.lobby_reconfirm_count = 0 player.lobby_reset_count += 1 if send_to_back or player.lobby_queue_entered_at == 0: player.lobby_queue_entered_at = now def next_batch_id(root_subsession): root_subsession.lobby_batch_counter += 1 return root_subsession.lobby_batch_counter def invite_player(player, batch_id, now): player.lobby_status = "invited" player.lobby_batch_id = batch_id player.lobby_invited_at = now player.lobby_invite_deadline = now + C.LOBBY_CONFIRM_SECONDS player.lobby_confirmed_at = 0 player.lobby_ready_deadline = 0 player.lobby_last_ready_confirmed_at = 0 player.lobby_reconfirm_count = 0 def finalize_lobby_group(root_subsession, confirmed_players): if any(p.lobby_final_group_id > 0 for p in confirmed_players): return confirmed_players = sorted( confirmed_players, key=lambda p: (p.lobby_confirmed_at, p.id_in_subsession) ) participant_codes = [p.participant.code for p in confirmed_players] participant_code_set = set(participant_codes) root_subsession.lobby_final_group_counter += 1 final_group_id = root_subsession.lobby_final_group_counter group_id_in_subsession = root_subsession._gbat_next_group_id_in_subsession() round1_group = None for round_number in range(1, C.NUM_ROUNDS + 1): subsession = root_subsession.in_round(round_number) round_players = [ p for p in subsession.get_players() if p.participant.code in participant_code_set ] player_map = {p.participant.code: p for p in round_players} missing_codes = [code for code in participant_codes if code not in player_map] if missing_codes: raise RuntimeError( f"Missing players in round {round_number} for participant codes: {missing_codes}" ) ordered_players = [player_map[code] for code in participant_codes] new_group = Group.objects_create( subsession=subsession, id_in_subsession=group_id_in_subsession, session=subsession.session, round_number=round_number, ) new_group.set_players(ordered_players) for round_player in ordered_players: round_player.lobby_final_group_id = final_group_id if round_number == 1: round1_group = new_group db.commit() set_matching(round1_group) set_player_code(round1_group) set_multipliers(round1_group) set_outside_options(round1_group) recover_replaced_by_bot(round1_group) for p in confirmed_players: p.lobby_status = "matched" p.lobby_matched_at = time.time() db.commit() class BeforeBargainingWaitPage(WaitPage): @staticmethod def is_displayed(player): return player.round_number > 1 @staticmethod def after_all_players_arrive(group: Group): set_round_to_pay(group.subsession) set_matching(group) set_player_code(group) set_multipliers(group) set_outside_options(group) recover_replaced_by_bot(group) # P02 ## Barg1 class P02_Bargaining_B1(Page): def is_displayed(player): return player.subsession.treatment_name == 'Barg1' def get_timeout_seconds(player): return player.subsession.bargaining_breakdown def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 self_offer_1_objects_A = "" if player.offer_1_objects_A == -1 else player.offer_1_objects_A self_offer_2_objects_A = "" if player.offer_2_objects_A == -1 else player.offer_2_objects_A self_offer_3_objects_A = "" if player.offer_3_objects_A == -1 else player.offer_3_objects_A self_offer_4_objects_A = "" if player.offer_4_objects_A == -1 else player.offer_4_objects_A match_offer_1_objects_A = "" if player.offer_1_objects_A == -1 else player.subsession.objects_A - player.offer_1_objects_A match_offer_2_objects_A = "" if player.offer_2_objects_A == -1 else player.subsession.objects_A - player.offer_2_objects_A match_offer_3_objects_A = "" if player.offer_3_objects_A == -1 else player.subsession.objects_A - player.offer_3_objects_A match_offer_4_objects_A = "" if player.offer_4_objects_A == -1 else player.subsession.objects_A - player.offer_4_objects_A return dict( self_offer_1_objects_A=self_offer_1_objects_A, self_offer_2_objects_A=self_offer_2_objects_A, self_offer_3_objects_A=self_offer_3_objects_A, self_offer_4_objects_A=self_offer_4_objects_A, match_offer_1_objects_A=match_offer_1_objects_A, match_offer_2_objects_A=match_offer_2_objects_A, match_offer_3_objects_A=match_offer_3_objects_A, match_offer_4_objects_A=match_offer_4_objects_A, ) def live_method(player, data): sender = player.id_in_group receiver = player.player_matched if data["message"] == "update": if player.all_offers_submitted_A: ## newnew 1102 player.all_offers_submitted_A += f"; {data['objects_A']}" else: player.all_offers_submitted_A = str(data["objects_A"]) ## newnew 1102 Offer.create( player=player, session=player.session.code, round_number=player.round_number, group=get_formal_group_id(player), # group=player.group.id_in_subsession, sender=sender, receiver=receiver, action=data["message"], offer_slot=data["slot"], offer_objects_A=data["objects_A"], offer_objects_B=data["objects_B"], time=data["time"], ) feedback = manage_message(player, data) if feedback: return {receiver: data, sender: data} else: return {receiver: data} def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: player.breakdown_happened = 1 player.ecu_earned_this_round = player.outside_option_A else: calculate_payoffs_B1_B2(player) calculate_earnings(player) class P02_Bargaining_B2(Page): def is_displayed(player): return player.subsession.treatment_name == 'Barg2' def get_timeout_seconds(player): return player.subsession.bargaining_breakdown def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 self_offer_1_objects_A = "" if player.offer_1_objects_A == -1 else player.offer_1_objects_A self_offer_1_objects_B = "" if player.offer_1_objects_B == -1 else player.offer_1_objects_B self_offer_2_objects_A = "" if player.offer_2_objects_A == -1 else player.offer_2_objects_A self_offer_2_objects_B = "" if player.offer_2_objects_B == -1 else player.offer_2_objects_B self_offer_3_objects_A = "" if player.offer_3_objects_A == -1 else player.offer_3_objects_A self_offer_3_objects_B = "" if player.offer_3_objects_B == -1 else player.offer_3_objects_B self_offer_4_objects_A = "" if player.offer_4_objects_A == -1 else player.offer_4_objects_A self_offer_4_objects_B = "" if player.offer_4_objects_B == -1 else player.offer_4_objects_B match_offer_1_objects_A = "" if player.offer_1_objects_A == -1 else player.subsession.objects_A - player.offer_1_objects_A match_offer_1_objects_B = "" if player.offer_1_objects_B == -1 else player.subsession.objects_B - player.offer_1_objects_B match_offer_2_objects_A = "" if player.offer_2_objects_A == -1 else player.subsession.objects_A - player.offer_2_objects_A match_offer_2_objects_B = "" if player.offer_2_objects_B == -1 else player.subsession.objects_B - player.offer_2_objects_B match_offer_3_objects_A = "" if player.offer_3_objects_A == -1 else player.subsession.objects_A - player.offer_3_objects_A match_offer_3_objects_B = "" if player.offer_3_objects_B == -1 else player.subsession.objects_B - player.offer_3_objects_B match_offer_4_objects_A = "" if player.offer_4_objects_A == -1 else player.subsession.objects_A - player.offer_4_objects_A match_offer_4_objects_B = "" if player.offer_4_objects_B == -1 else player.subsession.objects_B - player.offer_4_objects_B return dict( self_offer_1_objects_A=self_offer_1_objects_A, self_offer_1_objects_B=self_offer_1_objects_B, self_offer_2_objects_A=self_offer_2_objects_A, self_offer_2_objects_B=self_offer_2_objects_B, self_offer_3_objects_A=self_offer_3_objects_A, self_offer_3_objects_B=self_offer_3_objects_B, self_offer_4_objects_A=self_offer_4_objects_A, self_offer_4_objects_B=self_offer_4_objects_B, match_offer_1_objects_A=match_offer_1_objects_A, match_offer_1_objects_B=match_offer_1_objects_B, match_offer_2_objects_A=match_offer_2_objects_A, match_offer_2_objects_B=match_offer_2_objects_B, match_offer_3_objects_A=match_offer_3_objects_A, match_offer_3_objects_B=match_offer_3_objects_B, match_offer_4_objects_A=match_offer_4_objects_A, match_offer_4_objects_B=match_offer_4_objects_B, ) def live_method(player, data): sender = player.id_in_group receiver = player.player_matched if data["message"] == "update": if player.all_offers_submitted_A: ## newnew 1102 player.all_offers_submitted_A += f"; {data['objects_A']}" else: player.all_offers_submitted_A = str(data["objects_A"]) ## newnew 1102 if player.all_offers_submitted_B: ## newnew 1102 player.all_offers_submitted_B += f"; {data['objects_B']}" else: player.all_offers_submitted_B = str(data["objects_B"]) ## newnew 1102 Offer.create( player=player, session=player.session.code, round_number=player.round_number, group=get_formal_group_id(player), # group=player.group.id_in_subsession, sender=sender, receiver=receiver, action=data["message"], offer_slot=data["slot"], offer_objects_A=data["objects_A"], offer_objects_B=data["objects_B"], time=data["time"], ) feedback = manage_message(player, data) if feedback: return {receiver: data, sender: data} else: return {receiver: data} def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: player.breakdown_happened = 1 player.ecu_earned_this_round = player.outside_option_A else: calculate_payoffs_B1_B2(player) calculate_earnings(player) class P02_Bargaining1_O1_O2_SO1_SO2(Page): form_model = 'player' form_fields = ['submission_1_objects_A'] def is_displayed(player): return player.subsession.treatment_name in C.TREATMENTS_M def get_timeout_seconds(player): if player.replaced_by_bot == 1: if player.subsession.treatment_name in C.TREATMENTS_1: return C.TIMEOUT_BOT_SUBMISSION return C.TIMEOUT_BOT return C.TIMEOUT_SUBMISSION def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 objects = 0 if player.subsession.treatment_name in C.TREATMENTS_1: objects = 1 if player.subsession.treatment_name in C.TREATMENTS_2: objects = 2 return dict( objects=objects, ) def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: # player.timed_out = 1 # player.ecu_earned_this_round = 0 if player.replaced_by_bot == 0: player.timeout_page = "P02_Bargaining1_O1_O2_SO1_SO2" player.replaced_by_bot = 1 player.submission_1_objects_A = get_bot_submission_value(player) # ODR1 class BargainingWaitPage1(WaitPage): def after_all_players_arrive(group: Group): for player in group.get_players(): if player.timed_out == 0: run_mechanism_1(player) # P03 class P03_Mechanism1_O1_O2_SO1_SO2(Page): form_model = 'player' form_fields = ['accepted_suggestion_1'] def is_displayed(player): match = player.group.get_player_by_id(player.player_matched) return player.subsession.treatment_name in C.TREATMENTS_M and player.timed_out == 0 and match.timed_out == 0 and player.breakdown_happened == 0 def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT return C.TIMEOUT_MECHANISM def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 objects = 0 if player.subsession.treatment_name in C.TREATMENTS_1: objects = 1 if player.subsession.treatment_name in C.TREATMENTS_2: objects = 2 mechanisms = 0 if player.subsession.treatment_name in C.TREATMENTS_O: mechanisms = 1 if player.subsession.treatment_name in C.TREATMENTS_S: mechanisms = 2 if player.subsession.treatment_name in C.TREATMENTS_A: mechanisms = 3 ##newnew 250224 AIODR1 return dict( objects=objects, mechanisms=mechanisms, ) def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: # player.timed_out = 1 # player.ecu_earned_this_round = 0 if player.replaced_by_bot == 0: player.timeout_page = "P03_Mechanism1_O1_O2_SO1_SO2" player.replaced_by_bot = 1 if player.suggestion_1_objects_A >= player.outside_option_A: player.accepted_suggestion_1 = 1 else: player.accepted_suggestion_1 = 0 class MechanismWaitPage1(WaitPage): def after_all_players_arrive(group: Group): for player in group.get_players(): if player.timed_out == 0: if player.subsession.treatment_name in C.TREATMENTS_M: check_mutually_accepted_1(player) calculate_payoffs_O1_O2_S1_S2(player) calculate_earnings(player) # P04 class P04_Bargaining2_SO1_SO2(Page): form_model = 'player' form_fields = ['submission_2_objects_A'] def is_displayed(player): match = player.group.get_player_by_id(player.player_matched) return player.subsession.treatment_name in C.TREATMENTS_S and player.timed_out == 0 and match.timed_out == 0 and player.second_chance == 1 def get_timeout_seconds(player): if player.replaced_by_bot == 1: if player.subsession.treatment_name in C.TREATMENTS_1: return C.TIMEOUT_BOT_SUBMISSION return C.TIMEOUT_BOT return C.TIMEOUT_SUBMISSION def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 objects = 0 if player.subsession.treatment_name in C.TREATMENTS_1: objects = 1 if player.subsession.treatment_name in C.TREATMENTS_2: objects = 2 match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 return dict( objects=objects, match_accepted_suggestion_1=match_accepted_suggestion_1, ) def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: # player.timed_out = 1 # player.ecu_earned_this_round = 0 if player.replaced_by_bot == 0: player.timeout_page = "P04_Bargaining2_SO1_SO2" player.replaced_by_bot = 1 player.submission_2_objects_A = get_bot_submission_value(player) class BargainingWaitPage2(WaitPage): def after_all_players_arrive(group: Group): for player in group.get_players(): if player.timed_out == 0: if player.second_chance == 1: run_mechanism_2(player) # P05 class P05_Mechanism2_SO1_SO2(Page): form_model = 'player' form_fields = ['accepted_suggestion_2'] def is_displayed(player): match = player.group.get_player_by_id(player.player_matched) return player.subsession.treatment_name in C.TREATMENTS_S and player.timed_out == 0 and match.timed_out == 0 and player.second_chance == 1 and player.breakdown_happened == 0 def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT return C.TIMEOUT_MECHANISM def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 objects = 0 if player.subsession.treatment_name in C.TREATMENTS_1: objects = 1 if player.subsession.treatment_name in C.TREATMENTS_2: objects = 2 return dict( objects=objects, ) def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: # player.timed_out = 1 # player.ecu_earned_this_round = 0 if player.replaced_by_bot == 0: player.timeout_page = "P05_Mechanism2_SO1_SO2" player.replaced_by_bot = 1 if player.suggestion_2_objects_A >= player.outside_option_A: player.accepted_suggestion_2 = 1 else: player.accepted_suggestion_2 = 0 class MechanismWaitPage2(WaitPage): def after_all_players_arrive(group: Group): for player in group.get_players(): if player.timed_out == 0: if player.subsession.treatment_name in C.TREATMENTS_S: check_mutually_accepted_2(player) calculate_payoffs_O1_O2_S1_S2(player) calculate_earnings(player) # P06 class P06_Results_B1(Page): def is_displayed(player): return player.subsession.treatment_name == 'Barg1' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 return dict() def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_B1" player.replaced_by_bot = 1 class P06_Results_B2(Page): def is_displayed(player): return player.subsession.treatment_name == 'Barg2' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 return dict() def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_B2" player.replaced_by_bot = 1 class P06_Results_O1(Page): def is_displayed(player): return player.subsession.treatment_name in ['ODR1', 'OpaODR1'] def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_O1" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_timed_out=match.timed_out, ) class P06_Results_O2(Page): def is_displayed(player): return player.subsession.treatment_name == 'ODR2' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_O2" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_timed_out=match.timed_out, ) class P06_Results_SO1(Page): def is_displayed(player): return player.subsession.treatment_name == 'SeqODR1' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_SO1" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 match_accepted_suggestion_2 = match.accepted_suggestion_2 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_accepted_suggestion_2=match_accepted_suggestion_2, match_timed_out=match.timed_out, ) class P06_Results_SO2(Page): def is_displayed(player): return player.subsession.treatment_name == 'SeqODR2' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_SO2" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 match_accepted_suggestion_2 = match.accepted_suggestion_2 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_accepted_suggestion_2=match_accepted_suggestion_2, match_timed_out=match.timed_out, ) class P06_Results_AO1(Page): ## newnew 250224 AIODR1 def is_displayed(player): return player.subsession.treatment_name == 'AIODR1' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_AO1" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_timed_out=match.timed_out, ) ## newnew 250224 AIODR1 class P06_Results_AIsoph1(Page): ## newnew 250606 AIODRsoph1 def is_displayed(player): return player.subsession.treatment_name == 'AIODRsoph1' def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT if player.round_number == 1: return C.TIMEOUT_GENERIC_1 else: return C.TIMEOUT_GENERIC_2 def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P06_Results_AIsophO1" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() match = player.group.get_player_by_id(player.player_matched) match_accepted_suggestion_1 = match.accepted_suggestion_1 return dict( match_accepted_suggestion_1=match_accepted_suggestion_1, match_timed_out=match.timed_out, ) ## newnew 250606 AIODRsoph1 # P07 class P07_FinalResults(Page): def is_displayed(player): return player.round_number == C.NUM_ROUNDS def get_timeout_seconds(player): if player.replaced_by_bot == 1: return C.TIMEOUT_BOT def before_next_page(player, timeout_happened): time_on_page = time.time() - player.enter_time ## newnew 1028 page_name = player.participant._current_page_name if player.time_on_pages: player.time_on_pages += f"; {time_on_page}" else: player.time_on_pages = f"{time_on_page}" ## newnew 1028 if timeout_happened: if player.replaced_by_bot == 0: player.timeout_page = "P07_FinalResults" player.replaced_by_bot = 1 def vars_for_template(player): player.enter_time = time.time() ##newnew 1028 selected_round = player.in_round(player.round_to_pay) player.ecu_earned = selected_round.ecu_earned_this_round player.money_earned = round(player.ecu_earned * player.subsession.exchange_rate, 1) player.money_to_pay = player.money_earned player.participant.vars['money_earned_bargain'] = player.money_to_pay if player.replaced_by_bot == 1: player.ecu_earned = 0 player.money_earned = 0 player.money_to_pay = 0 player.participant.vars['money_earned_bargain'] = 0 rounds1 = list(range(1, int(C.NUM_ROUNDS / 2) + 1)) rounds2 = list(range(int(C.NUM_ROUNDS / 2) + 1, C.NUM_ROUNDS + 1)) earnings1 = [] earnings2 = [] for r in rounds1: earnings1.append(player.in_round(r).ecu_earned_this_round) for r in rounds2: earnings2.append(player.in_round(r).ecu_earned_this_round) return dict( table=zip(rounds1, earnings1, rounds2, earnings2), ) page_sequence = [ P01_LobbyMatching, # MatchingWaitPage, #P00_Welcome, BeforeBargainingWaitPage, P02_Bargaining_B1, P02_Bargaining_B2, P02_Bargaining1_O1_O2_SO1_SO2, BargainingWaitPage1, P03_Mechanism1_O1_O2_SO1_SO2, MechanismWaitPage1, P04_Bargaining2_SO1_SO2, BargainingWaitPage2, P05_Mechanism2_SO1_SO2, MechanismWaitPage2, P06_Results_B1, P06_Results_B2, P06_Results_O1, P06_Results_O2, P06_Results_SO1, P06_Results_SO2, P06_Results_AO1, ### newnew 250224 AIODR1 P06_Results_AIsoph1, ### newnew 250606 AIODRsoph1 P07_FinalResults] # Functions def creating_session(subsession): # Build environment set_treatment_vars(subsession) if subsession.round_number == 1: set_round_to_pay(subsession) set_bargaining_breakdown(subsession) players = subsession.get_players() ## newnew 1101 subsession.set_group_matrix([[p] for p in players]) # players[i:i + C.PLAYERS_PER_GROUP] for i in range(0, len(players), C.PLAYERS_PER_GROUP)]) ## newnew 1101 if subsession.round_number == 1: now = time.time() for player in players: player.lobby_status = "waiting" player.lobby_queue_entered_at = now player.lobby_last_seen_at = now player.lobby_tab_visible = True player.lobby_batch_id = 0 player.lobby_invited_at = 0 player.lobby_invite_deadline = 0 player.lobby_confirmed_at = 0 player.lobby_ready_deadline = 0 player.lobby_final_group_id = 0 player.lobby_last_ready_confirmed_at = 0 player.lobby_reconfirm_count = 0 player.lobby_reset_count = 0 player.lobby_matched_at = 0 def get_formal_group_id(player): round1_player = player.in_round(1) if round1_player.lobby_final_group_id: return round1_player.lobby_final_group_id return player.group.id_in_subsession def set_treatment_vars(subsession): subsession.treatment_name = subsession.session.config['treatment_name'] subsession.treatment_rep = subsession.session.config['treatment_rep'] subsession.exchange_rate = subsession.session.config['exchange_rate'] subsession.show_up_fee = subsession.session.config['show_up_fee'] subsession.objects_A = subsession.session.config['objects_A'] subsession.objects_B = subsession.session.config['objects_B'] subsession.group_order = subsession.session.config['group_order'] subsession.outside_type = subsession.session.config['outside_type'] def get_outside_option_slot(player): if player.matching not in [1, 2] or player.id_in_match not in [1, 2]: raise RuntimeError( f"Invalid pair assignment for player {player.id_in_subsession}: " f"matching={player.matching}, id_in_match={player.id_in_match}" ) return f"pair{player.matching}{player.id_in_match}" def set_player_code(group): for player in group.get_players(): slot = get_outside_option_slot(player) player.player_code = slot # def set_multipliers(group): ## previous setting for multipliers # Check if the csv file exists and open it # if os.path.exists(C.MULTIPLIERS_NAME): # with open(C.MULTIPLIERS_NAME, mode='r') as csv_file: # reader = csv.DictReader(csv_file) # Loop through all the rows of the csv # for row in reader: # Look for the correct row -> same session repetition and same round # if int(row["treatment_rep"]) == group.subsession.treatment_rep and int( # row["round"]) == group.subsession.round_number: # Extract multiplier for each subject # for player in group.get_players(): # player.objects_B_multiplier = float(row[player.player_code]) def set_multipliers(group): ## newnew 250401 change multiplier players = group.get_players() already_processed = set() for player in players: m = player.matching if m not in already_processed: multiplier = random.choice([0.7, 1.3]) for p in players: if p.matching == m: p.objects_B_multiplier = multiplier already_processed.add(m) ## maybe no need to modify multipliers def set_round_to_pay(subsession): for player in subsession.get_players(): if player.round_number == 1: player.round_to_pay = random.randint(1, C.NUM_ROUNDS) else: player.round_to_pay = player.in_round(1).round_to_pay def set_bargaining_breakdown(subsession): # Check if the csv file exists and open it if os.path.exists(C.BARGAINING_BREAKDOWN_NAME): with open(C.BARGAINING_BREAKDOWN_NAME, mode='r') as csv_file: reader = csv.DictReader(csv_file) # Loop through all the rows of the csv for row in reader: # Look for the correct row -> same session repetition and same round if int(row["treatment_rep"]) == subsession.treatment_rep and int( row["round"]) == subsession.round_number: # Extract duration subsession.bargaining_breakdown = int(row["duration"]) def get_outside_option_draw_key(subsession): return f"{subsession.treatment_name}:{subsession.treatment_rep}:{subsession.round_number}" def ensure_outside_option_draw(subsession): draw_store = subsession.session.vars.setdefault("outside_option_draws", {}) draw_key = get_outside_option_draw_key(subsession) if draw_key in draw_store: return draw_store[draw_key] if not os.path.exists(C.OUTSIDE_OPTIONS_NAME): raise RuntimeError(f"Missing outside options file: {C.OUTSIDE_OPTIONS_NAME}") with open(C.OUTSIDE_OPTIONS_NAME, mode='r', newline='') as csv_file: rows = list(csv.DictReader(csv_file)) if not rows: raise RuntimeError(f"No rows found in {C.OUTSIDE_OPTIONS_NAME}") selected_row = random.choice(rows) required_columns = ["draw", "pair11", "pair12", "pair21", "pair22"] missing_columns = [column for column in required_columns if column not in selected_row] if missing_columns: raise RuntimeError( f"Missing columns in {C.OUTSIDE_OPTIONS_NAME}: {', '.join(missing_columns)}" ) selected_draw = dict( draw=int(selected_row["draw"]), pair11=int(selected_row["pair11"]), pair12=int(selected_row["pair12"]), pair21=int(selected_row["pair21"]), pair22=int(selected_row["pair22"]), ) draw_store[draw_key] = selected_draw return selected_draw def set_outside_options(group): selected_draw = ensure_outside_option_draw(group.subsession) for player in group.get_players(): slot = get_outside_option_slot(player) player.player_code = slot player.outside_option_A = int(selected_draw[slot]) def recover_replaced_by_bot(group): for player in group.get_players(): if player.round_number > 1: player.replaced_by_bot = player.in_round(player.round_number - 1).replaced_by_bot def get_bot_submission_value(player): outside_option = player.outside_option_A if player.subsession.treatment_name in C.TREATMENTS_1: if outside_option < 75: return round((2 / 3) * outside_option + 25) return outside_option return outside_option def set_matching(group): # Build a list with all values from 0 to players_per_group twice # Players who receive the same number will be matched together matching_list = list(range(0, int(C.PLAYERS_PER_GROUP / 2))) * 2 # Shuffle the list random.shuffle(matching_list) # Give each player an element from the shuffled list for player, matching in zip(group.get_players(), matching_list): player.matching = matching + 1 # Get id from the matching for player in group.get_players(): for match in group.get_players(): if player.group == match.group and player.matching == match.matching and player.id_in_group != match.id_in_group: # Set player_matched player.player_matched = match.id_in_group player.matched_participant_code = match.participant.code ##newnew 0705 # Randomly set id_in_match ids = [1, 2] random.shuffle(ids) player.id_in_match = ids[0] match.id_in_match = ids[1] def manage_message(player, data): match = player.group.get_player_by_id(player.player_matched) total_A = player.subsession.objects_A total_B = player.subsession.objects_B # Security checks if total_A > player.subsession.objects_A: total_A = 0 if total_A < 0: total_A = 0 if total_B > player.subsession.objects_B: total_B = 0 if total_B < 0: total_B = 0 feedback = False if data["message"] == "update": if data["slot"] == 1: player.offer_1_objects_A = int(data["objects_A"]) player.offer_1_objects_B = int(data["objects_B"]) match.offer_3_objects_A = total_A - int(data["objects_A"]) match.offer_3_objects_B = total_B - int(data["objects_B"]) if data["slot"] == 2: player.offer_2_objects_A = int(data["objects_A"]) player.offer_2_objects_B = int(data["objects_B"]) match.offer_4_objects_A = total_A - int(data["objects_A"]) match.offer_4_objects_B = total_B - int(data["objects_B"]) if data["message"] == "cancel": if data["slot"] == 1: player.offer_1_objects_A = int(data["objects_A"]) player.offer_1_objects_B = int(data["objects_B"]) match.offer_3_objects_A = int(data["objects_A"]) match.offer_3_objects_B = int(data["objects_B"]) if data["slot"] == 2: player.offer_2_objects_A = int(data["objects_A"]) player.offer_2_objects_B = int(data["objects_B"]) match.offer_4_objects_A = int(data["objects_A"]) match.offer_4_objects_B = int(data["objects_B"]) if data["message"] == "reject": if data["slot"] == 1: match.offer_1_objects_A = int(data["objects_A"]) match.offer_1_objects_B = int(data["objects_B"]) player.offer_3_objects_A = int(data["objects_A"]) player.offer_3_objects_B = int(data["objects_B"]) if data["slot"] == 2: match.offer_2_objects_A = int(data["objects_A"]) match.offer_2_objects_B = int(data["objects_B"]) player.offer_4_objects_A = int(data["objects_A"]) player.offer_4_objects_B = int(data["objects_B"]) if data["message"] == "accept": feedback = True if data["slot"] == 1: player.accepted_objects_A = int(data["objects_A"]) player.accepted_objects_B = int(data["objects_B"]) player.accepted_offer = 3 match.accepted_objects_A = total_A - int(data["objects_A"]) match.accepted_objects_B = total_B - int(data["objects_B"]) match.accepted_offer = 1 if data["slot"] == 2: player.accepted_objects_A = int(data["objects_A"]) player.accepted_objects_B = int(data["objects_B"]) player.accepted_offer = 4 match.accepted_objects_A = total_A - int(data["objects_A"]) match.accepted_objects_B = total_B - int(data["objects_B"]) match.accepted_offer = 2 if data["message"] == "abandon": feedback = True return feedback def check_mutually_accepted_1(player): if player.suggestion_1_objects_A >= 0: match = player.group.get_player_by_id(player.player_matched) if player.accepted_suggestion_1 == 1 and match.accepted_suggestion_1 == 1: player.mutually_accepted_1 = 1 player.accepted_objects_A = player.suggestion_1_objects_A player.accepted_objects_B = player.suggestion_1_objects_B else: player.mutually_accepted_1 = 0 player.breakdown_happened = 1 def check_mutually_accepted_2(player): if player.suggestion_2_objects_A >= 0: match = player.group.get_player_by_id(player.player_matched) if player.accepted_suggestion_2 == 1 and match.accepted_suggestion_2 == 1: player.mutually_accepted_2 = 1 player.accepted_objects_A = player.suggestion_2_objects_A player.accepted_objects_B = player.suggestion_2_objects_B else: player.mutually_accepted_2 = 0 player.breakdown_happened = 1 def calculate_payoffs_B1_B2(player): if player.accepted_offer == 0: player.ecu_earned_this_round = player.outside_option_A else: # if player.subsession.outside_type == "Strong" and player.accepted_objects_A < player.outside_option_A: # player.ecu_earned_this_round = 0 # else: # 2604 drop the strong code if player.subsession.objects_B > 0: player.ecu_earned_this_round = round( player.accepted_objects_A + player.accepted_objects_B * player.objects_B_multiplier, 1) else: player.ecu_earned_this_round = round(player.accepted_objects_A, 1) def calculate_payoffs_O1_O2_S1_S2(player): match = player.group.get_player_by_id(player.player_matched) # Check if matched player timed out if match.timed_out == 1: player.match_timed_out = 1 if player.subsession.treatment_name in C.TREATMENTS_1: player.ecu_earned_this_round = player.outside_option_A if player.subsession.treatment_name in C.TREATMENTS_2: player.ecu_earned_this_round = player.outside_option_A + 50 * player.objects_B_multiplier else: # If there was an agreement if player.mutually_accepted_1 == 1 or player.mutually_accepted_2 == 1: # Check if Strong condition is active and broken # if player.subsession.outside_type == "Strong" and player.accepted_objects_A < player.outside_option_A: # player.ecu_earned_this_round = 0 # else: # 2604 drop strong code if player.subsession.objects_B > 0: player.ecu_earned_this_round = round( player.accepted_objects_A + player.accepted_objects_B * player.objects_B_multiplier, 1) else: player.ecu_earned_this_round = round(player.accepted_objects_A, 1) # If there was no agreement else: player.ecu_earned_this_round = player.outside_option_A def calculate_earnings(player): if player.round_number == player.round_to_pay: player.ecu_earned = player.ecu_earned_this_round player.money_earned = round(player.ecu_earned_this_round * player.subsession.exchange_rate, 1) if player.round_number > player.round_to_pay: player.ecu_earned = player.in_round(player.round_to_pay).ecu_earned player.money_earned = player.in_round(player.round_to_pay).money_earned # Check if player is a bot if player.replaced_by_bot == 1: player.money_earned = 0 def predict_outside_option_soph(submission): ## newnew 250606 AIODRsoph1 bins = [ (0, 10, 3.5), (11, 20, 10.5 + 3.5), (21, 30, 17.58 + 3.5), (31, 40, 20.08 + 3.5), (41, 50, 26.96 + 3.5), (51, 60, 34.51 + 3.5), (61, 70, 45.72 + 3.5), (71, 80, 54.94 + 3.5), (81, 90, 74.95 + 3.5), (91, 100, 80.80 + 3.5), ] for lower, upper, pred in bins: if lower <= submission <= upper: return int(round(pred)) return 3 # fallback, should not happen def run_mechanism_1(player): match = player.group.get_player_by_id(player.player_matched) objects_A = player.subsession.objects_A submissions_sum = player.submission_1_objects_A + match.submission_1_objects_A # Check if matched player timed out if match.timed_out == 1: player.match_timed_out = 1 if player.subsession.treatment_name in C.TREATMENTS_1: player.ecu_earned_this_round = player.outside_option_A if player.subsession.treatment_name in C.TREATMENTS_2: player.ecu_earned_this_round = player.outside_option_A + 50 * player.objects_B_multiplier else: # Check if the sum of both player submissions exceed the amount of objects A available if submissions_sum > objects_A: # If there are no second chances apply a breakdown if player.subsession.treatment_name in C.TREATMENTS_O: player.breakdown_happened = 1 # If there are second chances enable the key if player.subsession.treatment_name in C.TREATMENTS_S: player.second_chance = 1 # If AI will give suggestion before breakdown directly ## newnew 20250224 AIODR1 if player.subsession.treatment_name == "AIODR1": # match = player.group.get_player_by_id(player.player_matched) # newjust player.predicted_outside_option = round(-0.7696673+0.581821*player.submission_1_objects_A +0.0036717*player.submission_1_objects_A*player.submission_1_objects_A) match.predicted_outside_option = round(-0.7696673+0.581821*match.submission_1_objects_A +0.0036717*match.submission_1_objects_A*match.submission_1_objects_A) predicted_outside_options_sum = player.predicted_outside_option + match.predicted_outside_option player.predicted_outside_options_sum = predicted_outside_options_sum match.predicted_outside_options_sum = predicted_outside_options_sum if predicted_outside_options_sum > objects_A: player.breakdown_happened = 1 else: remaining_predict_objects = objects_A - predicted_outside_options_sum player.suggestion_1_objects_A = player.predicted_outside_option + remaining_predict_objects / 2 match.suggestion_1_objects_A = match.predicted_outside_option + remaining_predict_objects / 2 # sophicticated AI if player.subsession.treatment_name == "AIODRsoph1": ## newnew 250606 AIODRsoph1 player.predicted_outside_option = predict_outside_option_soph(player.submission_1_objects_A) match.predicted_outside_option = predict_outside_option_soph(match.submission_1_objects_A) predicted_outside_options_sum = player.predicted_outside_option + match.predicted_outside_option player.predicted_outside_options_sum = predicted_outside_options_sum match.predicted_outside_options_sum = predicted_outside_options_sum if predicted_outside_options_sum > objects_A: player.breakdown_happened = 1 else: remaining_predict_objects = objects_A - predicted_outside_options_sum player.suggestion_1_objects_A = player.predicted_outside_option + remaining_predict_objects / 2 match.suggestion_1_objects_A = match.predicted_outside_option + remaining_predict_objects / 2 ## newnew 250606 AIODRsoph1 else: # Treatments with only objects A if player.subsession.treatment_name in C.TREATMENTS_1: # How many objects can subjects share remaining_objects = objects_A - submissions_sum player.suggestion_1_objects_A = player.submission_1_objects_A + remaining_objects / 2 # Treatments with objects A and B if player.subsession.treatment_name in C.TREATMENTS_2: # If both subjects offered less than 50% if (player.submission_1_objects_A <= (objects_A / 2)) and ( match.submission_1_objects_A <= (objects_A / 2)): player.suggestion_1_objects_A = objects_A / 2 player.suggestion_1_objects_B = objects_A / 2 else: if player.submission_1_objects_A > (objects_A / 2): player.suggestion_1_objects_A = player.submission_1_objects_A player.suggestion_1_objects_B = objects_A - player.submission_1_objects_A if match.submission_1_objects_A > (objects_A / 2): player.suggestion_1_objects_A = objects_A - match.submission_1_objects_A player.suggestion_1_objects_B = match.submission_1_objects_A def run_mechanism_2(player): match = player.group.get_player_by_id(player.player_matched) objects_A = player.subsession.objects_A submissions_sum = player.submission_2_objects_A + match.submission_2_objects_A # Check if matched player timed out if match.timed_out == 1: player.match_timed_out = 1 if player.subsession.treatment_name in C.TREATMENTS_1: player.ecu_earned_this_round = player.outside_option_A if player.subsession.treatment_name in C.TREATMENTS_2: player.ecu_earned_this_round = player.outside_option_A + 50 * player.objects_B_multiplier else: # Check if the sum of both player submissions exceed the amount of objects A available if submissions_sum > objects_A: # Bargaining Breakdown player.breakdown_happened = 1 else: # Treatments with only objects A if player.subsession.treatment_name in C.TREATMENTS_1: # How many objects can subjects share remaining_objects = objects_A - submissions_sum player.suggestion_2_objects_A = player.submission_2_objects_A + remaining_objects / 2 # Treatments with objects A and B if player.subsession.treatment_name in C.TREATMENTS_2: # If both players offered less than 50% if (player.submission_2_objects_A <= (objects_A / 2)) and ( match.submission_2_objects_A <= (objects_A / 2)): player.suggestion_2_objects_A = objects_A / 2 player.suggestion_2_objects_B = objects_A / 2 else: if player.submission_2_objects_A > (objects_A / 2): player.suggestion_2_objects_A = player.submission_2_objects_A player.suggestion_2_objects_B = objects_A - player.submission_2_objects_A if match.submission_2_objects_A > (objects_A / 2): player.suggestion_2_objects_A = objects_A - match.submission_2_objects_A player.suggestion_2_objects_B = match.submission_2_objects_A def get_summary_name(player): treatment_name = player.subsession.treatment_name outside_type = player.subsession.outside_type if outside_type == "Weak": if treatment_name == "Barg1": return 'S01_Barg1_w.html' if treatment_name == "Barg2": return 'S03_Barg2_w.html' if treatment_name == "ODR1": return 'S05_ODR1_w.html' if treatment_name == "OpaODR1": return 'S05_ODR1_w.html' if treatment_name == "ODR2": return 'S07_ODR2_w.html' if treatment_name == "SeqODR1": return 'S09_SeqODR1_w.html' if treatment_name == "SeqODR2": return 'S11_SeqODR2_w.html' if outside_type == "Strong": if treatment_name == "Barg1": return 'S02_Barg1_s.html' if treatment_name == "Barg2": return 'S04_Barg2_s.html' if treatment_name == "ODR1": return 'S06_ODR1_s.html' if treatment_name == "OpaODR1": return 'S15_OpaODR1_s.html' if treatment_name == "ODR2": return 'S08_ODR2_s.html' if treatment_name == "SeqODR1": return 'S10_SeqODR1_s.html' if treatment_name == "SeqODR2": return 'S12_SeqODR2_s.html' if treatment_name == "AIODR1": ## newnew 20250224 AIODR1 return 'S13_AIODR1_s.html' if treatment_name == "AIODRsoph1": ## newnew 20250606 AIODRsoph1 return 'S14_AIODRsoph1_s.html' # Exporting offers data def custom_export(players): yield ['session', 'session_code', 'treatment_name', 'round_number', 'group', 'sender', 'receiver', 'action', 'offer_slot', 'offer_objects_A', 'offer_objects_B', 'time', 'all_offers_submitted_A', 'all_offers_submitted_B', 'page_time_records', 'predicted_outside_options_sum', 'predicted_outside_option'] for player in players: for offer in Offer.filter(player=player): yield [offer.session, player.session.code, player.subsession.treatment_name, offer.round_number, offer.group, offer.sender, offer.receiver, offer.action, offer.offer_slot, offer.offer_objects_A, offer.offer_objects_B, offer.time, player.all_offers_submitted_A, player.all_offers_submitted_B, player.time_on_pages, player.predicted_outside_options_sum, player.predicted_outside_option]