Lilli98 commited on
Commit
d9e55ee
·
verified ·
1 Parent(s): 61b46c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -165
app.py CHANGED
@@ -100,7 +100,8 @@ def init_game_state(llm_personality: str, info_sharing: str, participant_id: str
100
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
101
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
102
  }
103
- st.info(f"New game started for **{participant_id}**! AI Mode: **{llm_personality} / {info_sharing}**. You are the **{human_role}**.")
 
104
  # ==============================================================================
105
  def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
106
  # This function remains correct.
@@ -614,7 +615,8 @@ else:
614
  # 默认值,如果未输入ID
615
  llm_personality, info_sharing = ('human_like', 'local')
616
 
617
- #st.info(f"You will be automatically assigned to the condition: **{llm_personality.replace('_', ' ').title()} / {info_sharing.title()}**.")
 
618
  # =================================================================
619
 
620
  # 移除原有的 c1, c2 和 selectbox
@@ -655,174 +657,172 @@ else:
655
  week, human_role, echelons, info_sharing = state['week'], state['human_role'], state['echelons'], state['info_sharing']
656
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"] # Define here for UI
657
 
658
- # =============== 2. IMAGE SIZE MODIFICATION (使用 st.columns) ===============
659
- col_main, col_sidebar_image = st.columns([4, 1]) # 增大主内容区,同时保持侧边栏用于放置图表
660
 
661
- with col_main:
662
- st.header(f"Week {week} / {WEEKS}")
663
- st.subheader(f"Your Role: **{human_role}** ({state['participant_id']}) | AI Mode: **{state['llm_personality'].replace('_', ' ')}** | Information: **{state['info_sharing']}**")
664
- st.markdown("---")
665
- st.subheader("Supply Chain Status (Start of Week State)")
666
-
667
- # ... (UI LOGIC REMAINS HERE) ...
668
-
669
- # --- MODIFIED UI LOGIC (v12) ---
670
- if info_sharing == 'full':
671
- cols = st.columns(4)
672
- for i, name in enumerate(echelon_order):
673
- with cols[i]:
674
- e = echelons[name]
675
- icon = "👤" if name == human_role else "🤖"
676
- if name == human_role:
677
- st.markdown(f"##### **<span style='border: 1px solid #FF4B4B; padding: 2px 5px; border-radius: 3px;'>{icon} {name} (You)</span>**", unsafe_allow_html=True)
678
- else:
679
- st.markdown(f"##### {icon} {name}")
680
-
681
- st.metric("Inventory (Opening)", e['inventory'])
682
- st.metric("Backlog (Opening)", e['backlog'])
683
-
684
- current_incoming_order = 0
685
- if name == "Retailer":
686
- current_incoming_order = get_customer_demand(week)
687
- else:
688
- downstream_name = e['downstream_name']
689
- if downstream_name:
690
- current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
691
- st.write(f"Incoming Order (This Week): **{current_incoming_order}**")
692
-
693
- if name == "Factory":
694
- prod_completing_next = state['last_week_orders'].get("Distributor", 0)
695
- st.write(f"Completing Next Week: **{prod_completing_next}**")
696
- else:
697
- arriving_next = 0
698
- q = e['incoming_shipments']
699
- if q: arriving_next = list(q)[0] # Read W+1
700
- st.write(f"Arriving Next Week: **{arriving_next}**")
701
- else: # Local Info Mode
702
- st.info("In Local Information mode, you can only see your own status dashboard.")
703
- e = echelons[human_role] # Distributor
704
- st.markdown(f"### 👤 **<span style='color:#FF4B4B;'>{human_role} (Your Dashboard - Start of Week State)</span>**", unsafe_allow_html=True)
705
- col1, col2, col3 = st.columns(3)
706
- with col1:
707
  st.metric("Inventory (Opening)", e['inventory'])
708
  st.metric("Backlog (Opening)", e['backlog'])
709
-
710
- with col2:
711
  current_incoming_order = 0
