@jetta/blocks-allocator 0.2.1-0 → 0.2.1-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/package.json +2 -1
  2. package/src/Allocation_Attempt.js +4 -1
  3. package/src/Allocation_Optimization/Block_Allocator.js +12 -6
  4. package/src/Allocation_Optimization/block_builder/Block_Wall_Builder.js +8 -1
  5. package/src/Allocation_Optimization/block_builder/Blocks_Builder.js +3 -1
  6. package/src/Allocation_Optimization/block_builder/Slot_Item_Assigner.js +4 -2
  7. package/src/Allocation_Optimization/item_consumption_heuristic/Item_Chooser.js +6 -130
  8. package/src/Allocation_Optimization/item_consumption_heuristic/Slots_Items_Assigner.js +59 -32
  9. package/src/Allocation_Optimization/item_consumption_heuristic/assign_one_plan_slot.js +53 -0
  10. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/assert_layout_fits_axle_load.js +26 -0
  11. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/axle_load_exceeds_limit.js +62 -0
  12. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/candidate_item_axle_score.js +42 -0
  13. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/candidate_item_fits_axle_load.js +19 -0
  14. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/clone_env_with_layout_items.js +26 -0
  15. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/evaluate_layout_axle_load.js +63 -0
  16. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/layout_fits_axle_load.js +36 -0
  17. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/map_allocated_item_to_axle_wire.js +21 -0
  18. package/src/Allocation_Optimization/item_consumption_heuristic/select_axle_balanced_slot_item.js +143 -0
  19. package/src/Allocation_Optimization/item_consumption_heuristic/select_slot_item.js +149 -0
  20. package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_acceptance.js +110 -0
  21. package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_order.js +49 -0
  22. package/src/models/Cargo_Profile.js +27 -0
  23. package/src/models/transport/Container.js +11 -0
  24. package/src/models/transport/Transport.js +12 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetta/blocks-allocator",
3
- "version": "0.2.1-0",
3
+ "version": "0.2.1-2",
4
4
  "description": "Blocks wall allocation library",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -40,6 +40,7 @@
40
40
  "author": "",
41
41
  "license": "ISC",
