GSMEthesis commited on
Commit
37da5a9
·
verified ·
1 Parent(s): 8645dcc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +782 -790
app.py CHANGED
@@ -11,18 +11,14 @@ import random
11
  import time
12
 
13
  # تنظیمات اولیهه
14
-
15
- st.set\_page\_config(layout="wide", page\_title="راهیار - تحلیل انصاف قیمتی", page\_icon="🚖")
16
 
17
  # ========== تنظیمات دیتا ==========
18
-
19
- SHEET\_ID = "1mmdWAyOCYq4yXMgP53Duq712AnlqZWLkfIo76JqM7wM"
20
- SHEET\_NAME = "Condition1"
21
 
22
  # ========== استایل‌های سفارشی یکپارچه ==========
23
-
24
  st.markdown("""
25
-
26
  <style>
27
  /* تنظیمات کلی استایل‌ها */
28
  @font-face {
@@ -739,833 +735,829 @@ h3 {
739
  line-height: 1.8 !important;
740
  }
741
  </style>
742
-
743
- """, unsafe\_allow\_html=True)
744
 
745
  # ========== توابع اصلی ==========
746
 
747
- def enhanced\_likert\_scale(question\_data):
748
- """لیکرت اسکیل با خط و نقاط - کاملاً واکنش‌گرا"""
749
- question = question\_data\["question"]
750
- key = question\_data\["key"]
751
- scale = question\_data\["scale"]
752
- labels = question\_data.get("labels", \["کاملاً مخالفم", "کاملاً موافقم"])
753
-
754
- ```
755
- if key not in st.session_state:
756
- st.session_state[key] = None
757
-
758
- # نمایش سوال
759
- st.markdown(f"<div style='text-align:center; font-weight:bold; margin-bottom:15px;'>{question}</div>",
760
- unsafe_allow_html=True)
761
-
762
- # ایجاد خط و نقاط با HTML/CSS
763
- scale_html = f"""
764
- <style>
765
- .likert-line {{
766
- width: 80%;
767
- height: 2px;
768
- background: #6a0dad;
769
- margin: 0 auto;
770
- position: relative;
771
- display: flex;
772
- justify-content: space-between;
773
- direction: rtl; /* تغییر جهت از راست به چپ */
774
- }}
775
- .likert-dot {{
776
- width: 18px; /* اندازه دکمه‌ها کوچک‌تر شد */
777
- height: 18px; /* اندازه دکمه‌ها کوچک‌تر شد */
778
- border-radius: 50%;
779
- background: white;
780
- border: 2px solid #6a0dad;
781
- position: relative;
782
- top: -9px;
783
- cursor: pointer;
784
- display: flex;
785
- align-items: center;
786
- justify-content: center;
787
- }}
788
- .likert-dot.selected {{
789
- background: #6a0dad;
790
- }}
791
- .likert-labels {{
792
- width: 80%;
793
- margin: 5px auto 20px;
794
- display: flex;
795
- justify-content: space-between;
796
- direction: rtl; /* جهت لیبل‌ها باید از راست به چپ باشد */
797
- }}
798
- .likert-value {{
799
- text-align: center;
800
- margin-top: 10px;
801
- color: #6a0dad;
802
- font-weight: bold;
803
- }}
804
- /* مخفی کردن دکمه‌ها با اندازه کوچک */
805
- .likert-button {{
806
- visibility: hidden;
807
- width: 12px;
808
- height: 12px;
809
- margin: 2px; /* فاصله کم بین دکمه‌ها */
810
- }}
811
- @media (max-width: 768px) {{
812
  .likert-line {{
813
- width: 90%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  }}
815
  .likert-labels {{
816
- width: 90%;
 
 
 
 
817
  }}
818
- }}
819
- </style>
820
-
821
- <div class='likert-container'>
822
- <div class='likert-labels'>
823
- <span>{labels[1]}</span>
824
- <span>{labels[0]}</span>
825
- </div>
826
- <div class='likert-line'>
827
- """
828
-
829
- # اضافه کردن نقاط
830
- for i in range(scale):
831
- value = scale - i
832
- is_selected = st.session_state.get(key) == value
833
- scale_html += f"<div class='likert-dot {'selected' if is_selected else ''}' onclick='setLikertValue({value})'></div>"
834
-
835
- scale_html += "</div>"
836
-
837
- # نمایش مقدار انتخاب شده
838
- if st.session_state.get(key):
839
- scale_html += f"<div class='likert-value'>پاسخ شما: {st.session_state[key]}</div>"
840
-
841
- scale_html += "</div>"
842
-
843
- # تزریق JavaScript برای مدیریت کلیک
844
- components.html(scale_html + """
845
- <script>
846
- function setLikertValue(value) {
847
- const streamlitDoc = window.parent.document;
848
- const buttons = streamlitDoc.querySelectorAll('button[data-testid="stButton"]'); // جستجوی دکمه‌ها در DOM
849
- buttons.forEach(btn => {
850
- if (btn.textContent.trim() === String(value)) {
851
- btn.click(); // کلیک روی دکمه مورد نظر برای انتخاب مقدار
852
- }
853
- });
854
- }
855
- </script>
856
- """, height=80)
857
 
858
- # دکمه‌های واقعی (مخفی و کوچک)
859
- btn_cols = st.columns(scale)
860
- for i in range(scale):
861
- with btn_cols[i]:
862
  value = scale - i
863
- if st.button(
864
- str(value),
865
- key=f"{key}_btn_{value}",
866
- type="primary" if st.session_state.get(key) == value else "secondary",
867
- help="این دکمه‌ها مخفی هستند" # پیام راهنما
868
- ):
869
- st.session_state[key] = value
870
- st.rerun()
871
-
872
- return st.session_state.get(key)
873
- ```
874
-
875
- def create\_ride\_map():
876
- """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
877
- \# نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
878
- south\_tehran = \[35.65, 51.38] # منطقه عمومی جنوب تهران
879
- west\_tehran = \[35.72, 51.31] # منطقه عمومی غرب تهران
880
-
881
- ```
882
- # مرکز نقشه بین دو نقطه
883
- m = folium.Map(location=[35.685, 51.315], zoom_start=11)
884
-
885
- # ایجاد دایره برای مبدأ (جنوب تهران)
886
- folium.Circle(
887
- location=south_tehran,
888
- radius=2500, # شعاع 1.5 کیلومتر
889
- popup="<b>مبدأ:</b> منطقه جنوب تهران",
890
- color="#6a0dad",
891
- fill=True,
892
- fill_color="#6a0dad",
893
- fill_opacity=0.2,
894
- weight=2
895
- ).add_to(m)
896
-
897
- # ایجاد دایره برای مقصد (غرب تهران)
898
- folium.Circle(
899
- location=west_tehran,
900
- radius=2500, # شعاع 1.5 کیلومتر
901
- popup="<b>مقصد:</b> منطقه غرب تهران",
902
- color="#ff0000",
903
- fill=True,
904
- fill_color="#ff0000",
905
- fill_opacity=0.2,
906
- weight=2
907
- ).add_to(m)
908
-
909
- # خط ارتباطی بین دو منطقه
910
- folium.PolyLine(
911
- [south_tehran, west_tehran],
912
- color="#6a0dad",
913
- weight=3,
914
- opacity=0.7,
915
- dash_array='5, 5' # خط چین
916
- ).add_to(m)
917
-
918
- return m
919
- ```
920
-
921
- def show\_explanation(exp\_type):
922
- """نمایش توضیحات قیمت"""
923
- explanations = {
924
- "input": \[
925
- " سطح تقاضا در منطقه: زیاد (+)",
926
- " تعداد رانندگان فعال: کم (+)",
927
- " زمان روز: ساعت اوج ترافیک (+)",
928
- "شرایط جوی: هوای بارانی (++)"
929
- ],
930
- "counterfactual": \[
931
- "اگر این سفر را 1 ساعت دیرتر درخواست کنید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیش‌تر، زمان بهتر روز و شرایط جوی بهتر، احتمالاً قیمت حدوداً 40٪ کمتر (120 هزار تومان) خواهد بود.",
932
- ]
933
- }
934
-
935
- ```
936
- if exp_type != "control":
937
- if exp_type == "input":
938
- st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
939
- st.markdown("""
940
- <div style="direction: rtl; text-align: right;">
941
- <span style="font-size: 0.9em; color: #666;">(تعداد علامت + نشان دهنده شدت اثر عامل بر قیمت است)</span>
942
- </div>
943
- """, unsafe_allow_html=True)
944
- for item in explanations.get(exp_type, []):
945
- st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
946
 
947
- elif exp_type == "counterfactual":
948
- st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
949
- for item in explanations.get(exp_type, []):
950
- st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
951
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
 
954
- ```
955
-
956
- def create\_likert\_question(question, key, scale\_type="5point"):
957
- """نمایش سوال لیکرت با اسلا��در نقطه‌ای"""
958
- left\_label = "کاملاً مخالفم" if scale\_type == "7point" else "کاملاً مخالفم"
959
- right\_label = "کاملاً موافقم" if scale\_type == "7point" else "کاملاً موافقم"
960
-
961
- ```
962
- st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
963
-
964
- max_value = 7 if scale_type == "7point" else 5
965
-
966
- # اضافه کردن attribute برای انتخاب استایل مناسب
967
- slider_container = st.empty()
968
- with slider_container:
969
- st.markdown(f'<div data-testid="stSlider" data-discrete="{"seven-point" if scale_type=="7point" else "five-point"}">', unsafe_allow_html=True)
970
- value = st.slider(
971
- "",
972
- min_value=1,
973
- max_value=max_value,
974
- value=(max_value+1)//2,
975
- step=1,
976
- key=f"slider_{key}"
977
- )
978
- st.markdown('</div>', unsafe_allow_html=True)
979
 
980
- st.markdown(
981
- f"""
982
- <div class="slider-labels">
983
- <span>{left_label}</span>
984
- <span>{right_label}</span>
985
- </div>
986
- <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
987
- پاسخ شما: {value} ({'کاملاً مخالفم' if value==1 else 'کاملاً موافقم' if value==max_value else 'خنثی' if value==((max_value+1)//2) else 'موافقم' if value>((max_value+1)//2) else 'مخالفم'})
988
- </p>
989
- """,
990
- unsafe_allow_html=True
991
- )
992
- return value
993
- ```
994
 
995
- # ========== توابع مدیریت داده‌ها ==========
996
 
997
- def get\_credentials():
998
- """دریافت اعتبارنامه از Secrets"""
999
- try:
1000
- service\_account\_json = os.environ.get('GCP\_SERVICE\_ACCOUNT')
1001
- if not service\_account\_json:
1002
- st.error("مقدار GCP\_SERVICE\_ACCOUNT در محیط یافت نشد")
1003
- return None
1004
-
1005
- ```
1006
- service_account_info = json.loads(service_account_json)
1007
- creds = Credentials.from_service_account_info(
1008
- service_account_info,
1009
- scopes=[
1010
- "https://www.googleapis.com/auth/spreadsheets",
1011
- "https://www.googleapis.com/auth/drive.file"
1012
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1013
  )
1014
- return creds
1015
- except Exception as e:
1016
- st.error(f"خطا در دریافت اعتبارنامه: {str(e)}")
1017
- return None
1018
- ```
1019
-
1020
- def save\_to\_sheet(data):
1021
- try:
1022
- creds = get\_credentials()
1023
- if not creds:
1024
- return False
1025
-
1026
- ```
1027
- client = gspread.authorize(creds)
1028
- spreadsheet = client.open_by_key(SHEET_ID)
1029
- worksheet = spreadsheet.worksheet(SHEET_NAME)
1030
 
1031
- row_data = [
1032
- data.get("start_time", ""), # زمان شروع
1033
- data.get("end_time", ""), # زمان پایان
1034
- data.get("completion_time", ""), # مدت زمان تکمیل (ثانیه)
1035
- data.get("scenario_type", ""),
1036
- data.get("price", ""),
1037
- data.get("age", ""),
1038
- data.get("gender", ""),
1039
- data.get("education", ""),
1040
- data.get("ride_frequency", ""),
1041
- data.get("related_education_job",""),
1042
- data.get("city",""),
1043
- data.get("user_contact", ""),
1044
- data.get("price_accepted", ""),
1045
-
1046
- # سوالات توجه
1047
- data.get("attention_check1", ""),
1048
- data.get("attention_check2", ""),
 
 
 
 
 
 
 
 
 
 
 
 
1049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1050
 
1051
- # سوالات distributive (7 گزینه‌ای)
1052
- data.get("distributive_1", ""),
1053
- data.get("distributive_2", ""),
1054
- data.get("distributive_3", ""),
1055
-
1056
- # سوالات procedural (7 گزینه‌ای)
1057
- data.get("procedural_1", ""),
1058
- data.get("procedural_2", ""),
1059
- data.get("procedural_3", ""),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1060
 
1061
- # سوالات informational (5 گزینه‌ای)
1062
- data.get("informational_1", ""),
1063
- data.get("informational_2", ""),
1064
- data.get("informational_3", ""),
1065
- data.get("informational_4", ""),
1066
- data.get("informational_5", ""),
1067
 
1068
- # سوالات manipulation
1069
- data.get ("trust", ""),
1070
- data.get("pricing_method", ""),
1071
- data.get("price_increase", ""),
1072
- data.get("explanation_received", ""),
1073
- data.get("explanation_type", "")
1074
- ]
1075
-
1076
- worksheet.append_row(row_data)
1077
- return True
1078
-
1079
- except Exception as e:
1080
- st.error(f"خطا در ذخیره‌سازی: {str(e)}")
1081
- return False
1082
- ```
1083
 
1084
- # ========== بخش‌های فرم ==========
1085
 
1086
- def welcome\_page():
1087
- """صفحه خوشامدگویی"""
1088
- st.markdown(""" <div style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;"> <div style="text-align: center;"> <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png"
1089
- alt="لوگو دانشگاه شریف"
1090
- style="width: 200px; height: auto; margin-bottom: 10px;"> </div> <div style="flex: 1;"> <h3>به پرسشنامه ما خوش آمدید 🌟</h3> <p>با سلام و احترام،</p> <p>از شما دعوت می‌شود در یک پژوهش دانشگاهی شرکت کنید که در قالب پایان‌نامه کارشناسی‌ارشد در دانشگاه صن��تی شریف انجام می‌شود. این تحقیق به بررسی ادراک مصرف‌کنندگان از انصاف در قیمت‌گذاریِ اپلیکیشن‌های تاکسی اینترنتی (مانند اسنپ و تپسی 🚖) می‌پردازد.</p> <p>شرکت در این مطالعه کاملاً داوطلبانه است؛ پاسخ دقیقی برای سوالات وجود ندارد و پاسخ‌های صادقانه شما فقط برای پیش‌برد اهداف علمی تحلیل خواهد شد.</p> <p>پر کردن این پرسشنامه کمتر از 5 دقیقه وقت شما را می‌گیرد و پاسخ‌های ارزشمند شما کمک شایانی به ارتقای دانش علمی خواهد کرد. پیشاپیش از مشارکت شما صمیمانه سپاسگزاریم 🙏</p> <p>برای آغاز پرسشنامه، لطفاً روی دکمه زیر کلیک کنید 👇🏻</p> </div> </div> </div>
1091
- """, unsafe\_allow\_html=True)
1092
-
1093
- ```
1094
- if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
1095
- st.session_state.current_page = "scenario_explanation"
1096
- st.session_state.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1097
- st.rerun()
1098
- ```
1099
-
1100
- def scenario\_explanation():
1101
- """توضیح سناریو"""
1102
- col1, col2 = st.columns(\[2, 4]) # افزایش نسبت ستون اول
1103
- with col1:
1104
- st.markdown('<div style="padding-right: 25px;">', unsafe\_allow\_html=True)
1105
- try:
1106
- st.image("rahyar.png", width=80)
1107
- except:
1108
- st.image("[https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO](https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO)", width=80)
1109
- st.markdown('</div>', unsafe\_allow\_html=True)
1110
- with col2:
1111
- st.markdown(""" <h2 class="rahyar-title">رهیار 🚖</h2> <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1112
- """, unsafe\_allow\_html=True)
1113
-
1114
- ```
1115
- st.markdown("### سناریوی تحقیق")
1116
 
1117
- st.markdown("""
1118
- <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1119
- <p>فرض کنید در روزی از روزها شما مهمان یکی از اقوام‌تان در جنوب تهران هستید. قصد دارید برای خریدی به غرب تهران بروید...</p>
1120
- <p> گوشی‌تان را از کیف درمی‌آورید. چشم‌تان به آیکون جدیدی می‌افتد؛ اپلیکیشنی به نام <strong>رهیار</strong> — نه اسنپ است و نه تپسی، اما خیلی شبیه آن‌هاست. رنگ بنفش جذابی دارد و شعارش توی ذهن‌تان می‌نشیند:
1121
- <br>«همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار»</p>
1122
- <p>با کنجکاوی اپ را باز می‌کنید. ظاهر ساده و روانی دارد. فعلاً فقط گزینه‌ی «سفر معمولی» فعال است. خبری از امکانات اضافه مثل «سفر دو‌مسیـره»، «توقف در مسیر»، «اکوپلاس» یا «موتورسوار» نیست — اما خب، رهیار تازه‌کار است و قرار است توسعه پیدا کند!.</p>
1123
- <p>مبدأ و مقصد را انتخاب می‌کنید و با قیمت مواجه می‌شوید.</p>
1124
- <p>با کلیک روی «ادامه»، اطلاعات سفر را مشاهده کنید 👇🏻</p>
1125
- </div>
1126
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1127
 
1128
- if st.button("ادامه", key="continue_btn", type="primary"):
1129
- st.session_state.current_page = "map_view"
1130
- st.rerun()
1131
- ```
1132
-
1133
- def map\_view():
1134
- col1, col2 = st.columns(\[2, 4]) # افزایش نسبت ستون اول
1135
- with col1:
1136
- st.markdown('<div style="padding-right: 25px;">', unsafe\_allow\_html=True)
1137
- try:
1138
- st.image("rahyar.png", width=80)
1139
- except:
1140
- st.image("[https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO](https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO)", width=80)
1141
- st.markdown('</div>', unsafe\_allow\_html=True)
1142
- with col2:
1143
- st.markdown(""" <h2 class="rahyar-title">رهیار 🚖</h2> <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1144
- """, unsafe\_allow\_html=True)
1145
-
1146
- ```
1147
- st.markdown("""
1148
- <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1149
- <p>مسیر سفر شما به صورت حدودی، از جنوب به غرب تهران است.</p>
1150
- <p> با توجه به اطلاعاتی که بعد از نقشه دریافت میکنید، تصمیم بگیرید که سفر را می‌پذیرید را رد می‌کنید.</p>
1151
- <p>سپس با کلیک بر دکمه مربوطه به بخش بعدی بروید.</p>
1152
- </div>
1153
- """, unsafe_allow_html=True)
1154
- st.markdown("### مسیر سفر شما")
1155
- folium_static(create_ride_map(), width=1000 if st.session_state.is_desktop else 800,
1156
- height=500 if st.session_state.is_desktop else 400)
1157
-
1158
- # قیمت
1159
- st.markdown(f"""
1160
- <div class="price-container">
1161
- <div style="display: flex; justify-content: space-between; align-items: center;">
1162
- <span>رهیار <span style="background-color: #e6e6fa; color: #6a0dad; padding: 2px 8px; border-radius: 12px; font-size: 14px;">معمولی</span></span>
1163
- <span class="rahyar-price">{st.session_state.price:,} تومان</span>
1164
  </div>
1165
- </div>
1166
- """, unsafe_allow_html=True)
 
 
 
1167
 
1168
- show_explanation(st.session_state.scenario_type)
 
 
 
 
 
 
 
 
 
 
 
 
 
1169
 
1170
- # دکمه‌ها
1171
- col1, col2 = st.columns(2)
1172
- with col1:
1173
- if st.button("درخواست سفر", key="accept_btn", use_container_width=True):
1174
- st.session_state.price_accepted = 1
1175
- st.session_state.current_page = "attention_check1"
1176
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1177
 
1178
- with col2:
1179
- if st.button("رد سفر", key="reject_btn", use_container_width=True):
1180
- st.session_state.price_accepted = 0
1181
- st.session_state.current_page = "attention_check1"
1182
- st.rerun()
1183
- ```
1184
-
1185
- def attention\_check1():
1186
- """سوال توجه اول با دکمه سبز کاملاً عملی"""
1187
- \# 1. تزریق استایل‌های سفارشی
1188
- st.markdown(""" <style>
1189
- /\* مخفی کردن دکمه پیشفرض Streamlit \*/
1190
- div\[data-testid="stButton"] > button\[kind="primary"] {
1191
- display: none !important;
1192
- } </style>
1193
- """, unsafe\_allow\_html=True)
1194
-
1195
- ```
1196
- st.markdown("### سوال")
1197
-
1198
- answer = st.radio(
1199
- "رنگ لوگو و تم رنگی اپلیکیشن رهیار چگونه بود؟",
1200
- ["قرمز", "سبز", "بنفش", "آبی", "فراموش کردم"],
1201
- index=None,
1202
- key="att1_radio"
1203
- )
1204
-
1205
- # کامپوننت HTML با دکمه سبز اصلی
1206
- st.components.v1.html(f"""
1207
- <script>
1208
- function handleClick() {{
1209
- // فعال کردن دکمه مخفی Streamlit
1210
- parent.document.querySelector('div[data-testid="stButton"] > button[kind="primary"]').click();
1211
- }}
1212
- </script>
1213
-
1214
- <button onclick="handleClick()"
1215
- style="
1216
- background-color: #28a745 !important;
1217
- color: white !important;
1218
- border: none !important;
1219
- border-radius: 8px !important;
1220
- padding: 10px 20px !important;
1221
- font-weight: bold !important;
1222
- font-family: 'Vazir', sans-serif !important;
1223
- cursor: pointer !important;
1224
- margin-left: auto; /* این خط برای راست‌چین کردن مهم است */
1225
- transition: all 0.3s ease !important;
1226
- float: right;
1227
- margin-left: 15px;
1228
- "
1229
- onmouseover="this.style.backgroundColor='#218838'; this.style.boxShadow='0 4px 8px rgba(0,0,0,0.15)';"
1230
- onmouseout="this.style.backgroundColor='#28a745'; this.style.boxShadow='0 2px 5px rgba(0,0,0,0.1)';">
1231
- ادامه
1232
- </button>
1233
- """, height=70)
1234
-
1235
-
1236
- # 3. منطق اصلی دکمه (مخفی)
1237
- if st.button("ادامه", key="att1_real_btn", type="primary"):
1238
- if answer:
1239
- st.session_state.attention_check1 = answer
1240
- st.session_state.current_page = "random_likert_questions"
1241
- st.rerun()
1242
- else:
1243
- st.warning("لطفاً یک گزینه را انتخاب کنید")
1244
- ```
1245
-
1246
- def random\_likert\_questions():
1247
- """نمایش سوالات لیکرت با دکمه‌های دایره‌ای"""
1248
- question\_groups = \[
1249
- {
1250
- "title": "عدالت توزیعی",
1251
- "key": "distributive",
1252
- "questions": \[
1253
- {
1254
- "key": "distributive\_1",
1255
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1256
- "scale": 7,
1257
- "labels": \["کاملاً نامنصفانه", "کاملاً منصفانه"]
1258
- },
1259
- {
1260
- "key": "distributive\_2",
1261
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1262
- "scale": 7,
1263
- "labels": \["کاملاً غیرمعقول", "کاملاً معقول"]
1264
- },
1265
- {
1266
- "key": "distributive\_3",
1267
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1268
- "scale": 7,
1269
- "labels": \["کاملاً غیرقابل قبول", "کاملاً قابل قبول"]
1270
- }
1271
- ]
1272
- },
1273
- {
1274
- "title": "سوال توجه",
1275
- "key": "attention\_check",
1276
- "questions": \[
1277
- {"key": "attention\_check2", "question": "تا چه مقدار با دقت به سوالات پاسخ می‌دهید؟", "scale": 7, "labels": \["خیلی کم", "خیلی زیاد"]}
1278
- ]
1279
- },
1280
- {
1281
- "title": "عدالت رویه‌ای",
1282
- "key": "procedural",
1283
- "questions": \[
1284
- {"key": "procedural\_1", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است", "scale": 7, "labels": \["کاملاً مخالفم", "کاملاً موافقم"]},
1285
- {"key": "procedural\_2", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است", "scale": 7, "labels": \["کاملاً مخالفم", "کاملاً موافقم"]},
1286
- {"key": "procedural\_3", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم معقول است", "scale": 7, "labels": \["کاملاً مخالفم", "کاملاً موافقم"]}
1287
- ]
1288
- },
1289
- {
1290
- "title": "عدالت اطلاعاتی",
1291
- "key": "informational",
1292
- "questions": \[
1293
- {"key": "informational\_1", "question": "تا چه حد رهیار دلایل تعیین قیمت را به صورت صادقانه توضیح داد؟", "scale": 7, "labels": \["هیچ", "خیلی زیاد"]},
1294
- {"key": "informational\_2", "question": "تا چه حد رهیار عوامل مؤثر بر تعیین قیمت را به طور کامل شرح داد؟", "scale": 7, "labels": \["هیچ", "خیلی زیاد"]},
1295
- {"key": "informational\_3", "question": "تا چه حد دلایل ارائه‌شده توسط رهیار برای تعیین قیمت منطقی و قابل قبول بود؟", "scale": 7, "labels": \["هیچ", "خیلی زیاد"]},
1296
- {"key": "informational\_4", "question": "تا چه حد توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد؟", "scale": 7, "labels": \["هیچ", "خیلی زیاد"]},
1297
- {"key": "informational\_5", "question": "تا چه حد توضیحات رهیار درباره تعیین قیمت، متناسب با شرایط سفر شما بود؟", "scale": 7, "labels": \["هیچ", "خیلی زیاد"]}
1298
- ]
1299
- }
1300
- ]
1301
-
1302
- ```
1303
- # مقداردهی اولیه
1304
- if 'current_likert_group' not in st.session_state:
1305
- st.session_state.current_likert_group = 0
1306
-
1307
- current_group = question_groups[st.session_state.current_likert_group]
1308
-
1309
- st.markdown(f"### {current_group['title']}")
1310
-
1311
- # نمایش تمام سوالات این گروه
1312
- for question in current_group['questions']:
1313
- answer = enhanced_likert_scale(question)
1314
- st.session_state.answers[question["key"]] = answer
1315
-
1316
- # دکمه ادامه/اتمام
1317
- button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups)-1 else "اتمام پرسشنامه"
1318
-
1319
- if st.button(button_label):
1320
- # بررسی آیا همه سوالات این گروه پاسخ داده شده‌اند
1321
- all_answered = all(
1322
- question["key"] in st.session_state.answers and
1323
- st.session_state.answers[question["key"]] is not None
1324
- for question in current_group['questions']
1325
  )
 
 
 
 
 
 
 
 
 
1326
 
1327
- if not all_answered:
1328
- st.error("لطفاً به تمام سوالات این بخش پاسخ دهید قبل از ادامه")
1329
- else:
1330
- if st.session_state.current_likert_group < len(question_groups) - 1:
1331
- st.session_state.current_likert_group += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
  st.rerun()
1333
  else:
1334
- st.session_state.current_page = "explanation_questions"
1335
- st.rerun()
1336
- ```
1337
-
1338
- def explanation\_questions():
1339
- """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1340
- st.markdown("### 📋 سوالات تکمیلی")
1341
-
1342
- ```
1343
- # لیست سوالات به ترتیب نمایش
1344
- questions = [
1345
- {
1346
- "key": "trust",
1347
- "label": "آیا شما به تصمیم‌گیری‌هایی که توسط هوش مصنوعی انجام می‌شود اعتماد دارید؟",
1348
- "options": ["بله", "خیر", "نظری ندارم"],
1349
- "required": True
1350
- },
1351
- {
1352
- "key": "pricing_method",
1353
- "label": "به نظر شما پلتفرم قیمت را چگونه تعیین می‌کند؟",
1354
- "options": [
1355
- "به صورت دستی توسط تیم پلتفرم",
1356
- "به صورت خودکار توسط هوش مصنوعی و الگوریتم‌ها",
1357
- رکیبی از هر دو روش",
1358
- "نظری ندارم"
1359
- ],
1360
- "required": True
1361
- },
1362
- {
1363
- "key": "price_increase",
1364
- "label": "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
1365
- "options": ["بله", "خیر", "مطمئن نیستم"],
1366
- "required": True
1367
- },
1368
- {
1369
- "key": "explanation_received",
1370
- "label": "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
1371
- "options": ["بله", "خیر"],
1372
- "required": True
1373
- },
1374
- {
1375
- "key": "explanation_type",
1376
- "label": "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
1377
- "options": [
1378
- "بر اساس عواملی که در قیمت‌گذاری لحاظ شده‌اند",
1379
- "شامل سناریوهای جایگزین که می‌توانستند قیمت متفاوتی ایجاد کنند",
1380
- "توضیحی دریافت نکردم"
1381
- ],
1382
- "required": False,
1383
- "condition": lambda: st.session_state.get("explanation_received") == "بله"
1384
- }
1385
- ]
1386
-
1387
- # مقداردهی اولیه step اگر وجود ندارد
1388
- if "explanation_step" not in st.session_state:
1389
- st.session_state.explanation_step = 0
1390
-
1391
- # اگر همه سوالات پاسخ داده شده‌اند، به صفحه بعدی برو
1392
- if st.session_state.explanation_step >= len(questions):
1393
- st.session_state.current_page = "demographic"
1394
- st.rerun()
1395
- return
1396
-
1397
- # دریافت سوال جاری
1398
- current_q = questions[st.session_state.explanation_step]
1399
-
1400
- # بررسی شرط نمایش برای سوالات اختیاری
1401
- if "condition" in current_q and not current_q["condition"]():
1402
- st.session_state[current_q["key"]] = "N/A"
1403
- st.session_state.explanation_step += 1
1404
- st.rerun()
1405
- return
1406
-
1407
- # نمایش سوال جاری
1408
- answer = st.radio(
1409
- current_q["label"],
1410
- current_q["options"],
1411
- index=None,
1412
- key=f"explanation_q_{current_q['key']}"
1413
- )
1414
-
1415
- # دکمه ادامه
1416
- if st.button("ادامه", key=f"continue_{current_q['key']}"):
1417
- if answer is None and current_q["required"]:
1418
- st.warning("لطفاً یک گزینه را انتخاب کنید")
1419
- else:
1420
- # ذخیره پاسخ
1421
- st.session_state[current_q["key"]] = answer if answer is not None else "N/A"
1422
 
1423
- # افزایش شماره مرحله
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1424
  st.session_state.explanation_step += 1
1425
-
1426
- # رفرش صفحه برای نمایش سوال بعدی
1427
  st.rerun()
1428
-
1429
- ```
1430
-
1431
- def demographic\_form():
1432
- """فرم اطلاعات دموگرافیک"""
1433
- st.markdown("### 📝 اطلاعات دموگرافیک")
1434
- st.markdown(""" <div> <p>لطفاً اطلاعات زیر را صادقانه و به دقت وارد نمایید.</p> <p>همانطور که گفته شد،این اطلاعات کاملاً محرمانه نزد محقق خواهد ماند و در جهت اهداف پژوهشی استفاده خواهد شد.</p> </div>
1435
- """, unsafe\_allow\_html=True)
1436
-
1437
- ```
1438
- with st.form("demographic_form"):
1439
- age = st.number_input("سن", min_value=18, max_value=100, value=None, placeholder="سن خود را وارد کنید")
1440
- gender = st.selectbox("جنسیت", ["", "مرد", "زن", "سایر"], index=0)
1441
- education = st.selectbox("تحصیلات", ["", "دیپلم", یسانس", "فوق لیسانس", "دکترا"], index=0)
1442
- city = st.selectbox("لطفاً استان محل سکونت خود را انتخاب بفرمایید.",
1443
- ["", "آذربایجان شرقی", "آذربایجان غربی", "اردبیل", "اصفهان", "البرز", "ایلام",
1444
- "بوشهر", "تهران", "چهارمحال و بختیاری", "خراسان جنوبی", "خراسان رضوی", "خراسان شمالی",
1445
- "خوزستان", "زنجان", "سمنان", "سیستان و بلوچستان", "فارس", "قزوین", "قم", "کردستان",
1446
- "کرمان", "کرمانشاه", "کهگیلویه و بویراحمد", "گلستان", "گیلان", "لرستان", "مازندران",
1447
- "مرکزی", "هرمزگان", "همدان", "یزد"], index=0)
1448
- related_education_job = st.selectbox("رشته تحصیلی/شغل شما در کدام‌یک از دسته‌های زیر قرار دارد؟",
1449
- ["", "مهندسی", "درمانی", "فرهنگی", "مدیریتی (مالی)",
1450
- "مدیریتی (بازاریابی)", "مدیریتی (سایر)", "روانشناسی",
1451
- "اقتصادی", "حقوقی", "هنری", "ورزشی", "زبان", "غیره"], index=0)
1452
- ride_frequency = st.selectbox("دفعات استفاده از سرویس‌های اشتراک سفر در ماه",
1453
- ["", "هیچوقت", "کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"], index=0)
1454
-
1455
- submitted = st.form_submit_button("ادامه")
1456
- if submitted:
1457
- if not all([age, gender, education, city, related_education_job, ride_frequency]):
1458
- st.error("لطفاً تمام فیلدها را پر کنید")
1459
  else:
1460
- st.session_state.demographic_data = {
1461
- "age": age,
1462
- "gender": gender,
1463
- "education": education,
1464
- "city": city,
1465
- "ride_frequency": ride_frequency,
1466
- "related_education_job": related_education_job
1467
- }
1468
- st.session_state.current_page = "contact"
1469
  st.rerun()
1470
- ```
1471
-
1472
- def user\_contact():
1473
- """راه ارتباطی ساده"""
1474
- st.markdown(""" <div style="text-align: center; margin-bottom: 30px;"> <h3>📩 راه ارتباطی شما (اختیاری)</h3> <p>در صورت تمایل به شرکت در قرعه‌کشی می‌توانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:</p> </div>
1475
- """, unsafe\_allow\_html=True)
1476
-
1477
- ```
1478
- contact_info = st.text_input(
1479
- "راه ارتباطی (اختیاری)",
1480
- placeholder="مثال: @username یا 09123456789 یا example@email.com",
1481
- key="user_contact_input"
1482
- )
1483
-
1484
- if st.button("ثبت پاسخ‌ها", type="primary", key="submit_explanation"):
1485
- st.session_state.user_contact = contact_info
1486
- end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1487
- start_time = datetime.strptime(st.session_state.start_time, "%Y-%m-%d %H:%M:%S")
1488
- completion_time = (datetime.now() - start_time).total_seconds()
1489
 
1490
- save_data = {
1491
- "start_time": st.session_state.start_time,
1492
- "end_time": end_time,
1493
- "completion_time": completion_time,
1494
- "scenario_type": st.session_state.scenario_type,
1495
- "price": st.session_state.price,
1496
- "user_contact": st.session_state.get("user_contact", ""),
1497
- "price_accepted": st.session_state.get("price_accepted", 0),
1498
- "attention_check1": st.session_state.get("attention_check1", None),
1499
- "trust": st.session_state.trust,
1500
- "pricing_method": st.session_state.pricing_method,
1501
- "price_increase": st.session_state.price_increase,
1502
- "explanation_received": st.session_state.explanation_received,
1503
- "explanation_type": st.session_state.get("explanation_type", "N/A"),
1504
- **st.session_state.demographic_data,
1505
- **st.session_state.answers # اضافه کردن تمام پاسخ‌های لیکرت
1506
- }
 
 
 
 
 
 
 
 
 
1507
 
1508
- if save_to_sheet(save_data):
1509
- st.session_state.current_page = "thank_you"
1510
- st.rerun()
1511
- else:
1512
- st.error("خطا در ذخیره‌سازی داده‌ها. لطفاً دوباره تلاش کنید.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1513
 
 
 
 
 
 
1514
 
1515
- ```
1516
-
1517
- def thank\_you\_page():
1518
- """صفحه تشکر"""
1519
- st.success("""
1520
- پاسخ‌های شما با موفقیت ثبت شد.
1521
- سپاسگزاریم که وقت ارزشمند خود را به این پژوهش اختصاص دادید.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1522
 
1523
- ```
1524
- در صورت وجود هرگونه سوال، ابهام یا پیشنهاد می‌توانید با محقق تماس بگیرید:
 
 
 
1525
 
1526
- ✉ ایمیل: maryam.ilka2000@gmail.com
1527
- """)
1528
- st.balloons()
1529
- ```
1530
 
1531
- # ========== مدیریت وضعیت و صفحه‌بندی ==========
 
 
1532
 
1533
- def main():
1534
- \# تشخیص دستگاه
1535
- user\_agent = st.query\_params.get("user\_agent", \[""])\[0]
1536
- st.session\_state.is\_desktop = "mobile" not in user\_agent.lower()
1537
-
1538
- ```
1539
- if st.session_state.is_desktop:
1540
- # اطمینان از نمایش همان حالت موبایل برای همه دستگاه‌ها
1541
- st.session_state.is_desktop = False
1542
-
1543
- if 'answers' not in st.session_state:
1544
- st.session_state.answers = {}
1545
-
1546
- if 'current_page' not in st.session_state:
1547
- st.session_state.current_page = "welcome"
1548
- st.session_state.scenario_type = random.choice(["control", "input", "counterfactual"])
1549
- st.session_state.price = 200000
1550
- st.session_state.user_contact = None
1551
- st.session_state.demographic_data = None
1552
- st.session_state.price_accepted = 0
1553
- st.session_state.attention_check1 = None
1554
 
1555
- pages = {
1556
- "welcome": welcome_page,
1557
- "scenario_explanation": scenario_explanation,
1558
- "map_view": map_view,
1559
- "attention_check1": attention_check1,
1560
- "random_likert_questions": random_likert_questions,
1561
- "explanation_questions": explanation_questions,
1562
- "demographic": demographic_form, # دموگرافیک قبل از کانتکت
1563
- "contact": user_contact, # کانتکت در انتها
1564
- "thank_you": thank_you_page
1565
- }
1566
-
1567
- pages[st.session_state.current_page]()
1568
- ```
1569
-
1570
- if **name** == "**main**":
1571
- main()
 
 
 
 
 
 
 
 
 
11
  import time
12
 
13
  # تنظیمات اولیهه
14
+ st.set_page_config(layout="wide", page_title="راهیار - تحلیل انصاف قیمتی", page_icon="🚖")
 
15
 
16
  # ========== تنظیمات دیتا ==========
17
+ SHEET_ID = "1mmdWAyOCYq4yXMgP53Duq712AnlqZWLkfIo76JqM7wM"
18
+ SHEET_NAME = "Condition1"
 
19
 
20
  # ========== استایل‌های سفارشی یکپارچه ==========
 
21
  st.markdown("""
 
22
  <style>
23
  /* تنظیمات کلی استایل‌ها */
24
  @font-face {
 
735
  line-height: 1.8 !important;
736
  }
737
  </style>
738
+ """, unsafe_allow_html=True)
 
739
 
740
  # ========== توابع اصلی ==========
741
 
742
+ def enhanced_likert_scale(question_data):
743
+ """لیکرت اسکیل با خط و نقاط - کاملاً واکنش‌گرا"""
744
+ question = question_data["question"]
745
+ key = question_data["key"]
746
+ scale = question_data["scale"]
747
+ labels = question_data.get("labels", \["کاملاً مخالفم", "کاملاً موافقم"])
748
+
749
+ ```
750
+ if key not in st.session_state:
751
+ st.session_state[key] = None
752
+
753
+ # نمایش سوال
754
+ st.markdown(f"<div style='text-align:center; font-weight:bold; margin-bottom:15px;'>{question}</div>",
755
+ unsafe_allow_html=True)
756
+
757
+ # ایجاد خط و نقاط با HTML/CSS
758
+ scale_html = f"""
759
+ <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  .likert-line {{
761
+ width: 80%;
762
+ height: 2px;
763
+ background: #6a0dad;
764
+ margin: 0 auto;
765
+ position: relative;
766
+ display: flex;
767
+ justify-content: space-between;
768
+ direction: rtl; /* تغییر جهت از راست به چپ */
769
+ }}
770
+ .likert-dot {{
771
+ width: 18px; /* اندازه دکمه‌ها کوچک‌تر شد */
772
+ height: 18px; /* اندازه دکمه‌ها کوچک‌تر شد */
773
+ border-radius: 50%;
774
+ background: white;
775
+ border: 2px solid #6a0dad;
776
+ position: relative;
777
+ top: -9px;
778
+ cursor: pointer;
779
+ display: flex;
780
+ align-items: center;
781
+ justify-content: center;
782
+ }}
783
+ .likert-dot.selected {{
784
+ background: #6a0dad;
785
  }}
786
  .likert-labels {{
787
+ width: 80%;
788
+ margin: 5px auto 20px;
789
+ display: flex;
790
+ justify-content: space-between;
791
+ direction: rtl; /* جهت لیبل‌ها باید از راست به چپ باشد */
792
  }}
793
+ .likert-value {{
794
+ text-align: center;
795
+ margin-top: 10px;
796
+ color: #6a0dad;
797
+ font-weight: bold;
798
+ }}
799
+ /* مخفی کردن دکمه‌ها با اندازه کوچک */
800
+ .likert-button {{
801
+ visibility: hidden;
802
+ width: 12px;
803
+ height: 12px;
804
+ margin: 2px; /* فاصله کم بین دکمه‌ها */
805
+ }}
806
+ @media (max-width: 768px) {{
807
+ .likert-line {{
808
+ width: 90%;
809
+ }}
810
+ .likert-labels {{
811
+ width: 90%;
812
+ }}
813
+ }}
814
+ </style>
815
+
816
+ <div class='likert-container'>
817
+ <div class='likert-labels'>
818
+ <span>{labels[1]}</span>
819
+ <span>{labels[0]}</span>
820
+ </div>
821
+ <div class='likert-line'>
822
+ """
 
 
 
 
 
 
 
 
 
823
 
824
+ # اضافه کردن نقاط
825
+ for i in range(scale):
 
 
826
  value = scale - i
827
+ is_selected = st.session_state.get(key) == value
828
+ scale_html += f"<div class='likert-dot {'selected' if is_selected else ''}' onclick='setLikertValue({value})'></div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
 
830
+ scale_html += "</div>"
831
+
832
+ # نمایش مقدار انتخاب شده
833
+ if st.session_state.get(key):
834
+ scale_html += f"<div class='likert-value'>پاسخ شما: {st.session_state[key]}</div>"
835
+
836
+ scale_html += "</div>"
837
+
838
+ # تزریق JavaScript برای مدیریت کلیک
839
+ components.html(scale_html + """
840
+ <script>
841
+ function setLikertValue(value) {
842
+ const streamlitDoc = window.parent.document;
843
+ const buttons = streamlitDoc.querySelectorAll('button[data-testid="stButton"]'); // جستجوی دکمه‌ها در DOM
844
+ buttons.forEach(btn => {
845
+ if (btn.textContent.trim() === String(value)) {
846
+ btn.click(); // کلیک روی دکمه مورد نظر برای انتخاب مقدار
847
+ }
848
+ });
849
+ }
850
+ </script>
851
+ """, height=80)
852
+
853
+ # دکمه‌های واقعی (مخفی و کوچک)
854
+ btn_cols = st.columns(scale)
855
+ for i in range(scale):
856
+ with btn_cols[i]:
857
+ value = scale - i
858
+ if st.button(
859
+ str(value),
860
+ key=f"{key}_btn_{value}",
861
+ type="primary" if st.session_state.get(key) == value else "secondary",
862
+ help="این دکمه‌ها مخفی هستند" # پیام راهنما
863
+ ):
864
+ st.session_state[key] = value
865
+ st.rerun()
866
+
867
+ return st.session_state.get(key)
868
 
869
+ def create_ride_map():
870
+ """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
871
+ # نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
872
+ south_tehran = [35.65, 51.38] # منطقه عمومی جنوب تهران
873
+ west_tehran = [35.72, 51.31] # منطقه عمومی غرب تهران
874
+
875
+ # مرکز نقشه بین دو نقطه
876
+ m = folium.Map(location=[35.685, 51.315], zoom_start=11)
877
+
878
+ # ایجاد دایره برای مبدأ (جنوب تهران)
879
+ folium.Circle(
880
+ location=south_tehran,
881
+ radius=2500, # شعاع 1.5 کیلومتر
882
+ popup="<b>مبدأ:</b> منطقه جنوب تهران",
883
+ color="#6a0dad",
884
+ fill=True,
885
+ fill_color="#6a0dad",
886
+ fill_opacity=0.2,
887
+ weight=2
888
+ ).add_to(m)
889
+
890
+ # ایجاد دایره برای مقصد (غرب تهران)
891
+ folium.Circle(
892
+ location=west_tehran,
893
+ radius=2500, # شعاع 1.5 کیلومتر
894
+ popup="<b>مقصد:</b> منطقه غرب تهران",
895
+ color="#ff0000",
896
+ fill=True,
897
+ fill_color="#ff0000",
898
+ fill_opacity=0.2,
899
+ weight=2
900
+ ).add_to(m)
901
+
902
+ # خط ارتباطی بین دو منطقه
903
+ folium.PolyLine(
904
+ [south_tehran, west_tehran],
905
+ color="#6a0dad",
906
+ weight=3,
907
+ opacity=0.7,
908
+ dash_array='5, 5' # خط چین
909
+ ).add_to(m)
910
+
911
+ return m
912
+
913
+ def show_explanation(exp_type):
914
+ """نمایش توضیحات قیمت"""
915
+ explanations = {
916
+ "input": [
917
+ " سطح تقاضا در منطقه: زیاد (+)",
918
+ " تعداد رانندگان فعال: کم (+)",
919
+ " زمان روز: ساعت اوج ترافیک (+)",
920
+ "شرایط جوی: هوای بارانی (++)"
921
+ ],
922
+ "counterfactual": [
923
+ "اگر این سفر را 1 ساعت دیرتر درخواست کنید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیش‌تر، زمان بهتر روز و شرایط جوی بهتر، احتمالاً قیمت حدوداً 40٪ کمتر (120 هزار تومان) خواهد بود.",
924
+ ]
925
+ }
926
+
927
+ if exp_type != "control":
928
+ if exp_type == "input":
929
+ st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
930
+ st.markdown("""
931
+ <div style="direction: rtl; text-align: right;">
932
+ <span style="font-size: 0.9em; color: #666;">(تعداد علامت + نشان دهنده شدت اثر عامل بر قیمت است)</span>
933
+ </div>
934
+ """, unsafe_allow_html=True)
935
+ for item in explanations.get(exp_type, []):
936
+ st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
937
 
938
+ elif exp_type == "counterfactual":
939
+ st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
940
+ for item in explanations.get(exp_type, []):
941
+ st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
942
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
943
 
944
+
945
 
946
+ def create_likert_question(question, key, scale_type="5point"):
947
+ """نمایش سوال لیکرت با اسلایدر نقطهای"""
948
+ left_label = "کاملاً مخالفم" if scale_type == "7point" else "کاملاً مخالفم"
949
+ right_label = "کاملاً موافقم" if scale_type == "7point" else "کاملاً موافقم"
950
+
951
+ st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
952
+
953
+ max_value = 7 if scale_type == "7point" else 5
954
+
955
+ # اضافه کردن attribute برای انتخاب استایل مناسب
956
+ slider_container = st.empty()
957
+ with slider_container:
958
+ st.markdown(f'<div data-testid="stSlider" data-discrete="{"seven-point" if scale_type=="7point" else "five-point"}">', unsafe_allow_html=True)
959
+ value = st.slider(
960
+ "",
961
+ min_value=1,
962
+ max_value=max_value,
963
+ value=(max_value+1)//2,
964
+ step=1,
965
+ key=f"slider_{key}"
966
+ )
967
+ st.markdown('</div>', unsafe_allow_html=True)
968
+
969
+ st.markdown(
970
+ f"""
971
+ <div class="slider-labels">
972
+ <span>{left_label}</span>
973
+ <span>{right_label}</span>
974
+ </div>
975
+ <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
976
+ پاسخ شما: {value} ({'کاملاً مخالفم' if value==1 else 'کاملاً موافقم' if value==max_value else 'خنثی' if value==((max_value+1)//2) else 'موافقم' if value>((max_value+1)//2) else 'مخالفم'})
977
+ </p>
978
+ """,
979
+ unsafe_allow_html=True
980
  )
981
+ return value
982
+
983
+ # ========== توابع مدیریت داده‌ها ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
+ def get_credentials():
986
+ """دریافت اعتبارنامه از Secrets"""
987
+ try:
988
+ service_account_json = os.environ.get('GCP_SERVICE_ACCOUNT')
989
+ if not service_account_json:
990
+ st.error("مقدار GCP_SERVICE_ACCOUNT در محیط یافت نشد")
991
+ return None
992
+
993
+ service_account_info = json.loads(service_account_json)
994
+ creds = Credentials.from_service_account_info(
995
+ service_account_info,
996
+ scopes=[
997
+ "https://www.googleapis.com/auth/spreadsheets",
998
+ "https://www.googleapis.com/auth/drive.file"
999
+ ]
1000
+ )
1001
+ return creds
1002
+ except Exception as e:
1003
+ st.error(f"خطا در دریافت اعتبارنامه: {str(e)}")
1004
+ return None
1005
+
1006
+ def save_to_sheet(data):
1007
+ try:
1008
+ creds = get_credentials()
1009
+ if not creds:
1010
+ return False
1011
+
1012
+ client = gspread.authorize(creds)
1013
+ spreadsheet = client.open_by_key(SHEET_ID)
1014
+ worksheet = spreadsheet.worksheet(SHEET_NAME)
1015
 
1016
+ row_data = [
1017
+ data.get("start_time", ""), # زمان شروع
1018
+ data.get("end_time", ""), # زمان پایان
1019
+ data.get("completion_time", ""), # مدت زمان تکمیل (ثانیه)
1020
+ data.get("scenario_type", ""),
1021
+ data.get("price", ""),
1022
+ data.get("age", ""),
1023
+ data.get("gender", ""),
1024
+ data.get("education", ""),
1025
+ data.get("ride_frequency", ""),
1026
+ data.get("related_education_job",""),
1027
+ data.get("city",""),
1028
+ data.get("user_contact", ""),
1029
+ data.get("price_accepted", ""),
1030
+
1031
+ # سوالات توجه
1032
+ data.get("attention_check1", ""),
1033
+ data.get("attention_check2", ""),
1034
+
1035
 
1036
+ # سوالات distributive (7 گزینه‌ای)
1037
+ data.get("distributive_1", ""),
1038
+ data.get("distributive_2", ""),
1039
+ data.get("distributive_3", ""),
1040
+
1041
+ # سوالات procedural (7 گزینه‌ای)
1042
+ data.get("procedural_1", ""),
1043
+ data.get("procedural_2", ""),
1044
+ data.get("procedural_3", ""),
1045
+
1046
+ # سوالات informational (5 گزینه‌ای)
1047
+ data.get("informational_1", ""),
1048
+ data.get("informational_2", ""),
1049
+ data.get("informational_3", ""),
1050
+ data.get("informational_4", ""),
1051
+ data.get("informational_5", ""),
1052
+
1053
+ # سوالات manipulation
1054
+ data.get ("trust", ""),
1055
+ data.get("pricing_method", ""),
1056
+ data.get("price_increase", ""),
1057
+ data.get("explanation_received", ""),
1058
+ data.get("explanation_type", "")
1059
+ ]
1060
 
1061
+ worksheet.append_row(row_data)
1062
+ return True
 
 
 
 
1063
 
1064
+ except Exception as e:
1065
+ st.error(f"خطا در ذخیره‌سازی: {str(e)}")
1066
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
1067
 
 
1068
 
1069
+ # ========== بخش‌های فرم ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1070
 
1071
+ def welcome_page():
1072
+ """صفحه خوشامدگویی"""
1073
+ st.markdown("""
1074
+ <div style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;">
1075
+ <div style="text-align: center;">
1076
+ <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png"
1077
+ alt="لوگو دانشگاه شریف"
1078
+ style="width: 200px; height: auto; margin-bottom: 10px;">
1079
+ </div>
1080
+ <div style="flex: 1;">
1081
+ <h3>به پرسشنامه ما خوش آمدید 🌟</h3>
1082
+ <p>با سلام و احترام،</p>
1083
+ <p>از شما دعوت می‌شود در یک پژوهش دانشگاهی شرکت کنید که در قالب پایان‌نامه کارشناسی‌ارشد در دانشگاه صنعتی شریف انجام می‌شود. این تحقیق به بررسی ادراک مصرف‌کنندگان از انصاف در قیمت‌گذاریِ اپلیکیشن‌های تاکسی اینترنتی (مانند اسنپ و تپسی 🚖) می‌پردازد.</p>
1084
+ <p>شرکت در این مطالعه کاملاً داوطلبانه است؛ پاسخ دقیقی برای سوالات وجود ندارد و پاسخ‌های صادقانه شما فقط برای پیش‌برد اهداف علمی تحلیل خواهد شد.</p>
1085
+ <p>پر کردن این پرسشنامه کمتر از 5 دقیقه وقت شما را می‌گیرد و پاسخ‌های ارزشمند شما کمک شایانی به ارتقای دانش علمی خواهد کرد. پیشاپیش از مشارکت شما صمیمانه سپاسگزاریم 🙏</p>
1086
+ <p>برای آغاز پرسشنامه، لطفاً روی دکمه زیر کلیک کنید 👇🏻</p>
1087
+ </div>
1088
+ </div>
1089
+ </div>
1090
+ """, unsafe_allow_html=True)
1091
+
1092
+ if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
1093
+ st.session_state.current_page = "scenario_explanation"
1094
+ st.session_state.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1095
+ st.rerun()
1096
 
1097
+
1098
+ def scenario_explanation():
1099
+ """توضیح سناریو"""
1100
+ col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
1101
+ with col1:
1102
+ st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1103
+ try:
1104
+ st.image("rahyar.png", width=80)
1105
+ except:
1106
+ st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1107
+ st.markdown('</div>', unsafe_allow_html=True)
1108
+ with col2:
1109
+ st.markdown("""
1110
+ <h2 class="rahyar-title">رهیار 🚖</h2>
1111
+ <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1112
+ """, unsafe_allow_html=True)
1113
+
1114
+ st.markdown("### سناریوی تحقیق")
1115
+
1116
+ st.markdown("""
1117
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1118
+ <p>فرض کنید در روزی از روزها شما مهمان یکی از اقوام‌تان در جنوب تهران هستید. قصد دارید برای خریدی به غرب تهران بروید...</p>
1119
+ <p> گوشی‌تان را از کیف درمی‌آورید. چشم‌تان به آیکون جدیدی می‌افتد؛ اپلیکیشنی به نام <strong>رهیار</strong> — نه اسنپ است و نه تپسی، اما خیلی شبیه آن‌هاست. رنگ بنفش جذابی دارد و شعارش توی ذهن‌تان می‌نشیند:
1120
+ <br>«همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار»</p>
1121
+ <p>با کنجکاوی اپ را باز می‌کنید. ظاهر ساده و روانی دارد. فعلاً فقط گزینه‌ی «سفر معمولی» فعال است. خبری از امکانات اضافه مثل «سفر دو‌مسیـره»، «توقف در مسیر»، «اکوپلاس» یا «موتورسوار» نیست — اما خب، رهیار تازه‌کار است و قرار است توسعه پیدا کند!.</p>
1122
+ <p>مبدأ و مقصد را انتخاب می‌کنید و با قیمت مواجه می‌شوید.</p>
1123
+ <p>با کلیک روی «ادامه»، اطلاعات سفر را مشاهده کنید 👇🏻</p>
 
 
 
 
 
 
 
 
 
1124
  </div>
1125
+ """, unsafe_allow_html=True)
1126
+
1127
+ if st.button("ادامه", key="continue_btn", type="primary"):
1128
+ st.session_state.current_page = "map_view"
1129
+ st.rerun()
1130
 
1131
+ def map_view():
1132
+ col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
1133
+ with col1:
1134
+ st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1135
+ try:
1136
+ st.image("rahyar.png", width=80)
1137
+ except:
1138
+ st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1139
+ st.markdown('</div>', unsafe_allow_html=True)
1140
+ with col2:
1141
+ st.markdown("""
1142
+ <h2 class="rahyar-title">رهیار 🚖</h2>
1143
+ <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1144
+ """, unsafe_allow_html=True)
1145
 
1146
+ st.markdown("""
1147
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1148
+ <p>مسیر سفر شما به صورت حدودی، از جنوب به غرب تهران است.</p>
1149
+ <p> با توجه به اطلاعاتی که بعد از نقشه دریافت می‌کنید، تصمیم بگیرید که سفر را می‌پذیرید را رد می‌کنید.</p>
1150
+ <p>سپس با کلیک بر دکمه مربوطه به بخش بعدی بروید.</p>
1151
+ </div>
1152
+ """, unsafe_allow_html=True)
1153
+ st.markdown("### مسیر سفر شما")
1154
+ folium_static(create_ride_map(), width=1000 if st.session_state.is_desktop else 800,
1155
+ height=500 if st.session_state.is_desktop else 400)
1156
+
1157
+ # قیمت
1158
+ st.markdown(f"""
1159
+ <div class="price-container">
1160
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1161
+ <span>رهیار <span style="background-color: #e6e6fa; color: #6a0dad; padding: 2px 8px; border-radius: 12px; font-size: 14px;">معمولی</span></span>
1162
+ <span class="rahyar-price">{st.session_state.price:,} تومان</span>
1163
+ </div>
1164
+ </div>
1165
+ """, unsafe_allow_html=True)
1166
+
1167
+ show_explanation(st.session_state.scenario_type)
1168
+
1169
+ # دکمه‌ها
1170
+ col1, col2 = st.columns(2)
1171
+ with col1:
1172
+ if st.button("درخواست سفر", key="accept_btn", use_container_width=True):
1173
+ st.session_state.price_accepted = 1
1174
+ st.session_state.current_page = "attention_check1"
1175
+ st.rerun()
1176
+
1177
+ with col2:
1178
+ if st.button("رد سفر", key="reject_btn", use_container_width=True):
1179
+ st.session_state.price_accepted = 0
1180
+ st.session_state.current_page = "attention_check1"
1181
+ st.rerun()
1182
 
1183
+ def attention_check1():
1184
+ """سوال توجه اول با دکمه سبز کاملاً عملی"""
1185
+ # 1. تزریق استایل‌های سفارشی
1186
+ st.markdown("""
1187
+ <style>
1188
+ /* مخفی کردن دکمه پیشفرض Streamlit */
1189
+ div[data-testid="stButton"] > button[kind="primary"] {
1190
+ display: none !important;
1191
+ }
1192
+ </style>
1193
+ """, unsafe_allow_html=True)
1194
+
1195
+ st.markdown("### سوال")
1196
+
1197
+ answer = st.radio(
1198
+ "رنگ لوگو و تم رنگی اپلیکیشن رهیار چگونه بود؟",
1199
+ ["قرمز", "سبز", "بنفش", "آبی", "فراموش کردم"],
1200
+ index=None,
1201
+ key="att1_radio"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202
  )
1203
+
1204
+ # کامپوننت HTML با دکمه سبز اصلی
1205
+ st.components.v1.html(f"""
1206
+ <script>
1207
+ function handleClick() {{
1208
+ // فعال کردن دکمه مخفی Streamlit
1209
+ parent.document.querySelector('div[data-testid="stButton"] > button[kind="primary"]').click();
1210
+ }}
1211
+ </script>
1212
 
1213
+ <button onclick="handleClick()"
1214
+ style="
1215
+ background-color: #28a745 !important;
1216
+ color: white !important;
1217
+ border: none !important;
1218
+ border-radius: 8px !important;
1219
+ padding: 10px 20px !important;
1220
+ font-weight: bold !important;
1221
+ font-family: 'Vazir', sans-serif !important;
1222
+ cursor: pointer !important;
1223
+ margin-left: auto; /* این خط برای راست‌چین کردن مهم است */
1224
+ transition: all 0.3s ease !important;
1225
+ float: right;
1226
+ margin-left: 15px;
1227
+ "
1228
+ onmouseover="this.style.backgroundColor='#218838'; this.style.boxShadow='0 4px 8px rgba(0,0,0,0.15)';"
1229
+ onmouseout="this.style.backgroundColor='#28a745'; this.style.boxShadow='0 2px 5px rgba(0,0,0,0.1)';">
1230
+ ادامه
1231
+ </button>
1232
+ """, height=70)
1233
+
1234
+
1235
+ # 3. منطق اصلی دکمه (مخفی)
1236
+ if st.button("ادامه", key="att1_real_btn", type="primary"):
1237
+ if answer:
1238
+ st.session_state.attention_check1 = answer
1239
+ st.session_state.current_page = "random_likert_questions"
1240
  st.rerun()
1241
  else:
1242
+ st.warning("لطفاً یک گزینه را انتخاب کنید")
1243
+
1244
+ def random_likert_questions():
1245
+ """نمایش سوالات لیکرت با دکمه‌های دایره‌ای"""
1246
+ question_groups = [
1247
+ {
1248
+ "title": "عدالت توزیعی",
1249
+ "key": "distributive",
1250
+ "questions": [
1251
+ {
1252
+ "key": "distributive_1",
1253
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1254
+ "scale": 7,
1255
+ "labels": ["کاملاً نامنصفانه", املاً منصفانه"]
1256
+ },
1257
+ {
1258
+ "key": "distributive_2",
1259
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1260
+ "scale": 7,
1261
+ "labels": ["کاملاً غیرمعقول", املاً معقول"]
1262
+ },
1263
+ {
1264
+ "key": "distributive_3",
1265
+ "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1266
+ "scale": 7,
1267
+ "labels": ["کاملاً غیرقابل قبول", "کاملاً قابل قبول"]
1268
+ }
1269
+ ]
1270
+ },
1271
+ {
1272
+ "title": "سوال توجه",
1273
+ "key": "attention_check",
1274
+ "questions": [
1275
+ {"key": "attention_check2", "question": "تا چه مقدار با دقت به سوالات پاسخ می‌دهید؟", "scale": 7, "labels": ["خیلی کم", "خیلی زیاد"]}
1276
+ ]
1277
+ },
1278
+ {
1279
+ "title": "عدالت رویه‌ای",
1280
+ "key": "procedural",
1281
+ "questions": [
1282
+ {"key": "procedural_1", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]},
1283
+ {"key": "procedural_2", "question": "فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]},
1284
+ {"key": "procedural_3", "question": رآیند و رویه قیمت‌گذاری پلتفرم معقول است", "scale": 7, "labels": ["کاملاً مخالفم", املاً موافقم"]}
1285
+ ]
1286
+ },
1287
+ {
1288
+ "title": دالت اطلاعاتی",
1289
+ "key": "informational",
1290
+ "questions": [
1291
+ {"key": "informational_1", "question": "تا چه حد رهیار دلایل تعیین قیمت را به صورت صادقانه توضیح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1292
+ {"key": "informational_2", "question": "تا چه حد رهیار عوامل مؤثر بر تعیین قیمت را به طور کامل شرح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1293
+ {"key": "informational_3", "question": "تا چه حد دلایل ارائه‌شده توسط رهیار برای تعیین قیمت منطقی و قابل قبول بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1294
+ {"key": "informational_4", "question": "تا چه حد توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1295
+ {"key": "informational_5", "question": "تا چه حد توضیحات رهیار درباره تعیین قیمت، متناسب با شرایط سفر شما بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]}
1296
+ ]
1297
+ }
1298
+ ]
1299
+
1300
+ # مقداردهی اولیه
1301
+ if 'current_likert_group' not in st.session_state:
1302
+ st.session_state.current_likert_group = 0
1303
+
1304
+ current_group = question_groups[st.session_state.current_likert_group]
1305
+
1306
+ st.markdown(f"### {current_group['title']}")
1307
+
1308
+ # نمایش تمام سوالات این گروه
1309
+ for question in current_group['questions']:
1310
+ answer = enhanced_likert_scale(question)
1311
+ st.session_state.answers[question["key"]] = answer
1312
+
1313
+ # دکمه ادامه/اتمام
1314
+ button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups)-1 else "اتمام پرسشنامه"
1315
+
1316
+ if st.button(button_label):
1317
+ # بررسی آیا همه سوالات این گروه پاسخ داده شده‌اند
1318
+ all_answered = all(
1319
+ question["key"] in st.session_state.answers and
1320
+ st.session_state.answers[question["key"]] is not None
1321
+ for question in current_group['questions']
1322
+ )
 
 
 
 
 
 
 
1323
 
1324
+ if not all_answered:
1325
+ st.error("لطفاً به تمام سوالات این بخش پاسخ دهید قبل از ادامه")
1326
+ else:
1327
+ if st.session_state.current_likert_group < len(question_groups) - 1:
1328
+ st.session_state.current_likert_group += 1
1329
+ st.rerun()
1330
+ else:
1331
+ st.session_state.current_page = "explanation_questions"
1332
+ st.rerun()
1333
+
1334
+ def explanation_questions():
1335
+ """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1336
+ st.markdown("### 📋 سوالات تکمیلی")
1337
+
1338
+ # لیست سوالات به ترتیب نمایش
1339
+ questions = [
1340
+ {
1341
+ "key": "trust",
1342
+ "label": "آیا شما به تصمیم‌گیری‌هایی که توسط هوش مصنوعی انجام می‌شود اعتماد دارید؟",
1343
+ "options": ["بله", "خیر", "نظری ندارم"],
1344
+ "required": True
1345
+ },
1346
+ {
1347
+ "key": "pricing_method",
1348
+ "label": "به نظر شما پلتفرم قیمت را چگونه تعیین می‌کند؟",
1349
+ "options": [
1350
+ "به صورت دستی توسط تیم پلتفرم",
1351
+ "به صورت خودکار توسط هوش مصنوعی و الگوریتم‌ها",
1352
+ "ترکیبی از هر دو روش",
1353
+ "نظری ندارم"
1354
+ ],
1355
+ "required": True
1356
+ },
1357
+ {
1358
+ "key": "price_increase",
1359
+ "label": "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
1360
+ "options": ["بله", "خیر", "مطمئن نیستم"],
1361
+ "required": True
1362
+ },
1363
+ {
1364
+ "key": "explanation_received",
1365
+ "label": "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
1366
+ "options": ["بله", "خیر"],
1367
+ "required": True
1368
+ },
1369
+ {
1370
+ "key": "explanation_type",
1371
+ "label": "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
1372
+ "options": [
1373
+ "بر اساس عواملی که در قیمت‌گذاری لحاظ شده‌اند",
1374
+ "شامل سناریوهای جایگزین که می‌توانستند قیمت متفاوتی ایجاد کنند",
1375
+ "توضیحی دریافت نکردم"
1376
+ ],
1377
+ "required": False,
1378
+ "condition": lambda: st.session_state.get("explanation_received") == "بله"
1379
+ }
1380
+ ]
1381
+
1382
+ # مقداردهی اولیه step اگر وجود ندارد
1383
+ if "explanation_step" not in st.session_state:
1384
+ st.session_state.explanation_step = 0
1385
+
1386
+ # اگر همه سوالات پاسخ داده شده‌اند، به صفحه بعدی برو
1387
+ if st.session_state.explanation_step >= len(questions):
1388
+ st.session_state.current_page = "demographic"
1389
+ st.rerun()
1390
+ return
1391
+
1392
+ # دریافت سوال جاری
1393
+ current_q = questions[st.session_state.explanation_step]
1394
+
1395
+ # بررسی شرط نمایش برای سوالات اختیاری
1396
+ if "condition" in current_q and not current_q["condition"]():
1397
+ st.session_state[current_q["key"]] = "N/A"
1398
  st.session_state.explanation_step += 1
 
 
1399
  st.rerun()
1400
+ return
1401
+
1402
+ # نمایش سوال جاری
1403
+ answer = st.radio(
1404
+ current_q["label"],
1405
+ current_q["options"],
1406
+ index=None,
1407
+ key=f"explanation_q_{current_q['key']}"
1408
+ )
1409
+
1410
+ # دکمه ادامه
1411
+ if st.button("ادامه", key=f"continue_{current_q['key']}"):
1412
+ if answer is None and current_q["required"]:
1413
+ st.warning("لطفاً یک گزینه را انتخاب کنید")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1414
  else:
1415
+ # ذخیره پاسخ
1416
+ st.session_state[current_q["key"]] = answer if answer is not None else "N/A"
1417
+
1418
+ # افزایش شماره مرحله
1419
+ st.session_state.explanation_step += 1
1420
+
1421
+ # رفرش صفحه برای نمایش سوال بعدی
 
 
1422
  st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1423
 
1424
+ def demographic_form():
1425
+ """فرم اطلاعات دموگرافیک"""
1426
+ st.markdown("### 📝 اطلاعات دموگرافیک")
1427
+ st.markdown("""
1428
+ <div>
1429
+ <p>لطفاً اطلاعات زیر را صادقانه و به دقت وارد نمایید.</p>
1430
+ <p>همانطور که گفته شد،این اطلاعات کاملاً محرمانه نزد محقق خواهد ماند و در جهت اهداف پژوهشی استفاده خواهد شد.</p>
1431
+ </div>
1432
+ """, unsafe_allow_html=True)
1433
+
1434
+ with st.form("demographic_form"):
1435
+ age = st.number_input("سن", min_value=18, max_value=100, value=None, placeholder="سن خود را وارد کنید")
1436
+ gender = st.selectbox("جنسیت", ["", "مرد", "زن", "سایر"], index=0)
1437
+ education = st.selectbox("تحصیلات", ["", "دیپلم", "لیسانس", "فوق لیسانس", "دکترا"], index=0)
1438
+ city = st.selectbox("لطفاً استان محل سکونت خود را انتخاب بفرمایید.",
1439
+ ["", "آذربایجان شرقی", "آذربایجان غربی", "اردبیل", "اصفهان", لبرز", "ایلام",
1440
+ "بوشهر", "تهران", "چهارمحال و بختیاری", "خراسان جنوبی", "خراسان رضوی", "خراسان شمالی",
1441
+ "خوزستان", "زنجان", "سمنان", "سیستان و بلوچستان", "فارس", "قزوین", "قم", "کردستان",
1442
+ "کرمان", "کرمانشاه", "کهگیلویه و بویراحمد", "گلستان", "گیلان", "لرستان", "مازندران",
1443
+ "مرکزی", "هرمزگان", "همدان", "یزد"], index=0)
1444
+ related_education_job = st.selectbox("رشته تحصیلی/شغل شما در کدام‌یک از دسته‌های زیر قرار دارد؟",
1445
+ ["", "مهندسی", "درمانی", "فرهنگی", "مدیریتی (مالی)",
1446
+ "مدیریتی (بازاریابی)", "مدیریتی (سایر)", "روانشناسی",
1447
+ "اقتصادی", "حقوقی", "هنری", "ورزشی", "زبان", "غیره"], index=0)
1448
+ ride_frequency = st.selectbox("دفعات استفاده از سرویس‌های اشتراک سفر در ماه",
1449
+ ["", "هیچوقت", "کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"], index=0)
1450
 
1451
+ submitted = st.form_submit_button("ادامه")
1452
+ if submitted:
1453
+ if not all([age, gender, education, city, related_education_job, ride_frequency]):
1454
+ st.error("لطفاً تمام فیلدها را پر کنید")
1455
+ else:
1456
+ st.session_state.demographic_data = {
1457
+ "age": age,
1458
+ "gender": gender,
1459
+ "education": education,
1460
+ "city": city,
1461
+ "ride_frequency": ride_frequency,
1462
+ "related_education_job": related_education_job
1463
+ }
1464
+ st.session_state.current_page = "contact"
1465
+ st.rerun()
1466
+
1467
+ def user_contact():
1468
+ """راه ارتباطی ساده"""
1469
+ st.markdown("""
1470
+ <div style="text-align: center; margin-bottom: 30px;">
1471
+ <h3>📩 راه ارتباطی شما (اختیاری)</h3>
1472
+ <p>در صورت تمایل به شرکت در قرعه‌کشی می‌توانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:</p>
1473
+ </div>
1474
+ """, unsafe_allow_html=True)
1475
+
1476
+ contact_info = st.text_input(
1477
+ "راه ارتباطی (اختیاری)",
1478
+ placeholder="مثال: @username یا 09123456789 یا example@email.com",
1479
+ key="user_contact_input"
1480
+ )
1481
 
1482
+ if st.button("ثبت پاسخ‌ها", type="primary", key="submit_explanation"):
1483
+ st.session_state.user_contact = contact_info
1484
+ end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1485
+ start_time = datetime.strptime(st.session_state.start_time, "%Y-%m-%d %H:%M:%S")
1486
+ completion_time = (datetime.now() - start_time).total_seconds()
1487
 
1488
+ save_data = {
1489
+ "start_time": st.session_state.start_time,
1490
+ "end_time": end_time,
1491
+ "completion_time": completion_time,
1492
+ "scenario_type": st.session_state.scenario_type,
1493
+ "price": st.session_state.price,
1494
+ "user_contact": st.session_state.get("user_contact", ""),
1495
+ "price_accepted": st.session_state.get("price_accepted", 0),
1496
+ "attention_check1": st.session_state.get("attention_check1", None),
1497
+ "trust": st.session_state.trust,
1498
+ "pricing_method": st.session_state.pricing_method,
1499
+ "price_increase": st.session_state.price_increase,
1500
+ "explanation_received": st.session_state.explanation_received,
1501
+ "explanation_type": st.session_state.get("explanation_type", "N/A"),
1502
+ **st.session_state.demographic_data,
1503
+ **st.session_state.answers # اضافه کردن تمام پاسخ‌های لیکرت
1504
+ }
1505
+
1506
+ if save_to_sheet(save_data):
1507
+ st.session_state.current_page = "thank_you"
1508
+ st.rerun()
1509
+ else:
1510
+ st.error("خطا در ذخیره‌سازی داده‌ها. لطفاً دوباره تلاش کنید.")
1511
+
1512
+
1513
+ def thank_you_page():
1514
+ """صفحه تشکر"""
1515
+ st.success("""
1516
+ ✅ پاسخ‌های شما با موفقیت ثبت شد.
1517
+ سپاسگزاریم که وقت ارزشمند خود را به این پژوهش اختصاص دادید.
1518
+
1519
+ در صورت وجود هرگونه سوال، ابهام یا پیشنهاد می‌توانید با محقق تماس بگیرید:
1520
+
1521
+ ✉ ایمیل: maryam.ilka2000@gmail.com
1522
+ """)
1523
+ st.balloons()
1524
 
1525
+ # ========== مدیریت وضعیت و صفحه‌بندی ==========
1526
+ def main():
1527
+ # تشخیص دستگاه
1528
+ user_agent = st.query_params.get("user_agent", [""])[0]
1529
+ st.session_state.is_desktop = "mobile" not in user_agent.lower()
1530
 
 
 
 
 
1531
 
1532
+ if st.session_state.is_desktop:
1533
+ # اطمینان از نمایش همان حالت موبایل برای همه دستگاه‌ها
1534
+ st.session_state.is_desktop = False
1535
 
1536
+ if 'answers' not in st.session_state:
1537
+ st.session_state.answers = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1538
 
1539
+ if 'current_page' not in st.session_state:
1540
+ st.session_state.current_page = "welcome"
1541
+ st.session_state.scenario_type = random.choice(["control", "input", "counterfactual"])
1542
+ st.session_state.price = 200000
1543
+ st.session_state.user_contact = None
1544
+ st.session_state.demographic_data = None
1545
+ st.session_state.price_accepted = 0
1546
+ st.session_state.attention_check1 = None
1547
+
1548
+ pages = {
1549
+ "welcome": welcome_page,
1550
+ "scenario_explanation": scenario_explanation,
1551
+ "map_view": map_view,
1552
+ "attention_check1": attention_check1,
1553
+ "random_likert_questions": random_likert_questions,
1554
+ "explanation_questions": explanation_questions,
1555
+ "demographic": demographic_form, # دموگرافیک قبل از کانتکت
1556
+ "contact": user_contact, # کانتکت در انتها
1557
+ "thank_you": thank_you_page
1558
+ }
1559
+
1560
+ pages[st.session_state.current_page]()
1561
+
1562
+ if __name__ == "__main__":
1563
+ main()