Spaces:
Sleeping
Sleeping
File size: 36,062 Bytes
9324460 a5344e7 9324460 52c4f98 0acd8db 9324460 086c71b f261783 9324460 63a38c5 9015f8c 0e6fe6c 9015f8c 0b4422f 7d81d7e 9015f8c 55352ef 6519fe1 55352ef 9015f8c 98ed606 9fdc819 7d81d7e 55352ef 7d81d7e 55352ef 7d81d7e 55352ef 7d81d7e 55352ef 7d81d7e 55352ef f8482ac 7d81d7e 5bcef7c a3a4c2b 5bcef7c f8482ac 5bcef7c 35bbb27 7d81d7e 5bcef7c ab84714 5bcef7c ee6a19f fbf254d 5bcef7c 35bbb27 04c28a3 eeecd06 555cfbe 04c28a3 eeecd06 04c28a3 da60e9b eeca78e 04c28a3 eeecd06 606057b 773a0b2 35bbb27 555cfbe 7d81d7e 35bbb27 7d81d7e 35bbb27 7d81d7e 35bbb27 7d81d7e 9015f8c 535635c 4b07d17 535635c 4b07d17 535635c 4b07d17 535635c b434db7 535635c 9015f8c 8cf4881 bcdcd26 8cf4881 bcdcd26 fa1c171 bcdcd26 9015f8c a5344e7 f261783 9015f8c a5344e7 9015f8c a5344e7 dd0d02b 3b3f9b8 dd0d02b 0acd8db dd0d02b 0acd8db 3b3f9b8 dd0d02b 3b3f9b8 dd0d02b 3b3f9b8 a5344e7 9324460 9015f8c 9324460 0acd8db 9324460 168346a dd0d02b 168346a dd0d02b 58a30d8 dd0d02b 9324460 168346a 9324460 a5344e7 9015f8c c64bdb0 9015f8c 35fb670 a3fc044 eed3d4c a3fc044 35fb670 eed3d4c 35fb670 a3fc044 35fb670 a3fc044 35fb670 a3fc044 eed3d4c a3fc044 eed3d4c a3fc044 9015f8c 1bc2c53 58a30d8 9015f8c 1bc2c53 9015f8c 58a30d8 1bc2c53 58a30d8 9015f8c 58a30d8 9015f8c 1bc2c53 9015f8c 58a30d8 1bc2c53 58a30d8 9015f8c 1bc2c53 58a30d8 1bc2c53 58a30d8 1bc2c53 58a30d8 1bc2c53 58a30d8 1bc2c53 58a30d8 9015f8c 08b66d5 1bc2c53 9015f8c 58a30d8 9015f8c 3e69f41 2d25226 63a38c5 5bd228e b503394 2d25226 473e25d 6cf32ac 2d25226 473e25d 3e69f41 2d25226 e8b5c94 2d25226 0acd8db dd0d02b 6baf2ce 4cd2dff 2d25226 3e69f41 2d25226 3e69f41 9324460 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 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 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 | import streamlit as st
import folium
from streamlit_folium import folium_static
from datetime import datetime
import gspread
from google.oauth2.service_account import Credentials
import os
import json
import random
import time
# تنظیمات اولیهه
st.set_page_config(layout="wide", page_title="راهیار - تحلیل انصاف قیمتی", page_icon="🚖")
# ========== تنظیمات دیتا ==========
SHEET_ID = "1mmdWAyOCYq4yXMgP53Duq712AnlqZWLkfIo76JqM7wM"
SHEET_NAME = "Condition1"
# ========== استایلهای سفارشی یکپارچه ==========
st.markdown("""
<style>
@font-face {
font-family: 'Vazir';
src: url('https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v30.1.0/dist/Vazir.woff') format('woff');
}
:root {
--primary: #6a0dad;
--text: #333333;
--background: #ffffff;
--border: #dddddd;
--input-bg: #ffffff;
--secondary-bg: #f8f9fa;
--green: #f0ff0;
--dgreen: #006400
}
* {
font-family: 'Vazir', sans-serif !important;
text-align: right !important;
direction: rtl !important;
}
/* تنظیمات اصلی */
body, .stApp, [data-testid="stAppViewContainer"] {
background-color: var(--background) !important;
color: var(--text) !important;
}
/* هدر راهیار */
.rahyar-title {
color: var(--primary) !important;
margin: 0 !important;
}
.rahyar-subtitle {
color: var(--primary) !important;
margin: 0 !important;
font-size: 14px !important;
}
/* توضیحات */
.explanation-title {
color: var(--primary) !important;
font-weight: bold !important;
margin: 20px 0 10px 0 !important;
font-size: 18px !important;
}
.explanation-item {
background-color: var(--secondary-bg) !important;
border-radius: 8px !important;
padding: 12px 15px !important;
margin: 8px 0 !important;
border-right: 3px solid var(--primary) !important;
}
/* ========== استایلهای ورودی یکپارچه ========== */
/* استایل پایه برای تمام عناصر ورودی */
.stTextInput input,
.stNumberInput input,
.stSelectbox select,
.stTextArea textarea,
.stDateInput input,
.stTimeInput input,
.stMultiSelect div[role="combobox"],
div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > div > div > input,
/* لیست dropdown */
.st-bq, .st-br, .st-bs, .st-bt, .st-bu, .st-bv, .st-bw, .st-bx, .st-by, .st-bz {
color: var(--text) !important;
background-color: var(--input-bg) !important;
border: 1px var(--primary) !important;
}
/* استایل لیبلها */
.stTextInput > label,
.stNumberInput > label,
.stSelectbox > label,
.stRadio > label,
.stSlider > label {
color: var(--text) !important;
font-weight: bold !important;
margin-bottom: 4px !important;
}
/* استایل placeholder */
::placeholder {
color: var(--text) !important;
opacity: 0.7 !important;
}
/* ========== استایلهای خاص Selectbox ========== */
/* حذف کامل فلش پیشفرض در تمام مرورگرها */
div[data-baseweb="select"] > div:first-child {
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
background-image: none !important;
}
/* برای اینترنت اکسپلورر */
div[data-baseweb="select"] > div:first-child::-ms-expand {
display: none !important;
}
/* ایجاد فلش سفارشی */
div[data-baseweb="select"] {
position: relative;
}
div[data-baseweb="select"]::after {
content: "▼";
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
color: var(--primary);
background-color: white !important; /* پسزمینه سفید برای پوشاندن فلش پیشفرض */
font-size: 14px;
pointer-events: none;
}
/* ===== راه حل تضمینی برای استایل dropdown ===== */
/* آیتمهای لیست */
div[data-baseweb="popover"] [role="option"] {
color: var(--text) !important;
background-color: white !important;
border: 1px var(--primary) !important;
}
/* آیتم انتخاب شده */
div[data-baseweb="popover"] [role="option"][aria-selected="true"] {
background-color: #f0e6ff !important;
color: black !important;
}
/* ========== استایلهای Number Input ========== */
.stNumberInput button {
color: var(--primary) !important;
background-color: transparent !important;
border: none !important;
width: 30px !important;
font-weight: bold !important;
}
.stNumberInput button:hover {
background-color: #f0e6ff !important;
}
/* کامپوننتهای سفارشی */
.rahyar-header {
background-color: var(--primary) !important;
color: white !important;
padding: 15px !important;
border-radius: 10px !important;
margin-bottom: 20px !important;
text-align: center !important;
}
.price-container {
background-color: var(--secondary-bg) !important;
border-radius: 10px !important;
padding: 15px !important;
margin: 15px 0 !important;
border-right: 5px solid var(--primary) !important;
}
/* دکمه اصلی (بنفش با متن سفید) */
.stButton>button,
[data-testid="baseButton-primary"],
.accept-btn,
div[data-testid="stVerticalBlock"] > div[data-testid="stHorizontalBlock"] > div > div > button {
background-color: var(--primary) !important;
color: white !important;
border: none !important;
border-radius: 8px !important;
padding: 10px 20px !important;
font-weight: bold !important;
}
/* دکمه ثانویه (سفید با حاشیه بنفش) */
.stFormSubmitButton>button,
[data-testid="baseButton-secondary"],
.reject-btn {
background-color: var(--primary) !important;
color: var(--input-bg) !important;
border: 1px solid var(--primary) !important;
border-radius: 8px !important;
padding: 10px 20px !important;
font-weight: bold !important;
}
/* ========== استایل تضمینی برای گزینههای رادیویی ========== */
/* تمام سطوح لیبلها */
.stRadio > label,
.stRadio > div > label,
.stRadio > div > div > label,
.stRadio > div > div > div > label {
color: #000000 !important; /* مشکی خالص */
font-weight: normal !important;
margin-right: 8px !important; /* فاصله از دکمه رادیویی */
}
/* دایره رادیویی */
.stRadio input[type="radio"] + span {
border-color: #000000 !important; /* حاشیه مشکی */
}
/* دایره رادیویی هنگام انتخاب */
.stRadio input[type="radio"]:checked + span {
background-color: #000000 !important; /* پسزمینه مشکی */
border-color: #000000 !important;
}
/* متن گزینهها */
.stRadio span {
color: #000000 !important; /* مشکی خالص */
font-size: 14px !important;
padding-right: 5px !important;
}
/* حالت hover */
.stRadio label:hover span {
color: #333333 !important; /* مشکی کمی روشنتر */
}
/* کانتینر اصلی */
.stRadio > div {
margin-bottom: 10px !important;
}
/* تنظیمات مخصوص موبایل */
@media (max-width: 768px) {
.folium-map {
height: 300px !important;
}
/* فرمها در موبایل */
.stTextInput>label,
.stNumberInput>label,
.stSelectbox>label {
font-size: 14px !important;
padding: 4px 0 !important;
}
.stTextInput input,
.stNumberInput input,
.stSelectbox select {
font-size: 16px !important;
padding: 12px !important;
height: auto !important;
}
.stButton>button {
font-size: 14px !important;
padding: 8px 16px !important;
}
.stMarkdown h3 {
font-size: 16px !important;
}
.stMarkdown p {
font-size: 13px !important;
}
.stSelectbox [role="listbox"] {
font-size: 16px !important;
}
}
</style>
""", unsafe_allow_html=True)
# ========== توابع اصلی ==========
def create_ride_map():
"""ایجاد نقشه سفر با Folium"""
start_point = [35.7698, 51.4116] # میدان ونک
end_point = [35.8044, 51.4258] # میدان تجریش
m = folium.Map(location=[35.7871, 51.4187], zoom_start=13)
folium.Marker(
start_point,
popup="<b>مبدأ:</b> میدان ونک",
icon=folium.Icon(color="green", icon="flag", prefix="fa")
).add_to(m)
folium.Marker(
end_point,
popup="<b>مقصد:</b> میدان تجریش",
icon=folium.Icon(color="red", icon="flag", prefix="fa")
).add_to(m)
folium.PolyLine(
[start_point, end_point],
color="#6a0dad",
weight=3,
opacity=1
).add_to(m)
return m
def show_explanation(exp_type):
"""نمایش توضیحات قیمت"""
explanations = {
"input": [
"سطح تقاضا در منطقه: زیاد",
"تعداد رانندگان فعال: کم",
"زمان روز: ساعت اوج ترافیک",
"شرایط جوی: هوای بارانی"
],
"counterfactual": [
"اگر این سفر را ۳۰ دقیقه دیرتر درخواست میکردید، به دلیل سطح تقاضای کمتر، رانندگان فعال بیشتر، زمان بهتر روز و شرایط جوی بهتر، ممکن بود قیمت ۱۵٪ کمتر باشد.",
]
}
if exp_type != "control":
st.markdown("<p class='explanation-title'>علت قیمت گذاری:</p>", unsafe_allow_html=True)
for item in explanations.get(exp_type, []):
st.markdown(f"<p class='explanation-item'>• {item}</p>", unsafe_allow_html=True)
def create_likert_question(question, key, scale_type="5point"):
"""سوال لیکرت با دکمهها"""
if scale_type == "5point":
options = {
1: "کاملاً مخالفم",
2: "مخالفم",
3: "نظری ندارم",
4: "موافقم",
5: "کاملاً موافقم"
}
else: # 7-point scale
options = {
1: "کاملاً مخالفم",
2: "مخالفم",
3: "تا حدی مخالفم",
4: "نظری ندارم",
5: "تا حدی موافقم",
6: "موافقم",
7: "کاملاً موافقم"
}
st.markdown(f"<p style='margin-bottom: 10px;'>{question}</p>", unsafe_allow_html=True)
cols = st.columns(len(options))
selected = st.session_state.get(key, None)
# نمایش گزینهها از کاملاً موافقم (راست) تا کاملاً مخالفم (چپ)
for value, label in reversed(options.items()):
with cols[len(options) - value]:
if st.button(
label,
key=f"{key}_{value}",
on_click=lambda v=value: st.session_state.update({key: v}),
type="primary" if selected == value else "secondary"
):
pass
if selected:
st.markdown(f"<p style='color: #6a0dad;'>پاسخ شما: {options[selected]}</p>", unsafe_allow_html=True)
return selected
# ========== توابع مدیریت دادهها ==========
def get_credentials():
"""دریافت اعتبارنامه از Secrets"""
try:
service_account_json = os.environ.get('GCP_SERVICE_ACCOUNT')
if not service_account_json:
st.error("مقدار GCP_SERVICE_ACCOUNT در محیط یافت نشد")
return None
service_account_info = json.loads(service_account_json)
creds = Credentials.from_service_account_info(
service_account_info,
scopes=[
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.file"
]
)
return creds
except Exception as e:
st.error(f"خطا در دریافت اعتبارنامه: {str(e)}")
return None
def save_to_sheet(data):
try:
creds = get_credentials()
if not creds:
return False
client = gspread.authorize(creds)
spreadsheet = client.open_by_key(SHEET_ID)
worksheet = spreadsheet.worksheet(SHEET_NAME)
row_data = [
data.get("start_time", ""), # زمان شروع
data.get("scenario_type", ""),
data.get("price", ""),
data.get("age", ""),
data.get("gender", ""),
data.get("education", ""),
data.get("ride_frequency", ""),
data.get("user_contact", ""),
data.get("price_accepted", ""),
# سوالات توجه
data.get("attention_check1", ""),
data.get("attention_check2", ""),
# سوالات جدید
data.get("pricing_method", ""),
data.get("price_increase", ""),
# سوالات informational (5 گزینهای)
data.get("informational_1", ""),
data.get("informational_2", ""),
data.get("informational_3", ""),
data.get("informational_4", ""),
data.get("informational_5", ""),
# سوالات distributive (7 گزینهای)
data.get("distributive_1", ""),
data.get("distributive_2", ""),
data.get("distributive_3", ""),
# سوالات procedural (7 گزینهای)
data.get("procedural_1", ""),
data.get("procedural_2", ""),
data.get("procedural_3", ""),
# سوالات explanation
data.get("explanation_received", ""),
data.get("explanation_type", ""),
# زمانسنجی
data.get("end_time", ""), # زمان پایان
data.get("completion_time", "") # مدت زمان تکمیل (ثانیه)
]
worksheet.append_row(row_data)
return True
except Exception as e:
st.error(f"خطا در ذخیرهسازی: {str(e)}")
return False
# ========== بخشهای فرم ==========
def welcome_page():
"""صفحه خوشامدگویی"""
st.markdown("""
<div style="text-align: center; margin-bottom: 30px;">
<h2> 🚖 قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی</h2>
<p>👋با سلام و درود</p>
<p>پیشاپیش بابت زمانی که برای پاسخ به سوالات این پرسشنامه و پیشبرد اهداف علمی دانشجویان میگذارید، متشکرم.</p>
<p>جهت تقدیر از شرکتکنندگان، به دو نفر به قید قرعه جایزه 5 میلیون ریالی تقدیم خواهد شد.</p>
</div>
<div style="display: flex; align-items: center; background-color: #f0f2f6; border-radius: 10px; padding: 20px; gap: 20px;">
<div style="flex: 0 0 100px;">
<img src="https://huggingface.co/spaces/maryamilka/surge-pricing/resolve/main/shariflogo.png" alt="لوگو دانشگاه شریف" style="width: 100%; max-width: 100px;">
</div>
<div style="flex: 1;">
<h3>درباره تحقیق:</h3>
<p>این پرسشنامه بخشی از یک پایاننامه کارشناسی ارشد در دانشگاه صنعتی شریف است که به بررسی ادراک انصاف در قیمتگذاری در پلتفرمهای درخواست تاکسی اینترنتی میپردازد.</p>
<p>پاسخهای شما به سوالات کاملاً محرمانه خواهد بود و فقط در جهت اهداف علمی استفاده خواهد شذ.</p>
<p>جهت شروع روی دکمه زیر کلیک کنید 👇🏻</p>
</div>
</div>
""", unsafe_allow_html=True)
if st.button("شروع پرسشنامه", key="start_btn", type="primary"):
st.session_state.current_page = "contact"
st.session_state.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
st.rerun()
def user_contact():
"""راه ارتباطی ساده"""
st.markdown("""
<div style="text-align: center; margin-bottom: 30px;">
<h3>📩 راه ارتباطی شما (اختیاری)</h3>
<p>در صورت تمایل به شرکت در قرعهکشی میتوانید آیدی تلگرام، شماره تماس یا ایمیل خود را وارد کنید:</p>
</div>
""", unsafe_allow_html=True)
contact_info = st.text_input(
"راه ارتباطی (اختیاری)",
placeholder="مثال: @username یا 09123456789 یا example@email.com",
key="user_contact_input"
)
if st.button("ادامه", key="continue_btn", type="primary"):
st.session_state.user_contact = contact_info
st.session_state.current_page = "demographic"
st.rerun()
def demographic_form():
"""فرم اطلاعات دموگرافیک"""
st.markdown("### 📝 اطلاعات دموگرافیک")
with st.form("demographic_form"):
age = st.number_input("سن", min_value=18, max_value=100)
gender = st.selectbox("جنسیت", ["مرد", "زن", "سایر"])
education = st.selectbox("تحصیلات", ["دیپلم", "لیسانس", "فوق لیسانس", "دکترا"])
ride_frequency = st.selectbox("دفعات استفاده از سرویسهای اشتراک سفر در ماه",
["کمتر از 5 بار", "5-10 بار", "بیش از 10 بار"])
related_education_job = st.text_input(
"اگر تحصیلات یا شغل مرتبط با مدیریت/بازاریابی دارید، لطفاً مشخص کنید (اختیاری):",
placeholder="مثال: مدیریت بازرگانی، بازاریابی دیجیتال، MBA و...",
key="related_education_job"
)
if st.form_submit_button("ادامه"):
st.session_state.demographic_data = {
"age": age,
"gender": gender,
"education": education,
"ride_frequency": ride_frequency
}
st.session_state.current_page = "scenario_explanation"
st.rerun()
def scenario_explanation():
"""توضیح سناریو"""
col1, col2 = st.columns([1, 4])
with col1:
try:
st.image("rahyar.png", width=80)
except:
st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
with col2:
st.markdown("""
<h2 class="rahyar-title">رهیار 🚖</h2>
<p class="rahyar-subtitle">همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار</p>
""", unsafe_allow_html=True)
st.markdown("### سناریوی تحقیق")
st.markdown("""
<div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px;">
<p>فرض کنید یک اپلیکیشن حملونقل آنلاین ایرانی به اسم رهیار طراحی شده، چیزی شبیه اسنپ یا تپسی، اما جدیدتر و با شعار "همراه سفرهای شما، راهی مطمئن، راهی روشن، رهیار"</p>
<p>در یک روز عادی، شما قصد دارید برای سفری از طریق این پلتفرم اقدام کنید..</p>
<p>با کلیک بر دکمه ادامه، اطلاعات سفر را مشاهده کنید.</p>
</div>
""", unsafe_allow_html=True)
if st.button("ادامه", key="continue_btn", type="primary"):
st.session_state.current_page = "map_view"
st.rerun()
def map_view():
"""نمایش نقشه و قیمت"""
col1, col2 = st.columns([1, 4])
with col1:
st.image("https://via.placeholder.com/80/6a0dad/FFFFFF?text=LOGO", width=80)
with col2:
st.markdown("""
<h2 style="color: #6a0dad; margin: 0;">رهیار 🚖</h2>
<p style="color: #6a0dad; margin: 0;">همراه سفرهای درونشهری شما، راهی مطمئن، راهی روشن، رهیار</p>
""", unsafe_allow_html=True)
st.markdown("### مسیر سفر شما")
folium_static(create_ride_map(), width=1000 if st.session_state.is_desktop else 800,
height=500 if st.session_state.is_desktop else 400)
# قیمت
st.markdown(f"""
<div class="price-container">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>راهیار <span style="background-color: #e6e6fa; color: #6a0dad; padding: 2px 8px; border-radius: 12px; font-size: 14px;">به صرفه</span></span>
<span class="rahyar-price">{st.session_state.price:,} تومان</span>
</div>
</div>
""", unsafe_allow_html=True)
show_explanation(st.session_state.scenario_type)
# دکمهها
col1, col2 = st.columns(2)
with col1:
if st.button("درخواست راهیار", key="accept_btn", use_container_width=True):
st.session_state.price_accepted = 1
st.session_state.current_page = "attention_check1"
st.rerun()
with col2:
if st.button("رد قیمت", key="reject_btn", use_container_width=True):
st.session_state.price_accepted = 0
st.session_state.current_page = "attention_check1"
st.rerun()
def attention_check1():
"""سوال توجه اول (بدون بررسی پاسخ صحیح)"""
st.markdown("""
<style>
/* تضمین رنگ متن برای تمام سطوح */
.st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej {
color: black !important;
}
</style>
""", unsafe_allow_html=True)
st.markdown("### سوال توجه")
# استفاده از st.radio با key منحصر به فرد
answer = st.radio(
"رنگ سازمانی اپلیکیشن رهیار چه رنگی بود؟",
["قرمز", "سبز", "بنفش", "آبی", "زرد"],
index=None,
key="att1_radio"
)
if st.button("ادامه", key="att1_btn"):
if answer:
st.session_state.attention_check1 = answer # ذخیره پاسخ در session_state
st.session_state.current_page = "random_likert_questions"
st.rerun()
else:
st.warning("لطفاً یک گزینه را انتخاب کنید")
def attention_check2():
"""سوال توجه دوم (بدون بررسی پاسخ صحیح)"""
st.markdown("""
<style>
/* تضمین رنگ متن برای تمام سطوح */
.st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej {
color: black !important;
}
</style>
""", unsafe_allow_html=True)
st.markdown("### سوال توجه")
answer = st.radio(
"نام اپلیکیشنی که در این تحقیق بررسی میشود چیست؟",
["اسنپ", "تپسی", "راهیار", "ماکسیم", "دیگر"],
index=None,
key="att2_radio"
)
if st.button("ادامه", key="att2_btn"):
if answer:
st.session_state.attention_check2 = answer
st.session_state.current_page = "explanation_questions"
st.rerun()
else:
st.warning("لطفاً یک گزینه را انتخاب کنید")
def random_likert_questions():
"""نمایش تصادفی سوالات لیکرت"""
if 'all_questions' not in st.session_state:
# تعریف تمام سوالات با نوع مقیاس و لیبل مربوطه
st.session_state.all_questions = [
# سوالات informational (5 گزینهای)
{"key": "informational_1", "question": "پلتفرم به صورت صادقانه دلایل تغییر قیمت (مثل افزایش تقاضا یا شرایط جوی) را توضیح داد.", "scale": "5point", "label": "informational_1"},
{"key": "informational_2", "question": "پلتفرم به طور کامل عوامل مؤثر بر قیمت (مثل ترافیک، تعداد رانندگان) را شرح داد.", "scale": "5point", "label": "informational_2"},
{"key": "informational_3", "question": "دلایل ارائهشده برای تغییر قیمت منطقی و قابل قبول بود.", "scale": "5point", "label": "informational_3"},
{"key": "informational_4", "question": "توضیحات درباره قیمت بلافاصله و در زمان مناسب نمایش داده شد.", "scale": "5point", "label": "informational_4"},
{"key": "informational_5", "question": "توضیحات پلتفرم متناسب با شرایط سفر من (مثل مسیر یا ساعت درخواست) بود.", "scale": "5point", "label": "informational_5"},
# سوالات distributive (7 گزینهای)
{"key": "distributive_1", "question": "قیمتی که به شما ارائه شد، منصفانه است.", "scale": "7point", "label": "distributive_1"},
{"key": "distributive_2", "question": "قیمتی که به شما ارائه شد، معقول است.", "scale": "7point", "label": "distributive_2"},
{"key": "distributive_3", "question": "قیمتی که به شما ارائه شد، قابل قبول است.", "scale": "7point", "label": "distributive_3"},
# سوالات procedural (7 گزینهای)
{"key": "procedural_1", "question": "فرآیند و رویه قیمتگذاری پلتفرم قابل قبول است.", "scale": "7point", "label": "procedural_1"},
{"key": "procedural_2", "question": "فرآیند و رویه قیمتگذاری پلتفرم منصفانه است.", "scale": "7point", "label": "procedural_2"},
{"key": "procedural_3", "question": "فرآیند و رویه قیمتگذاری پلتفرم معقول است.", "scale": "7point", "label": "procedural_3"}
]
# تصادفیسازی ترتیب سوالات
random.shuffle(st.session_state.all_questions)
st.session_state.current_question_index = 0
if st.session_state.current_question_index < len(st.session_state.all_questions):
q = st.session_state.all_questions[st.session_state.current_question_index]
# نمایش سوال بدون شماره
answer = create_likert_question(q["question"], q["key"], q["scale"])
if answer:
st.session_state.current_question_index += 1
time.sleep(0.5)
st.rerun()
else:
st.session_state.current_page = "attention_check2"
st.rerun()
def explanation_questions():
"""سوالات درباره توضیحات قیمت"""
st.markdown("""
<style>
/* تضمین رنگ متن برای تمام سطوح */
.st-ec, .st-ed, .st-ee, .st-ef, .st-eg, .st-eh, .st-ei, .st-ej {
color: black !important;
}
</style>
""", unsafe_allow_html=True)
st.markdown("### 📋 سوالات تکمیلی")
# مقداردهی اولیه متغیرهای session_state اگر وجود ندارند
if 'pricing_method' not in st.session_state:
st.session_state.pricing_method = None
if 'price_increase' not in st.session_state:
st.session_state.price_increase = None
if 'explanation_received' not in st.session_state:
st.session_state.explanation_received = None
if 'explanation_type' not in st.session_state:
st.session_state.explanation_type = None
# سوال جدید 1: روش قیمتگذاری
pricing_method = st.radio(
"به نظر شما پلتفرم قیمت را چگونه تعیین میکند؟",
[
"به صورت دستی توسط تیم پلتفرم",
"به صورت خودکار توسط هوش مصنوعی و الگوریتمها",
"ترکیبی از هر دو روش",
"نظری ندارم"
],
index=None,
key="pricing_method_radio"
)
# سوال جدید 2: افزایش قیمت
price_increase = None
if pricing_method is not None: # فقط اگر به سوال اول پاسخ داده شده باشد
price_increase = st.radio(
"آیا به نظر شما در این سفر افزایش قیمت نسبت به حالت طبیعی وجود داشته است؟",
["بله", "خیر", "مطمئن نیستم"],
index=None,
key="price_increase_radio"
)
# سوالات قبلی
explanation_received = None
explanation_type = None
if price_increase is not None: # فقط اگر به سوال دوم پاسخ داده شده باشد
explanation_received = st.radio(
"آیا برای قیمت پیشنهادی این سفر، توضیحی به شما ارائه شد؟",
["بله", "خیر"],
index=None,
key="explanation_received_radio"
)
# سوال دوم (فقط اگر پاسخ بله باشد)
if explanation_received == "بله":
explanation_type = st.radio(
"اگر توضیحی دریافت کردید، این توضیح بیشتر به کدام مورد شباهت داشت؟",
[
"بر اساس عواملی که در قیمتگذاری لحاظ شدهاند",
"شامل سناریوهای جایگزین که میتوانستند قیمت متفاوتی ایجاد کنند",
"توضیحی دریافت نکردم"
],
index=None,
key="explanation_type_radio"
)
if st.button("ثبت پاسخها", type="primary", key="submit_explanation"):
# استفاده از مقادیر مستقیماً از session_state
st.session_state.pricing_method = st.session_state.get("pricing_method_radio")
st.session_state.price_increase = st.session_state.get("price_increase_radio")
st.session_state.explanation_received = st.session_state.get("explanation_received_radio")
st.session_state.explanation_type = st.session_state.get("explanation_type_radio")
# بررسی کامل بودن پاسخها
if st.session_state.pricing_method is None:
st.warning("لطفاً به سوال روش قیمتگذاری پاسخ دهید")
elif st.session_state.price_increase is None:
st.warning("لطفاً به سوال افزایش قیمت پاسخ دهید")
elif st.session_state.explanation_received is None:
st.warning("لطفاً به سوال دریافت توضیح پاسخ دهید")
elif st.session_state.explanation_received == "بله" and st.session_state.explanation_type is None:
st.warning("لطفاً به سوال نوع توضیح پاسخ دهید")
else:
# جمعآوری تمام دادهها برای ذخیرهسازی
end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
start_time = datetime.strptime(st.session_state.start_time, "%Y-%m-%d %H:%M:%S")
completion_time = (datetime.now() - start_time).total_seconds()
save_data = {
"start_time": st.session_state.start_time,
"end_time": end_time,
"completion_time": completion_time,
"scenario_type": st.session_state.scenario_type,
"price": st.session_state.price,
"user_contact": st.session_state.get("user_contact", ""),
"price_accepted": st.session_state.get("price_accepted", 0),
"attention_check1": st.session_state.attention_check1,
"attention_check2": st.session_state.attention_check2,
"pricing_method": st.session_state.pricing_method,
"price_increase": st.session_state.price_increase,
"explanation_received": st.session_state.explanation_received,
"explanation_type": st.session_state.explanation_type if st.session_state.explanation_received == "بله" else "N/A",
**st.session_state.demographic_data
}
# اضافه کردن پاسخهای لیکرت
for q in st.session_state.all_questions:
save_data[q["label"]] = st.session_state.get(q["key"], None)
if save_to_sheet(save_data):
st.session_state.current_page = "thank_you"
st.rerun()
else:
st.error("خطا در ذخیرهسازی دادهها. لطفاً دوباره تلاش کنید.")
def thank_you_page():
"""صفحه تشکر"""
st.success("✅ پاسخهای شما با موفقیت ثبت شد. با تشکر از مشارکت شما در این تحقیق!")
st.balloons()
if st.button("بازگشت به ابتدا"):
st.session_state.clear()
st.rerun()
# ========== مدیریت وضعیت و صفحهبندی ==========
def main():
# تشخیص دستگاه
user_agent = st.query_params.get("user_agent", [""])[0]
st.session_state.is_desktop = "mobile" not in user_agent.lower()
if 'current_page' not in st.session_state:
st.session_state.current_page = "welcome"
st.session_state.scenario_type = random.choice(["control", "input", "counterfactual"])
st.session_state.price = 200000
st.session_state.user_contact = None
st.session_state.demographic_data = None
st.session_state.price_accepted = 0
pages = {
"welcome": welcome_page,
"contact": user_contact,
"demographic": demographic_form,
"scenario_explanation": scenario_explanation,
"map_view": map_view,
"attention_check1": attention_check1,
"random_likert_questions": random_likert_questions,
"attention_check2": attention_check2,
"explanation_questions": explanation_questions,
"thank_you": thank_you_page
}
pages[st.session_state.current_page]()
if __name__ == "__main__":
main() |