Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# app.py
|
| 2 |
-
# @title Beer Game Final Version (
|
| 3 |
|
| 4 |
# -----------------------------------------------------------------------------
|
| 5 |
# 1. Import Libraries
|
|
@@ -35,8 +35,10 @@ INITIAL_BACKLOG = 0
|
|
| 35 |
ORDER_PASSING_DELAY = 1 # Handled by last_week_orders
|
| 36 |
SHIPPING_DELAY = 2 # General shipping delay (R->W, W->D)
|
| 37 |
FACTORY_LEAD_TIME = 1
|
| 38 |
-
#
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
HOLDING_COST = 0.5
|
| 41 |
BACKLOG_COST = 1.0
|
| 42 |
|
|
@@ -87,9 +89,12 @@ def init_game_state(llm_personality: str, info_sharing: str, participant_id: str
|
|
| 87 |
for i, name in enumerate(roles):
|
| 88 |
upstream = roles[i + 1] if i + 1 < len(roles) else None
|
| 89 |
downstream = roles[i - 1] if i - 1 >= 0 else None
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
elif name == "Factory": shipping_weeks = 0
|
| 92 |
else: shipping_weeks = SHIPPING_DELAY # This is 2
|
|
|
|
| 93 |
st.session_state.game_state['echelons'][name] = {
|
| 94 |
'name': name, 'inventory': INITIAL_INVENTORY, 'backlog': INITIAL_BACKLOG,
|
| 95 |
'incoming_shipments': deque([0] * shipping_weeks, maxlen=shipping_weeks),
|
|
@@ -142,9 +147,9 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
|
|
| 142 |
|
| 143 |
if llm_personality == 'perfect_rational' and info_sharing == 'full':
|
| 144 |
stable_demand = current_stable_demand # Use the correct demand
|
| 145 |
-
# --------------------- LT=3
|
| 146 |
if e_state['name'] == 'Factory': total_lead_time = FACTORY_LEAD_TIME # 1
|
| 147 |
-
elif e_state['name'] == 'Distributor': total_lead_time = ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY # 1+1+
|
| 148 |
else: total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY # 1+2 = 3
|
| 149 |
# ----------------------------------------------------
|
| 150 |
safety_stock = 4
|
|
@@ -245,9 +250,9 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
|
|
| 245 |
"""
|
| 246 |
# =========================================================
|
| 247 |
|
| 248 |
-
# =============== STEP_GAME (
|
| 249 |
def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
|
| 250 |
-
# This version uses the stable v2 game logic AND the correct
|
| 251 |
state = st.session_state.game_state
|
| 252 |
week, echelons, human_role = state['week'], state['echelons'], state['human_role']
|
| 253 |
llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
|
|
@@ -258,7 +263,7 @@ def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: i
|
|
| 258 |
opening_inventories = {name: e['inventory'] for name, e in echelons.items()}
|
| 259 |
opening_backlogs = {name: e['backlog'] for name, e in echelons.items()}
|
| 260 |
|
| 261 |
-
# --- LOG FIX (
|
| 262 |
arrived_this_week_LOG = {name: 0 for name in echelon_order}
|
| 263 |
arriving_next_week_LOG = {name: 0 for name in echelon_order}
|
| 264 |
factory_q = state['factory_production_pipeline']
|
|
@@ -274,44 +279,25 @@ def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: i
|
|
| 274 |
if shipment_q:
|
| 275 |
arrived_this_week_LOG[name] = shipment_q[0] # Arrives this week (W+0)
|
| 276 |
|
| 277 |
-
#
|
| 278 |
if name == 'Distributor':
|
| 279 |
-
# "Next" for Distributor is
|
| 280 |
-
#
|
| 281 |
-
#
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
#
|
| 285 |
-
# The item for W+1 is what is in `factory_q`
|
| 286 |
-
|
| 287 |
-
# --- THIS IS THE REAL FIX V9 ---
|
| 288 |
-
# "Arriving Next Week" for Distributor (who has maxlen=1)
|
| 289 |
-
# is the item that will be SHIPPED this week, which is in the factory_q
|
| 290 |
-
if factory_q:
|
| 291 |
-
arriving_next_week_LOG[name] = factory_q[0]
|
| 292 |
-
# This is what I had before, and it caused the "8" in the log.
|
| 293 |
-
# Let's re-read the user's trace:
|
| 294 |
-
# "W2 order 4 should... in W4 show arriving next week 4"
|
| 295 |
-
# W4: ANW=4. (Item for W5)
|
| 296 |
-
# Item for W5 is Order from W2.
|
| 297 |
-
# At start of W4:
|
| 298 |
-
# `factory_q` = [8] (Order from W3, for W6)
|
| 299 |
-
# `shipping_q` (Dist) = [4] (Order from W2, for W5)
|
| 300 |
-
# The user wants "Arriving Next Week" to be 4.
|
| 301 |
-
# This means `arriving_next_week` MUST read `shipping_q[0]`.
|
| 302 |
-
|
| 303 |
-
if shipment_q: # Read item for W+1
|
| 304 |
-
arriving_next_week_LOG[name] = shipment_q[0]
|
| 305 |
-
|
| 306 |
elif name in ("Retailer", "Wholesaler"):
|
| 307 |
-
# "Next" for R/W is the *second* item in their queue
|
| 308 |
if len(shipment_q) > 1:
|
| 309 |
arriving_next_week_LOG[name] = shipment_q[1]
|
| 310 |
-
# --- END LOG FIX (
|
| 311 |
|
| 312 |
|
| 313 |
# === NOW, THE STABLE (v2) GAME LOGIC ===
|
| 314 |
-
|
|
|
|
|
|
|
| 315 |
inventory_after_arrival = {}
|
| 316 |
factory_state = echelons["Factory"]
|
| 317 |
produced_units = 0
|
|
@@ -371,7 +357,7 @@ def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: i
|
|
| 371 |
for key in ['inventory', 'backlog', 'incoming_order', 'order_placed', 'shipment_sent', 'weekly_cost', 'total_cost']: log_entry[f'{name}.{key}'] = e[key]
|
| 372 |
log_entry[f'{name}.llm_raw_response'] = llm_raw_responses.get(name, "")
|
| 373 |
|
| 374 |
-
# --- LOG FIX (
|
| 375 |
log_entry[f'{name}.opening_inventory'] = opening_inventories[name]
|
| 376 |
log_entry[f'{name}.opening_backlog'] = opening_backlogs[name]
|
| 377 |
log_entry[f'{name}.arrived_this_week'] = arrived_this_week_LOG[name] # Use captured
|
|
@@ -380,7 +366,7 @@ def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: i
|
|
| 380 |
log_entry[f'{name}.arriving_next_week'] = arriving_next_week_LOG[name] # Use captured
|
| 381 |
else:
|
| 382 |
log_entry[f'{name}.production_completing_next_week'] = arriving_next_week_LOG[name] # Use captured
|
| 383 |
-
# --- END OF LOG FIX (
|
| 384 |
|
| 385 |
log_entry[f'{human_role}.initial_order'] = human_initial_order; log_entry[f'{human_role}.ai_suggestion'] = ai_suggestion
|
| 386 |
state['logs'].append(log_entry)
|
|
@@ -610,7 +596,7 @@ else:
|
|
| 610 |
st.markdown("---")
|
| 611 |
st.subheader("Supply Chain Status (Start of Week State)")
|
| 612 |
|
| 613 |
-
# =============== MODIFIED UI LOGIC (
|
| 614 |
if info_sharing == 'full':
|
| 615 |
cols = st.columns(4)
|
| 616 |
for i, name in enumerate(echelon_order):
|
|
@@ -641,17 +627,16 @@ else:
|
|
| 641 |
else:
|
| 642 |
arriving_next = 0
|
| 643 |
|
| 644 |
-
#
|
| 645 |
-
# "Arriving Next Week" (W+1)
|
| 646 |
if name == 'Distributor':
|
| 647 |
-
#
|
| 648 |
q = e['incoming_shipments']
|
| 649 |
-
if q: arriving_next = list(q)[
|
|
|
|
| 650 |
elif name in ('Wholesaler', 'Retailer'):
|
| 651 |
-
#
|
| 652 |
q = e['incoming_shipments']
|
| 653 |
if len(q) > 1: arriving_next = list(q)[1]
|
| 654 |
-
# --- END UI FIX V9 ---
|
| 655 |
|
| 656 |
st.write(f"Arriving Next Week: **{arriving_next}**")
|
| 657 |
|
|
@@ -673,13 +658,13 @@ else:
|
|
| 673 |
st.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}")
|
| 674 |
|
| 675 |
with col3:
|
| 676 |
-
# --------------------- LOCAL UI FIX
|
| 677 |
# "Arriving Next Week" for Distributor in LOCAL mode.
|
| 678 |
-
#
|
| 679 |
arriving_next = 0
|
| 680 |
q = e['incoming_shipments']
|
| 681 |
-
if q:
|
| 682 |
-
arriving_next = list(q)[
|
| 683 |
st.write(f"**Shipment Arriving (Next Week):**\n# {arriving_next}")
|
| 684 |
# -----------------------------------------------------------
|
| 685 |
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
# @title Beer Game Final Version (v7 - THE REAL FIX: LT=3, Log Fix, Prompt Fix)
|
| 3 |
|
| 4 |
# -----------------------------------------------------------------------------
|
| 5 |
# 1. Import Libraries
|
|
|
|
| 35 |
ORDER_PASSING_DELAY = 1 # Handled by last_week_orders
|
| 36 |
SHIPPING_DELAY = 2 # General shipping delay (R->W, W->D)
|
| 37 |
FACTORY_LEAD_TIME = 1
|
| 38 |
+
# --------------------- THE REAL BUG FIX (LT=3) ---------------------
|
| 39 |
+
# This MUST be 2 for LT=3. (Order W2 -> F Rec W3 -> F Prod W3 -> F Fin W4 -> F Ship W4 -> D Rec W5)
|
| 40 |
+
FACTORY_SHIPPING_DELAY = 2 # Specific delay from Factory to Distributor
|
| 41 |
+
# -------------------------------------------------------------------
|
| 42 |
HOLDING_COST = 0.5
|
| 43 |
BACKLOG_COST = 1.0
|
| 44 |
|
|
|
|
| 89 |
for i, name in enumerate(roles):
|
| 90 |
upstream = roles[i + 1] if i + 1 < len(roles) else None
|
| 91 |
downstream = roles[i - 1] if i - 1 >= 0 else None
|
| 92 |
+
|
| 93 |
+
# USE THE CORRECT DELAY
|
| 94 |
+
if name == "Distributor": shipping_weeks = FACTORY_SHIPPING_DELAY # This is 2
|
| 95 |
elif name == "Factory": shipping_weeks = 0
|
| 96 |
else: shipping_weeks = SHIPPING_DELAY # This is 2
|
| 97 |
+
|
| 98 |
st.session_state.game_state['echelons'][name] = {
|
| 99 |
'name': name, 'inventory': INITIAL_INVENTORY, 'backlog': INITIAL_BACKLOG,
|
| 100 |
'incoming_shipments': deque([0] * shipping_weeks, maxlen=shipping_weeks),
|
|
|
|
| 147 |
|
| 148 |
if llm_personality == 'perfect_rational' and info_sharing == 'full':
|
| 149 |
stable_demand = current_stable_demand # Use the correct demand
|
| 150 |
+
# --------------------- LT=3 FIX ---------------------
|
| 151 |
if e_state['name'] == 'Factory': total_lead_time = FACTORY_LEAD_TIME # 1
|
| 152 |
+
elif e_state['name'] == 'Distributor': total_lead_time = ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY # 1+1+2 = 4
|
| 153 |
else: total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY # 1+2 = 3
|
| 154 |
# ----------------------------------------------------
|
| 155 |
safety_stock = 4
|
|
|
|
| 250 |
"""
|
| 251 |
# =========================================================
|
| 252 |
|
| 253 |
+
# =============== STEP_GAME (v8) - Stable Logic + Correct Log Fix ===============
|
| 254 |
def step_game(human_final_order: int, human_initial_order: int, ai_suggestion: int):
|
| 255 |
+
# This version uses the stable v2 game logic AND the correct v5 logging fix
|
| 256 |
state = st.session_state.game_state
|
| 257 |
week, echelons, human_role = state['week'], state['echelons'], state['human_role']
|
| 258 |
llm_personality, info_sharing = state['llm_personality'], state['info_sharing']
|
|
|
|
| 263 |
opening_inventories = {name: e['inventory'] for name, e in echelons.items()}
|
| 264 |
opening_backlogs = {name: e['backlog'] for name, e in echelons.items()}
|
| 265 |
|
| 266 |
+
# --- LOG FIX (v8): Capture UI-visible values BEFORE any pops ---
|
| 267 |
arrived_this_week_LOG = {name: 0 for name in echelon_order}
|
| 268 |
arriving_next_week_LOG = {name: 0 for name in echelon_order}
|
| 269 |
factory_q = state['factory_production_pipeline']
|
|
|
|
| 279 |
if shipment_q:
|
| 280 |
arrived_this_week_LOG[name] = shipment_q[0] # Arrives this week (W+0)
|
| 281 |
|
| 282 |
+
# This logic MUST match the UI logic (v8)
|
| 283 |
if name == 'Distributor':
|
| 284 |
+
# "Next" for Distributor is the *second* item in its queue (or factory pipeline)
|
| 285 |
+
# --------------------- LT=3 FIX ---------------------
|
| 286 |
+
# With maxlen=2, the queue [0, 4] -> [0] is this week, [1] is next week
|
| 287 |
+
if len(shipment_q) > 1:
|
| 288 |
+
arriving_next_week_LOG[name] = shipment_q[1]
|
| 289 |
+
# ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
elif name in ("Retailer", "Wholesaler"):
|
| 291 |
+
# "Next" for R/W is the *second* item in their queue
|
| 292 |
if len(shipment_q) > 1:
|
| 293 |
arriving_next_week_LOG[name] = shipment_q[1]
|
| 294 |
+
# --- END LOG FIX (v8) ---
|
| 295 |
|
| 296 |
|
| 297 |
# === NOW, THE STABLE (v2) GAME LOGIC ===
|
| 298 |
+
# This block is separate from the logging block above.
|
| 299 |
+
# It uses its *own* variables to run the game.
|
| 300 |
+
arrived_this_week_GAME = {name: 0 for name in echelon_order} # Use a fresh dict for game logic
|
| 301 |
inventory_after_arrival = {}
|
| 302 |
factory_state = echelons["Factory"]
|
| 303 |
produced_units = 0
|
|
|
|
| 357 |
for key in ['inventory', 'backlog', 'incoming_order', 'order_placed', 'shipment_sent', 'weekly_cost', 'total_cost']: log_entry[f'{name}.{key}'] = e[key]
|
| 358 |
log_entry[f'{name}.llm_raw_response'] = llm_raw_responses.get(name, "")
|
| 359 |
|
| 360 |
+
# --- LOG FIX (v8): Use captured values ---
|
| 361 |
log_entry[f'{name}.opening_inventory'] = opening_inventories[name]
|
| 362 |
log_entry[f'{name}.opening_backlog'] = opening_backlogs[name]
|
| 363 |
log_entry[f'{name}.arrived_this_week'] = arrived_this_week_LOG[name] # Use captured
|
|
|
|
| 366 |
log_entry[f'{name}.arriving_next_week'] = arriving_next_week_LOG[name] # Use captured
|
| 367 |
else:
|
| 368 |
log_entry[f'{name}.production_completing_next_week'] = arriving_next_week_LOG[name] # Use captured
|
| 369 |
+
# --- END OF LOG FIX (v8) ---
|
| 370 |
|
| 371 |
log_entry[f'{human_role}.initial_order'] = human_initial_order; log_entry[f'{human_role}.ai_suggestion'] = ai_suggestion
|
| 372 |
state['logs'].append(log_entry)
|
|
|
|
| 596 |
st.markdown("---")
|
| 597 |
st.subheader("Supply Chain Status (Start of Week State)")
|
| 598 |
|
| 599 |
+
# =============== MODIFIED UI LOGIC (v8) ===============
|
| 600 |
if info_sharing == 'full':
|
| 601 |
cols = st.columns(4)
|
| 602 |
for i, name in enumerate(echelon_order):
|
|
|
|
| 627 |
else:
|
| 628 |
arriving_next = 0
|
| 629 |
|
| 630 |
+
# This logic matches the v8 Log Fix
|
|
|
|
| 631 |
if name == 'Distributor':
|
| 632 |
+
# --------------------- LT=3 FIX ---------------------
|
| 633 |
q = e['incoming_shipments']
|
| 634 |
+
if len(q) > 1: arriving_next = list(q)[1]
|
| 635 |
+
# ----------------------------------------------------
|
| 636 |
elif name in ('Wholesaler', 'Retailer'):
|
| 637 |
+
# "Next" for R/W is the *second* item in their queue
|
| 638 |
q = e['incoming_shipments']
|
| 639 |
if len(q) > 1: arriving_next = list(q)[1]
|
|
|
|
| 640 |
|
| 641 |
st.write(f"Arriving Next Week: **{arriving_next}**")
|
| 642 |
|
|
|
|
| 658 |
st.write(f"**Incoming Order (This Week):**\n# {current_incoming_order}")
|
| 659 |
|
| 660 |
with col3:
|
| 661 |
+
# --------------------- LOCAL UI FIX V8 ---------------------
|
| 662 |
# "Arriving Next Week" for Distributor in LOCAL mode.
|
| 663 |
+
# With LT=3 (maxlen=2), we can now show list(q)[1]
|
| 664 |
arriving_next = 0
|
| 665 |
q = e['incoming_shipments']
|
| 666 |
+
if len(q) > 1:
|
| 667 |
+
arriving_next = list(q)[1]
|
| 668 |
st.write(f"**Shipment Arriving (Next Week):**\n# {arriving_next}")
|
| 669 |
# -----------------------------------------------------------
|
| 670 |
|