Lilli98 commited on
Commit
f23713c
·
verified ·
1 Parent(s): 6dfc3e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -30
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # @title Beer Game Final Version (v4.4 - Added Diagram Image)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
@@ -57,15 +57,21 @@ else:
57
 
58
 
59
  # -----------------------------------------------------------------------------
60
- # 3. Core Game Logic Functions (No changes in this section)
61
  # -----------------------------------------------------------------------------
62
 
63
  def get_customer_demand(week: int) -> int:
64
  return 4 if week <= 4 else 8
65
 
66
  def init_game_state(llm_personality: str, info_sharing: str):
 
67
  roles = ["Retailer", "Wholesaler", "Distributor", "Factory"]
68
- human_role = random.choice(roles)
 
 
 
 
 
69
  participant_id = str(uuid.uuid4())[:8]
70
 
71
  st.session_state.game_state = {
@@ -80,6 +86,8 @@ def init_game_state(llm_personality: str, info_sharing: str):
80
  for i, name in enumerate(roles):
81
  upstream = roles[i + 1] if i + 1 < len(roles) else None
82
  downstream = roles[i - 1] if i - 1 >= 0 else None
 
 
83
  if name == "Distributor": shipping_weeks = FACTORY_SHIPPING_DELAY
84
  elif name == "Factory": shipping_weeks = 0
85
  else: shipping_weeks = SHIPPING_DELAY
@@ -91,7 +99,7 @@ def init_game_state(llm_personality: str, info_sharing: str):
91
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
92
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
93
  }
94
- st.info(f"New game started! AI Mode: **{llm_personality} / {info_sharing}**. You have been randomly assigned the role of: **{human_role}**.")
95
 
96
  def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
97
  if not client: return 8, "NO_API_KEY_DEFAULT"
