GSMEthesis commited on
Commit
1ff3b71
·
verified ·
1 Parent(s): c24141e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +241 -143
app.py CHANGED
@@ -10,6 +10,7 @@ import os
10
  import json
11
  import random
12
  import time
 
13
 
14
  # تنظیمات اولیهه
15
  st.set_page_config(layout="wide", page_title="راهیار - تحلیل انصاف قیمتی", page_icon="🚖")
@@ -773,125 +774,225 @@ body, .stApp {
773
 
774
  # ========== توابع اصلی ==========
775
  def enhanced_likert_scale(question_data):
776
- """لیکرت اسکیل با محدوده 0 تا scale و عدم نمایش نقطه برای 0"""
777
  question = question_data["question"]
778
  key = question_data["key"]
779
  scale = question_data["scale"]
780
  labels = question_data.get("labels", ["کاملاً مخالفم", "کاملاً موافقم"])
781
 
782
- # مقداردهی اولیه
783
- if key not in st.session_state:
784
- st.session_state[key] = 0
785
-
786
  # نمایش سوال
787
- st.markdown(f"<div style='text-align:center; font-weight:bold; margin-bottom:15px;'>{question}</div>",
788
  unsafe_allow_html=True)
789
 
790
- # ایجاد خط و نقاط با HTML/CSS
791
- scale_html = f"""
792
- <style>
793
- @font-face {{
794
- font-family: 'Vazir';
795
- src: url('https://cdn.fontcdn.ir/Font/Persian/Vazir/Vazir-Bold.woff2') format('woff2');
796
- }}
797
- .likert-line {{
798
- width: 80%;
799
- height: 2px;
800
- background: #6a0dad;
801
- margin: 0 auto;
802
- position: relative;
803
- display: flex;
804
- justify-content: space-between;
805
- direction: rtl;
806
- }}
807
- .likert-dot {{
808
- width: 18px;
809
- height: 18px;
810
- border-radius: 50%;
811
- background: white;
812
- border: 2px solid #6a0dad;
813
- position: relative;
814
- top: -9px;
815
- }}
816
- .likert-dot.active {{
817
- background: #6a0dad;
818
- }}
819
- .likert-labels {{
820
- width: 80%;
821
- margin: 5px auto 15px;
822
- display: flex;
823
- justify-content: space-between;
824
- direction: rtl;
825
- font-size: 14px;
826
- font-family: 'Vazir', sans-serif;
827
- font-weight: bold;
828
- color: #6a0dad;
829
- }}
830
- .separator-line {{
831
- width: 80%;
832
- height: 2px;
833
- background: #6a0dad;
834
- margin: 20px auto;
835
- }}
836
- @media (max-width: 768px) {{
837
- .likert-line {{
838
- width: 90%;
839
- }}
840
- .likert-labels {{
841
- width: 90%;
842
- }}
843
- }}
844
- </style>
845
-
846
- <div>
847
- <div class="likert-labels">
848
- <span>{labels[0]}</span>
849
- <span>{labels[1]}</span>
850
- </div>
851
- <div class="likert-line">
852
- """
853
 
854
- # اضافه کردن نقاط فقط برای مقادیر 1 تا scale
855
- current_value = st.session_state.get(key, 0)
856
- for i in range(1, scale+1):
857
- value = i # مقادیر از 1 تا scale
858
- active_class = "active" if current_value == value else ""
859
- scale_html += f"<div class='likert-dot {active_class}'></div>"
 
 
 
 
 
 
 
 
 
860
 
861
- scale_html += "</div></div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
862
 
863
- # نمایش کامپوننت HTML
864
- components.html(scale_html, height=60)
865
-
866
- # ورودی عددی با محدوده 0 تا scale (0 قابل انتخاب است)
867
- value = st.number_input(
868
- "پاسخ شما (از 1 تا 7):",
869
- min_value=0, # 0 مجاز است
870
- max_value=scale,
871
- value=st.session_state.get(key, 0),
872
- step=1,
873
- key=f"{key}_input",
874
- placeholder="0 (پاسخ نداده) یا 1-7",
875
- format="%d",
876
- on_change=lambda: st.session_state.update({key: st.session_state[f"{key}_input"]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
877
  )
878
 
879
- # خط جداکننده بنفش
880
- # خط جداکننده بنفش - تعریف استایل و HTML با هم
881
- st.markdown("""
882
- <style>
883
- .likert-separator {
884
- width: 80%;
885
- height: 2px;
886
- background: #6a0dad;
887
- margin: 20px auto;
888
- }
889
- </style>
890
- <div class="likert-separator"></div>
891
- """, unsafe_allow_html=True)
 
 
 
 
 
 
892
 
893
  return st.session_state.get(key)
894
-
895
 
896
  def create_ride_map():
897
  """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
@@ -1179,7 +1280,7 @@ def attention_check1():
1179
  </style>
1180
  """, unsafe_allow_html=True)
1181
 
1182
- st.markdown("### سوال")
1183
 
1184
  answer = st.radio(
1185
  "رنگ لوگو اپلیکیشن رهیار و دکمه‌ها در صفحات قبلی چگونه بود؟",
@@ -1239,7 +1340,7 @@ def random_likert_questions():
1239
  در این بخش، با یک سری سؤال درمورد قیمتی که در صفحه اطلاعات سفر و در زیر نقشه دیدید، مواجه خواهید شد. در زیر سوالات طیفی قرار دارد: <br>
1240
  - سمت راست (۱): کاملاً نامنصفانه، غیرمعقول یا غیرقابل قبول<br>
1241
  - سمت چپ (۷): کاملاً منصفانه، معقول یا قابل قبول<br>
1242
- لطفاً با دقت عدد مناسب را بین ۱ تا ۷ را با زدن دکمه + و – جعبه زیر طیف انتخاب نمایید. بدین گونه شما انتخاب خواهید کرد که چقدر قیمت به نظرتان منصفانه بوده. چقدر با توجه به شرایط منطقی بوده و چقدر قابل قبول بوده.
1243
  </p>
1244
  """),
1245
  "questions": [
@@ -1297,7 +1398,7 @@ def random_likert_questions():
1297
  در این بخش، با یک سری سؤال درمورد توضیحاتی که در صفحه اطلاعات سفر و در زیر نقشه درمورد قیمت به شما ارائه شد، مواجه خواهید شد. در زیر سوالات طیفی قرار دارد: <br>
1298
  - سمت راست (۱): به هیچ وجه<br>
1299
  - سمت چپ (۷): خیلی زیاد<br>
1300
- لطفاً با دقت عدد مناسب را بین ۱ تا ۷ را با زدن دکمه + و – جعبه زیر طیف انتخاب نمایید. بدین گونه شما انتخاب خواهید کرد که از هیچ مقدار تا خیلی زیاد به چه مقدار به شما توضیح با ویژگی‌های سوال ارائه شده است.
1301
  </p>
1302
  """),
1303
  "questions": [
@@ -1309,17 +1410,18 @@ def random_likert_questions():
1309
  ]
1310
  }
1311
  ]
