study2 / app.py
Maryam Ilka
Update app.py
4aa5060 verified
raw
history blame
19.4 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="🚖",
initial_sidebar_state="auto" # تنظیم خودکار وضعیت نوار کناری
)
# ========== تنظیمات دیتا ==========
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;
}
/* ریست پیش‌فرض مرورگرها */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Vazir', sans-serif !important;
text-align: right !important;
direction: rtl !important;
}
/* رنگ‌های ثابت برای همه دستگاه‌ها */
:root {
--primary-color: #6a0dad; # بنفش اصلی
--secondary-color: #f0e6ff; # بنفش روشن
--text-color: #333333; # متن تیره
--background-color: #ffffff; # سفید
}
/* اجتناب از تغییرات تم خودکار مرورگر */
@media (prefers-color-scheme: dark) {
:root {
--text-color: #333333 !important; # متن همیشه تیره
--background-color: #ffffff !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;
}
/* کلاس‌های اصلی با رنگ‌های ثابت */
.rahyar-header {
background-color: var(--primary-color) !important;
color: white !important;
}
.price-container {
background-color: #f8f9fa !important;
border-right: 5px solid var(--primary-color) !important;
}
.rahyar-price {
color: var(--primary-color) !important;
}
.rahyar-btn {
background-color: var(--primary-color) !important;
color: white !important;
}
/* تنظیمات پایه برای همه دستگاه‌ها */
* {
font-family: 'Vazir', sans-serif !important;
text-align: right !important;
direction: rtl !important;
box-sizing: border-box !important;
}
/* تنظیمات مخصوص موبایل */
@media only screen and (max-width: 768px) {
.rahyar-price {
font-size: 24px !important; /* کاهش اندازه فونت قیمت در موبایل */
}
.price-container {
padding: 10px !important; /* کاهش padding در موبایل */
}
.folium-map {
height: 300px !important; /* کاهش ارتفاع نقشه در موبایل */
}
}
/* تنظیمات مخصوص دسکتاپ */
@media only screen and (min-width: 769px) {
.folium-map {
height: 400px !important; /* ارتفاع ثابت برای دسکتاپ */
}
:root {
color-scheme: only light !important; # غیرفعال کردن تم تاریک
}
body {
background-color: white !important; # پس‌زمینه سفید اجباری
}
.stApp {
background-color: white !important;
}
}
</style>
""", unsafe_allow_html=True)
# ========== توابع اصلی ==========
def create_ride_map():
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")).add_to(m)
folium.Marker(end_point, popup="مقصد: میدان تجریش", icon=folium.Icon(color="red")).add_to(m)
# خط مسیر
folium.PolyLine([start_point, end_point], color="#6a0dad", weight=3).add_to(m)
# تنظیمات واکنش‌گرا برای نقشه
m.get_root().width = "100%"
m.get_root().height = "100%"
m.get_root().add_child(folium.Element("""
<style>
.folium-map {
width: 100% !important;
height: 100% !important;
}
</style>
"""))
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>
</div>
<div style="background-color: #f0f2f6; border-radius: 10px; padding: 20px;">
<h3>درباره تحقیق:</h3>
<p>این پرسشنامه بخشی از یک پژوهش علمی است که به بررسی ادراک انصاف در قیمت‌گذاری پویا می‌پردازد.</p>
<p>پس از مشاهده سناریو، لطفاً به سوالات پاسخ دهید.</p>
</div>
""", unsafe_allow_html=True)
if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
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():
"""توضیح سناریو"""
st.markdown("""
<div style="display: flex; align-items: center; justify-content: center; gap: 20px; margin-bottom: 20px;">
<img src="https://via.placeholder.com/100/6a0dad/FFFFFF?text=راهیار" width="80">
<div>
<h2 style="color: #6a0dad; margin: 0;">راهیار 🚖</h2>
<p style="color: #6a0dad; margin: 0;">سفرهای درون شهری سریع و مطمئن</p>
</div>
</div>
""", 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>قیمت پیشنهادی این سفر 200,000 تومان است.</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():
"""نمایش نقشه و قیمت"""
st.markdown("""
<div style="display: flex; align-items: center; justify-content: center; gap: 20px; margin-bottom: 20px;">
<img src="rahyar.png" width="80" alt="لوگوی راهیار">
<div>
<h2 style="color: #6a0dad; margin: 0;">راهیار 🚖</h2>
<p style="color: #6a0dad; margin: 0;">سفرهای درون شهری سریع و مطمئن</p>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("### مسیر سفر شما")
folium_static(create_ride_map(), width=800, height=400)
st.markdown(f"""
<div class="price-container" style="width: 100%;">
<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)
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.demographic_data = None
st.session_state.transparency_answers = []
st.session_state.fairness_answers = []
# نمایش صفحه فعلی
pages = {
"welcome": welcome_page,
"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()