# server/engine.py — Shared domain logic for the Supply Chain Simulator # Both main.py (HTTP API) and openenv_wrapper.py (RL API) import from here # to respect clean client/server separation. import random import uuid from typing import Any, List, Dict # ─── CONSTANTS ─────────────────────────────────────────────────────── INITIAL_FUNDS = 100000 SALE_PRICE = 150 STORAGE_COST = 10 CONTAINER_SIZE = 100 SHIPPING_COST = 3000 LATE_PENALTY_RATE = 0.20 # ─── DATA DEFINITIONS ─────────────────────────────────────────────── INITIAL_FACTORIES = [ {"id": "china", "name": "China (Shenzhen)", "baseCost": 50, "lat": 22.5, "lng": 114}, {"id": "india", "name": "India (Mumbai)", "baseCost": 45, "lat": 19, "lng": 72.8}, {"id": "mexico", "name": "Mexico (Monterrey)", "baseCost": 70, "lat": 25.6, "lng": -100.3}, {"id": "vietnam", "name": "Vietnam (Ho Chi Minh)", "baseCost": 42, "lat": 10.8, "lng": 106.6}, {"id": "brazil", "name": "Brazil (São Paulo)", "baseCost": 65, "lat": -23.5, "lng": -46.6}, {"id": "germany", "name": "Germany (Stuttgart)", "baseCost": 90, "lat": 48.8, "lng": 9.2}, ] INITIAL_PORTS = [ {"id": "shanghai", "name": "Port of Shanghai", "baseDelay": 1}, {"id": "singapore", "name": "Port of Singapore", "baseDelay": 1}, {"id": "rotterdam", "name": "Port of Rotterdam", "baseDelay": 2}, {"id": "la", "name": "Port of LA", "baseDelay": 1}, {"id": "busan", "name": "Port of Busan", "baseDelay": 1}, {"id": "dubai", "name": "Port of Dubai (Jebel Ali)", "baseDelay": 2}, {"id": "santos", "name": "Port of Santos", "baseDelay": 2}, ] WAREHOUSES = [ {"id": "usa", "name": "USA (Chicago)"}, {"id": "eu", "name": "EU (Frankfurt)"}, {"id": "asia", "name": "Asia (Tokyo)"}, {"id": "mideast", "name": "Middle East (Dubai)"}, {"id": "latam", "name": "Latin America (São Paulo)"}, {"id": "oceania", "name": "Oceania (Sydney)"}, ] INITIAL_DEMAND = {"usa": 100, "eu": 80, "asia": 120, "mideast": 60, "latam": 70, "oceania": 50} ALL_WAREHOUSE_IDS = [w["id"] for w in WAREHOUSES] # ─── GAME STATE ────────────────────────────────────────────────────── class GameState: turn: int = 1 scenario: str = "normal" balance: float = INITIAL_FUNDS benchmark_balance: float = INITIAL_FUNDS inventory: Dict[str, int] = {} demands: Dict[str, int] = {} factoryCosts: List[Dict[str, Any]] = [] portDelays: List[Dict[str, Any]] = [] inTransit: List[Dict[str, Any]] = [] history: List[Dict[str, Any]] = [] events: List[Dict[str, Any]] = [] alerts: List[Dict[str, Any]] = [] month_shipping_costs: float = 0.0 month_goods_costs: float = 0.0 last_revenue: float = 0.0 def __init__(self) -> None: self.reset() def reset(self): self.turn = 1 self.scenario = "normal" self.balance = INITIAL_FUNDS self.benchmark_balance = INITIAL_FUNDS self.inventory = {w["id"]: 0 for w in WAREHOUSES} self.demands = dict(INITIAL_DEMAND) self.factoryCosts = [{"id": f["id"], "name": f["name"], "baseCost": f["baseCost"], "currentCost": f["baseCost"]} for f in INITIAL_FACTORIES] self.portDelays = [{"id": p["id"], "name": p["name"], "baseDelay": p["baseDelay"], "currentDelay": p["baseDelay"], "isClosed": False} for p in INITIAL_PORTS] self.inTransit = [] self.history = [{"month": 0, "profit": 0, "revenue": 0, "storage_costs": 0, "shipping_costs": 0, "penalties": 0, "balance": INITIAL_FUNDS, "benchmark": INITIAL_FUNDS}] self.events = [] self.alerts = [] self.month_shipping_costs = 0 self.month_goods_costs = 0 self.last_revenue = 0