@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.
- package/package.json +2 -1
- package/src/Allocation_Attempt.js +4 -1
- package/src/Allocation_Optimization/Block_Allocator.js +12 -6
- package/src/Allocation_Optimization/block_builder/Block_Wall_Builder.js +8 -1
- package/src/Allocation_Optimization/block_builder/Blocks_Builder.js +3 -1
- package/src/Allocation_Optimization/block_builder/Slot_Item_Assigner.js +4 -2
- package/src/Allocation_Optimization/item_consumption_heuristic/Item_Chooser.js +6 -130
- package/src/Allocation_Optimization/item_consumption_heuristic/Slots_Items_Assigner.js +59 -32
- package/src/Allocation_Optimization/item_consumption_heuristic/assign_one_plan_slot.js +53 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/assert_layout_fits_axle_load.js +26 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/axle_load_exceeds_limit.js +62 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/candidate_item_axle_score.js +42 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/candidate_item_fits_axle_load.js +19 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/clone_env_with_layout_items.js +26 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/evaluate_layout_axle_load.js +63 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/layout_fits_axle_load.js +36 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/map_allocated_item_to_axle_wire.js +21 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/select_axle_balanced_slot_item.js +143 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/select_slot_item.js +149 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_acceptance.js +110 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_order.js +49 -0
- package/src/models/Cargo_Profile.js +27 -0
- package/src/models/transport/Container.js +11 -0
- 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-
|
|
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
|
|
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
|
|
44
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
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
|
package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/axle_load_exceeds_limit.js
ADDED
|
@@ -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
|