Spaces:
Running
Running
Update app.py
Browse files
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 |
-
|
| 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
|
| 748 |
-
"""لیکرت اسکیل با خط و نقاط - کاملاً واکنشگرا"""
|
| 749 |
-
question =
|
| 750 |
-
key =
|
| 751 |
-
scale =
|
| 752 |
-
labels =
|
| 753 |
-
|
| 754 |
-
```
|
| 755 |
-
if key not in st.session_state:
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
# نمایش سوال
|
| 759 |
-
st.markdown(f"<div style='text-align:center; font-weight:bold; margin-bottom:15px;'>{question}</div>",
|
| 760 |
-
|
| 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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
}}
|
| 815 |
.likert-labels {{
|
| 816 |
-
width:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
}}
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
<
|
| 846 |
-
|
| 847 |
-
|
| 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 |
-
|
| 860 |
-
for i in range(scale):
|
| 861 |
-
with btn_cols[i]:
|
| 862 |
value = scale - i
|
| 863 |
-
|
| 864 |
-
|
| 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 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 952 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 953 |
|
| 954 |
-
|
| 955 |
-
|
| 956 |
-
|
| 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
|
| 998 |
-
"""
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
| 1002 |
-
st.
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
"
|
| 1012 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
)
|
| 1014 |
-
return
|
| 1015 |
-
|
| 1016 |
-
|
| 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 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1049 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1060 |
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
data.get("informational_2", ""),
|
| 1064 |
-
data.get("informational_3", ""),
|
| 1065 |
-
data.get("informational_4", ""),
|
| 1066 |
-
data.get("informational_5", ""),
|
| 1067 |
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
<
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
</div>
|
| 1126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1127 |
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
| 1133 |
-
|
| 1134 |
-
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
| 1138 |
-
st.
|
| 1139 |
-
|
| 1140 |
-
st.
|
| 1141 |
-
|
| 1142 |
-
|
| 1143 |
-
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
st.markdown("""
|
| 1148 |
-
<div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
|
| 1149 |
-
|
| 1150 |
-
|
| 1151 |
-
|
| 1152 |
-
</
|
| 1153 |
-
|
| 1154 |
-
|
| 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 |
-
|
| 1166 |
-
|
|
|
|
|
|
|
|
|
|
| 1167 |
|
| 1168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1169 |
|
| 1170 |
-
|
| 1171 |
-
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
| 1176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1177 |
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
""
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 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 |
-
|
| 1328 |
-
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1332 |
st.rerun()
|
| 1333 |
else:
|
| 1334 |
-
st.
|
| 1335 |
-
|
| 1336 |
-
|
| 1337 |
-
|
| 1338 |
-
|
| 1339 |
-
|
| 1340 |
-
|
| 1341 |
-
|
| 1342 |
-
|
| 1343 |
-
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
-
|
| 1347 |
-
|
| 1348 |
-
|
| 1349 |
-
|
| 1350 |
-
|
| 1351 |
-
|
| 1352 |
-
|
| 1353 |
-
|
| 1354 |
-
|
| 1355 |
-
|
| 1356 |
-
|
| 1357 |
-
|
| 1358 |
-
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 1366 |
-
|
| 1367 |
-
|
| 1368 |
-
|
| 1369 |
-
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
-
|
| 1379 |
-
|
| 1380 |
-
"
|
| 1381 |
-
|
| 1382 |
-
|
| 1383 |
-
|
| 1384 |
-
|
| 1385 |
-
]
|
| 1386 |
-
|
| 1387 |
-
|
| 1388 |
-
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
st.session_state
|
| 1394 |
-
|
| 1395 |
-
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
#
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 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 |
-
|
| 1432 |
-
""
|
| 1433 |
-
|
| 1434 |
-
|
| 1435 |
-
""
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 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 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 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 |
-
|
| 1491 |
-
|
| 1492 |
-
|
| 1493 |
-
|
| 1494 |
-
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
-
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1507 |
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1514 |
|
| 1515 |
-
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
""
|
| 1519 |
-
|
| 1520 |
-
|
| 1521 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1522 |
|
| 1523 |
-
|
| 1524 |
-
|
|
|
|
|
|
|
|
|
|
| 1525 |
|
| 1526 |
-
✉ ایمیل: maryam.ilka2000@gmail.com
|
| 1527 |
-
""")
|
| 1528 |
-
st.balloons()
|
| 1529 |
-
```
|
| 1530 |
|
| 1531 |
-
|
|
|
|
|
|
|
| 1532 |
|
| 1533 |
-
|
| 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 |
-
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
-
|
| 1559 |
-
|
| 1560 |
-
|
| 1561 |
-
|
| 1562 |
-
|
| 1563 |
-
|
| 1564 |
-
|
| 1565 |
-
|
| 1566 |
-
|
| 1567 |
-
|
| 1568 |
-
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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()
|