GSMEthesis commited on
Commit
02bd2cc
·
verified ·
1 Parent(s): 7f6e3da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +477 -307
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import streamlit as st
2
  import folium
3
  from streamlit_folium import folium_static
 
4
  from datetime import datetime
5
  import gspread
6
  from google.oauth2.service_account import Credentials
@@ -74,7 +75,7 @@ html, body, .stApp {
74
  [data-testid="stAppViewContainer"] {
75
  width: 475px !important;
76
  height: 667px !important;
77
- margin: 0 auto !important;
78
  border: 12px solid #000 !important;
79
  border-radius: 40px !important;
80
  box-shadow:
@@ -156,7 +157,6 @@ html, body, .stApp {
156
  [data-testid="stAppViewContainer"] .stDateInput>div>div>input,
157
  [data-testid="stAppViewContainer"] .stTimeInput>div>div>input,
158
  [data-testid="stAppViewContainer"] .price-container,
159
- [data-testid="stAppViewContainer"] .explanation-item,
160
  [data-testid="stAppViewContainer"] .stAlert,
161
  [data-testid="stAppViewContainer"] .stMarkdown,
162
  [data-testid="stAppViewContainer"] .folium-map,
@@ -196,7 +196,7 @@ html, body, .stApp {
196
  .rahyar-subtitle {
197
  color: var(--primary) !important;
198
  margin: 0 !important;
199
- font-size: 14px !important;
200
  }
201
 
202
  /* توضیحات */
@@ -213,6 +213,7 @@ html, body, .stApp {
213
  padding: 12px 15px !important;
214
  margin: 8px 0 !important;
215
  border-right: 3px solid var(--primary) !important;
 
216
  }
217
 
218
 
@@ -308,7 +309,7 @@ div[data-baseweb="textarea"] > div:first-child:focus-within {
308
  color: var(--text) !important;
309
  font-weight: bold !important;
310
  margin-bottom: 8px !important;
311
- font-size: 14px !important;
312
  display: block !important;
313
  }
314
 
@@ -461,7 +462,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
461
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; /* سایه بیشتر */
462
  }
463
 
464
- /* استایل تضمینی برای رادیو باتن‌ها - نسخه نهایی */
465
  .stRadio > div > div > div > div > div > label,
466
  .stRadio > div > div > div > div > label,
467
  .stRadio > div > div > div > label,
@@ -470,11 +471,13 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
470
  .stRadio > label {
471
  color: #000000 !important;
472
  -webkit-text-fill-color: #000000 !important; /* برای مرورگرهای وبکیت */
 
473
  }
474
 
475
  .stRadio span {
476
  color: #000000 !important;
477
  -webkit-text-fill-color: #000000 !important;
 
478
  }
479
 
480
  /* اگر باز هم مشکل داشت این را اضافه کنید */
@@ -486,6 +489,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
486
  .stRadio > label > div:first-child {
487
  color: #000000 !important;
488
  -webkit-text-fill-color: #000000 !important;
 
489
  }
490
 
491
  /* استایل پایه برای هشدارها */
@@ -507,7 +511,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
507
  .stAlert .st-ag {
508
  color: #856404 !important;
509
  font-family: 'B Nazanin' !important;
510
- font-size: 14px !important;
511
  margin-right: 8px !important;
512
  }
513
 
@@ -527,7 +531,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
527
  @media (max-width: 768px) {
528
  .stAlert .st-ae {
529
  padding: 12px !important;
530
- font-size: 13px !important;
531
  }
532
  }
533
 
@@ -535,19 +539,16 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
535
  /* متن گزینه‌ها */
536
  .stRadio span {
537
  color: #000000 !important; /* مشکی خالص */
538
- font-size: 14px !important;
539
  padding-right: 5px !important;
540
  }
541
 
542
  /* حالت hover */
543
  .stRadio label:hover span {
544
  color: #333333 !important; /* مشکی کمی روشن‌تر */
 
545
  }
546
 
547
- /* کانتینر اصلی */
548
- .stRadio > div {
549
- margin-bottom: 10px !important;
550
- }
551
 
552
 
553
  /* تنظیمات مخصوص موبایل */
@@ -560,7 +561,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
560
  .stTextInput>label,
561
  .stNumberInput>label,
562
  .stSelectbox>label {
563
- font-size: 14px !important;
564
  padding: 4px 0 !important;
565
  }
566
 
@@ -573,30 +574,45 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
573
  }
574
 
575
  .stButton>button {
576
- font-size: 14px !important;
577
  padding: 8px 16px !important;
578
  }
 
 
 
 
579
 
580
  .stMarkdown h3 {
581
- font-size: 16px !important;
582
  }
583
 
584
  .stMarkdown p {
585
- font-size: 13px !important;
586
  }
587
 
588
  .stSelectbox [role="listbox"] {
589
- font-size: 16px !important;
 
 
 
 
 
 
 
 
 
 
 
590
  }
591
  }
592
 
593
  /* ======== تنظیمات پایه واکنش‌گرا ======== */
594
  :root {
595
  /* اندازه‌های پایه بر اساس عرض 375px (آیفون X) */
596
- --base-font-size: 14px;
597
  --base-heading1-size: 24px;
598
- --base-heading2-size: 20px;
599
- --base-heading3-size: 18px;
600
  --base-input-font: 16px;
601
  --base-button-font: 16px;
602
  }
@@ -604,7 +620,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
604
  /* ======== تنظیمات پویا بر اساس عرض دستگاه ======== */
605
  @media (max-width: 400px) {
606
  :root {
607
- --base-font-size: 13px;
608
  --base-heading1-size: 22px;
609
  --base-heading2-size: 18px;
610
  --base-heading3-size: 16px;
@@ -615,7 +631,7 @@ div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div
615
 
616
  @media (min-width: 401px) and (max-width: 768px) {
617
  :root {
618
- --base-font-size: 14px;
619
  --base-heading1-size: 24px;
620
  --base-heading2-size: 20px;
621
  --base-heading3-size: 18px;
@@ -669,10 +685,106 @@ h3 {
669
  .stButton>button {
670
  padding: calc(var(--base-font-size) * 0.7) calc(var(--base-font-size) * 1.2) !important;
671
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  </style>
673
  """, unsafe_allow_html=True)
674
 
675
  # ========== توابع اصلی ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
676
  def create_ride_map():
677
  """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
678
  # نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
@@ -721,64 +833,74 @@ def show_explanation(exp_type):
721
  """نمایش توضیحات قیمت"""
722
  explanations = {
723
  "input": [
724
- "سطح تقاضا در منطقه: زیاد",
725
- "تعداد رانندگان فعال: کم",
726
- "زمان روز: ساعت اوج ترافیک",
727
- "شرایط جوی: هوای بارانی"
728
  ],
729
  "counterfactual": [
730
- "اگر این سفر را ۳۰ دقیقه دیرتر درخواست می‌کردید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیش‌تر، زمان بهتر روز و شرایط جوی بهتر، ممکن بود قیمت ۱۵٪ کمتر باشد.",
731
  ]
732
  }
733
 
734
  if exp_type != "control":
735
- st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
736
- for item in explanations.get(exp_type, []):
737
- st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
 
739
  def create_likert_question(question, key, scale_type="5point"):
740
- """سوال لیکرت با دکمه‌ها"""
741
- if scale_type == "5point":
742
- options = {
743
- 1: "کاملاً مخالفم",
744
- 2: "مخالفم",
745
- 3: "نظری ندارم",
746
- 4: "موافقم",
747
- 5: "کاملاً موافقم"
748
- }
749
- else: # 7-point scale
750
- options = {
751
- 1: "کاملاً مخالفم",
752
- 2: "مخالفم",
753
- 3: "تا حدی مخالفم",
754
- 4: "نظری ندارم",
755
- 5: "تا حدی موافقم",
756
- 6: "موافقم",
757
- 7: "کاملاً موافقم"
758
- }
759
-
760
- st.markdown(f"<p style='margin-bottom: 10px;'>{question}</p>", unsafe_allow_html=True)
761
 
762
- cols = st.columns(len(options))
763
- selected = st.session_state.get(key, None)
764
 
765
- # نمایش گزینه‌ها از کاملاً موافقم (راست) تا کاملاً مخالفم (چپ)
766
- for value, label in reversed(options.items()):
767
- with cols[len(options) - value]:
768
- if st.button(
769
- label,
770
- key=f"{key}_{value}",
771
- on_click=lambda v=value: st.session_state.update({key: v}),
772
- type="primary" if selected == value else "secondary"
773
- ):
774
- pass
775
 
776
- if selected:
777
- st.markdown(f"<p style='color: #6a0dad;'>پاسخ شما: {options[selected]}</p>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
778
 
779
- return selected
 
 
 
 
 
 
 
 
 
 
 
 
780
 
781
  # ========== توابع مدیریت داده‌ها ==========
 
782
  def get_credentials():
783
  """دریافت اعتبارنامه از Secrets"""
784
  try:
@@ -821,6 +943,7 @@ def save_to_sheet(data):
821
  data.get("education", ""),
822
  data.get("ride_frequency", ""),
823
  data.get("related_education_job",""),
 
824
  data.get("user_contact", ""),
825
  data.get("price_accepted", ""),
826
 
@@ -828,24 +951,24 @@ def save_to_sheet(data):
828
  data.get("attention_check1", ""),
829
  data.get("attention_check2", ""),
830
 
831
-
832
- # سوالات informational (5 گزینه‌ای)
833
- data.get("informational_1", ""),
834
- data.get("informational_2", ""),
835
- data.get("informational_3", ""),
836
- data.get("informational_4", ""),
837
- data.get("informational_5", ""),
838
-
839
  # سوالات distributive (7 گزینه‌ای)
840
  data.get("distributive_1", ""),
841
  data.get("distributive_2", ""),
842
- data.get("distributive_3", ""),
843
 
844
  # سوالات procedural (7 گزینه‌ای)
845
  data.get("procedural_1", ""),
846
  data.get("procedural_2", ""),
847
  data.get("procedural_3", ""),
848
 
 
 
 
 
 
 
 
849
  # سوالات manipulation
850
  data.get ("trust", ""),
851
  data.get("pricing_method", ""),
@@ -861,30 +984,61 @@ def save_to_sheet(data):
861
  st.error(f"خطا در ذخیره‌سازی: {str(e)}")
862
  return False
863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
  # ========== بخش‌های فرم ==========
865
 
866
  def welcome_page():
867
  """صفحه خوشامدگویی"""
868
- st.markdown("""
869
- <div style="text-align: center; margin-bottom: 30px;">
870
- <h2> 🚖 قیمت‌گذاری در پلتفرم‌های درخواست تاکسی اینترنتی</h2>
871
- <p>👋با سلام و درود</p>
872
- <p>پیشاپیش بابت زمانی که برای پاسخ به سوالات این پرسشنامه و پیش‌برد اهداف علمی دانشجویان می‌گذارید، متشکرم.</p>
873
- <p>جهت تقدیر از شرکت‌کنندگان، به دو نفر به قید قرعه جایزه 5 میلیون ریالی تقدیم خواهد شد.</p>
874
- </div>
875
-
876
- <div style="display: flex; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 20px;">
877
- <div style="flex: 0 0 100px;">
878
- <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png" alt="لوگو دانشگاه شریف" style="width: 100%; max-width: 100px;">
879
  </div>
880
  <div style="flex: 1;">
881
- <h3>درباره تحقیق:</h3>
882
- <p>این پرسشنامه بخشی از یک پایان‌نامه کارشناسی ارشد در دانشگاه صنعتی شریف است که به بررسی ادراک انصاف در قیمت‌گذاری در پلتفرم‌های درخواست تاکسی اینترنتی می‌پردازد.</p>
883
- <h5>خواهشمندم فقط ساکنین استان تهران به تکمیل این پرسشنامه اقدام نمایند.</h5>
884
- <p>پاسخ‌های شما به سوالات کاملاً محرمانه خواهد بود و فقط در جهت اهداف علمی استفاده خواهد شد.</p>
885
- <p>جهت شروع روی دکمه زیر کلیک کنید 👇🏻</p>
 
886
  </div>
887
  </div>
 
888
  """, unsafe_allow_html=True)
889
 
890
  if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
@@ -895,12 +1049,14 @@ def welcome_page():
895
 
896
  def scenario_explanation():
897
  """توضیح سناریو"""
898
- col1, col2 = st.columns([1, 4])
899
  with col1:
 
900
  try:
901
  st.image("rahyar.png", width=80)
902
  except:
903
  st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
 
904
  with col2:
905
  st.markdown("""
906
  <h2 class="rahyar-title">رهیار 🚖</h2>
@@ -911,9 +1067,12 @@ def scenario_explanation():
911
 
912
  st.markdown("""
913
  <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
914
- <p>فرض کنید یک اپلیکیشن حمل‌ونقل آنلاین ایرانی به اسم رهیار طراحی شده. چیزی شبیه اسنپ یا تپسی؛ اما جدیدتر، با رنگ سازمانی بنفش و با شعار "همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار"</p>
915
- <p>در یک روز عادی، شما قصد دارید برای سفری از طریق این پلتفرم اقدام کنید..</p>
916
- <p>با کلیک بر دکمه ادامه، اطلاعات سفر را مشاهده کنید.</p>
 
 
 
917
  </div>
918
  """, unsafe_allow_html=True)
919
 
@@ -922,24 +1081,23 @@ def scenario_explanation():
922
  st.rerun()
923
 
924
  def map_view():
925
- """نمایش نقشه و قیمت"""
926
- col1, col2 = st.columns([1, 4])
927
  with col1:
 
928
  try:
929
  st.image("rahyar.png", width=80)
930
  except:
931
  st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
 
932
  with col2:
933
  st.markdown("""
934
- <h2 style="color: #6a0dad; margin: 0;">رهیار 🚖</h2>
935
- <p style="color: #6a0dad; margin: 0;">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
936
  """, unsafe_allow_html=True)
937
 
938
  st.markdown("""
939
  <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
940
  <p>مسیر سفر شما به صورت حدودی، از جنوب به غرب تهران است.</p>
941
- <p>فرض کنید در رهیار گزینه‌های مختلف سفر مثل به‌صرفه،اکوپلاس یا بایک وجود ندارد و فقط می‌توانید رهیار معمولی بگیرید.</p>
942
- <p>همچنین فرض کنید ویژگی‌هایی مانند رهیار با همسفر ، سفر دو مسیره، توقف در سفر و.. نیز در رهیار وجود ندارد.</p>
943
  <p> با توجه به اطلاعاتی که بعد از نقشه دریافت می‌کنید، تصمیم بگیرید که سفر را می‌پذیرید را رد می‌کنید.</p>
944
  <p>سپس با کلیک بر دکمه مربوطه به بخش بعدی بروید.</p>
945
  </div>
@@ -963,13 +1121,13 @@ def map_view():
963
  # دکمه‌ها
964
  col1, col2 = st.columns(2)
965
  with col1:
966
- if st.button("درخواست رهیار", key="accept_btn", use_container_width=True):
967
  st.session_state.price_accepted = 1
968
  st.session_state.current_page = "attention_check1"
969
  st.rerun()
970
 
971
  with col2:
972
- if st.button("رد رهیار", key="reject_btn", use_container_width=True):
973
  st.session_state.price_accepted = 0
974
  st.session_state.current_page = "attention_check1"
975
  st.rerun()
@@ -989,7 +1147,7 @@ def attention_check1():
989
  st.markdown("### سوال")
990
 
991
  answer = st.radio(
992
- "رنگ سازمانی اپلیکیشن رهیار چه رنگی بود؟",
993
  ["قرمز", "سبز", "بنفش", "آبی", "فراموش کردم"],
994
  index=None,
995
  key="att1_radio"
@@ -1035,198 +1193,202 @@ def attention_check1():
1035
  else:
1036
  st.warning("لطفاً یک گزینه را انتخاب کنید")
1037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
 
1039
 
1040
- def random_likert_questions():
1041
- """نمایش تصادفی سوالات لیکرت"""
1042
- if 'all_questions' not in st.session_state:
1043
- # تعریف تمام سوالات با نوع مقیاس و لیبل مربوطه
1044
- st.session_state.all_questions = [
1045
- # سوالات informational (5 گزینه‌ای)
1046
- {"key": "informational_1", "question": "پلتفرم به صورت صادقانه دلایل تعیین قیمت را توضیح داد.", "scale": "5point", "label": "informational_1"},
1047
- {"key": "informational_2", "question": "پلتفرم به طور کامل عوامل مؤثر بر تعیین قیمت را شرح داد.", "scale": "5point", "label": "informational_2"},
1048
- {"key": "informational_3", "question": "دلایل ارائه‌شده برای تعیین قیمت منطقی و قابل قبول بود.", "scale": "5point", "label": "informational_3"},
1049
- {"key": "informational_4", "question": "توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد.", "scale": "5point", "label": "informational_4"},
1050
- {"key": "informational_5", "question": "توضیحات پلتفرم درباره تعیین قیمت، متناسب با شرایط سفر من بود.", "scale": "5point", "label": "informational_5"},
1051
-
1052
- # سوالات distributive (7 گزینه‌ای)
1053
- {"key": "distributive_1", "question": "قیمتی که به شما ارائه شد، منصفانه است.", "scale": "7point", "label": "distributive_1"},
1054
- {"key": "distributive_2", "question": "قیمتی که به شما ارائه شد، معقول است.", "scale": "7point", "label": "distributive_2"},
1055
- {"key": "distributive_3", "question": "قیمتی که به شما ارائه شد، قابل قبول است.", "scale": "7point", "label": "distributive_3"},
1056
-
1057
- # سوالات procedural (7 گزینه‌ای)
1058
- {"key": "procedural_1", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است.", "scale": "7point", "label": "procedural_1"},
1059
- {"key": "procedural_2", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است.", "scale": "7point", "label": "procedural_2"},
1060
- {"key": "procedural_3", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم معقول است.", "scale": "7point", "label": "procedural_3"},
1061
-
1062
- # سوال attention_check2 به صورت رندوم در میان سوالات دیگر
1063
- {"key": "attention_check2", "question": "حاصل ۵ × ۳ به اضافه ۲ چقدر می‌شود؟", "scale": "attention", "label": "attention_check2"}
1064
- ]
1065
-
1066
- # تصادفی‌سازی ترتیب سوالات
1067
- random.shuffle(st.session_state.all_questions)
1068
- st.session_state.current_question_index = 0
1069
 
1070
- # نمایش توضیحات بالای سوالات (فقط برای سوالات لیکرت)
1071
- if (st.session_state.current_question_index < len(st.session_state.all_questions)) and \
1072
- (st.session_state.all_questions[st.session_state.current_question_index]["scale"] != "attention"):
1073
- st.markdown("""
1074
- <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
1075
- <p style="font-weight: bold;">راهنمای پاسخگویی:</p>
1076
- <p>لطفا موافقت خود را با سوالات زیر طبق گزینه‌ها اعلام کنید.</p>
1077
- <p>با کلیک روی هر گزینه پاسخ شما انتخاب خواهد شد. لطفا با صبوری و بررسی سوالات را پاسخ دهید و تا بارگزاری سوال بعدی کمی صبر کنید.</p>
1078
- </div>
1079
- """, unsafe_allow_html=True)
1080
 
1081
- if st.session_state.current_question_index < len(st.session_state.all_questions):
1082
- q = st.session_state.all_questions[st.session_state.current_question_index]
1083
-
1084
- # نمایش سوال attention_check2 به صورت خاص
1085
- if q["scale"] == "attention":
1086
-
1087
- st.markdown("""
1088
- <style>
1089
- /* تضمین رنگ متن برای تمام سطوح */
1090
- .st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej {
1091
- color: black !important;
1092
- }
1093
- </style>
1094
- """, unsafe_allow_html=True)
1095
- st.markdown("### سوال")
1096
-
1097
- answer = st.radio(
1098
- "حاصل ۵ × ۳ به اضافه ۲ چقدر می‌شود؟",
1099
- ["1", "13", "15", "17", "دیگر"],
1100
- index=None,
1101
- key="att2_radio"
1102
- )
1103
-
1104
- if st.button("ادامه", key="att2_btn"):
1105
- if answer:
1106
- st.session_state.attention_check2 = answer
1107
- st.session_state.current_question_index += 1
1108
- time.sleep(0.5)
1109
- st.rerun()
1110
- else:
1111
- st.warning("لطفاً یک گزینه را انتخاب کنید")
1112
- else:
1113
- # نمایش سوالات لیکرت به صورت معمول
1114
- answer = create_likert_question(q["question"], q["key"], q["scale"])
1115
-
1116
- if answer:
1117
- st.session_state.current_question_index += 1
1118
- time.sleep(0.5)
1119
- st.rerun()
1120
- else:
1121
- st.session_state.current_page = "explanation_questions"
1122
- st.rerun()
1123
-
1124
- def explanation_questions():
1125
- """سوالات درباره توضیحات قیمت"""
1126
  st.markdown("""
1127
  <style>
1128
- /* تضمین رنگ متن برای تمام سطوح */
1129
- .st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej {
1130
- color: black !important;
1131
  }
1132
  </style>
1133
  """, unsafe_allow_html=True)
1134
- st.markdown("### 📋 سوالات تکمیلی")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1135
 
1136
- # مقداردهی اولیه متغیرهای session_state اگر وجود ندارند
1137
- if 'trust' not in st.session_state:
1138
- st.session_state.trust = None
1139
- if 'pricing_method' not in st.session_state:
1140
- st.session_state.pricing_method = None
1141
- if 'price_increase' not in st.session_state:
1142
- st.session_state.price_increase = None
1143
- if 'explanation_received' not in st.session_state:
1144
- st.session_state.explanation_received = None
1145
- if 'explanation_type' not in st.session_state:
1146
- st.session_state.explanation_type = None
1147
-
1148
- trust = st.radio(
1149
- "آیا شما به تصمیم گیری هایی که توسط هوش مصنوعی انجام می شود اعتماد دارید؟",
1150
- [
1151
- "بله",
1152
- "خیر",
1153
- "نظری ندارم"
1154
- ],
1155
- index=None,
1156
- key="trust_radio"
1157
- )
1158
 
1159
- pricing_method = None
1160
- if trust is not None:
1161
- pricing_method = st.radio(
1162
- "به نظر شما پلتفرم قیمت را چگونه تعیین می‌کند؟",
1163
- [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
  "به صورت دستی توسط تیم پلتفرم",
1165
  "به صورت خودکار توسط هوش مصنوعی و الگوریتم‌ها",
1166
  "ترکیبی از هر دو روش",
1167
  "نظری ندارم"
1168
  ],
1169
- index=None,
1170
- key="pricing_method_radio"
1171
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172
 
1173
- # سوال جدید 2: افزایش قیمت
1174
- price_increase = None
1175
- if pricing_method is not None: # فقط اگر به سوال اول پاسخ داده شده باشد
1176
- price_increase = st.radio(
1177
- "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
1178
- ["بله", "خیر", "مطمئن نیستم"],
1179
- index=None,
1180
- key="price_increase_radio"
1181
- )
1182
 
1183
- # سوالات قبلی
1184
- explanation_received = None
1185
- explanation_type = None
1186
- if price_increase is not None: # فقط اگر به سوال دوم پاسخ داده شده باشد
1187
- explanation_received = st.radio(
1188
- "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
1189
- ["بله", "خیر"],
1190
- index=None,
1191
- key="explanation_received_radio"
1192
- )
1193
-
1194
- # سوال دوم (فقط اگر پاسخ بله باشد)
1195
- if explanation_received == "بله":
1196
- explanation_type = st.radio(
1197
- "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
1198
- [
1199
- "بر اساس عواملی که در قیمت‌گذاری لحاظ شده‌اند",
1200
- "شامل سناریوهای جایگزین که می‌توانستند قیمت متفاوتی ایجاد کنند",
1201
- "توضیحی دریافت نکردم"
1202
- ],
1203
- index=None,
1204
- key="explanation_type_radio"
1205
- )
1206
-
1207
- if st.button("ادامه", type="primary", key="submit_explanation"):
1208
- # استفاده از مقادیر مستقیماً از session_state
1209
- st.session_state.trust = st.session_state.get("trust_radio")
1210
- st.session_state.pricing_method = st.session_state.get("pricing_method_radio")
1211
- st.session_state.price_increase = st.session_state.get("price_increase_radio")
1212
- st.session_state.explanation_received = st.session_state.get("explanation_received_radio")
1213
- st.session_state.explanation_type = st.session_state.get("explanation_type_radio")
1214
-
1215
- # بررسی کامل بودن پاسخ‌ها
1216
- if st.session_state.trust is None:
1217
- st.warning("لطفاً به سوال تکمیلی اول پاسخ دهید")
1218
- elif st.session_state.pricing_method is None:
1219
- st.warning("لطفاً به سوال تکمیلی دوم پاسخ دهید")
1220
- elif st.session_state.price_increase is None:
1221
- st.warning("لطفاً به سوال تکمیلی سوم پاسخ دهید")
1222
- elif st.session_state.explanation_received is None:
1223
- st.warning("لطفاً به سوال تکمیلی چهارم پاسخ دهید")
1224
- elif st.session_state.explanation_received == "بله" and st.session_state.explanation_type is None:
1225
- st.warning("لطفاً به سوال تکمیلی پنجم پاسخ دهید")
1226
- else:
1227
- st.session_state.current_page = "demographic"
1228
- st.rerun()
1229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1230
 
1231
  def demographic_form():
1232
  """فرم اطلاعات دموگرافیک"""
@@ -1239,27 +1401,37 @@ def demographic_form():
1239
  """, unsafe_allow_html=True)
1240
 
1241
  with st.form("demographic_form"):
1242
- age = st.number_input("سن", min_value=18, max_value=100)
1243
- gender = st.selectbox("جنسیت", ["مرد", "زن", "سایر"])
1244
- education = st.selectbox("تحصیلات", ["دیپلم", "لیسانس", "فوق لیسانس", "دکترا"])
 
 
 
 
 
 
 
 
 
 
1245
  ride_frequency = st.selectbox("دفعات استفاده از سرویس‌های اشتراک سفر در ماه",
1246
- ["کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"])
1247
- related_education_job = st.text_input(
1248
- "اگر تحصیلات یا شغل مرتبط با مدیریت/بازاریابی دارید، لطفاً مشخص کنید (اختیاری):",
1249
- placeholder="مثال: مدیریت بازرگانی، بازاریابی دیجیتال، MBA و...",
1250
- key="related_education_job"
1251
- )
1252
-
1253
- if st.form_submit_button("ادامه"):
1254
- st.session_state.demographic_data = {
1255
- "age": age,
1256
- "gender": gender,
1257
- "education": education,
1258
- "ride_frequency": ride_frequency,
1259
- "related_education_job": related_education_job
1260
- }
1261
- st.session_state.current_page = "contact"
1262
- st.rerun()
1263
 
1264
  def user_contact():
1265
  """راه ارتباطی ساده"""
@@ -1290,20 +1462,16 @@ def user_contact():
1290
  "price": st.session_state.price,
1291
  "user_contact": st.session_state.get("user_contact", ""),
1292
  "price_accepted": st.session_state.get("price_accepted", 0),
1293
- "attention_check1": st.session_state.attention_check1,
1294
- "attention_check2": st.session_state.attention_check2,
1295
  "trust": st.session_state.trust,
1296
  "pricing_method": st.session_state.pricing_method,
1297
  "price_increase": st.session_state.price_increase,
1298
  "explanation_received": st.session_state.explanation_received,
1299
- "explanation_type": st.session_state.explanation_type if st.session_state.explanation_received == "بله" else "N/A",
1300
- **st.session_state.demographic_data
 
1301
  }
1302
 
1303
- # اضافه کردن پاسخ‌های لیکرت
1304
- for q in st.session_state.all_questions:
1305
- save_data[q["label"]] = st.session_state.get(q["key"], None)
1306
-
1307
  if save_to_sheet(save_data):
1308
  st.session_state.current_page = "thank_you"
1309
  st.rerun()
@@ -1330,10 +1498,12 @@ def main():
1330
  st.session_state.is_desktop = "mobile" not in user_agent.lower()
1331
 
1332
 
1333
- # اینجا می‌توانید از is_desktop برای تعیین اینکه نمایشگر موبایل اعمال شود، استفاده کنید
1334
  if st.session_state.is_desktop:
1335
  # اطمینان از نمایش همان حالت موبایل برای همه دستگاه‌ها
1336
  st.session_state.is_desktop = False
 
 
 
1337
 
1338
  if 'current_page' not in st.session_state:
1339
  st.session_state.current_page = "welcome"
@@ -1342,7 +1512,7 @@ def main():
1342
  st.session_state.user_contact = None
1343
  st.session_state.demographic_data = None
1344
  st.session_state.price_accepted = 0
1345
-
1346
 
1347
  pages = {
1348
  "welcome": welcome_page,
 
1
  import streamlit as st
2
  import folium
3
  from streamlit_folium import folium_static
4
+ import streamlit.components.v1 as components
5
  from datetime import datetime
6
  import gspread
7
  from google.oauth2.service_account import Credentials
 
75
  [data-testid="stAppViewContainer"] {
76
  width: 475px !important;
77
  height: 667px !important;
78
+ margin: 0 auto !important;
79
  border: 12px solid #000 !important;
80
  border-radius: 40px !important;
81
  box-shadow:
 
157
  [data-testid="stAppViewContainer"] .stDateInput>div>div>input,
158
  [data-testid="stAppViewContainer"] .stTimeInput>div>div>input,
159
  [data-testid="stAppViewContainer"] .price-container,
 
160
  [data-testid="stAppViewContainer"] .stAlert,
161
  [data-testid="stAppViewContainer"] .stMarkdown,
162
  [data-testid="stAppViewContainer"] .folium-map,
 
196
  .rahyar-subtitle {
197
  color: var(--primary) !important;
198
  margin: 0 !important;
199
+ font-size: 16px !important;
200
  }
201
 
202
  /* توضیحات */
 
213
  padding: 12px 15px !important;
214
  margin: 8px 0 !important;
215
  border-right: 3px solid var(--primary) !important;
216
+ font-size: 18px !important;
217
  }
218
 
219
 
 
309
  color: var(--text) !important;
310
  font-weight: bold !important;
311
  margin-bottom: 8px !important;
312
+ font-size: 16px !important;
313
  display: block !important;
314
  }
315
 
 
462
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; /* سایه بیشتر */
463
  }
464
 
465
+ /* استایل تضمینی برای رادیو باتن‌ها - نسخه نهایی */
466
  .stRadio > div > div > div > div > div > label,
467
  .stRadio > div > div > div > div > label,
468
  .stRadio > div > div > div > label,
 
471
  .stRadio > label {
472
  color: #000000 !important;
473
  -webkit-text-fill-color: #000000 !important; /* برای مرورگرهای وبکیت */
474
+ font-size: 20px !important
475
  }
476
 
477
  .stRadio span {
478
  color: #000000 !important;
479
  -webkit-text-fill-color: #000000 !important;
480
+ font-size: 20px !important
481
  }
482
 
483
  /* اگر باز هم مشکل داشت این را اضافه کنید */
 
489
  .stRadio > label > div:first-child {
490
  color: #000000 !important;
491
  -webkit-text-fill-color: #000000 !important;
492
+ font-size: 20px !important
493
  }
494
 
495
  /* استایل پایه برای هشدارها */
 
511
  .stAlert .st-ag {
512
  color: #856404 !important;
513
  font-family: 'B Nazanin' !important;
514
+ font-size: 16px !important;
515
  margin-right: 8px !important;
516
  }
517
 
 
531
  @media (max-width: 768px) {
532
  .stAlert .st-ae {
533
  padding: 12px !important;
534
+ font-size: 15px !important;
535
  }
536
  }
537
 
 
539
  /* متن گزینه‌ها */
540
  .stRadio span {
541
  color: #000000 !important; /* مشکی خالص */
542
+ font-size: 20px !important;
543
  padding-right: 5px !important;
544
  }
545
 
546
  /* حالت hover */
547
  .stRadio label:hover span {
548
  color: #333333 !important; /* مشکی کمی روشن‌تر */
549
+ font-size: 20px !important;
550
  }
551
 
 
 
 
 
552
 
553
 
554
  /* تنظیمات مخصوص موبایل */
 
561
  .stTextInput>label,
562
  .stNumberInput>label,
563
  .stSelectbox>label {
564
+ font-size: 16px !important;
565
  padding: 4px 0 !important;
566
  }
567
 
 
574
  }
575
 
576
  .stButton>button {
577
+ font-size: 17px !important;
578
  padding: 8px 16px !important;
579
  }
580
+
581
+ .stMarkdown h2 {
582
+ font-size: 22px !important;
583
+ }
584
 
585
  .stMarkdown h3 {
586
+ font-size: 19px !important;
587
  }
588
 
589
  .stMarkdown p {
590
+ font-size: 17px !important;
591
  }
592
 
593
  .stSelectbox [role="listbox"] {
594
+ font-size: 18px !important;
595
+ }
596
+ .stRadio span {
597
+ color: #000000 !important; /* مشکی خالص */
598
+ font-size: 20px !important;
599
+ padding-right: 5px !important;
600
+ }
601
+
602
+ /* حالت hover */
603
+ .stRadio label:hover span {
604
+ color: #333333 !important; /* مشکی کمی روشن‌تر */
605
+ font-size: 20px !important;
606
  }
607
  }
608
 
609
  /* ======== تنظیمات پایه واکنش‌گرا ======== */
610
  :root {
611
  /* اندازه‌های پایه بر اساس عرض 375px (آیفون X) */
612
+ --base-font-size: 20px;
613
  --base-heading1-size: 24px;
614
+ --base-heading2-size: 22px;
615
+ --base-heading3-size: 20px;
616
  --base-input-font: 16px;
617
  --base-button-font: 16px;
618
  }
 
620
  /* ======== تنظیمات پویا بر اساس عرض دستگاه ======== */
621
  @media (max-width: 400px) {
622
  :root {
623
+ --base-font-size: 17px;
624
  --base-heading1-size: 22px;
625
  --base-heading2-size: 18px;
626
  --base-heading3-size: 16px;
 
631
 
632
  @media (min-width: 401px) and (max-width: 768px) {
633
  :root {
634
+ --base-font-size: 17px;
635
  --base-heading1-size: 24px;
636
  --base-heading2-size: 20px;
637
  --base-heading3-size: 18px;
 
685
  .stButton>button {
686
  padding: calc(var(--base-font-size) * 0.7) calc(var(--base-font-size) * 1.2) !important;
687
  }
688
+ /* اضافه کردن این بخش جدید */
689
+ [data-testid="stAppViewContainer"] img {
690
+ max-width: 100% !important;
691
+ height: auto !important;
692
+ display: block !important;
693
+ padding: 0 !important;
694
+ margin: 0 auto !important;
695
+ }
696
+
697
+ /* برای تضمین نمایش در حالت موبایل شبیه‌سازی شده */
698
+ @media (min-width: 768px) {
699
+ [data-testid="stAppViewContainer"] img {
700
+ max-width: 200px !important;
701
+ }
702
+ }
703
+
704
+
705
+ /* تنظیم justify برای تمام متن‌ها */
706
+ [data-testid="stAppViewContainer"] * {
707
+ text-align: justify !important;
708
+ text-justify: inter-word !important;
709
+ }
710
+
711
+ /* استثناها برای عناصری که نباید justify شوند */
712
+ [data-testid="stAppViewContainer"] .stRadio>label,
713
+ [data-testid="stAppViewContainer"] .stCheckbox>label,
714
+ [data-testid="stAppViewContainer"] .stSelectbox>label,
715
+ [data-testid="stAppViewContainer"] .stTextInput>label,
716
+ [data-testid="stAppViewContainer"] .stNumberInput>label,
717
+ [data-testid="stAppViewContainer"] input,
718
+ [data-testid="stAppViewContainer"] select,
719
+ [data-testid="stAppViewContainer"] textarea,
720
+ [data-testid="stAppViewContainer"] .rahyar-title,
721
+ [data-testid="stAppViewContainer"] .rahyar-subtitle {
722
+ direction: rtl !important;
723
+ text-align: right !important;
724
+ text-justify: auto !important;
725
+ }
726
+
727
+ /* تنظیمات برای لیست‌ها */
728
+ [data-testid="stAppViewContainer"] ul,
729
+ [data-testid="stAppViewContainer"] ol {
730
+ padding-right: 25px !important;
731
+ }
732
+
733
+ [data-testid="stAppViewContainer"] p:not(.stButton p):not(.stDownloadButton p):not(.stFormSubmitButton p) {
734
+ margin-bottom: 1em !important;
735
+ line-height: 1.8 !important;
736
+ }
737
  </style>
738
  """, unsafe_allow_html=True)
739
 
740
  # ========== توابع اصلی ==========
741
+ def custom_likert_slider(question_data):
742
+ """نمایش سوال لیکرت با اسلایدر نقطه‌ای و لیبل‌های سفارشی"""
743
+ question = question_data["question"]
744
+ key = question_data["key"]
745
+ points = question_data["scale"]
746
+ labels = question_data.get("labels", ["کمترین", "بیشترین"]) # لیبل‌های پیش‌فرض
747
+
748
+ # مقدار پیش‌فرض (وسط طیف)
749
+ default_value = st.session_state.get(key, (points + 1) // 2)
750
+
751
+ # HTML و JavaScript
752
+ html = f"""
753
+ <div id="container_{key}" style="direction: ltr; font-family: 'B Nazanin'; margin-bottom: 30px;">
754
+ <label style="font-size: 16px; font-weight: bold; display: block; text-align: right;">{question}</label>
755
+ <div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 5px;">
756
+ <span>{labels[0]}</span>
757
+ <span>{labels[1]}</span>
758
+ </div>
759
+ <input type="range" id="{key}" min="1" max="{points}" step="1" value="{default_value}"
760
+ style="width: 100%; height: 10px; accent-color: #6a0dad; margin-bottom: 15px;"
761
+ oninput="updateSlider('{key}')">
762
+ <div style="text-align: center; margin-top: 10px; direction: rtl;">
763
+ پاسخ انتخاب‌شده: <strong><span id="output_{key}">{default_value}</span></strong>
764
+ </div>
765
+ </div>
766
+
767
+ <script>
768
+ function updateSlider(key) {{
769
+ const value = parseInt(document.getElementById(key).value);
770
+ document.getElementById('output_' + key).innerText = value;
771
+
772
+ // ارسال مقدار به Streamlit
773
+ window.parent.postMessage({{
774
+ type: 'streamlit:setComponentValue',
775
+ key: key,
776
+ value: value
777
+ }}, '*');
778
+ }}
779
+ </script>
780
+ """
781
+
782
+ # نمایش کامپوننت
783
+ components.html(html, height=150)
784
+
785
+ # مقدار نهایی
786
+ return st.session_state.get(key, default_value)
787
+
788
  def create_ride_map():
789
  """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
790
  # نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
 
833
  """نمایش توضیحات قیمت"""
834
  explanations = {
835
  "input": [
836
+ " سطح تقاضا در منطقه: زیاد (+)",
837
+ " تعداد رانندگان فعال: کم (+)",
838
+ " زمان روز: ساعت اوج ترافیک (+)",
839
+ "شرایط جوی: هوای بارانی (++)"
840
  ],
841
  "counterfactual": [
842
+ "اگر این سفر را 1 ساعت دیرتر درخواست کنید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیش‌تر، زمان بهتر روز و شرایط جوی بهتر، احتمالاً قیمت حدوداً 40٪ کمتر (120 هزار تومان) خواهد بود.",
843
  ]
844
  }
845
 
846
  if exp_type != "control":
847
+ if exp_type == "input":
848
+ st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
849
+ st.markdown("""
850
+ <div style="direction: rtl; text-align: right;">
851
+ <span style="font-size: 0.9em; color: #666;">(تعداد علامت + نشان دهنده شدت اثر عامل بر قیمت است)</span>
852
+ </div>
853
+ """, unsafe_allow_html=True)
854
+ for item in explanations.get(exp_type, []):
855
+ st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
856
+
857
+ elif exp_type == "counterfactual":
858
+ st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
859
+ for item in explanations.get(exp_type, []):
860
+ st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
861
+
862
+
863
+
864
 
865
  def create_likert_question(question, key, scale_type="5point"):
866
+ """نمایش سوال لیکرت با اسلایدر نقطه‌ای"""
867
+ left_label = "کاملاً مخالفم" if scale_type == "7point" else "کاملاً مخالفم"
868
+ right_label = "کاملاً موافقم" if scale_type == "7point" else "کاملاً موافقم"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
 
870
+ st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
 
871
 
872
+ max_value = 7 if scale_type == "7point" else 5
 
 
 
 
 
 
 
 
 
873
 
874
+ # اضافه کردن attribute برای انتخاب استایل مناسب
875
+ slider_container = st.empty()
876
+ with slider_container:
877
+ st.markdown(f'<div data-testid="stSlider" data-discrete="{"seven-point" if scale_type=="7point" else "five-point"}">', unsafe_allow_html=True)
878
+ value = st.slider(
879
+ "",
880
+ min_value=1,
881
+ max_value=max_value,
882
+ value=(max_value+1)//2,
883
+ step=1,
884
+ key=f"slider_{key}"
885
+ )
886
+ st.markdown('</div>', unsafe_allow_html=True)
887
 
888
+ st.markdown(
889
+ f"""
890
+ <div class="slider-labels">
891
+ <span>{left_label}</span>
892
+ <span>{right_label}</span>
893
+ </div>
894
+ <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
895
+ پاسخ شما: {value} ({'کاملاً مخالفم' if value==1 else 'کاملاً موافقم' if value==max_value else 'خنثی' if value==((max_value+1)//2) else 'موافقم' if value>((max_value+1)//2) else 'مخالفم'})
896
+ </p>
897
+ """,
898
+ unsafe_allow_html=True
899
+ )
900
+ return value
901
 
902
  # ========== توابع مدیریت داده‌ها ==========
903
+
904
  def get_credentials():
905
  """دریافت اعتبارنامه از Secrets"""
906
  try:
 
943
  data.get("education", ""),
944
  data.get("ride_frequency", ""),
945
  data.get("related_education_job",""),
946
+ data.get("city",""),
947
  data.get("user_contact", ""),
948
  data.get("price_accepted", ""),
949
 
 
951
  data.get("attention_check1", ""),
952
  data.get("attention_check2", ""),
953
 
954
+
 
 
 
 
 
 
 
955
  # سوالات distributive (7 گزینه‌ای)
956
  data.get("distributive_1", ""),
957
  data.get("distributive_2", ""),
958
+ data.get("distributive_3", ""),
959
 
960
  # سوالات procedural (7 گزینه‌ای)
961
  data.get("procedural_1", ""),
962
  data.get("procedural_2", ""),
963
  data.get("procedural_3", ""),
964
 
965
+ # سوالات informational (5 گزینه‌ای)
966
+ data.get("informational_1", ""),
967
+ data.get("informational_2", ""),
968
+ data.get("informational_3", ""),
969
+ data.get("informational_4", ""),
970
+ data.get("informational_5", ""),
971
+
972
  # سوالات manipulation
973
  data.get ("trust", ""),
974
  data.get("pricing_method", ""),
 
984
  st.error(f"خطا در ذخیره‌سازی: {str(e)}")
985
  return False
986
 
987
+ def create_likert_question(question, key, scale_type="5point"):
988
+ """نمایش سوال لیکرت با اسلایدر نقطه‌ای"""
989
+ # تعریف لیبل‌های دو طرف
990
+ left_label = "کاملاً مخالفم" if scale_type == "7point" else "کاملاً مخالفم"
991
+ right_label = "کاملاً موافقم" if scale_type == "7point" else "کاملاً موافقم"
992
+
993
+ st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
994
+
995
+ # ایجاد اسلایدر
996
+ max_value = 7 if scale_type == "7point" else 5
997
+ value = st.slider(
998
+ "",
999
+ min_value=1,
1000
+ max_value=max_value,
1001
+ value=(max_value+1)//2, # مقدار میانی
1002
+ step=1,
1003
+ key=f"slider_{key}"
1004
+ )
1005
+
1006
+ # نمایش متن دو طرف اسلایدر
1007
+ st.markdown(
1008
+ f"""
1009
+ <div class="slider-labels">
1010
+ <span>{left_label}</span>
1011
+ <span>{right_label}</span>
1012
+ </div>
1013
+ <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
1014
+ پاسخ شما: {value}
1015
+ </p>
1016
+ """,
1017
+ unsafe_allow_html=True
1018
+ )
1019
+ return value
1020
+
1021
  # ========== بخش‌های فرم ==========
1022
 
1023
  def welcome_page():
1024
  """صفحه خوشامدگویی"""
1025
+ st.markdown("""
1026
+ <div style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;">
1027
+ <div style="text-align: center;">
1028
+ <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png"
1029
+ alt="لوگو دانشگاه شریف"
1030
+ style="width: 200px; height: auto; margin-bottom: 10px;">
 
 
 
 
 
1031
  </div>
1032
  <div style="flex: 1;">
1033
+ <h3>به پرسشنامه ما خوش آمدید 🌟</h3>
1034
+ <p>با سلام و احترام،</p>
1035
+ <pز شما دعوت می‌شود در یک پژوهش دانشگاهی شرکت کنید که در قالب پایان‌نامه کارشناسی‌ارشد در دانشگاه صنعتی شریف انجام می‌شود. این تحقیق به بررسی ادراک مصرف‌کنندگان از انصاف در قیمت‌گذاریِ اپلیکیشن‌های تاکسی اینترنتی (مانند اسنپ و تپسی 🚖) می‌پردازد.</p>
1036
+ <p>شرکت در این مطالعه کاملاً داوطلبانه است؛ پاسخ دقیقی برای سوالات وجود ندارد و پاسخ‌های صادقانه شما فقط برای پیش‌برد اهداف علمی تحلیل خواهد شد.</p>
1037
+ <p>پر کردن این پرسشنامه کمتر از 5 دقیقه وقت شما را می‌گیرد و پاسخ‌های ارزشمند شما کمک شایانی به ارتقای دانش علمی خواهد کرد. پیشاپیش از مشارکت شما صمیمانه سپاسگزاریم 🙏</p>
1038
+ <p>برای آغاز پرسشنامه، لطفاً روی دکمه زیر کلیک کنید 👇🏻</p>
1039
  </div>
1040
  </div>
1041
+ </div>
1042
  """, unsafe_allow_html=True)
1043
 
1044
  if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
 
1049
 
1050
  def scenario_explanation():
1051
  """توضیح سناریو"""
1052
+ col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
1053
  with col1:
1054
+ st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1055
  try:
1056
  st.image("rahyar.png", width=80)
1057
  except:
1058
  st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1059
+ st.markdown('</div>', unsafe_allow_html=True)
1060
  with col2:
1061
  st.markdown("""
1062
  <h2 class="rahyar-title">رهیار 🚖</h2>
 
1067
 
1068
  st.markdown("""
1069
  <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1070
+ <p>فرض کنید در روزی از روزها شما مهمان یکی از اقوام‌تان در جنوب تهران هستید. قصد دارید برای خریدی به غرب تهران بروید...</p>
1071
+ <p> گوشی‌تان را از کیف درمی‌آورید. چشم‌تان به آیکون جدیدی می‌افتد؛ اپلیکیشنی به نام <strong>رهیار</strong> — نه اسنپ است و نه تپسی، اما خیلی شبیه آن‌هاست. رنگ بنفش جذابی دارد و شعارش توی ذهن‌تان می‌نشیند:
1072
+ <br>«همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار»</p>
1073
+ <p>با کنجکاوی اپ را باز می‌کنید. ظاهر ساده و روانی دارد. فعلاً فقط گزینه‌ی «سفر معمولی» فعال است. خبری از امکانات اضافه مثل «سفر دو‌مسیـره»، «توقف در مسیر»، «اکوپلاس» یا «موتورسوار» نیست — اما خب، رهیار تازه‌کار است و قرار است توسعه پیدا کند!.</p>
1074
+ <p>مبدأ و مقصد را انتخاب می‌کنید و با قیمت مواجه می‌شوید.</p>
1075
+ <p>با کلیک روی «ادامه»، اطلاعات سفر را مشاهده کنید 👇🏻</p>
1076
  </div>
1077
  """, unsafe_allow_html=True)
1078
 
 
1081
  st.rerun()
1082
 
1083
  def map_view():
1084
+ col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
 
1085
  with col1:
1086
+ st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1087
  try:
1088
  st.image("rahyar.png", width=80)
1089
  except:
1090
  st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1091
+ st.markdown('</div>', unsafe_allow_html=True)
1092
  with col2:
1093
  st.markdown("""
1094
+ <h2 class="rahyar-title">رهیار 🚖</h2>
1095
+ <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1096
  """, unsafe_allow_html=True)
1097
 
1098
  st.markdown("""
1099
  <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1100
  <p>مسیر سفر شما به صورت حدودی، از جنوب به غرب تهران است.</p>
 
 
1101
  <p> با توجه به اطلاعاتی که بعد از نقشه دریافت می‌کنید، تصمیم بگیرید که سفر را می‌پذیرید را رد می‌کنید.</p>
1102
  <p>سپس با کلیک بر دکمه مربوطه به بخش بعدی بروید.</p>
1103
  </div>
 
1121
  # دکمه‌ها
1122
  col1, col2 = st.columns(2)
1123
  with col1:
1124
+ if st.button("درخواست سفر", key="accept_btn", use_container_width=True):
1125
  st.session_state.price_accepted = 1
1126
  st.session_state.current_page = "attention_check1"
1127
  st.rerun()
1128
 
1129
  with col2:
1130
+ if st.button("رد سفر", key="reject_btn", use_container_width=True):
1131
  st.session_state.price_accepted = 0
1132
  st.session_state.current_page = "attention_check1"
1133
  st.rerun()
 
1147
  st.markdown("### سوال")
1148
 
1149
  answer = st.radio(
1150
+ "رنگ لوگو و تم رنگی اپلیکیشن رهیار چگونه بود؟",
1151
  ["قرمز", "سبز", "بنفش", "آبی", "فراموش کردم"],
1152
  index=None,
1153
  key="att1_radio"
 
1193
  else:
1194
  st.warning("لطفاً یک گزینه را انتخاب کنید")
1195
 
1196
+ def random_likert_questions():
1197
+ """نمایش سوالات لیکرت به ترتیب مشخص با اسلایدر سفارشی"""
1198
+ # تعریف گروه‌های سوالات با لیبل‌های سفارشی
1199
+ question_groups = [
1200
+ {
1201
+ "title": "عدالت توزیعی",
1202
+ "key": "distributive",
1203
+ "questions": [
1204
+ {
1205
+ "key": "distributive_1",
1206
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1207
+ "scale": 7,
1208
+ "labels": ["کاملاً نامنصفانه", "کاملاً منصفانه"] # لیبل‌های سفارشی برای این سوال
1209
+ },
1210
+ {
1211
+ "key": "distributive_2",
1212
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1213
+ "scale": 7,
1214
+ "labels": ["کاملاً غیرمعقول", "کاملاً معقول"]
1215
+ },
1216
+ {
1217
+ "key": "distributive_3",
1218
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1219
+ "scale": 7,
1220
+ "labels": ["کاملاً غیرقابل قبول", "کاملاً قابل قبول"]
1221
+ }
1222
+ ]
1223
+ },
1224
+ {
1225
+ "title": "سوال توجه",
1226
+ "key": "attention_check",
1227
+ "questions": [
1228
+ {"key": "attention_check2", "question": "تا چه مقدار با دقت به سوالات پاسخ می‌دهید؟", "scale": 7,"labels": ["خیلی کم", "خیلی زیاد"]}
1229
+ ]
1230
+ },
1231
+ {
1232
+ "title": "عدالت رویه‌ای",
1233
+ "key": "procedural",
1234
+ "questions": [
1235
+ {"key": "procedural_1", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]},
1236
+ {"key": "procedural_2", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]},
1237
+ {"key": "procedural_3", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم معقول است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]}
1238
+ ]
1239
+ },
1240
+ {
1241
+ "title": "عدالت اطلاعاتی",
1242
+ "key": "informational",
1243
+ "questions": [
1244
+ {"key": "informational_1", "question": "تا چه حد رهیار دلایل تعیین قیمت را به صورت صادقانه توضیح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1245
+ {"key": "informational_2", "question": "تا چه حد رهیار عوامل مؤثر بر تعیین قیمت را به طور کامل شرح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1246
+ {"key": "informational_3", "question": "تا چه حد دلایل ارائه‌شده توسط رهیار برای تعیین قیمت منطقی و قابل قبول بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1247
+ {"key": "informational_4", "question": "تا چه حد توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1248
+ {"key": "informational_5", "question": "تا چه حد توضیحات رهیار درباره تعیین قیمت، متناسب با شرایط سفر شما بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]}
1249
+ ]
1250
+ }
1251
+ ]
1252
 
1253
 
1254
+ # مقداردهی اولیه
1255
+ if 'current_likert_group' not in st.session_state:
1256
+ st.session_state.current_likert_group = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1257
 
1258
+ # دریافت گروه جاری
1259
+ current_group = question_groups[st.session_state.current_likert_group]
 
 
 
 
 
 
 
 
1260
 
1261
+ # نمایش عنوان گروه
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1262
  st.markdown("""
1263
  <style>
1264
+ .guide-text p {
1265
+ font-size: 14px !important;
 
1266
  }
1267
  </style>
1268
  """, unsafe_allow_html=True)
1269
+ st.markdown("""
1270
+ <div class="guide-text" style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;">
1271
+ <div style="flex: 1;">
1272
+ <h3>راهنمای پاسخ:</h3>
1273
+ <p>برای پاسخ به سوالات، با یک طیف کشویی مواجه خواهید شد.</p>
1274
+ <p>در این طیف 7 نقطه وجود دارد:
1275
+ <br>- سمت چپ: کمترین مقد��ر
1276
+ <br>- سمت راست: بیشترین مقدار
1277
+ </p>
1278
+ <p>نقطه روی کشو را جابجا کنید و در مقداری که پاسخ شماست متوقف شوید.</p>
1279
+ <p>پاسخ شما زیر طیف نمایش داده خواهد شد.</p>
1280
+ <p>اگر از پاسخ‌هایتان مطمئن هستید، روی دکمه «ادامه» کلیک کنید.</p>
1281
+ </div>
1282
+ </div>
1283
+ """, unsafe_allow_html=True)
1284
+
1285
+ # نمایش تمام سوالات این گروه در یک صفحه
1286
+ for question in current_group['questions']:
1287
+ answer = custom_likert_slider(question)
1288
+ st.session_state.answers[question["key"]] = answer
1289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1290
 
1291
+ # دکمه ادامه/اتمام
1292
+ button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups)-1 else "اتمام پرسشنامه"
1293
+
1294
+ if st.button(button_label):
1295
+ # رفتن به گروه بعدی یا صفحه پایانی
1296
+ if st.session_state.current_likert_group < len(question_groups) - 1:
1297
+ st.session_state.current_likert_group += 1
1298
+ st.rerun()
1299
+ else:
1300
+ st.session_state.current_page = "explanation_questions"
1301
+ st.rerun()
1302
+
1303
+ def explanation_questions():
1304
+ """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1305
+ st.markdown("### 📋 سوالات تکمیلی")
1306
+
1307
+ # لیست سوالات به ترتیب نمایش
1308
+ questions = [
1309
+ {
1310
+ "key": "trust",
1311
+ "label": "آیا شما به تصمیم‌گیری‌هایی که توسط هوش مصنوعی انجام می‌شود اعتماد دارید؟",
1312
+ "options": ["بله", "خیر", "نظری ندارم"],
1313
+ "required": True
1314
+ },
1315
+ {
1316
+ "key": "pricing_method",
1317
+ "label": "به نظر شما پلتفرم قیمت را چگونه تعیین می‌کند؟",
1318
+ "options": [
1319
  "به صورت دستی توسط تیم پلتفرم",
1320
  "به صورت خودکار توسط هوش مصنوعی و الگوریتم‌ها",
1321
  "ترکیبی از هر دو روش",
1322
  "نظری ندارم"
1323
  ],
1324
+ "required": True
1325
+ },
1326
+ {
1327
+ "key": "price_increase",
1328
+ "label": "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
1329
+ "options": ["بله", "خیر", "مطمئن نیستم"],
1330
+ "required": True
1331
+ },
1332
+ {
1333
+ "key": "explanation_received",
1334
+ "label": "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
1335
+ "options": ["بله", "خیر"],
1336
+ "required": True
1337
+ },
1338
+ {
1339
+ "key": "explanation_type",
1340
+ "label": "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
1341
+ "options": [
1342
+ "بر اساس عواملی که در قیمت‌گذاری لحاظ شده‌اند",
1343
+ "شامل سناریوهای جایگزین که می‌توانستند قیمت متفاوتی ایجاد کنند",
1344
+ "توضیحی دریافت نکردم"
1345
+ ],
1346
+ "required": False,
1347
+ "condition": lambda: st.session_state.get("explanation_received") == "بله"
1348
+ }
1349
+ ]
1350
 
1351
+ # مقداردهی اولیه step اگر وجود ندارد
1352
+ if "explanation_step" not in st.session_state:
1353
+ st.session_state.explanation_step = 0
 
 
 
 
 
 
1354
 
1355
+ # اگر همه سوالات پاسخ داده شده‌اند، به صفحه بعدی برو
1356
+ if st.session_state.explanation_step >= len(questions):
1357
+ st.session_state.current_page = "demographic"
1358
+ st.rerun()
1359
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1360
 
1361
+ # دریافت سوال جاری
1362
+ current_q = questions[st.session_state.explanation_step]
1363
+
1364
+ # بررسی شرط نمایش برای سوالات اختیاری
1365
+ if "condition" in current_q and not current_q["condition"]():
1366
+ st.session_state[current_q["key"]] = "N/A"
1367
+ st.session_state.explanation_step += 1
1368
+ st.rerun()
1369
+ return
1370
+
1371
+ # نمایش سوال جاری
1372
+ answer = st.radio(
1373
+ current_q["label"],
1374
+ current_q["options"],
1375
+ index=None,
1376
+ key=f"explanation_q_{current_q['key']}"
1377
+ )
1378
+
1379
+ # دکمه ادامه
1380
+ if st.button("ادامه", key=f"continue_{current_q['key']}"):
1381
+ if answer is None and current_q["required"]:
1382
+ st.warning("لطفاً یک گزینه را انتخاب کنید")
1383
+ else:
1384
+ # ذخیره پاسخ
1385
+ st.session_state[current_q["key"]] = answer if answer is not None else "N/A"
1386
+
1387
+ # افزایش شماره مرحله
1388
+ st.session_state.explanation_step += 1
1389
+
1390
+ # رفرش صفحه برای نمایش سوال بعدی
1391
+ st.rerun()
1392
 
1393
  def demographic_form():
1394
  """فرم اطلاعات دموگرافیک"""
 
1401
  """, unsafe_allow_html=True)
1402
 
1403
  with st.form("demographic_form"):
1404
+ age = st.number_input("سن", min_value=18, max_value=100, value=None, placeholder="سن خود را وارد کنید")
1405
+ gender = st.selectbox("جنسیت", ["", "مرد", "زن", "سایر"], index=0)
1406
+ education = st.selectbox("تحصیلات", ["", "دیپلم", "لیسانس", "فوق لیسانس", "دکترا"], index=0)
1407
+ city = st.selectbox("لطفاً استان محل سکونت خود را انتخاب بفرمایید.",
1408
+ ["", "آذربایجان شرقی", "آذربایجان غربی", "اردبیل", "اصفهان", "البرز", "ایلام",
1409
+ "بوشهر", "تهران", "چهارمحال و بختیاری", "خراسان جنوبی", "خراسان رضوی", "خراسان شمالی",
1410
+ "خوزستان", "زنجان", "سمنان", "سیستان و بلوچستان", "فارس", "قزوین", "قم", "کردستان",
1411
+ "کرمان", "کرمانشاه", "کهگیلویه و بویراحمد", "گلستان", "گیلان", "لرستان", "مازندران",
1412
+ "مرکزی", "هرمزگان", "همدان", "یزد"], index=0)
1413
+ related_education_job = st.selectbox("رشته تحصیلی/شغل شما در کدام‌یک از دسته‌های زیر قرار دارد؟",
1414
+ ["", "مهندسی", "درمانی", "فرهنگی", "مدیریتی (مالی)",
1415
+ "مدیریتی (بازاریابی)", "مدیریتی (سایر)", "روانشناسی",
1416
+ "اقتصادی", "حقوقی", "هنری", "ورزشی", "زبان", "غیره"], index=0)
1417
  ride_frequency = st.selectbox("دفعات استفاده از سرویس‌های اشتراک سفر در ماه",
1418
+ ["", "هیچوقت", "کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"], index=0)
1419
+
1420
+ submitted = st.form_submit_button("ادامه")
1421
+ if submitted:
1422
+ if not all([age, gender, education, city, related_education_job, ride_frequency]):
1423
+ st.error("لطفاً تمام فیلدها را پر کنید")
1424
+ else:
1425
+ st.session_state.demographic_data = {
1426
+ "age": age,
1427
+ "gender": gender,
1428
+ "education": education,
1429
+ "city": city,
1430
+ "ride_frequency": ride_frequency,
1431
+ "related_education_job": related_education_job
1432
+ }
1433
+ st.session_state.current_page = "contact"
1434
+ st.rerun()
1435
 
1436
  def user_contact():
1437
  """راه ارتباطی ساده"""
 
1462
  "price": st.session_state.price,
1463
  "user_contact": st.session_state.get("user_contact", ""),
1464
  "price_accepted": st.session_state.get("price_accepted", 0),
1465
+ "attention_check1": st.session_state.get("attention_check1", None),
 
1466
  "trust": st.session_state.trust,
1467
  "pricing_method": st.session_state.pricing_method,
1468
  "price_increase": st.session_state.price_increase,
1469
  "explanation_received": st.session_state.explanation_received,
1470
+ "explanation_type": st.session_state.get("explanation_type", "N/A"),
1471
+ **st.session_state.demographic_data,
1472
+ **st.session_state.answers # اضافه کردن تمام پاسخ‌های لیکرت
1473
  }
1474
 
 
 
 
 
1475
  if save_to_sheet(save_data):
1476
  st.session_state.current_page = "thank_you"
1477
  st.rerun()
 
1498
  st.session_state.is_desktop = "mobile" not in user_agent.lower()
1499
 
1500
 
 
1501
  if st.session_state.is_desktop:
1502
  # اطمینان از نمایش همان حالت موبایل برای همه دستگاه‌ها
1503
  st.session_state.is_desktop = False
1504
+
1505
+ if 'answers' not in st.session_state:
1506
+ st.session_state.answers = {}
1507
 
1508
  if 'current_page' not in st.session_state:
1509
  st.session_state.current_page = "welcome"
 
1512
  st.session_state.user_contact = None
1513
  st.session_state.demographic_data = None
1514
  st.session_state.price_accepted = 0
1515
+ st.session_state.attention_check1 = None
1516
 
1517
  pages = {
1518
  "welcome": welcome_page,