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", ""),
# سوالات 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 بار"])
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("### 📋 سوالات تکمیلی")
# سوال اول
explanation_received = st.radio(
"آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
["بله", "خیر"],
index=None,
key="explanation_received"
)
# سوال دوم (فقط اگر پاسخ بله باشد)
explanation_type = None
if explanation_received == "بله":
explanation_type = st.radio(
"اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
[
"بر اساس عواملی که در قیمتگذاری لحاظ شدهاند",
"شامل سناریوهای جایگزین که میتوانستند قیمت متفاوتی ایجاد کنند",
"توضیحی دریافت نکردم"
],
index=None,
key="explanation_type"
)
if st.button("ثبت پاسخها", type="primary"):
if explanation_received is None:
st.warning("لطفاً به سوال اول پاسخ دهید")
elif explanation_received == "بله" and explanation_type is None:
st.warning("لطفاً به سوال دوم پاسخ دهید")
else:
st.session_state.explanation_data = {
"explanation_received": explanation_received,
"explanation_type": explanation_type if explanation_received == "بله" else "N/A"
}
# جمعآوری تمام دادهها برای ذخیرهسازی
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,
"explanation_received": explanation_received,
"explanation_type": explanation_type if 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()