File size: 11,331 Bytes
c9196c4
 
 
071cfd5
 
 
e62445f
0a27978
efd290d
071cfd5
c9196c4
4d9a2c9
6daa786
 
 
 
 
 
 
 
 
 
 
4d9a2c9
 
 
 
 
 
efd290d
 
4d9a2c9
 
 
 
 
 
 
 
 
 
 
 
4dcfe05
 
 
791b3f1
4dcfe05
da23793
4dcfe05
 
 
 
4d9a2c9
4dcfe05
efd290d
 
4d9a2c9
 
4dcfe05
4d9a2c9
 
 
 
4dcfe05
 
 
 
 
 
 
 
 
 
 
 
071cfd5
 
ebb5620
d3cdf49
 
071cfd5
 
 
 
a485aee
071cfd5
 
 
 
 
a666b37
071cfd5
 
 
abffdcf
 
 
 
 
 
 
 
 
4dcfe05
abffdcf
 
 
1e12aa0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ea5e48
 
f9ba5d1
 
1e12aa0
46cbeb2
88deb61
 
 
4836da8
88deb61
77a2745
 
 
f6374fa
46cbeb2
a9f4a86
 
01b80e5
 
4836da8
a9f4a86
01b80e5
 
4836da8
46cbeb2
01b80e5
4836da8
f6374fa
88deb61
 
 
7d9ed5d
c9196c4
071cfd5
 
 
 
f6374fa
071cfd5
3fd32a0
 
f6374fa
ab131b6
f6374fa
 
071cfd5
f6374fa
77a2745
 
 
 
 
 
f6374fa
071cfd5
77a2745
 
 
f6374fa
77a2745
 
 
 
 
3347a66
 
071cfd5
77a2745
 
 
 
9be2a68
 
 
 
 
 
f6374fa
9be2a68
77a2745
9be2a68
f6374fa
 
 
 
97eefc3
 
 
f6374fa
97eefc3
 
 
 
4836da8
 
f6374fa
97eefc3
f6374fa
 
 
 
 
f9ba5d1
cb8c338
 
 
921810a
f6374fa
5a8d686
77a2745
071cfd5
77a2745
071cfd5
3ea5e48
071cfd5
 
 
 
 
 
a485aee
e62445f
071cfd5
 
 
77a2745
 
 
88deb61
 
7d9ed5d
e62445f
3347a66
071cfd5
3b249e4
 
 
42a7017
2983940
6fdfc42
 
 
 
 
 
 
efd290d
a485aee
6fdfc42
 
 
 
3b249e4
aba867e
6fdfc42
 
 
 
 
 
4dcfe05
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
import streamlit as st
import pandas as pd
import random
import time
import string
import gspread
import os
import json
import datetime
from oauth2client.service_account import ServiceAccountCredentials

# Load worker-specific stimuli
def get_google_creds():
    service_account_json = os.getenv("SERVICE_ACCOUNT_JSON")
    if service_account_json:
        creds_dict = json.loads(service_account_json)
        scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
        creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
        return gspread.authorize(creds)
    else:
        st.error("Google service account credentials not found.")
        return None

def get_next_worker_id():
    client = get_google_creds()
    if client is None:
        return None
    
    try:
        # sheet_name = "Odd-One-Out Experiment Responses"
        sheet_name = "Odd-one-out results v2.0"
        sheet = client.open(sheet_name).sheet1
        existing_worker_ids = sheet.col_values(1)[1:]  # Get worker IDs (skip header)
        assigned_workers = set(map(int, existing_worker_ids))
        
        for i in range(1, 21):  # Assign worker ID between 1 and 20
            if i not in assigned_workers:
                return i
        return None  # No available worker slots
    except Exception as e:
        st.error(f"Error retrieving worker ID: {str(e)}")
        return None

# Load worker-specific stimuli
@st.cache_data
def load_worker_data(worker_id):
    file_path = os.path.join("exp1", f"worker_{worker_id:02d}.tsv")
    if os.path.exists(file_path):
        df = pd.read_csv(file_path, sep='\t')
        return df.dropna().reset_index(drop=True)
    else:
        return None

