GSMEthesis commited on
Commit
a3342e1
·
verified ·
1 Parent(s): 90d9e61

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -1653
app.py CHANGED
@@ -1,1654 +1,40 @@
1
  import streamlit as st
2
- import folium
3
- from streamlit_folium import folium_static
4
- import streamlit.components.v1 as components
5
- from datetime import datetime
6
- import gspread
7
- from google.oauth2.service_account import Credentials
8
- import os
9
- import json
10
- import random
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 {
25
- font-family: 'B Nazanin';
26
- src: url('https://cdn.jsdelivr.net/gh/rastikerdar/fonts@master/fonts/B%20Nazanin/B%20Nazanin.woff') format('woff');
27
- }
28
-
29
- :root {
30
- --primary: #6a0dad;
31
- --text: #333333;
32
- --background: #ffffff;
33
- --border: #dddddd;
34
- --input-bg: #ffffff;
35
- --secondary-bg: #f8f9fa;
36
- --green: #f0ff0;
37
- --dgreen: #006400;
38
- }
39
-
40
- * {
41
- font-family: 'B Nazanin', 'B Nazanin Bold', sans-serif !important;
42
- text-align: right !important;
43
- direction: rtl !important;
44
- }
45
-
46
- /* ======== تنظیمات پایه ======== */
47
- * {
48
- font-family: 'B Nazanin', 'B Nazanin Bold', sans-serif !important;
49
- text-align: right !important;
50
- direction: rtl !important;
51
- box-sizing: border-box !important;
52
- }
53
-
54
- /* تنظیمات برای موبایل و نمایش موبایل در تمام دستگاه‌ها */
55
- html, body, .stApp {
56
- max-width: 768px !important; /* حداکثر عرض صفحه */
57
- margin: 0 auto; /* وسط‌چین کردن محتوا */
58
- width: 100% !important
59
- background-color: var(--background) !important;
60
- color: var(--text) !important;
61
- }
62
-
63
- @media (min-width: 768px) {
64
- /* Container for the mobile frame */
65
- body {
66
- background: #f0f0f0 !important;
67
- display: flex !important;
68
- justify-content: center !important;
69
- align-items: center !important;
70
- height: 100vh !important;
71
- overflow: hidden !important;
72
- }
73
-
74
- /* Mobile frame styling */
75
- [data-testid="stAppViewContainer"] {
76
- width: 475px !important;
77
- height: 667px !important;
78
- margin: 0 auto !important;
79
- border: 12px solid #000 !important;
80
- border-radius: 40px !important;
81
- box-shadow:
82
- 0 0 0 6px #666,
83
- 0 0 30px rgba(0,0,0,0.5) !important;
84
- background-color: var(--background) !important;
85
- color: var(--text) !important;
86
- overflow-y: auto !important;
87
- overflow-x: hidden !important;
88
- scrollbar-width: thin !important;
89
- scrollbar-color: #6a0dad #f0f0f0 !important;
90
- direction: rtl !important;
91
- }
92
-
93
- /* Remove any potential dark background from parent elements */
94
- #root, .stApp {
95
- background: transparent !important;
96
- margin: 0 auto;
97
- }
98
-
99
- /* Ensure Streamlit's main container doesn't add extra space */
100
- [data-testid="stAppViewContainer"] > div {
101
- max-width: 100% !important;
102
- margin: 0 auto;
103
- }
104
-
105
- /* Webkit scrollbar styles */
106
- [data-testid="stAppViewContainer"]::-webkit-scrollbar {
107
- width: 6px !important;
108
- right: 0 !important;
109
- left: auto !important;
110
- }
111
-
112
- [data-testid="stAppViewContainer"]::-webkit-scrollbar-track {
113
- background: #f0f0f0 !important;
114
- border-radius: 3px !important;
115
- margin: 10px 0 !important;
116
- }
117
-
118
- [data-testid="stAppViewContainer"]::-webkit-scrollbar-thumb {
119
- background-color: #6a0dad !important;
120
- border-radius: 3px !important;
121
- margin-left: 10px !important;
122
- }
123
-
124
- /* Reset direction for content */
125
- [data-testid="stAppViewContainer"] > * {
126
- direction: ltr !important;
127
- text-align: right !important;
128
- }
129
-
130
- /* Virtual home button */
131
- [data-testid="stAppViewContainer"]::after {
132
- content: "";
133
- position: absolute;
134
- bottom: 15px;
135
- left: 50%;
136
- transform: translateX(-50%);
137
- width: 100px;
138
- height: 4px;
139
- background: #333;
140
- border-radius: 2px;
141
- }
142
- /* تنظیم پدینگ عمومی با * */
143
- [data-testid="stAppViewContainer"] * {
144
- padding-right: 5px !important;
145
- padding-left: 8px !important;
146
- box-sizing: border-box !important;
147
- }
148
-
149
- /* استثناهای اصلی */
150
- [data-testid="stAppViewContainer"]::after,
151
- [data-testid="stAppViewContainer"]::-webkit-scrollbar,
152
- [data-testid="stAppViewContainer"] .stButton>button,
153
- [data-testid="stAppViewContainer"] .stTextInput>div>div>input,
154
- [data-testid="stAppViewContainer"] .stNumberInput>div>div>input,
155
- [data-testid="stAppViewContainer"] .stSelectbox>div>div>select,
156
- [data-testid="stAppViewContainer"] .stTextArea>div>textarea,
157
- [data-testid="stAppViewContainer"] .stDateInput>div>div>input,
158
- [data-testid="stAppViewContainer"] .stTimeInput>div>div>input,
159
- [data-testid="stAppViewContainer"] .price-container,
160
- [data-testid="stAppViewContainer"] .stAlert,
161
- [data-testid="stAppViewContainer"] .stMarkdown,
162
- [data-testid="stAppViewContainer"] .folium-map,
163
- [data-testid="stAppViewContainer"] .stRadio>div>div>div>label,
164
- [data-testid="stAppViewContainer"] .stCheckbox>div>div>label,
165
- [data-testid="stAppViewContainer"] .stSlider>div>div>div>div,
166
- [data-testid="stAppViewContainer"] .stMultiSelect>div>div>div {
167
- padding-right: 0 !important;
168
- padding-left: 0 !important;
169
- }
170
-
171
- /* تنظیمات خاص برای کانتینرهای Streamlit */
172
- [data-testid="stAppViewContainer"] [data-testid="stVerticalBlock"]>div,
173
- [data-testid="stAppViewContainer"] [data-testid="stHorizontalBlock"]>div {
174
- padding-right: 5px !important;
175
- padding-left: 8px !important;
176
- }
177
-
178
- /* بازنشانی پدینگ برای المان‌های داخلی خاص */
179
- [data-testid="stAppViewContainer"] .stTextInput>div,
180
- [data-testid="stAppViewContainer"] .stNumberInput>div,
181
- [data-testid="stAppViewContainer"] .stSelectbox>div,
182
- [data-testid="stAppViewContainer"] .stTextArea>div,
183
- [data-testid="stAppViewContainer"] .stDateInput>div,
184
- [data-testid="stAppViewContainer"] .stTimeInput>div {
185
- padding-right: 0 !important;
186
- padding-left: 0 !important;
187
- }
188
- }
189
-
190
- /* هدر راهیار */
191
- .rahyar-title {
192
- color: var(--primary) !important;
193
- margin: 0 !important;
194
- }
195
-
196
- .rahyar-subtitle {
197
- color: var(--primary) !important;
198
- margin: 0 !important;
199
- font-size: 16px !important;
200
- }
201
-
202
- /* توضیحات */
203
- .explanation-title {
204
- color: var(--primary) !important;
205
- font-weight: bold !important;
206
- margin: 20px 0 10px 0 !important;
207
- font-size: 18px !important;
208
- }
209
-
210
- .explanation-item {
211
- background-color: var(--secondary-bg) !important;
212
- border-radius: 8px !important;
213
- padding: 12px 15px !important;
214
- margin: 8px 0 !important;
215
- border-right: 3px solid var(--primary) !important;
216
- font-size: 18px !important;
217
- }
218
-
219
-
220
- /* ========== استایل‌های ورودی یکپارچه ========== */
221
- /* استایل پایه برای تمام کانتینرهای ورودی */
222
- div[data-baseweb="select"] > div:first-child,
223
- div[data-baseweb="input"] > div:first-child,
224
- div[data-baseweb="textarea"] > div:first-child {
225
- color: var(--text) !important;
226
- border-radius: 6px !important;
227
- border: 1px solid var(--primary) !important;
228
- background-color: white !important;
229
- }
230
-
231
- /* استایل داخلی برای المان‌های ورودی */
232
- div[data-baseweb="select"] input,
233
- div[data-baseweb="input"] input,
234
- div[data-baseweb="textarea"] textarea {
235
- color: var(--text) !important;
236
- background-color: white !important;
237
- border: none !important;
238
- min-height: 40px !important;
239
- font-family: 'B Nazanin' !important;
240
- direction: rtl !important;
241
- }
242
-
243
- /* استایل خاص برای سلکت باکس */
244
- div[data-baseweb="select"] > div:first-child {
245
- -webkit-appearance: none !important;
246
- -moz-appearance: none !important;
247
- appearance: none !important;
248
- background-image: none !important;
249
- padding-right: 30px !important; /* فضای برای فلش */
250
- }
251
-
252
- /* فلش سفارشی برای سلکت باکس */
253
- div[data-baseweb="select"]::after {
254
- content: "▼";
255
- position: absolute;
256
- left: 7px !important;
257
- top: 50%;
258
- transform: translateY(-50%);
259
- color: var(--primary) !important;
260
- background-color: white !important; /* پسزمینه سفید برای پوشاندن فلش پیشفرض */
261
- font-size: 14px !important;
262
- pointer-events: none;
263
- }
264
-
265
- /* استایل dropdown */
266
- div[data-baseweb="popover"] {
267
- border: 1px solid var(--primary) !important;
268
- border-radius: 6px !important;
269
- box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
270
- }
271
-
272
- /* آیتم‌های dropdown */
273
- div[data-baseweb="popover"] [role="option"] {
274
- color: var(--text) !important;
275
- background-color: white !important;
276
- padding: 10px 12px !important;
277
- font-family: 'B Nazanin' !important;
278
- direction: rtl !important;
279
- }
280
-
281
- /* آیتم انتخاب شده در dropdown */
282
- div[data-baseweb="popover"] [role="option"][aria-selected="true"] {
283
- background-color: #f0e6ff !important;
284
- }
285
-
286
- /* استایل hover برای تمام المان‌های ورودی */
287
- div[data-baseweb="select"] > div:first-child:hover,
288
- div[data-baseweb="input"] > div:first-child:hover,
289
- div[data-baseweb="textarea"] > div:first-child:hover {
290
- border-color: var(--primary) !important;
291
- box-shadow: 0 0 0 3px rgba(106, 13, 173, 0.15) !important;
292
- }
293
-
294
- /* استایل focus */
295
- div[data-baseweb="select"] > div:first-child:focus-within,
296
- div[data-baseweb="input"] > div:first-child:focus-within,
297
- div[data-baseweb="textarea"] > div:first-child:focus-within {
298
- border-color: var(--primary) !important;
299
- box-shadow: 0 0 0 3px rgba(106, 13, 173, 0.15) !important;
300
- outline: none !important;
301
- }
302
-
303
- /* استایل لیبل‌ها */
304
- .stTextInput > label,
305
- .stNumberInput > label,
306
- .stSelectbox > label,
307
- .stRadio > label,
308
- .stSlider > label {
309
- color: var(--text) !important;
310
- font-weight: bold !important;
311
- margin-bottom: 8px !important;
312
- font-size: 16px !important;
313
- display: block !important;
314
- }
315
-
316
- /* استایل placeholder */
317
- ::placeholder {
318
- color: var(--text) !important;
319
- opacity: 0.5 !important;
320
- font-family: 'B Nazanin' !important;
321
- }
322
-
323
- /* پاکسازی عناصر سیاه رنگ ناخواسته */
324
- div[data-testid="stNumberInput"] > div > div:first-child,
325
- div[data-testid="stNumberInput"] > div > div:last-child {
326
- background-color: transparent !important;
327
- border: none !important;
328
- }
329
-
330
- /* حذف عناصر سیاه پس‌زمینه */
331
- div[data-testid="stNumberInput"] button > div {
332
- display: none !important;
333
- }
334
-
335
- /* حذف کامل عناصر داخلی ناخواسته */
336
- div[data-testid="stNumberInput"] button > div,
337
- div[data-testid="stNumberInput"] button > svg {
338
- display: none !important;
339
- visibility: hidden !important;
340
- }
341
-
342
- /* ایجاد آیکون‌های سفارشی با رنگ بنفش */
343
- div[data-testid="stNumberInput"] button {
344
- position: relative !important;
345
- color: transparent !important; /* مخفی کردن متن پیش‌فرض */
346
- }
347
-
348
- /* دکمه سمت راست (کاهش) */
349
- div[data-testid="stNumberInput"] button:first-child::after {
350
- content: "-" !important;
351
- position: absolute !important;
352
- top: 50% !important;
353
- left: 50% !important;
354
- transform: translate(-50%, -50%) !important;
355
- color: var(--primary) !important;
356
- font-size: 1.2rem !important;
357
- font-weight: bold !important;
358
- }
359
-
360
- /* دکمه سمت چپ (افزایش) */
361
- div[data-testid="stNumberInput"] button:last-child::after {
362
- content: "+" !important;
363
- position: absolute !important;
364
- top: 50% !important;
365
- left: 50% !important;
366
- transform: translate(-50%, -50%) !important;
367
- color: var(--primary) !important;
368
- font-size: 1.2rem !important;
369
- font-weight: bold !important;
370
- }
371
-
372
- /* بازنشانی کامل استایل‌های Streamlit */
373
- div[data-testid="stNumberInput"] button {
374
- all: unset !important;
375
- width: 36px !important;
376
- height: 100% !important;
377
- position: relative !important;
378
- cursor: pointer !important;
379
- z-index: 10 !important;
380
- }
381
-
382
- /* تضمین رنگ در حالت‌های مختلف */
383
- div[data-testid="stNumberInput"] button:hover::after,
384
- div[data-testid="stNumberInput"] button:focus::after {
385
- color: var(--primary) !important;
386
- }
387
-
388
- /* استایل hover برای دکمه‌ها */
389
- div[data-testid="stNumberInput"] button:hover {
390
- background-color: #f0e6ff !important;
391
- }
392
-
393
- /* استایل focus */
394
- div[data-testid="stNumberInput"] > div:focus-within {
395
- border-color: var(--primary) !important;
396
- box-shadow: 0 0 0 3px rgba(106, 13, 173, 0.15) !important;
397
- outline: none !important;
398
- }
399
-
400
-
401
- /* کامپوننت‌های سفارشی */
402
- .rahyar-header {
403
- background-color: var(--primary) !important;
404
- color: white !important;
405
- padding: 15px !important;
406
- border-radius: 10px !important;
407
- margin-bottom: 20px !important;
408
- text-align: center !important;
409
- }
410
-
411
- .price-container {
412
- background-color: var(--secondary-bg) !important;
413
- border-radius: 10px !important;
414
- padding: 15px !important;
415
- margin: 15px 0 !important;
416
- border-right: 5px solid var(--primary) !important;
417
- }
418
-
419
- /* دکمه اصلی (بنفش با متن سفید) */
420
- .stButton>button,
421
- [data-testid="baseButton-primary"],
422
- .accept-btn,
423
- div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > button {
424
- background-color: var(--primary) !important;
425
- color: white !important;
426
- border: none !important;
427
- border-radius: 8px !important;
428
- padding: 10px 20px !important;
429
- font-weight: bold !important;
430
- transition: all 0.3s ease !important; /* افزودن transition برای انیمیشن نرم */
431
- }
432
-
433
- /* افکت hover برای دکمه اصلی */
434
- .stButton>button:hover,
435
- [data-testid="baseButton-primary"]:hover,
436
- .accept-btn:hover,
437
- div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > button:hover {
438
- background-color: #5a0a96 !important; /* بنفش تیره‌تر */
439
- transform: translateY(-1px) !important; /* حرکت جزئی به بالا */
440
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; /* سایه بیشتر */
441
- }
442
-
443
- /* دکمه ثانویه (سفید با حاشیه بنفش) */
444
- .stFormSubmitButton>button,
445
- [data-testid="baseButton-secondary"],
446
- .reject-btn {
447
- background-color: var(--primary) !important;
448
- color: white !important;
449
- border: none !important;
450
- border-radius: 8px !important;
451
- padding: 10px 20px !important;
452
- font-weight: bold !important;
453
- transition: all 0.3s ease !important; /* افزودن transition بر��ی انیمیشن نرم */
454
- }
455
-
456
- /* افکت hover برای دکمه ثانویه */
457
- .stFormSubmitButton>button:hover,
458
- [data-testid="baseButton-secondary"]:hover,
459
- .reject-btn:hover {
460
- background-color: #5a0a96 !important; /* بنفش تیره‌تر */
461
- transform: translateY(-1px) !important; /* حرکت جزئی به بالا */
462
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; /* سایه بیشتر */
463
- }
464
-
465
- /* استایل تضمینی برای رادیو باتن‌ها - نسخه نهایی */
466
- .stRadio > div > div > div > div > div > label,
467
- .stRadio > div > div > div > div > label,
468
- .stRadio > div > div > div > label,
469
- .stRadio > div > div > label,
470
- .stRadio > div > label,
471
- .stRadio > label {
472
- color: #000000 !important;
473
- -webkit-text-fill-color: #000000 !important; /* برای مرورگرهای وبکیت */
474
- font-size: 20px !important
475
- }
476
-
477
- .stRadio span {
478
- color: #000000 !important;
479
- -webkit-text-fill-color: #000000 !important;
480
- font-size: 20px !important
481
- }
482
-
483
- /* اگر باز هم مشکل داشت این را اضافه کنید */
484
- .stRadio > div > div > div > div > div > label > div:first-child,
485
- .stRadio > div > div > div > div > label > div:first-child,
486
- .stRadio > div > div > div > label > div:first-child,
487
- .stRadio > div > div > label > div:first-child,
488
- .stRadio > div > label > div:first-child,
489
- .stRadio > label > div:first-child {
490
- color: #000000 !important;
491
- -webkit-text-fill-color: #000000 !important;
492
- font-size: 20px !important
493
- }
494
-
495
- /* استایل پایه برای هشدارها */
496
- .stAlert .st-ae {
497
- border-right: 4px solid #ffc107 !important;
498
- background-color: #fff3cd !important;
499
- border-radius: 8px !important;
500
- padding: 16px !important;
501
- color: #856404 !important;
502
- }
503
-
504
- /* آیکون هشدار */
505
- .stAlert .st-af {
506
- color: #ffc107 !important;
507
- font-size: 20px !important;
508
- }
509
-
510
- /* متن هشدار */
511
- .stAlert .st-ag {
512
- color: #856404 !important;
513
- font-family: 'B Nazanin' !important;
514
- font-size: 16px !important;
515
- margin-right: 8px !important;
516
- }
517
-
518
- /* حالت hover برای هشدار */
519
- .stAlert .st-ae:hover {
520
- box-shadow: 0 2px 8px rgba(255, 193, 7, 0.2) !important;
521
- transform: translateX(2px);
522
- transition: all 0.3s ease;
523
- }
524
-
525
- /* استایل برای دکمه بستن هشدار */
526
- .stAlert .st-ah {
527
- color: #856404 !important;
528
- }
529
-
530
- /* استایل مخصوص موبایل */
531
- @media (max-width: 768px) {
532
- .stAlert .st-ae {
533
- padding: 12px !important;
534
- font-size: 15px !important;
535
- }
536
- }
537
-
538
-
539
- /* متن گزینه‌ها */
540
- .stRadio span {
541
- color: #000000 !important; /* مشکی خالص */
542
- font-size: 20px !important;
543
- padding-right: 5px !important;
544
- }
545
-
546
- /* حالت hover */
547
- .stRadio label:hover span {
548
- color: #333333 !important; /* مشکی کمی روشن‌تر */
549
- font-size: 20px !important;
550
- }
551
-
552
-
553
-
554
- /* تنظیمات مخصوص موبایل */
555
- @media (max-width: 768px) {
556
- .folium-map {
557
- height: 300px !important;
558
- }
559
-
560
- /* فرم‌ها در موبایل */
561
- .stTextInput>label,
562
- .stNumberInput>label,
563
- .stSelectbox>label {
564
- font-size: 16px !important;
565
- padding: 4px 0 !important;
566
- }
567
-
568
- .stTextInput input,
569
- .stNumberInput input,
570
- .stSelectbox select {
571
- font-size: 16px !important;
572
- padding: 12px !important;
573
- height: auto !important;
574
- }
575
-
576
- .stButton>button {
577
- font-size: 17px !important;
578
- padding: 8px 16px !important;
579
- }
580
-
581
- .stMarkdown h2 {
582
- font-size: 22px !important;
583
- }
584
-
585
- .stMarkdown h3 {
586
- font-size: 19px !important;
587
- }
588
-
589
- .stMarkdown p {
590
- font-size: 17px !important;
591
- }
592
-
593
- .stSelectbox [role="listbox"] {
594
- font-size: 18px !important;
595
- }
596
- .stRadio span {
597
- color: #000000 !important; /* مشکی خالص */
598
- font-size: 20px !important;
599
- padding-right: 5px !important;
600
- }
601
-
602
- /* حالت hover */
603
- .stRadio label:hover span {
604
- color: #333333 !important; /* مشکی کمی روشن‌تر */
605
- font-size: 20px !important;
606
- }
607
- }
608
-
609
- /* ======== تنظیمات پایه واکنش‌گرا ======== */
610
- :root {
611
- /* اندازه‌های پایه بر اساس عرض 375px (آیفون X) */
612
- --base-font-size: 20px;
613
- --base-heading1-size: 24px;
614
- --base-heading2-size: 22px;
615
- --base-heading3-size: 20px;
616
- --base-input-font: 16px;
617
- --base-button-font: 16px;
618
- }
619
-
620
- /* ======== تنظیمات پویا بر اساس عرض دستگاه ======== */
621
- @media (max-width: 400px) {
622
- :root {
623
- --base-font-size: 17px;
624
- --base-heading1-size: 22px;
625
- --base-heading2-size: 18px;
626
- --base-heading3-size: 16px;
627
- --base-input-font: 14px;
628
- --base-button-font: 14px;
629
- }
630
- }
631
-
632
- @media (min-width: 401px) and (max-width: 768px) {
633
- :root {
634
- --base-font-size: 17px;
635
- --base-heading1-size: 24px;
636
- --base-heading2-size: 20px;
637
- --base-heading3-size: 18px;
638
- --base-input-font: 16px;
639
- --base-button-font: 16px;
640
- }
641
- }
642
-
643
- /* ======== اعمال اندازه‌های پویا ======== */
644
- * {
645
- font-size: var(--base-font-size) !important;
646
- }
647
-
648
- h1 {
649
- font-size: var(--base-heading1-size) !important;
650
- }
651
-
652
- h2 {
653
- font-size: var(--base-heading2-size) !important;
654
- }
655
-
656
- h3 {
657
- font-size: var(--base-heading3-size) !important;
658
- }
659
-
660
- .stTextInput input,
661
- .stNumberInput input,
662
- .stSelectbox select,
663
- .stTextArea textarea {
664
- font-size: var(--base-input-font) !important;
665
- }
666
-
667
- .stButton>button {
668
- font-size: var(--base-button-font) !important;
669
- }
670
-
671
- /* ======== تنظیمات نقشه واکنش‌گرا ======== */
672
- .folium-map {
673
- height: calc(100vw * 0.8) !important; /* ارتفاع متناسب با عرض */
674
- max-height: 400px !important;
675
- }
676
-
677
- /* ======== تنظیمات کانتینرها ======== */
678
- .price-container,
679
- .explanation-item {
680
- padding: calc(var(--base-font-size) * 0.8) !important;
681
- margin: calc(var(--base-font-size) * 0.5) 0 !important;
682
- }
683
-
684
- /* ======== تنظیمات دکمه‌ها ======== */
685
- .stButton>button {
686
- padding: calc(var(--base-font-size) * 0.7) calc(var(--base-font-size) * 1.2) !important;
687
- }
688
- /* اضافه کردن این بخش جدید */
689
- [data-testid="stAppViewContainer"] img {
690
- max-width: 100% !important;
691
- height: auto !important;
692
- display: block !important;
693
- padding: 0 !important;
694
- margin: 0 auto !important;
695
- }
696
-
697
- /* برای تضمین نمایش در حالت موبایل شبیه‌سازی شده */
698
- @media (min-width: 768px) {
699
- [data-testid="stAppViewContainer"] img {
700
- max-width: 200px !important;
701
- }
702
- }
703
-
704
-
705
- /* تنظیم justify برای تمام متن‌ها */
706
- [data-testid="stAppViewContainer"] * {
707
- text-align: justify !important;
708
- text-justify: inter-word !important;
709
- }
710
-
711
- /* استثناها برای عناصری که نباید justify شوند */
712
- [data-testid="stAppViewContainer"] .stRadio>label,
713
- [data-testid="stAppViewContainer"] .stCheckbox>label,
714
- [data-testid="stAppViewContainer"] .stSelectbox>label,
715
- [data-testid="stAppViewContainer"] .stTextInput>label,
716
- [data-testid="stAppViewContainer"] .stNumberInput>label,
717
- [data-testid="stAppViewContainer"] input,
718
- [data-testid="stAppViewContainer"] select,
719
- [data-testid="stAppViewContainer"] textarea,
720
- [data-testid="stAppViewContainer"] .rahyar-title,
721
- [data-testid="stAppViewContainer"] .rahyar-subtitle {
722
- direction: rtl !important;
723
- text-align: right !important;
724
- text-justify: auto !important;
725
- }
726
-
727
- /* تنظیمات برای لیست‌ها */
728
- [data-testid="stAppViewContainer"] ul,
729
- [data-testid="stAppViewContainer"] ol {
730
- padding-right: 25px !important;
731
- }
732
-
733
- [data-testid="stAppViewContainer"] p:not(.stButton p):not(.stDownloadButton p):not(.stFormSubmitButton p) {
734
- margin-bottom: 1em !important;
735
- line-height: 1.8 !important;
736
- }
737
-
738
- /* استایل برای اسلایدر استاندارد Streamlit (فلیپ‌شده، شبیه اسلایدر سفارشی) */
739
- .stSlider {
740
- direction: ltr !important; /* حفظ ltr برای اسلایدر */
741
- width: 100% !important;
742
- margin: 0 auto !important;
743
- padding: 0 !important;
744
- position: relative !important;
745
- transform: scaleX(-1) !important; /* فلیپ کردن اسلایدر */
746
- background: #f8f9fa !important; /* پس‌زمینه شبیه اسلایدرهای سفارشی */
747
- border-radius: 8px !important;
748
- padding: 10px 0 !important; /* فاصله داخلی برای حس سفارشی */
749
- }
750
-
751
- /* مسیر اسلایدر */
752
- .stSlider [role="slider"] {
753
- background-color: #d8d2f3 !important; /* رنگ مسیر، بنفش خیلی روشن */
754
- height: 14px !important; /* مسیر ضخیم‌تر */
755
- border-radius: 7px !important;
756
- width: 100% !important;
757
- position: relative !important;
758
- transform: scaleX(-1) !important; /* معکوس کردن مسیر */
759
- box-shadow: inset 0 2px 4px rgba(0,0,0,0.1) !important; /* سایه داخلی */
760
- }
761
-
762
- /* دستگیره اسلایدر */
763
- .stSlider [role="slider"]::-webkit-slider-thumb {
764
- background-color: #6a0dad !important; /* رنگ دستگیره، بنفش تیره */
765
- border: 3px solid #ffffff !important; /* حاشیه سفید */
766
- width: 28px !important; /* دستگیره بزرگ‌تر */
767
- height: 28px !important;
768
- border-radius: 50% !important; /* دایره‌ای */
769
- cursor: pointer !important;
770
- box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important; /* سایه قوی */
771
- position: relative !important;
772
- transition: all 0.3s ease !important; /* انیمیشن نرم */
773
- transform: scaleX(-1) !important; /* معکوس کردن دستگیره */
774
- }
775
-
776
- .stSlider [role="slider"]::-webkit-slider-thumb:hover {
777
- transform: scaleX(-1) scale(1.3) !important; /* بزرگ شدن موقع هاور */
778
- box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important; /* سایه قوی‌تر */
779
- }
780
-
781
- .stSlider [role="slider"]::-webkit-slider-thumb:active {
782
- transform: scaleX(-1) scale(1.2) !important; /* کمی کوچک‌تر موقع کلیک */
783
- }
784
-
785
- .stSlider [role="slider"]::-moz-range-thumb {
786
- background-color: #6a0dad !important;
787
- border: 3px solid #ffffff !important;
788
- width: 28px !important;
789
- height: 28px !important;
790
- border-radius: 50% !important;
791
- cursor: pointer !important;
792
- box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
793
- position: relative !important;
794
- transition: all 0.3s ease !important;
795
- transform: scaleX(-1) !important;
796
- }
797
-
798
- .stSlider [role="slider"]::-moz-range-thumb:hover {
799
- transform: scaleX(-1) scale(1.3) !important;
800
- box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important;
801
- }
802
-
803
- .stSlider [role="slider"]::-moz-range-thumb:active {
804
- transform: scaleX(-1) scale(1.2) !important;
805
- }
806
-
807
- .stSlider [role="slider"]::-webkit-slider-runnable-track {
808
- background-color: #d8d2f3 !important;
809
- height: 14px !important;
810
- border-radius: 7px !important;
811
- }
812
-
813
- .stSlider [role="slider"]::-moz-range-track {
814
- background-color: #d8d2f3 !important;
815
- height: 14px !important;
816
- border-radius: 7px !important;
817
- }
818
-
819
-
820
-
821
- /* لیبل‌های اسلایدر */
822
- .slider-labels {
823
- display: flex !important;
824
- justify-content: space-between !important;
825
- font-size: 16px !important; /* لیبل‌ها بزرگ‌تر */
826
- font-weight: bold !important;
827
- margin-bottom: 12px !important;
828
- color: #333333 !important;
829
- font-family: 'B Nazanin' !important;
830
- direction: ltr !important; /* هماهنگی با فلیپ */
831
- width: 100% !important;
832
- }
833
-
834
- /* استایل برای متن پاسخ انتخاب‌شده */
835
- .slider-response {
836
- text-align: center !important;
837
- color: #6a0dad !important;
838
- font-weight: bold !important;
839
- font-size: 18px !important; /* متن بزرگ‌تر */
840
- margin-top: 15px !important;
841
- font-family: 'B Nazanin' !important;
842
- direction: rtl !important;
843
- background: #f0e6ff !important; /* پس‌زمینه بنفش روشن */
844
- padding: 8px !important;
845
- border-radius: 6px !important;
846
- }
847
-
848
- /* جلوگیری از پریدن اسلایدر */
849
- .stSlider > div {
850
- width: 100% !important;
851
- padding: 0 !important;
852
- margin: 0 !important;
853
- direction: ltr !important;
854
- }
855
- </style>
856
- """, unsafe_allow_html=True)
857
-
858
- # ========== توابع اصلی ==========
859
- def custom_likert_slider(question_data):
860
- """نمایش سوال لیکرت با اسلایدر استاندارد Streamlit (فلیپ‌شده)"""
861
- question = question_data["question"]
862
- key = question_data["key"]
863
- points = question_data["scale"]
864
- labels = question_data.get("labels", ["کمترین", "بیشترین"]) # لیبل‌های پیش‌فرض
865
-
866
- # مقدار پیش‌فرض (وسط طیف)
867
- default_value = (points + 1) // 2
868
-
869
- # نمایش سوال
870
- st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
871
-
872
- # لیبل‌های دو طرف (جابه‌جا شده برای فلیپ)
873
- st.markdown(
874
- f"""
875
- <div class="slider-labels">
876
- <span>{labels[1]}</span> <!-- لیبل بیشترین (مثل کاملاً منصفانه) سمت راست -->
877
- <span>{labels[0]}</span> <!-- لیبل کمترین (مثل کاملاً نامنصفانه) سمت چپ -->
878
- </div>
879
- """,
880
- unsafe_allow_html=True
881
- )
882
-
883
- # اسلایدر استاندارد Streamlit
884
- raw_value = st.slider(
885
- "",
886
- min_value=1,
887
- max_value=points,
888
- value=st.session_state.get(key, default_value),
889
- step=1,
890
- key=key,
891
- format="%d"
892
- )
893
-
894
- # معکوس کردن مقدار برای ذخیره‌سازی (۱ به ۷، ۲ به ۶، و غیره)
895
- flipped_value = points + 1 - raw_value
896
-
897
- # نمایش پاسخ انتخاب‌شده (مقدار معکوس‌شده)
898
- st.markdown(
899
- f"""
900
- <p class="slider-response">
901
- پاسخ انتخاب‌شده: {flipped_value}
902
- </p>
903
- """,
904
- unsafe_allow_html=True
905
- )
906
-
907
- # ذخیره مقدار فلیپ‌شده
908
- return flipped_value
909
-
910
- def create_ride_map():
911
- """ایجاد نقشه سفر با Folium - نسخه اصلاح شده با مناطق عمومی"""
912
- # نقاط تقریبی برای مناطق عمومی جنوب و غرب تهران
913
- south_tehran = [35.65, 51.38] # منطقه عمومی جنوب تهران
914
- west_tehran = [35.72, 51.31] # منطقه عمومی غرب تهران
915
-
916
- # مرکز نقشه بین دو نقطه
917
- m = folium.Map(location=[35.685, 51.315], zoom_start=11)
918
-
919
- # ایجاد دایره برای مبدأ (جنوب تهران)
920
- folium.Circle(
921
- location=south_tehran,
922
- radius=2500, # شعاع 1.5 کیلومتر
923
- popup="<b>مبدأ:</b> منطقه جنوب تهران",
924
- color="#6a0dad",
925
- fill=True,
926
- fill_color="#6a0dad",
927
- fill_opacity=0.2,
928
- weight=2
929
- ).add_to(m)
930
-
931
- # ایجاد دایره برای مقصد (غرب تهران)
932
- folium.Circle(
933
- location=west_tehran,
934
- radius=2500, # شعاع 1.5 کیلومتر
935
- popup="<b>مقصد:</b> منطقه غرب تهران",
936
- color="#ff0000",
937
- fill=True,
938
- fill_color="#ff0000",
939
- fill_opacity=0.2,
940
- weight=2
941
- ).add_to(m)
942
-
943
- # خط ارتباطی بین دو منطقه
944
- folium.PolyLine(
945
- [south_tehran, west_tehran],
946
- color="#6a0dad",
947
- weight=3,
948
- opacity=0.7,
949
- dash_array='5, 5' # خط چین
950
- ).add_to(m)
951
-
952
- return m
953
-
954
- def show_explanation(exp_type):
955
- """نمایش توضیحات قیمت"""
956
- explanations = {
957
- "input": [
958
- " سطح تقاضا در منطقه: زیاد (+)",
959
- " تعداد رانندگان فعال: کم (+)",
960
- " زمان روز: ساعت اوج ترافیک (+)",
961
- "شرایط جوی: هوای بارانی (++)"
962
- ],
963
- "counterfactual": [
964
- "اگر این سفر را 1 ساعت دیرتر درخواست کنید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیش‌تر، زمان بهتر روز و شرایط جوی بهتر، احتمالاً قیمت حدوداً 40٪ کمتر (120 هزار تومان) خواهد بود.",
965
- ]
966
- }
967
-
968
- if exp_type != "control":
969
- if exp_type == "input":
970
- st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
971
- st.markdown("""
972
- <div style="direction: rtl; text-align: right;">
973
- <span style="font-size: 0.9em; color: #666;">(تعداد علامت + نشان دهنده شدت اثر عامل بر قیمت است)</span>
974
- </div>
975
- """, unsafe_allow_html=True)
976
- for item in explanations.get(exp_type, []):
977
- st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
978
-
979
- elif exp_type == "counterfactual":
980
- st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
981
- for item in explanations.get(exp_type, []):
982
- st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
983
-
984
- def create_likert_question(question, key, scale_type="5point"):
985
- """نمایش سوال لیکرت با اسلایدر نقطه‌ای"""
986
- left_label = "کاملاً مخالفم" if scale_type == "7point" else "کاملاً مخالفم"
987
- right_label = "کاملاً موافقم" if scale_type == "7point" else "کاملاً موافقم"
988
-
989
- st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
990
-
991
- max_value = 7 if scale_type == "7point" else 5
992
-
993
- # اضافه کردن attribute برای انتخاب استایل مناسب
994
- slider_container = st.empty()
995
- with slider_container:
996
- st.markdown(f'<div data-testid="stSlider" data-discrete="{"seven-point" if scale_type=="7point" else "five-point"}">', unsafe_allow_html=True)
997
- value = st.slider(
998
- "",
999
- min_value=1,
1000
- max_value=max_value,
1001
- value=(max_value+1)//2,
1002
- step=1,
1003
- key=f"slider_{key}"
1004
- )
1005
- st.markdown('</div>', unsafe_allow_html=True)
1006
-
1007
- st.markdown(
1008
- f"""
1009
- <div class="slider-labels">
1010
- <span>{left_label}</span>
1011
- <span>{right_label}</span>
1012
- </div>
1013
- <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
1014
- پاسخ شما: {value} ({'کاملاً مخالفم' if value==1 else 'کاملاً موافقم' if value==max_value else 'خنثی' if value==((max_value+1)//2) else 'موافقم' if value>((max_value+1)//2) else 'مخالفم'})
1015
- </p>
1016
- """,
1017
- unsafe_allow_html=True
1018
- )
1019
- return value
1020
-
1021
- # ========== توابع مدیریت داده‌ها ==========
1022
-
1023
- def get_credentials():
1024
- """دریافت اعتبارنامه از Secrets"""
1025
- try:
1026
- service_account_json = os.environ.get('GCP_SERVICE_ACCOUNT')
1027
- if not service_account_json:
1028
- st.error("مقدار GCP_SERVICE_ACCOUNT در محیط یافت نشد")
1029
- return None
1030
-
1031
- service_account_info = json.loads(service_account_json)
1032
- creds = Credentials.from_service_account_info(
1033
- service_account_info,
1034
- scopes=[
1035
- "https://www.googleapis.com/auth/spreadsheets",
1036
- "https://www.googleapis.com/auth/drive.file"
1037
- ]
1038
- )
1039
- return creds
1040
- except Exception as e:
1041
- st.error(f"خطا در دریافت اعتبارنامه: {str(e)}")
1042
- return None
1043
-
1044
- def save_to_sheet(data):
1045
- try:
1046
- creds = get_credentials()
1047
- if not creds:
1048
- return False
1049
-
1050
- client = gspread.authorize(creds)
1051
- spreadsheet = client.open_by_key(SHEET_ID)
1052
- worksheet = spreadsheet.worksheet(SHEET_NAME)
1053
-
1054
- row_data = [
1055
- data.get("start_time", ""), # زمان شروع
1056
- data.get("end_time", ""), # زمان پایان
1057
- data.get("completion_time", ""), # مدت زمان تکمیل (ثانیه)
1058
- data.get("scenario_type", ""),
1059
- data.get("price", ""),
1060
- data.get("age", ""),
1061
- data.get("gender", ""),
1062
- data.get("education", ""),
1063
- data.get("ride_frequency", ""),
1064
- data.get("related_education_job",""),
1065
- data.get("city",""),
1066
- data.get("user_contact", ""),
1067
- data.get("price_accepted", ""),
1068
-
1069
- # سوالات توجه
1070
- data.get("attention_check1", ""),
1071
- data.get("attention_check2", ""),
1072
-
1073
-
1074
- # سوالات distributive (7 گزینه‌ای)
1075
- data.get("distributive_1", ""),
1076
- data.get("distributive_2", ""),
1077
- data.get("distributive_3", ""),
1078
-
1079
- # سوالات procedural (7 گزینه‌ای)
1080
- data.get("procedural_1", ""),
1081
- data.get("procedural_2", ""),
1082
- data.get("procedural_3", ""),
1083
-
1084
- # سوالات informational (5 گزینه‌ای)
1085
- data.get("informational_1", ""),
1086
- data.get("informational_2", ""),
1087
- data.get("informational_3", ""),
1088
- data.get("informational_4", ""),
1089
- data.get("informational_5", ""),
1090
-
1091
- # سوالات manipulation
1092
- data.get ("trust", ""),
1093
- data.get("pricing_method", ""),
1094
- data.get("price_increase", ""),
1095
- data.get("explanation_received", ""),
1096
- data.get("explanation_type", "")
1097
- ]
1098
-
1099
- worksheet.append_row(row_data)
1100
- return True
1101
-
1102
- except Exception as e:
1103
- st.error(f"خطا در ذخیره‌سازی: {str(e)}")
1104
- return False
1105
-
1106
- def create_likert_question(question, key, scale_type="5point"):
1107
- """نمایش سوال لیکرت با اسلایدر نقطه‌ای"""
1108
- # تعریف لیبل‌های دو طرف
1109
- left_label = "کاملاً مخالفم" if scale_type == "7point" else "کاملاً مخالفم"
1110
- right_label = "کاملاً موافقم" if scale_type == "7point" else "کاملاً موافقم"
1111
-
1112
- st.markdown(f"<p style='font-size:16px; margin-bottom:5px;'>{question}</p>", unsafe_allow_html=True)
1113
-
1114
- # ایجاد اسلایدر
1115
- max_value = 7 if scale_type == "7point" else 5
1116
- value = st.slider(
1117
- "",
1118
- min_value=1,
1119
- max_value=max_value,
1120
- value=(max_value+1)//2, # مقدار میانی
1121
- step=1,
1122
- key=f"slider_{key}"
1123
- )
1124
-
1125
- # نمایش متن دو طرف اسلایدر
1126
- st.markdown(
1127
- f"""
1128
- <div class="slider-labels">
1129
- <span>{left_label}</span>
1130
- <span>{right_label}</span>
1131
- </div>
1132
- <p style='text-align:center; color:#6a0dad; font-weight:bold;'>
1133
- پاسخ شما: {value}
1134
- </p>
1135
- """,
1136
- unsafe_allow_html=True
1137
- )
1138
- return value
1139
-
1140
- # ========== بخش‌های فرم ==========
1141
-
1142
- def welcome_page():
1143
- """صفحه خوشامدگویی"""
1144
- st.markdown("""
1145
- <div style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;">
1146
- <div style="text-align: center;">
1147
- <img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png"
1148
- alt="لوگو دانشگاه شریف"
1149
- style="width: 200px; height: auto; margin-bottom: 10px;">
1150
- </div>
1151
- <div style="flex: 1;">
1152
- <h3>به پرسشنامه ما خوش آمدید 🌟</h3>
1153
- <p>با سلام و احترام،</p>
1154
- <p>از شما دعوت می‌شود در یک پژوهش دانشگاهی شرکت کنید که در قالب پایان‌نامه کارشناسی‌ارشد در دانشگاه صنعتی شریف انجام می‌شود. این تحقیق به بررسی ادراک مصرف‌کنندگان از انصاف در قیمت‌گذاریِ اپلیکیشن‌های تاکسی اینترنتی (مانند اسنپ و تپسی 🚖) می‌پردازد.</p>
1155
- <p>شرکت در این مطالعه کاملاً داوطلبانه است؛ پاسخ دقیقی برای سوالات وجود ندارد و پاسخ‌های صادقانه شما فقط برای پیش‌برد اهداف علمی تحلیل خواهد شد.</p>
1156
- <p>پر کردن این پرسشنامه کمتر از 5 دقیقه وقت شما را می‌گیرد و پاسخ‌های ارزشمند شما کمک شایانی به ارتقای دانش علمی خواهد کر��. پیشاپیش از مشارکت شما صمیمانه سپاسگزاریم 🙏</p>
1157
- <p>برای آغاز پرسشنامه، لطفاً روی دکمه زیر کلیک کنید 👇🏻</p>
1158
- </div>
1159
- </div>
1160
- </div>
1161
- """, unsafe_allow_html=True)
1162
-
1163
- if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
1164
- st.session_state.current_page = "scenario_explanation"
1165
- st.session_state.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1166
- st.rerun()
1167
-
1168
-
1169
- def scenario_explanation():
1170
- """توضیح سناریو"""
1171
- col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
1172
- with col1:
1173
- st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1174
- try:
1175
- st.image("rahyar.png", width=80)
1176
- except:
1177
- st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1178
- st.markdown('</div>', unsafe_allow_html=True)
1179
- with col2:
1180
- st.markdown("""
1181
- <h2 class="rahyar-title">رهیار 🚖</h2>
1182
- <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1183
- """, unsafe_allow_html=True)
1184
-
1185
- st.markdown("### سناریوی تحقیق")
1186
-
1187
- st.markdown("""
1188
- <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1189
- <p>فرض کنید در روزی از روزها شما مهمان یکی از اقوام‌تان در جنوب تهران هستید. قصد دارید برای خریدی به غرب تهران بروید...</p>
1190
- <p> گوشی‌تان را از کیف درمی‌آورید. چشم‌تان به آیکون جدیدی می‌افتد؛ اپلیکیشنی به نام <strong>رهیار</strong> — نه اسنپ است و نه تپسی، اما خیلی شبیه آن‌هاست. رنگ بنفش جذابی دارد و شعارش توی ذهن‌تان می‌نشیند:
1191
- <br>«همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار»</p>
1192
- <p>با کنجکاوی اپ را باز می‌کنید. ظاهر ساده و روانی دارد. فعلاً فقط گزینه‌ی «سفر معمولی» فعال است. خبری از امکانات اضافه مثل «سفر دو‌مسیـره»، «توقف در مسیر»، «اکوپلاس» یا «موتورسوار» نیست — اما خب، رهیار تازه‌کار است و قرار است توسعه پیدا کند!.</p>
1193
- <p>مبدأ و مقصد را انتخاب می‌کنید و با قیمت مواجه می‌شوید.</p>
1194
- <p>با کلیک روی «ادامه»، اطلاعات سفر را مشاهده کنید 👇🏻</p>
1195
- </div>
1196
- """, unsafe_allow_html=True)
1197
-
1198
- if st.button("ادامه", key="continue_btn", type="primary"):
1199
- st.session_state.current_page = "map_view"
1200
- st.rerun()
1201
-
1202
- def map_view():
1203
- col1, col2 = st.columns([2, 4]) # افزایش نسبت ستون اول
1204
- with col1:
1205
- st.markdown('<div style="padding-right: 25px;">', unsafe_allow_html=True)
1206
- try:
1207
- st.image("rahyar.png", width=80)
1208
- except:
1209
- st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
1210
- st.markdown('</div>', unsafe_allow_html=True)
1211
- with col2:
1212
- st.markdown("""
1213
- <h2 class="rahyar-title">رهیار 🚖</h2>
1214
- <p class="rahyar-subtitle">همراه سفرهای درون‌شهری شما، راهی مطمئن، راهی روشن، رهیار</p>
1215
- """, unsafe_allow_html=True)
1216
-
1217
- st.markdown("""
1218
- <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
1219
- <p>مسیر سفر شما به صورت حدودی، از جنوب به غرب تهران است.</p>
1220
- <p> با توجه به اطلاعاتی که بعد از نقشه دریافت می‌کنید، تصمیم بگیرید که سفر را می‌پذیرید را رد می‌کنید.</p>
1221
- <p>سپس با کلیک بر دکمه مربوطه به بخش بعدی بروید.</p>
1222
- </div>
1223
- """, unsafe_allow_html=True)
1224
- st.markdown("### مسیر سفر شما")
1225
- folium_static(create_ride_map(), width=1000 if st.session_state.is_desktop else 800,
1226
- height=500 if st.session_state.is_desktop else 400)
1227
-
1228
- # قیمت
1229
- st.markdown(f"""
1230
- <div class="price-container">
1231
- <div style="display: flex; justify-content: space-between; align-items: center;">
1232
- <span>رهیار <span style="background-color: #e6e6fa; color: #6a0dad; padding: 2px 8px; border-radius: 12px; font-size: 14px;">معمولی</span></span>
1233
- <span class="rahyar-price">{st.session_state.price:,} تومان</span>
1234
- </div>
1235
- </div>
1236
- """, unsafe_allow_html=True)
1237
-
1238
- show_explanation(st.session_state.scenario_type)
1239
-
1240
- # دکمه‌ها
1241
- col1, col2 = st.columns(2)
1242
- with col1:
1243
- if st.button("درخواست سفر", key="accept_btn", use_container_width=True):
1244
- st.session_state.price_accepted = 1
1245
- st.session_state.current_page = "attention_check1"
1246
- st.rerun()
1247
-
1248
- with col2:
1249
- if st.button("رد سفر", key="reject_btn", use_container_width=True):
1250
- st.session_state.price_accepted = 0
1251
- st.session_state.current_page = "attention_check1"
1252
- st.rerun()
1253
-
1254
- def attention_check1():
1255
- """سوال توجه اول با دکمه سبز کاملاً عملی"""
1256
- # 1. تزریق استایل‌های سفارشی
1257
- st.markdown("""
1258
- <style>
1259
- /* مخفی کردن دکمه پیشفرض Streamlit */
1260
- div[data-testid="stButton"] > button[kind="primary"] {
1261
- display: none !important;
1262
- }
1263
- </style>
1264
- """, unsafe_allow_html=True)
1265
-
1266
- st.markdown("### سوال")
1267
-
1268
- answer = st.radio(
1269
- "رنگ لوگو و تم رنگی اپلیکیشن رهیار چگونه بود؟",
1270
- ["قرمز", "سبز", "بنفش", "آبی", "فراموش کردم"],
1271
- index=None,
1272
- key="att1_radio"
1273
- )
1274
-
1275
- # کامپوننت HTML با دکمه سبز اصلی
1276
- st.components.v1.html(f"""
1277
- <script>
1278
- function handleClick() {{
1279
- // فعال کردن دکمه مخفی Streamlit
1280
- parent.document.querySelector('div[data-testid="stButton"] > button[kind="primary"]').click();
1281
- }}
1282
- </script>
1283
-
1284
- <button onclick="handleClick()"
1285
- style="
1286
- background-color: #28a745 !important;
1287
- color: white !important;
1288
- border: none !important;
1289
- border-radius: 8px !important;
1290
- padding: 10px 20px !important;
1291
- font-weight: bold !important;
1292
- font-family: 'Vazir', sans-serif !important;
1293
- cursor: pointer !important;
1294
- margin-left: auto; /* این خط برای راست‌چین کردن مهم است */
1295
- transition: all 0.3s ease !important;
1296
- float: right;
1297
- margin-left: 15px;
1298
- "
1299
- onmouseover="this.style.backgroundColor='#218838'; this.style.boxShadow='0 4px 8px rgba(0,0,0,0.15)';"
1300
- onmouseout="this.style.backgroundColor='#28a745'; this.style.boxShadow='0 2px 5px rgba(0,0,0,0.1)';">
1301
- ادامه
1302
- </button>
1303
- """, height=70)
1304
-
1305
-
1306
- # 3. منطق اصلی دکمه (مخفی)
1307
- if st.button("ادامه", key="att1_real_btn", type="primary"):
1308
- if answer:
1309
- st.session_state.attention_check1 = answer
1310
- st.session_state.current_page = "random_likert_questions"
1311
- st.rerun()
1312
- else:
1313
- st.warning("لطفاً یک گزینه را انتخاب کنید")
1314
-
1315
- def random_likert_questions():
1316
- """نمایش سوالات لیکرت به ترتیب مشخص با اسلایدر سفارشی"""
1317
- # تعریف گروه‌های سوالات با لیبل‌های سفارشی
1318
- question_groups = [
1319
- {
1320
- "title": "عدالت توزیعی",
1321
- "key": "distributive",
1322
- "questions": [
1323
- {
1324
- "key": "distributive_1",
1325
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1326
- "scale": 7,
1327
- "labels": ["کاملاً نامنصفانه", "کاملاً منصفانه"]
1328
- },
1329
- {
1330
- "key": "distributive_2",
1331
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1332
- "scale": 7,
1333
- "labels": ["کاملاً غیرمعقول", "کاملاً معقول"]
1334
- },
1335
- {
1336
- "key": "distributive_3",
1337
- "question": "قیمتی که به شما ارائه شد، چگونه بود؟",
1338
- "scale": 7,
1339
- "labels": ["کاملاً غیرقابل قبول", "کاملاً قابل قبول"]
1340
- }
1341
- ]
1342
- },
1343
- {
1344
- "title": "سوال توجه",
1345
- "key": "attention_check",
1346
- "questions": [
1347
- {"key": "attention_check2", "question": "تا چه مقدار با دقت به سوالات پاسخ می‌دهید؟", "scale": 7, "labels": ["خیلی کم", "خیلی زیاد"]}
1348
- ]
1349
- },
1350
- {
1351
- "title": "عدالت رویه‌ای",
1352
- "key": "procedural",
1353
- "questions": [
1354
- {"key": "procedural_1", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم قابل قبول است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]},
1355
- {"key": "procedural_2", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم منصفانه است", "scale": 7, "labels": ["کاملاً مخا��فم", "کاملاً موافقم"]},
1356
- {"key": "procedural_3", "question": ".فرآیند و رویه قیمت‌گذاری پلتفرم معقول است", "scale": 7, "labels": ["کاملاً مخالفم", "کاملاً موافقم"]}
1357
- ]
1358
- },
1359
- {
1360
- "title": "عدالت اطلاعاتی",
1361
- "key": "informational",
1362
- "questions": [
1363
- {"key": "informational_1", "question": "تا چه حد رهیار دلایل تعیین قیمت را به صورت صادقانه توضیح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1364
- {"key": "informational_2", "question": "تا چه حد رهیار عوامل مؤثر بر تعیین قیمت را به طور کامل شرح داد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1365
- {"key": "informational_3", "question": "تا چه حد دلایل ارائه‌شده توسط رهیار برای تعیین قیمت منطقی و قابل قبول بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1366
- {"key": "informational_4", "question": "تا چه حد توضیحات درباره تعیین قیمت بلافاصله و در زمان مناسب نمایش داده شد؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]},
1367
- {"key": "informational_5", "question": "تا چه حد توضیحات رهیار درباره تعیین قیمت، متناسب با شرایط سفر شما بود؟", "scale": 7, "labels": ["هیچ", "خیلی زیاد"]}
1368
- ]
1369
- }
1370
- ]
1371
-
1372
- # مقداردهی اولیه
1373
- if 'current_likert_group' not in st.session_state:
1374
- st.session_state.current_likert_group = 0
1375
-
1376
- # دریافت گروه جاری
1377
- current_group = question_groups[st.session_state.current_likert_group]
1378
-
1379
- # نمایش عنوان گروه
1380
- st.markdown("""
1381
- <style>
1382
- .guide-text p {
1383
- font-size: 14px !important;
1384
- }
1385
- </style>
1386
- """, unsafe_allow_html=True)
1387
- st.markdown("""
1388
- <div class="guide-text" style="display: flex; flex-direction: column; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 15px;">
1389
- <div style="flex: 1;">
1390
- <h3>راهنمای پاسخ:</h3>
1391
- <p>برای پاسخ به سوالات، با یک طیف کشویی مواجه خواهید شد.</p>
1392
- <p>در این طیف 7 نقطه وجود دارد:
1393
- <br>- سمت چپ: کمترین مقدار
1394
- <br>- سمت راست: بیشترین مقدار
1395
- </p>
1396
- <p>نقطه روی کشو را جابجا کنید و در مقداری که پاسخ شماست متوقف شوید.</p>
1397
- <p>پاسخ شما زیر طیف نمایش داده خواهد شد.</p>
1398
- <p>اگر از پاسخ‌هایتان مطمئن هستید، روی دکمه «ادامه» کلیک کنید.</p>
1399
- </div>
1400
- </div>
1401
- """, unsafe_allow_html=True)
1402
-
1403
- # نمایش تمام سوالات این گروه در یک صفحه
1404
- for question in current_group['questions']:
1405
- answer = custom_likert_slider(question)
1406
- st.session_state.answers[question["key"]] = answer
1407
-
1408
- # دکمه ادامه/اتمام
1409
- button_label = "ادامه به گروه بعدی" if st.session_state.current_likert_group < len(question_groups)-1 else "اتمام پرسشنامه"
1410
-
1411
- if st.button(button_label):
1412
- # ذخیره پاسخ‌ها قبل از رفتن به گروه بعدی
1413
- for question in current_group['questions']:
1414
- if question["key"] in st.session_state:
1415
- st.session_state.answers[question["key"]] = st.session_state[question["key"]]
1416
-
1417
- # رفتن به گروه بعدی یا صفحه پایانی
1418
- if st.session_state.current_likert_group < len(question_groups) - 1:
1419
- st.session_state.current_likert_group += 1
1420
- st.rerun()
1421
- else:
1422
- st.session_state.current_page = "explanation_questions"
1423
- st.rerun()
1424
-
1425
- def explanation_questions():
1426
- """نمایش سوالات تکمیلی به صورت مرحله‌ای با دکمه ادامه"""
1427
- st.markdown("### 📋 سوالات تکمیلی")
1428
-
1429
- # لیست سوالات به ترتیب نمایش
1430
- questions = [
1431
- {
1432
- "key": "trust",
1433
- "label": "آیا شما به تصمیم‌گیری‌هایی که توسط هوش مصنوعی انجام می‌شود اعتماد دارید؟",
1434
- "options": ["بله", "خیر", "نظری ندارم"],
1435
- "required": True
1436
- },
1437
- {
1438
- "key": "pricing_method",
1439
- "label": "به نظر شما پلتفرم قیمت را چگونه تعیین می‌کند؟",
1440
- "options": [
1441
- "به صورت دستی توسط تیم پلتفرم",
1442
- "به صورت خودکار توسط هوش مصنوعی و الگوریتم‌ها",
1443
- "ترکیبی از هر دو روش",
1444
- "نظری ندارم"
1445
- ],
1446
- "required": True
1447
- },
1448
- {
1449
- "key": "price_increase",
1450
- "label": "آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
1451
- "options": ["بله", "خیر", "مطمئن نیستم"],
1452
- "required": True
1453
- },
1454
- {
1455
- "key": "explanation_received",
1456
- "label": "آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
1457
- "options": ["بله", "خیر"],
1458
- "required": True
1459
- },
1460
- {
1461
- "key": "explanation_type",
1462
- "label": "اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
1463
- "options": [
1464
- "بر اساس عواملی که در قیمت‌گذاری لحاظ شده‌اند",
1465
- "شامل سناریوهای جایگزین که می‌توانستند قیمت متفاوتی ایجاد کنند",
1466
- "توضیحی دریافت نکردم"
1467
- ],
1468
- "required": False,
1469
- "condition": lambda: st.session_state.get("explanation_received") == "بله"
1470
- }
1471
- ]
1472
-
1473
- # مقداردهی اولیه step اگر وجود ندارد
1474
- if "explanation_step" not in st.session_state:
1475
- st.session_state.explanation_step = 0
1476
-
1477
- # اگر همه سوالات پاسخ داده شده‌اند، به صفحه بعدی برو
1478
- if st.session_state.explanation_step >= len(questions):
1479
- st.session_state.current_page = "demographic"
1480
- st.rerun()
1481
- return
1482
-
1483
- # دریافت سوال جاری
1484
- current_q = questions[st.session_state.explanation_step]
1485
-
1486
- # بررسی شرط نمایش برای سوالات اختیاری
1487
- if "condition" in current_q and not current_q["condition"]():
1488
- st.session_state[current_q["key"]] = "N/A"
1489
- st.session_state.explanation_step += 1
1490
- st.rerun()
1491
- return
1492
-
1493
- # نمایش سوال جاری
1494
- answer = st.radio(
1495
- current_q["label"],
1496
- current_q["options"],
1497
- index=None,
1498
- key=f"explanation_q_{current_q['key']}"
1499
- )
1500
-
1501
- # دکمه ادامه
1502
- if st.button("ادامه", key=f"continue_{current_q['key']}"):
1503
- if answer is None and current_q["required"]:
1504
- st.warning("لطفاً یک گزینه را انتخاب کنید")
1505
- else:
1506
- # ذخیره پاسخ
1507
- st.session_state[current_q["key"]] = answer if answer is not None else "N/A"
1508
-
1509
- # افزایش شماره مرحله
1510
- st.session_state.explanation_step += 1
1511
-
1512
- # رفرش صفحه برای نمایش سوال بعدی
1513
- st.rerun()
1514
-
1515
- def demographic_form():
1516
- """فرم اطلاعات دموگرافیک"""
1517
- st.markdown("### 📝 اطلاعات دموگرافیک")
1518
- st.markdown("""
1519
- <div>
1520
- <p>لطفاً اطلاعات زیر را صادقانه و به دقت وارد نمایید.</p>
1521
- <p>همانطور که گفته شد،این اطلاعات کاملاً محرمانه نزد محقق خواهد ماند و در جهت اهداف پژوهشی استفاده خواهد شد.</p>
1522
- </div>
1523
- """, unsafe_allow_html=True)
1524
-
1525
- with st.form("demographic_form"):
1526
- age = st.number_input("سن", min_value=18, max_value=100, value=None, placeholder="سن خود را وارد کنید")
1527
- gender = st.selectbox("جنسیت", ["", "مرد", "زن", "سایر"], index=0)
1528
- education = st.selectbox("تحصیلات", ["", "دیپلم", "لیسانس", "فوق لیسانس", "دکترا"], index=0)
1529
- city = st.selectbox("لطفاً استان محل سکونت خود را انتخاب بفرمایید.",
1530
- ["", "آذربایجان شرقی", "آذربایجان غربی", "اردبیل", "اصفهان", "البرز", "ایلام",
1531
- "بوشهر", "تهران", "چهارمحال و بختیاری", "خراسان جنوبی", "خراسان رضوی", "خراسان شمالی",
1532
- "خوزستان", "زنجان", "سمنان", "سیستان و بلوچستان", "فارس", "قزوین", "قم", "کردستان",
1533
- "کرمان", "کرمانشاه", "کهگیلویه و بویراحمد", "گلستان", "گیلان", "لرستان", "مازندران",
1534
- "مرکزی", "هرمزگان", "همدان", "یزد"], index=0)
1535
- related_education_job = st.selectbox("رشته تحصیلی/شغل شما در کدام‌یک از دسته‌های زیر قرار دارد؟",
1536
- ["", "مهندسی", "درمانی", "فره��گی", "مدیریتی (مالی)",
1537
- "مدیریتی (بازاریابی)", "مدیریتی (سایر)", "روانشناسی",
1538
- "اقتصادی", "حقوقی", "هنری", "ورزشی", "زبان", "غیره"], index=0)
1539
- ride_frequency = st.selectbox("دفعات استفاده از سرویس‌های اشتراک سفر در ماه",
1540
- ["", "هیچوقت", "کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"], index=0)
1541
-
1542
- submitted = st.form_submit_button("ادامه")
1543
- if submitted:
1544
- if not all([age, gender, education, city, related_education_job, ride_frequency]):
1545
- st.error("لطفاً تمام فیلدها را پر کنید")
1546
- else:
1547
- st.session_state.demographic_data = {
1548
- "age": age,
1549
- "gender": gender,
1550
- "education": education,
1551
- "city": city,
1552
- "ride_frequency": ride_frequency,
1553
- "related_education_job": related_education_job
1554
- }
1555
- st.session_state.current_page = "contact"
1556
- st.rerun()
1557
-
1558
- def user_contact():
1559
- """راه ارتباطی ساده"""
1560
- st.markdown("""
1561
- <div style="text-align: center; margin-bottom: 30px;">
1562
- <h3>📩 راه ارتباطی شما (اختیاری)</h3>
1563
- <p>در صورت تمایل به شرکت در قرعه‌کشی می‌توانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:</p>
1564
- </div>
1565
- """, unsafe_allow_html=True)
1566
-
1567
- contact_info = st.text_input(
1568
- "راه ارتباطی (اختیاری)",
1569
- placeholder="مثال: @username یا 09123456789 یا example@email.com",
1570
- key="user_contact_input"
1571
- )
1572
-
1573
- if st.button("ثبت پاسخ‌ها", type="primary", key="submit_explanation"):
1574
- st.session_state.user_contact = contact_info
1575
- end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1576
- start_time = datetime.strptime(st.session_state.start_time, "%Y-%m-%d %H:%M:%S")
1577
- completion_time = (datetime.now() - start_time).total_seconds()
1578
-
1579
- save_data = {
1580
- "start_time": st.session_state.start_time,
1581
- "end_time": end_time,
1582
- "completion_time": completion_time,
1583
- "scenario_type": st.session_state.scenario_type,
1584
- "price": st.session_state.price,
1585
- "user_contact": st.session_state.get("user_contact", ""),
1586
- "price_accepted": st.session_state.get("price_accepted", 0),
1587
- "attention_check1": st.session_state.get("attention_check1", None),
1588
- "trust": st.session_state.trust,
1589
- "pricing_method": st.session_state.pricing_method,
1590
- "price_increase": st.session_state.price_increase,
1591
- "explanation_received": st.session_state.explanation_received,
1592
- "explanation_type": st.session_state.get("explanation_type", "N/A"),
1593
- **st.session_state.demographic_data,
1594
- **st.session_state.answers # اضافه کردن تمام پاسخ‌های لیکرت
1595
- }
1596
-
1597
- if save_to_sheet(save_data):
1598
- st.session_state.current_page = "thank_you"
1599
- st.rerun()
1600
- else:
1601
- st.error("خطا در ذخیره‌سازی داده‌ها. لطفاً دوباره تلاش کنید.")
1602
-
1603
-
1604
- def thank_you_page():
1605
- """صفحه تشکر"""
1606
- st.success("""
1607
- ✅ پاسخ‌های شما با موفقیت ثبت شد.
1608
- سپاسگزاریم که وقت ارزشمند خود را به این پژوهش اختصاص دادید.
1609
-
1610
- در صورت وجود هرگونه سوال، ابهام یا پیشنهاد می‌توانید با محقق تماس بگیرید:
1611
-
1612
- ✉ ایمیل: maryam.ilka2000@gmail.com
1613
- """)
1614
- st.balloons()
1615
-
1616
- # ========== مدیریت وضعیت و صفحه‌بندی ==========
1617
- def main():
1618
- # تشخیص دستگاه
1619
- user_agent = st.query_params.get("user_agent", [""])[0]
1620
- st.session_state.is_desktop = "mobile" not in user_agent.lower()
1621
-
1622
-
1623
- if st.session_state.is_desktop:
1624
- # اطمینان از نمایش همان حالت موبایل برای همه دستگاه‌ها
1625
- st.session_state.is_desktop = False
1626
-
1627
- if 'answers' not in st.session_state:
1628
- st.session_state.answers = {}
1629
-
1630
- if 'current_page' not in st.session_state:
1631
- st.session_state.current_page = "welcome"
1632
- st.session_state.scenario_type = random.choice(["control", "input", "counterfactual"])
1633
- st.session_state.price = 200000
1634
- st.session_state.user_contact = None
1635
- st.session_state.demographic_data = None
1636
- st.session_state.price_accepted = 0
1637
- st.session_state.attention_check1 = None
1638
-
1639
- pages = {
1640
- "welcome": welcome_page,
1641
- "scenario_explanation": scenario_explanation,
1642
- "map_view": map_view,
1643
- "attention_check1": attention_check1,
1644
- "random_likert_questions": random_likert_questions,
1645
- "explanation_questions": explanation_questions,
1646
- "demographic": demographic_form, # دموگرافیک قبل از کانتکت
1647
- "contact": user_contact, # کانتکت در انتها
1648
- "thank_you": thank_you_page
1649
- }
1650
-
1651
- pages[st.session_state.current_page]()
1652
-
1653
- if __name__ == "__main__":
1654
- main()
 
1
  import streamlit as st
2
+ import plotly.graph_objects as go
3
+
4
+ # طیف گسسته
5
+ labels = ["خیلی کم", "کم", "نسبتاً کم", "متوسط", "نسبتاً زیاد", "زیاد", "خیلی زیاد"]
6
+ positions = list(range(len(labels)))
7
+
8
+ # انتخاب عددی از اسلایدر معمولی
9
+ selected = st.slider("انتخاب شدت", min_value=0, max_value=6, value=3, step=1)
10
+
11
+ # نمایش طیف با رنگ بنفش روی گزینه انتخاب‌شده
12
+ fig = go.Figure()
13
+
14
+ fig.add_trace(go.Scatter(
15
+ x=positions,
16
+ y=[0]*len(labels),
17
+ mode='markers+text',
18
+ marker=dict(size=20, color=['#d1c4e9' if i != selected else '#8e44ad' for i in positions]),
19
+ text=labels,
20
+ textposition="top center"
21
+ ))
22
+
23
+ fig.update_layout(
24
+ xaxis=dict(
25
+ tickmode='array',
26
+ tickvals=positions,
27
+ ticktext=labels,
28
+ showgrid=False,
29
+ zeroline=False
30
+ ),
31
+ yaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
32
+ margin=dict(l=20, r=20, t=20, b=20),
33
+ height=200,
34
+ showlegend=False
35
+ )
36
+
37
+ st.plotly_chart(fig, use_container_width=True)
38
+
39
+ # نمایش انتخاب
40
+ st.write(f"انتخاب شما: **{labels[selected]}**")