42
42
  "dependencies": {
43
+ "@jetta/axle-load-calculator": "^0.4.1",
43
44
  "@jetta/empty-spaces-manager": "^0.1.1-0",
44
45
  "@jetta/item-amount-controller": "0.1.0-0",
45
46
  "@jetta/logger": "^2.0.1",
@@ -22,12 +22,14 @@ class Allocation_Attempt
22
22
  #item_list
23
23
  #amount_controller
24
24
  #allocator
25
+ #transport
25
26
 
26
27
  constructor(input_state)
27
28
  {
28
29
  this.#item_list = input_state.items_to_allocate
29
30
  this.#amount_controller = new Item_Amount_Controller(input_state.items_to_allocate)
30
31
  this.#allocator = new Block_Allocator()
32
+ this.#transport = input_state.transport
31
33
  }
32
34
 
33
35
  /**
@@ -103,7 +105,8 @@ class Allocation_Attempt
103
105
  this.#item_list,
104
106
  block_amount_map,
105
107
  effective_item_list,
106
- picking_continuity_seed
108
+ picking_continuity_seed,
109
+ this.#transport
107
110
  )
108
111
  this.#update_state(container, allocation_output)
109
112
  mark_trace_outcome(TRACE_CONTEXT.BLOCK_WALL_ALLOCATED)
@@ -19,7 +19,8 @@ class Block_Allocator
19
19
  item_list,
20
20
  block_amount_map,
21
21
  effective_item_list,
22
- picking_continuity_seed
22
+ picking_continuity_seed,
23
+ transport
23
24
  )
24
25
  {
25
26
  this.#next_group_id = resolve_next_group_id(container, this.#next_group_id)
@@ -47,7 +48,8 @@ class Block_Allocator
47
48
  item_amount_map,
48
49
  container,
49
50
  picking_continuity_seed,
50
- optimized_blocks.block_instance_item_queues
51
+ optimized_blocks.block_instance_item_queues,
52
+ transport
51
53
  )
52
54
 
53
55
  if (block_builder_output.allocated_item_list.length === 0)
@@ -85,7 +87,8 @@ class Block_Allocator
85
87
  item_amount_map,
86
88
  container,
87
89
  picking_continuity_seed,
88
- block_instance_item_queues
90
+ block_instance_item_queues,
91
+ transport
89
92
  )
90
93
  {
91
94
  const block_builder_data = this.#create_blocks_builder_data(
@@ -94,7 +97,8 @@ class Block_Allocator
94
97
  item_list,
95
98
  item_amount_map,
96
99
  container,
97
- picking_continuity_seed
100
+ picking_continuity_seed,
101
+ transport
98
102
  )
99
103
  block_builder_data.block_instance_item_queues = block_instance_item_queues ?? new Map()
100
104
  const block_builder = new Block_Builder(block_builder_data)
@@ -109,7 +113,8 @@ class Block_Allocator
109
113
  item_list,
110
114
  item_amount_map,
111
115
  container,
112
- picking_continuity_seed
116
+ picking_continuity_seed,
117
+ transport
113
118
  )
114
119
  {
115
120
  return {
@@ -119,7 +124,8 @@ class Block_Allocator
119
124
  item_amount_map: item_amount_map,
120
125
  container: container,
121
126
  picking_continuity_seed: picking_continuity_seed,
122
- next_group_id: this.#next_group_id
127
+ next_group_id: this.#next_group_id,
128
+ transport: transport
123
129
  }
124
130
  }
125
131
 
@@ -12,6 +12,7 @@ const { ALLOCATOR_MESSAGE } = require('../../messages/Allocator_Message')
12
12
  const assert_tie_ratio_block_matches_orientation_below = require('./assert_tie_ratio_block_matches_orientation_below')
13
13
  const snap_elevated_block_wall_items = require('./snap_elevated_block_wall_items')
14
14
  const should_omit_block_group_id = require('./should_omit_block_group_id')
15
+ const assert_layout_fits_axle_load = require('../item_consumption_heuristic/axle_load/assert_layout_fits_axle_load')
15
16
  const allocated_items_weight_sum = (item_list) => item_list.reduce((acc, item) => acc + item.get_weight(), 0)
16
17
 
17
18
  /**
@@ -124,7 +125,8 @@ function build_a_block(chosen_block, is_single_slot, state, wall_data, pending_w
124
125
  wall_data.allocated_blocks_amount,
125
126
  state.container_supported_weight,
126
127
  item_position,
127
- state.last_chosen_item_id
128
+ state.last_chosen_item_id,
129
+ pending_wall_allocated_items
128
130
  )
129
131
  let realigned_item_list = item_in_block_list
130
132
  try
@@ -143,6 +145,11 @@ function build_a_block(chosen_block, is_single_slot, state, wall_data, pending_w
143
145
  state,
144
146
  pending_wall_allocated_items
145
147
  )
148
+ assert_layout_fits_axle_load(
149
+ wall_data,
150
+ pending_wall_allocated_items,
151
+ realigned_item_list
152
+ )
146
153
  }
147
154
  catch (error)
148
155
  {
@@ -15,11 +15,13 @@ module.exports = class Block_Builder
15
15
  container: block_builder_data.container,
16
16
  picking_continuity_seed: block_builder_data.picking_continuity_seed,
17
17
  block_instance_item_queues: block_builder_data.block_instance_item_queues ?? new Map(),
18
+ transport: block_builder_data.transport,
18
19
  heuristic: new Slots_Items_Assigner(
19
20
  block_builder_data.space_to_allocate,
20
21
  block_builder_data.item_list,
21
22
  block_builder_data.item_amount_map.copy(),
22
- block_builder_data.container
23
+ block_builder_data.container,
24
+ block_builder_data.transport
23
25
  ),
24
26
  allocated_blocks_amount: {},
25
27
  next_group_id: block_builder_data.next_group_id ?? 0
@@ -9,7 +9,8 @@ module.exports = function assign_item_to_slots(
9
9
  allocated_blocks_amount,
10
10
  container_supported_weight,
11
11
  item_position,
12
- picking_continuity_seed
12
+ picking_continuity_seed,
13
+ pending_wall_allocated_items
13
14
  )
14
15
  {
15
16
  try
@@ -18,7 +19,8 @@ module.exports = function assign_item_to_slots(
18
19
  block,
19
20
  container_supported_weight,
20
21
  item_position,
21
- picking_continuity_seed
22
+ picking_continuity_seed,
23
+ pending_wall_allocated_items
22
24
  )
23
25
  return assigned_item_list
24
26
  }
@@ -1,23 +1,11 @@
1
1
  const No_Available_Items_Exception = require('./exceptions/No_Available_Items_Exception')
2
- const No_Slots_For_Items_Exception = require('./exceptions/No_Slots_For_Items_Exception')
3
2
  const Cargo_Profile = require('../../models/Cargo_Profile')
4
- const No_Suitable_Item_Exception = require('../block_builder/exceptions/No_Suitable_Item_Exception')
5
- const { ALLOCATOR_MESSAGE } = require('../../messages/Allocator_Message')
6
- const Allocated_Item = require('../../models/Allocated_Item')
7
- const { distribute_items_by_weight_range } = require('./Weight_Slot_Distribution')
8
- const {
9
- resolve_choose_item_heuristic_context,
10
- format_slot_label_for_exception
11
- } = require('./Choose_Item_Heuristic_Context')
12
- const {
13
- calculate_total_stack_slots,
14
- sort_stacking_slots_by_proximity
15
- } = require('./Stack_Slot_Calculator')
16
- const { order_items_by_capacity_bias } = require('./Capacity_Bias_Item_Order')
3
+ const { select_slot_item, select_preferred_or_heuristic } = require('./select_slot_item')
17
4
 
18
5
  /**
19
6
  * Chooses an item from available items by distributing them into stacking slots by weight
20
7
  * and returning the item in the slot closest to the block slot position.
8
+ * When the axle gate is on, legal SKUs in that band are ranked by hottest-axle ratio.
21
9
  * @param {object} data - Input bag.
22
10
  * @param {object} [data.slot] - Slot with get_y() and get_height() (production).
23
11
  * @param {object} [data.block_slot] - Legacy alias for slot (tests).
@@ -40,120 +28,8 @@ module.exports = function choose_item(data)
40
28
  {
41
29
  throw new No_Available_Items_Exception()
42
30
  }
43
- const is_to_prefer_same_id = data.block.get_slot_list().length > 0 && previous_chosen_item_id !== undefined && Cargo_Profile.prefer_block_with_same_item()
44
- return is_to_prefer_same_id ? prefer_same_id(data) : choose_item_with_heuristic(data)
45
- }
46
-
47
- function prefer_same_id(data)
48
- {
49
- const { container, item_list, previous_chosen_item_id, item_position } = data
50
- const allocation_item = item_list.find(item => item.get_id() === previous_chosen_item_id)
51
- if (allocation_item)
52
- {
53
- const result = is_stacking_valid(container, allocation_item, item_position, data.block)
54
- if (result.is_valid)
55
- {
56
- return result.allocated_item
57
- }
58
- }
59
-
60
- return choose_item_with_heuristic(data)
61
- }
62
-
63
- /**
64
- * Heuristic path: weight bands, proximity-ordered bands, stacking validation per candidate.
65
- * @param {object} data - Input bag (see choose_item).
66
- * @returns {object} Chosen item.
67
- */
68
- function choose_item_with_heuristic(data)
69
- {
70
- const { slot, container, item_position } = resolve_choose_item_heuristic_context(data)
71
- const { item_list, qty_of_slots } = data
72
- const total_slots = calculate_total_stack_slots(container.get_height(), slot)
73
- const stacking_slots = distribute_items_by_weight_range(item_list, total_slots)
74
- const ordered_bands = sort_stacking_slots_by_proximity(
75
- stacking_slots,
76
- slot.get_y(),
77
- slot.get_height()
78
- )
79
-
80
- for (const band of ordered_bands)
81
- {
82
- const candidate_items = band.items
83
- while (candidate_items.length > 0)
84
- {
85
- const allocation_item = get_item(candidate_items, qty_of_slots, container)
86
- const result = is_stacking_valid(container, allocation_item, item_position, data.block)
87
- if (result.is_valid)
88
- {
89
- return result.allocated_item
90
- }
91
-
92
- remove_item_from_candidate_list(candidate_items, allocation_item)
93
- }
94
- }
95
-
96
- const slot_label = format_slot_label_for_exception(slot)
97
- throw new No_Suitable_Item_Exception(ALLOCATOR_MESSAGE.NO_SUITABLE_ITEM, [slot_label])
98
- }
99
-
100
- function is_stacking_valid(container, allocation_item, item_position, block)
101
- {
102
- const allocated_item = new Allocated_Item(
103
- allocation_item,
104
- item_position.x,
105
- item_position.y,
106
- item_position.z
107
- )
108
- if (should_defer_loose_on_block_stacking_validation(block))
109
- {
110
- return {
111
- is_valid: true,
112
- allocated_item
113
- }
114
- }
115
- const is_valid = container.is_a_valid_stacking(allocated_item)
116
-
117
- return {
118
- is_valid,
119
- allocated_item
120
- }
121
- }
122
-
123
- function should_defer_loose_on_block_stacking_validation(block)
124
- {
125
- return Cargo_Profile.stack_loose_on_tie_ratio_blocks()
126
- && block?.get_slot_list?.().length === 1
127
- }
128
-
129
- function remove_item_from_candidate_list(candidate_item_list, allocation_item)
130
- {
131
- const index = candidate_item_list.indexOf(allocation_item)
132
- if (index > -1)
133
- {
134
- candidate_item_list.splice(index, 1)
135
- }
136
- }
137
-
138
- function get_item(item_list, qty_of_slots, container)
139
- {
140
- if (qty_of_slots === undefined)
141
- {
142
- throw new No_Slots_For_Items_Exception()
143
- }
144
-
145
- if (item_list.length === 0)
146
- {
147
- throw new No_Available_Items_Exception()
148
- }
149
-
150
- const ordered_item_list = order_items_by_capacity_bias(item_list, container)
151
- const preferable_item_list = ordered_item_list.filter(item => item.get_amount() >= qty_of_slots)
152
-
153
- if (preferable_item_list.length > 0)
154
- {
155
- return preferable_item_list[0]
156
- }
157
-
158
- return ordered_item_list[0]
31
+ const is_to_prefer_same_id = data.block.get_slot_list().length > 0
32
+ && previous_chosen_item_id !== undefined
33
+ && Cargo_Profile.prefer_block_with_same_item()
34
+ return is_to_prefer_same_id ? select_preferred_or_heuristic(data) : select_slot_item(data)
159
35
  }
@@ -1,7 +1,7 @@
1
1
  const choose_item_for_slot = require('./Item_For_Slot_Chooser')
2
2
  const No_Suitable_Item_Exception = require('../block_builder/exceptions/No_Suitable_Item_Exception')
3
3
  const { ALLOCATOR_MESSAGE } = require('../../messages/Allocator_Message')
4
- const Allocated_Item = require('../../models/Allocated_Item')
4
+ const assign_one_plan_slot = require('./assign_one_plan_slot')
5
5
  const { block_has_sufficient_shape_stock } = require('../block_builder/block_shape_stock')
6
6
  const { Logger } = require('@jetta/logger')
7
7
  const logger = new Logger('Item Consumption Heuristic')
@@ -26,6 +26,12 @@ function process_one_slot(data)
26
26
  return { allocated_item, next_weight }
27
27
  }
