Lilli98 commited on
Commit
5b0c6b2
·
verified ·
1 Parent(s): e8bd11c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -158
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # @title Beer Game Final Version (v4.21 - Corrected 3-Week Lead Time Logic & UI)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
@@ -89,7 +89,8 @@ def init_game_state(llm_personality: str, info_sharing: str):
89
  else: shipping_weeks = SHIPPING_DELAY
90
 
91
  st.session_state.game_state['echelons'][name] = {
92
- 'name': name, 'inventory': INITIAL_INVENTORY, 'backlog': INITIAL_BACKLOG,
 
93
  'incoming_shipments': deque([0] * shipping_weeks, maxlen=shipping_weeks),
94
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
95
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
@@ -183,139 +184,76 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
183
  **React emotionally.** What is your knee-jerk {task_word}? Respond with a single integer.
184
  """
185
 
186
- # =============== CORRECTED step_game FUNCTION (Fixed Lead Time Logic) ===============
187
  def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
 
188
  state = st.session_state.game_state
189
  week, echelons, human_role = state['week'], state['echelons'], state['human_role']
190
  llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
191
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"]
192
  llm_raw_responses = {}
193
-
194
- # Store state at the very beginning of the week (End of last week)
195
  opening_inventories = {name: e['inventory'] for name, e in echelons.items()}
196
  opening_backlogs = {name: e['backlog'] for name, e in echelons.items()}
197
  arrived_this_week = {name: 0 for name in echelon_order}
198
-
199
- # --- Game Simulation Steps ---
200
-
201
- # Step 1a: Factory Production completes
202
  factory_state = echelons["Factory"]
203
  produced_units = 0
204
  if state['factory_production_pipeline']:
205
- produced_units = state['factory_production_pipeline'].popleft() # Pop completed production
206
  arrived_this_week["Factory"] = produced_units
207
- inventory_after_arrival = {} # Store intermediate inventory state
208
  inventory_after_arrival["Factory"] = factory_state['inventory'] + produced_units
209
-
210
- # Step 1b: Shipments arrive at downstream echelons
211
  for name in ["Retailer", "Wholesaler", "Distributor"]:
212
  arrived_shipment = 0
213
  if echelons[name]['incoming_shipments']:
214
- arrived_shipment = echelons[name]['incoming_shipments'].popleft() # Pop arrived shipment
215
  arrived_this_week[name] = arrived_shipment
216
  inventory_after_arrival[name] = echelons[name]['inventory'] + arrived_shipment
217
-
218
- # Step 2: Orders Arrive from Downstream
219
- total_backlog_before_shipping = {} # Store intermediate backlog state
220
  for name in echelon_order:
221
  incoming_order_for_this_week = 0
222
- if name == "Retailer":
223
- incoming_order_for_this_week = get_customer_demand(week)
224
  else:
225
  downstream_name = echelons[name]['downstream_name']
226
- if downstream_name:
227
- incoming_order_for_this_week = state['last_week_orders'].get(downstream_name, 0)
228
-
229
- echelons[name]['incoming_order'] = incoming_order_for_this_week # Store for logging/UI this week
230
  total_backlog_before_shipping[name] = echelons[name]['backlog'] + incoming_order_for_this_week
231
-
232
- # --- Create State Snapshot for AI/Human Decision Point ---
233
  decision_point_states = {}
234
  for name in echelon_order:
235
  decision_point_states[name] = {
236
- 'name': name,
237
- 'inventory': inventory_after_arrival[name], # Inventory available
238
- 'backlog': total_backlog_before_shipping[name], # Total demand to meet
239
- 'incoming_order': echelons[name]['incoming_order'], # Order received this week
240
  'incoming_shipments': echelons[name]['incoming_shipments'].copy() if name != "Factory" else deque(),
241
  }
242
-
243
- # --- Step 4: Agent Decisions (Place Orders / Schedule Production) ---
244
- current_week_orders = {} # Store THIS week's decisions
245
  for name in echelon_order:
246
- e = echelons[name]
247
- prompt_state = decision_point_states[name]
248
-
249
- if name == human_role:
250
- order_amount, raw_resp = human_final_order, "HUMAN_FINAL_INPUT"
251
  else:
252
  prompt = get_llm_prompt(prompt_state, week, llm_personality, info_sharing, decision_point_states)
253
  order_amount, raw_resp = get_llm_order_decision(prompt, name)
254
-
255
- llm_raw_responses[name] = raw_resp
256
- e['order_placed'] = max(0, order_amount)
257
- current_week_orders[name] = e['order_placed'] # Store for NEXT week's Step 2
258
-
259
- # --- Step 3 (Logic Moved): Fulfill orders (Ship Beer) ---
260
  units_shipped = {name: 0 for name in echelon_order}
261
  for name in echelon_order:
262
- e = echelons[name]
263
- demand_to_meet = total_backlog_before_shipping[name]
264
- available_inv = inventory_after_arrival[name]
265
-
266
- e['shipment_sent'] = min(available_inv, demand_to_meet)
267
- units_shipped[name] = e['shipment_sent'] # Store temporarily
268
-
269
- # Update the main state dict's inventory and backlog to reflect END OF WEEK state
270
- e['inventory'] = available_inv - e['shipment_sent']
271
- e['backlog'] = demand_to_meet - e['shipment_sent']
272
-
273
- # --- Step 5: Advance Pipelines (New Logic) ---
274
- # Factory's decision ('order_placed') from this week enters the production pipeline
275
- # This simulates the FACTORY_LEAD_TIME
276
- state['factory_production_pipeline'].append(echelons["Factory"]['order_placed'])
277
-
278
- # Items shipped in Step 3 now enter their respective shipping pipelines
279
- # Factory -> Distributor (uses FACTORY_SHIPPING_DELAY)
280
- if units_shipped["Factory"] > 0:
281
- echelons['Distributor']['incoming_shipments'].append(units_shipped["Factory"])
282
- # Distributor -> Wholesaler (uses SHIPPING_DELAY)
283
- if units_shipped['Distributor'] > 0:
284
- echelons['Wholesaler']['incoming_shipments'].append(units_shipped['Distributor'])
285
- # Wholesaler -> Retailer (uses SHIPPING_DELAY)
286
- if units_shipped['Wholesaler'] > 0:
287
- echelons['Retailer']['incoming_shipments'].append(units_shipped['Wholesaler'])
288
-
289
-
290
- # --- Calculate Costs & Log (End of Week) ---
291
  log_entry = {'timestamp': datetime.utcnow().isoformat() + "Z", 'week': week, **state}
292
  del log_entry['echelons'], log_entry['factory_production_pipeline'], log_entry['logs'], log_entry['last_week_orders']
293
-
294
  for name in echelon_order:
295
- e = echelons[name]
296
- e['weekly_cost'] = (e['inventory'] * HOLDING_COST) + (e['backlog'] * BACKLOG_COST)
297
- e['total_cost'] += e['weekly_cost']
298
-
299
- log_entry[f'{name}.inventory'] = e['inventory']; log_entry[f'{name}.backlog'] = e['backlog']
300
- log_entry[f'{name}.incoming_order'] = e['incoming_order']; log_entry[f'{name}.order_placed'] = e['order_placed']
301
- log_entry[f'{name}.shipment_sent'] = e['shipment_sent']; log_entry[f'{name}.weekly_cost'] = e['weekly_cost']
302
- log_entry[f'{name}.total_cost'] = e['total_cost']; log_entry[f'{name}.llm_raw_response'] = llm_raw_responses.get(name, "")
303
  log_entry[f'{name}.opening_inventory'] = opening_inventories[name]; log_entry[f'{name}.opening_backlog'] = opening_backlogs[name]
304
  log_entry[f'{name}.arrived_this_week'] = arrived_this_week[name]
305
-
306
- if name != 'Factory':
307
- log_entry[f'{name}.arriving_next_week'] = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
308
- else:
309
- log_entry[f'{name}.production_completing_next_week'] = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
310
-
311
  log_entry[f'{human_role}.initial_order'] = human_initial_order; log_entry[f'{human_role}.ai_suggestion'] = ai_suggestion
312
  state['logs'].append(log_entry)
313
-
314
- # --- Advance Week ---
315
  state['week'] += 1; state['decision_step'] = 'initial_order'; state['last_week_orders'] = current_week_orders
316
  if state['week'] > WEEKS: state['game_running'] = False
317
- # ==============================================================================
318
-
319
 
320
  def plot_results(df: pd.DataFrame, title: str, human_role: str):
321
  # This function remains correct.
@@ -376,34 +314,16 @@ if st.session_state.get('initialization_error'):
376
  else:
377
  # --- Game Setup & Instructions ---
378
  if 'game_state' not in st.session_state or not st.session_state.game_state.get('game_running', False):
379
-
380
- # --- Introduction Section (Remains Correct) ---
381
- st.markdown("---")
382
- st.header("📖 Welcome to the Beer Game!")
383
- st.markdown("This is a simulation of a supply chain. You will play against 3 AI agents. **You do not need any prior knowledge to play.** Please read these instructions carefully.")
384
- st.subheader("1. Your Goal: Minimize Costs")
385
- st.success("**Your single, most important goal is to: Minimize the total cost for your position in the supply chain.**")
386
- st.markdown("You get costs from two things every week:")
387
- st.markdown(f"- **Holding Inventory:** **${HOLDING_COST:,.2f} per unit per week.** (Cost applies to inventory left *after* shipping)\n- **Backlog (Unfilled Orders):** **${BACKLOG_COST:,.2f} per unit per week.** (Cost applies to orders you couldn't fill *after* shipping)")
388
- with st.expander("Click to see a cost calculation example"):
389
- st.markdown(f"Imagine at the **end** of Week 5, *after* you shipped beer to the Wholesaler, your final state is:\n- Inventory: 10 units\n- Backlog: 0 units\nYour cost for Week 5 would be calculated *at this point*:\n- `(10 units of Inventory * ${HOLDING_COST:,.2f})` = $5.00\n- `(0 units of Backlog * ${BACKLOG_COST:,.2f})` = $0.00\n- **Total Weekly Cost:** = **$5.00**\nThis cost is added to your cumulative total.")
390
- st.subheader("2. Your Role: The Distributor")
391
- st.markdown("You will always play as the **Distributor**. The other 3 roles are played by AI.\n- **Retailer (AI):** Sells to the final customer.\n- **Wholesaler (AI):** Sells to the Retailer.\n- **Distributor (You):** You sell to the Wholesaler.\n- **Factory (AI):** You order from the Factory.")
392
- try: st.image(IMAGE_PATH, caption="You are the Distributor. You get orders from the Wholesaler and place orders to the Factory.")
393
- except FileNotFoundError: st.warning("Image file not found.")
394
- st.subheader("3. The Core Challenge: Delays!")
395
- st.warning(f"It takes **{ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY} weeks** for an order you place to arrive in your inventory.")
396
- with st.expander("Click to see a detailed example of the 3-week delay"):
397
- st.markdown(f"* **Week 10 (You):** You place an order for **50**.\n* **Week 11 (System):** Your order arrives at the Factory (**{ORDER_PASSING_DELAY}w Order Delay**). Factory AI decides to produce 50.\n* **Week 12 (System):** Factory finishes producing 50 (**{FACTORY_LEAD_TIME}w Production Delay**) & ships it.\n* **Week 13 (System):** The 50 units arrive at your warehouse (**{FACTORY_SHIPPING_DELAY}w Shipping Delay**).\n**Conclusion:** Think 3 weeks ahead! Your order in Week 10 arrives at the start of Week 13.")
398
- st.subheader("4. Understanding Inventory & Backlog")
399
- st.markdown("Managing your inventory and backlog is key to minimizing costs. Here's how they work:\n* **Effective \"Orders to Fill\":** Each week, the total demand you need to satisfy is your `Incoming Order` for the week PLUS any `Backlog` carried over from the previous week.\n* **If you DON'T have enough inventory:**\n * You ship **all** the inventory you have (after receiving any arrivals for the week).\n * The remaining unfilled \"Orders to Fill\" becomes your **new Backlog** for next week.\n * **Backlog is cumulative!** If you start Week 10 with a backlog of 5, get an order for 8 (total needed = 13), receive 10 units, and ship those 10 units, your new backlog for Week 11 is `13 - 10 = 3`.\n* **If you DO have enough inventory:**\n * You ship all the \"Orders to Fill\".\n * Your Backlog becomes 0.\n * The remaining inventory is carried over to next week (and incurs holding costs).")
400
- st.subheader("5. The Bullwhip Effect (What to Avoid)")
401
- st.markdown("The \"Bullwhip Effect\" happens when small changes in customer demand cause **amplified**, chaotic swings in orders further up the supply chain (like you and the Factory). This often leads to cycles of **panic ordering** (ordering too much when out of stock) followed by **massive inventory pile-ups** (when late orders arrive). This cycle is very expensive. Try to order smoothly.")
402
- st.subheader("6. How Each Week Works & Understanding Your Dashboard")
403
- st.markdown(f"Your main job is simple: place one order each week based on the dashboard presented to you.\n\n**A) At the start of every week, BEFORE your turn:**\n* **(Step 1) Shipments Arrive:** Beer you ordered {ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY} weeks ago arrives.\n* **(Step 2) New Orders Arrive:** You receive a new order from the Wholesaler (their order from *last* week).\n* **(Step 3) You Ship Beer (Automatically):** The system ships beer *immediately* based on your inventory *after* Step 1 and the total demand *after* Step 2.\n\n**B) Your Dashboard (What You See for Your Turn):**\nThe dashboard shows your status **at the start of the week, BEFORE Steps 1, 2, and 3 happen**:\n* `Inventory (Opening)`: Your stock **at the beginning of the week**.\n* `Backlog (Opening)`: Unfilled orders **carried over from the end of last week**.\n* `Incoming Order (This Week)`: The specific order quantity that **will arrive** from the Wholesaler *during* this week (Step 2).\n* `Arriving This Week`: The shipment from the Factory that **will arrive** *during* this week (Step 1).\n* `Arriving Next Week`: The quantity scheduled to arrive at the start of the **next week**.\n\n**C) Your Decision (Step 4 - Two Parts):**\nNow, looking at the dashboard, you decide how much to order:\n* **(Step 4a - Initial Order):** Submit your first estimate. Input box starts blank.\n* **(Step 4b - Final Order):** See the AI's suggestion, then submit your final decision. This order will arrive in 3 weeks.\n\nSubmitting your final order ends the week. The system then calculates your `Weekly Cost` based on your inventory/backlog *after* Step 3 shipping, logs everything, and advances to the next week.")
404
-
405
- # --- Game Configuration ---
406
- st.markdown("---")
407
  st.header("⚙️ Game Configuration")
408
  c1, c2 = st.columns(2)
409
  with c1:
@@ -421,29 +341,36 @@ else:
421
  week, human_role, echelons, info_sharing = state['week'], state['human_role'], state['echelons'], state['info_sharing']
422
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"] # Define here for UI
423
 
 
424
  st.header(f"Week {week} / {WEEKS}")
425
  st.subheader(f"Your Role: **{human_role}** | AI Mode: **{state['llm_personality'].replace('_', ' ')}** | Information: **{state['info_sharing']}**")
426
  st.markdown("---")
427
  st.subheader("Supply Chain Status (Start of Week State)") # Clarified Timing
428
 
429
- # =============== MODIFIED UI LOGIC (v4.21) ===============
430
  if info_sharing == 'full':
431
  cols = st.columns(4)
432
- for i, name in enumerate(echelon_order):
433
  with cols[i]:
434
- e = echelons[name]
435
  icon = "👤" if name == human_role else "🤖"
436
 
 
437
  if name == human_role:
 
438
  st.markdown(f"##### **<span style='border: 1px solid #FF4B4B; padding: 2px 5px; border-radius: 3px;'>{icon} {name} (You)</span>**", unsafe_allow_html=True)
439
  else:
440
  st.markdown(f"##### {icon} {name}")
 
441
 
442
  st.metric("Inventory (Opening)", e['inventory'])
443
  st.metric("Backlog (Opening)", e['backlog'])
444
 
445
- # --- Calculate and Display This Week's Events ---
446
- # Incoming Order (arriving in Step 2)
 
 
 
 
447
  current_incoming_order = 0
448
  if name == "Retailer":
449
  current_incoming_order = get_customer_demand(week)
@@ -451,51 +378,41 @@ else:
451
  downstream_name = e['downstream_name']
452
  if downstream_name:
453
  current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
454
- st.write(f"Incoming Order (This Week): **{current_incoming_order}**")
455
-
 
456
  if name == "Factory":
457
- # Production completing THIS week (Step 1a)
458
- arriving_this_week = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
459
- st.write(f"Completing This Week: **{arriving_this_week}**")
460
  # Production completing NEXT week
461
- prod_completing_next = list(state['factory_production_pipeline'])[1] if len(state['factory_production_pipeline']) > 1 else 0
462
  st.write(f"Completing Next Week: **{prod_completing_next}**")
463
  else:
464
- # Shipment arriving THIS week (Step 1b)
465
- arriving_this_week = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
466
- st.write(f"Arriving This Week: **{arriving_this_week}**")
467
  # Shipment arriving NEXT week
468
- arriving_next = list(e['incoming_shipments'])[1] if len(e['incoming_shipments']) > 1 else 0
469
  st.write(f"Arriving Next Week: **{arriving_next}**")
470
-
471
  else: # Local Info Mode
472
  st.info("In Local Information mode, you can only see your own status dashboard.")
473
  e = echelons[human_role]
474
- st.markdown(f"### 👤 **<span style='color:#FF4B4B;'>{human_role} (Your Dashboard - Start of Week State)</span>**", unsafe_allow_html=True)
475
-
476
- col1, col2 = st.columns(2)
477
- with col1:
478
- st.metric("Inventory (Opening)", e['inventory'])
479
- st.metric("Backlog (Opening)", e['backlog'])
480
 
481
- with col2:
482
- # Calculate Incoming Order for this week
483
- current_incoming_order = 0
484
- downstream_name = e['downstream_name'] # Wholesaler
485
- if downstream_name:
486
- current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
487
- st.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}")
488
-
489
- # Arriving THIS week (Step 1)
490
- arriving_this_week = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
491
- st.write(f"**Shipment Arriving (This Week):**\n# {arriving_this_week}")
492
-
493
- # Arriving NEXT week
494
- arriving_next = list(e['incoming_shipments'])[1] if len(e['incoming_shipments']) > 1 else 0
495
- st.write(f"**Shipment Arriving (Next Week):**\n# {arriving_next}")
 
 
496
 
497
- # =======================================================
498
-
499
  st.markdown("---")
500
  st.header("Your Decision (Step 4)")
501
 
@@ -504,6 +421,7 @@ else:
504
  for name in echelon_order:
505
  e_curr = echelons[name] # This is END OF LAST WEEK state
506
  arrived = 0
 
507
  if name == "Factory":
508
  if state['factory_production_pipeline']: arrived = list(state['factory_production_pipeline'])[0]
509
  else:
@@ -539,6 +457,7 @@ else:
539
 
540
  elif state['decision_step'] == 'final_order':
541
  st.success(f"Your initial order was: **{state['human_initial_order']}** units.")
 
542
  prompt_sugg = get_llm_prompt(human_echelon_state_for_prompt, week, state['llm_personality'], state['info_sharing'], all_decision_point_states)
543
  ai_suggestion, _ = get_llm_order_decision(prompt_sugg, f"{human_role} (Suggestion)")
544
 
 
1
  # app.py
2
+ # @title Beer Game Final Version (v4.20 - NO Introduction)
3
 
4
  # -----------------------------------------------------------------------------
5
  # 1. Import Libraries
 
89
  else: shipping_weeks = SHIPPING_DELAY
90
 
91
  st.session_state.game_state['echelons'][name] = {
92
+ 'name': name,
93
+ 'inventory': INITIAL_INVENTORY, 'backlog': INITIAL_BACKLOG,
94
  'incoming_shipments': deque([0] * shipping_weeks, maxlen=shipping_weeks),
95
  'incoming_order': 0, 'order_placed': 0, 'shipment_sent': 0,
96
  'weekly_cost': 0, 'total_cost': 0, 'upstream_name': upstream, 'downstream_name': downstream,
 
184
  **React emotionally.** What is your knee-jerk {task_word}? Respond with a single integer.
185
  """
186
 
 
187
  def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
188
+ # This function's logic remains correct (from v4.17).
189
  state = st.session_state.game_state
190
  week, echelons, human_role = state['week'], state['echelons'], state['human_role']
191
  llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
192
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"]
193
  llm_raw_responses = {}
 
 
194
  opening_inventories = {name: e['inventory'] for name, e in echelons.items()}
195
  opening_backlogs = {name: e['backlog'] for name, e in echelons.items()}
196
  arrived_this_week = {name: 0 for name in echelon_order}
197
+ inventory_after_arrival = {}
 
 
 
198
  factory_state = echelons["Factory"]
199
  produced_units = 0
200
  if state['factory_production_pipeline']:
201
+ produced_units = state['factory_production_pipeline'].popleft()
202
  arrived_this_week["Factory"] = produced_units
 
203
  inventory_after_arrival["Factory"] = factory_state['inventory'] + produced_units
 
 
204
  for name in ["Retailer", "Wholesaler", "Distributor"]:
205
  arrived_shipment = 0
206
  if echelons[name]['incoming_shipments']:
207
+ arrived_shipment = echelons[name]['incoming_shipments'].popleft()
208
  arrived_this_week[name] = arrived_shipment
209
  inventory_after_arrival[name] = echelons[name]['inventory'] + arrived_shipment
210
+ total_backlog_before_shipping = {}
 
 
211
  for name in echelon_order:
212
  incoming_order_for_this_week = 0
213
+ if name == "Retailer": incoming_order_for_this_week = get_customer_demand(week)
 
214
  else:
215
  downstream_name = echelons[name]['downstream_name']
216
+ if downstream_name: incoming_order_for_this_week = state['last_week_orders'].get(downstream_name, 0)
217
+ echelons[name]['incoming_order'] = incoming_order_for_this_week
 
 
218
  total_backlog_before_shipping[name] = echelons[name]['backlog'] + incoming_order_for_this_week
 
 
219
  decision_point_states = {}
220
  for name in echelon_order:
221
  decision_point_states[name] = {
222
+ 'name': name, 'inventory': inventory_after_arrival[name],
223
+ 'backlog': total_backlog_before_shipping[name], 'incoming_order': echelons[name]['incoming_order'],
 
 
224
  'incoming_shipments': echelons[name]['incoming_shipments'].copy() if name != "Factory" else deque(),
225
  }
226
+ current_week_orders = {}
 
 
227
  for name in echelon_order:
228
+ e = echelons[name]; prompt_state = decision_point_states[name]
229
+ if name == human_role: order_amount, raw_resp = human_final_order, "HUMAN_FINAL_INPUT"
 
 
 
230
  else:
231
  prompt = get_llm_prompt(prompt_state, week, llm_personality, info_sharing, decision_point_states)
232
  order_amount, raw_resp = get_llm_order_decision(prompt, name)
233
+ llm_raw_responses[name] = raw_resp; e['order_placed'] = max(0, order_amount); current_week_orders[name] = e['order_placed']
234
+ state['factory_production_pipeline'].append(echelons["Factory"]['order_placed'])
 
 
 
 
235
  units_shipped = {name: 0 for name in echelon_order}
236
  for name in echelon_order:
237
+ e = echelons[name]; demand_to_meet = total_backlog_before_shipping[name]; available_inv = inventory_after_arrival[name]
238
+ e['shipment_sent'] = min(available_inv, demand_to_meet); units_shipped[name] = e['shipment_sent']
239
+ e['inventory'] = available_inv - e['shipment_sent']; e['backlog'] = demand_to_meet - e['shipment_sent']
240
+ if units_shipped["Factory"] > 0: echelons['Distributor']['incoming_shipments'].append(units_shipped["Factory"])
241
+ if units_shipped['Distributor'] > 0: echelons['Wholesaler']['incoming_shipments'].append(units_shipped['Distributor'])
242
+ if units_shipped['Wholesaler'] > 0: echelons['Retailer']['incoming_shipments'].append(units_shipped['Wholesaler'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  log_entry = {'timestamp': datetime.utcnow().isoformat() + "Z", 'week': week, **state}
244
  del log_entry['echelons'], log_entry['factory_production_pipeline'], log_entry['logs'], log_entry['last_week_orders']
 
245
  for name in echelon_order:
246
+ e = echelons[name]; e['weekly_cost'] = (e['inventory'] * HOLDING_COST) + (e['backlog'] * BACKLOG_COST); e['total_cost'] += e['weekly_cost']
247
+ for key in ['inventory', 'backlog', 'incoming_order', 'order_placed', 'shipment_sent', 'weekly_cost', 'total_cost']: log_entry[f'{name}.{key}'] = e[key]
248
+ log_entry[f'{name}.llm_raw_response'] = llm_raw_responses.get(name, "")
 
 
 
 
 
249
  log_entry[f'{name}.opening_inventory'] = opening_inventories[name]; log_entry[f'{name}.opening_backlog'] = opening_backlogs[name]
250
  log_entry[f'{name}.arrived_this_week'] = arrived_this_week[name]
251
+ if name != 'Factory': log_entry[f'{name}.arriving_next_week'] = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
252
+ else: log_entry[f'{name}.production_completing_next_week'] = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
 
 
 
 
253
  log_entry[f'{human_role}.initial_order'] = human_initial_order; log_entry[f'{human_role}.ai_suggestion'] = ai_suggestion
254
  state['logs'].append(log_entry)
 
 
255
  state['week'] += 1; state['decision_step'] = 'initial_order'; state['last_week_orders'] = current_week_orders
256
  if state['week'] > WEEKS: state['game_running'] = False
 
 
257
 
258
  def plot_results(df: pd.DataFrame, title: str, human_role: str):
259
  # This function remains correct.
 
314
  else:
315
  # --- Game Setup & Instructions ---
316
  if 'game_state' not in st.session_state or not st.session_state.game_state.get('game_running', False):
317
+
318
+ # =============== INTRODUCTION REMOVED ===============
319
+ # st.markdown("---")
320
+ # st.header("📖 Welcome to the Beer Game!")
321
+ # ... (Introduction text) ...
322
+ # st.subheader("6. How Each Week Works & Understanding Your Dashboard")
323
+ # ... (Explanation text) ...
324
+ # =======================================================
325
+
326
+ st.markdown("---") # Add a separator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  st.header("⚙️ Game Configuration")
328
  c1, c2 = st.columns(2)
329
  with c1:
 
341
  week, human_role, echelons, info_sharing = state['week'], state['human_role'], state['echelons'], state['info_sharing']
342
  echelon_order = ["Retailer", "Wholesaler", "Distributor", "Factory"] # Define here for UI
343
 
344
+
345
  st.header(f"Week {week} / {WEEKS}")
346
  st.subheader(f"Your Role: **{human_role}** | AI Mode: **{state['llm_personality'].replace('_', ' ')}** | Information: **{state['info_sharing']}**")
347
  st.markdown("---")
348
  st.subheader("Supply Chain Status (Start of Week State)") # Clarified Timing
349
 
 
350
  if info_sharing == 'full':
351
  cols = st.columns(4)
352
+ for i, name in enumerate(echelon_order): # Use the defined echelon_order
353
  with cols[i]:
354
+ e = echelons[name] # Get the echelon state
355
  icon = "👤" if name == human_role else "🤖"
356
 
357
+ # =============== UI CHANGE: Highlight Player ===============
358
  if name == human_role:
359
+ # Use markdown with HTML/CSS for highlighting
360
  st.markdown(f"##### **<span style='border: 1px solid #FF4B4B; padding: 2px 5px; border-radius: 3px;'>{icon} {name} (You)</span>**", unsafe_allow_html=True)
361
  else:
362
  st.markdown(f"##### {icon} {name}")
363
+ # ========================================================
364
 
365
  st.metric("Inventory (Opening)", e['inventory'])
366
  st.metric("Backlog (Opening)", e['backlog'])
367
 
368
+ # =============== UI CHANGE: Removed Costs ===============
369
+ # Costs are no longer displayed on the main dashboard
370
+ # =======================================================
371
+
372
+ # Display info about THIS week's events / NEXT week's arrivals
373
+ # Calculate the INCOMING order for THIS week
374
  current_incoming_order = 0
375
  if name == "Retailer":
376
  current_incoming_order = get_customer_demand(week)
 
378
  downstream_name = e['downstream_name']
379
  if downstream_name:
380
  current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
381
+
382
+ st.write(f"Incoming Order (This Week): **{current_incoming_order}**") # Display calculated order
383
+
384
  if name == "Factory":
 
 
 
385
  # Production completing NEXT week
386
+ prod_completing_next = list(state['factory_production_pipeline'])[0] if state['factory_production_pipeline'] else 0
387
  st.write(f"Completing Next Week: **{prod_completing_next}**")
388
  else:
 
 
 
389
  # Shipment arriving NEXT week
390
+ arriving_next = list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0
391
  st.write(f"Arriving Next Week: **{arriving_next}**")
 
392
  else: # Local Info Mode
393
  st.info("In Local Information mode, you can only see your own status dashboard.")
394
  e = echelons[human_role]
395
+ st.markdown(f"### 👤 **<span style='color:#FF4B4B;'>{human_role} (Your Dashboard - Start of Week State)</span>**", unsafe_allow_html=True) # Highlight self
396
+ col1, col2, col3, col4 = st.columns(4)
 
 
 
 
397
 
398
+ # Display OPENING state
399
+ col1.metric("Inventory (Opening)", e['inventory'])
400
+ col2.metric("Backlog (Opening)", e['backlog'])
401
+
402
+ # Display info about THIS week's events / NEXT week's arrivals
403
+ # Calculate the INCOMING order for THIS week
404
+ current_incoming_order = 0
405
+ downstream_name = e['downstream_name'] # Wholesaler
406
+ if downstream_name:
407
+ current_incoming_order = state['last_week_orders'].get(downstream_name, 0)
408
+
409
+ col3.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}") # Display calculated order
410
+ col4.write(f"**Shipment Arriving (Next Week):**\n# {list(e['incoming_shipments'])[0] if e['incoming_shipments'] else 0}")
411
+
412
+ # =============== UI CHANGE: Removed Costs ===============
413
+ # Costs are no longer displayed on the main dashboard
414
+ # =======================================================
415
 
 
 
416
  st.markdown("---")
417
  st.header("Your Decision (Step 4)")
418
 
 
421
  for name in echelon_order:
422
  e_curr = echelons[name] # This is END OF LAST WEEK state
423
  arrived = 0
424
+ # Peek at what *will* arrive this week (Step 1) based on current queues
425
  if name == "Factory":
426
  if state['factory_production_pipeline']: arrived = list(state['factory_production_pipeline'])[0]
427
  else:
 
457
 
458
  elif state['decision_step'] == 'final_order':
459
  st.success(f"Your initial order was: **{state['human_initial_order']}** units.")
460
+ # Use the correctly timed state for the prompt
461
  prompt_sugg = get_llm_prompt(human_echelon_state_for_prompt, week, state['llm_personality'], state['info_sharing'], all_decision_point_states)
462
  ai_suggestion, _ = get_llm_order_decision(prompt_sugg, f"{human_role} (Suggestion)")
463