# Norm Experiment — oTree App ## Overview Three-condition between-subjects experiment eliciting participants' conditional preferences as a function of a descriptive norm. Conditions: | Condition | Elicitation method | |---|---| | `threshold` | Slider: participant states their norm threshold directly | | `staircase` | Staircasing: binary choices narrow down the switching point | | `full_MPL` | All 11 norm values presented in random order for each decision pair | Participants are **balanced across conditions** using a rotating session counter (participant 1 → threshold, 2 → staircase, 3 → full_MPL, then repeat). --- ## File Structure ``` dm_experiment/ ├── __init__.py ├── constants.py ← ALL INPUT TABLES go here — edit this file ├── models.py ← Player/Session data fields & helper methods ├── pages.py ← Page logic and sequence ├── settings.py ← oTree project settings (copy to project root) └── templates/ └── dm_experiment/ ├── _base.html ← Shared layout & CSS ├── Consent.html ├── NoConsent.html ├── GeneralInstructions.html ├── GeneralComprehension.html ├── ConditionInstructions.html ├── ConditionComprehension.html ├── TaskBlock1.html ← Shared template for both task blocks ├── TaskBlock2.html ├── ConsistencyChecks.html ├── FinalPayoff.html └── ProlificRedirect.html ``` --- ## Quick-Start Checklist ### 1. Install & set up ```bash pip install otree cp dm_experiment/settings.py settings.py # copy to project root otree devserver ``` ### 2. Edit `constants.py` — the only file you need to touch for content | Table | What to change | |---|---| | `DICTATOR_OPTIONS` | The 6 dictator options (own_payoff, transfer, multiplier) | | `RISK_OPTIONS` | The 6 risk options (left_outcome, right_outcome, probability_left) | | `ACTUAL_NORMS` | Real descriptive norm data for payoff calculation | | `CONSISTENCY_CHECKS` | Which 8 decision situations are used as consistency checks | Pairwise combinations (15 each) are **generated automatically** from these tables. ### 3. Add instruction text (placeholders are in templates) Search for `[Placeholder` or `[Add` in the template files and replace with your actual text. Files to update: - `GeneralInstructions.html` — main study instructions - `ConditionInstructions.html` — one block per condition (`threshold` / `staircase` / `full_MPL`) - `GeneralComprehension.html` — comprehension question labels & correct answers in `pages.py` - `ConditionComprehension.html` — condition-specific comprehension questions - `Consent.html` — information letter text ### 4. Update comprehension question correct answers in `pages.py` ```python # In GeneralComprehension class: CORRECT = {'gen_q1': 'a', 'gen_q2': 'b', 'gen_q3': 'c'} # In ConditionComprehension class: CORRECT = {'cond_q1': 'a', 'cond_q2': 'a'} ``` ### 5. Add your option visualisations The option boxes have a `[Visualization]` placeholder `
`. Replace it in `_base.html` / `TaskBlock1.html` with your actual visualisation code, passing the relevant option data from JavaScript (`sit.left` / `sit.right`). ### 6. Set your Prolific completion URL In `settings.py`: ```python prolific_completion_url = 'https://app.prolific.com/submissions/complete?cc=C197921G' ``` --- ## Data Fields Exported | Field | Description | |---|---| | `condition` | Participant's assigned condition | | `task_order` | JSON: order of task blocks | | `dictator_decisions` | JSON array of all dictator game decisions | | `risk_decisions` | JSON array of all risk task decisions | | `consistency_decisions` | JSON array of consistency check decisions | | `selected_task` | Which task was selected for payment | | `selected_pair_index` | Index of the selected pair | | `selected_option_chosen` | Option ID chosen by participant | | `other_payoff` | Payoff to other participant (dictator game) | | `risk_outcome_realised` | Realised lottery outcome (risk task) | | `page_timestamps` | JSON: start/end/duration per page | | `gen_q1_errors` … | Error counts for each comprehension question | | `payoff` | Final monetary payoff | ### Decision record structure (in JSON arrays) **Threshold condition:** ```json {"situation_index": 0, "task_type": "dictator", "left_option_id": "D1", "right_option_id": "D2", "a_is_left": true, "threshold": 4} ``` **Staircase condition:** ```json {"situation_index": 0, "task_type": "risk", "left_option_id": "R1", "right_option_id": "R2", "a_is_left": false, "switching_point": 7} ``` **Full MPL condition:** ```json {"situation_index": 0, "task_type": "dictator", "left_option_id": "D1", "right_option_id": "D2", "a_is_left": true, "choices_map": {"0":"D2","1":"D2","2":"D1","3":"D1",...}} ``` --- ## Payment Logic 1. Pool = all 15 dictator decisions + 15 risk decisions + 8 consistency checks 2. One situation is drawn at random 3. The actual descriptive norm (from `ACTUAL_NORMS`) is applied to the participant's conditional decision to determine which option they chose 4. For dictator: `payoff = own_payoff` of chosen option; `other_payoff = transfer × multiplier` 5. For risk: the applicable option's lottery is drawn using `probability_left` --- ## Condition Assignment Balanced using a **session-wide rotating counter** stored in `session.vars['condition_counter']`. - Participant 1 → threshold (counter=0, 0 % 3 = 0) - Participant 2 → staircase (counter=1, 1 % 3 = 1) - Participant 3 → full_MPL (counter=2, 2 % 3 = 2) - Participant 4 → threshold (counter=3, 3 % 3 = 0) - … If you run multiple sessions, the counter resets per session. To balance *across* sessions, note the counter value and manually set it at session creation, or use a database-level counter.