Lilli98 commited on
Commit
5f0621c
·
verified ·
1 Parent(s): f23713c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -41
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # @title Beer Game Final Version (v4.6 - Fixed Role, UI Logic, and Upload Bug)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
@@ -64,14 +64,8 @@ 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 = {
@@ -87,7 +81,6 @@ def init_game_state(llm_personality: str, info_sharing: str):
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
@@ -109,7 +102,7 @@ def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
109
  response = client.chat.completions.create(
110
  model=OPENAI_MODEL,
111
  messages=[
112
- {"role": "system", "content": "You are a supply chain manager playing the Beer Game. Your response must be only an integer number representing your order quantity and nothing else. For example: 8"},
113
  {"role": "user", "content": prompt}
114
  ],
115
  temperature=temp, max_tokens=10
@@ -122,36 +115,90 @@ def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
122
  st.error(f"API call failed for {echelon_name}: {e}")
123
  return 8, f"API_ERROR: {e}"
124
 
 
125
  def get_llm_prompt(echelon_state: dict, week: int, llm_personality: str, info_sharing: str, all_echelons_state: dict) -> str:
126
- # This function's logic is complex and correct, so it remains unchanged.
127
- base_info = f"Your Current Status at the **{echelon_state['name']}** for **Week {week}**:\n- On-hand inventory: {echelon_state['inventory']} units.\n- Backlog (unfilled orders): {echelon_state['backlog']} units.\n- Incoming order this week (from your customer): {echelon_state['incoming_order']} units.\n- Shipments on the way to you: {list(echelon_state['incoming_shipments'])}\n- Orders you have placed being processed by your supplier: {list(echelon_state['order_pipeline'])}"
 
 
 
 
 
 
 
 
 
 
 
128
  if llm_personality == 'perfect_rational' and info_sharing == 'full':
129
- stable_demand = 8; total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY; safety_stock = 4
 
 
 
 
 
 
 
 
130
  target_inventory_level = (stable_demand * total_lead_time) + safety_stock
131
- inventory_position = (echelon_state['inventory'] - echelon_state['backlog'] + sum(echelon_state['incoming_shipments']) + sum(echelon_state['order_pipeline']))
 
 
 
 
 
 
 
132
  optimal_order = max(0, int(target_inventory_level - inventory_position))
133
- return f"**You are a perfectly rational supply chain AI with full system visibility.**\nYour only goal is to maintain stability and minimize costs based on mathematical optimization.\n**System Analysis:**\n* **Known Stable End-Customer Demand:** {stable_demand} units/week.\n* **Your Current Total Inventory Position:** {inventory_position} units.\n* **Optimal Target Inventory Level:** {target_inventory_level} units.\n* **Mathematically Optimal Order:** The optimal order is **{optimal_order} units**.\n**Your Task:** Confirm this optimal quantity. Respond with a single integer."
 
 
134
  elif llm_personality == 'perfect_rational' and info_sharing == 'local':
135
  safety_stock = 4; anchor_demand = echelon_state['incoming_order']
136
  inventory_correction = safety_stock - (echelon_state['inventory'] - echelon_state['backlog'])
137
- supply_line = sum(echelon_state['incoming_shipments']) + sum(echelon_state['order_pipeline'])
 
 
 
 
 
 
 
138
  calculated_order = anchor_demand + inventory_correction - supply_line
139
  rational_local_order = max(0, int(calculated_order))
140
- return f"**You are a perfectly rational supply chain AI with ONLY LOCAL information.**\nYou must use a logical heuristic to make a stable decision. A proven method is \"Anchoring and Adjustment\".\n\n{base_info}\n\n**Rational Calculation (Anchoring & Adjustment):**\n1. **Anchor on Demand:** Your best guess for future demand is your last incoming order: **{anchor_demand} units**.\n2. **Adjust for Inventory:** You want to hold a safety stock of {safety_stock} units. Your current stock is {echelon_state['inventory'] - echelon_state['backlog']}. You need to order an extra **{inventory_correction} units** to correct this.\n3. **Account for Supply Line:** You already have **{supply_line} units** in transit or being processed. These should be subtracted from your new order.\n\n**Final Calculation:**\n* Order = (Anchor Demand) + (Inventory Adjustment) - (Supply Line)\n* Order = {anchor_demand} + {inventory_correction} - {supply_line} = **{rational_local_order} units**.\n**Your Task:** Confirm this locally rational quantity. Respond with a single integer."
 
 
 
141
  elif llm_personality == 'human_like' and info_sharing == 'full':
142
  full_info_str = f"\n**Full Supply Chain Information:**\n- End-Customer Demand this week: {get_customer_demand(week)} units.\n"
143
  for name, e_state in all_echelons_state.items():
144
  if name != echelon_state['name']: full_info_str += f"- {name}: Inventory={e_state['inventory']}, Backlog={e_state['backlog']}\n"
145
- return f"**You are a supply chain manager with full visibility across the entire system.**\nYou can see everyone's inventory and the real customer demand. Your goal is to use this information to make a smart, coordinated decision. However, you are still human and might get anxious about your own stock levels.\n{base_info}\n{full_info_str}\n**Your Task:** Look at the full picture, especially the stable end-customer demand. Try to avoid causing the bullwhip effect. However, also consider your own inventory pressure. What quantity should you order this week? Respond with a single integer."
 
 
 
 
 
 
 
 
 
 
 
146
  elif llm_personality == 'human_like' and info_sharing == 'local':
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']
@@ -288,33 +335,34 @@ else:
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
 
@@ -356,14 +404,12 @@ 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
@@ -372,7 +418,6 @@ else:
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("---")
@@ -443,13 +488,13 @@ else:
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)
 
1
  # app.py
2
+ # @title Beer Game Final Version (v4.8 - Unified Human-Like Prompts)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
 
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 = "Distributor" # Role is fixed
 
 
 
 
 
69
  participant_id = str(uuid.uuid4())[:8]
70
 
71
  st.session_state.game_state = {
 
81
  upstream = roles[i + 1] if i + 1 < len(roles) else None
82
  downstream = roles[i - 1] if i - 1 >= 0 else None
83
 
 
84
  if name == "Distributor": shipping_weeks = FACTORY_SHIPPING_DELAY
85
  elif name == "Factory": shipping_weeks = 0
86
  else: shipping_weeks = SHIPPING_DELAY
 
102
  response = client.chat.completions.create(
103
  model=OPENAI_MODEL,
104
  messages=[
105
+ {"role": "system", "content": "You are a supply chain manager playing the Beer Game. Your response must be only an integer number representing your order or production quantity and nothing else. For example: 8"},
106
  {"role": "user", "content": prompt}
107
  ],
108
  temperature=temp, max_tokens=10
 
115
  st.error(f"API call failed for {echelon_name}: {e}")
116
  return 8, f"API_ERROR: {e}"
117
 
118
+ # =============== MODIFIED FUNCTION (Unified Prompts) ===============
119
  def get_llm_prompt(echelon_state: dict, week: int, llm_personality: str, info_sharing: str, all_echelons_state: dict) -> str:
120
+ """Generates the prompt for the LLM based on the game scenario."""
121
+
122
+ base_info = f"Your Current Status at the **{echelon_state['name']}** for **Week {week}**:\n- On-hand inventory: {echelon_state['inventory']} units.\n- Backlog (unfilled orders): {echelon_state['backlog']} units.\n- Incoming order this week (from your customer): {echelon_state['incoming_order']} units.\n"
123
+
124
+ # Define task word and role-specific info
125
+ if echelon_state['name'] == 'Factory':
126
+ task_word = "production quantity"
127
+ base_info += f"- Production pipeline (completing in future weeks): {list(st.session_state.game_state['factory_production_pipeline'])}"
128
+ else:
129
+ task_word = "order quantity"
130
+ base_info += f"- Shipments on the way to you: {list(echelon_state['incoming_shipments'])}\n- Orders you have placed being processed by your supplier: {list(echelon_state['order_pipeline'])}"
131
+
132
+ # --- Perfect Rational (Unchanged from last version, logic is correct) ---
133
  if llm_personality == 'perfect_rational' and info_sharing == 'full':
134
+ stable_demand = 8
135
+ if echelon_state['name'] == 'Factory':
136
+ total_lead_time = FACTORY_LEAD_TIME
137
+ elif echelon_state['name'] == 'Distributor':
138
+ total_lead_time = ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY
139
+ else:
140
+ total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY
141
+
142
+ safety_stock = 4
143
  target_inventory_level = (stable_demand * total_lead_time) + safety_stock
144
+
145
+ if echelon_state['name'] == 'Factory':
146
+ inv_pos_components = f"(Inv: {echelon_state['inventory']} - Backlog: {echelon_state['backlog']} + In_Production: {sum(st.session_state.game_state['factory_production_pipeline'])})"
147
+ inventory_position = (echelon_state['inventory'] - echelon_state['backlog'] + sum(st.session_state.game_state['factory_production_pipeline']))
148
+ else:
149
+ inv_pos_components = f"(Inv: {echelon_state['inventory']} - Backlog: {echelon_state['backlog']} + In_Transit: {sum(echelon_state['incoming_shipments'])} + In_Pipeline: {sum(echelon_state['order_pipeline'])})"
150
+ inventory_position = (echelon_state['inventory'] - echelon_state['backlog'] + sum(echelon_state['incoming_shipments']) + sum(echelon_state['order_pipeline']))
151
+
152
  optimal_order = max(0, int(target_inventory_level - inventory_position))
153
+
154
+ return f"**You are a perfectly rational supply chain AI with full system visibility.**\nYour only goal is to maintain stability and minimize costs based on mathematical optimization.\n**System Analysis:**\n* **Known Stable End-Customer Demand:** {stable_demand} units/week.\n* **Your Current Total Inventory Position:** {inventory_position} units. {inv_pos_components}\n* **Optimal Target Inventory Level:** {target_inventory_level} units (Target for {total_lead_time} weeks lead time).\n* **Mathematically Optimal {task_word.title()}:** The optimal decision is **{optimal_order} units**.\n**Your Task:** Confirm this optimal {task_word}. Respond with a single integer."
155
+
156
  elif llm_personality == 'perfect_rational' and info_sharing == 'local':
157
  safety_stock = 4; anchor_demand = echelon_state['incoming_order']
158
  inventory_correction = safety_stock - (echelon_state['inventory'] - echelon_state['backlog'])
159
+
160
+ if echelon_state['name'] == 'Factory':
161
+ supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
162
+ supply_line_desc = "In Production"
163
+ else:
164
+ supply_line = sum(echelon_state['incoming_shipments']) + sum(echelon_state['order_pipeline'])
165
+ supply_line_desc = "Supply Line (In Transit + In Pipeline)"
166
+
167
  calculated_order = anchor_demand + inventory_correction - supply_line
168
  rational_local_order = max(0, int(calculated_order))
169
+
170
+ return f"**You are a perfectly rational supply chain AI with ONLY LOCAL information.**\nYou must use a logical heuristic to make a stable decision. A proven method is \"Anchoring and Adjustment\".\n\n{base_info}\n\n**Rational Calculation (Anchoring & Adjustment):**\n1. **Anchor on Demand:** Your best guess for future demand is your last incoming order: **{anchor_demand} units**.\n2. **Adjust for Inventory:** You want to hold a safety stock of {safety_stock} units. Your current stock is {echelon_state['inventory'] - echelon_state['backlog']}. You need to order an extra **{inventory_correction} units** to correct this.\n3. **Account for {supply_line_desc}:** You already have **{supply_line} units** being processed. These should be subtracted from your new order.\n\n**Final Calculation:**\n* Decision = (Anchor Demand) + (Inventory Adjustment) - ({supply_line_desc})\n* Decision = {anchor_demand} + {inventory_correction} - {supply_line} = **{rational_local_order} units**.\n**Your Task:** Confirm this locally rational {task_word}. Respond with a single integer."
171
+
172
+ # --- Human-like (Unified Prompts as requested) ---
173
  elif llm_personality == 'human_like' and info_sharing == 'full':
174
  full_info_str = f"\n**Full Supply Chain Information:**\n- End-Customer Demand this week: {get_customer_demand(week)} units.\n"
175
  for name, e_state in all_echelons_state.items():
176
  if name != echelon_state['name']: full_info_str += f"- {name}: Inventory={e_state['inventory']}, Backlog={e_state['backlog']}\n"
177
+
178
+ return f"""
179
+ **You are a supply chain manager ({echelon_state['name']}) with full system visibility.**
180
+ You can see everyone's inventory and the real customer demand.
181
+ {base_info}
182
+ {full_info_str}
183
+ **Your Task:** Your primary responsibility is to meet the demand from your direct customer (your `Incoming order this week`: **{echelon_state['incoming_order']}** units).
184
+ While you can see the stable end-customer demand ({get_customer_demand(week)} units), your priority is to fulfill the order you just received to avoid a backlog.
185
+ You are still human and might get anxious about your own stock levels.
186
+ What {task_word} should you decide on this week? Respond with a single integer.
187
+ """
188
+
189
  elif llm_personality == 'human_like' and info_sharing == 'local':
190
+ return f"""
191
+ **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.
192
+ Your top priority is to NOT have a backlog.
193
+ {base_info}
194
+ **Your Task:** You just received an incoming order for **{echelon_state['incoming_order']}** units.
195
+ Your gut instinct is to panic and {task_word.split(' ')[0]} enough to ensure you are never caught with a backlog again.
196
+ **React emotionally.** What is your knee-jerk {task_word}? Respond with a single integer.
197
+ """
198
+ # ===============================================
199
 
200
  def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
201
+ # This core logic function remains correct and unchanged.
 
 
 
 
202
  state = st.session_state.game_state
203
  week, echelons, human_role = state['week'], state['echelons'], state['human_role']
204
  llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
 
335
  st.image(IMAGE_PATH, caption="Orders flow upstream (right), beer flows downstream (left).")
336
  except FileNotFoundError:
337
  st.warning("Image file not found. Please ensure 'beer_game_diagram.png' is uploaded to the repository.")
338
+
339
  st.markdown("""
340
  You will play the role of the **Distributor**. The other three roles (Retailer, Wholesaler, Factory) will be controlled by AI agents.
341
+ - **Retailer (AI):** Fulfills end-customer demand. Orders from the Wholesaler.
342
+ - **Wholesaler (AI):** Fulfills Retailer orders. Orders from the Distributor.
343
  - **Distributor (You):** Fulfills Wholesaler orders. Orders from the Factory.
344
+ - **Factory (AI):** Fulfills your orders. Produces new beer.
345
  """)
346
+
347
  with col2:
348
  st.subheader("⏳ The Challenge: Delays!")
349
+ st.markdown(f"""
350
+ The key challenge is managing the **delays** in our specific game:
351
 
352
+ * **Order Delay:** It takes **{ORDER_PASSING_DELAY} week** for your order to reach the Factory.
353
+ * **Production Delay:** The Factory needs **{FACTORY_LEAD_TIME} week** to produce your order.
354
+ * **Shipping Delay:** It takes **{FACTORY_SHIPPING_DELAY} week** for the Factory to ship the finished beer to you.
355
 
356
+ This means there is a **{ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY}-week total lead time** from when you place an order to when you receive the beer. You must order for what you'll need 3 weeks from now!
357
  """)
358
 
359
  st.subheader("🎮 How Each Week Works")
360
+ st.markdown(f"""
361
  Your main job is to place an order each week. The system handles the rest.
362
 
363
  **1. Automated Steps (Start of Week)**
364
  When a new week begins, the system first automates three steps based on past decisions:
365
+ * **(Step 1: Shipments Arrive):** Shipments from the Factory (which you ordered {ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY} weeks ago) arrive and are added to your inventory.
366
  * **(Step 2: New Orders Arrive):** You receive a new incoming order from the Wholesaler.
367
  * **(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.
368
 
 
404
  st.metric("Inventory", e['inventory']); st.metric("Backlog", e['backlog'])
405
  st.write(f"Incoming Order: **{e['incoming_order']}**")
406
 
 
407
  if name == "Factory":
408
  prod_completing = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
409
  st.write(f"Production Completing: **{prod_completing}**")
410
  else:
411
  arriving = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
412
  st.write(f"Arriving Next Week: **{arriving}**")
 
413
  else:
414
  st.info("In Local Information mode, you can only see your own status dashboard.")
415
  e = echelons[human_role] # This is always the Distributor
 
418
  col1.metric("Current Inventory", e['inventory'])
419
  col2.metric("Current Backlog", e['backlog'])
420
  col3.write(f"**Incoming Order (This Week):**\n# {e['incoming_order']}")
 
421
  col4.write(f"**Shipment Arriving (Next Week):**\n# {list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}")
422
 
423
  st.markdown("---")
 
488
  state = st.session_state.game_state
489
  logs_df = pd.json_normalize(state['logs'])
490
 
491
+ # =============== FIXED Typo Bug ===============
492
  fig = plot_results(
493
  logs_df,
494
  f"Beer Game (Human: {state['human_role']})\n(AI: {state['llm_personality']} | Info: {state['info_sharing']})",
495
  state['human_role']
496
  )
497
+ # ==============================================
498
 
499
  st.pyplot(fig)
500
  save_logs_and_upload(state)