# Automatically assign worker ID
if 'worker_id' not in st.session_state:
    # assigned_worker_id = get_next_worker_id()
    assigned_worker_id = 4
    if assigned_worker_id is None:
        st.error("No available worker slots. Please try again later.")
        st.stop()
    st.session_state.worker_id = assigned_worker_id
    st.session_state.df = load_worker_data(st.session_state.worker_id)
    st.session_state.step = "instructions"
    st.rerun()

# Load main experiment data assigned to individual worker
df = st.session_state.df
if df is None:
    st.error("No stimuli available for this worker.")
    st.stop()

# Function to generate a unique passcode
def generate_passcode():
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))

# Function to upload responses to Google Drive
def upload_to_google_drive(response_df):
    try:
        client = get_google_creds()
        # sheet_name = "Odd-One-Out Experiment Responses"
        sheet_name = "Odd-one-out results v2.0"
        try:
            sheet = client.open(sheet_name).sheet1
        except gspread.exceptions.SpreadsheetNotFound:
            sheet = client.create(sheet_name).sheet1
            sheet.append_row(["worker_id", "passcode", "question", "keyword", "selected", "correct_answer", "is_correct", "response_time"])

        data_list = response_df.values.tolist()
        for row in data_list:
            sheet.append_row(row)

        st.success("โœ… Your responses have been recorded successfully.")
    except Exception as e:
        st.error(f"Error uploading to Google Drive: {str(e)}")

# Load training samples
def load_training_samples():
    file_path = "training_samples.csv"
    if os.path.exists(file_path):
        return pd.read_csv(file_path).dropna().reset_index(drop=True)
    else:
        st.error("Training samples file not found.")
        return pd.DataFrame()


# Load training data
training_df = load_training_samples()

# Initialize session state variables
if "step" not in st.session_state:
    st.session_state.step = "instructions"
if "training_index" not in st.session_state:
    st.session_state.training_index = 0
if "training_complete" not in st.session_state:
    st.session_state.training_complete = False
if "experiment_index" not in st.session_state:
    st.session_state.experiment_index = 0
if "experiment_complete" not in st.session_state:
    st.session_state.experiment_complete = False
if "responses" not in st.session_state:
    st.session_state.responses = []
if "start_time" not in st.session_state:
    st.session_state.start_time = None
if "passcode" not in st.session_state:
    st.session_state.passcode = None
if "show_answer" not in st.session_state:
    st.session_state.show_answer = False

# Increase font size for instructions and multiple-choice options
st.markdown("""
    <style>
    .stApp {
        font-size: 17px !important;
    }
    .correct-answer {
        color: green;
    }
    .highlight-red {
        color: #D9534F;
        font-weight: bold;
    }
    /* Increase font size for radio button labels */
    div[data-testid="stRadio"] label {
        font-size: 17px !important;
    }
    /* Increase font size for general text */
    div[data-testid="stMarkdownContainer"] {
        font-size: 17px !important;
    }
    div[data-testid="stVerticalBlock"] p {
        font-size: 17px !important;
    }
    </style>
    """, unsafe_allow_html=True)

st.title("Scene Identification Experiment")

# **STEP 1: Instructions Page**
if st.session_state.step == "instructions":
    st.header("๐Ÿ“– Instructions")
    st.write("""
        Welcome to the experiment! Hereโ€™s how it works:

        - You will be presented with five short text passages excerpted from **fictional stories**.
        - The passages describe scenes related to a **specific keyword**.
        - Four of these passages describe **a similar type of scene**, while **one is different**.
        - Your task is to identify the passage that describes <span style='color:#D9534F; font-weight:bold;'>a scene distinct from the rest</span>.
        - Please try to answer as quickly and accurately as possible.
    """, unsafe_allow_html=True)

    if st.button("Start Practicing"):
        st.session_state.step = "training"
        st.rerun()