1312
- # مقداردهی اولیه
1313
  if 'current_likert_group' not in st.session_state:
1314
  st.session_state.current_likert_group = 0
1315
-
 
 
1316
  current_group = question_groups[st.session_state.current_likert_group]
1317
-
1318
-
1319
- st.markdown(f"## {current_group['title']}")
1320
-
1321
- if 'guide' in current_group:
1322
- # ترکیب CSS و HTML با جایگذاری امن
1323
  guide_html = textwrap.dedent("""
1324
  <div class="guide-text" style="
1325
  display: flex;
@@ -1334,44 +1436,40 @@ def random_likert_questions():
1334
  {}
1335
  </div>
1336
  """).format(current_group['guide'])
1337
-
1338
  st.markdown(guide_html, unsafe_allow_html=True)
 
 
 
 
 
1339
 
1340
- # نمایش تمام سوالات این گروه
1341
- for question in current_group['questions']:
1342
- answer = enhanced_likert_scale(question)
1343
- st.session_state.answers[question["key"]] = answer
1344
-
1345
- # دکمه ادامه/اتمام
1346
- button_label = "ادامه" if st.session_state.current_likert_group < len(question_groups)-1 else "ادامه"
1347
-
1348
- if st.button(button_label):
1349
- # بررسی آیا همه سوالات این گروه پاسخ داده شده‌اند و هیچ کدام صفر نیستند
1350
- all_answered = all(
1351
- question["key"] in st.session_state.answers and
1352
- st.session_state.answers[question["key"]] is not None
1353
- for question in current_group['questions']
1354
- )
1355
 
1356
- has_zero = any(
1357
- st.session_state.answers.get(question["key"]) == 0
1358
- for question in current_group['questions']
1359
- )
1360
 
