Spaces:
Running
Running
| import streamlit as st | |
| import folium | |
| from streamlit_folium import folium_static | |
| from datetime import datetime | |
| import gspread | |
| from google.oauth2.service_account import Credentials | |
| import os | |
| import json | |
| import random | |
| import time | |
| # تنظیمات اولیهه | |
| st.set_page_config(layout="wide", page_title="راهیار - تحلیل انصاف قیمتی", page_icon="🚖") | |
| # ========== تنظیمات دیتا ========== | |
| SHEET_ID = "1mmdWAyOCYq4yXMgP53Duq712AnlqZWLkfIo76JqM7wM" | |
| SHEET_NAME = "Condition1" | |
| # ========== استایلهای سفارشی یکپارچه ========== | |
| st.markdown(""" | |
| <style> | |
| @font-face { | |
| font-family: 'Vazir'; | |
| src: url('https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v30.1.0/dist/Vazir.woff') format('woff'); | |
| } | |
| :root { | |
| --primary: #6a0dad; | |
| --text: #333333; | |
| --background: #ffffff; | |
| --border: #dddddd; | |
| --input-bg: #ffffff; | |
| --secondary-bg: #f8f9fa; | |
| --green: #f0ff0; | |
| --dgreen: #006400 | |
| } | |
| * { | |
| font-family: 'Vazir', sans-serif !important; | |
| text-align: right !important; | |
| direction: rtl !important; | |
| } | |
| /* تنظیمات اصلی */ | |
| body, .stApp, [data-testid="stAppViewContainer"] { | |
| background-color: var(--background) !important; | |
| color: var(--text) !important; | |
| } | |
| /* هدر راهیار */ | |
| .rahyar-title { | |
| color: var(--primary) !important; | |
| margin: 0 !important; | |
| } | |
| .rahyar-subtitle { | |
| color: var(--primary) !important; | |
| margin: 0 !important; | |
| font-size: 14px !important; | |
| } | |
| /* توضیحات */ | |
| .explanation-title { | |
| color: var(--primary) !important; | |
| font-weight: bold !important; | |
| margin: 20px 0 10px 0 !important; | |
| font-size: 18px !important; | |
| } | |
| .explanation-item { | |
| background-color: var(--secondary-bg) !important; | |
| border-radius: 8px !important; | |
| padding: 12px 15px !important; | |
| margin: 8px 0 !important; | |
| border-right: 3px solid var(--primary) !important; | |
| } | |
| /* ========== استایلهای ورودی یکپارچه ========== */ | |
| /* استایل پایه برای تمام عناصر ورودی */ | |
| .stTextInput input, | |
| .stNumberInput input, | |
| .stSelectbox select, | |
| .stTextArea textarea, | |
| .stDateInput input, | |
| .stTimeInput input, | |
| .stMultiSelect div[role="combobox"], | |
| div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > div > div > input, | |
| /* لیست dropdown */ | |
| .st-bq, .st-br, .st-bs, .st-bt, .st-bu, .st-bv, .st-bw, .st-bx, .st-by, .st-bz { | |
| color: var(--text) !important; | |
| background-color: var(--input-bg) !important; | |
| border: 1px var(--primary) !important; | |
| } | |
| /* استایل لیبلها */ | |
| .stTextInput > label, | |
| .stNumberInput > label, | |
| .stSelectbox > label, | |
| .stRadio > label, | |
| .stSlider > label { | |
| color: var(--text) !important; | |
| font-weight: bold !important; | |
| margin-bottom: 4px !important; | |
| } | |
| /* استایل placeholder */ | |
| ::placeholder { | |
| color: var(--text) !important; | |
| opacity: 0.7 !important; | |
| } | |
| /* ========== استایلهای خاص Selectbox ========== */ | |
| /* حذف کامل فلش پیشفرض در تمام مرورگرها */ | |
| div[data-baseweb="select"] > div:first-child { | |
| -webkit-appearance: none !important; | |
| -moz-appearance: none !important; | |
| appearance: none !important; | |
| background-image: none !important; | |
| } | |
| /* برای اینترنت اکسپلورر */ | |
| div[data-baseweb="select"] > div:first-child::-ms-expand { | |
| display: none !important; | |
| } | |
| /* ایجاد فلش سفارشی */ | |
| div[data-baseweb="select"] { | |
| position: relative; | |
| } | |
| div[data-baseweb="select"]::after { | |
| content: "▼"; | |
| position: absolute; | |
| left: 8px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: var(--primary); | |
| background-color: white !important; /* پسزمینه سفید برای پوشاندن فلش پیشفرض */ | |
| font-size: 14px; | |
| pointer-events: none; | |
| } | |
| /* ===== راه حل تضمینی برای استایل dropdown ===== */ | |
| /* آیتمهای لیست */ | |
| div[data-baseweb="popover"] [role="option"] { | |
| color: var(--text) !important; | |
| background-color: white !important; | |
| border: 1px var(--primary) !important; | |
| } | |
| /* آیتم انتخاب شده */ | |
| div[data-baseweb="popover"] [role="option"][aria-selected="true"] { | |
| background-color: #f0e6ff !important; | |
| color: black !important; | |
| } | |
| /* ========== استایلهای Number Input ========== */ | |
| .stNumberInput button { | |
| color: var(--primary) !important; | |
| background-color: transparent !important; | |
| border: none !important; | |
| width: 30px !important; | |
| font-weight: bold !important; | |
| } | |
| .stNumberInput button:hover { | |
| background-color: #f0e6ff !important; | |
| } | |
| /* کامپوننتهای سفارشی */ | |
| .rahyar-header { | |
| background-color: var(--primary) !important; | |
| color: white !important; | |
| padding: 15px !important; | |
| border-radius: 10px !important; | |
| margin-bottom: 20px !important; | |
| text-align: center !important; | |
| } | |
| .price-container { | |
| background-color: var(--secondary-bg) !important; | |
| border-radius: 10px !important; | |
| padding: 15px !important; | |
| margin: 15px 0 !important; | |
| border-right: 5px solid var(--primary) !important; | |
| } | |
| /* دکمه اصلی (بنفش با متن سفید) */ | |
| .stButton>button, | |
| [data-testid="baseButton-primary"], | |
| .accept-btn, | |
| div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > button { | |
| background-color: var(--primary) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| padding: 10px 20px !important; | |
| font-weight: bold !important; | |
| } | |
| /* دکمه ثانویه (سفید با حاشیه بنفش) */ | |
| .stFormSubmitButton>button, | |
| [data-testid="baseButton-secondary"], | |
| .reject-btn { | |
| background-color: var(--primary) !important; | |
| color: var(--input-bg) !important; | |
| border: 1px solid var(--primary) !important; | |
| border-radius: 8px !important; | |
| padding: 10px 20px !important; | |
| font-weight: bold !important; | |
| } | |
| /* ========== استایل تضمینی برای گزینههای رادیویی ========== */ | |
| /* تمام سطوح لیبلها */ | |
| .stRadio > label, | |
| .stRadio > div > label, | |
| .stRadio > div > div > label, | |
| .stRadio > div > div > div > label { | |
| color: #000000 !important; /* مشکی خالص */ | |
| font-weight: normal !important; | |
| margin-right: 8px !important; /* فاصله از دکمه رادیویی */ | |
| } | |
| /* دایره رادیویی */ | |
| .stRadio input[type="radio"] + span { | |
| border-color: #000000 !important; /* حاشیه مشکی */ | |
| } | |
| /* دایره رادیویی هنگام انتخاب */ | |
| .stRadio input[type="radio"]:checked + span { | |
| background-color: #000000 !important; /* پسزمینه مشکی */ | |
| border-color: #000000 !important; | |
| } | |
| /* متن گزینهها */ | |
| .stRadio span { | |
| color: #000000 !important; /* مشکی خالص */ | |
| font-size: 14px !important; | |
| padding-right: 5px !important; | |
| } | |
| /* حالت hover */ | |
| .stRadio label:hover span { | |
| color: #333333 !important; /* مشکی کمی روشنتر */ | |
| } | |
| /* کانتینر اصلی */ | |
| .stRadio > div { | |
| margin-bottom: 10px !important; | |
| } | |
| /* تنظیمات مخصوص موبایل */ | |
| @media (max-width: 768px) { | |
| .folium-map { | |
| height: 300px !important; | |
| } | |
| /* فرمها در موبایل */ | |
| .stTextInput>label, | |
| .stNumberInput>label, | |
| .stSelectbox>label { | |
| font-size: 14px !important; | |
| padding: 4px 0 !important; | |
| } | |
| .stTextInput input, | |
| .stNumberInput input, | |
| .stSelectbox select { | |
| font-size: 16px !important; | |
| padding: 12px !important; | |
| height: auto !important; | |
| } | |
| .stButton>button { | |
| font-size: 14px !important; | |
| padding: 8px 16px !important; | |
| } | |
| .stMarkdown h3 { | |
| font-size: 16px !important; | |
| } | |
| .stMarkdown p { | |
| font-size: 13px !important; | |
| } | |
| .stSelectbox [role="listbox"] { | |
| font-size: 16px !important; | |
| } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ========== توابع اصلی ========== | |
| def create_ride_map(): | |
| """ایجاد نقشه سفر با Folium""" | |
| start_point = [35.7698, 51.4116] # میدان ونک | |
| end_point = [35.8044, 51.4258] # میدان تجریش | |
| m = folium.Map(location=[35.7871, 51.4187], zoom_start=13) | |
| folium.Marker( | |
| start_point, | |
| popup="<b>مبدأ:</b> میدان ونک", | |
| icon=folium.Icon(color="green", icon="flag", prefix="fa") | |
| ).add_to(m) | |
| folium.Marker( | |
| end_point, | |
| popup="<b>مقصد:</b> میدان تجریش", | |
| icon=folium.Icon(color="red", icon="flag", prefix="fa") | |
| ).add_to(m) | |
| folium.PolyLine( | |
| [start_point, end_point], | |
| color="#6a0dad", | |
| weight=3, | |
| opacity=1 | |
| ).add_to(m) | |
| return m | |
| def show_explanation(exp_type): | |
| """نمایش توضیحات قیمت""" | |
| explanations = { | |
| "input": [ | |
| "سطح تقاضا در منطقه: زیاد", | |
| "تعداد رانندگان فعال: کم", | |
| "زمان روز: ساعت اوج ترافیک", | |
| "شرایط جوی: هوای بارانی" | |
| ], | |
| "counterfactual": [ | |
| "اگر این سفر را ۳۰ دقیقه دیرتر درخواست میکردید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیشتر، زمان بهتر روز و شرایط جوی بهتر، ممکن بود قیمت ۱۵٪ کمتر باشد.", | |
| ] | |
| } | |
| if exp_type != "control": | |
| st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True) | |
| for item in explanations.get(exp_type, []): | |
| st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True) | |
| def create_likert_question(question, key, scale_type="5point"): | |
| """سوال لیکرت با دکمهها""" | |
| if scale_type == "5point": | |
| options = { | |
| 1: "کاملاً مخالفم", | |
| 2: "مخالفم", | |
| 3: "نظری ندارم", | |
| 4: "موافقم", | |
| 5: "کاملاً موافقم" | |
| } | |
| else: # 7-point scale | |
| options = { | |
| 1: "کاملاً مخالفم", | |
| 2: "مخالفم", | |
| 3: "تا حدی مخالفم", | |
| 4: "نظری ندارم", | |
| 5: "تا حدی موافقم", | |
| 6: "موافقم", | |
| 7: "کاملاً موافقم" | |
| } | |
| st.markdown(f"<p style='margin-bottom: 10px;'>{question}</p>", unsafe_allow_html=True) | |
| cols = st.columns(len(options)) | |
| selected = st.session_state.get(key, None) | |
| # نمایش گزینهها از کاملاً موافقم (راست) تا کاملاً مخالفم (چپ) | |
| for value, label in reversed(options.items()): | |
| with cols[len(options) - value]: | |
| if st.button( | |
| label, | |
| key=f"{key}_{value}", | |
| on_click=lambda v=value: st.session_state.update({key: v}), | |
| type="primary" if selected == value else "secondary" | |
| ): | |
| pass | |
| if selected: | |
| st.markdown(f"<p style='color: #6a0dad;'>پاسخ شما: {options[selected]}</p>", unsafe_allow_html=True) | |
| return selected | |
| # ========== توابع مدیریت دادهها ========== | |
| def get_credentials(): | |
| """دریافت اعتبارنامه از Secrets""" | |
| try: | |
| service_account_json = os.environ.get('GCP_SERVICE_ACCOUNT') | |
| if not service_account_json: | |
| st.error("مقدار GCP_SERVICE_ACCOUNT در محیط یافت نشد") | |
| return None | |
| service_account_info = json.loads(service_account_json) | |
| creds = Credentials.from_service_account_info( | |
| service_account_info, | |
| scopes=[ | |
| "https://www.googleapis.com/auth/spreadsheets", | |
| "https://www.googleapis.com/auth/drive.file" | |
| ] | |
| ) | |
| return creds | |
| except Exception as e: | |
| st.error(f"خطا در دریافت اعتبارنامه: {str(e)}") | |
| return None | |
| def save_to_sheet(data): | |
| try: | |
| creds = get_credentials() | |
| if not creds: | |
| return False | |
| client = gspread.authorize(creds) | |
| spreadsheet = client.open_by_key(SHEET_ID) | |
| worksheet = spreadsheet.worksheet(SHEET_NAME) | |
| row_data = [ | |
| data.get("start_time", ""), # زمان شروع | |
| data.get("scenario_type", ""), | |
| data.get("price", ""), | |
| data.get("age", ""), | |
| data.get("gender", ""), | |
| data.get("education", ""), | |
| data.get("ride_frequency", ""), | |
| data.get("user_contact", ""), | |
| data.get("price_accepted", ""), | |
| # سوالات توجه | |
| data.get("attention_check1", ""), | |
| data.get("attention_check2", ""), | |
| # سوالات جدید | |
| data.get("pricing_method", ""), | |
| data.get("price_increase", ""), | |
| # سوالات informational (5 گزینهای) | |
| data.get("informational_1", ""), | |
| data.get("informational_2", ""), | |
| data.get("informational_3", ""), | |
| data.get("informational_4", ""), | |
| data.get("informational_5", ""), | |
| # سوالات distributive (7 گزینهای) | |
| data.get("distributive_1", ""), | |
| data.get("distributive_2", ""), | |
| data.get("distributive_3", ""), | |
| # سوالات procedural (7 گزینهای) | |
| data.get("procedural_1", ""), | |
| data.get("procedural_2", ""), | |
| data.get("procedural_3", ""), | |
| # سوالات explanation | |
| data.get("explanation_received", ""), | |
| data.get("explanation_type", ""), | |
| # زمانسنجی | |
| data.get("end_time", ""), # زمان پایان | |
| data.get("completion_time", "") # مدت زمان تکمیل (ثانیه) | |
| ] | |
| worksheet.append_row(row_data) | |
| return True | |
| except Exception as e: | |
| st.error(f"خطا در ذخیرهسازی: {str(e)}") | |
| return False | |
| # ========== بخشهای فرم ========== | |
| def welcome_page(): | |
| """صفحه خوشامدگویی""" | |
| st.markdown(""" | |
| <div style="text-align: center; margin-bottom: 30px;"> | |
| <h2> 🚖 قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی</h2> | |
| <p>👋با سلام و درود</p> | |
| <p>پیشاپیش بابت زمانی که برای پاسخ به سوالات این پرسشنامه و پیشبرد اهداف علمی دانشجویان میگذارید، متشکرم.</p> | |
| <p>جهت تقدیر از شرکتکنندگان، به دو نفر به قید قرعه جایزه 5 میلیون ریالی تقدیم خواهد شد.</p> | |
| </div> | |
| <div style="display: flex; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 20px;"> | |
| <div style="flex: 0 0 100px;"> | |
| <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png" alt="لوگو دانشگاه شریف" style="width: 100%; max-width: 100px;"> | |
| </div> | |
| <div style="flex: 1;"> | |
| <h3>درباره تحقیق:</h3> | |
| <p>این پرسشنامه بخشی از یک پایاننامه کارشناسی ارشد در دانشگاه صنعتی شریف است که به بررسی ادراک انصاف در قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی میپردازد.</p> | |
| <p>پاسخهای شما به سوالات کاملاً محرمانه خواهد بود و فقط در جهت اهداف علمی استفاده خواهد شذ.</p> | |
| <p>جهت شروع روی دکمه زیر کلیک کنید 👇🏻</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if st.button("شروع پرسشنامه", key="start_btn", type="primary"): | |
| st.session_state.current_page = "contact" | |
| st.session_state.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| st.rerun() | |
| def user_contact(): | |
| """راه ارتباطی ساده""" | |
| st.markdown(""" | |
| <div style="text-align: center; margin-bottom: 30px;"> | |
| <h3>📩 راه ارتباطی شما (اختیاری)</h3> | |
| <p>در صورت تمایل به شرکت در قرعهکشی میتوانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| contact_info = st.text_input( | |
| "راه ارتباطی (اختیاری)", | |
| placeholder="مثال: @username یا 09123456789 یا example@email.com", | |
| key="user_contact_input" | |
| ) | |
| if st.button("ادامه", key="continue_btn", type="primary"): | |
| st.session_state.user_contact = contact_info | |
| st.session_state.current_page = "demographic" | |
| st.rerun() | |
| def demographic_form(): | |
| """فرم اطلاعات دموگرافیک""" | |
| st.markdown("### 📝 اطلاعات دموگرافیک") | |
| with st.form("demographic_form"): | |
| age = st.number_input("سن", min_value=18, max_value=100) | |
| gender = st.selectbox("جنسیت", ["مرد", "زن", "سایر"]) | |
| education = st.selectbox("تحصیلات", ["دیپلم", "لیسانس", "فوق لیسانس", "دکترا"]) | |
| ride_frequency = st.selectbox("دفعات استفاده از سرویسهای اشتراک سفر در ماه", | |
| ["کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"]) | |
| related_education_job = st.text_input( | |
| "اگر تحصیلات یا شغل مرتبط با مدیریت/بازاریابی دارید، لطفاً مشخص کنید (اختیاری):", | |
| placeholder="مثال: مدیریت بازرگانی، بازاریابی دیجیتال، MBA و...", | |
| key="related_education_job" | |
| ) | |
| if st.form_submit_button("ادامه"): | |
| st.session_state.demographic_data = { | |
| "age": age, | |
| "gender": gender, | |
| "education": education, | |
| "ride_frequency": ride_frequency | |
| } | |
| st.session_state.current_page = "scenario_explanation" | |
| st.rerun() | |
| def scenario_explanation(): | |
| """توضیح سناریو""" | |
| col1, col2 = st.columns([1, 4]) | |
| with col1: | |
| try: | |
| st.image("rahyar.png", width=80) | |
| except: | |
| st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80) | |
| with col2: | |
| st.markdown(""" | |
| <h2 class="rahyar-title">رهیار 🚖</h2> | |
| <p class="rahyar-subtitle">همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار</p> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### سناریوی تحقیق") | |
| st.markdown(""" | |
| <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;"> | |
| <p>فرض کنید یک اپلیکیشن حملونقل آنلاین ایرانی به اسم رهیار طراحی شده، چیزی شبیه اسنپ یا تپسی، اما جدیدتر و با شعار "همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار"</p> | |
| <p>در یک روز عادی، شما قصد دارید برای سفری از طریق این پلتفرم اقدام کنید..</p> | |
| <p>با کلیک بر دکمه ادامه، اطلاعات سفر را مشاهده کنید.</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if st.button("ادامه", key="continue_btn", type="primary"): | |
| st.session_state.current_page = "map_view" | |
| st.rerun() | |
| def map_view(): | |
| """نمایش نقشه و قیمت""" | |
| col1, col2 = st.columns([1, 4]) | |
| with col1: | |
| st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80) | |
| with col2: | |
| st.markdown(""" | |
| <h2 style="color: #6a0dad; margin: 0;">رهیار 🚖</h2> | |
| <p style="color: #6a0dad; margin: 0;">همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار</p> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### مسیر سفر شما") | |
| folium_static(create_ride_map(), width=1000 if st.session_state.is_desktop else 800, | |
| height=500 if st.session_state.is_desktop else 400) | |
| # قیمت | |
| st.markdown(f""" | |
| <div class="price-container"> | |
| <div style="display: flex; justify-content: space-between; align-items: center;"> | |
| <span>راهیار <span style="background-color: #e6e6fa; color: #6a0dad; padding: 2px 8px; border-radius: 12px; font-size: 14px;">به صرفه</span></span> | |
| <span class="rahyar-price">{st.session_state.price:,} تومان</span> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| show_explanation(st.session_state.scenario_type) | |
| # دکمهها | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("درخواست راهیار", key="accept_btn", use_container_width=True): | |
| st.session_state.price_accepted = 1 | |
| st.session_state.current_page = "attention_check1" | |
| st.rerun() | |
| with col2: | |
| if st.button("رد قیمت", key="reject_btn", use_container_width=True): | |
| st.session_state.price_accepted = 0 | |
| st.session_state.current_page = "attention_check1" | |
| st.rerun() | |
| def attention_check1(): | |
| """سوال توجه اول (بدون بررسی پاسخ صحیح)""" | |
| st.markdown(""" | |
| <style> | |
| /* تضمین رنگ متن برای تمام سطوح */ | |
| .st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej { | |
| color: black !important; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### سوال توجه") | |
| # استفاده از st.radio با key منحصر به فرد | |
| answer = st.radio( | |
| "رنگ سازمانی اپلیکیشن رهیار چه رنگی بود؟", | |
| ["قرمز", "سبز", "بنفش", "آبی", "زرد"], | |
| index=None, | |
| key="att1_radio" | |
| ) | |
| if st.button("ادامه", key="att1_btn"): | |
| if answer: | |
| st.session_state.attention_check1 = answer # ذخیره پاسخ در session_state | |
| st.session_state.current_page = "random_likert_questions" | |
| st.rerun() | |
| else: | |
| st.warning("لطفاً یک گزینه را انتخاب کنید") | |
| def attention_check2(): | |
| """سوال توجه دوم (بدون بررسی پاسخ صحیح)""" | |
| st.markdown(""" | |
| <style> | |
| /* تضمین رنگ متن برای تمام سطوح */ | |
| .st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej { | |
| color: black !important; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### سوال توجه") | |
| answer = st.radio( | |
| "نام اپلیکیشنی که در این تحقیق بررسی میشود چیست؟", | |
| ["اسنپ", "تپسی", "راهیار", "ماکسیم", "دیگر"], | |
| index=None, | |
| key="att2_radio" | |
| ) | |
| if st.button("ادامه", key="att2_btn"): | |
| if answer: | |
| st.session_state.attention_check2 = answer | |
| st.session_state.current_page = "explanation_questions" | |
| st.rerun() | |
| else: | |
| st.warning("لطفاً یک گزینه را انتخاب کنید") | |
| def random_likert_questions(): | |
| """نمایش تصادفی سوالات لیکرت""" | |
| if 'all_questions' not in st.session_state: | |
| # تعریف تمام سوالات با نوع مقیاس و لیبل مربوطه | |
| st.session_state.all_questions = [ | |
| # سوالات informational (5 گزینهای) | |
| {"key": "informational_1", "question": "پلتفرم به صورت صادقانه دلایل تغییر قیمت (مثل افزایش تقاضا یا شرایط جوی) را توضیح داد.", "scale": "5point", "label": "informational_1"}, | |
| {"key": "informational_2", "question": "پلتفرم به طور کامل عوامل مؤثر بر قیمت (مثل ترافیک، تعداد رانندگان) را شرح داد.", "scale": "5point", "label": "informational_2"}, | |
| {"key": "informational_3", "question": "دلایل ارائهشده برای تغییر قیمت منطقی و قابل قبول بود.", "scale": "5point", "label": "informational_3"}, | |
| {"key": "informational_4", "question": "توضیحات درباره قیمت بلافاصله و در زمان مناسب نمایش داده شد.", "scale": "5point", "label": "informational_4"}, | |
| {"key": "informational_5", "question": "توضیحات پلتفرم متناسب با شرایط سفر من (مثل مسیر یا ساعت درخواست) بود.", "scale": "5point", "label": "informational_5"}, | |
| # سوالات distributive (7 گزینهای) | |
| {"key": "distributive_1", "question": "قیمتی که به شما ارائه شد، منصفانه است.", "scale": "7point", "label": "distributive_1"}, | |
| {"key": "distributive_2", "question": "قیمتی که به شما ارائه شد، معقول است.", "scale": "7point", "label": "distributive_2"}, | |
| {"key": "distributive_3", "question": "قیمتی که به شما ارائه شد، قابل قبول است.", "scale": "7point", "label": "distributive_3"}, | |
| # سوالات procedural (7 گزینهای) | |
| {"key": "procedural_1", "question": "فرآیند و رویه قیمتگذاری پلتفرم قابل قبول است.", "scale": "7point", "label": "procedural_1"}, | |
| {"key": "procedural_2", "question": "فرآیند و رویه قیمتگذاری پلتفرم منصفانه است.", "scale": "7point", "label": "procedural_2"}, | |
| {"key": "procedural_3", "question": "فرآیند و رویه قیمتگذاری پلتفرم معقول است.", "scale": "7point", "label": "procedural_3"} | |
| ] | |
| # تصادفیسازی ترتیب سوالات | |
| random.shuffle(st.session_state.all_questions) | |
| st.session_state.current_question_index = 0 | |
| if st.session_state.current_question_index < len(st.session_state.all_questions): | |
| q = st.session_state.all_questions[st.session_state.current_question_index] | |
| # نمایش سوال بدون شماره | |
| answer = create_likert_question(q["question"], q["key"], q["scale"]) | |
| if answer: | |
| st.session_state.current_question_index += 1 | |
| time.sleep(0.5) | |
| st.rerun() | |
| else: | |
| st.session_state.current_page = "attention_check2" | |
| st.rerun() | |
| def explanation_questions(): | |
| """سوالات درباره توضیحات قیمت""" | |
| st.markdown(""" | |
| <style> | |
| /* تضمین رنگ متن برای تمام سطوح */ | |
| .st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej { | |
| color: black !important; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown("### 📋 سوالات تکمیلی") | |
| # مقداردهی اولیه متغیرهای session_state اگر وجود ندارند | |
| if 'pricing_method' not in st.session_state: | |
| st.session_state.pricing_method = None | |
| if 'price_increase' not in st.session_state: | |
| st.session_state.price_increase = None | |
| if 'explanation_received' not in st.session_state: | |
| st.session_state.explanation_received = None | |
| if 'explanation_type' not in st.session_state: | |
| st.session_state.explanation_type = None | |
| # سوال جدید 1: روش قیمتگذاری | |
| pricing_method = st.radio( | |
| "به نظر شما پلتفرم قیمت را چگونه تعیین میکند؟", | |
| [ | |
| "به صورت دستی توسط تیم پلتفرم", | |
| "به صورت خودکار توسط هوش مصنوعی و الگوریتمها", | |
| "ترکیبی از هر دو روش", | |
| "نظری ندارم" | |
| ], | |
| index=None, | |
| key="pricing_method_radio" | |
| ) | |
| # سوال جدید 2: افزایش قیمت | |
| price_increase = None | |
| if pricing_method is not None: # فقط اگر به سوال اول پاسخ داده شده باشد | |
| price_increase = st.radio( | |
| "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟", | |
| ["بله", "خیر", "مطمئن نیستم"], | |
| index=None, | |
| key="price_increase_radio" | |
| ) | |
| # سوالات قبلی | |
| explanation_received = None | |
| explanation_type = None | |
| if price_increase is not None: # فقط اگر به سوال دوم پاسخ داده شده باشد | |
| explanation_received = st.radio( | |
| "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟", | |
| ["بله", "خیر"], | |
| index=None, | |
| key="explanation_received_radio" | |
| ) | |
| # سوال دوم (فقط اگر پاسخ بله باشد) | |
| if explanation_received == "بله": | |
| explanation_type = st.radio( | |
| "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟", | |
| [ | |
| "بر اساس عواملی که در قیمتگذاری لحاظ شدهاند", | |
| "شامل سناریوهای جایگزین که میتوانستند قیمت متفاوتی ایجاد کنند", | |
| "توضیحی دریافت نکردم" | |
| ], | |
| index=None, | |
| key="explanation_type_radio" | |
| ) | |
| if st.button("ثبت پاسخها", type="primary", key="submit_explanation"): | |
| # استفاده از مقادیر مستقیماً از session_state | |
| st.session_state.pricing_method = st.session_state.get("pricing_method_radio") | |
| st.session_state.price_increase = st.session_state.get("price_increase_radio") | |
| st.session_state.explanation_received = st.session_state.get("explanation_received_radio") | |
| st.session_state.explanation_type = st.session_state.get("explanation_type_radio") | |
| # بررسی کامل بودن پاسخها | |
| if st.session_state.pricing_method is None: | |
| st.warning("لطفاً به سوال روش قیمتگذاری پاسخ دهید") | |
| elif st.session_state.price_increase is None: | |
| st.warning("لطفاً به سوال افزایش قیمت پاسخ دهید") | |
| elif st.session_state.explanation_received is None: | |
| st.warning("لطفاً به سوال دریافت توضیح پاسخ دهید") | |
| elif st.session_state.explanation_received == "بله" and st.session_state.explanation_type is None: | |
| st.warning("لطفاً به سوال نوع توضیح پاسخ دهید") | |
| else: | |
| # جمعآوری تمام دادهها برای ذخیرهسازی | |
| end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| start_time = datetime.strptime(st.session_state.start_time, "%Y-%m-%d %H:%M:%S") | |
| completion_time = (datetime.now() - start_time).total_seconds() | |
| save_data = { | |
| "start_time": st.session_state.start_time, | |
| "end_time": end_time, | |
| "completion_time": completion_time, | |
| "scenario_type": st.session_state.scenario_type, | |
| "price": st.session_state.price, | |
| "user_contact": st.session_state.get("user_contact", ""), | |
| "price_accepted": st.session_state.get("price_accepted", 0), | |
| "attention_check1": st.session_state.attention_check1, | |
| "attention_check2": st.session_state.attention_check2, | |
| "pricing_method": st.session_state.pricing_method, | |
| "price_increase": st.session_state.price_increase, | |
| "explanation_received": st.session_state.explanation_received, | |
| "explanation_type": st.session_state.explanation_type if st.session_state.explanation_received == "بله" else "N/A", | |
| **st.session_state.demographic_data | |
| } | |
| # اضافه کردن پاسخهای لیکرت | |
| for q in st.session_state.all_questions: | |
| save_data[q["label"]] = st.session_state.get(q["key"], None) | |
| if save_to_sheet(save_data): | |
| st.session_state.current_page = "thank_you" | |
| st.rerun() | |
| else: | |
| st.error("خطا در ذخیرهسازی دادهها. لطفاً دوباره تلاش کنید.") | |
| def thank_you_page(): | |
| """صفحه تشکر""" | |
| st.success("✅ پاسخهای شما با موفقیت ثبت شد. با تشکر از مشارکت شما در این تحقیق!") | |
| st.balloons() | |
| if st.button("بازگشت به ابتدا"): | |
| st.session_state.clear() | |
| st.rerun() | |
| # ========== مدیریت وضعیت و صفحهبندی ========== | |
| def main(): | |
| # تشخیص دستگاه | |
| user_agent = st.query_params.get("user_agent", [""])[0] | |
| st.session_state.is_desktop = "mobile" not in user_agent.lower() | |
| if 'current_page' not in st.session_state: | |
| st.session_state.current_page = "welcome" | |
| st.session_state.scenario_type = random.choice(["control", "input", "counterfactual"]) | |
| st.session_state.price = 200000 | |
| st.session_state.user_contact = None | |
| st.session_state.demographic_data = None | |
| st.session_state.price_accepted = 0 | |
| pages = { | |
| "welcome": welcome_page, | |
| "contact": user_contact, | |
| "demographic": demographic_form, | |
| "scenario_explanation": scenario_explanation, | |
| "map_view": map_view, | |
| "attention_check1": attention_check1, | |
| "random_likert_questions": random_likert_questions, | |
| "attention_check2": attention_check2, | |
| "explanation_questions": explanation_questions, | |
| "thank_you": thank_you_page | |
| } | |
| pages[st.session_state.current_page]() | |
| if __name__ == "__main__": | |
| main() |