study2 / app.py
Maryam Ilka
Update app.py
019a41d verified
raw
history blame
19 kB
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
# تنظیمات اولیه
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');
}
* {
font-family: 'Vazir', sans-serif !important;
text-align: right !important;
direction: rtl !important;
}
.rahyar-header {
background-color: #6a0dad;
padding: 15px;
border-radius: 10px;
color: white;
margin-bottom: 20px;
text-align: center;
}
.price-container {
background-color: #f8f9fa;
border-radius: 10px;
padding: 15px;
margin: 15px 0;
border-right: 5px solid #6a0dad;
}
.rahyar-price {
color: #6a0dad;
font-size: 28px;
font-weight: bold;
text-align: center;
margin: 10px 0;
}
.rahyar-btn {
background-color: #6a0dad !important;
color: white !important;
border: none !important;
padding: 10px 20px !important;
border-radius: 8px !important;
font-weight: bold !important;
margin: 5px 0 !important;
width: 100% !important;
}
.likert-btn {
margin: 5px;
padding: 10px 15px;
border-radius: 8px;
border: 1px solid #ddd;
background-color: white;
}
.likert-btn-selected {
border: 2px solid #6a0dad !important;
background-color: #f0e6ff !important;
}
.survey-section {
background-color: #f0f2f6;
border-radius: 10px;
padding: 20px;
margin-top: 30px;
}
.rahyar-badge {
background-color: #e6e6fa;
color: #6a0dad;
padding: 5px 10px;
border-radius: 15px;
font-size: 14px;
display: inline-block;
margin-left: 10px;
}
.explanation-title {
color: #6a0dad;
font-weight: bold;
margin-top: 20px;
}
.explanation-item {
margin: 10px 0;
padding-right: 15px;
border-right: 3px solid #6a0dad;
.fake-btn {
background-color: #6a0dad !important;
color: #6a0dad !important;
border: none !important;
padding: 12px 24px !important;
border-radius: 8px !important;
font-weight: bold !important;
cursor: pointer;
transition: transform 0.2s;
}
.fake-btn:active {
transform: scale(0.95);
}
}
</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 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 = [
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
data.get("scenario_type"),
data.get("price"),
data.get("age"),
data.get("gender"),
data.get("education"),
data.get("ride_frequency"),
*data.get("transparency_answers", []),
*data.get("fairness_answers", [])
]
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="background-color: #f0f2f6; border-radius: 10px; padding: 20px;">
<h3>درباره تحقیق:</h3>
<p>این پرسشنامه بخشی از یک پایان‌نامه کارشناسی ارشد در دانشگاه صنعتی شریف است که به بررسی ادراک انصاف در قیمت‌گذاری در پلتفرم‌های درخواست تاکسی اینترنتی می‌پردازد.</p>
<p>پاسخ‌های شما به سوالات کاملاً محرمانه خواهد بود و فقط در جهت اهداف علمی استفاده خواهد شذ.</p>
<p>جهت شروع روی دکمه زیر کلیک کنید 👇🏻</p>
</div>
""", unsafe_allow_html=True)
if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
st.session_state.current_page = "contact"
st.rerun()
def user_contact():
"""راه ارتباطی ساده"""
st.markdown("""
<div style="margin-bottom: 20px;">
<h3 style="color: #6a0dad;">📩 راه ارتباطی شما (اختیاری)</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():
"""فرم اطلاعات دموگرافیک"""
with st.form("demographic"):
st.header("📝 اطلاعات دموگرافیک")
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("""
<h2 style="color: #6a0dad; margin: 0;">رهیار 🚖</h2>
<p style="color: #6a0dad; margin: 0;">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
""", unsafe_allow_html=True)
st.markdown("### سناریوی تحقیق")
if st.session_state.scenario_type == "control":
st.markdown("""
<div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
<p>شما قصد دارید سفری را از مبدأ میدان ونک به مقصد میدان تجریش درخواست کنید.</p>
<p>قیمت پیشنهادی این سفر 200,000 تومان است.</p>
</div>
""", unsafe_allow_html=True)
else:
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:
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 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=800, height=400)
st.markdown(f"""
<div class="price-container">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>راهیار <span class="rahyar-badge">به صرفه</span></span>
<span class="rahyar-price">{st.session_state.price:,} تومان</span>
</div>
</div>
""", unsafe_allow_html=True)
show_explanation(st.session_state.scenario_type)
st.markdown("""
<div style="text-align: center;">
<button class="fake-btn">درخواست راهیار</button>
</div>
""", unsafe_allow_html=True)
if st.button("ادامه", key="request_btn", type="primary"):
st.session_state.current_page = "transparency_questions"
st.rerun()
def create_likert_question(question, key):
"""سوال لیکرت با دکمه‌ها"""
st.markdown(f"<p style='margin-bottom: 10px;'>{question}</p>", unsafe_allow_html=True)
cols = st.columns(5)
options = {
1: "کاملاً مخالفم",
2: "مخالفم",
3: "نظری ندارم",
4: "موافقم",
5: "کاملاً موافقم"
}
selected = st.session_state.get(key, None)
for value, label in options.items():
with cols[value-1]:
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 transparency_questions():
"""سوالات شفافیت"""
st.header("📊 پرسشنامه شفافیت قیمت")
st.markdown("**لطفاً میزان موافقت خود با جملات زیر را مشخص کنید:**")
questions = [
"پلتفرم به صورت صادقانه دلایل تغییر قیمت (مثل افزایش تقاضا یا شرایط جوی) را توضیح داد.",
"پلتفرم به طور کامل عوامل مؤثر بر قیمت (مثل ترافیک، تعداد رانندگان) را شرح داد.",
"دلایل ارائه‌شده برای تغییر قیمت منطقی و قابل قبول بود.",
"توضیحات درباره قیمت بلافاصله و در زمان مناسب نمایش داده شد.",
"توضیحات پلتفرم متناسب با شرایط سفر من (مثل مسیر یا ساعت درخواست) بود."
]
answers = []
for i, question in enumerate(questions):
answer = create_likert_question(question, f"transparency_q{i}")
answers.append(answer)
if None not in answers:
st.session_state.transparency_answers = answers
if st.button("ادامه به سوالات بعدی", type="primary"):
st.session_state.current_page = "fairness_questions"
st.rerun()
def fairness_questions():
"""سوالات انصاف"""
st.header("📊 پرسشنامه ادراک انصاف")
st.markdown("**لطفاً میزان موافقت خود با جملات زیر را مشخص کنید:**")
questions = [
"قیمتی که به شما ارائه شد، منصفانه است.",
"قیمتی که به شما ارائه شد، معقول است.",
"قیمتی که به شما ارائه شد، قابل قبول است.",
"فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است.",
"فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است.",
"فرآیند و رویه قیمت‌گذاری پلتفرم معقول است."
]
answers = []
for i, question in enumerate(questions):
answer = create_likert_question(question, f"fairness_q{i}")
answers.append(answer)
if None not in answers:
st.session_state.fairness_answers = answers
if st.button("ارسال پاسخ‌ها", type="primary"):
all_data = {
"scenario_type": st.session_state.scenario_type,
"price": st.session_state.price,
**st.session_state.demographic_data,
"transparency_answers": st.session_state.transparency_answers,
"fairness_answers": st.session_state.fairness_answers
}
if save_to_sheet(all_data):
st.session_state.current_page = "thank_you"
st.rerun()
def thank_you_page():
"""صفحه تشکر"""
st.success("✅ پاسخ‌های شما با موفقیت ثبت شد. با تشکر از مشارکت شما در این تحقیق!")
st.balloons()
if st.button("بازگشت به ابتدا"):
st.session_state.clear()
st.rerun()
# ========== مدیریت وضعیت و صفحه‌بندی ==========
def main():
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 # قیمت ثابت 200 هزار تومان
st.session_state.user_contact = None
st.session_state.demographic_data = None
st.session_state.transparency_answers = []
st.session_state.fairness_answers = []
# نمایش صفحه فعلی
pages = {
"welcome": welcome_page,
"contact": user_contact,
"demographic": demographic_form,
"scenario_explanation": scenario_explanation,
"map_view": map_view,
"transparency_questions": transparency_questions,
"fairness_questions": fairness_questions,
"thank_you": thank_you_page
}
pages[st.session_state.current_page]()
if __name__ == "__main__":
main()