Lilli98 commited on
Commit
097e6a9
·
verified ·
1 Parent(s): 3fb14e8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -70
app.py CHANGED
@@ -195,99 +195,104 @@ def init_game(weeks=DEFAULT_WEEKS):
195
  def step_game(state: dict, distributor_order: int):
196
  """
197
  Apply one week's dynamics.
198
- Order of events (typical simplification):
199
- 1. Customer demand hits retailer this week.
200
- 2. Deliveries that are at pipeline[front] arrive to each role this week.
201
- 3. Roles fulfill incoming orders from downstream (if backlog arises).
202
- 4. Human (distributor) order is recorded; LLMs decide orders for their roles.
203
- 5. Place orders into upstream's pipeline so they will arrive after TRANSPORT_DELAY.
204
- 6. Log everything.
205
  """
206
- week = state["week"]
 
 
207
  roles = state["roles"]
208
 
 
 
 
 
 
 
 
 
 
 
 
209
  # 1) Customer demand for this week to retailer
210
- demand = state["customer_demand"][week - 1] # week is 1-indexed
 
211
  state["incoming_orders"]["retailer"] = demand
212
 
213
- # 2) Shipments arrive (front of pipeline)
214
  arriving = {}
215
  for r in roles:
216
- # Pop front arrival if exists
217
  arr = 0
218
- if len(state["pipeline"][r]) > 0:
219
- arr = state["pipeline"][r].pop(0)
 
 
 
 
 
 
 
220
  state["inventory"][r] += arr
221
  arriving[r] = arr
222
 
223
- # 3) Fulfill incoming orders from downstream (downstream -> this role)
224
- # For each role, the incoming_order is whatever downstream ordered last turn.
225
- # For first week, incoming_orders maybe zero for non-retailer; that's fine.
226
  shipments_out = {}
227
  for r in roles:
228
- incoming = state["incoming_orders"].get(r, 0) or 0
229
- inv = state["inventory"].get(r, 0) or 0
230
  shipped = min(inv, incoming)
231
- state["inventory"][r] -= shipped
232
- # any unfilled becomes backlog
233
  unfilled = incoming - shipped
234
  if unfilled > 0:
 
235
  state["backlog"][r] += unfilled
236
  shipments_out[r] = shipped
237
- state["shipments_history"][r].append(shipped)
238
 
239
- # 4) Record human distributor order (this week's order placed by distributor)
240
- # distributor_order is the order placed to wholesaler by the distributor this week
241
- # Save to orders_history for distributor
242
- state["orders_history"]["distributor"].append(int(distributor_order))
243
- # Also set downstream->upstream linking: the upstream (wholesaler) will see distributor_order as incoming next period
244
  state["incoming_orders"]["wholesaler"] = int(distributor_order)
245
 
246
  # 5) LLM decisions for AI roles (retailer, wholesaler, factory)
247
  demand_history_visible = []
248
- if state["info_sharing"] and state["info_history_weeks"] > 0:
249
- start_idx = max(0, (week - 1) - state["info_history_weeks"])
250
- demand_history_visible = state["customer_demand"][start_idx: (week - 1)]
251
 
252
  llm_outputs = {}
253
  for role in ["retailer", "wholesaler", "factory"]:
254
- order_val, raw = call_llm_for_order(role, state_snapshot_for_prompt(state), state["info_sharing"], demand_history_visible)
255
  order_val = max(0, int(order_val))
256
- state["orders_history"][role].append(order_val)
257
  llm_outputs[role] = {"order": order_val, "raw": raw}
258
- # set incoming_orders for upstream relation: upstream will see this order next period
259
- # e.g., if retailer orders X, upstream (distributor) incoming_orders will be X
260
  if role == "retailer":
261
  state["incoming_orders"]["distributor"] = order_val
262
  elif role == "wholesaler":
263
  state["incoming_orders"]["factory"] = order_val
264
- # factory's upstream is the supplier/external: we don't model beyond factory
265
-
266
- # 6) Place orders into pipelines: these are shipments that will be sent upstream now and arrive after TRANSPORT_DELAY
267
- # In the simple Beer Game, the shipped amounts are based on inventories; but orders placed lead to upstream shipments in future after they process.
268
- # We'll model that orders placed this week translate into future shipments arriving after TRANSPORT_DELAY at the ordering party.
 
 
 
 
269
  for role in roles:
270
- # Determine the order placed by this role this week:
271
  if role == "distributor":
272
  placed_order = int(distributor_order)
273
  else:
274
- # role in orders_history last appended
275
- placed_order = state["orders_history"][role][-1] if state["orders_history"][role] else 0
276
-
277
- # For the downstream partner (the entity that will receive the shipment), we append to that partner's pipeline tail
278
- # Example: distributor placed order to wholesaler -> wholesaler will receive shipment after TRANSPORT_DELAY
279
- # Map role -> downstream partner (who receives shipments from role)
280
- # shipments flow downstream: factory -> wholesaler -> distributor -> retailer
281
- downstream_map = {
282
- "factory": "wholesaler",
283
- "wholesaler": "distributor",
284
- "distributor": "retailer",
285
- "retailer": None
286
- }
287
  downstream = downstream_map.get(role)