28
28
 
29
+ function pending_items_for_slot(pending_wall_allocated_items, allocated_item_list, index)
30
+ {
31
+ const filled_slots = allocated_item_list.slice(0, index).filter(Boolean)
32
+ return (pending_wall_allocated_items ?? []).concat(filled_slots)
33
+ }
34
+
29
35
  /**
30
36
  * Assigns items to block slots using a consumption heuristic (slot order and stacking).
31
37
  */
@@ -35,18 +41,21 @@ class Item_Consumption_Heuristic
35
41
  #item_map = {}
36
42
  #item_amount_map
37
43
  #container
44
+ #transport
38
45
 
39
46
  /**
40
47
  * @param {object[]} item_list - Items available for allocation.
41
48
  * @param {object} item_amount_map - Tracks remaining amount per item.
42
49
  * @param {object} container - Target container.
50
+ * @param {object} [transport] - Transport holding the axle env template.
43
51
  */
44
- constructor(empty_space, item_list, item_amount_map, container)
52
+ constructor(empty_space, item_list, item_amount_map, container, transport)
45
53
  {
46
54
  this.#empty_space = empty_space
47
55
  this.#item_map = build_item_map(item_list)
48
56
  this.#item_amount_map = item_amount_map
49
57
  this.#container = container
58
+ this.#transport = transport
50
59
  }