1361
- if not all_answered or has_zero:
1362
- st.error("لطفاً به تمام سوالات این بخش پاسخ دهید")
 
1363
  else:
1364
- if st.session_state.current_likert_group < len(question_groups) - 1:
1365
- st.session_state.current_likert_group += 1
1366
- st.rerun()
1367
- else:
 
 
 
1368
  st.session_state.current_page = "explanation_questions"
1369
- st.rerun()
 
1370
 
1371
 
1372
  def explanation_questions():
1373
  """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1374
- st.markdown("### 📋 سوالات تکمیلی")
1375
 
1376
  # لیست سوالات به ترتیب نمایش
1377
  questions = [
 
10
  import json
11
  import random
12
  import time
13
+ import requests
14
 
15
  # تنظیمات اولیهه
16
  st.set_page_config(layout="wide", page_title="راهیار - تحلیل انصاف قیمتی", page_icon="🚖")
 
774
 
775
  # ========== توابع اصلی ==========
776
  def enhanced_likert_scale(question_data):
 
777
  question = question_data["question"]
778
  key = question_data["key"]
779
  scale = question_data["scale"]
780
  labels = question_data.get("labels", ["کاملاً مخالفم", "کاملاً موافقم"])
781
 
 
 
 
 
782
  # نمایش سوال
783
+ st.markdown(f"<div style='text-align:right; font-weight:bold; margin-bottom:15px; direction: rtl;'>{question}</div>",
784
  unsafe_allow_html=True)
785
 
786
+ # رادیو باتن اصلی (مخفی)
787
+ selected_value = st.radio(
788
+ "",
789
+ options=list(range(1, scale+1)),
790
+ index=st.session_state.get(key, 0) - 1 if st.session_state.get(key, 0) > 0 else None,
791
+ label_visibility="collapsed",
792
+ horizontal=True,
793
+ key=f"{key}_radio"
794
+ )
795
+
796
+ # مخفی کردن رادیو باتن
797
+ st.markdown(
798
+ """
799
+ <style>
800
+ div[data-testid="stRadio"] {
801
+ display: none;
802
+ }
803
+ </style>
804
+ """,
805
+ unsafe_allow_html=True
806
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
 
808
+ # ذخیره مقدار انتخاب شده
809
+ if selected_value is not None:
810
+ st.session_state[key] = selected_value
811
+
812
+ # ایجاد JavaScript
813
+ js_code = """
814
+ <script>
815
+ function handleLikertClick(index) {
816
+ const radios = parent.document.querySelectorAll('div[data-testid="stRadio"] input[type="radio"]');
817
+ if (radios.length > index) {
818
+ radios[index].click();
819
+ }
820
+ }
821
+ </script>
822
+ """
823
 
824
+ # ایجاد دکمه‌های سفارشی
825
+ options_html = "".join([
826
+ f'<div class="likert-option {"selected" if st.session_state.get(key) == i+1 else ""} {"middle-option" if (scale % 2 == 1 and i+1 == (scale//2 + 1)) else ""}" '
827
+ f'onclick="handleLikertClick({i})">'
828
+ f'<span class="likert-number">{i+1}</span>'
829
+ '</div>'
830
+ for i in range(scale)
831
+ ])
832
+
833
+ # تنظیم لیبل وسط برای مقیاس‌های فرد
834
+ middle_label = ""
835
+ middle_label_text = "متوسط"
836
+ if scale % 2 == 1: # اگر مقیاس فرد باشد
837
+ middle_label = f"""
838
+ <div class='label-container middle-label'>
839
+ <span>{middle_label_text}</span>
840
+ </div>
841
+ """
842
 
843
+ # ترکیب تمام بخش‌ها با استایل بهبود یافته
844
+ components.html(
845
+ f"""
846
+ {js_code}
847
+ <style>
848
+ .likert-wrapper {{
849
+ width: 100%;
850
+ max-width: 475px;
851
+ margin: 0 auto;
852
+ direction: rtl;
853
+ position: relative;
854
+ }}
855
+ .likert-container {{
856
+ display: flex;
857
+ justify-content: space-between;
858
+ width: 100%;
859
+ padding-top: 50px; /* فضای کافی برای لیبل‌ها و خطوط */
860
+ }}
861
+ .likert-option {{
862
+ width: 36px;
863
+ height: 36px;
864
+ border-radius: 50%;
865
+ background: white;
866
+ border: 2px solid #6a0dad;
867
+ display: flex;
868
+ align-items: center;
869
+ justify-content: center;
870
+ cursor: pointer;
871
+ margin: 0 3px;
872
+ transition: all 0.2s;
873
+ position: relative;
874
+ z-index: 1;
875
+ }}
876
+ .likert-option:hover {{
877
+ transform: scale(1.1);
878
+ box-shadow: 0 0 10px rgba(106, 13, 173, 0.4);
879
+ }}
880
+ .likert-option.selected {{
881
+ background: #6a0dad;
882
+ }}
883
+ .likert-number {{
884
+ color: #6a0dad;
885
+ font-weight: bold;
886
+ font-size: 16px;
887
+ user-select: none;
888
+ transition: all 0.2s;
889
+ }}
890
+ .likert-option.selected .likert-number {{
891
+ color: white;
892
+ }}
893
+ .labels-container {{
894
+ display: flex;
895
+ justify-content: space-between;
896
+ width: 100%;
897
+ position: absolute;
898
+ top: 0;
899
+ direction: rtl;
900
+ }}
901
+ .label-container {{
902
+ font-size: 14px;
903
+ font-weight: bold;
904
+ color: #6a0dad;
905
+ text-align: center;
906
+ flex: 1;
907
+ position: relative;
908
+ }}
909
+ .label-container span {{
910
+ display: inline-block;
911
+ white-space: normal; /* اجازه می‌دهد متن به‌صورت کامل نمایش داده شود */
912
+ line-height: 1.2; /* فاصله خطوط برای خوانایی بهتر */
913
+ }}
914
+ .left-label {{
915
+ text-align: right;
916
+ padding-right: 10px;
917
+ }}
918
+ .right-label {{
919
+ text-align: left;
920
+ padding-left: 10px;
921
+ }}
922
+ .middle-label {{
923
+ position: absolute;
924
+ left: 50%;
925
+ transform: translateX(-50%);
926
+ }}
927
+ /* خطوط ارتباطی فقط از سمت لیبل‌ها */
928
+ .left-label::before {{
929
+ content: '';
930
+ position: absolute;
931
+ bottom: -30px;
932
+ right: 10%;
933
+ width: 2px;
934
+ height: 25px;
935
+ background-color: #6a0dad;
936
+ }}
937
+ .right-label::before {{
938
+ content: '';
939
+ position: absolute;
940
+ bottom: -30px;
941
+ left: 10%;
942
+ width: 2px;
943
+ height: 25px;
944
+ background-color: #6a0dad;
945
+ }}
946
+ .middle-label::before {{
947
+ content: '';
948
+ position: absolute;
949
+ bottom: -30px;
950
+ left: 50%;
951
+ transform: translateX(-50%);
952
+ width: 2px;
953
+ height: 25px;
954
+ background-color: #6a0dad;
955
+ }}
956
+ </style>
957
+ <div class='likert-wrapper'>
958
+ <div class='labels-container'>
959
+ <div class='label-container left-label'>
960
+ <span>{labels[0]}</span>
961
+ </div>
962
+ {middle_label}
963
+ <div class='label-container right-label'>
964
+ <span>{labels[1]}</span>
965
+ </div>
966
+ </div>
967
+ <div class="likert-container">
968
+ {options_html}
969
+ </div>
970
+ </div>
971
+ """,
972
+ height=120 # ارتفاع برای جا دادن لیبل‌ها و خطوط
973
  )
974
 
975
+ # نمایش وضعیت انتخاب
976
+ status = f"پاسخ شما: {st.session_state[key]}" if st.session_state.get(key) else "پاسخ شما: هنوز انتخاب نشده"
977
+ st.markdown(
978
+ f"""
979
+ <p style='
980
+ text-align: right;
981
+ color: #6a0dad;
982
+ direction: rtl;
983
+ margin-top: 20px;
984
+ padding: 10px;
985
+ background-color: #f8f0ff;
986
+ border-radius: 8px;
987
+ border-right: 3px solid #6a0dad;
988
+ '>
989
+ {status}
990
+ </p>
991
+ """,
992
+ unsafe_allow_html=True
993
+ )
994
 
995
  return st.session_state.get(key)
 
996
 
997
  def create_ride_map():
998
  """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
 
