Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -449,7 +449,7 @@ def plot_results(df: pd.DataFrame, title: str, human_role: str):
|
|
| 449 |
plt.tight_layout(rect=[0, 0, 1, 0.96]); return fig
|
| 450 |
|
| 451 |
|
| 452 |
-
# =============== NEW: Leaderboard Functions ===============
|
| 453 |
@st.cache_data(ttl=60)
|
| 454 |
def load_leaderboard_data():
|
| 455 |
if not hf_api or not HF_REPO_ID: return {}
|
|
@@ -474,38 +474,57 @@ def save_leaderboard_data(data):
|
|
| 474 |
except Exception as e:
|
| 475 |
st.sidebar.error(f"Failed to upload leaderboard: {e}")
|
| 476 |
|
|
|
|
| 477 |
def display_rankings(df, top_n=10):
|
| 478 |
if df.empty:
|
| 479 |
st.info("No completed games for this category yet. Be the first!")
|
| 480 |
return
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
df = df.
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
c1, c2, c3 = st.columns(3)
|
|
|
|
|
|
|
| 488 |
with c1:
|
| 489 |
st.subheader("🏆 Supply Chain Champions")
|
| 490 |
-
st.caption(f"Top {top_n} - Lowest Total Cost")
|
| 491 |
-
|
| 492 |
-
champs_df
|
| 493 |
-
champs_df.
|
| 494 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
with c2:
|
| 496 |
-
st.subheader("
|
| 497 |
-
st.caption(f"Top {top_n} -
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
with c3:
|
| 503 |
st.subheader("🧘 Mr. Smooth")
|
| 504 |
st.caption(f"Top {top_n} - Lowest Order Variation (Std. Dev.)")
|
| 505 |
-
smooth_df = df.sort_values('order_std_dev', ascending=True).head(top_n).copy()
|
| 506 |
-
|
| 507 |
-
smooth_df.
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
|
| 510 |
def show_leaderboard_ui():
|
| 511 |
st.markdown("---")
|
|
@@ -518,9 +537,12 @@ def show_leaderboard_ui():
|
|
| 518 |
try:
|
| 519 |
df = pd.DataFrame(leaderboard_data.values())
|
| 520 |
if 'id' not in df.columns and not df.empty: df['id'] = list(leaderboard_data.keys())
|
|
|
|
|
|
|
| 521 |
if 'total_cost' not in df.columns or 'order_std_dev' not in df.columns or 'setting' not in df.columns:
|
| 522 |
st.error("Leaderboard data is corrupted or incomplete.")
|
| 523 |
return
|
|
|
|
| 524 |
groups = sorted(df.setting.unique())
|
| 525 |
tabs = st.tabs(["**Overall**"] + groups)
|
| 526 |
with tabs[0]: display_rankings(df)
|
|
@@ -532,6 +554,7 @@ def show_leaderboard_ui():
|
|
| 532 |
st.error(f"Error displaying leaderboard: {e}")
|
| 533 |
st.dataframe(leaderboard_data)
|
| 534 |
|
|
|
|
| 535 |
def save_logs_and_upload(state: dict):
|
| 536 |
if not state.get('logs'):
|
| 537 |
st.warning("No log data to save.")
|
|
@@ -559,17 +582,34 @@ def save_logs_and_upload(state: dict):
|
|
| 559 |
st.subheader("Updating Leaderboard...")
|
| 560 |
try:
|
| 561 |
human_role = state['human_role']
|
| 562 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
order_std_dev = logs_df[f'{human_role}.order_placed'].std()
|
|
|
|
| 564 |
setting_name = f"{state['llm_personality']} / {state['info_sharing']}"
|
|
|
|
|
|
|
| 565 |
new_entry = {
|
| 566 |
-
'id': participant_id,
|
| 567 |
-
'
|
|
|
|
|
|
|
| 568 |
'order_std_dev': float(order_std_dev) if pd.notna(order_std_dev) else 0.0
|
| 569 |
}
|
|
|
|
| 570 |
leaderboard_data = load_leaderboard_data()
|
| 571 |
leaderboard_data[participant_id] = new_entry
|
| 572 |
save_leaderboard_data(leaderboard_data)
|
|
|
|
| 573 |
except Exception as e_board:
|
| 574 |
st.error(f"Error calculating or saving leaderboard score: {e_board}")
|
| 575 |
# ==============================================================================
|
|
|
|
| 449 |
plt.tight_layout(rect=[0, 0, 1, 0.96]); return fig
|
| 450 |
|
| 451 |
|
| 452 |
+
# =============== NEW: Leaderboard Functions (MODIFIED) ===============
|
| 453 |
@st.cache_data(ttl=60)
|
| 454 |
def load_leaderboard_data():
|
| 455 |
if not hf_api or not HF_REPO_ID: return {}
|
|
|
|
| 474 |
except Exception as e:
|
| 475 |
st.sidebar.error(f"Failed to upload leaderboard: {e}")
|
| 476 |
|
| 477 |
+
# ---------- MODIFIED FUNCTION (v2) ----------
|
| 478 |
def display_rankings(df, top_n=10):
|
| 479 |
if df.empty:
|
| 480 |
st.info("No completed games for this category yet. Be the first!")
|
| 481 |
return
|
| 482 |
+
|
| 483 |
+
# 为新旧数据列进行数值转换
|
| 484 |
+
df['distributor_cost'] = pd.to_numeric(df.get('total_cost'), errors='coerce') # 'total_cost' 是旧的 distributor_cost
|
| 485 |
+
df['total_chain_cost'] = pd.to_numeric(df.get('total_chain_cost'), errors='coerce')
|
| 486 |
+
df['order_std_dev'] = pd.to_numeric(df.get('order_std_dev'), errors='coerce')
|
| 487 |
+
|
| 488 |
c1, c2, c3 = st.columns(3)
|
| 489 |
+
|
| 490 |
+
# 排行榜 1: 总供应链成本 (新)
|
| 491 |
with c1:
|
| 492 |
st.subheader("🏆 Supply Chain Champions")
|
| 493 |
+
st.caption(f"Top {top_n} - Lowest **Total Chain** Cost")
|
| 494 |
+
# .dropna() 确保只对有该数据的条目进行排序 (兼容旧数据)
|
| 495 |
+
champs_df = df.dropna(subset=['total_chain_cost']).sort_values('total_chain_cost', ascending=True).head(top_n).copy()
|
| 496 |
+
if champs_df.empty:
|
| 497 |
+
st.info("No data for this category yet.")
|
| 498 |
+
else:
|
| 499 |
+
champs_df['total_chain_cost'] = champs_df['total_chain_cost'].map('${:,.2f}'.format)
|
| 500 |
+
champs_df.rename(columns={'id': 'Participant', 'total_chain_cost': 'Total Chain Cost'}, inplace=True)
|
| 501 |
+
st.dataframe(champs_df[['Participant', 'Total Chain Cost']], use_container_width=True, hide_index=True)
|
| 502 |
+
|
| 503 |
+
# 排行榜 2: 你的 (Distributor) 成本 (修改)
|
| 504 |
with c2:
|
| 505 |
+
st.subheader("👤 Distributor Champions")
|
| 506 |
+
st.caption(f"Top {top_n} - Lowest **Your** (Distributor) Cost")
|
| 507 |
+
dist_df = df.dropna(subset=['distributor_cost']).sort_values('distributor_cost', ascending=True).head(top_n).copy()
|
| 508 |
+
|
| 509 |
+
if dist_df.empty:
|
| 510 |
+
st.info("No data for this category yet.")
|
| 511 |
+
else:
|
| 512 |
+
dist_df['distributor_cost'] = dist_df['distributor_cost'].map('${:,.2f}'.format)
|
| 513 |
+
dist_df.rename(columns={'id': 'Participant', 'distributor_cost': 'Your Cost'}, inplace=True)
|
| 514 |
+
st.dataframe(dist_df[['Participant', 'Your Cost']], use_container_width=True, hide_index=True)
|
| 515 |
+
|
| 516 |
+
# 排行榜 3: 订单平滑度 (不变)
|
| 517 |
with c3:
|
| 518 |
st.subheader("🧘 Mr. Smooth")
|
| 519 |
st.caption(f"Top {top_n} - Lowest Order Variation (Std. Dev.)")
|
| 520 |
+
smooth_df = df.dropna(subset=['order_std_dev']).sort_values('order_std_dev', ascending=True).head(top_n).copy()
|
| 521 |
+
|
| 522 |
+
if smooth_df.empty:
|
| 523 |
+
st.info("No data for this category yet.")
|
| 524 |
+
else:
|
| 525 |
+
smooth_df['order_std_dev'] = smooth_df['order_std_dev'].map('{:,.2f}'.format)
|
| 526 |
+
smooth_df.rename(columns={'id': 'Participant', 'order_std_dev': 'Order Std. Dev.'}, inplace=True)
|
| 527 |
+
st.dataframe(smooth_df[['Participant', 'Order Std. Dev.']], use_container_width=True, hide_index=True)
|
| 528 |
|
| 529 |
def show_leaderboard_ui():
|
| 530 |
st.markdown("---")
|
|
|
|
| 537 |
try:
|
| 538 |
df = pd.DataFrame(leaderboard_data.values())
|
| 539 |
if 'id' not in df.columns and not df.empty: df['id'] = list(leaderboard_data.keys())
|
| 540 |
+
|
| 541 |
+
# 检查旧列是否存在即可
|
| 542 |
if 'total_cost' not in df.columns or 'order_std_dev' not in df.columns or 'setting' not in df.columns:
|
| 543 |
st.error("Leaderboard data is corrupted or incomplete.")
|
| 544 |
return
|
| 545 |
+
|
| 546 |
groups = sorted(df.setting.unique())
|
| 547 |
tabs = st.tabs(["**Overall**"] + groups)
|
| 548 |
with tabs[0]: display_rankings(df)
|
|
|
|
| 554 |
st.error(f"Error displaying leaderboard: {e}")
|
| 555 |
st.dataframe(leaderboard_data)
|
| 556 |
|
| 557 |
+
# ---------- MODIFIED FUNCTION (v2) ----------
|
| 558 |
def save_logs_and_upload(state: dict):
|
| 559 |
if not state.get('logs'):
|
| 560 |
st.warning("No log data to save.")
|
|
|
|
| 582 |
st.subheader("Updating Leaderboard...")
|
| 583 |
try:
|
| 584 |
human_role = state['human_role']
|
| 585 |
+
|
| 586 |
+
# 1. 计算你的 (Distributor) 成本
|
| 587 |
+
distributor_cost = logs_df[f'{human_role}.total_cost'].iloc[-1]
|
| 588 |
+
|
| 589 |
+
# 2. 计算总供应链成本
|
| 590 |
+
r_cost = logs_df['Retailer.total_cost'].iloc[-1]
|
| 591 |
+
w_cost = logs_df['Wholesaler.total_cost'].iloc[-1]
|
| 592 |
+
f_cost = logs_df['Factory.total_cost'].iloc[-1]
|
| 593 |
+
total_chain_cost = r_cost + w_cost + distributor_cost + f_cost
|
| 594 |
+
|
| 595 |
+
# 3. 计算订单标准差
|
| 596 |
order_std_dev = logs_df[f'{human_role}.order_placed'].std()
|
| 597 |
+
|
| 598 |
setting_name = f"{state['llm_personality']} / {state['info_sharing']}"
|
| 599 |
+
|
| 600 |
+
# 4. 创建新的条目
|
| 601 |
new_entry = {
|
| 602 |
+
'id': participant_id,
|
| 603 |
+
'setting': setting_name,
|
| 604 |
+
'total_cost': float(distributor_cost), # 'total_cost' 现在明确是 distributor_cost
|
| 605 |
+
'total_chain_cost': float(total_chain_cost), # 新增: 总成本
|
| 606 |
'order_std_dev': float(order_std_dev) if pd.notna(order_std_dev) else 0.0
|
| 607 |
}
|
| 608 |
+
|
| 609 |
leaderboard_data = load_leaderboard_data()
|
| 610 |
leaderboard_data[participant_id] = new_entry
|
| 611 |
save_leaderboard_data(leaderboard_data)
|
| 612 |
+
|
| 613 |
except Exception as e_board:
|
| 614 |
st.error(f"Error calculating or saving leaderboard score: {e_board}")
|
| 615 |
# ==============================================================================
|