712
- downstream_name = e['downstream_name'] # Wholesaler
713
- if downstream_name:
714
- current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
715
- st.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}")
716
-
717
- with col3:
718
- arriving_next = 0
719
- q = e['incoming_shipments']
720
- if q:
721
- arriving_next = list(q)[0]
722
- st.write(f"**Shipment Arriving (Next Week):**\n# {arriving_next}")
723
-
724
- # --- Decision Logic (Remainging the Same) ---
725
- st.markdown("---")
726
- st.header("Your Decision (Step 4)")
727
-
728
- # Prepare state snapshot for the AI prompt (logic remains identical)
729
- all_decision_point_states = {}
730
- for name in echelon_order:
731
- e_curr = echelons[name]
732
- arrived = 0
733
- if name == "Factory":
734
- if state['factory_production_pipeline']: arrived = list(state['factory_production_pipeline'])[0]
735
- else:
736
- if e_curr['incoming_shipments']: arrived = list(e_curr['incoming_shipments'])[0]
737
-
738
- inc_order_this_week = 0
739
- if name == "Retailer": inc_order_this_week = get_customer_demand(week)
740
- else:
741
- ds_name = e_curr['downstream_name']
742
- if ds_name: inc_order_this_week = state['last_week_orders'].get(ds_name, 0)
743
- inv_after_arrival = e_curr['inventory'] + arrived
744
- backlog_after_new_order = e_curr['backlog'] + inc_order_this_week
745
 
746
- all_decision_point_states[name] = {
747
- 'name': name, 'inventory': inv_after_arrival, 'backlog': backlog_after_new_order,
748
- 'incoming_order': inc_order_this_week,
749
- 'incoming_shipments': e_curr['incoming_shipments'].copy() if name != "Factory" else deque()
750
- }
751
- human_echelon_state_for_prompt = all_decision_point_states[human_role]
752
-
753
- if state['decision_step'] == 'initial_order':
754
- with st.form(key="initial_order_form"):
755
- st.markdown("#### **Step 4a:** Based on the dashboard, submit your **initial** order to the Factory.")
756
- initial_order = st.number_input("Your Initial Order Quantity:", min_value=0, step=1, value=None) # Start blank
757
- if st.form_submit_button("Submit Initial Order & See AI Suggestion", type="primary"):
758
- state['human_initial_order'] = int(initial_order) if initial_order is not None else 0
759
- state['decision_step'] = 'final_order'
760
- prompt_sugg = get_llm_prompt(human_echelon_state_for_prompt, week, state['llm_personality'], state['info_sharing'], all_decision_point_states)
761
- ai_suggestion, _ = get_llm_order_decision(prompt_sugg, f"{human_role} (Suggestion)")
762
- state['current_ai_suggestion'] = ai_suggestion # Store it
763
- st.rerun()
764
- elif state['decision_step'] == 'final_order':
765
- st.success(f"Your initial order was: **{state['human_initial_order']}** units.")
766
- ai_suggestion = state.get('current_ai_suggestion', 4) # Read stored value
767
- with st.form(key="final_order_form"):
768
- st.markdown(f"#### **Step 4b:** The AI suggests ordering **{ai_suggestion}** units.")
769
- st.markdown("Considering the AI's advice, submit your **final** order to end the week. (This order will arrive in 3 weeks).")
770
- st.number_input("Your Final Order Quantity:", min_value=0, step=1, key='final_order_input', value=None) # Start blank
771
-
772
- if st.form_submit_button("Submit Final Order & Advance to Next Week"):
773
- final_order_value = st.session_state.get('final_order_input', 0)
774
- final_order_value = int(final_order_value) if final_order_value is not None else 0
775
-
776
- step_game(final_order_value, state['human_initial_order'], ai_suggestion)
777
-
778
- if 'final_order_input' in st.session_state: del st.session_state.final_order_input
779
- st.rerun()
780
-
781
- st.markdown("---")
782
- with st.expander("📖 Your Weekly Decision Log", expanded=False):
783
- if not state.get('logs'):
784
- st.write("Your weekly history will be displayed here after you complete the first week.")
785
- else:
786
- try:
787
- history_df = pd.json_normalize(state['logs'])
788
- human_cols = {
789
- 'week': 'Week', f'{human_role}.opening_inventory': 'Opening Inv.',
790
- f'{human_role}.opening_backlog': 'Opening Backlog',
791
- f'{human_role}.incoming_order': 'Incoming Order', f'{human_role}.initial_order': 'Your Initial Order',
792
- f'{human_role}.ai_suggestion': 'AI Suggestion', f'{human_role}.order_placed': 'Your Final Order',
793
- f'{human_role}.arriving_next_week': 'Arriving Next Week', f'{human_role}.weekly_cost': 'Weekly Cost',
794
- }
795
- ordered_display_cols_keys = [
796
- 'week', f'{human_role}.opening_inventory', f'{human_role}.opening_backlog',
797
- f'{human_role}.incoming_order',
798
- f'{human_role}.initial_order', f'{human_role}.ai_suggestion', f'{human_role}.order_placed',
799
- f'{human_role}.arriving_next_week', f'{human_role}.weekly_cost'
800
- ]
801
- final_cols_to_display = [col for col in ordered_display_cols_keys if col in history_df.columns]
802
- if not final_cols_to_display:
803
- st.write("No data columns available to display.")
804
- else:
805
- display_df = history_df[final_cols_to_display].rename(columns=human_cols)
806
- if 'Weekly Cost' in display_df.columns:
807
- display_df['Weekly Cost'] = display_df['Weekly Cost'].apply(lambda x: f"${x:,.2f}" if isinstance(x, (int, float)) else "")
808
- st.dataframe(display_df.sort_values(by="Week", ascending=False), hide_index=True, use_container_width=True)
809
- except Exception as e:
810
- st.error(f"Error displaying weekly log: {e}")
811
 
