import streamlit as st import pandas as pd import random import time import string import gspread import os import json import datetime import re # Make sure re is imported from oauth2client.service_account import ServiceAccountCredentials # Set page config at the very beginning of the script st.set_page_config(page_title="Experiment 3: Scene Evaluation", layout="wide") # Define the primary highlight color HIGHLIGHT_COLOR = "#2c7be5" # --- ALL UTILITY FUNCTIONS DEFINED AT THE TOP (Solving NameError) --- def highlight_keyword(sentence, keyword, color=HIGHLIGHT_COLOR): """Highlights a specific keyword in a sentence, ignoring case.""" # Use word boundaries (\b) to match whole words and ignore case return re.sub(r'\b' + re.escape(keyword) + r'\b', r"\g<0>", sentence, flags=re.IGNORECASE) def generate_passcode(worker_id): suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) return f"EXP3-pilot-W{worker_id:02d}-{suffix}" def get_google_creds(): service_account_json = os.getenv("SERVICE_ACCOUNT_JSON") if service_account_json: try: 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) except json.JSONDecodeError: st.error("Invalid JSON format in SERVICE_ACCOUNT_JSON environment variable. Please ensure it's a single, valid JSON string.") return None except Exception as e: st.error(f"Error loading Google credentials: {e}") return None else: st.error("Google service account credentials (SERVICE_ACCOUNT_JSON) not found in environment variables. Please configure your Streamlit app secrets or local environment.") return None def upload_to_google_drive(response_df): if response_df.empty: st.warning("No responses to upload.") return try: client = get_google_creds() if client is None: st.error("❌ Google credentials not loaded. Cannot upload results.") return sheet_name = "EXP3-pilot" # Sheet name for Experiment 3 try: sheet = client.open(sheet_name).sheet1 except gspread.exceptions.SpreadsheetNotFound: st.info(f"Creating new Google Sheet: {sheet_name}") sheet = client.create(sheet_name).sheet1 # Get current headers from the sheet current_sheet_headers = sheet.row_values(1) if sheet.row_count > 0 else [] expected_headers = list(response_df.columns) # Add headers if the sheet is empty or headers don't match if not current_sheet_headers or current_sheet_headers != expected_headers: # if sheet.row_count > 0: # st.warning("Google Sheet headers do not match. Data will be appended, but consider manual alignment or creating a new sheet/worksheet.") if not current_sheet_headers: # Only add if sheet is truly empty after potential clear sheet.append_row(expected_headers) # st.info("Added headers to the Google Sheet.") # elif current_sheet_headers != expected_headers: # st.error("Existing sheet headers mismatch. Data will be appended, but columns might be misaligned.") # Prepare data: Replace NaN, inf with empty string, then convert to list of lists response_df_clean = response_df.replace([float("inf"), float("-inf")], None).fillna("") data_to_upload = response_df_clean.values.tolist() # Append all rows at once for efficiency if data_to_upload: sheet.append_rows(data_to_upload) st.success("✅ Your responses have been recorded successfully.") # Clear responses after successful upload to prevent re-uploading on rerun st.session_state.responses = [] else: st.warning("No new responses to upload.") except Exception as e: st.error("❌ Error uploading to Google Drive:") st.error(f"Details: {e}") # Function to record all responses for the current sample (Exp3 specific) def record_current_sample_responses(current_sample_index, target_keyword, example_sentences_raw): timestamp = datetime.datetime.now().isoformat() worker_id = st.session_state.get("worker_id", "N/A") passcode = st.session_state.get("passcode", "N/A") # Calculate response_time_sec *before* appending to state, as time.time() changes. start_time_for_sample = st.session_state.get(f'start_time_{current_sample_index}', time.time()) response_time = time.time() - start_time_for_sample # Collect Part 1 data part1_radio_key = f"part1_overall_evaluation_bottom_tab_{current_sample_index}" part3_a_radio_key = f"part3_summary_a_component_{current_sample_index}" part3_b_radio_key = f"part3_summary_b_component_{current_sample_index}" # Record Overall Comparison st.session_state.responses.append({ "timestamp": timestamp, "worker_id": worker_id, "passcode": passcode, "sample_index": current_sample_index, "keyword": target_keyword, "context_sentences": " || ".join(example_sentences_raw), # Store raw sentences for context "evaluation_type": "Overall Comparison", "question": "Which summary better captures the kinds of situations typically associated with the keyword?", "response": st.session_state.get(part1_radio_key), "response_time_sec": response_time }) # Record Summary A Representative Component st.session_state.responses.append({ "timestamp": timestamp, "worker_id": worker_id, "passcode": passcode, "sample_index": current_sample_index, "keyword": target_keyword, "context_sentences": " || ".join(example_sentences_raw), "evaluation_type": "Summary A Representative Component", "question": "For Summary A, select the component that best reflects the typical situations.", "response": st.session_state.get(part3_a_radio_key), "response_time_sec": response_time }) # Record Summary B Representative Component st.session_state.responses.append({ "timestamp": timestamp, "worker_id": worker_id, "passcode": passcode, "sample_index": current_sample_index, "keyword": target_keyword, "context_sentences": " || ".join(example_sentences_raw), "evaluation_type": "Summary B Representative Component", "question": "For Summary B, select the component that best reflects the typical situations.", "response": st.session_state.get(part3_b_radio_key), "response_time_sec": response_time }) # Collect Part 2 data (Aspect-Based Evaluation) aspects_to_evaluate_redefined_local = [ # Using a local variable to avoid NameError {"Aspect": "Interpretability"}, {"Aspect": "Completeness"}, {"Aspect": "Conciseness"}, {"Aspect": "Coherence"} ] for i, aspect_info in enumerate(aspects_to_evaluate_redefined_local): aspect_name = aspect_info["Aspect"] radio_key = f"aspect_part2_bottom_tab_{current_sample_index}_{i}" st.session_state.responses.append({ "timestamp": timestamp, "worker_id": worker_id, "passcode": passcode, "sample_index": current_sample_index, "keyword": target_keyword, "context_sentences": " || ".join(example_sentences_raw), "evaluation_type": f"Aspect-Based Evaluation - {aspect_name}", "question": f"Rate {aspect_name} for summaries A vs B.", "response": st.session_state.get(radio_key), "response_time_sec": response_time }) # --- Data Definition for Samples --- SAMPLES = [ { "target_keyword": "dolphin", "example_sentences_raw": [ "Explore an underwater cave, follow a school of clownfish, or look a dolphin in the eye.", "A.J. Pierzynski befriends a dolphin Friday at Sea World Discovery Cove in Orlando, Fla.", "And we're going to go swimming with a dolphin, well, sort of." ], "summary_a_content": """

