from otree.api import * import random import json import copy doc = """ Description-Experience Gap Experiment: Tests four behavioral economics phenomena (fourfold pattern, common ratio, common consequence, salience) under three feedback conditions (no feedback, partial feedback, full feedback). 12 between-subjects cells, 40 rounds per participant. """ EFFECT_TYPES = ['fourfold', 'common_ratio', 'common_consequence', 'salience_mao'] FEEDBACK_CONDS = ['no_feedback', 'partial_feedback', 'full_feedback'] class C(BaseConstants): NAME_IN_URL = 'gambles' PLAYERS_PER_GROUP = None NUM_ROUNDS = 40 # --------------------------------------------------------------------------- # Helper functions # --------------------------------------------------------------------------- def format_percentage(prob): """Format probability as percentage string. 0.001 -> '0.1', 0.30 -> '30'.""" val = round(prob * 100, 1) if val == int(val): return str(int(val)) return str(val) def make_outcome(prob, payoff): return { 'probability': prob, 'percentage': format_percentage(prob), 'payoff': payoff, } # --------------------------------------------------------------------------- # Fixed item pool definitions # --------------------------------------------------------------------------- def build_fourfold_items(M): """Build fourfold pattern items (Kahneman & Tversky). Payoffs scale with M.""" m133 = round(1.33 * M) m2 = 2 * M return [ { 'item_id': 'ff_1', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(1.0, M)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.80, m133), make_outcome(0.20, 0)]}, ], 'metadata': {'cell': 'high_p_gains', 'domain': 'gains', 'probability': 'high', 'risk_prediction': 'averse', 'predicted_option': 'A', 'hypothesis': 'H1'}, }, { 'item_id': 'ff_2', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.20, m133), make_outcome(0.80, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.25, M), make_outcome(0.75, 0)]}, ], 'metadata': {'cell': 'low_p_gains', 'domain': 'gains', 'probability': 'low', 'risk_prediction': 'seeking', 'predicted_option': 'A', 'hypothesis': 'H2'}, }, { 'item_id': 'ff_3', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.80, -m133), make_outcome(0.20, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(1.0, -M)]}, ], 'metadata': {'cell': 'high_p_losses', 'domain': 'losses', 'probability': 'high', 'risk_prediction': 'seeking', 'predicted_option': 'A', 'hypothesis': 'H3'}, }, { 'item_id': 'ff_4', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.25, -M), make_outcome(0.75, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.20, -m133), make_outcome(0.80, 0)]}, ], 'metadata': {'cell': 'low_p_losses', 'domain': 'losses', 'probability': 'low', 'risk_prediction': 'averse', 'predicted_option': 'A', 'hypothesis': 'H4'}, }, { 'item_id': 'ff_5', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.90, M), make_outcome(0.10, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.45, m2), make_outcome(0.55, 0)]}, ], 'metadata': {'cell': 'high_p_gains', 'domain': 'gains', 'probability': 'high', 'risk_prediction': 'averse', 'predicted_option': 'A', 'hypothesis': 'H1'}, }, { 'item_id': 'ff_6', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.001, m2), make_outcome(0.999, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.002, M), make_outcome(0.998, 0)]}, ], 'metadata': {'cell': 'low_p_gains', 'domain': 'gains', 'probability': 'low', 'risk_prediction': 'seeking', 'predicted_option': 'A', 'hypothesis': 'H2'}, }, { 'item_id': 'ff_7', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.45, -m2), make_outcome(0.55, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.90, -M), make_outcome(0.10, 0)]}, ], 'metadata': {'cell': 'high_p_losses', 'domain': 'losses', 'probability': 'high', 'risk_prediction': 'seeking', 'predicted_option': 'A', 'hypothesis': 'H3'}, }, { 'item_id': 'ff_8', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.002, -M), make_outcome(0.998, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.001, -m2), make_outcome(0.999, 0)]}, ], 'metadata': {'cell': 'low_p_losses', 'domain': 'losses', 'probability': 'low', 'risk_prediction': 'averse', 'predicted_option': 'A', 'hypothesis': 'H4'}, }, ] def _make_cr_item(item_id, prob_a, payoff_a, prob_b, payoff_b, choice_number): """Build a common ratio item. All are payoff-or-zero binary lotteries.""" outcomes_a = [make_outcome(prob_a, payoff_a)] if prob_a < 1.0: outcomes_a.append(make_outcome(round(1.0 - prob_a, 10), 0)) outcomes_b = [make_outcome(prob_b, payoff_b)] if prob_b < 1.0: outcomes_b.append(make_outcome(round(1.0 - prob_b, 10), 0)) return { 'item_id': item_id, 'canonical_options': [ {'option_name': 'A', 'outcomes': outcomes_a}, {'option_name': 'B', 'outcomes': outcomes_b}, ], 'metadata': {'choice_number': choice_number}, } # DeKay 2016 Experiment 1 (11 items) COMMON_RATIO_ITEMS = [ _make_cr_item('cr_1', 0.30, 110, 0.40, 70, 1), _make_cr_item('cr_2', 0.25, 60, 0.20, 100, 2), _make_cr_item('cr_3', 0.15, 140, 0.30, 80, 3), _make_cr_item('cr_4', 0.02, 50, 0.01, 120, 4), _make_cr_item('cr_5', 0.50, 60, 0.25, 90, 5), _make_cr_item('cr_6', 0.40, 80, 0.30, 70, 6), _make_cr_item('cr_7', 0.65, 150, 0.85, 120, 7), _make_cr_item('cr_8', 0.80, 100, 1.00, 60, 8), _make_cr_item('cr_9', 0.02, 130, 0.04, 70, 9), _make_cr_item('cr_10', 0.45, 120, 0.90, 50, 10), _make_cr_item('cr_11', 0.10, 150, 0.25, 70, 11), ] def _make_cc_outcomes(p0, p1, p5): """Build outcomes for a common consequence option over {$0, $1, $5}, omitting zero-prob.""" outcomes = [] for prob, payoff in [(p0, 0), (p1, 1), (p5, 5)]: if prob > 0: outcomes.append(make_outcome(prob, payoff)) return outcomes def _make_cc_item(item_id, probs_a, probs_b, experiment, treatment, question, is_dominance_check=False): return { 'item_id': item_id, 'canonical_options': [ {'option_name': 'A', 'outcomes': _make_cc_outcomes(*probs_a)}, {'option_name': 'B', 'outcomes': _make_cc_outcomes(*probs_b)}, ], 'metadata': { 'experiment': experiment, 'treatment': treatment, 'question': question, 'is_dominance_check': is_dominance_check, }, } # Sopher 1993 — payoffs rescaled from $0/$1M/$5M to $0/$1/$5 COMMON_CONSEQUENCE_ITEMS = [ # Experiment I on-border (5 questions) _make_cc_item('cc_1ob_1', (0, 1.00, 0 ), (0.01, 0.89, 0.10), 1, 'on_border', 1), _make_cc_item('cc_1ob_2', (0.89, 0.11, 0 ), (0.90, 0.10, 0 ), 1, 'on_border', 2, is_dominance_check=True), _make_cc_item('cc_1ob_3', (0, 0.11, 0.89), (0.01, 0, 0.99), 1, 'on_border', 3), _make_cc_item('cc_1ob_4', (0.79, 0.11, 0.10), (0.80, 0, 0.20), 1, 'on_border', 4), _make_cc_item('cc_1ob_5', (0.01, 0.89, 0.10), (0.02, 0.78, 0.20), 1, 'on_border', 5), # Experiment I off-border (5 questions) _make_cc_item('cc_1of_1', (0.01, 0.98, 0.01), (0.02, 0.87, 0.11), 1, 'off_border', 1), _make_cc_item('cc_1of_2', (0.80, 0.19, 0.01), (0.81, 0.08, 0.11), 1, 'off_border', 2), _make_cc_item('cc_1of_3', (0.01, 0.19, 0.80), (0.02, 0.08, 0.90), 1, 'off_border', 3), _make_cc_item('cc_1of_4', (0.70, 0.19, 0.11), (0.71, 0.08, 0.21), 1, 'off_border', 4), _make_cc_item('cc_1of_5', (0.02, 0.87, 0.11), (0.03, 0.76, 0.21), 1, 'off_border', 5), # Experiment II on-border (5 questions) _make_cc_item('cc_2ob_1', (0, 1.000, 0 ), (0.100, 0, 0.900), 2, 'on_border', 1), _make_cc_item('cc_2ob_2', (0.700, 0.300, 0 ), (0.730, 0, 0.270), 2, 'on_border', 2), _make_cc_item('cc_2ob_3', (0, 0.300, 0.700), (0.030, 0, 0.970), 2, 'on_border', 3), _make_cc_item('cc_2ob_4', (0.380, 0.620, 0 ), (0.442, 0, 0.558), 2, 'on_border', 4), _make_cc_item('cc_2ob_5', (0, 0.620, 0.380), (0.062, 0, 0.938), 2, 'on_border', 5), # Experiment II off-border (5 questions) _make_cc_item('cc_2of_1', (0.010, 0.900, 0.090), (0.090, 0.100, 0.810), 2, 'off_border', 1), _make_cc_item('cc_2of_2', (0.710, 0.200, 0.090), (0.720, 0.100, 0.180), 2, 'off_border', 2), _make_cc_item('cc_2of_3', (0.010, 0.200, 0.790), (0.020, 0.100, 0.880), 2, 'off_border', 3), _make_cc_item('cc_2of_4', (0.390, 0.520, 0.090), (0.432, 0.100, 0.468), 2, 'off_border', 4), _make_cc_item('cc_2of_5', (0.010, 0.520, 0.470), (0.052, 0.100, 0.848), 2, 'off_border', 5), ] # Dertwinkel-Kalt 2019 Experiment 2 — Mao pairs # Canonical A = left-skewed, B = right-skewed SALIENCE_MAO_ITEMS = [ { 'item_id': 'sm_1', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.90, 120), make_outcome(0.10, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.90, 96), make_outcome(0.10, 216)]}, ], 'metadata': {'variance': 1296, 'abs_skewness': 2.7}, }, { 'item_id': 'sm_2', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.64, 135), make_outcome(0.36, 60)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.64, 81), make_outcome(0.36, 156)]}, ], 'metadata': {'variance': 1296, 'abs_skewness': 0.6}, }, { 'item_id': 'sm_3', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.90, 40), make_outcome(0.10, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.90, 32), make_outcome(0.10, 72)]}, ], 'metadata': {'variance': 144, 'abs_skewness': 2.7}, }, { 'item_id': 'sm_4', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.64, 45), make_outcome(0.36, 20)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.64, 27), make_outcome(0.36, 52)]}, ], 'metadata': {'variance': 144, 'abs_skewness': 0.6}, }, { 'item_id': 'sm_5', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.90, 80), make_outcome(0.10, 0)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.90, 64), make_outcome(0.10, 144)]}, ], 'metadata': {'variance': 576, 'abs_skewness': 2.7}, }, { 'item_id': 'sm_6', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(0.64, 90), make_outcome(0.36, 40)]}, {'option_name': 'B', 'outcomes': [make_outcome(0.64, 54), make_outcome(0.36, 104)]}, ], 'metadata': {'variance': 576, 'abs_skewness': 0.6}, }, ] # --------------------------------------------------------------------------- # Random item generators (optional, for supplementing fixed pools) # --------------------------------------------------------------------------- def generate_random_fourfold_items(n, M): """Generate n fourfold items with parameters close to the fixed set. Cycles through the four cells (high/low prob × gains/losses). Risky option has sampled probability and payoff; safe option is EV-matched (sometimes a sure thing, sometimes a higher-prob gamble). """ cells = ['high_p_gains', 'low_p_gains', 'high_p_losses', 'low_p_losses'] items = [] for i in range(n): cell = cells[i % 4] is_loss = 'loss' in cell is_high_p = 'high_p' in cell sign = -1 if is_loss else 1 # Risky option probability if is_high_p: p_risky = round(random.uniform(0.70, 0.95), 2) else: p_risky = round(random.uniform(0.05, 0.30), 2) multiplier = random.uniform(1.1, 2.5) risky_payoff = round(multiplier * M) ev_risky = p_risky * risky_payoff # Safe option: ~30% chance of sure thing, else higher-prob/lower-payoff gamble if random.random() < 0.3: safe_payoff = round(ev_risky * random.uniform(0.85, 1.00)) safe_outcomes = [make_outcome(1.0, sign * safe_payoff)] else: p_safe = min(round(p_risky * random.uniform(1.05, 1.4), 2), 0.99) safe_payoff = max(1, round(ev_risky * random.uniform(0.90, 1.10) / p_safe)) safe_outcomes = [ make_outcome(p_safe, sign * safe_payoff), make_outcome(round(1.0 - p_safe, 10), 0), ] risky_outcomes = [ make_outcome(p_risky, sign * risky_payoff), make_outcome(round(1.0 - p_risky, 10), 0), ] items.append({ 'item_id': f'ff_gen_{i + 1}', 'canonical_options': [ {'option_name': 'A', 'outcomes': safe_outcomes}, {'option_name': 'B', 'outcomes': risky_outcomes}, ], 'metadata': { 'cell': cell, 'domain': 'losses' if is_loss else 'gains', 'probability': 'high' if is_high_p else 'low', 'generated': True, }, }) return items def generate_random_cr_items(n): """Generate n common ratio items with parameters close to the fixed set. Produces payoff-or-zero binary lotteries with probabilities in [0.02, 0.85] and payoffs in [50, 160]. Option B's payoff is set so expected values are within ~30% of each other. """ items = [] for i in range(n): prob_a = round(random.uniform(0.02, 0.85), 2) payoff_a = random.randint(50, 160) ev_a = prob_a * payoff_a # prob_b must differ meaningfully from prob_a prob_b = prob_a while abs(prob_b - prob_a) < 0.05: prob_b = round(random.uniform(0.01, 1.00), 2) # payoff_b so EVs are roughly matched (±30%) target_ev_b = ev_a * random.uniform(0.7, 1.3) payoff_b = max(10, round(target_ev_b / prob_b)) item = _make_cr_item( f'cr_gen_{i + 1}', prob_a, payoff_a, prob_b, payoff_b, 100 + i + 1, ) item['metadata']['generated'] = True items.append(item) return items def _random_cc_probs(): """Random probability triple (p0, p1, p5) summing to 1, in 1% increments.""" n_nonzero = random.choices([2, 3], weights=[3, 2])[0] if n_nonzero == 2: active = sorted(random.sample([0, 1, 2], 2)) pcts = [0, 0, 0] pcts[active[0]] = random.randint(1, 99) pcts[active[1]] = 100 - pcts[active[0]] else: pcts = [0, 0, 0] pcts[0] = random.randint(1, 98) pcts[1] = random.randint(1, 99 - pcts[0]) pcts[2] = 100 - pcts[0] - pcts[1] return tuple(p / 100 for p in pcts) def generate_random_cc_items(n): """Generate n common consequence items with parameters close to the fixed set. Produces three-outcome lotteries over {$0, $1, $5} with random probability triples (in 1% increments). Each item has two options that differ meaningfully (total probability shift >= 10 percentage points). """ items = [] for i in range(n): probs_a = _random_cc_probs() probs_b = _random_cc_probs() # Ensure options are meaningfully different while sum(abs(a - b) for a, b in zip(probs_a, probs_b)) < 0.10: probs_b = _random_cc_probs() item = _make_cc_item( f'cc_gen_{i + 1}', probs_a, probs_b, experiment=0, treatment='generated', question=i + 1, ) item['metadata']['generated'] = True items.append(item) return items def generate_random_sm_items(n): """Generate n salience Mao pair items with parameters close to the fixed set. Uses the Mao pair construction: given base payoff b, probability p > 0.5, and spread Δ, the left-skewed lottery is (b+Δ, p; b, 1-p) and the right-skewed lottery is (b+(2p-1)Δ, p; b+2pΔ, 1-p). Both have the same mean (b + pΔ) and variance (p(1-p)Δ²) but opposite skewness. Parameters sampled from ranges matching Dertwinkel-Kalt 2019 Exp 2: b ∈ [0, 80], p ∈ [0.55, 0.95], Δ ∈ [20, 150]. """ items = [] for i in range(n): p = round(random.uniform(0.55, 0.95), 2) q = round(1.0 - p, 10) b = random.randint(0, 80) delta = random.randint(20, 150) a = b + delta c = round(b + (2 * p - 1) * delta) d = round(b + 2 * p * delta) variance = round(p * (1 - p) * delta ** 2) items.append({ 'item_id': f'sm_gen_{i + 1}', 'canonical_options': [ {'option_name': 'A', 'outcomes': [make_outcome(p, a), make_outcome(q, b)]}, {'option_name': 'B', 'outcomes': [make_outcome(p, c), make_outcome(q, d)]}, ], 'metadata': {'variance': variance, 'generated': True}, }) return items # --------------------------------------------------------------------------- # Sequence generators # --------------------------------------------------------------------------- def generate_round_sequence(pool_size, num_rounds=40): """Repeat shuffled pool to fill num_rounds, no consecutive repeats at boundaries.""" sequence = [] while len(sequence) < num_rounds: block = list(range(pool_size)) random.shuffle(block) if sequence and block[0] == sequence[-1] and pool_size > 1: swap_idx = random.randint(1, pool_size - 1) block[0], block[swap_idx] = block[swap_idx], block[0] sequence.extend(block) return sequence[:num_rounds] def generate_swap_sequence(num_rounds=40): """Random booleans: True = swap canonical A/B display positions.""" return [random.choice([True, False]) for _ in range(num_rounds)] def build_gamble_for_round(item, swapped): """Build display-ready gamble dict with outcome_names assigned by display position.""" a = copy.deepcopy(item['canonical_options'][0]) b = copy.deepcopy(item['canonical_options'][1]) if swapped: display_a, display_b = b, a else: display_a, display_b = a, b display_a['option_name'] = 'A' display_b['option_name'] = 'B' for i, outcome in enumerate(display_a['outcomes']): outcome['outcome_name'] = f'A_{i}' for i, outcome in enumerate(display_b['outcomes']): outcome['outcome_name'] = f'B_{i}' return {'options': [display_a, display_b]} # --------------------------------------------------------------------------- # oTree models and session logic # --------------------------------------------------------------------------- class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): if subsession.round_number == 1: players = subsession.get_players() n = len(players) M = subsession.session.config.get('monetary_unit_M', 100) n_extra = subsession.session.config.get('n_extra_items', 0) item_pools = { 'fourfold': build_fourfold_items(M), 'common_ratio': list(COMMON_RATIO_ITEMS), 'common_consequence': list(COMMON_CONSEQUENCE_ITEMS), 'salience_mao': list(SALIENCE_MAO_ITEMS), } if n_extra > 0: item_pools['fourfold'].extend(generate_random_fourfold_items(n_extra, M)) item_pools['common_ratio'].extend(generate_random_cr_items(n_extra)) item_pools['common_consequence'].extend(generate_random_cc_items(n_extra)) item_pools['salience_mao'].extend(generate_random_sm_items(n_extra)) force_effect = subsession.session.config.get('force_effect', '') if force_effect and force_effect in EFFECT_TYPES: # Fixed effect, balanced feedback assignment feedback_indices = list(range(3)) * ((n // 3) + 1) feedback_indices = feedback_indices[:n] random.shuffle(feedback_indices) cells = [EFFECT_TYPES.index(force_effect) * 3 + fi for fi in feedback_indices] else: # Balanced cell assignment: repeat [0..11], truncate to n, shuffle cells = list(range(12)) * ((n // 12) + 1) cells = cells[:n] random.shuffle(cells) for i, p in enumerate(players): cell = cells[i] effect_type = EFFECT_TYPES[cell // 3] feedback_cond = FEEDBACK_CONDS[cell % 3] pool = item_pools[effect_type] p.participant.feedback_cond = feedback_cond p.participant.effect_type = effect_type p.participant.cell_index = cell p.participant.item_pool = pool p.participant.round_sequence = generate_round_sequence( len(pool), C.NUM_ROUNDS ) p.participant.swap_sequence = generate_swap_sequence(C.NUM_ROUNDS) # Set player fields for this round for p in subsession.get_players(): round_idx = subsession.round_number - 1 item_idx = p.participant.round_sequence[round_idx] item = p.participant.item_pool[item_idx] swapped = p.participant.swap_sequence[round_idx] gamble = build_gamble_for_round(item, swapped) p.gambles = json.dumps(gamble) p.feedback_cond = p.participant.feedback_cond p.effect_type = p.participant.effect_type p.item_id = item['item_id'] p.options_swapped = swapped p.cell_index = p.participant.cell_index p.effect_metadata = json.dumps(item.get('metadata', {})) class Group(BaseGroup): pass class Player(BasePlayer): gambles = models.StringField(default="") choice = models.StringField(default="-1") feedback_cond = models.StringField(default="") outcome_idx = models.IntegerField(default=-1) all_outcomes = models.StringField(default="") outcome_name = models.StringField(default="") effect_metadata = models.StringField(default="") item_id = models.StringField(default="") effect_type = models.StringField(default="") options_swapped = models.BooleanField() canonical_choice = models.StringField(default="") cell_index = models.IntegerField(default=-1) # --------------------------------------------------------------------------- # Pages # --------------------------------------------------------------------------- def calculate_outcome(gambles, choice): gamble = [g for g in gambles["options"] if g["option_name"] == choice][0] weights = [outcome["probability"] for outcome in gamble["outcomes"]] outcome_idx = random.choices(range(len(weights)), weights=weights)[0] outcome = gamble["outcomes"][outcome_idx] return outcome, outcome_idx def calculate_all_outcomes(gambles, choice): pos_choices = [g["option_name"] for g in gambles["options"]] payoffs = [] for c in pos_choices: outcome, outcome_idx = calculate_outcome(gambles, c) payoffs.append({ "choice": c, "payoff": outcome["payoff"], "outcome_name": outcome["outcome_name"], "outcome_idx": outcome_idx, }) return payoffs class BinaryGamble(Page): form_fields = ['choice'] form_model = 'player' @staticmethod def live_method(player, data): if "choice" in data: if player.choice != "-1": all_outcomes = json.loads(player.all_outcomes) outcome_name = [o["outcome_name"] for o in all_outcomes if o["choice"] == player.choice][0] return {player.id_in_group: { "payoff": player.payoff, "outcome_idx": player.outcome_idx, "type": "outcome", "choice": player.choice, "all_outcomes": player.all_outcomes, "outcome_name": outcome_name, }} player.choice = data["choice"] # Compute canonical choice (undo display swap) if player.options_swapped: player.canonical_choice = ( "B" if data["choice"] == "A" else "A" ) else: player.canonical_choice = data["choice"] all_outcomes = calculate_all_outcomes( json.loads(player.gambles), data["choice"] ) outcome = [o for o in all_outcomes if o["choice"] == data["choice"]][0] player.outcome_idx = outcome["outcome_idx"] player.payoff = outcome["payoff"] player.all_outcomes = json.dumps(all_outcomes) player.outcome_name = outcome["outcome_name"] return {player.id_in_group: { "payoff": outcome["payoff"], "outcome_idx": outcome["outcome_idx"], "type": "feedback", "choice": player.choice, "all_outcomes": player.all_outcomes, "outcome_name": outcome["outcome_name"], }} @staticmethod def js_vars(player): return {"gamble_data": json.loads(player.gambles)} @staticmethod def vars_for_template(player): return {"gamble_data": json.loads(player.gambles)} page_sequence = [BinaryGamble]