@jetta/blocks-allocator 0.1.0-0 → 0.1.1-0
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 -2
- package/src/Allocation_Optimization/lp_model/model_builder/restrictions/Container_Supported_Weight_Restriction.js +69 -25
- package/src/Allocation_Optimization/tie_ratio/Same_Height_Tie_Ratio_Filter.js +28 -0
- package/src/Allocation_Optimization/tie_ratio/filter_out_mixed_height_tie_ratio.js +20 -0
- package/src/Allocation_Optimization/tie_ratio/has_stocked_same_height_tie_ratio.js +40 -0
- package/src/Allocation_Optimization/tie_ratio/is_tie_ratio_block.js +12 -0
- package/src/Allocation_Optimization/tie_ratio/max_tie_ratio_slot_count.js +24 -0
- package/src/Allocation_Optimization/tie_ratio/tie_ratio_block_has_shape_stock.js +113 -0
- package/src/models/Cargo_Profile.js +15 -0
- package/src/space_and_block_chooser/blocks_filters/pos_selection_filters/Post_Selection_Filter_List.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetta/blocks-allocator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-0",
|
|
4
4
|
"description": "Blocks wall allocation library",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@jetta/lp-framework": "2.1.0-6",
|
|
47
47
|
"@jetta/number-compare": "^3.0.2-4",
|
|
48
48
|
"@jetta/stacking-constraints": "^1.1.0",
|
|
49
|
-
"@jetta/tie-ratio-block-creator-core": "1.2.2-
|
|
49
|
+
"@jetta/tie-ratio-block-creator-core": "1.2.2-5",
|
|
50
50
|
"@jetta/z-intersected-block-creator": "^0.0.1-42",
|
|
51
51
|
"@jettacargo/single-item-block-creator-core": "2.0.1-0",
|
|
52
52
|
"@jettacargo/spiral-block-creator-core": "2.1.2",
|
|
@@ -1,42 +1,86 @@
|
|
|
1
1
|
const { VariableType, create_variable, create_restriction } = require('@jetta/lp-framework')
|
|
2
|
-
const
|
|
3
|
-
get_dimension_to_max_weight_map,
|
|
4
|
-
calculate_block_potential_weight
|
|
5
|
-
} = require('../../../Block_Potential_Weight')
|
|
2
|
+
const create_item_assignment_variables = require('../Item_Assignment_Variables')
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
|
-
* Creates
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @param {object
|
|
12
|
-
* @param {object[]}
|
|
13
|
-
* @
|
|
5
|
+
* Creates one LP restriction so the weight of items assigned to chosen blocks
|
|
6
|
+
* stays within the container supported weight.
|
|
7
|
+
* Each assignment variable is weighted by that item's own weight.
|
|
8
|
+
* @param {object} container - Container with supported_weight() or a supported_weight number.
|
|
9
|
+
* @param {object[]} block_list - Block objects.
|
|
10
|
+
* @param {object[]} item_list - Item objects with get_id() and get_weight().
|
|
11
|
+
* @returns {object[]} One restriction, or an empty list when no positive item weight applies.
|
|
14
12
|
*/
|
|
15
13
|
module.exports = function create_container_supported_weight_restriction(container, block_list, item_list)
|
|
16
14
|
{
|
|
17
|
-
const
|
|
15
|
+
const assignment = create_item_assignment_variables(block_list, item_list)
|
|
16
|
+
const variable_list = build_item_weight_variables(assignment.slot_demand_list, item_list)
|
|
17
|
+
if (variable_list.length === 0)
|
|
18
|
+
{
|
|
19
|
+
return []
|
|
20
|
+
}
|
|
21
|
+
return [create_restriction(
|
|
22
|
+
'container_supported_weight',
|
|
23
|
+
variable_list,
|
|
24
|
+
{lower_limit: 0, upper_limit: read_supported_weight(container)}
|
|
25
|
+
)]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Array<{compatibility_list: Array<{item_id: string, variable_name: string}>}>} slot_demand_list
|
|
30
|
+
* @param {object[]} item_list
|
|
31
|
+
* @returns {object[]}
|
|
32
|
+
*/
|
|
33
|
+
function build_item_weight_variables(slot_demand_list, item_list)
|
|
34
|
+
{
|
|
35
|
+
const weight_by_item_id = map_item_weight(item_list)
|
|
18
36
|
const variable_list = []
|
|
37
|
+
for (const slot_demand of slot_demand_list)
|
|
38
|
+
{
|
|
39
|
+
append_slot_item_weights(variable_list, slot_demand.compatibility_list, weight_by_item_id)
|
|
40
|
+
}
|
|
41
|
+
return variable_list
|
|
42
|
+
}
|
|
19
43
|
|
|
20
|
-
|
|
44
|
+
/**
|
|
45
|
+
* @param {object[]} item_list
|
|
46
|
+
* @returns {Map<string, number>}
|
|
47
|
+
*/
|
|
48
|
+
function map_item_weight(item_list)
|
|
49
|
+
{
|
|
50
|
+
const weight_by_item_id = new Map()
|
|
51
|
+
for (const item of item_list)
|
|
21
52
|
{
|
|
22
|
-
|
|
53
|
+
weight_by_item_id.set(item.get_id(), item.get_weight())
|
|
54
|
+
}
|
|
55
|
+
return weight_by_item_id
|
|
56
|
+
}
|
|
23
57
|
|
|
24
|
-
|
|
58
|
+
/**
|
|
59
|
+
* @param {object[]} variable_list
|
|
60
|
+
* @param {Array<{item_id: string, variable_name: string}>} compatibility_list
|
|
61
|
+
* @param {Map<string, number>} weight_by_item_id
|
|
62
|
+
*/
|
|
63
|
+
function append_slot_item_weights(variable_list, compatibility_list, weight_by_item_id)
|
|
64
|
+
{
|
|
65
|
+
for (const entry of compatibility_list)
|
|
66
|
+
{
|
|
67
|
+
const item_weight = weight_by_item_id.get(entry.item_id) || 0
|
|
68
|
+
if (item_weight > 0)
|
|
25
69
|
{
|
|
26
|
-
variable_list.push(create_variable(
|
|
70
|
+
variable_list.push(create_variable(entry.variable_name, item_weight, VariableType.INTEGER))
|
|
27
71
|
}
|
|
28
72
|
}
|
|
73
|
+
}
|
|
29
74
|
|
|
30
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param {object} container
|
|
77
|
+
* @returns {number}
|
|
78
|
+
*/
|
|
79
|
+
function read_supported_weight(container)
|
|
80
|
+
{
|
|
81
|
+
if (typeof container.supported_weight === 'function')
|
|
31
82
|
{
|
|
32
|
-
return
|
|
83
|
+
return container.supported_weight()
|
|
33
84
|
}
|
|
34
|
-
|
|
35
|
-
const supported_weight = typeof container.supported_weight === 'function'
|
|
36
|
-
? container.supported_weight()
|
|
37
|
-
: container.supported_weight
|
|
38
|
-
|
|
39
|
-
const name = 'container_supported_weight'
|
|
40
|
-
const restriction = create_restriction(name, variable_list, {lower_limit: 0, upper_limit: supported_weight})
|
|
41
|
-
return [restriction]
|
|
85
|
+
return container.supported_weight
|
|
42
86
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const Block_Filter_Interface = require('../../space_and_block_chooser/blocks_filters/Block_Filter_Interface')
|
|
2
|
+
const Cargo_Profile = require('../../models/Cargo_Profile')
|
|
3
|
+
const filter_out_mixed_height_tie_ratio = require('./filter_out_mixed_height_tie_ratio')
|
|
4
|
+
const has_stocked_same_height_tie_ratio = require('./has_stocked_same_height_tie_ratio')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Post-selection filter: prefer same-height tie-ratio over mixed-height when stock allows.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = class Same_Height_Tie_Ratio_Filter extends Block_Filter_Interface
|
|
10
|
+
{
|
|
11
|
+
static run(filter_data)
|
|
12
|
+
{
|
|
13
|
+
if (!Cargo_Profile.prefer_same_height_tie_ratio_slots())
|
|
14
|
+
{
|
|
15
|
+
return filter_data.block_list
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!has_stocked_same_height_tie_ratio(
|
|
19
|
+
filter_data.block_list,
|
|
20
|
+
filter_data.item_list || []
|
|
21
|
+
))
|
|
22
|
+
{
|
|
23
|
+
return filter_data.block_list
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return filter_out_mixed_height_tie_ratio(filter_data.block_list)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const is_tie_ratio_block = require('./is_tie_ratio_block')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drops mixed-height tie-ratio blocks; keeps same-height tie-ratio and non-tie-ratio.
|
|
5
|
+
* @param {Array<object>} block_list
|
|
6
|
+
* @returns {Array<object>}
|
|
7
|
+
*/
|
|
8
|
+
function filter_out_mixed_height_tie_ratio(block_list)
|
|
9
|
+
{
|
|
10
|
+
return block_list.filter((block) =>
|
|
11
|
+
{
|
|
12
|
+
if (!is_tie_ratio_block(block))
|
|
13
|
+
{
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
return block.is_slots_of_same_measure()
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = filter_out_mixed_height_tie_ratio
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const is_tie_ratio_block = require('./is_tie_ratio_block')
|
|
2
|
+
const max_tie_ratio_slot_count = require('./max_tie_ratio_slot_count')
|
|
3
|
+
const tie_ratio_block_has_shape_stock = require('./tie_ratio_block_has_shape_stock')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* True when a same-height tie-ratio at the max slot count has enough shape stock.
|
|
7
|
+
* Smaller same-height partials must not hide larger mixed walls.
|
|
8
|
+
* @param {Array<object>} block_list
|
|
9
|
+
* @param {Array<object>} item_list
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
function has_stocked_same_height_tie_ratio(block_list, item_list)
|
|
13
|
+
{
|
|
14
|
+
const target_slot_count = max_tie_ratio_slot_count(block_list)
|
|
15
|
+
|
|
16
|
+
if (target_slot_count === 0)
|
|
17
|
+
{
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const block of block_list)
|
|
22
|
+
{
|
|
23
|
+
if (!is_tie_ratio_block(block) || !block.is_slots_of_same_measure())
|
|
24
|
+
{
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
if (block.get_slot_list().length !== target_slot_count)
|
|
28
|
+
{
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
if (tie_ratio_block_has_shape_stock(block, item_list))
|
|
32
|
+
{
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = has_stocked_same_height_tie_ratio
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when the block reports itself as a tie-ratio block.
|
|
3
|
+
* Probe objects without the method are not tie-ratio blocks.
|
|
4
|
+
* @param {object} block
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
function is_tie_ratio_block(block)
|
|
8
|
+
{
|
|
9
|
+
return typeof block.is_tie_ratio_block === 'function' && block.is_tie_ratio_block()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = is_tie_ratio_block
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const is_tie_ratio_block = require('./is_tie_ratio_block')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the largest slot count among tie-ratio blocks in the list.
|
|
5
|
+
* @param {Array<object>} block_list
|
|
6
|
+
* @returns {number}
|
|
7
|
+
*/
|
|
8
|
+
function max_tie_ratio_slot_count(block_list)
|
|
9
|
+
{
|
|
10
|
+
let maximum = 0
|
|
11
|
+
|
|
12
|
+
for (const block of block_list)
|
|
13
|
+
{
|
|
14
|
+
if (!is_tie_ratio_block(block))
|
|
15
|
+
{
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
maximum = Math.max(maximum, block.get_slot_list().length)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return maximum
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = max_tie_ratio_slot_count
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { cube_map_function } = require('../../Blocks_Preparation/Cube_Map_Function')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds required shape amounts from a block's slots.
|
|
5
|
+
* @param {object} block
|
|
6
|
+
* @returns {Map<string, number>}
|
|
7
|
+
*/
|
|
8
|
+
function build_required_shapes(block)
|
|
9
|
+
{
|
|
10
|
+
const required_shapes = new Map()
|
|
11
|
+
for (const slot of block.get_slot_list())
|
|
12
|
+
{
|
|
13
|
+
const shape_hash = cube_map_function(slot)
|
|
14
|
+
required_shapes.set(shape_hash, (required_shapes.get(shape_hash) || 0) + 1)
|
|
15
|
+
}
|
|
16
|
+
return required_shapes
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds available shape amounts (amount counted once per SKU per unique rotation hash).
|
|
21
|
+
* @param {Array<object>} item_list
|
|
22
|
+
* @returns {Map<string, number>}
|
|
23
|
+
*/
|
|
24
|
+
function build_available_shapes(item_list)
|
|
25
|
+
{
|
|
26
|
+
const available_shapes = new Map()
|
|
27
|
+
for (const item of item_list)
|
|
28
|
+
{
|
|
29
|
+
const unique_shape_hashes = new Set()
|
|
30
|
+
for (const rotated_item of item.get_all_rotated_options())
|
|
31
|
+
{
|
|
32
|
+
unique_shape_hashes.add(cube_map_function(rotated_item))
|
|
33
|
+
}
|
|
34
|
+
const item_amount = item.get_amount()
|
|
35
|
+
for (const shape_hash of unique_shape_hashes)
|
|
36
|
+
{
|
|
37
|
+
available_shapes.set(
|
|
38
|
+
shape_hash,
|
|
39
|
+
(available_shapes.get(shape_hash) || 0) + item_amount
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return available_shapes
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Counts units that can form any required shape (once per SKU).
|
|
48
|
+
* @param {Map<string, number>} required_shapes
|
|
49
|
+
* @param {Array<object>} item_list
|
|
50
|
+
* @returns {number}
|
|
51
|
+
*/
|
|
52
|
+
function count_capable_item_amount(required_shapes, item_list)
|
|
53
|
+
{
|
|
54
|
+
let capable_amount = 0
|
|
55
|
+
|
|
56
|
+
for (const item of item_list)
|
|
57
|
+
{
|
|
58
|
+
const amount = item.get_amount()
|
|
59
|
+
if (amount <= 0)
|
|
60
|
+
{
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
const can_form = item.get_all_rotated_options().some((rotated_item) =>
|
|
64
|
+
required_shapes.has(cube_map_function(rotated_item)))
|
|
65
|
+
if (can_form)
|
|
66
|
+
{
|
|
67
|
+
capable_amount += amount
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return capable_amount
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* True when remaining item_list stock can fill the block.
|
|
76
|
+
* Combines total capable units with per-shape availability so rotation
|
|
77
|
+
* double-counting cannot keep a stale same-height five-slot alive.
|
|
78
|
+
* @param {object} block
|
|
79
|
+
* @param {Array<object>} item_list
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
function tie_ratio_block_has_shape_stock(block, item_list)
|
|
83
|
+
{
|
|
84
|
+
const required_shapes = build_required_shapes(block)
|
|
85
|
+
if (required_shapes.size === 0)
|
|
86
|
+
{
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let total_required = 0
|
|
91
|
+
for (const required_amount of required_shapes.values())
|
|
92
|
+
{
|
|
93
|
+
total_required += required_amount
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (count_capable_item_amount(required_shapes, item_list) < total_required)
|
|
97
|
+
{
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const available_shapes = build_available_shapes(item_list)
|
|
102
|
+
for (const [shape_hash, required_amount] of required_shapes)
|
|
103
|
+
{
|
|
104
|
+
if ((available_shapes.get(shape_hash) || 0) < required_amount)
|
|
105
|
+
{
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = tie_ratio_block_has_shape_stock
|
|
@@ -362,6 +362,21 @@ class Cargo_Profile
|
|
|
362
362
|
return str_to_bool(this.#instance[key], false)
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Prefer same-height tie-ratio blocks over mixed-height when both fit.
|
|
367
|
+
* Default true when omitted.
|
|
368
|
+
* @returns {boolean}
|
|
369
|
+
*/
|
|
370
|
+
static prefer_same_height_tie_ratio_slots()
|
|
371
|
+
{
|
|
372
|
+
if (Cargo_Profile.#instance === null)
|
|
373
|
+
{
|
|
374
|
+
return true
|
|
375
|
+
}
|
|
376
|
+
const key = 'allocation.blocks.prefer_same_height_tie_ratio_slots'
|
|
377
|
+
return str_to_bool(this.#instance[key], true)
|
|
378
|
+
}
|
|
379
|
+
|
|
365
380
|
static tie_ratio_allocate_remainder_as_partial_blocks()
|
|
366
381
|
{
|
|
367
382
|
if (Cargo_Profile.#instance === null)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const Blocks_Bigger_Than_Space_Filter = require('./Blocks_Bigger_Than_Space_Filter')
|
|
2
2
|
const Tie_Ratio_Orientation_Filter = require('../../../Allocation_Optimization/tie_ratio/Tie_Ratio_Orientation_Filter')
|
|
3
3
|
const Tie_Ratio_Vertical_Stack_Filter = require('../../../Allocation_Optimization/tie_ratio/Tie_Ratio_Vertical_Stack_Filter')
|
|
4
|
+
const Same_Height_Tie_Ratio_Filter = require('../../../Allocation_Optimization/tie_ratio/Same_Height_Tie_Ratio_Filter')
|
|
4
5
|
|
|
5
6
|
module.exports = [
|
|
6
7
|
Blocks_Bigger_Than_Space_Filter,
|
|
7
8
|
Tie_Ratio_Orientation_Filter,
|
|
8
|
-
Tie_Ratio_Vertical_Stack_Filter
|
|
9
|
-
|
|
9
|
+
Tie_Ratio_Vertical_Stack_Filter,
|
|
10
|
+
Same_Height_Tie_Ratio_Filter
|
|
11
|
+
]
|