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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -89
app.py CHANGED
@@ -195,104 +195,99 @@ def init_game(weeks=DEFAULT_WEEKS):
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,49 +299,35 @@ def step_game(state: dict, distributor_order: int):
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
 
@@ -549,3 +530,4 @@ if state["week"] > state["weeks_total"]:
549
  url = upload_log_to_hf(final_csv, participant_id)
550
  if url:
551
  st.write(f"Final logs uploaded to HF Hub: {url}")
 
 
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
  "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
 
 
530
  url = upload_log_to_hf(final_csv, participant_id)
531
  if url:
532
  st.write(f"Final logs uploaded to HF Hub: {url}")
533
+