Summary A:

🔹 Typical events:

• PersonX interacts with them

• PersonX swims with them

• PersonX embodies the role of Dolphin

🔹 Typical properties:

• They are often associated with playful and social behavior

• They symbolize playfulness and freedom

🔹 Typical emotions they evoke:

• Joy (Implied by the excitement of encountering dolphins during adventures.)

• Joy (Their playful nature contributes to a sense of happiness in the scene.)

""", "summary_b_content": """

Summary B:

🔹 Typical events:

• This includes these steps: the dolphin swims, the dolphin interacts with others

• Situations often happen before: the dolphin is alive

🔹 Typical properties:

• It's made of: blubber, skin, fins

• It has the quality of: is a marine animal, intelligent

• It's capable of: swimming, communicating, jumping

• It's typically found at: in the ocean, in an aquarium

• It's used for: used for entertainment, education, research

• It's used for: can be trained for entertainment or research

• It's used for: represents the sports team

🔹 Typical emotions they evoke:

• The person in the scene feels: feels joy or overwhelming emotion

• Others feel: others may feel amused or surprised

🔹 Typical intentions and outcomes:

• This happens to others afterward: others may feel entertained or surprised by the dolphin's actions

• Others want to: to interact with the dolphin, to observe it

""" }, { "target_keyword": "duck", "example_sentences_raw": [ "Multiple people were shot simply because this git didn't have to duck and could watch the exits and fire on anyone trying to use them.", "What? Granddad, does she duck under the table every time a dude in a purple suit walks in ?", "He sat straight, looking at me out of the corner of his eye, ready to duck, as if all he ever had from me was getting hit." ], "summary_a_content": """

Summary A:

🔹 Typical events:

• PersonX ducks to avoid ObjectY

• PersonX is described as a lame duck

🔹 Typical properties:

• It signifies a protective action in response to danger

• It signifies a transitional phase in governance

• They are a common target for hunting

