@jetta/blocks-allocator 0.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetta/blocks-allocator",
3
- "version": "0.1.0",
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-3",
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 a single LP restriction to ensure the total weight of items in the chosen blocks
9
- * does not exceed the container's supported weight.
10
- * @param {object} container - The container object, with a supported_weight() method.
11
- * @param {object[]} block_list - An array of Block objects.
12
- * @param {object[]} item_list - An array of Item objects.
13
- * @returns {Restriction[]} An array containing the single weight restriction.
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 dimension_to_max_weight_map = get_dimension_to_max_weight_map(item_list)
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
- for (const block of block_list)
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
- const block_potential_weight = calculate_block_potential_weight(block, dimension_to_max_weight_map)
53
+ weight_by_item_id.set(item.get_id(), item.get_weight())
54
+ }
55
+ return weight_by_item_id
56
+ }
23
57
 
24
- if (block_potential_weight > 0)
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(block.get_id(), block_potential_weight, VariableType.INTEGER))
70
+ variable_list.push(create_variable(entry.variable_name, item_weight, VariableType.INTEGER))
27
71
  }
28
72
  }
73
+ }
29
74
 
30
- if (variable_list.length === 0)
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
+ ]