uploading done
Browse files- .gitignore +0 -0
- Dockerfile +13 -0
- README.md +78 -11
- __pycache__/server.cpython-313.pyc +0 -0
- app/__pycache__/env.cpython-313.pyc +0 -0
- app/__pycache__/models.cpython-313.pyc +0 -0
- app/__pycache__/tasks.cpython-313.pyc +0 -0
- app/env.py +262 -0
- app/grader.py +37 -0
- app/models.py +55 -0
- app/tasks.py +17 -0
- inference.py +141 -0
- openenv.yaml +46 -0
- out.txt +0 -0
- requirements.txt +6 -0
- server.py +47 -0
.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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|