@jetta/blocks-allocator 0.2.1-0 → 0.2.1-1
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 +25 -17
- 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 +51 -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 +43 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/layout_fits_axle_load.js +35 -0
- package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/map_allocated_item_to_axle_wire.js +21 -0
- package/src/models/Cargo_Profile.js +27 -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-1",
|
|
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
|
}
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
sort_stacking_slots_by_proximity
|
|
15
15
|
} = require('./Stack_Slot_Calculator')
|
|
16
16
|
const { order_items_by_capacity_bias } = require('./Capacity_Bias_Item_Order')
|
|
17
|
+
const candidate_item_fits_axle_load = require('./axle_load/candidate_item_fits_axle_load')
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Chooses an item from available items by distributing them into stacking slots by weight
|
|
@@ -50,10 +51,10 @@ function prefer_same_id(data)
|
|
|
50
51
|
const allocation_item = item_list.find(item => item.get_id() === previous_chosen_item_id)
|
|
51
52
|
if (allocation_item)
|
|
52
53
|
{
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
54
|
+
const accepted_item = try_accept_candidate(data, container, allocation_item, item_position)
|
|
55
|
+
if (accepted_item)
|
|
55
56
|
{
|
|
56
|
-
return
|
|
57
|
+
return accepted_item
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -83,10 +84,10 @@ function choose_item_with_heuristic(data)
|
|
|
83
84
|
while (candidate_items.length > 0)
|
|
84
85
|
{
|
|
85
86
|
const allocation_item = get_item(candidate_items, qty_of_slots, container)
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
87
|
+
const accepted_item = try_accept_candidate(data, container, allocation_item, item_position)
|
|
88
|
+
if (accepted_item)
|
|
88
89
|
{
|
|
89
|
-
return
|
|
90
|
+
return accepted_item
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
remove_item_from_candidate_list(candidate_items, allocation_item)
|
|
@@ -97,7 +98,14 @@ function choose_item_with_heuristic(data)
|
|
|
97
98
|
throw new No_Suitable_Item_Exception(ALLOCATOR_MESSAGE.NO_SUITABLE_ITEM, [slot_label])
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
/**
|
|
102
|
+
* @param {object} data
|
|
103
|
+
* @param {object} container
|
|
104
|
+
* @param {object} allocation_item
|
|
105
|
+
* @param {object} item_position
|
|
106
|
+
* @returns {object|undefined}
|
|
107
|
+
*/
|
|
108
|
+
function try_accept_candidate(data, container, allocation_item, item_position)
|
|
101
109
|
{
|
|
102
110
|
const allocated_item = new Allocated_Item(
|
|
103
111
|
allocation_item,
|
|
@@ -105,19 +113,19 @@ function is_stacking_valid(container, allocation_item, item_position, block)
|
|
|
105
113
|
item_position.y,
|
|
106
114
|
item_position.z
|
|
107
115
|
)
|
|
108
|
-
if (
|
|
116
|
+
if (!candidate_item_fits_axle_load(data, allocated_item))
|
|
109
117
|
{
|
|
110
|
-
return
|
|
111
|
-
is_valid: true,
|
|
112
|
-
allocated_item
|
|
113
|
-
}
|
|
118
|
+
return undefined
|
|
114
119
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
if (should_defer_loose_on_block_stacking_validation(data.block))
|
|
121
|
+
{
|
|
122
|
+
return allocated_item
|
|
123
|
+
}
|
|
124
|
+
if (!container.is_a_valid_stacking(allocated_item))
|
|
125
|
+
{
|
|
126
|
+
return undefined
|
|
120
127
|
}
|
|
128
|
+
return allocated_item
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
function should_defer_loose_on_block_stacking_validation(block)
|
|
@@ -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,51 @@
|
|
|
1
|
+
const { gt } = require('@jetta/number-compare')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether any container axle row exceeds the folga-adjusted final max.
|
|
5
|
+
* @param {object} env - Calculator output env with containers[].pesoEixos.
|
|
6
|
+
* @param {number} folga_percent - Slack percent (10 means 110% of pesoMaxFinal).
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
function axle_load_exceeds_limit(env, folga_percent)
|
|
10
|
+
{
|
|
11
|
+
const axle_rows = collect_peso_eixos_rows(env)
|
|
12
|
+
for (const axle of axle_rows)
|
|
13
|
+
{
|
|
14
|
+
if (axle_row_exceeds_limit(axle, folga_percent))
|
|
15
|
+
{
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} env
|
|
24
|
+
* @returns {object[]}
|
|
25
|
+
*/
|
|
26
|
+
function collect_peso_eixos_rows(env)
|
|
27
|
+
{
|
|
28
|
+
const containers = env.containers ?? []
|
|
29
|
+
const rows = []
|
|
30
|
+
for (const container of containers)
|
|
31
|
+
{
|
|
32
|
+
const axle_list = container.pesoEixos ?? []
|
|
33
|
+
rows.push(...axle_list)
|
|
34
|
+
}
|
|
35
|
+
return rows
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} axle
|
|
40
|
+
* @param {number} folga_percent
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function axle_row_exceeds_limit(axle, folga_percent)
|
|
44
|
+
{
|
|
45
|
+
const loaded_kilograms = Number(axle.pesoCarregadoFinal ?? 0)
|
|
46
|
+
const max_kilograms = Number(axle.pesoMaxFinal ?? 0)
|
|
47
|
+
const limit_kilograms = max_kilograms * (1 + folga_percent / 100)
|
|
48
|
+
return gt(loaded_kilograms, limit_kilograms)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = axle_load_exceeds_limit
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const layout_fits_axle_load = require('./layout_fits_axle_load')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether placing this allocated item on top of pending axle items stays within axle caps.
|
|
5
|
+
* @param {object} data - Chooser bag with transport, container, pending_axle_items.
|
|
6
|
+
* @param {object} allocated_item
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
function candidate_item_fits_axle_load(data, allocated_item)
|
|
10
|
+
{
|
|
11
|
+
const pending_axle_items = data.pending_axle_items ?? []
|
|
12
|
+
return layout_fits_axle_load({
|
|
13
|
+
transport: data.transport,
|
|
14
|
+
container: data.container,
|
|
15
|
+
extra_items: pending_axle_items.concat(allocated_item)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = candidate_item_fits_axle_load
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const clone = require('../../../tools/Clone')
|
|
2
|
+
const map_allocated_item_to_axle_wire = require('./map_allocated_item_to_axle_wire')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Deep-clones the axle env template and writes current plus extra items per container.
|
|
6
|
+
* @param {object} transport
|
|
7
|
+
* @param {object} current_container
|
|
8
|
+
* @param {object[]} extra_items - Pending wall, earlier slots, and candidate for current container.
|
|
9
|
+
* @returns {object}
|
|
10
|
+
*/
|
|
11
|
+
function clone_env_with_layout_items(transport, current_container, extra_items)
|
|
12
|
+
{
|
|
13
|
+
const env = clone(transport.get_axle_env_template())
|
|
14
|
+
const extra_item_list = extra_items ?? []
|
|
15
|
+
for (const container_data of env.containers ?? [])
|
|
16
|
+
{
|
|
17
|
+
const sequence = Number(container_data.sequencia)
|
|
18
|
+
const live_container = transport.get_containers().get(sequence)
|
|
19
|
+
const committed_items = live_container?.get_allocated_items?.() ?? []
|
|
20
|
+
const pending_items = live_container === current_container ? extra_item_list : []
|
|
21
|
+
container_data.items = committed_items.concat(pending_items).map(map_allocated_item_to_axle_wire)
|
|
22
|
+
}
|
|
23
|
+
return env
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = clone_env_with_layout_items
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { calculate_weight_on_axles_physics } = require('@jetta/axle-load-calculator')
|
|
2
|
+
const { Logger } = require('@jetta/logger')
|
|
3
|
+
const Cargo_Profile = require('../../../models/Cargo_Profile')
|
|
4
|
+
const clone_env_with_layout_items = require('./clone_env_with_layout_items')
|
|
5
|
+
const axle_load_exceeds_limit = require('./axle_load_exceeds_limit')
|
|
6
|
+
|
|
7
|
+
const logger = new Logger('Axle Load Slot Gate')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs axle physics for the current layout plus extra items on the active container.
|
|
11
|
+
* @param {object} data
|
|
12
|
+
* @param {object} data.transport
|
|
13
|
+
* @param {object} data.container
|
|
14
|
+
* @param {object[]} data.extra_items
|
|
15
|
+
* @returns {boolean} True when no axle exceeds the folga-adjusted cap.
|
|
16
|
+
*/
|
|
17
|
+
function evaluate_layout_axle_load(data)
|
|
18
|
+
{
|
|
19
|
+
const env = clone_env_with_layout_items(data.transport, data.container, data.extra_items)
|
|
20
|
+
try
|
|
21
|
+
{
|
|
22
|
+
return layout_is_within_axle_limit(env)
|
|
23
|
+
}
|
|
24
|
+
catch (error)
|
|
25
|
+
{
|
|
26
|
+
logger.warn(`axle calculator rejected layout: ${error.message}`)
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} env
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
function layout_is_within_axle_limit(env)
|
|
36
|
+
{
|
|
37
|
+
const output = calculate_weight_on_axles_physics({ envs: [env] })
|
|
38
|
+
const calculated_env = output.envs[0]
|
|
39
|
+
const folga_percent = Cargo_Profile.axle_weight_gap_percent()
|
|
40
|
+
return axle_load_exceeds_limit(calculated_env, folga_percent) === false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = evaluate_layout_axle_load
|
package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/layout_fits_axle_load.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const Cargo_Profile = require('../../../models/Cargo_Profile')
|
|
2
|
+
const evaluate_layout_axle_load = require('./evaluate_layout_axle_load')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns whether the layout may be committed under the optional axle slot gate.
|
|
6
|
+
* @param {object} data
|
|
7
|
+
* @param {object} [data.transport]
|
|
8
|
+
* @param {object} [data.container]
|
|
9
|
+
* @param {object[]} [data.extra_items]
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
function layout_fits_axle_load(data)
|
|
13
|
+
{
|
|
14
|
+
if (!Cargo_Profile.enable_axle_load_slot_gate())
|
|
15
|
+
{
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
if (!env_has_axles(data.transport))
|
|
19
|
+
{
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
return evaluate_layout_axle_load(data)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object|undefined} transport
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function env_has_axles(transport)
|
|
30
|
+
{
|
|
31
|
+
const env = transport?.get_axle_env_template?.()
|
|
32
|
+
return Array.isArray(env?.eixos) && env.eixos.length > 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = layout_fits_axle_load
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps an allocated item to the ms-faixas axle-calculator item contract.
|
|
3
|
+
* Longitudinal chassis position is JSON `y` (allocator `z`).
|
|
4
|
+
* @param {object} allocated_item
|
|
5
|
+
* @returns {object}
|
|
6
|
+
*/
|
|
7
|
+
function map_allocated_item_to_axle_wire(allocated_item)
|
|
8
|
+
{
|
|
9
|
+
return {
|
|
10
|
+
id: allocated_item.get_id(),
|
|
11
|
+
x: allocated_item.get_x(),
|
|
12
|
+
y: allocated_item.get_z(),
|
|
13
|
+
z: allocated_item.get_y(),
|
|
14
|
+
peso: allocated_item.get_weight(),
|
|
15
|
+
comprimento: allocated_item.get_length(),
|
|
16
|
+
largura: allocated_item.get_width(),
|
|
17
|
+
altura: allocated_item.get_height()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = map_allocated_item_to_axle_wire
|
|
@@ -81,6 +81,33 @@ class Cargo_Profile
|
|
|
81
81
|
return str_to_bool(this.#instance['allocation.blocks.enable_item_continuation_lock'], true)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Whether slot item choice rejects SKUs that would overload axles.
|
|
86
|
+
* Defaults to false when omitted or when no cargo profile is loaded.
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
static enable_axle_load_slot_gate()
|
|
90
|
+
{
|
|
91
|
+
if (Cargo_Profile.#instance === null)
|
|
92
|
+
{
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
return str_to_bool(this.#instance['allocation.blocks.enable_axle_load_slot_gate'], false)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Axle slack percent applied as pesoMaxFinal * (1 + percent / 100).
|
|
100
|
+
* @returns {number}
|
|
101
|
+
*/
|
|
102
|
+
static axle_weight_gap_percent()
|
|
103
|
+
{
|
|
104
|
+
if (Cargo_Profile.#instance === null)
|
|
105
|
+
{
|
|
106
|
+
return 0
|
|
107
|
+
}
|
|
108
|
+
return finite_float_from_string_or_number(this.#instance.folga_de_peso_nos_eixos, 0)
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
static enable_key_item_mapping()
|
|
85
112
|
{
|
|
86
113
|
return str_to_bool(this.#instance['allocation.blocks.preferential.enable'], true)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const create_container_map = require('./factory/Container_Factory')
|
|
2
|
+
const clone = require('../../tools/Clone')
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* In-memory representation of one transport environment: a map of sequence → container model.
|
|
@@ -6,6 +7,7 @@ const create_container_map = require('./factory/Container_Factory')
|
|
|
6
7
|
class Transport
|
|
7
8
|
{
|
|
8
9
|
#containers = new Map()
|
|
10
|
+
#axle_env_template
|
|
9
11
|
/**
|
|
10
12
|
* @param {object} transport_data - First element shape from `input_data.envs` (includes `containers` array).
|
|
11
13
|
* @param {object|undefined} empty_space_finder_options - Optional merge tolerance for each {@link Container} finder.
|
|
@@ -13,6 +15,16 @@ class Transport
|
|
|
13
15
|
constructor(data)
|
|
14
16
|
{
|
|
15
17
|
this.#containers = create_container_map(data)
|
|
18
|
+
this.#axle_env_template = clone(data.transport_data)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Frozen-at-construction clone of the env JSON used by axle-load physics.
|
|
23
|
+
* @returns {object}
|
|
24
|
+
*/
|
|
25
|
+
get_axle_env_template()
|
|
26
|
+
{
|
|
27
|
+
return this.#axle_env_template
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
/**
|