| 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")) |
| """ |
|
|
| |
|
|
| 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 |
|
|
| |
|
|
| def _parse_result(self, payload: dict) -> StepResult: |
| obs_data = payload.get("observation", {}) |
|
|
| |
| |
| 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( |
| |
| done=payload.get("done", False), |
| reward=_reward, |
|
|
| |
| phase=obs_data.get("phase", "market"), |
|
|
| |
| 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), |
|
|
| |
| 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), |
|
|
| |
| 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", {}), |
|
|
| |
| 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), |
|
|
| |
| task_id=obs_data.get("task_id", "profit_negotiator"), |
| weights=obs_data.get("weights", []), |
| cumulative_reward=_cum, |
|
|
| |
| message=obs_data.get("message", ""), |
| ) |
|
|
| return StepResult( |
| observation=observation, |
| reward=_reward, |
| done=payload.get("done", False), |
| ) |
|
|
| |
|
|
| 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), |
| ) |