TanmaySK commited on
Commit
b96c7d5
·
verified ·
1 Parent(s): ba4d381

uploading done

Browse files
.gitignore ADDED
File without changes
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /workspace
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Expose the API port
11
+ EXPOSE 7860
12
+
13
+ CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,78 @@
1
- ---
2
- title: CrisisSim
3
- emoji: 💻
4
- colorFrom: gray
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- short_description: 'AI-powered financial crisis simulator where agents learn to '
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CrisisSim: AI Crisis Economy Simulator
2
+
3
+ ## Problem Description
4
+ CrisisSim provides a real-world reinforcement learning environment where an AI agent must survive financial crises. The simulator bridges multi-layer economic structures involving world events, overall market inflation, and personal finances. The goal is to develop adaptive strategies for extreme crises such as wars, inflation surges, supply shortages, and currency instability.
5
+
6
+ ## Environment Design
7
+ The simulation triggers global events such as `war_outbreak` or `oil_supply_shock`, which realistically cascade down to the market level (increasing fuel and food prices, triggering inflation), and ultimately impact personal finances (increasing expenses, eroding currency value).
8
+
9
+ ### Cause-Effect Chain
10
+ Example of the economic cascade implemented:
11
+ `war` `oil shortage` `fuel price ↑` → `transport cost ↑` → `food price ↑` → `inflation ↑`
12
+
13
+ ## Action & Observation Space
14
+
15
+ ### Observation Space
16
+ The state provides a full snapshot of the individual's finances and global economy indices:
17
+ - `income`: Current monthly income
18
+ - `expenses`: Personal monthly expenses
19
+ - `savings`: Liquid emergency/savings amount
20
+ - `debt`: Total outstanding debt
21
+ - `inflation`: Current inflation rate
22
+ - `currency_value`: Purchasing power of local currency
23
+ - `food_price_index`: Dynamic index affecting basic expenses
24
+ - `fuel_price`: Market per-unit fuel cost
25
+ - `current_event`: Disabling/Enabling event currently active
26
+ - `month`: Current step/month ticker
27
+ - `bankrupt`: Boolean status of agent's bankruptcy
28
+
29
+ ### Action Space
30
+ Discrete actions representing personal finance choices:
31
+ 1. `cut_expenses`: Reduces monthly expenses.
32
+ 2. `stock_essentials`: Buy food/assets ahead of price surges.
33
+ 3. `invest_gold`: Move savings to stable assets.
34
+ 4. `hold_cash`: Maintains liquidity.
35
+ 5. `convert_currency`: Switch to a safer foreign currency.
36
+ 6. `take_loan`: Bolsters temporary liquidity with debt.
37
+ 7. `pay_debt`: Use savings to lower outstanding debts.
38
+ 8. `reduce_luxury`: Strongly cut down non-essential spending.
39
+ 9. `build_emergency_fund`: Siphon income to safety buffer.
40
+
41
+ ## Task Descriptions
42
+ The environment is initialized with tasks dictating difficulty:
43
+ - **EASY**: Mild inflation, stable income. Small chance of random shocks. Good for baseline learning.
44
+ - **MEDIUM**: Supply shock scenario where food and fuel become expensive gradually.
45
+ - **HARD**: War-time crisis featuring high likelihood of inflation spikes, massive job losses, and currency crashes.
46
+
47
+ ## Setup Instructions
48
+
49
+ ### Environment Setup
50
+ 1. Clone the repository and navigate to the root directory `CrisisSim`.
51
+ 2. Install standard dependencies from `requirements.txt`, or run via Docker.
52
+ ```bash
53
+ pip install -r requirements.txt
54
+ ```
55
+ 3. Run the FastAPI Application locally on port `7860`:
56
+ ```bash
57
+ python server.py
58
+ ```
59
+
60
+ ### Docker Setup
61
+ To deploy on any standard Docker-containerized host (including Hugging Face Spaces):
62
+ ```bash
63
+ docker build -t crisissim .
64
+ docker run -p 7860:7860 crisissim
65
+ ```
66
+
67
+ ## Baseline Results
68
+ An LLM baseline can be tested using the inference script, which requires the OpenAI Python Client standard environment variables:
69
+ ```bash
70
+ export API_BASE_URL="https://api.openai.com/v1" # Or your vLLM / custom endpoint
71
+ export MODEL_NAME="gpt-4o-mini"
72
+ export HF_TOKEN="your_api_key_here"
73
+ export OpenAI_API_KEY= "your_api_key_here"
74
+
75
+ python inference.py easy
76
+ ```
77
+ **Baseline Expectations**:
78
+ Random baselines heavily struggle to not go bankrupt within `max_months=12`, especially on `hard`. A good LLM prompt zero-shot agent typically survives `medium` with > 80% accuracy, but still occasionally struggles on `hard` due to black swan events unless properly tuned.
__pycache__/server.cpython-313.pyc ADDED
Binary file (2.46 kB). View file
 
