@jetta/blocks-allocator 0.2.1-1 → 0.2.1-3

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 (19) hide show
  1. package/package.json +1 -1
  2. package/src/Allocation_Optimization/item_consumption_heuristic/Item_Chooser.js +6 -138
  3. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/axle_load_exceeds_limit.js +27 -16
  4. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/candidate_item_axle_score.js +42 -0
  5. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/evaluate_layout_axle_load.js +28 -8
  6. package/src/Allocation_Optimization/item_consumption_heuristic/axle_load/layout_fits_axle_load.js +3 -2
  7. package/src/Allocation_Optimization/item_consumption_heuristic/select_axle_balanced_slot_item.js +143 -0
  8. package/src/Allocation_Optimization/item_consumption_heuristic/select_slot_item.js +149 -0
  9. package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_acceptance.js +110 -0
  10. package/src/Allocation_Optimization/item_consumption_heuristic/slot_candidate_order.js +49 -0
  11. package/src/allocation_flow/leftover_catalog_retry.js +67 -0
  12. package/src/allocation_flow/run_allocation_iteration.js +4 -25
  13. package/src/models/transport/Container.js +11 -0
  14. package/src/space_and_block_chooser/compound_first_with_leftover_fill.js +2 -2
  15. package/src/space_and_block_chooser/leftover_sku_hashes.js +159 -0
  16. package/src/space_and_block_chooser/leftover_space_memory.js +21 -5
  17. package/src/space_and_block_chooser/leftover_unitary_admission.js +89 -0
  18. package/src/space_and_block_chooser/leftover_unitary_blocks.js +73 -98
  19. package/src/space_and_block_chooser/leftover_unitary_empty_spaces.js +2 -2
