from otree.api import * import time import json import requests doc = "Post-survey questionnaire and reward distribution." class C(BaseConstants): NAME_IN_URL = 'postsurvey' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 # It's recommended to store API keys securely (e.g., as environment variables) # rather than directly in the code, but this will work for now. API_URL = 'https://api.giftbit.com/papi/v1' API_KEY = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJTSEEyNTYifQ==.ZXk4SW02M09naENtd25nUmZlbXZNVVF6NnVpeUdKQVFEdVJzblBBVS9Db2U1VFNiajgweGtvTmxVWktZNG44WTBNajJzemtYWThhY0cyVEY1L2FVS0s3QXhMaDEwRit5aGJwaTlpdTRTRlVkK081Nk1rVGtxZk9GSGU3MVUxeFE=.InJfBAm622E0CQmHjQ2yi25Ad5D4Oh157JP3JnnS2Hg=' class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # --- Survey Fields (for tool users) --- trust_digital_tools = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How much do you trust digital tools in decision-making after using them in this project? (1 = Not at all, 5 = Completely)', widget=widgets.RadioSelectHorizontal ) overwhelmed = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How overwhelmed, if at all, did you feel by the tools provided? (1 = Not at all, 5 = Completely)', widget=widgets.RadioSelectHorizontal ) effectiveness = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How effective do you think your decisions were? (1 = Not confident, 5 = Very confident)', widget=widgets.RadioSelectHorizontal ) decision_confidence = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How confident are you in your decision-making abilities after using digital tools in this simulation? (1 = Not confident, 5 = Very confident)', widget=widgets.RadioSelectHorizontal ) newsvendor_knowledge = models.IntegerField( choices=[1, 2, 3, 4, 5], label='After completing this simulation, how familiar are you with the newsvendor model? (1 = Not familiar, 5 = Very familiar)', widget=widgets.RadioSelectHorizontal ) usefulness_digital_tools = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How useful did you find digital tools for improving your decision-making during the simulation? (1 = Not useful, 5 = Very useful)', widget=widgets.RadioSelectHorizontal ) informed_decisions = models.IntegerField( choices=[1, 2, 3, 4, 5], label='Did you feel the digital tools helped you make more informed decisions compared to traditional methods (e.g., intuition, manual calculations)? (1 = Not at all, 5 = Definitely)', widget=widgets.RadioSelectHorizontal ) ease_of_use = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How easy was it to use the digital tools during the project? (1 = Very difficult, 5 = Very easy)', widget=widgets.RadioSelectHorizontal ) understanding_complexity = models.IntegerField( choices=[1, 2, 3, 4, 5], label='How much did the digital tools help you understand complex problems? (1 = Not at all, 5 = A lot)', widget=widgets.RadioSelectHorizontal ) confidence_change = models.IntegerField( choices=[1, 2, 3, 4, 5], label='Did you feel more or less confident in your decisions after using digital tools in the simulation? (1 = Less confident, 5 = More confident)', widget=widgets.RadioSelectHorizontal ) future_use = models.IntegerField( choices=[1, 2, 3, 4, 5], label='Would you consider using digital tools in future assignments or decision-making tasks? (1 = Not at all, 5 = Definitely)', widget=widgets.RadioSelectHorizontal ) decision_making_aspects = models.LongStringField( blank=True, label='Which aspects of decision-making did the digital tools help improve the most? (e.g., Organizing information, making predictions, evaluating options)' ) better_decisions = models.IntegerField( choices=[1, 2, 3, 4, 5], label='Do you feel the digital tools helped you make better decisions during the project? (1 = Not at all, 5 = Definitely)', widget=widgets.RadioSelectHorizontal ) # --- Email Field (for ALL users) --- email = models.StringField( label="Please enter your email address to receive your payment:", blank=False # 'blank=False' makes the field required ) # ================================================================ # PAGES # ================================================================ class PostSurvey(Page): """This page contains the full survey and is ONLY shown to the tools group.""" form_model = 'player' # Add all the fields you want to display on this page to this list form_fields = [ 'trust_digital_tools', 'overwhelmed', 'effectiveness', 'decision_confidence', 'newsvendor_knowledge', 'usefulness_digital_tools', 'informed_decisions', 'ease_of_use', 'understanding_complexity', 'confidence_change', 'future_use', 'decision_making_aspects', 'better_decisions', ] @staticmethod def is_displayed(player: Player): # Only display this page to players who had tools tool_count = player.participant.vars.get('tool_count', 1) return tool_count > 1 @staticmethod def before_next_page(player: Player, timeout_happened): # This logic is specific to the survey, so it stays here player.participant.vars['postsurvey_newsvendor_knowledge'] = player.newsvendor_knowledge class EmailPage(Page): """This page ONLY collects the email and is shown to EVERYONE.""" form_model = 'player' form_fields = ['email'] @staticmethod def is_displayed(player: Player): # This page is for all players, so we just return True return True @staticmethod def error_message(player: Player, values): # Email validation logic now lives on this page if '@' not in values['email']: return 'Please enter a valid email address.' @staticmethod def before_next_page(player: Player, timeout_happened): """ This function is called after the user submits the page. We will use it to call the Giftbit API and send the reward. """ # --- 1. Get values from oTree --- participant_email = player.email # Use the oTree participant code as a unique identifier for our reward # We add the time to ensure it's 100% unique for retries unique_id = f"{player.participant.code}_{int(time.time())}" # --- 2. Set up the API Request --- headers = { "Authorization": f"Bearer {C.API_KEY}", "Content-Type": "application/json" } payload = { "id": unique_id, "price_in_cents": 500, "brand_codes": ["amazonus"], "delivery_type": "GIFTBIT_EMAIL", "contacts": [ { "email": participant_email } ], "subject": "Thank you for participating in our study!", "message": "Here is your $5 reward. We appreciate your time and contribution." } # --- 3. Send the Request and Log the Result --- try: # We print this to the oTree console for debugging print(f"Attempting to send reward to: {participant_email}") response = requests.post(C.API_URL, headers=headers, data=json.dumps(payload)) if response.status_code == 200: print(f"Success! Reward queued for {participant_email}.") # You could save the response ID to the player model if needed # player.giftbit_reward_id = response.json()['id'] else: # Log errors to the oTree console print(f"ERROR: Giftbit API failed for {participant_email}.") print(f"Status Code: {response.status_code}") print(f"Response: {response.json()}") except requests.exceptions.RequestException as e: # Handle network errors, etc. print(f"CRITICAL ERROR: Could not connect to Giftbit API. {e}") class RewardPage(Page): """This page is a final confirmation page for everyone.""" def vars_for_template(self): # Correctly access the player's email using self.player.email # Use field_maybe_none in case the player somehow skipped the email page return { 'email': self.field_maybe_none('email') } @staticmethod def is_displayed(player: Player): return True # Define the order in which players see the pages page_sequence = [PostSurvey, EmailPage, RewardPage]