Aryanshh commited on
Commit
c547cb1
·
1 Parent(s): 262e2fd

fix: Decouple orders from time advance and implement cart cancellation

Browse files
Files changed (3) hide show
  1. dashboard/app.js +15 -17
  2. netzero_nav/env.py +55 -32
  3. netzero_nav/models.py +1 -0
dashboard/app.js CHANGED
@@ -62,24 +62,18 @@ async function updateState() {
62
  document.getElementById('chips-count').textContent = data.inventory.chips;
63
  document.getElementById('sensors-count').textContent = data.inventory.sensors;
64
 
65
- // Update Cart with Grouping & Relative Time
66
  const cartContainer = document.getElementById('cart-container');
67
  if (data.active_shipments && data.active_shipments.length > 0) {
68
- // Group by part and eta
69
- const grouped = data.active_shipments.reduce((acc, ship) => {
70
- const key = `${ship.part}-${ship.eta}`;
71
- if (!acc[key]) {
72
- acc[key] = { ...ship };
73
- } else {
74
- acc[key].quantity += ship.quantity;
75
- }
76
- return acc;
77
- }, {});
78
-
79
- cartContainer.innerHTML = Object.values(grouped).map(ship => `
80
  <div class="cart-item">
81
- <div class="cart-type">${ship.part} (x${ship.quantity})</div>
82
- <div class="cart-meta">Mode: ${ship.mode.toUpperCase()}</div>
 
 
 
 
 
83
  <div class="arrival-day">Arrives in: ${ship.eta} days</div>
84
  </div>
85
  `).join('');
@@ -149,7 +143,7 @@ function log(message, type = 'system') {
149
  }
150
 
151
  // Global Execute Function
152
- window.execute = async function(type, event) {
153
  document.querySelector('main').classList.add('transitioning');
154
 
155
  let actionObj = { action_type: type };
@@ -165,6 +159,9 @@ window.execute = async function(type, event) {
165
  actionObj.offset_amount = 100.0;
166
  } else if (type === 'skip') {
167
  actionObj.action_type = 'skip';
 
 
 
168
  }
169
 
170
  try {
@@ -178,7 +175,8 @@ window.execute = async function(type, event) {
178
  if (result.info && result.info.error) {
179
  log(`ERROR: ${result.info.error}`, 'error');
180
  } else {
181
- log(`SUCCESS: ${type === 'skip' ? 'Skipped Day' : 'Executed ' + type}`, 'action');
 
182
  }
183
 
184
  await updateState();
 
62
  document.getElementById('chips-count').textContent = data.inventory.chips;
63
  document.getElementById('sensors-count').textContent = data.inventory.sensors;
64
 
65
+ // Update Cart
66
  const cartContainer = document.getElementById('cart-container');
67
  if (data.active_shipments && data.active_shipments.length > 0) {
68
+ cartContainer.innerHTML = data.active_shipments.map(ship => `
 
 
 
 
 
 
 
 
 
 
 
69
  <div class="cart-item">
70
+ <div class="cart-type">
71
+ ${ship.part} (x${ship.quantity})
72
+ <button onclick="execute('cancel', event, '${ship.id}')" class="btn btn-ghost small" style="float: right; color: #ff4136;">✕</button>
73
+ </div>
74
+ <div class="cart-meta">Mode: ${ship.mode.toUpperCase()}
75
+ <span style="float:right; color:var(--primary-green); font-weight:600;">$${ship.cost.toFixed(0)}</span>
76
+ </div>
77
  <div class="arrival-day">Arrives in: ${ship.eta} days</div>
78
  </div>
79
  `).join('');
 
143
  }
144
 
145
  // Global Execute Function
146
+ window.execute = async function(type, event, shipment_id = null) {
147
  document.querySelector('main').classList.add('transitioning');
148
 
149
  let actionObj = { action_type: type };
 
159
  actionObj.offset_amount = 100.0;
160
  } else if (type === 'skip') {
161
  actionObj.action_type = 'skip';
162
+ } else if (type === 'cancel') {
163
+ actionObj.action_type = 'cancel';
164
+ actionObj.shipment_id = shipment_id;
165
  }
166
 
167
  try {
 
175
  if (result.info && result.info.error) {
176
  log(`ERROR: ${result.info.error}`, 'error');
177
  } else {
178
+ if (type === 'cancel') log(`SUCCESS: Canceled Shipment`, 'action');
179
+ else log(`SUCCESS: ${type === 'skip' ? 'Skipped Day' : 'Executed ' + type}`, 'action');
180
  }
181
 
182
  await updateState();
netzero_nav/env.py CHANGED
@@ -50,44 +50,45 @@ class AtlasEcoEnv:
50
  )
51
 
52
  def step(self, action: Action) -> Tuple[Observation, float, bool, dict]:
53
- self.step_count += 1
54
  reward = 0.0
55
  news = None
56
-
57
- if self.step_count < self.sea_blocked_until:
58
- news = "EMERGENCY: Suez Canal blocked! Sea routes offline."
59
-
60
  info = {}
61
 
62
- # 1. Process Action
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  if action.action_type == ActionType.ORDER_PARTS:
64
  reward += self._handle_order_parts(action, info)
65
  elif action.action_type == ActionType.PRODUCE:
66
  reward += self._handle_production(action, info)
67
  elif action.action_type == ActionType.OFFSET:
68
  reward += self._handle_offset(action, info)
69
- elif action.action_type == ActionType.REROUTE:
70
- reward += self._handle_reroute(action, info)
71
-
72
- # 2. Advance Shipments
73
- for ship in self.active_shipments:
74
- ship.eta -= 1
75
- if ship.eta <= 0:
76
- self._receive_shipment(ship)
77
-
78
- self.active_shipments = [s for s in self.active_shipments if s.eta > 0]
79
 
80
- # 3. Check Order Deadlines
81
- for order in self.pending_orders:
82
- if self.step_count > order.due_date:
83
- reward -= 50.0 # Late penalty
84
-
85
  # 4. Check Termination
86
  if self.step_count >= 50 or not self.pending_orders:
87
  self.done = True
88
  info["final_score"] = self._calculate_final_score()
89
 
90
- return self._get_obs(), reward, self.done, info
91
 
92
  def _handle_order_parts(self, action: Action, info: dict) -> float:
93
  if not action.part_type or not action.mode or not action.quantity:
@@ -110,18 +111,40 @@ class AtlasEcoEnv:
110
  self.cash_balance -= total_cost
111
  self.carbon_total += carbon * action.quantity
112
 
113
- new_ship = Shipment(
114
- id=f"SHP_{len(self.active_shipments) + 1}",
115
- part=action.part_type,
116
- quantity=action.quantity,
117
- mode=action.mode,
118
- eta=eta,
119
- carbon_impact=carbon * action.quantity,
120
- cost=total_cost
121
- )
122
- self.active_shipments.append(new_ship)
 
 
 
 
 
 
 
 
 
 
123
  return 2.0
124
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  def _receive_shipment(self, ship: Shipment):
126
  current_val = getattr(self.inventory, ship.part.value)
127
  setattr(self.inventory, ship.part.value, current_val + ship.quantity)
 
50
  )
51
 
52
  def step(self, action: Action) -> Tuple[Observation, float, bool, dict]:
 
53
  reward = 0.0
54
  news = None
 
 
 
 
55
  info = {}
56
 
57
+ # Day Advancement Loop
58
+ if action.action_type == ActionType.SKIP:
59
+ self.step_count += 1
60
+ if self.step_count < self.sea_blocked_until:
61
+ news = "EMERGENCY: Suez Canal blocked! Sea routes offline."
62
+
63
+ # 2. Advance Shipments
64
+ for ship in self.active_shipments:
65
+ ship.eta -= 1
66
+ if ship.eta <= 0:
67
+ self._receive_shipment(ship)
68
+
69
+ self.active_shipments = [s for s in self.active_shipments if s.eta > 0]
70
+
71
+ # 3. Check Order Deadlines
72
+ for order in self.pending_orders:
73
+ if self.step_count > order.due_date:
74
+ reward -= 50.0 # Late penalty
75
+
76
+ # 1. Process Actions (No Time Advancement)
77
  if action.action_type == ActionType.ORDER_PARTS:
78
  reward += self._handle_order_parts(action, info)
79
  elif action.action_type == ActionType.PRODUCE:
80
  reward += self._handle_production(action, info)
81
  elif action.action_type == ActionType.OFFSET:
82
  reward += self._handle_offset(action, info)
83
+ elif action.action_type == ActionType.CANCEL:
84
+ reward += self._handle_cancel(action, info)
 
 
 
 
 
 
 
 
85
 
 
 
 
 
 
86
  # 4. Check Termination
87
  if self.step_count >= 50 or not self.pending_orders:
88
  self.done = True
89
  info["final_score"] = self._calculate_final_score()
90
 
91
+ return self._get_obs(news=news), reward, self.done, info
92
 
93
  def _handle_order_parts(self, action: Action, info: dict) -> float:
94
  if not action.part_type or not action.mode or not action.quantity:
 
111
  self.cash_balance -= total_cost
112
  self.carbon_total += carbon * action.quantity
113
 
114
+ merged = False
115
+ for ship in self.active_shipments:
116
+ if ship.part == action.part_type and ship.mode == action.mode and ship.eta == eta:
117
+ ship.quantity += action.quantity
118
+ ship.cost += total_cost
119
+ ship.carbon_impact += carbon * action.quantity
120
+ merged = True
121
+ break
122
+
123
+ if not merged:
124
+ new_ship = Shipment(
125
+ id=f"SHP_{random.randint(1000, 9999)}",
126
+ part=action.part_type,
127
+ quantity=action.quantity,
128
+ mode=action.mode,
129
+ eta=eta,
130
+ carbon_impact=carbon * action.quantity,
131
+ cost=total_cost
132
+ )
133
+ self.active_shipments.append(new_ship)
134
  return 2.0
135
 
136
+ def _handle_cancel(self, action: Action, info: dict) -> float:
137
+ if not action.shipment_id: return 0.0
138
+
139
+ for i, ship in enumerate(self.active_shipments):
140
+ if ship.id == action.shipment_id:
141
+ # Refund
142
+ self.cash_balance += ship.cost
143
+ self.carbon_total = max(0.0, self.carbon_total - ship.carbon_impact)
144
+ self.active_shipments.pop(i)
145
+ return 0.0
146
+ return 0.0
147
+
148
  def _receive_shipment(self, ship: Shipment):
149
  current_val = getattr(self.inventory, ship.part.value)
150
  setattr(self.inventory, ship.part.value, current_val + ship.quantity)
netzero_nav/models.py CHANGED
@@ -52,6 +52,7 @@ class ActionType(str, Enum):
52
  PRODUCE = "produce"
53
  OFFSET = "offset"
54
  SKIP = "skip"
 
55
 
56
  class Action(BaseModel):
57
  action_type: ActionType
 
52
  PRODUCE = "produce"
53
  OFFSET = "offset"
54
  SKIP = "skip"
55
+ CANCEL = "cancel"
56
 
57
  class Action(BaseModel):
58
  action_type: ActionType