🔹 Typical emotions they evoke:

• Anxiety (The need to duck suggests a sense of urgency and potential danger.)

• Urgency (The warning indicates a need for quick action.)

• Amusement (The comparison may evoke a sense of humor.)

• Anxiety (Implied by the context of vulnerability.)

• Joy (The presence of the duck brings a sense of happiness.)

• Nostalgia (The connection to a past identity evokes a longing for that time.)

• Amusement (The playful tone suggests a light-hearted interaction.)

• Disappointment (Associated with the term "lame duck" reflecting ineffective leadership.)

""", "summary_b_content": """

Summary B:

🔹 Typical events:

• Situations often happen before: the duck is approached

• This includes these steps: the duck quacks, the duck moves, the duck is approached

• This can be prevented by: lack of awareness, obstacles in the way

• This typically happens after: the duck leaves the area

🔹 Typical properties:

• It's made of: feathers, beak, webbed feet

• It's capable of: flying, quacking, swimming

• It's capable of: avoiding danger, protecting oneself

• It has the quality of: is a bird, can swim, has feathers

• It has the quality of: is a movement, involves bending down

• It's used for: can be kept as a pet, can be hunted or observed

• It's used for: used for avoiding detection or obstacles

• It's typically found at: near water, in a park or farm

🔹 Typical emotions they evoke:

• The person in the scene feels: feels scared or anxious

• Others feel: others may feel surprised or concerned

🔹 Typical intentions and outcomes:

• This happens to others afterward: others may react to the duck's presence

• This happens to others afterward: others may feel confused or amused

• This happens to others afterward: others may not notice the person in the scene

• This happens to the person in the scene afterward: the person in the scene successfully avoids danger

• Others want to: to see the duck in action

• Others want to: to find food, to escape danger

• Others want to: to avoid danger, to find safety

• The person in the scene intends to: to avoid being seen or hit

""" }, { "target_keyword": "swan", "example_sentences_raw": [ "In his swan song to the party faithful, political tolerance took on a double meaning.", "If I did it, '' he says, '' it would have to be the swan song, the thank-you-and-good-night. ''", "My swan song, '' she said, then added, '' If you weren't jailbait If you were n't jailbait, I'd take you along." ], "summary_a_content": """

Summary A:

🔹 Typical events:

• PersonX refers to a "swan song" as a metaphor for a final performance

• PersonX creates a swan song

• PersonX contrasts it with a "black swan" event

• PersonY performs a swan dive

• It symbolizes a farewell

• Market conditions are influenced by unpredictable events

• Finale is perceived as a swan song

• PersonX imagines swanning in (as a metaphor for a carefree entrance)

🔹 Typical properties:

• It symbolizes a final act or farewell

🔹 Typical emotions they evoke:

• Nostalgia (The concept of a "swan song" often carries a sense of longing or reflection on the past.)

""", "summary_b_content": """

Summary B:

🔹 Typical events:

• This can be prevented by: lack of opportunity, negative reception

• Something happens because of: the swan song signifies the end of something

• Situations often happen before: the swan song is performed

🔹 Typical properties:

• It has the quality of: represents beauty, grace, finality

• It's used for: symbolizes a final performance or farewell

• It's made of: feathers, body, wings

• It's made of: metaphorical concept, not a physical object

• It's made of: music, lyrics, performance

• It's capable of: swimming, flying, making sounds

• It's typically found at: near water, in a park or lake

• It's typically found at: in a performance setting or stage

🔹 Typical emotions they evoke:

• The person in the scene feels: feels fulfilled and emotional

• Others feel: others may feel admiration or confusion

🔹 Typical intentions and outcomes:

• This happens to others afterward: others may feel nostalgic or emotional about the performance

• The person in the scene intends to: to leave a lasting impression

• This happens to the person in the scene afterward: the person in the scene seeks recognition or closure