@@ -0,0 +1,110 @@
1
+ const Cargo_Profile = require('../../models/Cargo_Profile')
2
+ const Allocated_Item = require('../../models/Allocated_Item')
3
+ const candidate_item_fits_axle_load = require('./axle_load/candidate_item_fits_axle_load')
4
+ const candidate_item_axle_score = require('./axle_load/candidate_item_axle_score')
5
+
6
+ /**
7
+ * Builds the allocated item at the slot position.
8
+ * @param {object} allocation_item
9
+ * @param {object} item_position
10
+ * @returns {object}
11
+ */
12
+ function build_allocated_item(allocation_item, item_position)
13
+ {
14
+ return new Allocated_Item(allocation_item, item_position.x, item_position.y, item_position.z)
15
+ }
16
+
17
+ /**
18
+ * Accepts the first legal candidate and commits stacking.
19
+ * @param {object} data
20
+ * @param {object} container
21
+ * @param {object} allocation_item
22
+ * @param {object} item_position
23
+ * @returns {object|undefined}
24
+ */
25
+ function try_accept_candidate(data, container, allocation_item, item_position)
26
+ {
27
+ const allocated_item = build_allocated_item(allocation_item, item_position)
28
+ if (!candidate_item_fits_axle_load(data, allocated_item))
29
+ {
30
+ return undefined
31
+ }
32
+ if (should_defer_loose_on_block_stacking_validation(data.block))
33
+ {
34
+ return allocated_item
35
+ }
36
+ if (!container.is_a_valid_stacking(allocated_item))
37
+ {
38
+ return undefined
39
+ }
40
+ return allocated_item
41
+ }
42
+
43
+ /**
44
+ * Scores a candidate without writing the stacking index.
45
+ * @param {object} data
46
+ * @param {object} context
47
+ * @param {object} allocation_item
48
+ * @returns {{ allocated_item: object, ratio: number, allocation_item: object }|undefined}
49
+ */
50
+ function score_without_commit(data, context, allocation_item)
51
+ {
52
+ const allocated_item = build_allocated_item(allocation_item, context.item_position)
53
+ const score = candidate_item_axle_score(data, allocated_item)
54
+ if (!score.fits || !stacking_probe_allows(data, context.container, allocated_item))
55
+ {
56
+ return undefined
57
+ }
58
+ return { allocated_item, ratio: score.ratio, allocation_item }
59
+ }
60
+
61
+ /**
62
+ * Commits the chosen candidate into the live stacking index.
63
+ * @param {object} data
64
+ * @param {object} container
65
+ * @param {{ allocated_item: object }} scored
66
+ * @returns {object|undefined}
67
+ */
68
+ function commit_scored_candidate(data, container, scored)
69
+ {
70
+ if (should_defer_loose_on_block_stacking_validation(data.block))
71
+ {
72
+ return scored.allocated_item
73
+ }
74
+ if (!container.is_a_valid_stacking(scored.allocated_item))
75
+ {
76
+ return undefined
77
+ }
78
+ return scored.allocated_item
79
+ }
80
+
81
+ /**
82
+ * @param {object} data
83
+ * @param {object} container
84
+ * @param {object} allocated_item
85
+ * @returns {boolean}
86
+ */
87
+ function stacking_probe_allows(data, container, allocated_item)
88
+ {
89
+ if (should_defer_loose_on_block_stacking_validation(data.block))
90
+ {
91
+ return true
92
+ }
93
+ if (typeof container.probe_stacking === 'function')
94
+ {
95
+ return container.probe_stacking(allocated_item)
96
+ }
97
+ return container.is_a_valid_stacking(allocated_item)
98
+ }
99
+
100
+ function should_defer_loose_on_block_stacking_validation(block)
101
+ {
102
+ return Cargo_Profile.stack_loose_on_tie_ratio_blocks()
103
+ && block?.get_slot_list?.().length === 1
104
+ }
105
+
106
+ module.exports = {
107
+ try_accept_candidate,
108
+ score_without_commit,
109
+ commit_scored_candidate
110
+ }
@@ -0,0 +1,49 @@
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
+ const { order_items_by_capacity_bias } = require('./Capacity_Bias_Item_Order')
4
+
5
+ /**
6
+ * Removes one candidate from the working list.
7
+ * @param {object[]} candidate_item_list
8
+ * @param {object} allocation_item
9
+ * @returns {void}
10
+ */
11
+ function remove_item_from_candidate_list(candidate_item_list, allocation_item)
12
+ {
13
+ const index = candidate_item_list.indexOf(allocation_item)
14
+ if (index > -1)
15
+ {
16
+ candidate_item_list.splice(index, 1)
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Next candidate in the existing capacity-bias order.
22
+ * @param {object[]} item_list
23
+ * @param {number|undefined} qty_of_slots
24
+ * @param {object} container
25
+ * @returns {object}
26
+ */
27
+ function get_item(item_list, qty_of_slots, container)
28
+ {
29
+ if (qty_of_slots === undefined)
30
+ {
31
+ throw new No_Slots_For_Items_Exception()
32
+ }
33
+ if (item_list.length === 0)
34
+ {
35
+ throw new No_Available_Items_Exception()
36
+ }
37
+ const ordered_item_list = order_items_by_capacity_bias(item_list, container)
38
+ const preferable_item_list = ordered_item_list.filter(item => item.get_amount() >= qty_of_slots)
39
+ if (preferable_item_list.length > 0)
40
+ {
41
+ return preferable_item_list[0]
42
+ }
43
+ return ordered_item_list[0]
44
+ }
45
+
46
+ module.exports = {
47
+ get_item,
48
+ remove_item_from_candidate_list
49
+ }
@@ -0,0 +1,67 @@
1
+ const leftover_unitary_blocks = require('../space_and_block_chooser/leftover_unitary_blocks')
2
+
3
+ /**
4
+ * Records a stall, unless this is the one-shot retry that admits hidden unitaries.
5
+ * @param {object} context
6
+ * @param {object[]} effective_item_list
7
+ * @param {Record<string, number>} amounts_before
8
+ * @param {object} empty_space
9
+ * @param {string} fingerprint
10
+ * @param {object[]} block_list
11
+ * @returns {boolean} True when any effective item amount decreased.
12
+ */
13
+ function update_skip_state_after_attempt(
14
+ context,
15
+ effective_item_list,
16
+ amounts_before,
17
+ empty_space,
18
+ fingerprint,
19
+ block_list
20
+ )
21
+ {
22
+ if (amount_decreased(effective_item_list, amounts_before))
23
+ {
24
+ context.space_skip_state.clear_stalls()
25
+ context.recovery_state.count = 0
26
+ return true
27
+ }
28
+ if (defer_stall_for_hidden_unitaries(context, block_list))
29
+ {
30
+ return false
31
+ }
32
+ context.space_skip_state.record_stall(empty_space, fingerprint)
33
+ return false
34
+ }
35
+
36
+ /**
37
+ * @param {object[]} effective_item_list
38
+ * @param {Record<string, number>} amounts_before
39
+ * @returns {boolean}
40
+ */
41
+ function amount_decreased(effective_item_list, amounts_before)
42
+ {
43
+ return effective_item_list.some(item => item.get_amount() < amounts_before[item.get_id()])
44
+ }
45
+
46
+ /**
47
+ * Opens the leftover catalog once when a no-progress attempt used only remembered unitaries.
48
+ * @param {object} context
49
+ * @param {object[]} block_list
50
+ * @returns {boolean} True when the space must be retried before it is stalled.
51
+ */
52
+ function defer_stall_for_hidden_unitaries(context, block_list)
53
+ {
54
+ const session = context.chooser_session?.obligate_compound_session
55
+ const remembered_only = leftover_unitary_blocks.blocks_are_remembered_unitaries_only(
56
+ block_list ?? [],
57
+ session
58
+ )
59
+ if (!remembered_only)
60
+ {
61
+ return false
62
+ }
63
+ session.admit_non_remembered_unitaries = true
64
+ return true
65
+ }
66
+
67
+ module.exports = { update_skip_state_after_attempt }
@@ -9,6 +9,7 @@ const {
9
9
  update_item_continuation_lock
10
10
  } = require('./item_continuation_lock_gate')
11
11
  const { empty_space_stacks_on_tie_ratio_supporter } = require('../Allocation_Optimization/tie_ratio/tie_ratio_orientation_below_empty_space')
12
+ const { update_skip_state_after_attempt } = require('./leftover_catalog_retry')
12
13
 
13
14
  /**
14
15
  * Runs one allocation iteration: chooser, LP attempt, skip-state update.
@@ -69,7 +70,8 @@ async function run_allocation_iteration(context, container, locked_item)
69
70
  effective_item_list,
70
71
  amounts_before,
71
72
  empty_space,
72
- fingerprint
73
+ fingerprint,
74
+ block_list
73
75
  )
74
76
  update_item_continuation_lock(context, wall_output.single_slot_allocated_item_ids)
75
77
  context.picking_continuity.update_from_wall(wall_output.last_allocated_item_id)
@@ -117,29 +119,6 @@ function update_compound_probe_session(context, block_list, effective_item_list,
117
119
  }
118
120
  }
119
121
 
120
- /**
121
- * @param {object} context
122
- * @param {object[]} effective_item_list
123
- * @param {Record<string, number>} amounts_before
124
- * @param {object} empty_space
125
- * @param {string} fingerprint
126
- * @returns {boolean} True when any effective item amount decreased.
127
- */
128
- function update_skip_state_after_attempt(context, effective_item_list, amounts_before, empty_space, fingerprint)
129
- {
130
- const progressed = effective_item_list.some(
131
- item => item.get_amount() < amounts_before[item.get_id()]
132
- )
133
- if (progressed)
134
- {
135
- context.space_skip_state.clear_stalls()
136
- context.recovery_state.count = 0
137
- return true
138
- }
139
- context.space_skip_state.record_stall(empty_space, fingerprint)
140
- return false
141
- }
142
-
143
122
  /**
144
123
  * @param {object[]} block_list
145
124
  * @returns {void}
@@ -152,4 +131,4 @@ function assert_blocks_available(block_list)
152
131
  }
153
132
  }
154
133
 
155
- module.exports = { run_allocation_iteration }
134
+ module.exports = { run_allocation_iteration, update_skip_state_after_attempt }
@@ -87,6 +87,17 @@ class Container extends Space
87
87
  return this.#stacking_feasibility_checker.validate_stackings(item_data)
88
88
  }
89
89
 
90
+ /**
91
+ * Checks stacking on a clone so the live index stays unchanged.
92
+ * @param {object} item
93
+ * @returns {boolean}
94
+ */
95
+ probe_stacking(item)
96
+ {
97
+ const probe = this.#stacking_feasibility_checker.clone()
98
+ return probe.validate_stackings(create_input(item))
99
+ }
100
+
90
101
  /**
91
102
  * Drops provisional stacking commits left by failed slot/wall attempts.
92
103
  * Restores the pre-attempt checkpoint when stacking was touched; otherwise a no-op.
@@ -24,10 +24,10 @@ async function resolve_after_compound_probe(
24
24
  )
25
25
  {
26
26
  const compound_block_list = compound_blocks_from_list(pre_selected_block_list)
27
- const leftover_block_list = leftover_unitary_blocks(
27
+ const leftover_block_list = leftover_unitary_blocks.from_session(
28
28
  pre_selected_block_list,
29
29
  compound_block_list,
30
- filter_data.obligate_compound_session?.leftover_sku_shape_hashes
30
+ filter_data.obligate_compound_session
31
31
  )
32
32
  leftover_unitary_blocks.remember_leftover_sku_hashes(
33
33
  filter_data.obligate_compound_session,
@@ -0,0 +1,159 @@
1
+ const { cube_map_function } = require('../Blocks_Preparation/Cube_Map_Function')
2
+ const round = require('../tools/Rounder')
3
+
4
+ /**
5
+ * Stores leftover SKU shape hashes while compounds still remain.
6
+ * @param {{ leftover_sku_shape_hashes?: string[] }|undefined} session
7
+ * @param {object[]} leftover_block_list
8
+ * @param {object[]} compound_block_list
9
+ * @returns {void}
10
+ */
11
+ function remember_leftover_sku_hashes(session, leftover_block_list, compound_block_list)
12
+ {
13
+ if (!session || compound_block_list.length === 0 || leftover_block_list.length === 0)
14
+ {
15
+ return
16
+ }
17
+ if (session.leftover_sku_shape_hashes?.length > 0)
18
+ {
19
+ return
20
+ }
21
+ session.leftover_sku_shape_hashes = [...leftover_sku_shape_hashes(leftover_block_list)]
22
+ }
23
+
24
+ /**
25
+ * Width/height/length hashes for leftover 1-slot blocks, including swapped floor axes.
26
+ * @param {object[]} leftover_block_list
27
+ * @returns {Set<string>}
28
+ */
29
+ function leftover_sku_shape_hashes(leftover_block_list)
30
+ {
31
+ const hashes = new Set()
32
+ for (const block of leftover_block_list)
33
+ {
34
+ add_slot_orientation_hashes(hashes, block)
35
+ }
36
+ return hashes
37
+ }
38
+
39
+ /**
40
+ * @param {Set<string>} hashes
41
+ * @param {object} block
42
+ * @returns {void}
43
+ */
44
+ function add_slot_orientation_hashes(hashes, block)
45
+ {
46
+ if (typeof block.get_slot_list !== 'function')
47
+ {
48
+ return
49
+ }
50
+ const slot_list = block.get_slot_list()
51
+ if (slot_list.length !== 1 || typeof slot_list[0].get_width !== 'function')
52
+ {
53
+ return
54
+ }
55
+ hashes.add(cube_map_function(slot_list[0]))
56
+ hashes.add(swapped_length_width_hash(slot_list[0]))
57
+ }
58
+
59
+ /**
60
+ * @param {object} slot
61
+ * @returns {string}
62
+ */
63
+ function swapped_length_width_hash(slot)
64
+ {
65
+ return round(slot.get_length()) + ';' + round(slot.get_height()) + ';' + round(slot.get_width())
66
+ }
67
+
68
+ /**
69
+ * @param {Set<string>|string[]|undefined} remembered_sku_hashes
70
+ * @returns {Set<string>}
71
+ */
72
+ function to_hash_set(remembered_sku_hashes)
73
+ {
74
+ if (!remembered_sku_hashes)
75
+ {
76
+ return new Set()
77
+ }
78
+ if (remembered_sku_hashes instanceof Set)
79
+ {
80
+ return remembered_sku_hashes
81
+ }
82
+ return new Set(remembered_sku_hashes)
83
+ }
84
+
85
+ /**
86
+ * @param {object[]} compound_block_list
87
+ * @returns {Set<string>}
88
+ */
89
+ function compound_slot_shape_hashes(compound_block_list)
90
+ {
91
+ const hashes = new Set()
92
+ for (const block of compound_block_list)
93
+ {
94
+ add_compound_slot_hashes(hashes, block)
95
+ }
96
+ return hashes
97
+ }
98
+
99
+ /**
100
+ * @param {Set<string>} hashes
101
+ * @param {object} block
102
+ * @returns {void}
103
+ */
104
+ function add_compound_slot_hashes(hashes, block)
105
+ {
106
+ if (typeof block.get_slot_list !== 'function')
107
+ {
108
+ return
109
+ }
110
+ for (const slot of block.get_slot_list())
111
+ {
112
+ add_measured_slot_hash(hashes, slot)
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @param {Set<string>} hashes
118
+ * @param {object} slot
119
+ * @returns {void}
120
+ */
121
+ function add_measured_slot_hash(hashes, slot)
122
+ {
123
+ if (typeof slot.get_width !== 'function')
124
+ {
125
+ return
126
+ }
127
+ hashes.add(cube_map_function(slot))
128
+ hashes.add(swapped_length_width_hash(slot))
129
+ }
130
+
131
+ /**
132
+ * @param {object} block
133
+ * @param {Set<string>} remembered
134
+ * @returns {boolean}
135
+ */
136
+ function is_unitary_matching_hashes(block, remembered)
137
+ {
138
+ if (typeof block.get_slot_list !== 'function')
139
+ {
140
+ return false
141
+ }
142
+ const slot_list = block.get_slot_list()
143
+ if (slot_list.length !== 1 || typeof slot_list[0].get_width !== 'function')
144
+ {
145
+ return false
146
+ }
147
+ return remembered.has(cube_map_function(slot_list[0]))
148
+ || remembered.has(swapped_length_width_hash(slot_list[0]))
149
+ }
150
+
151
+ module.exports = {
152
+ remember_leftover_sku_hashes,
153
+ leftover_sku_shape_hashes,
154
+ to_hash_set,
155
+ compound_slot_shape_hashes,
156
+ is_unitary_matching_hashes,
157
+ cube_map_function,
158
+ swapped_length_width_hash
159
+ }
@@ -56,12 +56,10 @@ function skip_probe_or_leftover_resolution(filter_data, empty_space_list, pre_se
56
56
  {
57
57
  const session = filter_data.obligate_compound_session
58
58
  const leftover_spaces = spaces_matching_remembered_leftover(empty_space_list, session)
59
- const leftover_block_list = leftover_unitary_blocks(
59
+ const leftover_block_list = leftover_unitary_blocks.from_session(
60
60
  pre_selected_block_list,
61
- pre_selected_block_list.filter(
62
- block => typeof block.get_slot_list === 'function' && block.get_slot_list().length > 1
63
- ),
64
- session?.leftover_sku_shape_hashes
61
+ compound_blocks(pre_selected_block_list),
62
+ session
65
63
  )
66
64
  if (leftover_spaces.length > 0 && leftover_block_list.length > 0)
67
65
  {
@@ -75,6 +73,24 @@ function skip_probe_or_leftover_resolution(filter_data, empty_space_list, pre_se
75
73
  return skip_probe_resolution(empty_space_list, pre_selected_block_list)
76
74
  }
77
75
 
76
+ /**
77
+ * @param {object[]} block_list
78
+ * @returns {object[]}
79
+ */
80
+ function compound_blocks(block_list)
81
+ {
82
+ return block_list.filter(is_compound_block)
83
+ }
84
+
85
+ /**
86
+ * @param {object} block
87
+ * @returns {boolean}
88
+ */
89
+ function is_compound_block(block)
90
+ {
91
+ return typeof block.get_slot_list === 'function' && block.get_slot_list().length > 1
92
+ }
93
+
78
94
  function space_bbox(space)
79
95
  {
80
96
  if (typeof space.get_x !== 'function')
@@ -0,0 +1,89 @@
1
+ const {
2
+ to_hash_set,
3
+ is_unitary_matching_hashes
4
+ } = require('./leftover_sku_hashes')
5
+
6
+ /**
7
+ * 1-slot blocks whose shape is outside the remembered leftover allow-list.
8
+ * @param {object[]} pre_selected_block_list
9
+ * @param {Set<string>|string[]} [remembered_sku_hashes]
10
+ * @returns {object[]}
11
+ */
12
+ function unitaries_outside_remembered_hashes(pre_selected_block_list, remembered_sku_hashes)
13
+ {
14
+ const remembered = to_hash_set(remembered_sku_hashes)
15
+ return pre_selected_block_list.filter(block => is_hidden_unitary(block, remembered))
16
+ }
17
+
18
+ /**
19
+ * Remembered matches plus every other 1-slot block, in pre-selected order.
20
+ * @param {object[]} pre_selected_block_list
21
+ * @param {object[]} remembered_matches
22
+ * @param {Set<string>|string[]|undefined} remembered_sku_hashes
23
+ * @returns {object[]}
24
+ */
25
+ function unite_with_hidden_unitaries(pre_selected_block_list, remembered_matches, remembered_sku_hashes)
26
+ {
27
+ const hidden = new Set(
28
+ unitaries_outside_remembered_hashes(pre_selected_block_list, remembered_sku_hashes)
29
+ )
30
+ const remembered = new Set(remembered_matches)
31
+ return pre_selected_block_list.filter(block => remembered.has(block) || hidden.has(block))
32
+ }
33
+
34
+ /**
35
+ * True when every block is a remembered 1-slot and the one-shot release has not run.
36
+ * @param {object[]} block_list
37
+ * @param {{ leftover_sku_shape_hashes?: string[], admit_non_remembered_unitaries?: boolean }|undefined} session
38
+ * @returns {boolean}
39
+ */
40
+ function blocks_are_remembered_unitaries_only(block_list, session)
41
+ {
42
+ if (!session || session.admit_non_remembered_unitaries === true || block_list.length === 0)
43
+ {
44
+ return false
45
+ }
46
+ const remembered = to_hash_set(session.leftover_sku_shape_hashes)
47
+ if (remembered.size === 0)
48
+ {
49
+ return false
50
+ }
51
+ return block_list.every(block => is_unitary_matching_hashes(block, remembered))
52
+ }
53
+
54
+ /**
55
+ * @param {object} block
56
+ * @param {Set<string>} remembered
57
+ * @returns {boolean}
58
+ */
59
+ function is_hidden_unitary(block, remembered)
60
+ {
61
+ if (!is_single_slot_block(block))
62
+ {
63
+ return false
64
+ }
65
+ if (remembered.size === 0)
66
+ {
67
+ return true
68
+ }
69
+ return !is_unitary_matching_hashes(block, remembered)
70
+ }
71
+
72
+ /**
73
+ * @param {object} block
74
+ * @returns {boolean}
75
+ */
76
+ function is_single_slot_block(block)
77
+ {
78
+ if (typeof block.get_slot_list !== 'function')
79
+ {
80
+ return false
81
+ }
82
+ return block.get_slot_list().length === 1
83
+ }
84
+
85
+ module.exports = {
86
+ unitaries_outside_remembered_hashes,
87
+ unite_with_hidden_unitaries,
88
+ blocks_are_remembered_unitaries_only
89
+ }