# **STEP 2: Training Phase**
elif st.session_state.step == "training":
    if st.session_state.training_index >= len(training_df):
        st.session_state.step = "training_complete"
        st.rerun()
    else:
        row = training_df.iloc[st.session_state.training_index]
        
        st.write(f"### Practice Sample {st.session_state.training_index + 1}/{len(training_df)}")
        st.write(f"**Keyword: {row['keyword']}**")
        
        options = [row['text_1'], row['text_2'], row['text_3'], row['text_4'], row['text_5']]
        correct_answer = options[row['odd_index'] - 1]  # Get the correct answer using odd_index
        
        user_choice = st.radio("Which scene is distinct from the others?", options, 
            key=f"training_{st.session_state.training_index}", index=None)

        if st.button("Submit Answer"):
            if user_choice is None:
                st.warning("โš ๏ธ Please select an option first!")
            else:
                st.session_state.show_answer = True
                st.rerun()

        if st.session_state.show_answer:
            st.write("### โœ… The correct answer was:")
            st.markdown(f"<p class='correct-answer'>{correct_answer}</p>", unsafe_allow_html=True)
            if st.button("Next Practice Sample"):
                st.session_state.show_answer = False
                st.session_state.training_index += 1
                st.rerun()

# **STEP 3: Training Complete Page**
elif st.session_state.step == "training_complete":
    st.header("๐ŸŽ‰ Practice Complete!")
    # st.write("""
    # You have completed the practice phase! Now, you will proceed to the main experiment.
    # Please remember to select the passage that describes <span style='color:#D9534F; font-weight:bold;'>a distinct scene from the rest</span>.
    
    # Click **"Start Experiment"** when you are ready. Your response time will begin after you proceed.
    # """)
    st.markdown("""
        You have completed the practice phase! Now, you will proceed to the main experiment.  
        Please remember to select the passage that describes <span style='color:#D9534F; font-weight:bold;'>a distinct scene from the rest</span>.

    Click **"Start Experiment"** when you are ready. Your response time will begin after you proceed.
    """, unsafe_allow_html=True)
    
    if st.button("Start Experiment"):
        st.session_state.start_time = time.time()
        st.session_state.step = "experiment"
        st.rerun()

# Ensure 'Submit Answer' button is hidden after experiment completion
if 'experiment_complete' not in st.session_state:
    st.session_state.experiment_complete = False

# **STEP 4: Main Experiment**
elif st.session_state.step == "experiment":
    if st.session_state.experiment_index >= len(df):
        st.success("โœ… You've completed the experiment!")
        st.session_state.experiment_complete = True

        if "passcode" not in st.session_state or st.session_state.passcode is None:
            st.session_state.passcode = generate_passcode()

        st.write("### ๐ŸŽ‰ Your unique completion passcode:")
        st.code(st.session_state.passcode, language="text")

        response_df = pd.DataFrame(st.session_state.responses)
        response_df["worker_id"] = st.session_state.worker_id
        response_df["passcode"] = st.session_state.passcode

        upload_to_google_drive(response_df)
    else:
        row = df.iloc[st.session_state.experiment_index]
        
        st.write(f"### Question {st.session_state.experiment_index + 1}/{len(df)}")
        st.write(f"**Keyword: {row['keyword']}**")
        
        user_choice = st.radio("Which scene is distinct from the others?", [
            row['text_1'], row['text_2'], row['text_3'], row['text_4'], row['text_5']
        ], key=st.session_state.experiment_index, index=None)

        options = [row['text_1'], row['text_2'], row['text_3'], row['text_4'], row['text_5']]
        correct_answer = options[row['odd_index'] - 1]  # Get the correct answer using odd_index

        # if not st.session_state.experiment_complete:
        if not st.session_state.experiment_complete and st.session_state.experiment_index < len(df):
            if st.button("Submit Answer"):
                if user_choice is None:
                    st.warning("โš ๏ธ Please select an option first!")
                else:
                    response_time = time.time() - st.session_state.start_time

                    st.session_state.responses.append({
                        'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                        'worker_id': st.session_state.worker_id,
                        'passcode': st.session_state.passcode if st.session_state.passcode else "TEMP",
                        'question': st.session_state.experiment_index + 1,
                        'keyword': row['keyword'],
                        'selected': user_choice,
                        'correct_answer': correct_answer,
                        'is_correct': user_choice == correct_answer,
                        'response_time': round(response_time, 2)
                    })

                    st.session_state.experiment_index += 1
                    st.session_state.start_time = time.time()
                    st.rerun()