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; | |
| } | |
| </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> | |
| </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="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("### سناریوی تحقیق") | |
| 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"> | |
| <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"> | |
| <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() |