812
- # 将原来的侧边栏内容移入 col_sidebar_image
813
- with col_sidebar_image:
814
- st.header("Game Info")
815
- st.markdown(f"**Game ID**: `{state['participant_id']}`\n\n**Current Week**: {week}")
816
- # --- 放大图片 ---
817
- try: st.image(IMAGE_PATH, caption="Supply Chain Reference", use_column_width=True)
818
- except FileNotFoundError: st.warning("Image file not found.")
819
- # --- 放大图片 ---
820
-
821
- if st.button("🔄 Reset Game"):
822
- if 'final_order_input' in st.session_state: del st.session_state.final_order_input
823
- if 'current_ai_suggestion' in st.session_state.game_state: del st.session_state.game_state['current_ai_suggestion']
824
- del st.session_state.game_state
825
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
826
 
827
  # --- Game Over Interface ---
828
  if 'game_state' in st.session_state and not st.session_state.game_state.get('game_running', False) and st.session_state.game_state['week'] > WEEKS:
@@ -832,7 +832,8 @@ else:
832
  logs_df = pd.json_normalize(state['logs'])
833
  fig = plot_results(
834
  logs_df,
835
- f"Beer Game (Human: {state['human_role']})\n(AI: {state['llm_personality']} | Info: {state['info_sharing']})",
 
836
  state['human_role']
837
  )
838
  st.pyplot(fig)
 
100
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
101
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
102
  }
103
+ # [修改点 2.1]: 移除启动时显示实验条件的提示
104
+ st.info(f"New game started for **{participant_id}**! You are the **{human_role}**.")
105
  # ==============================================================================
106
  def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
107
  # This function remains correct.
 
615
  # 默认值,如果未输入ID
616
  llm_personality, info_sharing = ('human_like', 'local')
617
 
618
+ # [修改点 2.2]: 移除启动时显示实验条件的提示
619
+ # st.info(f"You will be automatically assigned to the condition: **{llm_personality.replace('_', ' ').title()} / {info_sharing.title()}**.")
620
  # =================================================================
621
 
622
  # 移除原有的 c1, c2 和 selectbox
 
657
  week, human_role, echelons, info_sharing = state['week'], state['human_role'], state['echelons'], state['info_sharing']
658
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"] # Define here for UI
659
 
660
+ # [修改点 1.1]: 移除 st.columns,恢复主内容到左侧,侧边栏不变
661
+ # col_main, col_sidebar_image = st.columns([4, 1]) 移除
662
 
663
+ # with col_main: 移除
664
+ st.header(f"Week {week} / {WEEKS}")
665
+ # [修改点 2.3]: 移除 subheader AI Mode Information 的��示
666
+ st.subheader(f"Your Role: **{human_role}** ({state['participant_id']})")
667
+ st.markdown("---")
668
+ st.subheader("Supply Chain Status (Start of Week State)")
669
+
670
+ # --- MODIFIED UI LOGIC (v12) ---
671
+ if info_sharing == 'full':
672
+ cols = st.columns(4)
673
+ for i, name in enumerate(echelon_order):
674
+ with cols[i]:
675
+ e = echelons[name]
676
+ icon = "👤" if name == human_role else "🤖"
677
+ if name == human_role:
678
+ st.markdown(f"##### **<span style='border: 1px solid #FF4B4B; padding: 2px 5px; border-radius: 3px;'>{icon} {name} (You)</span>**", unsafe_allow_html=True)
679
+ else:
680
+ st.markdown(f"##### {icon} {name}")
681
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
  st.metric("Inventory (Opening)", e['inventory'])
683
  st.metric("Backlog (Opening)", e['backlog'])
684
+
 
685
  current_incoming_order = 0