51
60
 
52
61
  /**
@@ -55,9 +64,16 @@ class Item_Consumption_Heuristic
55
64
  * @param {number} container_supported_weight - Supported weight for the layer.
56
65
  * @param {object} item_position - Base block position { x, y, z } before per-slot offsets (from space / block placement).
57
66
  * @param {string|undefined} picking_continuity_seed - Prior SKU id to prefer on the first slot.
67
+ * @param {object[]} [pending_wall_allocated_items] - Items already chosen in this wall.
58
68
  * @returns {object[]|undefined} Allocated items, or undefined if a non-rethrown error occurred.
59
69
  */
60
- assign_items_to_slots(block, container_supported_weight, item_position, picking_continuity_seed)
70
+ assign_items_to_slots(
71
+ block,
72
+ container_supported_weight,
73
+ item_position,
74
+ picking_continuity_seed,
75
+ pending_wall_allocated_items
76
+ )
61
77
  {
62
78
  const slot_count = block.get_slot_list().length
63
79
  if (slot_count === 0)
@@ -76,7 +92,8 @@ class Item_Consumption_Heuristic
76
92
  block,
77
93
  Number(container_supported_weight),
78
94
  item_position,
79
- picking_continuity_seed
95
+ picking_continuity_seed,
96
+ pending_wall_allocated_items
80
97
  )