""" } ] # --- Main Experiment UI Function --- def main_experiment_ui(): """Renders the main experiment UI with summaries and evaluation forms.""" current_sample_index = st.session_state.get('current_sample_index', 0) # If all samples are completed, transition to the training complete page if current_sample_index >= len(SAMPLES): st.session_state.page = "training_complete" st.rerun() return # Record the start time when the sample is first loaded/displayed if f'start_time_{current_sample_index}' not in st.session_state: st.session_state[f'start_time_{current_sample_index}'] = time.time() current_sample = SAMPLES[current_sample_index] target_keyword_stimuli = current_sample["target_keyword"] example_sentences_stimuli_raw = current_sample["example_sentences_raw"] summary_a_content = current_sample["summary_a_content"] summary_b_content = current_sample["summary_b_content"] example_sentences_formatted = [highlight_keyword(s, target_keyword_stimuli) for s in example_sentences_stimuli_raw] st.markdown(f""" """, unsafe_allow_html=True) # 2개의 컬럼 생성 (왼쪽: 1, 오른쪽: 1.5 비율로) col_left, col_right = st.columns([7, 3]) with col_left: # Sample Number Display st.markdown(f"

Sample # {current_sample_index + 1} / {len(SAMPLES)}

", unsafe_allow_html=True) # Scene Context st.markdown(f"

Target Keyword: {target_keyword_stimuli}

", unsafe_allow_html=True) st.markdown(f"

Consider the situational meaning of '{target_keyword_stimuli}' in the following scenes:

", unsafe_allow_html=True) example_sentences_box_full_html = "
" example_sentences_box_full_html += '

🎬 Scene Context:

' for sentence_formatted in example_sentences_formatted: example_sentences_box_full_html += f"

• {sentence_formatted}

\n" example_sentences_box_full_html += "
" st.markdown(example_sentences_box_full_html, unsafe_allow_html=True) # Summaries st.markdown("

Please evaluate how well the following summaries capture the keyword's situational meaning:

", unsafe_allow_html=True) col_summary_a_box, col_summary_b_box = st.columns(2) with col_summary_a_box: st.markdown(f"
{summary_a_content}
", unsafe_allow_html=True) with col_summary_b_box: st.markdown(f"
{summary_b_content}
", unsafe_allow_html=True) with col_right: # Evaluation Forms (Tabs) tab_part1_eval, tab_part2_eval = st.tabs(["Overall Evaluation", "Aspect-Based Evaluation"]) with tab_part1_eval: st.markdown("

Q: Which summary better captures the kinds of situations typically associated with the keyword?

", unsafe_allow_html=True) part1_evaluation_options = [ "A ≫ B: Summary A is clearly better. Summary B is poor.", "A > B: Summary A is slightly better. Summary B is acceptable.", "Equal (Good): Both are equally good.", "Equal (Bad): Both are equally poor.", "B > A: Summary B is slightly better. Summary A is acceptable.", "B ≫ A: Summary B is clearly better. A Summary is poor." ] part1_radio_key = f"part1_overall_evaluation_bottom_tab_{current_sample_index}" st.radio( "Select one:", part1_evaluation_options, key=part1_radio_key, index=None # Keep index=None for proper initial state ) st.markdown("---") st.markdown("

Q: For each summary, select the component that best reflects the typical situations in which the keyword is used.

", unsafe_allow_html=True) representative_component_options_a = [ "Typical events", "Typical properties", "Typical emotions they evoke", "(None in particular)" ] representative_component_options_b = [ "Typical events", "Typical properties", "Typical emotions they evoke", "Typical intentions and outcomes", "(None in particular)" ] st.markdown("

Summary A:

", unsafe_allow_html=True) part3_a_radio_key = f"part3_summary_a_component_{current_sample_index}" st.radio( "Select one for Summary A:", options=representative_component_options_a, key=part3_a_radio_key, index=None, # Keep index=None label_visibility="collapsed" ) st.markdown("---") st.markdown("

Summary B:

", unsafe_allow_html=True) part3_b_radio_key = f"part3_summary_b_component_{current_sample_index}" st.radio( "Select one for Summary B:", options=representative_component_options_b, key=part3_b_radio_key, index=None, # Keep index=None label_visibility="collapsed" ) st.markdown("---") with tab_part2_eval: st.markdown("

Please rate which summary performs better on each of the following aspects.

", unsafe_allow_html=True) aspect_evaluation_options_redefined = ["A ≫ B", "A > B", "Equal (Good)", "Equal (Bad)", "B > A", "B ≫ A"] aspects_to_evaluate_redefined = [ {"Aspect": "Interpretability", "Description": "Which summary is easier to understand and more clearly conveys the situational meaning of the keyword?"}, {"Aspect": "Completeness", "Description": "Which summary offers a fuller and more informative depiction of the kinds of situations typically associated with the keyword?"}, {"Aspect": "Conciseness", "Description": "Which summary conveys the key situational meaning more concisely, without unnecessary details or over-interpretation?"}, {"Aspect": "Coherence", "Description": "Does the summary consistently describe a single, coherent type of situation or scene associated with the keyword, without mixing different senses or unrelated contexts? (e.g., does not mix 'baseball bat' scenes with 'animal bat' scenes)."} ] header_col1, header_col2 = st.columns([3, 2]) with header_col1: st.markdown("Aspect", unsafe_allow_html=True) with header_col2: st.markdown("Ratings", unsafe_allow_html=True) st.markdown("---") for i, aspect_info in enumerate(aspects_to_evaluate_redefined): aspect_name = aspect_info["Aspect"] aspect_description = aspect_info["Description"] col1, col2 = st.columns([3, 2]) with col1: st.markdown(f"

{aspect_name}

", unsafe_allow_html=True) st.markdown(f"_{aspect_description}_", unsafe_allow_html=True) with col2: radio_key = f"aspect_part2_bottom_tab_{current_sample_index}_{i}" st.radio( "Select:", options=aspect_evaluation_options_redefined, key=radio_key, index=None, # Keep index=None horizontal=False, label_visibility="collapsed" ) st.markdown("---") st.markdown("---") # Add a separator before the buttons inside col_right # Navigation buttons (now inside col_right) # Function to validate answers and time AND RECORD RESPONSES def validate_and_proceed_and_record(): all_questions_answered = True # Check Part 1 answers if st.session_state.get(f"part1_overall_evaluation_bottom_tab_{current_sample_index}") is None or \ st.session_state.get(f"part3_summary_a_component_{current_sample_index}") is None or \ st.session_state.get(f"part3_summary_b_component_{current_sample_index}") is None: all_questions_answered = False # Check Part 2 answers for i, aspect_info in enumerate(aspects_to_evaluate_redefined): radio_key = f"aspect_part2_bottom_tab_{current_sample_index}_{i}" if st.session_state.get(radio_key) is None: all_questions_answered = False break if not all_questions_answered: st.warning("⚠️ Please answer all questions across both tabs before proceeding.") return False # Check response time for the current sample start_time_for_sample = st.session_state.get(f'start_time_{current_sample_index}', time.time()) end_time = time.time() response_time = end_time - start_time_for_sample if response_time < 30: # Minimum 30 seconds for evaluation st.warning("⚠️ Please take enough time to read and evaluate carefully before proceeding. (Minimum 30 seconds required per sample)") return False # If all validations pass, record responses # This is where we call the record function to save all choices for the current sample record_current_sample_responses(current_sample_index, target_keyword_stimuli, example_sentences_raw) return True if current_sample_index < len(SAMPLES) - 1: if st.button("Next Sample"): if validate_and_proceed_and_record(): st.session_state.current_sample_index += 1 st.rerun() else: if st.button("Complete Experiment"): if validate_and_proceed_and_record(): st.session_state.page = "training_complete" st.rerun() # --- Instruction Pages --- def instruction_page(): # Instruction-specific CSS st.markdown(f""" """, unsafe_allow_html=True) # Page 0: Welcome / Worker ID Input if st.session_state.page == "worker_id_input": # Adjusted to be the first logical step st.title("Welcome to the Experiment 3!") st.write("This is your final experiment, at last!") st.write("Please enter your participant ID to begin:") with st.form(key='worker_id_form'): participant_input = st.text_input("Participant ID (e.g., 4)") submit_btn = st.form_submit_button("Submit") if submit_btn: try: worker_id = int(participant_input) st.session_state.worker_id = worker_id # Generate passcode immediately when worker_id is set st.session_state.passcode = generate_passcode(worker_id) st.session_state.page = 1 # Move to instructions page 1 st.rerun() except ValueError: st.error("Please enter a valid numeric ID.") st.stop() # Stop rendering until ID is submitted and state changes # Page 1: What is situational meaning? (Updated Content with tags) elif st.session_state.page == 1: st.markdown("

🎯 What is the Situational Meaning of a Word? (1/4)

", unsafe_allow_html=True) st.markdown("

What does a word truly mean? It's more than just a dictionary definition or a collection of encyclopedic facts. Words gain deeper meaning from the common situations and scenarios they play a part in, building a mental template of frequently associated events, properties, and emotions through repeated experiences.

", unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.image("crow.jpg", caption="A crow, often associated with ominous scenes in stories.", width=300) # Changed to local crow.jpg st.markdown("

For example, crows aren't just big black birds; they're often linked to death, witches, and haunted mansions in stories, carrying an ominous connotation far beyond their simple definition. This comprehensive understanding, rooted in how words appear in typical scenes, is what we call situational meaning.

", unsafe_allow_html=True) col_prev, col_next = st.columns([1, 1]) with col_prev: st.markdown("
", unsafe_allow_html=True) # Back button leads to worker_id_input if on first instruction page if st.button("Previous", key="prev_1_btn"): st.session_state.page = "worker_id_input" st.rerun() st.markdown("
", unsafe_allow_html=True) with col_next: st.markdown("
", unsafe_allow_html=True) if st.button("Next", key="next_1_btn"): st.session_state.page = 2 st.rerun() st.markdown("
", unsafe_allow_html=True) # Page 2: Your Task (Formerly Page 3) - Moved content here elif st.session_state.page == 2: st.markdown("

✅ Your Task (2/4)

", unsafe_allow_html=True) st.markdown("

In this experiment, you’ll compare two summaries (Summary A & Summary B) that describe the situational meaning of a given keyword in a specific scene context.

", unsafe_allow_html=True) st.markdown("

You will be given three example sentences that illustrate the scene context in question. Your goal is to evaluate which summary better reflects what the keyword typically means in the given scene context, focusing on its situational characteristics rather than its dictionary or encyclopedic definition.

", unsafe_allow_html=True) st.info(""" **🧩 Consider Scene Contexts Together** Scene contexts can be specific or broad, so consider all three example sentences together to understand the full situational range. If you don't find any clear commonalities across the examples, it’s reasonable for the summary to reflect a broader range of scenes as well. """) st.markdown("