288
  if downstream:
289
- # append zeros if pipeline too short to ensure correct index, then append placed_order at tail
290
- # We want the placed_order to be delivered to downstream after TRANSPORT_DELAY weeks (so push at tail)
 
291
  state["pipeline"][downstream].append(placed_order)
292
 
293
  # 7) Log the week's summary
@@ -299,35 +304,49 @@ def step_game(state: dict, distributor_order: int):
299
  "shipments_out": shipments_out,
300
  "orders_submitted": {
301
  "distributor": int(distributor_order),
302
- "retailer": state["orders_history"]["retailer"][-1] if state["orders_history"]["retailer"] else None,
303
- "wholesaler": state["orders_history"]["wholesaler"][-1] if state["orders_history"]["wholesaler"] else None,
304
- "factory": state["orders_history"]["factory"][-1] if state["orders_history"]["factory"] else None,
305
  },
306
- "inventory": dict(state["inventory"]),
307
- "backlog": dict(state["backlog"]),
308
- "info_sharing": state["info_sharing"],
309
- "info_history_weeks": state["info_history_weeks"],
310
  "llm_raw": {k: v["raw"] for k, v in llm_outputs.items()}
311
  }
312
- state["logs"].append(log_entry)
313
 
314
  # 8) Advance week
315
- state["week"] += 1
316
 
317
  return state
318
 
319
  def state_snapshot_for_prompt(state):
320
  """
321
  Prepare a compact snapshot of state for LLM prompt (avoid sending huge objects).
322
- We'll include week, inventory and backlog for each role and incoming_orders for this week.
323
  """
 
 
 
 
 
 
 
 
 
 
 
324
  snap = {
325
- "week": state["week"],
326
- "inventory": state["inventory"].copy(),
327
- "backlog": state["backlog"].copy(),
328
- "incoming_orders": state["incoming_orders"].copy(),
329
- # pipeline front (arriving next week)
330
- "incoming_shipments_next_week": {r: (state["pipeline"][r][0] if state["pipeline"][r] else 0) for r in state["roles"]}
 
 
 
331
  }
332
  return snap
333
 
 
195
  def step_game(state: dict, distributor_order: int):
196
  """
197
  Apply one week's dynamics.
198
+ Defensive: ensure 'pipeline' exists and lists are padded; safe pops and appends.
 
 
 
 
 
 
199
  """
200
+ # Defensive defaults
201
+ if "roles" not in state:
202
+ state["roles"] = ["retailer", "wholesaler", "distributor", "factory"]
203
  roles = state["roles"]
204
 
205
+ # Ensure pipeline structure exists and each role has a list of length >= TRANSPORT_DELAY
206
+ if "pipeline" not in state or not isinstance(state["pipeline"], dict):
207
+ state["pipeline"] = {r: [0] * TRANSPORT_DELAY for r in roles}
208
+ else:
209
+ for r in roles:
210
+ state["pipeline"].setdefault(r, [0] * TRANSPORT_DELAY)
211
+ if len(state["pipeline"][r]) < TRANSPORT_DELAY:
212
+ state["pipeline"][r].extend([0] * (TRANSPORT_DELAY - len(state["pipeline"][r])))
213
+
214
+ week = state.get("week", 1)
215
+
216
  # 1) Customer demand for this week to retailer
217
+ demand = state.get("customer_demand", [])[week - 1] if (state.get("customer_demand") and week - 1 < len(state["customer_demand"])) else 0
218
+ state.setdefault("incoming_orders", {})
219
  state["incoming_orders"]["retailer"] = demand
220
 
221
+ # 2) Shipments arrive (front of pipeline) -- safe pop
222
  arriving = {}
223
  for r in roles:
 
224
  arr = 0
225
+ pl = state["pipeline"].get(r, [])
226
+ if len(pl) > 0:
227
+ try:
228
+ arr = pl.pop(0)
229
+ except Exception:
230
+ arr = 0
231
+ # ensure inventory keys
232
+ state.setdefault("inventory", {})
233
+ state["inventory"].setdefault(r, 0)
234
  state["inventory"][r] += arr
235
  arriving[r] = arr
236
 
237
+ # 3) Fulfill incoming orders from downstream
238
+ state.setdefault("backlog", {})
239
+ state.setdefault("shipments_history", {r: [] for r in roles})
240
  shipments_out = {}
241
  for r in roles:
242
+ incoming = state.get("incoming_orders", {}).get(r, 0) or 0
243
+ inv = state.get("inventory", {}).get(r, 0) or 0
244
  shipped = min(inv, incoming)
245
+ state["inventory"][r] = inv - shipped
 
246
  unfilled = incoming - shipped
247
  if unfilled > 0:
248
+ state["backlog"].setdefault(r, 0)
249
  state["backlog"][r] += unfilled