686
+ if name == "Retailer":
687
+ current_incoming_order = get_customer_demand(week)
688
+ else:
689
+ downstream_name = e['downstream_name']
690
+ if downstream_name:
691
+ current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
692
+ st.write(f"Incoming Order (This Week): **{current_incoming_order}**")
693
+
694
+ if name == "Factory":
695
+ prod_completing_next = state['last_week_orders'].get("Distributor", 0)
696
+ st.write(f"Completing Next Week: **{prod_completing_next}**")
697
+ else:
698
+ arriving_next = 0
699
+ q = e['incoming_shipments']
700
+ if q: arriving_next = list(q)[0] # Read W+1
701
+ st.write(f"Arriving Next Week: **{arriving_next}**")
702
+ else: # Local Info Mode
703
+ st.info("In Local Information mode, you can only see your own status dashboard.")
704
+ e = echelons[human_role] # Distributor
705
+ st.markdown(f"### 👤 **<span style='color:#FF4B4B;'>{human_role} (Your Dashboard - Start of Week State)</span>**", unsafe_allow_html=True)
706
+ col1, col2, col3 = st.columns(3)
707
+ with col1:
708
+ st.metric("Inventory (Opening)", e['inventory'])
709
+ st.metric("Backlog (Opening)", e['backlog'])
710
+
711
+ with col2:
712
+ current_incoming_order = 0
713
+ downstream_name = e['downstream_name'] # Wholesaler
714
+ if downstream_name:
715
+ current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
716
+ st.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}")
 
 
717
 