A good summary will effectively capture the typical features of the scenes where the word is used, including common events, properties, and emotions, that go beyond simple factual definitions.

", unsafe_allow_html=True) col_prev, col_next = st.columns([1, 1]) with col_prev: st.markdown("
", unsafe_allow_html=True) if st.button("Previous", key="prev_2_btn"): # Point back to Page 1 st.session_state.page = 1 st.rerun() st.markdown("
", unsafe_allow_html=True) with col_next: st.markdown("
", unsafe_allow_html=True) if st.button("Next: How to Compare", key="next_2_btn"): st.session_state.page = 3 # Updated to new Page 3 st.rerun() st.markdown("
", unsafe_allow_html=True) # Page 3: How You'll Be Comparing (Formerly Page 4) elif st.session_state.page == 3: st.markdown("

💡 How You’ll Be Comparing the Summaries (3/4)

", unsafe_allow_html=True) st.markdown("

You’ll rate the summaries across four aspects. Each question focuses on a different quality that makes a summary effective.

", unsafe_allow_html=True) st.markdown(""" | Aspect | Description | Key Questions to Ask | | :-------------- | :-------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Interpretability | Which summary is easier to understand and more clearly conveys the situational meaning of the keyword? | • Can you understand what the summary is trying to say without confusion? Does it make sense to you? | | Completeness | Which summary offers a fuller and more informative depiction of the kinds of situations typically associated with the keyword? | • Is all the important situational meaning information for this word in this scene context sufficiently expressed? | | Conciseness | Which summary conveys its meaning more efficiently, without unnecessary detail or over-interpretation? | • Does it avoid adding irrelevant content or too much speculation? | | Coherence | Which summary consistently describes a single, unified type of situation or scene associated with the keyword? | • Does it avoid confusing different senses or unrelated contexts (e.g., does it avoid mixing "animal bat" 🦇 scenes with "baseball bat" ⚾️ scenes?) | """, unsafe_allow_html=True) st.markdown("

🗳 Your Response Options

", unsafe_allow_html=True) st.markdown("

For each aspect, please select one of the following:

", unsafe_allow_html=True) st.markdown("""

