Lilli98 commited on
Commit
38135f4
·
verified ·
1 Parent(s): b65258e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -31
app.py CHANGED
@@ -104,7 +104,7 @@ def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
104
  if not client: return 8, "NO_API_KEY_DEFAULT"
105
  with st.spinner(f"Getting AI decision for {echelon_name}..."):
106
  try:
107
- temp = 0.1 if 'rational' in prompt else 0.7
108
  response = client.chat.completions.create(
109
  model=OPENAI_MODEL,
110
  messages=[
@@ -122,13 +122,12 @@ def get_llm_order_decision(prompt: str, echelon_name: str) -> (int, str):
122
  st.error(f"API call failed for {echelon_name}: {e}. Defaulting to 4.")
123
  return 4, f"API_ERROR: {e}"
124
 
125
- # =============== PROMPT FUNCTION (v3 - Sterman Heuristic + Demand Fix) ===============
126
  def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personality: str, info_sharing: str, all_echelons_state_decision_point: dict) -> str:
127
  # This function's logic is updated for "human_like" to follow a flawed Sterman heuristic.
128
  e_state = echelon_state_decision_point
129
  base_info = f"Your Current Status at the **{e_state['name']}** for **Week {week}** (Before Shipping):\n- On-hand inventory: {e_state['inventory']} units.\n- Backlog (total unfilled orders): {e_state['backlog']} units.\n- Incoming order this week (just received): {e_state['incoming_order']} units.\n"
130
 
131
- # --- PROMPT FIX: Get correct demand (current, not future) ---
132
  current_stable_demand = get_customer_demand(week) # Use current week's demand
133
 
134
  if e_state['name'] == 'Factory':
@@ -141,60 +140,97 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
141
  # --- PERFECT RATIONAL (NORMATIVE) PROMPTS ---
142
 
143
  if llm_personality == 'perfect_rational' and info_sharing == 'full':
144
- stable_demand = current_stable_demand # Use the correct demand
145
- # --------------------- LT=3 (1+1+1) ---------------------
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+1 = 3
148
- else: total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY # 1+2 = 3
149
- # ----------------------------------------------------
 
 
 
 
150
  safety_stock = 4
151
  target_inventory_level = (stable_demand * total_lead_time) + safety_stock
 
 
 
 
152
  if e_state['name'] == 'Factory':
153
- inventory_position = (e_state['inventory'] - e_state['backlog'] + sum(st.session_state.game_state['factory_production_pipeline']))
154
- inv_pos_components = f"(Inv={e_state['inventory']} - Backlog={e_state['backlog']} + InProd={sum(st.session_state.game_state['factory_production_pipeline'])})"
155
- else:
156
- order_in_transit_to_supplier = st.session_state.game_state['last_week_orders'].get(e_state['name'], 0)
157
- inventory_position = (e_state['inventory'] - e_state['backlog'] + sum(e_state['incoming_shipments']) + order_in_transit_to_supplier)
158
- inv_pos_components = f"(Inv={e_state['inventory']} - Backlog={e_state['backlog']} + InTransitShip={sum(e_state['incoming_shipments'])} + OrderToSupplier={order_in_transit_to_supplier})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  optimal_order = max(0, int(target_inventory_level - inventory_position))
160
  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."
161
 
162
  elif llm_personality == 'perfect_rational' and info_sharing == 'local':
163
- safety_stock = 4; anchor_demand = e_state['incoming_order']
 
164
  inventory_correction = safety_stock - (e_state['inventory'] - e_state['backlog'])
 
 
165
  if e_state['name'] == 'Factory':
 
166
  supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
167
  supply_line_desc = "In Production"
168
- else:
169
- order_in_transit_to_supplier = st.session_state.game_state['last_week_orders'].get(e_state['name'], 0)
170
- supply_line = sum(e_state['incoming_shipments']) + order_in_transit_to_supplier
171
- supply_line_desc = "Supply Line (In Transit Shipments + Order To Supplier)"
 
 
 
 
 
 
 
 
 
172
  calculated_order = anchor_demand + inventory_correction - supply_line
173
  rational_local_order = max(0, int(calculated_order))
174
- 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 (before shipping) is {e_state['inventory'] - e_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 decision.\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."
 
175
 
176
  # --- HUMAN-LIKE (DESCRIPTIVE) PROMPTS ---
177
 
178
- else: # Catches both 'human_like' / 'local' and 'human_like' / 'full'
179
-
180
- # This is the flawed Sterman heuristic
181
  DESIRED_INVENTORY = 12 # Matches initial inventory
182
- anchor_demand = e_state['incoming_order']
183
  net_inventory = e_state['inventory'] - e_state['backlog']
184
  stock_correction = DESIRED_INVENTORY - net_inventory
185
- panicky_order = max(0, int(anchor_demand + stock_correction))
186
- panicky_order_calc = f"{anchor_demand} (Your Incoming Order) + {stock_correction} (Your Stock Correction)"
187
 
188
  # Get supply line info *just to show* the AI it's being ignored
189
  if e_state['name'] == 'Factory':
190
  supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
191
  supply_line_desc = "In Production"
192
  else:
 
193
  order_in_transit_to_supplier = st.session_state.game_state['last_week_orders'].get(e_state['name'], 0)
194
- supply_line = sum(e_state['incoming_shipments']) + order_in_transit_to_supplier
195
  supply_line_desc = "Supply Line"
196
 
197
  if info_sharing == 'local':
 
 
 
 
 
198
  return f"""
199
  **You are a reactive supply chain manager for the {e_state['name']}.** You have a limited (local) view.
200
  You tend to make **reactive, 'gut-instinct' decisions** (like the classic Sterman 1989 model) that cause the Bullwhip Effect.
@@ -217,6 +253,11 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
217
  """
218
 
219
  elif info_sharing == 'full':
 
 
 
 
 
220
  # Build the "Full Info" string just for context
221
  full_info_str = f"\n**Full Supply Chain Information (State Before Shipping):**\n- End-Customer Demand this week: {current_stable_demand} units.\n"
222
  for name, other_e_state in all_echelons_state_decision_point.items():
@@ -230,14 +271,14 @@ def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personalit
230
  **A "Human-like" Flawed Decision:**
231
  Even though you have full information, you are judged by *your own* performance (your inventory, your backlog).
232
  You tend to react to your *local* situation (like the classic Sterman 1989 model) instead of using the complex full-system data.
233
- A 'rational' player would use the end-customer demand ({current_stable_demand}) and account for the *entire* system, but your gut-instinct is to panic about *your* numbers.
234
 
235
  **Your 'Panic' Calculation (Ignoring Full Info and Your Supply Line):**
236
- 1. **Anchor on *Your* Demand:** You just got an order for **{anchor_demand}** units. You react to this, not the end-customer demand.
237
  2. **Correct for *Your* Stock:** Your desired 'safe' inventory is {DESIRED_INVENTORY}. Your current net inventory is {net_inventory}. You need to order **{stock_correction}** more units.
238
  3. **Ignore *Your* Supply Line:** You'll ignore the **{supply_line} units** in your own pipeline ({supply_line_desc}).
239
 
240
- **Final Panic Order:** (Your Incoming Order) + (Your Stock Correction)
241
  * Order = {panicky_order_calc} = **{panicky_order} units**.
242
 
243
  **Your Task:** Confirm this 'gut-instinct', locally-focused {task_word}. Respond with a single integer.
 
104
  if not client: return 8, "NO_API_KEY_DEFAULT"
105
  with st.spinner(f"Getting AI decision for {echelon_name}..."):
106
  try:
107
+ temp = 0.1 if 'perfectly rational' in prompt else 0.7
108
  response = client.chat.completions.create(
109
  model=OPENAI_MODEL,
110
  messages=[
 
122
  st.error(f"API call failed for {echelon_name}: {e}. Defaulting to 4.")
123
  return 4, f"API_ERROR: {e}"
124
 
125
+ # =============== PROMPT FUNCTION (v4 - FIXES FOR OSCILLATION AND HUMAN-LIKE) ===============
126
  def get_llm_prompt(echelon_state_decision_point: dict, week: int, llm_personality: str, info_sharing: str, all_echelons_state_decision_point: dict) -> str:
127
  # This function's logic is updated for "human_like" to follow a flawed Sterman heuristic.
128
  e_state = echelon_state_decision_point
129
  base_info = f"Your Current Status at the **{e_state['name']}** for **Week {week}** (Before Shipping):\n- On-hand inventory: {e_state['inventory']} units.\n- Backlog (total unfilled orders): {e_state['backlog']} units.\n- Incoming order this week (just received): {e_state['incoming_order']} units.\n"
130
 
 
131
  current_stable_demand = get_customer_demand(week) # Use current week's demand
132
 
133
  if e_state['name'] == 'Factory':
 
140
  # --- PERFECT RATIONAL (NORMATIVE) PROMPTS ---
141
 
142
  if llm_personality == 'perfect_rational' and info_sharing == 'full':
143
+ stable_demand = current_stable_demand
144
+
145
+ # 1. CALCULATE CORRECT LEAD TIME (UNCHANGED)
146
+ if e_state['name'] == 'Factory':
147
+ total_lead_time = FACTORY_LEAD_TIME # 1
148
+ elif e_state['name'] == 'Distributor':
149
+ total_lead_time = ORDER_PASSING_DELAY + FACTORY_LEAD_TIME + FACTORY_SHIPPING_DELAY # 1+1+1 = 3
150
+ else:
151
+ total_lead_time = ORDER_PASSING_DELAY + SHIPPING_DELAY # 1+2 = 3
152
+
153
  safety_stock = 4
154
  target_inventory_level = (stable_demand * total_lead_time) + safety_stock
155
+
156
+ # 2. OSCILLATION FIX: Calculate CORRECT Inventory Position
157
+ order_in_transit_to_supplier = st.session_state.game_state['last_week_orders'].get(e_state['name'], 0) # Order Delay (1 week)
158
+
159
  if e_state['name'] == 'Factory':
160
+ # Factory pipeline: In Production (1 week)
161
+ supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
162
+ inventory_position = (e_state['inventory'] - e_state['backlog'] + supply_line)
163
+ inv_pos_components = f"(Inv={e_state['inventory']} - Backlog={e_state['backlog']} + InProd={supply_line})"
164
+
165
+ elif e_state['name'] == 'Distributor':
166
+ # Distributor pipeline: In Shipping (1 week) + In Production (1 week) + Order Delay (1 week)
167
+ in_shipping = sum(e_state['incoming_shipments'])
168
+ in_production = sum(st.session_state.game_state['factory_production_pipeline'])
169
+ supply_line = in_shipping + in_production + order_in_transit_to_supplier
170
+ inventory_position = (e_state['inventory'] - e_state['backlog'] + supply_line)
171
+ inv_pos_components = f"(Inv={e_state['inventory']} - Backlog={e_state['backlog']} + InTransitShip={in_shipping} + InProd={in_production} + OrderToSupplier={order_in_transit_to_supplier})"
172
+
173
+ else: # Retailer and Wholesaler
174
+ # R/W pipeline: In Shipping (2 weeks) + Order Delay (1 week)
175
+ in_shipping = sum(e_state['incoming_shipments'])
176
+ supply_line = in_shipping + order_in_transit_to_supplier
177
+ inventory_position = (e_state['inventory'] - e_state['backlog'] + supply_line)
178
+ inv_pos_components = f"(Inv={e_state['inventory']} - Backlog={e_state['backlog']} + InTransitShip={in_shipping} + OrderToSupplier={order_in_transit_to_supplier})"
179
+
180
  optimal_order = max(0, int(target_inventory_level - inventory_position))
181
  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."
182
 
183
  elif llm_personality == 'perfect_rational' and info_sharing == 'local':
184
+ safety_stock = 4
185
+ anchor_demand = e_state['incoming_order']
186
  inventory_correction = safety_stock - (e_state['inventory'] - e_state['backlog'])
187
+
188
+ # 2. OSCILLATION FIX: Calculate CORRECT *Local* Supply Line
189
  if e_state['name'] == 'Factory':
190
+ # Factory can see its full (local) pipeline
191
  supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
192
  supply_line_desc = "In Production"
193
+
194
+ elif e_state['name'] == 'Distributor':
195
+ # Distributor can *only* see its shipping queue (1 week)
196
+ # It CANNOT see the factory pipeline or its own order delay
197
+ # This is a weak heuristic, but it's *locally* correct and won't oscillate.
198
+ supply_line = sum(e_state['incoming_shipments'])
199
+ supply_line_desc = "Supply Line (In Transit Shipments)"
200
+
201
+ else: # Retailer and Wholesaler
202
+ # R/W can see their full (local) pipeline: Shipping (2 weeks)
203
+ supply_line = sum(e_state['incoming_shipments'])
204
+ supply_line_desc = "Supply Line (In Transit Shipments)"
205
+
206
  calculated_order = anchor_demand + inventory_correction - supply_line
207
  rational_local_order = max(0, int(calculated_order))
208
+
209
+ 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 (before shipping) is {e_state['inventory'] - e_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 (that you can see). These should be subtracted from your new decision.\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."
210
 
211
  # --- HUMAN-LIKE (DESCRIPTIVE) PROMPTS ---
212
 
213
+ else:
 
 
214
  DESIRED_INVENTORY = 12 # Matches initial inventory
 
215
  net_inventory = e_state['inventory'] - e_state['backlog']
216
  stock_correction = DESIRED_INVENTORY - net_inventory
 
 
217
 
218
  # Get supply line info *just to show* the AI it's being ignored
219
  if e_state['name'] == 'Factory':
220
  supply_line = sum(st.session_state.game_state['factory_production_pipeline'])
221
  supply_line_desc = "In Production"
222
  else:
223
+ # This is just for display, not calculation
224
  order_in_transit_to_supplier = st.session_state.game_state['last_week_orders'].get(e_state['name'], 0)
225
+ supply_line = sum(e_state['incoming_shipments']) + order_in_transit_to_supplier
226
  supply_line_desc = "Supply Line"
227
 
228
  if info_sharing == 'local':
229
+ # 1. HUMAN-LIKE / LOCAL (Unchanged): Anchors on *local* incoming order
230
+ anchor_demand = e_state['incoming_order']
231
+ panicky_order = max(0, int(anchor_demand + stock_correction))
232
+ panicky_order_calc = f"{anchor_demand} (Your Incoming Order) + {stock_correction} (Your Stock Correction)"
233
+
234
  return f"""
235
  **You are a reactive supply chain manager for the {e_state['name']}.** You have a limited (local) view.
236
  You tend to make **reactive, 'gut-instinct' decisions** (like the classic Sterman 1989 model) that cause the Bullwhip Effect.
 
253
  """
254
 
255
  elif info_sharing == 'full':
256
+ # 1. HUMAN-LIKE / FULL (FIXED): Anchors on *global* customer demand
257
+ anchor_demand = current_stable_demand
258
+ panicky_order = max(0, int(anchor_demand + stock_correction))
259
+ panicky_order_calc = f"{anchor_demand} (End-Customer Demand) + {stock_correction} (Your Stock Correction)"
260
+
261
  # Build the "Full Info" string just for context
262
  full_info_str = f"\n**Full Supply Chain Information (State Before Shipping):**\n- End-Customer Demand this week: {current_stable_demand} units.\n"
263
  for name, other_e_state in all_echelons_state_decision_point.items():
 
271
  **A "Human-like" Flawed Decision:**
272
  Even though you have full information, you are judged by *your own* performance (your inventory, your backlog).
273
  You tend to react to your *local* situation (like the classic Sterman 1989 model) instead of using the complex full-system data.
274
+ A 'rational' player would use a complex formula, but your gut-instinct is to panic about *your* numbers.
275
 
276
  **Your 'Panic' Calculation (Ignoring Full Info and Your Supply Line):**
277
+ 1. **Anchor on *End-Customer* Demand:** You can see the real demand is **{anchor_demand}** units. You'll use that as your base.
278
  2. **Correct for *Your* Stock:** Your desired 'safe' inventory is {DESIRED_INVENTORY}. Your current net inventory is {net_inventory}. You need to order **{stock_correction}** more units.
279
  3. **Ignore *Your* Supply Line:** You'll ignore the **{supply_line} units** in your own pipeline ({supply_line_desc}).
280
 
281
+ **Final Panic Order:** (End-Customer Demand) + (Your Stock Correction)
282
  * Order = {panicky_order_calc} = **{panicky_order} units**.
283
 
284
  **Your Task:** Confirm this 'gut-instinct', locally-focused {task_word}. Respond with a single integer.