app/__pycache__/env.cpython-313.pyc ADDED
Binary file (10.9 kB). View file
 
app/__pycache__/models.cpython-313.pyc ADDED
Binary file (2.68 kB). View file
 
app/__pycache__/tasks.cpython-313.pyc ADDED
Binary file (525 Bytes). View file
 
app/env.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import math
3
+ import math
4
+ from copy import deepcopy
5
+ from app.models import ActionEnum, EventEnum, Observation, Reward
6
+
7
+ class CrisisSimEnv:
8
+ def __init__(self, config: Dict[str, Any]):
9
+ self.config = config
10
+ self.max_months = config.get("max_months", 12)
11
+ self.task_difficulty = config.get("difficulty", "medium")
12
+ self.reset()
13
+
14
+ def reset(self) -> Observation:
15
+ self.month = 0
16
+
17
+ # Initial states based on difficulty
18
+ self.income = 3000.0
19
+ self.expenses = 2000.0
20
+ self.savings = 5000.0
21
+ self.debt = 1000.0
22
+ self.inflation = 0.02
23
+ self.currency_value = 1.0 # Base 1.0
24
+ self.food_price_index = 100.0
25
+ self.fuel_price = 3.0 # per unit
26
+ self.current_event = EventEnum.none
27
+ self.bankrupt = False
28
+
29
+ if self.task_difficulty == "easy":
30
+ self.savings = 8000.0
31
+ self.debt = 500.0
32
+ elif self.task_difficulty == "medium":
33
+ self.inflation = 0.05
34
+ elif self.task_difficulty == "hard":
35
+ self.inflation = 0.08
36
+ self.savings = 2000.0
37
+ self.debt = 3000.0
38
+
39
+ # Metrics for reward calculation
40
+ self.initial_savings = self.savings
41
+ self.initial_debt = self.debt
42
+ self.smart_decisions = 0
43
+ self.bad_decisions = 0
44
+ self.consecutive_negative_months = 0
45
+
46
+ return self.state()
47
+
48
+ def state(self) -> Observation:
49
+ return Observation(
50
+ income=self.income,
51
+ expenses=self.expenses,
52
+ savings=self.savings,
53
+ debt=self.debt,
54
+ inflation=self.inflation,
55
+ currency_value=self.currency_value,
56
+ food_price_index=self.food_price_index,
57
+ fuel_price=self.fuel_price,
58
+ current_event=self.current_event.value,
59
+ month=self.month,
60
+ bankrupt=self.bankrupt
61
+ )
62
+
63
+ def _apply_action(self, action: ActionEnum):
64
+ # Default action behavior
65
+ if action == ActionEnum.cut_expenses:
66
+ self.expenses = max(1000.0, self.expenses - 300.0)
67
+ self.smart_decisions += 1
68
+ elif action == ActionEnum.stock_essentials:
69
+ self.savings -= 500.0
70
+ self.expenses += 100.0 # higher maintenance
71
+ # Provides buffer against food price index
72
+ self.smart_decisions += 1
73
+ elif action == ActionEnum.invest_gold:
74
+ self.savings -= 1000.0
75
+ self.smart_decisions += 1
76
+ elif action == ActionEnum.hold_cash:
77
+ pass # No direct change, safe but vulnerable to inflation
78
+ elif action == ActionEnum.convert_currency:
79
+ self.savings -= 50.0 # fee
80
+ self.smart_decisions += 1
81
+ elif action == ActionEnum.take_loan:
82
+ self.savings += 2000.0
83
+ self.debt += 2200.0 # interest
84
+ self.bad_decisions += 1
85
+ elif action == ActionEnum.pay_debt:
86
+ amount = min(self.savings, self.debt)
87
+ self.savings -= amount
88
+ self.debt -= amount
89
+ if amount > 0:
90
+ self.smart_decisions += 1
91
+ elif action == ActionEnum.reduce_luxury:
92
+ self.expenses -= 500.0
93
+ self.smart_decisions += 1
94
+ elif action == ActionEnum.build_emergency_fund:
95
+ self.savings += 500.0
96
+ self.expenses += 500.0 # moving from income stream effectively, abstract logic: increase expenses, keep savings higher
97
+ self.smart_decisions += 1
98
+
99
+ def _trigger_event(self):
100
+ # Event probabilities based on difficulty
101
+ events = [EventEnum.none]
102
+ weights = [1.0]
103
+
104
+ if self.task_difficulty == "easy":
105
+ events.extend([EventEnum.job_loss, EventEnum.currency_crash])
106
+ weights.extend([0.05, 0.05])
107
+ elif self.task_difficulty == "medium":
108
+ events.extend([EventEnum.oil_supply_shock, EventEnum.food_shortage])
109
+ weights = [0.4, 0.3, 0.3]
110
+ elif self.task_difficulty == "hard":
111
+ events.extend([EventEnum.war_outbreak, EventEnum.job_loss, EventEnum.currency_crash, EventEnum.import_ban])
112
+ weights = [0.1, 0.3, 0.2, 0.2, 0.2]
113
+
114
+ self.current_event = random.choices(events, weights=weights, k=1)[0]
115
+
116
+ def _apply_event(self):
117
+ # Soften severity for easy task
118
+ severity_mult = 0.5 if self.task_difficulty == "easy" else 1.0
119
+
120
+ if self.current_event == EventEnum.war_outbreak:
121
+ self.fuel_price *= (1.0 + 0.15 * severity_mult)
122
+ self.inflation += (0.02 * severity_mult)
123
+ elif self.current_event == EventEnum.oil_supply_shock:
124
+ self.fuel_price *= (1.0 + 0.15 * severity_mult)
125
+ elif self.current_event == EventEnum.currency_crash:
126
+ self.currency_value *= (1.0 - 0.15 * severity_mult)
127
+ self.inflation += (0.02 * severity_mult)
128
+ elif self.current_event == EventEnum.food_shortage:
129
+ self.food_price_index *= (1.0 + 0.20 * severity_mult)
130
+ elif self.current_event == EventEnum.job_loss:
131
+ self.income *= (1.0 - 0.70 * severity_mult) # 30% retention normally, 65% on easy
132
+ elif self.current_event == EventEnum.import_ban:
133
+ self.food_price_index *= (1.0 + 0.10 * severity_mult)
134
+ self.inflation += (0.01 * severity_mult)
135
+
136
+ def _update_economy(self):
137
+ # Cause-effect propagation
138
+ # Fuel price increases transport costs, making food more expensive
139
+ if self.fuel_price > 3.5:
140
+ self.food_price_index += (self.fuel_price - 3.5) * 5.0
141
+
142
+ # Food price -> inflation
143
+ if self.food_price_index > 120.0:
144
+ self.inflation += 0.01 * (self.food_price_index / 100.0)
145
+
146
+ # Apply inflation to expenses
147
+ self.expenses *= (1.0 + self.inflation)
148
+
149
+ # Debts accrue interest (e.g., 5% per month minimum)
150
+ self.debt *= 1.05
151
+
152
+ # Update savings
153
+ self.savings = self.savings + self.income - self.expenses
154
+
155
+ def _check_bankruptcy(self):
156
+ # Determine strictness
157
+ if self.task_difficulty == "hard":
158
+ consec_limit = 3
159
+ grace_buffer = 10000.0
160
+ elif self.task_difficulty == "medium":
161
+ consec_limit = 4
162
+ grace_buffer = 15000.0
163
+ else: # easy
164
+ consec_limit = 6
165
+ grace_buffer = 20000.0
166
+
167
+ # Register bad months vs recovery
168
+ if self.savings < -grace_buffer:
169
+ self.consecutive_negative_months += 1
170
+ else:
171
+ self.consecutive_negative_months = max(0, self.consecutive_negative_months - 1)
172
+
173
+ if self.consecutive_negative_months >= consec_limit:
174
+ self.bankrupt = True
175
+
176
+ def _compute_reward(self) -> float:
177
+ survival_score = 1.0 if not self.bankrupt else 0.0
178
+
179
+ # Soft scaling: asymptotic curves instead of hard caps
180
+ if self.savings > 0:
181
+ savings_ratio = math.tanh(self.savings / max(1.0, self.initial_savings))
182
+ else:
183
+ savings_ratio = -math.tanh(abs(self.savings) / 5000.0)
184
+
185
+ if self.initial_debt > 0:
186
+ debt_ratio = 1.0 - math.tanh(self.debt / max(1.0, self.initial_debt))
187
+ else:
188
+ debt_ratio = math.exp(-self.debt / 2000.0)
189
+
190
+ # State change dynamics (monthly deltas)
191
+ prev_savings = getattr(self, "previous_savings", self.savings)
192
+ prev_debt = getattr(self, "previous_debt", self.debt)
193
+ prev_inflation = getattr(self, "previous_inflation", self.inflation)
194
+
195
+ savings_delta = (self.savings - prev_savings) / 1000.0
196
+ debt_delta = (prev_debt - self.debt) / 1000.0
197
+ inflation_delta = (prev_inflation - self.inflation) * 20.0
198
+
199
+ # Small dynamic variation based on monthly momentum (+/- 0.05)
200
+ momentum_bonus = math.tanh(savings_delta + debt_delta + inflation_delta) * 0.05
201
+
202
+ smart_bonus_ratio = math.tanh(self.smart_decisions / 5.0)
203
+ bad_penalty_ratio = math.tanh(self.bad_decisions / 5.0)
204
+
205
+ # Gradual penalty over steps instead of instant max punishment
206
+ bad_state_penalty = self.consecutive_negative_months * 0.05
207
+ bankruptcy_penalty = 0.1 if self.bankrupt else 0.0
208
+
209
+ reward = (
210
+ survival_score * 0.30 +
211
+ savings_ratio * 0.15 +
212
+ debt_ratio * 0.15 +
213
+ smart_bonus_ratio * 0.15 -
214
+ bad_penalty_ratio * 0.15 -
215
+ bad_state_penalty -
216
+ bankruptcy_penalty +
217
+ momentum_bonus
218
+ )
219
+
220
+ # Add soft-scaled survival bonus per step
221
+ reward += math.tanh(self.month / 12.0) * 0.10
222
+
223
+ # Normalize reward Incrementally to [0,1] with a floor to prevent instant 0.0 drops
224
+ min_reward_floor = 0.15
225
+
226
+ # Soft clamp near the top to prevent flatlining at 1.00 or 0.90
227
+ if reward > 0.90:
228
+ reward = 0.90 + 0.10 * math.tanh((reward - 0.90) * 5.0)
229
+
230
+ normalized_reward = max(min_reward_floor, min(1.0, reward))
231
+ return normalized_reward
232
+
233
+ def step(self, action: ActionEnum) -> Tuple[Observation, float, bool, Dict[str, Any]]:
234
+ self.month += 1
235
+
236
+ # 1. Apply agent action
237
+ self._apply_action(action)
238
+
239
+ # 2. Random events
240
+ self._trigger_event()
241
+
242
+ # 3. Apply event impact
243
+ self._apply_event()
244
+
245
+ # 4. Update inflation and prices, savings
246
+ self._update_economy()
247
+
248
+ # 5. Check bankruptcy condition
249
+ self._check_bankruptcy()
250
+
251
+ # 6. Compute reward
252
+ reward = self._compute_reward()
253
+
254
+ # Store state variables to compare deltas next month
255
+ self.previous_savings = self.savings
256
+ self.previous_debt = self.debt
257
+ self.previous_inflation = self.inflation
258
+
259
+ # Check termination
260
+ done = self.month >= self.max_months or self.bankrupt
261
+
262
+ return self.state(), reward, done, {"bankrupt": self.bankrupt, "month": self.month}
app/grader.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any
2
+ from app.models import Observation
3
+
4
+ def grade(observations: List[Dict[str, Any]], actions: List[str]) -> float:
5
+ if not observations:
6
+ return 0.0
7
+
8
+ final_obs = observations[-1]
9
+ initial_obs = observations[0]
10
+
11
+ score = 0.0
12
+
13
+ # Check survival (25%)
14
+ survived = not final_obs.get("bankrupt", True)
15
+ if survived:
16
+ score += 0.25
17
+
18
+ # Check bankruptcy (25%)
19
+ # redundant with survival? the spec separated them:
20
+ # "survival till end", "no bankruptcy"
21
+ # let's map survival = max_months reached, no bankruptcy = not bankrupt
22
+ if not final_obs.get("bankrupt", True):
23
+ score += 0.25
24
+
25
+ # Savings > threshold (25%)
26
+ # Threshold could be the initial savings or a fixed amount like 1000
27
+ if final_obs.get("savings", 0) > 1000.0:
28
+ score += 0.25
29
+
30
+ # Debt reduced (25%)
31
+ initial_debt = initial_obs.get("debt", 0)
32
+ final_debt = final_obs.get("debt", 0)
33
+
34
+ if final_debt < initial_debt or final_debt == 0:
35
+ score += 0.25
36
+
37
+ return max(0.0, min(1.0, score))
app/models.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, ConfigDict
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ class ActionEnum(str, Enum):
6
+ cut_expenses = "cut_expenses"
7
+ stock_essentials = "stock_essentials"
8
+ invest_gold = "invest_gold"
9
+ hold_cash = "hold_cash"
10
+ convert_currency = "convert_currency"
11
+ take_loan = "take_loan"
12
+ pay_debt = "pay_debt"
13
+ reduce_luxury = "reduce_luxury"
14
+ build_emergency_fund = "build_emergency_fund"
15
+
16
+ class EventEnum(str, Enum):
17
+ none = "none"
18
+ war_outbreak = "war_outbreak"
19
+ oil_supply_shock = "oil_supply_shock"
20
+ currency_crash = "currency_crash"
21
+ food_shortage = "food_shortage"
22
+ job_loss = "job_loss"
23
+ import_ban = "import_ban"
24
+
25
+ class Action(BaseModel):
26
+ action: ActionEnum
27
+
28
+ class Observation(BaseModel):
29
+ model_config = ConfigDict(extra="forbid")
30
+ income: float
31
+ expenses: float
32
+ savings: float
33
+ debt: float
34
+ inflation: float
35
+ currency_value: float
36
+ food_price_index: float
37
+ fuel_price: float
38
+ current_event: str
39
+ month: int
40
+ bankrupt: bool
41
+
42
+ class Reward(BaseModel):
43
+ survival_score: float
44
+ savings_stability: float
45
+ debt_reduction: float
46
+ smart_decision_bonus: float
47
+ bad_decision_penalty: float
48
+ bankruptcy_penalty: float
49
+ total: float
50
+
51
+ class StepResponse(BaseModel):
52
+ observation: Observation
53
+ reward: float
54
+ done: bool
55
+ info: dict
app/tasks.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TASKS = {
2
+ "easy": {
3
+ "difficulty": "easy",
4
+ "max_months": 12,
5
+ "description": "Mild inflation, stable income. Focus on learning basic financial survival."
6
+ },
7
+ "medium": {
8
+ "difficulty": "medium",
9
+ "max_months": 12,
10
+ "description": "Supply shock scenario. Food and fuel become expensive."
11
+ },
12
+ "hard": {
13
+ "difficulty": "hard",
14
+ "max_months": 12,
15
+ "description": "War-time crisis. High risk of inflation spike, job loss, and extreme shortages."
16
+ }
17
+ }
inference.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import sys
3
+ import os
4
+ import json
5
+ from dotenv import load_dotenv
6
+ from openai import OpenAI
7
+
8
+ load_dotenv()
9
+
10
+ BASE_URL = "http://localhost:7860"
11
+
12
+ def run_inference(task_name: str):
13
+ env_name = "CrisisSim"
14
+
15
+ API_BASE_URL = os.getenv("API_BASE_URL") or "https://api.openai.com/v1"
16
+ MODEL_NAME = os.getenv("MODEL_NAME") or "gpt-4o-mini"
17
+ API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY")
18
+
19
+ if not API_KEY:
20
+ print(f"[START] task={task_name} env={env_name} model={MODEL_NAME}", flush=True)
21
+ print("[STEP] step=1 action=null reward=0.00 done=true error=API_KEY or HF_TOKEN environment variable is not set", flush=True)
22
+ print("[END] success=false steps=0 rewards=", flush=True)
23
+ sys.exit(1)
24
+
25
+ print(f"[START] task={task_name} env={env_name} model={MODEL_NAME}", flush=True)
26
+
27
+ try:
28
+ client = OpenAI(
29
+ base_url=API_BASE_URL,
30
+ api_key=API_KEY
31
+ )
32
+ except Exception as e:
33
+ print(f"[STEP] step=1 action=null reward=0.00 done=true error=api_error", flush=True)
34
+ print("[END] success=false steps=0 rewards=", flush=True)
35
+ sys.exit(1)
36
+
37
+ steps = 0
38
+ rewards = []
39
+ last_actions = []
40
+
41
+ VALID_ACTIONS = [
42
+ "cut_expenses", "stock_essentials", "invest_gold", "hold_cash",
43
+ "convert_currency", "take_loan", "pay_debt", "reduce_luxury", "build_emergency_fund"
44
+ ]
45
+
46
+ SYSTEM_PROMPT = """You are an expert financial crisis manager operating in a simulated economy.
47
+
48
+ Your goals:
49
+ - Survive as long as possible (avoid bankruptcy)
50
+ - Maintain and grow savings
51
+ - Reduce debt strategically
52
+ - Adapt to inflation and economic shocks
53
+
54
+ Rules:
55
+ - Do NOT repeat the same action more than 2 times in a row
56
+ - Avoid passive strategies like always holding cash
57
+ - Balance short-term survival with long-term stability
58
+ - Consider consequences of each decision
59
+
60
+ Always return ONLY one valid action."""
61
+
62
+ try:
63
+ # Reset Environment
64
+ res = requests.post(f"{BASE_URL}/reset", json={"task_name": task_name})
65
+ res.raise_for_status()
66
+ state = res.json()
67
+
68
+ while True:
69
+ steps += 1
70
+ error_val = "null"
71
+
72
+ user_prompt = f"""Current State:
73
+ {state}
74
+
75
+ Previous Actions (last 3):
76
+ {last_actions[-3:]}
77
+
78
+ Choose ONE action from:
79
+ [cut_expenses, stock_essentials, invest_gold, hold_cash, convert_currency, take_loan, pay_debt, reduce_luxury, build_emergency_fund]
80
+
81
+ Avoid repeating same action too often.
82
+
83
+ Return ONLY the action name."""
84
+
85
+ # Prompt the model
86
+ try:
87
+ completion = client.chat.completions.create(
88
+ model=MODEL_NAME,
89
+ messages=[
90
+ {"role": "system", "content": SYSTEM_PROMPT.strip()},
91
+ {"role": "user", "content": user_prompt.strip()}
92
+ ],
93
+ temperature=0.5,
94
+ max_tokens=50
95
+ )
96
+ action = completion.choices[0].message.content.strip()
97
+
98
+ # fallback if invalid
99
+ if action not in VALID_ACTIONS:
100
+ action = "hold_cash"
101
+
102
+ # prevent repetition >2
103
+ if len(last_actions) >= 2 and last_actions[-1] == last_actions[-2] == action:
104
+ alternatives = [a for a in VALID_ACTIONS if a != action]
105
+ action = alternatives[0]
106
+
107
+ except Exception as e:
108
+ # SAFE FALLBACK
109
+ action = "hold_cash"
110
+ error_val = "api_error"
111
+
112
+ last_actions.append(action)
113
+
114
+ # Step the environment
115
+ step_res = requests.post(f"{BASE_URL}/step", json={"action": action})
116
+ step_res.raise_for_status()
117
+
118
+ data = step_res.json()
119
+ state = data["observation"]
120
+ reward = data["reward"]
121
+ done = data["done"]
122
+ rewards.append(reward)
123
+
124
+ print(f"[STEP] step={steps} action={action} reward={reward:.2f} done={str(done).lower()} error={error_val}", flush=True)
125
+
126
+ if done:
127
+ success = not state.get("bankrupt", True)
128
+ break
129
+
130
+ rewards_str = ",".join(f"{r:.2f}" for r in rewards)
131
+ print(f"[END] success={str(success).lower()} steps={steps} rewards={rewards_str}", flush=True)
132
+
133
+ except Exception as e:
134
+ error_val = "api_error"
135
+ print(f"[STEP] step={steps} action=null reward=0.00 done=true error={error_val}", flush=True)
136
+ rewards_str = ",".join(f"{r:.2f}" for r in rewards)
137
+ print(f"[END] success=false steps={steps} rewards={rewards_str}", flush=True)
138
+
139
+ if __name__ == "__main__":
140
+ task = sys.argv[1] if len(sys.argv) > 1 else "easy"
141
+ run_inference(task)
openenv.yaml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "CrisisSim"
2
+ version: "1.0.0"
3
+
4
+ description: "AI Crisis Economy Financial Survival Simulator"
5
+
6
+ authors:
7
+ - name: "Submission Team"
8
+
9
+ entry_point: "app.env:CrisisSimEnv"
10
+
11
+ tasks:
12
+ - name: "easy"
13
+ description: "Mild inflation, stable income. Small chance of random shocks."
14
+ - name: "medium"
15
+ description: "Supply shock scenario with rising food and fuel prices."
16
+ - name: "hard"
17
+ description: "War-time crisis with inflation spikes, job loss, and currency instability."
18
+
19
+ observation_space:
20
+ type: "object"
21
+ properties:
22
+ income: float
23
+ expenses: float
24
+ savings: float
25
+ debt: float
26
+ inflation: float
27
+ currency_value: float
28
+ food_price_index: float
29
+ fuel_price: float
30
+ event: string
31
+ month: integer
32
+
33
+ action_space:
34
+ type: "discrete"
35
+ actions:
36
+ - cut_expenses
37
+ - stock_essentials
38
+ - invest_gold
39
+ - hold_cash
40
+ - convert_currency
41
+ - take_loan
42
+ - pay_debt
43
+ - reduce_luxury
44
+ - build_emergency_fund
45
+
46
+ reward_range: [0.0, 1.0]
out.txt ADDED
Binary file (478 Bytes). View file
 
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
+ requests
5
+ openai
6
+ python-dotenv
server.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import Optional
4
+ from app.models import Action, Observation, StepResponse
5
+ from app.env import CrisisSimEnv
6
+ from app.tasks import TASKS
7
+
8
+ app = FastAPI(title="CrisisSim")
9
+
10
+ env_instance: Optional[CrisisSimEnv] = None
11
+
12
+ class ResetRequest(BaseModel):
13
+ task_name: str = "easy"
14
+
15
+ @app.post("/reset", response_model=Observation)
16
+ def reset_env(req: ResetRequest):
17
+ global env_instance
18
+ task_config = TASKS.get(req.task_name)
19
+ if not task_config:
20
+ raise HTTPException(status_code=400, detail="Invalid task name")
21
+ env_instance = CrisisSimEnv(task_config)
22
+ return env_instance.state()
23
+
24
+ @app.post("/step", response_model=StepResponse)
25
+ def step_env(action: Action):
26
+ global env_instance
27
+ if not env_instance:
28
+ raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.")
29
+
30
+ obs, reward, done, info = env_instance.step(action.action)
31
+ return StepResponse(
32
+ observation=obs,
33
+ reward=reward,
34
+ done=done,
35
+ info=info
36
+ )
37
+
38
+ @app.get("/state", response_model=Observation)
39
+ def get_state():
40
+ global env_instance
41
+ if not env_instance:
42
+ raise HTTPException(status_code=400, detail="Environment not initialized.")
43
+ return env_instance.state()
44
+
45
+ if __name__ == "__main__":
46
+ import uvicorn
47
+ uvicorn.run("server:app", host="0.0.0.0", port=7860)