A ≫ B — Summary A is clearly better. Summary B is poor.

A > B — Summary A is slightly better. Summary B is acceptable.

Equal (Good) — Both are equally good.

Equal (Bad) — Both are equally poor.

B > A — Summary B is slightly better. Summary A is acceptable.

B ≫ A — Summary B is clearly better. Summary A is poor.

""", unsafe_allow_html=True) col_prev, col_next = st.columns([1, 1]) with col_prev: st.markdown("
", unsafe_allow_html=True) if st.button("Previous", key="prev_3_btn"): # Point back to new Page 2 st.session_state.page = 2 st.rerun() st.markdown("
", unsafe_allow_html=True) with col_next: st.markdown("
", unsafe_allow_html=True) if st.button("Next: Response Options", key="next_3_btn"): st.session_state.page = 4 # Updated to new Page 4 st.rerun() st.markdown("
", unsafe_allow_html=True) # Page 4: Response Options + Final Notes (Formerly Page 5) elif st.session_state.page == 4: st.markdown("

🧭 Final Notes (4/4)

", unsafe_allow_html=True) st.markdown("

When you're evaluating, please keep these guidelines in mind to help you make the best judgment:

", unsafe_allow_html=True) st.info(""" **💡 Focus on Situational Meaning:** Remember, you're evaluating how well the summary captures the **situational meaning** of the word—that is, the characteristics, events, and emotions typically associated with the scenes where the word is used. Do *not* focus on dictionary definitions or purely encyclopedic facts. **🚫 Avoid Unrelated Content:** If a summary includes a lot of content unrelated to the example scenes, it likely didn't capture the situational meaning well. You should **rate it lower** in such cases. **✅ Capture Key Scene Features:** A good summary effectively shows the **core features of the typical scenes** associated with the keyword. If it does this well, you **can rate it higher**. """) st.markdown("

There are no right or wrong answers—your intuitive judgment is what matters most.

", unsafe_allow_html=True) col_prev, col_next = st.columns([1, 1]) with col_prev: st.markdown("
", unsafe_allow_html=True) if st.button("Previous", key="prev_4_btn"): # Point back to new Page 3 st.session_state.page = 3 st.rerun() st.markdown("
", unsafe_allow_html=True) with col_next: st.markdown("
", unsafe_allow_html=True) if st.button("Start Experiment", key="start_experiment_btn_final"): st.session_state.page = 5 # Updated to new Page 5 (main UI) st.session_state.current_sample_index = 0 # Initialize sample index # Initialize the start time for the first sample when the experiment begins st.session_state[f'start_time_{st.session_state.current_sample_index}'] = time.time() st.rerun() st.markdown("
", unsafe_allow_html=True) # Page 5: Main Experiment UI elif st.session_state.page == 5: main_experiment_ui() # Training Complete Page (now renamed from "training" to "practice" for clarity) elif st.session_state.page == "training_complete": st.header("🎉 Practice Complete!") st.markdown(""" You have completed the practice phase! Please let us know if you had any questions or comments on the task/experiment. If everything is clear, we will provide you the link for the main experiment. """, unsafe_allow_html=True) # Prepare DataFrame for upload response_df = pd.DataFrame(st.session_state.responses) # Ensure column order for consistency in Google Sheet (optional, but good practice) # First, define expected columns to ensure order and presence. expected_cols = [ "timestamp", "worker_id", "passcode", "sample_index", "target_keyword", "context_sentences", "evaluation_type", "question", "response", "response_time_sec" ] # Get existing columns from the DataFrame existing_cols = response_df.columns.tolist() # Add any missing expected columns with None/NaN as placeholder for col in expected_cols: if col not in existing_cols: response_df[col] = None # Reorder DataFrame columns response_df = response_df[expected_cols] upload_to_google_drive(response_df) st.markdown("#### 🔑 Your Unique Completion Code") st.code(st.session_state.passcode) # --- Main app entry point --- if __name__ == "__main__": # Initialize session state variables if they don't exist if 'page' not in st.session_state: st.session_state.page = "worker_id_input" # Start with worker ID input if 'current_sample_index' not in st.session_state: st.session_state.current_sample_index = 0 if 'worker_id' not in st.session_state: st.session_state.worker_id = None if 'passcode' not in st.session_state: st.session_state.passcode = None if 'responses' not in st.session_state: st.session_state.responses = [] if 'start_time_current_sample' not in st.session_state: st.session_state.start_time_current_sample = None # Changed to None, will be set on first display # Global CSS styling (moved here for consistency as it applies globally) st.markdown(""" """, unsafe_allow_html=True) # Call the manager function to control the app flow instruction_page()