81
98
  }
82
99
 
@@ -86,9 +103,16 @@ class Item_Consumption_Heuristic
86
103
  * @param {string[]} slot_item_ids - Item ids per slot (same order as ordered slots).
87
104
  * @param {number} container_supported_weight
88
105
  * @param {object} item_position
106
+ * @param {object[]} [pending_wall_allocated_items]
89
107
  * @returns {object[]}
90
108
  */
91
- assign_items_from_plan(block, slot_item_ids, container_supported_weight, item_position)
109
+ assign_items_from_plan(
110
+ block,
111
+ slot_item_ids,
112
+ container_supported_weight,
113
+ item_position,
114
+ pending_wall_allocated_items
115
+ )
92
116
  {
93
117
  const slot_list = order_slots_by_y(block.get_slot_list())
94
118
  const allocated_item_list = []
@@ -97,31 +121,22 @@ class Item_Consumption_Heuristic
97
121
  {
98
122
  const slot = slot_list[index]
99
123
  const item_id = slot_item_ids[index]
100
- const allocation_item = this.#item_map[item_id]
101
- if (!allocation_item || allocation_item.get_amount() <= 0)
102
- {
103
- throw new No_Suitable_Item_Exception(
104
- ALLOCATOR_MESSAGE.LP_PLAN_ITEM_UNAVAILABLE,
105
- [String(item_id), slot.to_string()]
106
- )
107
- }
108
-
109
- const current_item_position = this.#update_item_position(item_position, slot)
110
- const allocated_item = new Allocated_Item(
111
- allocation_item,
112
- current_item_position.x,
113
- current_item_position.y,
114
- current_item_position.z
115
- )
116
-
117
- if (!this.#container.is_a_valid_stacking(allocated_item))
118
- {
119
- throw new No_Suitable_Item_Exception(
120
- ALLOCATOR_MESSAGE.LP_PLAN_ITEM_STACKING_FAILED,
121
- [String(item_id), slot.to_string()]
122
- )
123
- }
124
-
124
+ const allocated_item = assign_one_plan_slot({
125
+ item_id,
126
+ slot,
127
+ allocation_item: this.#item_map[item_id],
128
+ item_position: this.#update_item_position(item_position, slot),
129
+ container: this.#container,
130
+ axle_probe: {
131
+ transport: this.#transport,
132
+ container: this.#container,
133
+ pending_axle_items: pending_items_for_slot(
134
+ pending_wall_allocated_items,
135
+ allocated_item_list,
136
+ allocated_item_list.length
137
+ )
138
+ }
139
+ })
125
140
  this.#item_amount_map.discount_single_item(allocated_item)
126
141
  allocated_item_list.push(allocated_item)
127
142
  }
@@ -139,7 +154,13 @@ class Item_Consumption_Heuristic
139
154
  }
140
155
  }
141
156
 
