Spaces:
Sleeping
Sleeping
| from openenv.core.env_client import EnvClient | |
| from openenv.core.client_types import StepResult | |
| try: | |
| from .models import JewelryAction, JewelryObservation, JewelryState, PRODUCT_CATALOG | |
| except ImportError: | |
| from models import JewelryAction, JewelryObservation, JewelryState, PRODUCT_CATALOG | |
| class JewelryShopEnv(EnvClient[JewelryAction, JewelryObservation, JewelryState]): | |
| """ | |
| Client for the Jewelry Shop RL environment. | |
| Usage: | |
| env = JewelryShopEnv(base_url="http://localhost:8000") | |
| obs = await env.reset() | |
| # Phase 1 β Market (buy or wait) | |
| obs = await env.step(JewelryAction(market_action="wait")) | |
| obs = await env.step(JewelryAction(market_action="buy", gold_qty=2.0)) | |
| # Phase 2 β Warehouse (choose product) | |
| obs = await env.step(JewelryAction(product_choice="ring")) | |
| # Phase 3 β Showroom (negotiate) | |
| obs = await env.step(JewelryAction(message="How about $600?")) | |
| obs = await env.step(JewelryAction(message="I accept")) | |
| """ | |
| # ββ 1. PACK action β dict (sent TO server) ββββββββββββββββββββββββββββββ | |
| def _step_payload(self, action: JewelryAction) -> dict: | |
| payload = {} | |
| if action.market_action is not None: | |
| payload["market_action"] = action.market_action | |
| if action.gold_qty is not None: | |
| payload["gold_qty"] = action.gold_qty | |
| if action.product_choice is not None: | |
| payload["product_choice"] = action.product_choice | |
| if action.message is not None: | |
| payload["message"] = action.message | |
| if action.target_price_usd is not None: | |
| payload["target_price_usd"] = action.target_price_usd | |
| if action.ai_confidence_pct is not None: | |
| payload["ai_confidence_pct"] = action.ai_confidence_pct | |
| if action.ai_reasoning is not None: | |
| payload["ai_reasoning"] = action.ai_reasoning | |
| if action.inventory_urgent is not None: | |
| payload["inventory_urgent"] = action.inventory_urgent | |
| if action.need_gold_grams is not None: | |
| payload["need_gold_grams"] = action.need_gold_grams | |
| if action.buy_deadline_iso is not None: | |
| payload["buy_deadline_iso"] = action.buy_deadline_iso | |
| return payload | |
| # ββ 2. UNPACK dict β typed observation (received FROM server) βββββββββββ | |
| def _parse_result(self, payload: dict) -> StepResult: | |
| obs_data = payload.get("observation", {}) | |
| # Force reward and cumulative_reward to Python floats. JSON over the wire | |
| # can deliver int (e.g. 0) which would later break formatters / training. | |
| try: | |
| _reward = float(payload.get("reward")) if payload.get("reward") is not None else 0.0 | |
| except (TypeError, ValueError): | |
| _reward = 0.0 | |
| try: | |
| _cum = float(obs_data.get("cumulative_reward", 0.0)) | |
| except (TypeError, ValueError): | |
| _cum = 0.0 | |
| observation = JewelryObservation( | |
| # Base fields | |
| done=payload.get("done", False), | |
| reward=_reward, | |
| # Phase info | |
| phase=obs_data.get("phase", "market"), | |
| # Finances & inventory | |
| cash=obs_data.get("cash", 1000.0), | |
| gold_oz=obs_data.get("gold_oz", 0.0), | |
| gold_grams=obs_data.get("gold_grams", 0.0), | |
| # Market | |
| gold_price=obs_data.get("gold_price", 0.0), | |
| gold_price_history=obs_data.get("gold_price_history", []), | |
| market_round=obs_data.get("market_round", 0), | |
| max_market_rounds=obs_data.get("max_market_rounds", 0), | |
| market_mode=obs_data.get("market_mode", "real"), | |
| gold_price_source=obs_data.get("gold_price_source", ""), | |
| inventory_urgent=obs_data.get("inventory_urgent", False), | |
| need_gold_grams=obs_data.get("need_gold_grams", None), | |
| buy_deadline_iso=obs_data.get("buy_deadline_iso", None), | |
| cannot_wait=obs_data.get("cannot_wait", False), | |
| market_reentries=obs_data.get("market_reentries", 0), | |
| max_market_reentries=obs_data.get("max_market_reentries", 2), | |
| # Warehouse | |
| demand=obs_data.get("demand", {}), | |
| demand_forecast=obs_data.get("demand_forecast", {}), | |
| product_catalog=obs_data.get("product_catalog", PRODUCT_CATALOG), | |
| inventory=obs_data.get("inventory", {}), | |
| # Showroom | |
| product_for_sale=obs_data.get("product_for_sale", None), | |
| cost_basis=obs_data.get("cost_basis", 0.0), | |
| current_offer=obs_data.get("current_offer", None), | |
| negotiation_round=obs_data.get("negotiation_round", 0), | |
| # Per-task grading | |
| task_id=obs_data.get("task_id", "profit_negotiator"), | |
| weights=obs_data.get("weights", []), | |
| cumulative_reward=_cum, | |
| # Feedback | |
| message=obs_data.get("message", ""), | |
| ) | |
| return StepResult( | |
| observation=observation, | |
| reward=_reward, | |
| done=payload.get("done", False), | |
| ) | |
| # ββ 3. UNPACK dict β typed state (server internal state) ββββββββββββββββ | |
| def _parse_state(self, payload: dict) -> JewelryState: | |
| return JewelryState( | |
| episode_id=payload.get("episode_id", None), | |
| step_count=payload.get("step_count", 0), | |
| cash=payload.get("cash", 1000.0), | |
| gold_oz=payload.get("gold_oz", 0.0), | |
| gold_price=payload.get("gold_price", 0.0), | |
| gold_price_history=payload.get("gold_price_history", []), | |
| market_round=payload.get("market_round", 0), | |
| max_market_rounds=payload.get("max_market_rounds", 0), | |
| use_fifo_lots=payload.get("use_fifo_lots", False), | |
| market_mode=payload.get("market_mode", "real"), | |
| gold_price_source=payload.get("gold_price_source", ""), | |
| demand=payload.get("demand", {}), | |
| demand_forecast=payload.get("demand_forecast", {}), | |
| inventory=payload.get("inventory", {}), | |
| phase=payload.get("phase", "market"), | |
| product_for_sale=payload.get("product_for_sale", None), | |
| cost_basis=payload.get("cost_basis", 0.0), | |
| negotiation_round=payload.get("negotiation_round", 0), | |
| current_offer=payload.get("current_offer", 0.0), | |
| base_offer=payload.get("base_offer", 0.0), | |
| lowest_price_seen=payload.get("lowest_price_seen", 0.0), | |
| inventory_urgent=payload.get("inventory_urgent", False), | |
| need_gold_grams=payload.get("need_gold_grams", None), | |
| buy_deadline_iso=payload.get("buy_deadline_iso", None), | |
| market_reentries=payload.get("market_reentries", 0), | |
| max_market_reentries=payload.get("max_market_reentries", 2), | |
| task_id=payload.get("task_id", "profit_negotiator"), | |
| weights=payload.get("weights", []), | |
| cumulative_reward=payload.get("cumulative_reward", 0.0), | |
| last_phase_emitted_reward=payload.get("last_phase_emitted_reward", 0.0), | |
| ) |