from otree.api import * import time doc = """ Final screen: show the experiment ended and compute/display final payment. Final payment = participation_fee + implemented_part_payoff + implemented_part_bonus + wtp_bonus (if use_wtp). """ class C(BaseConstants): NAME_IN_URL = 'EndOfExperiment' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 # Map implemented_part -> participant.vars key where the payoff is stored # Adjust these keys to whatever you actually store upstream. PART_PAYOFF_KEY = { 'part1': 'part1_payoff', 'part2': 'part2_payoff', 'part3': 'part3_payoff', #'part4': 'part4_payoff', } class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Store components for export implemented_part = models.StringField(blank=True) participation_fee = models.CurrencyField() payoff_implemented_part = models.CurrencyField() bonus_implemented_part = models.CurrencyField() wtp_bonus = models.CurrencyField() final_payment = models.CurrencyField() def _to_cu(x) -> Currency: """ Convert int/float/str/Currency into Currency safely. """ if isinstance(x, Currency): return x if x is None: return cu(0) try: return cu(x) except Exception: return cu(0) def _euros_from_cents(cents) -> Currency: if cents is None: return cu(0) return cu(int(cents) / 100) def is_session1(player) -> bool: return bool(player.session.config.get('session1', False)) class End(Page): @staticmethod def vars_for_template(player: Player): part = player.participant v = part.vars session = player.session # --- participation fee (from settings) --- participation_fee = _to_cu(session.config.get('participation_fee', cu(0))) # --- which part is implemented --- implemented_part = v.get('implemented_part') if implemented_part is None: implemented_part = '' # keep running; you'll see it in export player.implemented_part = str(implemented_part) # --- implemented part payoff (read from participant.vars) --- if implemented_part == 'part2': payoff_implemented_part = _to_cu(v.get('part2_puzzles_payoff', 0)) + _to_cu( v.get('part2_sliders_payoff', 0)) else: payoff_key = C.PART_PAYOFF_KEY.get(implemented_part, None) payoff_implemented_part = _to_cu(v.get(payoff_key, 0)) if payoff_key else cu(0) # --- implemented part bonus (from session.config dict) --- bonus_dict = session.config.get('part_participation_bonus', {}) or {} bonus_implemented_part = _to_cu(bonus_dict.get(implemented_part, 0)) # --- WTP bonus --- use_wtp = bool(v.get('use_wtp', False)) wtp_base_payment_cents = v.get('wtp_base_payment_cents') wtp_base_payment = _euros_from_cents(wtp_base_payment_cents) # wtp_bonus = base_payment - participation_fee, only if use_wtp if use_wtp: wtp_bonus = wtp_base_payment - participation_fee if wtp_bonus < 0: wtp_bonus = cu(0) else: wtp_bonus = cu(0) # --- final payment --- final_payment = participation_fee + payoff_implemented_part + bonus_implemented_part + wtp_bonus # store for export player.participation_fee = participation_fee player.payoff_implemented_part = payoff_implemented_part player.bonus_implemented_part = bonus_implemented_part player.wtp_bonus = wtp_bonus player.final_payment = final_payment # If you want this to be the oTree payoff, uncomment: part.payoff = final_payment - participation_fee # Also store a few items in participant.vars for convenience at the end v['final_payment'] = float(final_payment) v['final_payment_participation_fee'] = float(participation_fee) v['final_payment_payoff_implemented_part_payoff'] = float(payoff_implemented_part) v['final_payment_bonus_implemented_part'] = float(bonus_implemented_part) v['final_payment_wtp_bonus'] = float(wtp_bonus) # set finished (always safe) part.vars.setdefault('finished_ts', time.time()) finished = part.vars.get('finished_ts') # duration (always safe) started = part.vars.get('started_ts') if started is None: started = getattr(part, 'started_ts', None) if started is not None and finished is not None: dur = int(finished - started) part.vars['duration_seconds'] = dur if hasattr(part, 'duration_seconds'): part.duration_seconds = dur return dict( final_payment=final_payment, participation_fee=participation_fee, implemented_part=implemented_part, payoff_implemented_part=payoff_implemented_part, bonus_implemented_part=bonus_implemented_part, use_wtp=use_wtp, wtp_base_payment=wtp_base_payment if use_wtp else None, wtp_bonus=wtp_bonus, is_session1=bool(player.session.config.get('session1', False)) ) class Waiting(Page): pass page_sequence = [End, Waiting]