1280
  </style>
1281
  """, unsafe_allow_html=True)
1282
 
1283
+ st.markdown("### سؤال")
1284
 
1285
  answer = st.radio(
1286
  "رنگ لوگو اپلیکیشن رهیار و دکمه‌ها در صفحات قبلی چگونه بود؟",
 
1340
  در این بخش، با یک سری سؤال درمورد قیمتی که در صفحه اطلاعات سفر و در زیر نقشه دیدید، مواجه خواهید شد. در زیر سوالات طیفی قرار دارد: <br>
1341
  - سمت راست (۱): کاملاً نامنصفانه، غیرمعقول یا غیرقابل قبول<br>
1342
  - سمت چپ (۷): کاملاً منصفانه، معقول یا قابل قبول<br>
1343
+ لطفاً با دقت عدد مناسب را بین ۱ تا ۷ را انتخاب نمایید. بدین گونه شما انتخاب خواهید کرد که چقدر قیمت به نظرتان منصفانه بوده. چقدر با توجه به شرایط منطقی بوده و چقدر قابل قبول بوده.
1344
  </p>
1345
  """),
1346
  "questions": [
 
1398
  در این بخش، با یک سری سؤال درمورد توضیحاتی که در صفحه اطلاعات سفر و در زیر نقشه درمورد قیمت به شما ارائه شد، مواجه خواهید شد. در زیر سوالات طیفی قرار دارد: <br>
1399
  - سمت راست (۱): به هیچ وجه<br>
1400
  - سمت چپ (۷): خیلی زیاد<br>
1401
+ لطفاً با دقت عدد مناسب را بین ۱ تا ۷ را انتخاب نمایید. بدین گونه شما انتخاب خواهید کرد که از هیچ مقدار تا خیلی زیاد به چه مقدار به شما توضیح با ویژگی‌های سوال ارائه شده است.
1402
  </p>
1403
  """),
1404
  "questions": [
 
1410
  ]
1411
  }
1412
  ]
1413
+ # مقداردهی اولیه
1414
  if 'current_likert_group' not in st.session_state:
1415
  st.session_state.current_likert_group = 0
1416
+ st.session_state.current_question_index = 0
1417
+ st.session_state.show_guide = True
1418
+
1419
  current_group = question_groups[st.session_state.current_likert_group]
1420
+ current_question = current_group['questions'][st.session_state.current_question_index]
1421
+
1422
+ # نمایش راهنما فقط برای اولین سوال هر گروه
1423
+ if st.session_state.show_guide and 'guide' in current_group:
1424
+ st.markdown(f"## {current_group['title']}")
 
1425
  guide_html = textwrap.dedent("""
1426
  <div class="guide-text" style="
1427
  display: flex;
 
1436
  {}
1437
  </div>
1438
  """).format(current_group['guide'])
 
1439
  st.markdown(guide_html, unsafe_allow_html=True)
1440
+ else:
1441
+ st.markdown(f"## {current_group['title']}")
1442
+
1443
+ # نمایش سوال جاری
1444
+ answer = enhanced_likert_scale(current_question)
1445
 
1446
+ # اگر پاسخ داده شد، به سوال بعدی برو
1447
+ if answer is not None:
1448
+ st.session_state.answers[current_question["key"]] = answer
1449
+ st.session_state.show_guide = False
 
 
 
 
 
 
 
 
 
 
 
1450
 
1451
+ # تاخیر برای نمایش پاسخ قبل از رفتن به سوال بعدی
1452
+ time.sleep(0.5)
 
 
1453
 
1454
+ # بررسی آیا سوالات این گروه تمام شده یا نه
1455
+ if st.session_state.current_question_index < len(current_group['questions']) - 1:
1456
+ st.session_state.current_question_index += 1
1457
  else:
1458
+ # رفتن به گروه بعدی
1459
+ st.session_state.current_likert_group += 1
1460
+ st.session_state.current_question_index = 0
1461
+ st.session_state.show_guide = True
1462
+
1463
+ # اگر همه گروه‌ها تمام شدند، به صفحه بعدی برو
1464
+ if st.session_state.current_likert_group >= len(question_groups):
1465
  st.session_state.current_page = "explanation_questions"
1466
+
1467
+ st.rerun()
1468
 
1469
 
1470
  def explanation_questions():
1471
  """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1472
+ st.markdown("### 📋 سؤالات تکمیلی")
1473
 
1474
  # لیست سوالات به ترتیب نمایش
1475
  questions = [