142
- #assign_items_for_ordered_slots(block, initial_weight, starting_item_position, picking_continuity_seed)
157
+ #assign_items_for_ordered_slots(
158
+ block,
159
+ initial_weight,
160
+ starting_item_position,
161
+ picking_continuity_seed,
162
+ pending_wall_allocated_items
163
+ )
143
164
  {
144
165
  const slot_list = order_slots_by_y(block.get_slot_list())
145
166
  const allocated_item_list = new Array(slot_list.length)
@@ -162,7 +183,13 @@ class Item_Consumption_Heuristic
162
183
  item_amount_map: provisional_amount_map,
163
184
  previous_chosen_item_id,
164
185
  qty_of_slots,
165
- item_position: current_item_position
186
+ item_position: current_item_position,
187
+ transport: this.#transport,
188
+ pending_axle_items: pending_items_for_slot(
189
+ pending_wall_allocated_items,
190
+ allocated_item_list,
191
+ index
192
+ )
166
193
  })
167
194
  allocated_item_list[index] = result.allocated_item
168
195
  current_weight = result.next_weight
@@ -0,0 +1,53 @@
1
+ const Allocated_Item = require('../../models/Allocated_Item')
2
+ const No_Suitable_Item_Exception = require('../block_builder/exceptions/No_Suitable_Item_Exception')
3
+ const { ALLOCATOR_MESSAGE } = require('../../messages/Allocator_Message')
4
+ const candidate_item_fits_axle_load = require('./axle_load/candidate_item_fits_axle_load')
5
+
6
+ /**
7
+ * Places one LP-plan item onto a slot, validating stacking and axle load.
8
+ * @param {object} data
9
+ * @returns {object}
10
+ */
11
+ function assign_one_plan_slot(data)
12
+ {
13
+ const allocated_item = create_plan_allocated_item(data)
14
+ if (!data.container.is_a_valid_stacking(allocated_item))
15
+ {
16
+ throw new No_Suitable_Item_Exception(
17
+ ALLOCATOR_MESSAGE.LP_PLAN_ITEM_STACKING_FAILED,
18
+ [String(data.item_id), data.slot.to_string()]
19
+ )
20
+ }
21
+ if (!candidate_item_fits_axle_load(data.axle_probe, allocated_item))
22
+ {
23
+ throw new No_Suitable_Item_Exception(
24
+ ALLOCATOR_MESSAGE.NO_SUITABLE_ITEM,
25
+ [data.slot.to_string()]
26
+ )
27
+ }
28
+ return allocated_item
29
+ }
30
+
31
+ /**
32
+ * @param {object} data
33
+ * @returns {object}
34
+ */
35
+ function create_plan_allocated_item(data)
36
+ {
37
+ const allocation_item = data.allocation_item
38
+ if (!allocation_item || allocation_item.get_amount() <= 0)
39
+ {
40
+ throw new No_Suitable_Item_Exception(
41
+ ALLOCATOR_MESSAGE.LP_PLAN_ITEM_UNAVAILABLE,
42
+ [String(data.item_id), data.slot.to_string()]
43
+ )
44
+ }
45
+ return new Allocated_Item(
46
+ allocation_item,
47
+ data.item_position.x,
48
+ data.item_position.y,
49
+ data.item_position.z
50
+ )
51
+ }
52
+
53
+ module.exports = assign_one_plan_slot
@@ -0,0 +1,26 @@
1
+ const No_Suitable_Item_Exception = require('../../block_builder/exceptions/No_Suitable_Item_Exception')
2
+ const { ALLOCATOR_MESSAGE } = require('../../../messages/Allocator_Message')
3
+ const layout_fits_axle_load = require('./layout_fits_axle_load')
4
+
5
+ /**
6
+ * Throws when the realigned block items would overload axles.
7
+ * @param {object} wall_data
8
+ * @param {object[]} pending_wall_allocated_items
9
+ * @param {object[]} realigned_item_list
10
+ * @returns {void}
11
+ */
12
+ function assert_layout_fits_axle_load(wall_data, pending_wall_allocated_items, realigned_item_list)
13
+ {
14
+ const extra_items = (pending_wall_allocated_items ?? []).concat(realigned_item_list)
15
+ const fits = layout_fits_axle_load({
16
+ transport: wall_data.transport,
17
+ container: wall_data.container,
18
+ extra_items
19
+ })
20
+ if (!fits)
21
+ {
22
+ throw new No_Suitable_Item_Exception(ALLOCATOR_MESSAGE.NO_SUITABLE_ITEM)
23
+ }
24
+ }
25
+
26
+ module.exports = assert_layout_fits_axle_load
@@ -0,0 +1,62 @@
1
+ const { gt } = require('@jetta/number-compare')
2
+
3
+ /**
4
+ * Hottest axle load as a fraction of the folga-adjusted cap. Above 1 is over limit.
5
+ * @param {object} env - Calculator output env with containers[].pesoEixos.
6
+ * @param {number} folga_percent - Slack percent (10 means 110% of pesoMaxFinal).
7
+ * @returns {number}
8
+ */
9
+ function max_axle_load_ratio(env, folga_percent)
10
+ {
11
+ let hottest_ratio = 0
12
+ for (const axle of collect_peso_eixos_rows(env))
13
+ {
14
+ hottest_ratio = Math.max(hottest_ratio, axle_row_ratio(axle, folga_percent))
15
+ }
16
+ return hottest_ratio
17
+ }
18
+
19
+ /**
20
+ * Whether any container axle row exceeds the folga-adjusted final max.
21
+ * @param {object} env - Calculator output env with containers[].pesoEixos.
22
+ * @param {number} folga_percent - Slack percent (10 means 110% of pesoMaxFinal).
23
+ * @returns {boolean}
24
+ */
25
+ function axle_load_exceeds_limit(env, folga_percent)
26
+ {
27
+ return gt(max_axle_load_ratio(env, folga_percent), 1)
28
+ }
29
+
30
+ /**
31
+ * @param {object} env
32
+ * @returns {object[]}
33
+ */
34
+ function collect_peso_eixos_rows(env)
35
+ {
36
+ const rows = []
37
+ for (const container of env.containers ?? [])
38
+ {
39
+ rows.push(...(container.pesoEixos ?? []))
40
+ }
41
+ return rows
42
+ }
43
+
44
+ /**
45
+ * @param {object} axle
46
+ * @param {number} folga_percent
47
+ * @returns {number}
48
+ */
49
+ function axle_row_ratio(axle, folga_percent)
50
+ {
51
+ const loaded_kilograms = Number(axle.pesoCarregadoFinal ?? 0)
52
+ const max_kilograms = Number(axle.pesoMaxFinal ?? 0)
53
+ const limit_kilograms = max_kilograms * (1 + folga_percent / 100)
54
+ if (limit_kilograms === 0)
55
+ {
56
+ return loaded_kilograms > 0 ? Number.POSITIVE_INFINITY : 0
57
+ }
58
+ return loaded_kilograms / limit_kilograms
59
+ }
60
+
61
+ module.exports = axle_load_exceeds_limit
62
+ module.exports.max_axle_load_ratio = max_axle_load_ratio
@@ -0,0 +1,42 @@
1
+ const { gt } = require('@jetta/number-compare')
2
+ const Cargo_Profile = require('../../../models/Cargo_Profile')
3
+ const { score_layout_axle_load } = require('./evaluate_layout_axle_load')
4
+ const layout_fits_axle_load = require('./layout_fits_axle_load')
5
+
6
+ /**
7
+ * Axle score for one candidate. fits is false when the cap is exceeded or physics fails.
8
+ * When the gate is off, ratio is 0 and fits is true.
9
+ * @param {object} data
10
+ * @param {object} allocated_item
11
+ * @returns {{ fits: boolean, ratio: number|undefined }}
12
+ */
13
+ function candidate_item_axle_score(data, allocated_item)
14
+ {
15
+ if (!Cargo_Profile.enable_axle_load_slot_gate() || !layout_fits_axle_load.transport_has_axles(data.transport))
16
+ {
17
+ return { fits: true, ratio: 0 }
18
+ }
19
+ const ratio = score_layout_axle_load(layout_probe(data, allocated_item))
20
+ if (ratio === undefined)
21
+ {
22
+ return { fits: false, ratio: undefined }
23
+ }
24
+ return { fits: gt(ratio, 1) === false, ratio }
25
+ }
26
+
27
+ /**
28
+ * @param {object} data
29
+ * @param {object} allocated_item
30
+ * @returns {object}
31
+ */
32
+ function layout_probe(data, allocated_item)
33
+ {
34
+ const pending_axle_items = data.pending_axle_items ?? []
35
+ return {
36
+ transport: data.transport,
37
+ container: data.container,
38
+ extra_items: pending_axle_items.concat(allocated_item)
39
+ }
40
+ }
41
+
42
+ module.exports = candidate_item_axle_score