File size: 6,875 Bytes
5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 5c6ca01 048f186 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | from typing import Optional, Dict, List
from openenv.core.env_server import Action, Observation, State
# βββββββββββββββββββββββββββββββββββββββββββββ
# PRODUCT CATALOG (shared constant)
# βββββββββββββββββββββββββββββββββββββββββββββ
PRODUCT_CATALOG = {
"ring": {"gold_oz": 1.0, "labor": 200.0, "base_demand": 0.8},
"necklace": {"gold_oz": 2.0, "labor": 300.0, "base_demand": 0.5},
"bracelet": {"gold_oz": 0.5, "labor": 100.0, "base_demand": 0.3},
}
# βββββββββββββββββββββββββββββββββββββββββββββ
# ACTION
# One unified action covers all 3 phases.
# βββββββββββββββββββββββββββββββββββββββββββββ
class JewelryAction(Action):
"""
Phase 1 (market) β market_action ("buy"/"wait") + gold_qty (oz to buy)
Phase 2 (warehouse) β product_choice ("ring"/"necklace"/"bracelet")
Phase 3 (showroom) β message (accept / counter / reject)
Market (optional): when logging a BUY to SQLite / invoice, the agent may send
LLM target + reasoning; when coordinating with the inventory side, it may
update urgency / need-by fields that were also set on reset.
"""
market_action: Optional[str] = None # "buy" or "wait"
gold_qty: Optional[float] = None # How many oz to buy (market phase)
product_choice: Optional[str] = None # "ring" / "necklace" / "bracelet"
message: Optional[str] = None # Showroom negotiation text
target_price_usd: Optional[float] = None
ai_confidence_pct: Optional[float] = None
ai_reasoning: Optional[str] = None
inventory_urgent: Optional[bool] = None
need_gold_grams: Optional[float] = None
buy_deadline_iso: Optional[str] = None
# βββββββββββββββββββββββββββββββββββββββββββββ
# OBSERVATION
# Everything the agent can SEE each step.
# βββββββββββββββββββββββββββββββββββββββββββββ
class JewelryObservation(Observation):
# Base fields: done, reward (inherited)
phase: str # "market" | "warehouse" | "showroom"
cash: float # Agent's current cash ($)
gold_oz: float # Raw gold in inventory (oz)
# Market phase
gold_price: float # Current gold price ($/oz)
gold_grams: float = 0.0 # Raw gold in inventory (grams) β troy-oz * GRAMS_PER_TROY_OZ
gold_price_history: List[float] = [] # Last N prices for trend analysis
market_round: int = 0 # "Wait" count in this episode (for analytics; no cap in real mode)
max_market_rounds: int = 0 # 0 = no forced round limit (real market); >0 = synthetic only
market_mode: str = "real" # "real" | "synthetic"
gold_price_source: str = "" # e.g. yfinance:GC=F
# Inventory <-> market coordination (from reset / optional step updates)
inventory_urgent: bool = False
need_gold_grams: Optional[float] = None
buy_deadline_iso: Optional[str] = None
cannot_wait: bool = False # If urgent, "wait" action is rejected
# Inventory -> Market bounce-back (when warehouse cannot craft due to low gold)
market_reentries: int = 0 # How many times warehouse has sent us back to market
max_market_reentries: int = 2 # Cap on bounce-backs to avoid infinite loops
# Warehouse phase
demand: Dict[str, float] = {} # "True" per-product demand this episode (0-1)
demand_forecast: Dict[str, float] = {} # Noisy / model-facing signal (inventory "prediction" slot)
product_catalog: Dict[str, dict] = {} # Gold/labor costs per product
inventory: Dict[str, int] = {} # Crafted products in stock
# Showroom phase
product_for_sale: Optional[str] = None # Which product is being sold
cost_basis: float = 0.0 # Total cost to make the product
current_offer: Optional[float] = None # Customer's live offer
negotiation_round: int = 0 # Counter-offer rounds so far
# Per-task grading (chosen at reset() from openenv.yaml task_id)
task_id: str = "profit_negotiator"
weights: List[float] = [] # [w_market, w_warehouse, w_showroom], sums to 1.0
cumulative_reward: float = 0.0 # Running sum of per-step rewards in this episode
message: str = "" # Human-readable feedback
# βββββββββββββββββββββββββββββββββββββββββββββ
# STATE
# Full internal state (server-side truth).
# βββββββββββββββββββββββββββββββββββββββββββββ
class JewelryState(State):
# Base: episode_id, step_count (inherited)
cash: float = 1000.0
gold_oz: float = 0.0
gold_price: float = 0.0
gold_price_history: List[float] = []
market_round: int = 0
max_market_rounds: int = 0 # 0 = no cap (real); >0 only in synthetic mode
demand: Dict[str, float] = {}
demand_forecast: Dict[str, float] = {}
inventory: Dict[str, int] = {}
phase: str = "market"
product_for_sale: Optional[str] = None
cost_basis: float = 0.0
negotiation_round: int = 0
current_offer: float = 0.0
base_offer: float = 0.0 # Hidden from agent
lowest_price_seen: float = 0.0 # For r1 scoring
inventory_urgent: bool = False
need_gold_grams: Optional[float] = None
buy_deadline_iso: Optional[str] = None
use_fifo_lots: bool = False # If True, warehouse cost uses per-gram lots in SQLite
gold_price_source: str = ""
market_mode: str = "real"
# Inventory -> Market bounce-back loop
market_reentries: int = 0
max_market_reentries: int = 2
# Per-task grading (selected at reset)
task_id: str = "profit_negotiator"
weights: List[float] = [] # [w_market, w_warehouse, w_showroom]
cumulative_reward: float = 0.0
last_phase_emitted_reward: float = 0.0 # Reward emitted at the most recent step (debug) |