@@ -139,6 +147,11 @@ def get_llm_prompt(echelon_state: dict, week: int, llm_personality: str, info_sh
139
  return f"**You are a reactive supply chain manager for the {echelon_state['name']}.** You have a limited view and tend to over-correct based on fear.\n\n**Your Mindset: **Your top priority is try to not have a backlog.\n\n{base_info}\n\n**Your Task:** You just saw your own inventory and a new order coming. Your gut instinct is to panic and order enough to ensure you are never caught with a backlog again.\n\n**React emotionally.** What is your knee-jerk order quantity? Respond with a single integer."
140
 
141
  def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
 
 
 
 
 
142
  state = st.session_state.game_state
143
  week, echelons, human_role = state['week'], state['echelons'], state['human_role']
144
  llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
@@ -215,7 +228,7 @@ def plot_results(df: pd.DataFrame, title: str, human_role: str):
215
  inventory_pivot.plot(ax=axes[0], kind='line', marker='o', markersize=4); axes[0].set_title('Inventory Levels'); axes[0].grid(True, linestyle='--'); axes[0].set_ylabel('Stock (Units)')
216
 
217
  order_pivot = plot_df.pivot(index='week', columns='echelon', values='order_placed').reindex(columns=echelons)
218
- order_pivot.plot(ax=axes[1], style='--'); axes[1].plot(range(1, WS + 1), [get_customer_demand(w) for w in range(1, WEEKS + 1)], label='Customer Demand', color='black', lw=2.5); axes[1].set_title('Order Quantities (The Bullwhip Effect)'); axes[1].grid(True, linestyle='--'); axes[1].legend(); axes[1].set_ylabel('Ordered (Units)')
219
 
220
  total_costs = plot_df.groupby('echelon')['total_cost'].max().reindex(echelons)
221
  total_costs.plot(kind='bar', ax=axes[2], rot=0); axes[2].set_title('Total Cumulative Cost'); axes[2].set_ylabel('Cost ($)')
@@ -271,42 +284,49 @@ else:
271
  col1, col2 = st.columns(2)
272
  with col1:
273
  st.subheader("🔗 The Supply Chain")
274
-
275
- # =============== IMAGE ADDED HERE ===============
276
  try:
277
  st.image(IMAGE_PATH, caption="Orders flow upstream (right), beer flows downstream (left).")
278
  except FileNotFoundError:
279
  st.warning("Image file not found. Please ensure 'beer_game_diagram.png' is uploaded to the repository.")
280
- # ================================================
281
 
282
  st.markdown("""
283
- You will be randomly assigned one of four roles. The other three will be controlled by AI agents.
284
  - **Retailer:** Fulfills end-customer demand. Orders from the Wholesaler.
285
  - **Wholesaler:** Fulfills Retailer orders. Orders from the Distributor.
286
- - **Distributor:** Fulfills Wholesaler orders. Orders from the Factory.
287
- - **Factory:** Fulfills Distributor orders. Produces new beer.
288
  """)
289
  with col2:
290
  st.subheader("⏳ The Challenge: Delays!")
291
  st.markdown("""
292
- The key challenge is managing delays. As shown in the diagram, there are **shipping delays** for beer to arrive and **production delays** at the factory.
293
 
294
- There is also a one-week **order-passing delay** (the small boxes) for your order to reach your supplier.
 
 
295
 
296
- You must order enough to meet future demand without creating a huge pile of expensive inventory.
297
  """)
298
 
299
- st.subheader("🎮 How to Play This Version")
300
  st.markdown("""
301
- 1. **Configure the Game:** Choose the AI's behavior and the level of information sharing.
302
- 2. **Get Your Role:** You will be randomly assigned a role.
303
- 3. **Make Your Decision (Two Steps):**
304
- - **Step 1:** Based on your current status, submit your **initial order**.
305
- - **Step 2:** After submitting, you will see an **AI suggestion**. You can either follow it or stick to your own judgment when submitting your **final order**.
306
- 4. **Advance:** Once you submit your final order, the week advances, and all AI agents make their moves.
 
 
 
 
 
 
 
 
307
  """)
 
308
  st.markdown("---")
309
-
310
  st.header("⚙️ Game Configuration")
311
  c1, c2 = st.columns(2)
312
  with c1:
@@ -335,23 +355,33 @@ else:
335
  st.markdown(f"##### {icon} {name} {'(You)' if name == human_role else ''}")
336
  st.metric("Inventory", e['inventory']); st.metric("Backlog", e['backlog'])
337
  st.write(f"Incoming Order: **{e['incoming_order']}**")
338
- st.write(f"Arriving Next Week: **{list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}**")
 
 
 
 
 
 
 
 
339
  else:
340
  st.info("In Local Information mode, you can only see your own status dashboard.")
341
- e = echelons[human_role]
342
  st.markdown(f"### 👤 {human_role} (Your Dashboard)")
343
  col1, col2, col3, col4 = st.columns(4)
344
  col1.metric("Current Inventory", e['inventory'])
345
  col2.metric("Current Backlog", e['backlog'])
346
  col3.write(f"**Incoming Order (This Week):**\n# {e['incoming_order']}")
 
347
  col4.write(f"**Shipment Arriving (Next Week):**\n# {list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}")
 
348
  st.markdown("---")
349
- st.header("Your Decision")
350
  human_echelon_state = echelons[human_role]
351
 
352
  if state['decision_step'] == 'initial_order':
353
  with st.form(key="initial_order_form"):
354
- st.markdown("#### **Step 1:** Based on the information available, submit your **initial** order.")
355
  initial_order = st.number_input("Your Initial Order Quantity:", min_value=0, step=1, value=human_echelon_state['incoming_order'])
356
  if st.form_submit_button("Submit Initial Order & See AI Suggestion", type="primary"):
357
  state['human_initial_order'] = int(initial_order)
@@ -367,7 +397,7 @@ else:
367
  st.session_state.final_order_input = ai_suggestion
368
 
369
  with st.form(key="final_order_form"):
370
- st.markdown(f"#### **Step 2:** The AI suggests ordering **{ai_suggestion}** units.")
371
  st.markdown("Considering the AI's advice, submit your **final** order to end the week.")
372
  st.number_input("Your Final Order Quantity:", min_value=0, step=1, key='final_order_input')
373
  if st.form_submit_button("Submit Final Order & Advance to Next Week"):
@@ -394,12 +424,10 @@ else:
394
  display_df['Weekly Cost'] = display_df['Weekly Cost'].apply(lambda x: f"${x:,.2f}")
395
  st.dataframe(display_df.sort_values(by="Week", ascending=False), hide_index=True, use_container_width=True)
396
 
397
- # =============== IMAGE ADDED TO SIDEBAR ===============
398
  try:
399
  st.sidebar.image(IMAGE_PATH, caption="Supply Chain Reference")
400
  except FileNotFoundError:
401
  st.sidebar.warning("Image file not found.")
402
- # ======================================================
403
 
404
  st.sidebar.header("Game Info")
405
  st.sidebar.markdown(f"**Game ID**: `{state['participant_id']}`\n\n**Current Week**: {week}")
@@ -414,7 +442,15 @@ else:
414
  st.header("🎉 Game Over!")
415
  state = st.session_state.game_state
416
  logs_df = pd.json_normalize(state['logs'])
417
- fig = plot_results(logs_df, f"Beer Game (Human: {state['human_role']})\n(AI: {state['llm_personality']} | Info: {state['info_sharing']})", state['human_role'])
 
 
 
 
 
 
 
 
418
  st.pyplot(fig)
419
  save_logs_and_upload(state)
420
  if st.button("✨ Start a New Game"):
 
1
  # app.py
2
+ # @title Beer Game Final Version (v4.6 - Fixed Role, UI Logic, and Upload Bug)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
 
57
 
58
 
59
  # -----------------------------------------------------------------------------
60
+ # 3. Core Game Logic Functions
61
  # -----------------------------------------------------------------------------
62
 
63
  def get_customer_demand(week: int) -> int:
64
  return 4 if week <= 4 else 8
65
 
66
  def init_game_state(llm_personality: str, info_sharing: str):
67
+ """Initializes or resets the game state in st.session_state."""
68
  roles = ["Retailer", "Wholesaler", "Distributor", "Factory"]
69
+
70
+ # =============== CHANGE 1: Fixed Role ===============
71
+ # Human role is no longer random. Everyone is Distributor.
72
+ human_role = "Distributor"
73
+ # ======================================================
74
+
75
  participant_id = str(uuid.uuid4())[:8]
76
 
77
  st.session_state.game_state = {
 
86
  for i, name in enumerate(roles):
87
  upstream = roles[i + 1] if i + 1 < len(roles) else None
88
  downstream = roles[i - 1] if i - 1 >= 0 else None
89
+
90
+ # This logic remains correct: Factory has 0 shipping weeks for incoming.
91
  if name == "Distributor": shipping_weeks = FACTORY_SHIPPING_DELAY
92
  elif name == "Factory": shipping_weeks = 0
93
  else: shipping_weeks = SHIPPING_DELAY
 
99
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
100
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
101
  }
102
+ st.info(f"New game started! AI Mode: **{llm_personality} / {info_sharing}**. You are playing as the: **{human_role}**.")
103
 
104
  def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
105
  if not client: return 8, "NO_API_KEY_DEFAULT"
 
147
  return f"**You are a reactive supply chain manager for the {echelon_state['name']}.** You have a limited view and tend to over-correct based on fear.\n\n**Your Mindset: **Your top priority is try to not have a backlog.\n\n{base_info}\n\n**Your Task:** You just saw your own inventory and a new order coming. Your gut instinct is to panic and order enough to ensure you are never caught with a backlog again.\n\n**React emotionally.** What is your knee-jerk order quantity? Respond with a single integer."
148
 
149
  def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
150
+ # This function's core logic is sound and correctly models the delays.
151
+ # Factory inventory is correctly handled by 'factory_production_pipeline'.
152
+ # Other echelons are correctly handled by 'incoming_shipments'.
153
+ # No changes needed here.
154
+
155
  state = st.session_state.game_state
156
  week, echelons, human_role = state['week'], state['echelons'], state['human_role']
157
  llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
 
228
  inventory_pivot.plot(ax=axes[0], kind='line', marker='o', markersize=4); axes[0].set_title('Inventory Levels'); axes[0].grid(True, linestyle='--'); axes[0].set_ylabel('Stock (Units)')
229
 
230
  order_pivot = plot_df.pivot(index='week', columns='echelon', values='order_placed').reindex(columns=echelons)
231
+ order_pivot.plot(ax=axes[1], style='--'); axes[1].plot(range(1, WEEKS + 1), [get_customer_demand(w) for w in range(1, WEEKS + 1)], label='Customer Demand', color='black', lw=2.5); axes[1].set_title('Order Quantities (The Bullwhip Effect)'); axes[1].grid(True, linestyle='--'); axes[1].legend(); axes[1].set_ylabel('Ordered (Units)')
232
 
233
  total_costs = plot_df.groupby('echelon')['total_cost'].max().reindex(echelons)
234
  total_costs.plot(kind='bar', ax=axes[2], rot=0); axes[2].set_title('Total Cumulative Cost'); axes[2].set_ylabel('Cost ($)')
 
284
  col1, col2 = st.columns(2)
285
  with col1:
286
  st.subheader("🔗 The Supply Chain")
 
 
287
  try:
288
  st.image(IMAGE_PATH, caption="Orders flow upstream (right), beer flows downstream (left).")
289
  except FileNotFoundError:
290
  st.warning("Image file not found. Please ensure 'beer_game_diagram.png' is uploaded to the repository.")
 
291
 
292
  st.markdown("""
293
+ You will play the role of the **Distributor**. The other three roles (Retailer, Wholesaler, Factory) will be controlled by AI agents.
294
  - **Retailer:** Fulfills end-customer demand. Orders from the Wholesaler.
295
  - **Wholesaler:** Fulfills Retailer orders. Orders from the Distributor.
296
+ - **Distributor (You):** Fulfills Wholesaler orders. Orders from the Factory.
297
+ - **Factory:** Fulfills your orders. Produces new beer.
298
  """)
299
  with col2:
300
  st.subheader("⏳ The Challenge: Delays!")
301
  st.markdown("""
302
+ The key challenge is managing the **delays** shown in the diagram.
303
 
304
+ * **Order Delays:** It takes **1 week** for your order to reach your supplier (the Factory).
305
+ * **Shipping Delays:** It takes **1 week** for beer to be shipped from the Factory to you. (It takes 2 weeks for all other shipping links).
306
+ * **Production Delays:** The Factory needs **1 week** to produce new beer.
307
 
308
+ This means there is a **2-week** total lead time from when you order to when you receive beer (1 week order delay + 1 week production delay, or 1 week shipping delay). *Note: The diagram shows generic delays; our game uses the specific delays listed here.*
309
  """)
310
 
311
+ st.subheader("🎮 How Each Week Works")
312
  st.markdown("""
313
+ Your main job is to place an order each week. The system handles the rest.
314
+
315
+ **1. Automated Steps (Start of Week)**
316
+ When a new week begins, the system first automates three steps based on past decisions:
317
+ * **(Step 1: Shipments Arrive):** Shipments from the Factory (which you ordered weeks ago) arrive and are added to your inventory.
318
+ * **(Step 2: New Orders Arrive):** You receive a new incoming order from the Wholesaler.
319
+ * **(Step 3: Ship Beer):** The system automatically ships as much beer as you have in stock to fulfill the Wholesaler's order, plus any leftover backlog. Any unfulfilled amount becomes your new backlog for next week.
320
+
321
+ **2. Your Decision (Two-Step Process)**
322
+ After the automated steps, you will see your new inventory and backlog. Now, it's your turn to perform **Step 4**:
323
+ * **(Step 4a):** Based on your new status, submit your **initial order** to the Factory.
324
+ * **(Step 4b):** After submitting, you will see an **AI suggestion**. Review it, then submit your **final order**.
325
+
326
+ Submitting your final order advances the game to the next week, and the cycle repeats.
327
  """)
328
+
329
  st.markdown("---")
 
330
  st.header("⚙️ Game Configuration")
331
  c1, c2 = st.columns(2)
332
  with c1:
 
355
  st.markdown(f"##### {icon} {name} {'(You)' if name == human_role else ''}")
356
  st.metric("Inventory", e['inventory']); st.metric("Backlog", e['backlog'])
357
  st.write(f"Incoming Order: **{e['incoming_order']}**")
358
+
359
+ # =============== CHANGE 2: Fixed Factory UI Logic ===============
360
+ if name == "Factory":
361
+ prod_completing = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
362
+ st.write(f"Production Completing: **{prod_completing}**")
363
+ else:
364
+ arriving = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
365
+ st.write(f"Arriving Next Week: **{arriving}**")
366
+ # ==============================================================
367
  else:
368
  st.info("In Local Information mode, you can only see your own status dashboard.")
369
+ e = echelons[human_role] # This is always the Distributor
370
  st.markdown(f"### 👤 {human_role} (Your Dashboard)")
371
  col1, col2, col3, col4 = st.columns(4)
372
  col1.metric("Current Inventory", e['inventory'])
373
  col2.metric("Current Backlog", e['backlog'])
374
  col3.write(f"**Incoming Order (This Week):**\n# {e['incoming_order']}")
375
+ # This is correct for the Distributor
376
  col4.write(f"**Shipment Arriving (Next Week):**\n# {list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}")
377
+
378
  st.markdown("---")
379
+ st.header("Your Decision (Step 4)")
380
  human_echelon_state = echelons[human_role]
381
 
382
  if state['decision_step'] == 'initial_order':
383
  with st.form(key="initial_order_form"):
384
+ st.markdown("#### **Step 4a:** Based on the information available, submit your **initial** order.")
385
  initial_order = st.number_input("Your Initial Order Quantity:", min_value=0, step=1, value=human_echelon_state['incoming_order'])
386
  if st.form_submit_button("Submit Initial Order & See AI Suggestion", type="primary"):
387
  state['human_initial_order'] = int(initial_order)
 
397
  st.session_state.final_order_input = ai_suggestion
398
 
399
  with st.form(key="final_order_form"):
400
+ st.markdown(f"#### **Step 4b:** The AI suggests ordering **{ai_suggestion}** units.")
401
  st.markdown("Considering the AI's advice, submit your **final** order to end the week.")
402
  st.number_input("Your Final Order Quantity:", min_value=0, step=1, key='final_order_input')
403
  if st.form_submit_button("Submit Final Order & Advance to Next Week"):
 
424
  display_df['Weekly Cost'] = display_df['Weekly Cost'].apply(lambda x: f"${x:,.2f}")
425
  st.dataframe(display_df.sort_values(by="Week", ascending=False), hide_index=True, use_container_width=True)
426
 
 
427
  try:
428
  st.sidebar.image(IMAGE_PATH, caption="Supply Chain Reference")
429
  except FileNotFoundError:
430
  st.sidebar.warning("Image file not found.")
 
431
 
432
  st.sidebar.header("Game Info")
433
  st.sidebar.markdown(f"**Game ID**: `{state['participant_id']}`\n\n**Current Week**: {week}")
 
442
  st.header("🎉 Game Over!")
443
  state = st.session_state.game_state
444
  logs_df = pd.json_normalize(state['logs'])
445
+
446
+ # =============== CHANGE 3: Fixed Typo Bug ===============
447
+ fig = plot_results(
448
+ logs_df,
449
+ f"Beer Game (Human: {state['human_role']})\n(AI: {state['llm_personality']} | Info: {state['info_sharing']})",
450
+ state['human_role']
451
+ )
452
+ # ========================================================
453
+
454
  st.pyplot(fig)
455
  save_logs_and_upload(state)
456
  if st.button("✨ Start a New Game"):