250
  shipments_out[r] = shipped
251
+ state["shipments_history"].setdefault(r, []).append(shipped)
252
 
253
+ # 4) Record human distributor order
254
+ state.setdefault("orders_history", {r: [] for r in roles})
255
+ state["orders_history"].setdefault("distributor", []).append(int(distributor_order))
256
+ # set incoming_orders for upstream relation
 
257
  state["incoming_orders"]["wholesaler"] = int(distributor_order)
258
 
259
  # 5) LLM decisions for AI roles (retailer, wholesaler, factory)
260
  demand_history_visible = []
261
+ if state.get("info_sharing") and state.get("info_history_weeks", 0) > 0:
262
+ start_idx = max(0, (week - 1) - state.get("info_history_weeks", 0))
263
+ demand_history_visible = state.get("customer_demand", [])[start_idx: (week - 1)]
264
 
265
  llm_outputs = {}
266
  for role in ["retailer", "wholesaler", "factory"]:
267
+ order_val, raw = call_llm_for_order(role, state_snapshot_for_prompt(state), state.get("info_sharing", False), demand_history_visible)
268
  order_val = max(0, int(order_val))
269
+ state["orders_history"].setdefault(role, []).append(order_val)
270
  llm_outputs[role] = {"order": order_val, "raw": raw}
271
+ # set incoming_orders mapping for next period
 
272
  if role == "retailer":
273
  state["incoming_orders"]["distributor"] = order_val
274
  elif role == "wholesaler":
275
  state["incoming_orders"]["factory"] = order_val
276
+ # factory's upstream beyond model
277
+
278
+ # 6) Place orders into pipelines: append to downstream partner's pipeline tail
279
+ downstream_map = {
280
+ "factory": "wholesaler",
281
+ "wholesaler": "distributor",
282
+ "distributor": "retailer",
283
+ "retailer": None
284
+ }
285
  for role in roles:
 
286
  if role == "distributor":
287
  placed_order = int(distributor_order)
288
  else:
289
+ placed_order = int(state["orders_history"].get(role, [0])[-1] if state["orders_history"].get(role) else 0)
290
+
 
 
 
 
 
 
 
 
 
 
 
291
  downstream = downstream_map.get(role)
292
  if downstream:
293
+ # ensure downstream pipeline exists
294
+ state["pipeline"].setdefault(downstream, [0] * TRANSPORT_DELAY)
295
+ # append placed_order at tail (arrival after TRANSPORT_DELAY)
296
  state["pipeline"][downstream].append(placed_order)
297
 
298
  # 7) Log the week's summary
 
304
  "shipments_out": shipments_out,
305
  "orders_submitted": {
306
  "distributor": int(distributor_order),
307
+ "retailer": state["orders_history"].get("retailer", [None])[-1],
308
+ "wholesaler": state["orders_history"].get("wholesaler", [None])[-1],
309
+ "factory": state["orders_history"].get("factory", [None])[-1],
310
  },
311
+ "inventory": dict(state.get("inventory", {})),
312
+ "backlog": dict(state.get("backlog", {})),
313
+ "info_sharing": state.get("info_sharing", False),
314
+ "info_history_weeks": state.get("info_history_weeks", 0),
315
  "llm_raw": {k: v["raw"] for k, v in llm_outputs.items()}
316
  }
317
+ state.setdefault("logs", []).append(log_entry)
318
 
319
  # 8) Advance week
320
+ state["week"] = state.get("week", 1) + 1
321
 
322
  return state
323
 
324
  def state_snapshot_for_prompt(state):
325
  """
326
  Prepare a compact snapshot of state for LLM prompt (avoid sending huge objects).
327
+ Robust to missing 'pipeline' key or missing role entries.
328
  """
329
+ roles = state.get("roles", ["retailer", "wholesaler", "distributor", "factory"])
330
+ # Ensure pipeline exists and has at least TRANSPORT_DELAY entries per role
331
+ pipeline = state.get("pipeline", {})
332
+ for r in roles:
333
+ if r not in pipeline:
334
+ pipeline[r] = [0] * TRANSPORT_DELAY
335
+ else:
336
+ # if pipeline exists but is shorter than transport delay, pad with zeros
337
+ if len(pipeline[r]) < TRANSPORT_DELAY:
338
+ pipeline[r] = pipeline[r] + [0] * (TRANSPORT_DELAY - len(pipeline[r]))
339
+
340
  snap = {
341
+ "week": state.get("week", 1),
342
+ "inventory": state.get("inventory", {}).copy(),
343
+ "backlog": state.get("backlog", {}).copy(),
344
+ "incoming_orders": state.get("incoming_orders", {}).copy(),
345
+ # pipeline front (arriving next week) -- safe access
346
+ "incoming_shipments_next_week": {
347
+ r: (pipeline.get(r, [0])[0] if pipeline.get(r) and len(pipeline.get(r)) > 0 else 0)
348
+ for r in roles
349
+ }
350
  }
351
  return snap
352