718
+ with col3:
719
+ arriving_next = 0
720
+ q = e['incoming_shipments']
721
+ if q:
722
+ arriving_next = list(q)[0]
723
+ st.write(f"**Shipment Arriving (Next Week):**\n# {arriving_next}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
 
725
+ # --- Decision Logic (Remainging the Same) ---
726
+ st.markdown("---")
727
+ st.header("Your Decision (Step 4)")
728
+
729
+ # Prepare state snapshot for the AI prompt (logic remains identical)
730
+ all_decision_point_states = {}
731
+ for name in echelon_order:
732
+ e_curr = echelons[name]
733
+ arrived = 0
734
+ if name == "Factory":
735
+ if state['factory_production_pipeline']: arrived = list(state['factory_production_pipeline'])[0]
736
+ else:
737
+ if e_curr['incoming_shipments']: arrived = list(e_curr['incoming_shipments'])[0]
738
+
739
+ inc_order_this_week = 0
740
+ if name == "Retailer": inc_order_this_week = get_customer_demand(week)
741
+ else:
742
+ ds_name = e_curr['downstream_name']
743
+ if ds_name: inc_order_this_week = state['last_week_orders'].get(ds_name, 0)
744
+ inv_after_arrival = e_curr['inventory'] + arrived
745
+ backlog_after_new_order = e_curr['backlog'] + inc_order_this_week
746
+
747
+ all_decision_point_states[name] = {
748
+ 'name': name, 'inventory': inv_after_arrival, 'backlog': backlog_after_new_order,
749
+ 'incoming_order': inc_order_this_week,
750
+ 'incoming_shipments': e_curr['incoming_shipments'].copy() if name != "Factory" else deque()
751
+ }
752
+ human_echelon_state_for_prompt = all_decision_point_states[human_role]
753
+
754
+ if state['decision_step'] == 'initial_order':
755
+ with st.form(key="initial_order_form"):
756
+ st.markdown("#### **Step 4a:** Based on the dashboard, submit your **initial** order to the Factory.")
757
+ initial_order = st.number_input("Your Initial Order Quantity:", min_value=0, step=1, value=None) # Start blank
758
+ if st.form_submit_button("Submit Initial Order & See AI Suggestion", type="primary"):
759
+ state['human_initial_order'] = int(initial_order) if initial_order is not None else 0
760
+ state['decision_step'] = 'final_order'
761
+ prompt_sugg = get_llm_prompt(human_echelon_state_for_prompt, week, state['llm_personality'], state['info_sharing'], all_decision_point_states)
762
+ ai_suggestion, _ = get_llm_order_decision(prompt_sugg, f"{human_role} (Suggestion)")
763
+ state['current_ai_suggestion'] = ai_suggestion # Store it
764
+ st.rerun()
765
+ elif state['decision_step'] == 'final_order':
766
+ st.success(f"Your initial order was: **{state['human_initial_order']}** units.")
767
+ ai_suggestion = state.get('current_ai_suggestion', 4) # Read stored value
768
+ with st.form(key="final_order_form"):
769
+ st.markdown(f"#### **Step 4b:** The AI suggests ordering **{ai_suggestion}** units.")
770
+ st.markdown("Considering the AI's advice, submit your **final** order to end the week. (This order will arrive in 3 weeks).")
771
+ st.number_input("Your Final Order Quantity:", min_value=0, step=1, key='final_order_input', value=None) # Start blank
772
+
773
+ if st.form_submit_button("Submit Final Order & Advance to Next Week"):
774
+ final_order_value = st.session_state.get('final_order_input', 0)
775
+ final_order_value = int(final_order_value) if final_order_value is not None else 0
776
+
777
+ step_game(final_order_value, state['human_initial_order'], ai_suggestion)
778
+
779
+ if 'final_order_input' in st.session_state: del st.session_state.final_order_input
780
+ st.rerun()
781
+
782
+ st.markdown("---")
783
+ with st.expander("📖 Your Weekly Decision Log", expanded=False):
784
+ if not state.get('logs'):
785
+ st.write("Your weekly history will be displayed here after you complete the first week.")
786
+ else:
787
+ try:
788
+ history_df = pd.json_normalize(state['logs'])
789
+ human_cols = {
790
+ 'week': 'Week', f'{human_role}.opening_inventory': 'Opening Inv.',
791
+ f'{human_role}.opening_backlog': 'Opening Backlog',
792
+ f'{human_role}.incoming_order': 'Incoming Order', f'{human_role}.initial_order': 'Your Initial Order',
793
+ f'{human_role}.ai_suggestion': 'AI Suggestion', f'{human_role}.order_placed': 'Your Final Order',
794
+ f'{human_role}.arriving_next_week': 'Arriving Next Week', f'{human_role}.weekly_cost': 'Weekly Cost',
795
+ }
796
+ ordered_display_cols_keys = [
797
+ 'week', f'{human_role}.opening_inventory', f'{human_role}.opening_backlog',
798
+ f'{human_role}.incoming_order',
799
+ f'{human_role}.initial_order', f'{human_role}.ai_suggestion', f'{human_role}.order_placed',
800
+ f'{human_role}.arriving_next_week', f'{human_role}.weekly_cost'
801
+ ]
802
+ final_cols_to_display = [col for col in ordered_display_cols_keys if col in history_df.columns]
803
+ if not final_cols_to_display:
804
+ st.write("No data columns available to display.")
805
+ else:
806
+ display_df = history_df[final_cols_to_display].rename(columns=human_cols)
807
+ if 'Weekly Cost' in display_df.columns:
808
+ display_df['Weekly Cost'] = display_df['Weekly Cost'].apply(lambda x: f"${x:,.2f}" if isinstance(x, (int, float)) else "")
809
+ st.dataframe(display_df.sort_values(by="Week", ascending=False), hide_index=True, use_container_width=True)
810
+ except Exception as e:
811
+ st.error(f"Error displaying weekly log: {e}")
812
+
813
+ # [修改点 1.2]: 恢复 Game Info 和图片到 st.sidebar
814
+ # 移除 with col_sidebar_image:
815
+ st.sidebar.header("Game Info")
816
+ st.sidebar.markdown(f"**Game ID**: `{state['participant_id']}`\n\n**Current Week**: {week}")
817
+ # [修改点 1.3]: 恢复 st.sidebar.image 但使用 use_column_width=True 保持图片放大
818
+ try: st.sidebar.image(IMAGE_PATH, caption="Supply Chain Reference", use_column_width=True)
819
+ except FileNotFoundError: st.sidebar.warning("Image file not found.")
820
+
821
+ if st.sidebar.button("🔄 Reset Game"):
822
+ if 'final_order_input' in st.session_state: del st.session_state.final_order_input
823
+ if 'current_ai_suggestion' in st.session_state.game_state: del st.session_state.game_state['current_ai_suggestion']
824
+ del st.session_state.game_state
825
+ st.rerun()
826
 
827
  # --- Game Over Interface ---
828
  if 'game_state' in st.session_state and not st.session_state.game_state.get('game_running', False) and st.session_state.game_state['week'] > WEEKS:
 
832
  logs_df = pd.json_normalize(state['logs'])
833
  fig = plot_results(
834
  logs_df,
835
+ # [修改点 2.4]: 移除报告标题中的实验条件提示
836
+ f"Beer Game (Human: {state['human_role']})",
837
  state['human_role']
838
  )
839
  st.pyplot(fig)