experiment-template/Tutorial oTree.md
duartegoncalvesds 73fc2121bf first commit
2026-03-12 21:05:38 +00:00

384 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Lecture: Introduction to oTree
**Goal:** Understand the architecture of an oTree application and build a basic Bayesian game.
---
### 0. Preliminaries:
- [Learn Python in X Minutes](https://learnxinyminutes.com/python/)
- [oTree documentation](https://otree.readthedocs.io/en/latest/)
- [oTree Tutorials](https://www.youtube.com/playlist?list=PLBL9eqPcwzGPli11Yighw5LWwzIifEFd_)
- [Deploying oTree on Heroku via command line](https://www.youtube.com/watch?v=VrPdBEghYEM)
---
## 1. oTree
### What is oTree?
oTree is an open-source, Starlette-based framework for implementing interactive experiments in the browser.
* **Logic:** Written in **Python**.
* **Interface:** Written in **HTML/Starlette** template tags.
* **Data Storage:** Automatic integration with **PostgreSQL** or **SQLite**.
* **Deployment:** Easy to host on **Heroku** or local servers.
---
### Installation + project creation
#### Install
```bash
python -m pip install otree
otree --version
```
#### Create a project
```bash
otree startproject myproject
cd myproject
otree devserver
```
Then open: `http://localhost:8000/`
#### Create an app inside the project
```bash
otree startapp my_app
```
---
### File layout (important ones)
Inside the project:
- `settings.py` — session configs, installed apps, currency settings, etc.
- `_static/` — CSS/JS (optional)
- `_templates/` — global templates (optional)
- `my_app/` — your app code
Inside an app:
- `__init__.py` — main app logic (models + pages + constants)
- `templates/my_app/*.html` — participant-facing pages
- `tests.py` — automated tests/bots (optional but recommended)
---
### The Core Structure
An oTree project is organised into **Apps**. Each app contains:
* **`__init__.py`**: The "brain." Defines your models, variables, and logic.
* **`Pages.py`**: Controls the sequence of what the participant sees.
* **Templates (`.html`)**: The visual interface for each page.
### The Hierarchy
1. **Session:** The entire group of participants.
2. **Subsession:** A specific round within an app.
3. **Group:** A collection of players interacting (e.g., in a prisoner's dilemma; we won't do this today).
4. **Player:** The individual unit.
5. **Pages**: The screens participants see (forms, instructions, results).
Data live in the database. Each **Player** has fields that get saved automatically.
---
### Defining Variables
Variables are defined in `__init__.py` under three classes: `Constants`, `Subsession`, `Group`, and `Player`.
```python
class Constants(BaseConstants):
NAME_IN_URL = 'Decision'
PLAYERS_PER_GROUP = None
NUM_PRACTICE_ROUNDS = 2
NUM_PAYMENT_ROUNDS = 3
NUM_ROUNDS = NUM_PRACTICE_ROUNDS + NUM_PAYMENT_ROUNDS
class Player(BasePlayer):
decision = models.IntegerField()
belief_1 = models.FloatField(min=0, max=100)
captcha_attempt_1 = models.StringField(label="", initial="")
```
More types of fields here https://otree.readthedocs.io/en/latest/models.html#fields.
### Forms and validation
Basic:
```python
class Decision(Page):
form_model = "player"
form_fields = ["choice"]
```
Cross-field validation:
```python
class Player(BasePlayer):
a = models.IntegerField()
b = models.IntegerField()
class SomePage(Page):
form_model = "player"
form_fields = ["a", "b"]
@staticmethod
def error_message(player, values):
if values["a"] + values["b"] != 10:
return "a + b must equal 10."
```
---
### Where to put logic: common hooks
- `Subsession.creating_session()`
Initialise per-session/per-round variables, assign treatments, form groups.
- `Page.is_displayed(player)`
Conditional page display (by round/treatment/role).
- `Page.vars_for_template(player)`
Pass computed values to the template.
- `Page.error_message(player, values)`
Validate across multiple fields.
- `Page.before_next_page(player, timeout_happened)`
Compute payoffs, set state, write vars.
- `Group.set_payoffs()` (your own method)
Common pattern: compute payoffs after everyone submits.
---
### Game Logic & Functions
Logic is triggered by events.
#### Determining if page is displayed
```python
class TaskFeedback(Page):
@staticmethod
def is_displayed(player):
return (
player.round_number <= C.NUM_PRACTICE_ROUNDS and
not player.participant.vars.get('failed_captcha', False)
)
```
#### Setting values in particular rounds
```python
def creating_session(subsession: Subsession):
for player in subsession.get_players():
if player.round_number == 1:
treatments_index = list(range(len(C.payoffs_list)))
random.shuffle(treatments_index)
for r in range(C.NUM_PAYMENT_ROUNDS):
player.in_round(C.NUM_PRACTICE_ROUNDS+1+r).treatment_index = treatments_index[r]
```
Useful built-in methods: https://otree.readthedocs.io/en/latest/models.html#built-in-fields-and-methods.
---
### The User Interface
Templates use HTML combined with oTree tags to display data dynamically.
**Contribute.html**
```html
{{ block title }}
Round {{ payment_round_number }} out of {{ C.NUM_PAYMENT_ROUNDS }}
{{ endblock }}
```
---
### Live pages (real-time interaction)
For truly interactive real-time tasks (e.g., chat, continuous-time trading),
oTree supports **live pages** (WebSocket-style). Basic pattern:
```python
class MyLivePage(Page):
live_method = "live_handler"
@staticmethod
def live_handler(player, data):
# data is a dict sent from JS
return {player.id_in_group: dict(message="received")}
```
You also write JS in the template to send/receive messages.
See more here: https://otree.readthedocs.io/en/latest/multiplayer/intro.html.
---
### Running the Server
To see your work in the browser:
1. **Initialise:** `otree devserver 8000`
2. **Access:** Open `http://localhost:8000`
3. **Data:** Go to the Admin tab to monitor real-time results and export CSV/Excel files.
---
### Quick Reference Table
| Command | Purpose |
| --- | --- |
| `otree startproject <name>` | Create a new project folder |
| `otree startapp <name>` | Create a new app within the project |
| `otree devserver` | Run the local testing server |
| `otree zip` | Package the app for server upload |
---
> **Important:** Always test your app using to ensure your payoff logic works before bringing in real participants.
---
## 2. Deployment
For testing/class projects, focus first on:
- local devserver (`otree devserver`);
- exporting data reliably.
Common option:
1. oTree app on GitHub.
2. Host on [heroku.com](heroku.com).
3. Recruit on [UCL ELFE](https://uclelfe.github.io) or [Prolific.com](https://www.prolific.com).
Template already fitted to deal with this pipeline.
The setting (in config; available when creating a session) completionlink is where you should put the prolific link.
Prolific ID should be saved in participant labels.
---
### On Heroku
- Create new app
- Deploy from GitHub; manual deploy; then turn on automatic deployments
- Add web dyno (GitHub education gives free dynos); worker dyno not needed
- Add-on: postgresql (cheapest for testing)
Running for real:
- Performance M dynos with auto-scalling with max latency of 500ms
- Postgresql Standard 2
- Config vars: OTREE_AUTH_LEVEL: STUDY; OTREE_PRODUCTION: 1; OTREE_ADMIN_PASSWORD: whateveryouwant
Downgrading postgresql is a real pain. Here's my cheatsheet:
```bash
Procedure to copy and remove previous database on heroku
# Step 0. Login
heroku login
# Step 1. Provision a new database
heroku addons:create heroku-postgresql:essential-0 --app example-app
# Step 2. Enter maintenance mode to prevent database writes
heroku maintenance:on --app appname
# Step 3. Transfer data to the new database
## Check name and colour; or on website; it takes a while to provision, give it 2-3 minutes for new database to to show up
heroku addons --all
heroku config --app example-app
## Copy
heroku pg:copy app_name_to_copy_from::HEROKU_POSTGRESQL_COLOUR_TO_COPY_FROM HEROKU_POSTGRESQL_COLOUR_TO_COPY_TO --app app_name_to_copy_to
# Step 4. Promote the new database
heroku pg:promote HEROKU_POSTGRESQL_COLOUR_TO_PROMOTE -a appname
# Step 5. Destroy previous database
heroku addons:destroy HEROKU_POSTGRESQL_COLOUR --confirm appname
# Step 6. Exit maintenance mode
heroku maintenance:off --app appname
Example
heroku login
heroku maintenance:on --app southern-path-2
heroku addons:create heroku-postgresql:essential-0 --app southern-path-2
heroku addons --all
# This gets you the colours of the database; note that it takes a bit to provision, so they won't show immediately
heroku config --app southern-path-2
heroku pg:copy southern-path-2::DATABASE HEROKU_POSTGRESQL_PINK_URL --app southern-path-2 --confirm southern-path-2
heroku pg:promote postgresql-clear-38905 -a southern-path-2
heroku addons:destroy postgresql-round-22954 --confirm southern-path-2
heroku maintenance:off --app southern-path-2
```
---
### On Prolific
My typical study name: Survey on Decision-Making (up to **£X.00/Nmin** with bonus)
Internal name: Acronym YYYY.MM.DD
My typical description:
```
This survey seeks to understand how individuals make choices and is conducted by academic researchers. 
You can earn up to £X.00 for Nmin.
You will earn a minimum of £Y.00. 
Please keep in mind that how much you will receive will depend on your own choices and the choices of other participants. The payment scheme is designed to encourage you to think carefully about your choices.
```
- Study label: None
- Device requirements: Desktop only
- Labels: None
- Limit participant access: Depends on your budget
- What's the URL of your study?
https://HEROKUAPPNAME.herokuapp.com/room/prolific?participant_label={{%PROLIFIC_PID%}}&STUDY_ID={{%STUDY_ID%}}&SESSION_ID={{%SESSION_ID%}}
- How do you want to record IDs? URL parameters
- Completion paths: Manually review
- Screeners:
- Current Country of Residence: United Kingdom (better sample)
- Approval Rate: 95100
- Fluent languages: English
- Exclude participants from other studies: any pilot and other waves of the same study that you've ran
- Do participants require a login & password to access your survey tool? No
- Total times a participant can complete your study: once
- Reject exceptionally fast submissions: NO!
---
### On ELFE
- Login to main terminal.
- Run the app in production mode, create ELFE session.
- Turn on all terminals and open the link.
- Test the Qualtrics questionnaire at the end.
- Test (elfe.lab.run)[elfe.lab.run].
- Schedule session on (elfe.lab.run)[elfe.lab.run] and invite participants.
On the day
- Login to main terminal.
- Run the app in production mode, create ELFE session.
- Turn on all terminals and open the link.
- Sit participants and allocate them on the app.
- Let them start after all seated.
- Take note of anything unusual.
- Show timer and remind participants of the time every half hour or so.
- Make sure everyone filled out the survey at the end before leaving (if they finished).
- Pay participants (lab manager, via Wise account).