Spaces:
Sleeping
Sleeping
| 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() |