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("""
""", 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="مبدأ: میدان ونک",
icon=folium.Icon(color="green", icon="flag", prefix="fa")
).add_to(m)
folium.Marker(
end_point,
popup="مقصد: میدان تجریش",
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("
علت قیمت گذاری:
", unsafe_allow_html=True)
for item in explanations.get(exp_type, []):
st.markdown(f"• {item}
", 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"{question}
", 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"پاسخ شما: {options[selected]}
", 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("""
🚖 قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی
👋با سلام و درود
پیشاپیش بابت زمانی که برای پاسخ به سوالات این پرسشنامه و پیشبرد اهداف علمی دانشجویان میگذارید، متشکرم.
جهت تقدیر از شرکتکنندگان، به دو نفر به قید قرعه جایزه 5 میلیون ریالی تقدیم خواهد شد.
درباره تحقیق:
این پرسشنامه بخشی از یک پایاننامه کارشناسی ارشد در دانشگاه صنعتی شریف است که به بررسی ادراک انصاف در قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی میپردازد.
پاسخهای شما به سوالات کاملاً محرمانه خواهد بود و فقط در جهت اهداف علمی استفاده خواهد شذ.
جهت شروع روی دکمه زیر کلیک کنید 👇🏻
""", 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("""
📩 راه ارتباطی شما (اختیاری)
در صورت تمایل به شرکت در قرعهکشی میتوانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:
""", 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("""
رهیار 🚖
همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار
""", unsafe_allow_html=True)
st.markdown("### سناریوی تحقیق")
st.markdown("""
فرض کنید یک اپلیکیشن حملونقل آنلاین ایرانی به اسم رهیار طراحی شده، چیزی شبیه اسنپ یا تپسی، اما جدیدتر و با شعار "همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار"
در یک روز عادی، شما قصد دارید برای سفری از طریق این پلتفرم اقدام کنید..
با کلیک بر دکمه ادامه، اطلاعات سفر را مشاهده کنید.
""", 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("""
رهیار 🚖
همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار
""", 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"""
راهیار به صرفه
{st.session_state.price:,} تومان
""", 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("""
""", 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("""
""", 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("""
""", 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()