@jetta/cargo-stabilizer 0.1.0-11 → 0.1.0-14
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/CHANGELOG.md +2 -1
- package/README.md +2 -2
- package/build/core/adapters/coordinate_mapper.d.ts +1 -1
- package/build/core/adapters/coordinate_mapper.js +3 -3
- package/build/core/filters/collect_movable_items.d.ts +4 -4
- package/build/core/filters/collect_movable_items.d.ts.map +1 -1
- package/build/core/filters/collect_movable_items.js +9 -28
- package/build/core/filters/collect_pinned_floor_tie_ratio_group_ids.d.ts +8 -0
- package/build/core/filters/collect_pinned_floor_tie_ratio_group_ids.d.ts.map +1 -0
- package/build/core/filters/collect_pinned_floor_tie_ratio_group_ids.js +41 -0
- package/build/core/filters/is_item_on_cm_floor.d.ts +8 -0
- package/build/core/filters/is_item_on_cm_floor.d.ts.map +1 -0
- package/build/core/filters/is_item_on_cm_floor.js +9 -0
- package/build/core/geometry/has_penetrating_overlap.d.ts +2 -2
- package/build/core/geometry/has_penetrating_overlap.js +3 -3
- package/build/core/validation/contract/map_contract_to_wire_shape.js +3 -0
- package/build/core/validation/contract/map_wire_input_to_contract_shape.d.ts.map +1 -1
- package/build/core/validation/contract/map_wire_input_to_contract_shape.js +2 -1
- package/build/core/validation/contract/map_wire_item_to_contract.js +13 -5
- package/build/core/validation/contract/uniquify_contract_item_ids.d.ts +8 -0
- package/build/core/validation/contract/uniquify_contract_item_ids.d.ts.map +1 -0
- package/build/core/validation/contract/uniquify_contract_item_ids.js +38 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,13 +13,14 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
13
13
|
- Empty-space candidate discovery for floor and elevated slots via `@jetta/empty-spaces-manager`.
|
|
14
14
|
- Supporter-top candidate slots aligned to each stacked item footprint, included in beam expansion (`top_k ∪ supporter_top`).
|
|
15
15
|
- Elevated candidates must sit on a supporter `(x, z)` origin so stacked loose items copy the supporter footprint.
|
|
16
|
-
- `cargo_stabilizer.pin_tie_ratio_blocks` (default `false`): when true, 3_2
|
|
16
|
+
- `cargo_stabilizer.pin_tie_ratio_blocks` (default `false`): when true, only **floor five-slot** `3_2`/`2_3` walls stay pinned; stacked tie-ratio items and generic groups stay movable.
|
|
17
17
|
- Public contract exports: `parse_stabilizer_input`, `parse_stabilizer_output`, `safe_parse_stabilizer_input`, `safe_parse_stabilizer_output`, and related domain types.
|
|
18
18
|
|
|
19
19
|
### Changed
|
|
20
20
|
|
|
21
21
|
- README documents I/O contract mapping, configuration keys, dependencies, and test/coverage commands.
|
|
22
22
|
- Reorder objective is whole-cargo `transport_score`; loose-item score sum is only a tie-break. Axle, waypoint, and milkrun gates are unchanged.
|
|
23
|
+
- `pin_tie_ratio_blocks` pins only floor five-slot `3_2`/`2_3` walls; stacked members of the same group stay movable.
|
|
23
24
|
|
|
24
25
|
### Testing
|
|
25
26
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jetta/cargo-stabilizer
|
|
2
2
|
|
|
3
|
-
TypeScript library that reorders items to improve **whole-cargo transport stability**. By default every item is movable; set `cargo_stabilizer.pin_tie_ratio_blocks` to pin 3_2
|
|
3
|
+
TypeScript library that reorders items to improve **whole-cargo transport stability**. By default every item is movable; set `cargo_stabilizer.pin_tie_ratio_blocks` to pin **floor five-slot** `3_2`/`2_3` walls. Stacked tie-ratio pieces and generic groups stay movable one item at a time. The reorder objective is `transport_score` (loose-item score sum is a tie-break). Implements five config-selectable strategies with organization gates (waypoint order, milkrun wall reservation) and empty-space candidate discovery (JET-2262).
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -73,7 +73,7 @@ Exported parsers: `parse_stabilizer_input`, `parse_stabilizer_output`, `safe_par
|
|
|
73
73
|
| `cargo_stabilizer.transport_stability_floor` | `0` | Hard minimum transport stability score (gate, not the maximize objective) |
|
|
74
74
|
| `cargo_stabilizer.max_iteration` | `10` | Greedy / SA iteration budget |
|
|
75
75
|
| `cargo_stabilizer.preserve_tie_block_organization` | `true` | When true, enforce waypoint Z zones and macro delivery order |
|
|
76
|
-
| `cargo_stabilizer.pin_tie_ratio_blocks` | `false` | When true, do not move 3_2
|
|
76
|
+
| `cargo_stabilizer.pin_tie_ratio_blocks` | `false` | When true, do not move floor five-slot `3_2`/`2_3` walls; stacked tie-ratio items and other groups remain movable |
|
|
77
77
|
| `cargo_stabilizer.enforce_milkrun_wall_reservation` | `false` | When true, reserve rear compound-wall Z bands using profile key below |
|
|
78
78
|
| `number_of_wall_to_allocate_from_last_waypoints` | `0` | Count of trailing waypoints whose compound walls are reserved (read from `cargo_state.config.algoritmo` when milkrun enforcement is enabled) |
|
|
79
79
|
| `folga_de_peso_nos_eixos` | `0` | Axle folga percent |
|
|
@@ -15,7 +15,7 @@ export interface StabilityContainerPayload {
|
|
|
15
15
|
item_list: StabilityItemPayload[];
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Maps contract
|
|
18
|
+
* Maps contract coordinates onto cargo-stability axes (y = height, z = length).
|
|
19
19
|
* @param item - Stabilizer item record.
|
|
20
20
|
* @returns Stability item payload entry.
|
|
21
21
|
*/
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { item_to_box } from '../geometry/box_geometry.js';
|
|
2
2
|
/**
|
|
3
|
-
* Maps contract
|
|
3
|
+
* Maps contract coordinates onto cargo-stability axes (y = height, z = length).
|
|
4
4
|
* @param item - Stabilizer item record.
|
|
5
5
|
* @returns Stability item payload entry.
|
|
6
6
|
*/
|
|
7
7
|
export function map_item_to_stability(item) {
|
|
8
8
|
return {
|
|
9
9
|
x: item.x,
|
|
10
|
-
y: item.
|
|
11
|
-
z: item.
|
|
10
|
+
y: item.y,
|
|
11
|
+
z: item.z,
|
|
12
12
|
w: item.width,
|
|
13
13
|
h: item.height,
|
|
14
14
|
l: item.depth
|
|
@@ -3,7 +3,7 @@ import type { LooseItemRef } from './loose_item_filter.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Collects items the stabilizer may move.
|
|
5
5
|
* @param containers - Container list from env.
|
|
6
|
-
* @param pin_tie_ratio_blocks - When true, skip 3_2/2_3
|
|
6
|
+
* @param pin_tie_ratio_blocks - When true, skip floor five-slot 3_2/2_3 walls.
|
|
7
7
|
* @returns Movable item references with container index.
|
|
8
8
|
*/
|
|
9
9
|
export declare function collect_movable_items(containers: {
|
|
@@ -11,8 +11,8 @@ export declare function collect_movable_items(containers: {
|
|
|
11
11
|
}[], pin_tie_ratio_blocks: boolean): LooseItemRef[];
|
|
12
12
|
/**
|
|
13
13
|
* @param item - Item record.
|
|
14
|
-
* @param
|
|
15
|
-
* @returns True when the item
|
|
14
|
+
* @param pinned_group_ids - Floor five-slot 3_2/2_3 group ids.
|
|
15
|
+
* @returns True when the item is a floor member of a pinned wall.
|
|
16
16
|
*/
|
|
17
|
-
export declare function is_tie_ratio_block_item(item: StabilizerItem,
|
|
17
|
+
export declare function is_tie_ratio_block_item(item: StabilizerItem, pinned_group_ids: Set<string>): boolean;
|
|
18
18
|
//# sourceMappingURL=collect_movable_items.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect_movable_items.d.ts","sourceRoot":"","sources":["../../../src/core/filters/collect_movable_items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;
|
|
1
|
+
{"version":3,"file":"collect_movable_items.d.ts","sourceRoot":"","sources":["../../../src/core/filters/collect_movable_items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAI1D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,UAAU,EAAE;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,EAAE,EACzC,oBAAoB,EAAE,OAAO,GAC9B,YAAY,EAAE,CAMhB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACnC,IAAI,EAAE,cAAc,EACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC9B,OAAO,CAOT"}
|
|
@@ -1,38 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collect_pinned_floor_tie_ratio_group_ids } from './collect_pinned_floor_tie_ratio_group_ids.js';
|
|
2
|
+
import { is_item_on_cm_floor } from './is_item_on_cm_floor.js';
|
|
2
3
|
/**
|
|
3
4
|
* Collects items the stabilizer may move.
|
|
4
5
|
* @param containers - Container list from env.
|
|
5
|
-
* @param pin_tie_ratio_blocks - When true, skip 3_2/2_3
|
|
6
|
+
* @param pin_tie_ratio_blocks - When true, skip floor five-slot 3_2/2_3 walls.
|
|
6
7
|
* @returns Movable item references with container index.
|
|
7
8
|
*/
|
|
8
9
|
export function collect_movable_items(containers, pin_tie_ratio_blocks) {
|
|
9
10
|
const pinned_groups = pin_tie_ratio_blocks
|
|
10
|
-
?
|
|
11
|
+
? collect_pinned_floor_tie_ratio_group_ids(flatten_container_items(containers))
|
|
11
12
|
: new Set();
|
|
12
13
|
return collect_items_except_pinned(containers, pinned_groups);
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* @param item - Item record.
|
|
16
|
-
* @param
|
|
17
|
-
* @returns True when the item
|
|
17
|
+
* @param pinned_group_ids - Floor five-slot 3_2/2_3 group ids.
|
|
18
|
+
* @returns True when the item is a floor member of a pinned wall.
|
|
18
19
|
*/
|
|
19
|
-
export function is_tie_ratio_block_item(item,
|
|
20
|
-
if (item.group_id == null) {
|
|
20
|
+
export function is_tie_ratio_block_item(item, pinned_group_ids) {
|
|
21
|
+
if (item.group_id == null || !is_item_on_cm_floor(item)) {
|
|
21
22
|
return false;
|
|
22
23
|
}
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
function collect_tie_ratio_group_ids(items) {
|
|
26
|
-
const group_ids = new Set();
|
|
27
|
-
for (const item of items) {
|
|
28
|
-
if (item.group_id == null) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (is_tie_ratio_orientation(read_tie_ratio_orientation(item))) {
|
|
32
|
-
group_ids.add(item.group_id);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return group_ids;
|
|
24
|
+
return pinned_group_ids.has(item.group_id);
|
|
36
25
|
}
|
|
37
26
|
function collect_items_except_pinned(containers, pinned_groups) {
|
|
38
27
|
const movable = [];
|
|
@@ -62,11 +51,3 @@ function flatten_container_items(containers) {
|
|
|
62
51
|
}
|
|
63
52
|
return items;
|
|
64
53
|
}
|
|
65
|
-
function read_tie_ratio_orientation(item) {
|
|
66
|
-
const orientation = item.attributes?.tie_ratio_orientation;
|
|
67
|
-
return typeof orientation === 'string' ? orientation : undefined;
|
|
68
|
-
}
|
|
69
|
-
function is_tie_ratio_orientation(orientation) {
|
|
70
|
-
return orientation === TIE_RATIO_ORIENTATION.THREE_TWO
|
|
71
|
-
|| orientation === TIE_RATIO_ORIENTATION.TWO_THREE;
|
|
72
|
-
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StabilizerItem } from '../validation/contract/stabilizer_domain_schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Group ids of floor five-slot 3_2/2_3 walls that the stabilizer must pin.
|
|
4
|
+
* @param items - All items in the cargo.
|
|
5
|
+
* @returns Group ids with exactly five floor tie-ratio members.
|
|
6
|
+
*/
|
|
7
|
+
export declare function collect_pinned_floor_tie_ratio_group_ids(items: StabilizerItem[]): Set<string>;
|
|
8
|
+
//# sourceMappingURL=collect_pinned_floor_tie_ratio_group_ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect_pinned_floor_tie_ratio_group_ids.d.ts","sourceRoot":"","sources":["../../../src/core/filters/collect_pinned_floor_tie_ratio_group_ids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AAMxF;;;;GAIG;AACH,wBAAgB,wCAAwC,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAQ7F"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TIE_RATIO_ORIENTATION } from '../constants/TieRatioOrientation.js';
|
|
2
|
+
import { is_item_on_cm_floor } from './is_item_on_cm_floor.js';
|
|
3
|
+
const FULL_TIE_RATIO_FLOOR_COUNT = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Group ids of floor five-slot 3_2/2_3 walls that the stabilizer must pin.
|
|
6
|
+
* @param items - All items in the cargo.
|
|
7
|
+
* @returns Group ids with exactly five floor tie-ratio members.
|
|
8
|
+
*/
|
|
9
|
+
export function collect_pinned_floor_tie_ratio_group_ids(items) {
|
|
10
|
+
const floor_tie_count_by_group = new Map();
|
|
11
|
+
for (const item of items) {
|
|
12
|
+
add_floor_tie_member(floor_tie_count_by_group, item);
|
|
13
|
+
}
|
|
14
|
+
return groups_with_full_floor_wall(floor_tie_count_by_group);
|
|
15
|
+
}
|
|
16
|
+
function add_floor_tie_member(counts, item) {
|
|
17
|
+
if (item.group_id == null || !is_item_on_cm_floor(item)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!is_tie_ratio_orientation(read_tie_ratio_orientation(item))) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
counts.set(item.group_id, (counts.get(item.group_id) ?? 0) + 1);
|
|
24
|
+
}
|
|
25
|
+
function groups_with_full_floor_wall(counts) {
|
|
26
|
+
const pinned = new Set();
|
|
27
|
+
for (const [group_id, count] of counts) {
|
|
28
|
+
if (count === FULL_TIE_RATIO_FLOOR_COUNT) {
|
|
29
|
+
pinned.add(group_id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return pinned;
|
|
33
|
+
}
|
|
34
|
+
function read_tie_ratio_orientation(item) {
|
|
35
|
+
const orientation = item.attributes?.tie_ratio_orientation;
|
|
36
|
+
return typeof orientation === 'string' ? orientation : undefined;
|
|
37
|
+
}
|
|
38
|
+
function is_tie_ratio_orientation(orientation) {
|
|
39
|
+
return orientation === TIE_RATIO_ORIENTATION.THREE_TWO
|
|
40
|
+
|| orientation === TIE_RATIO_ORIENTATION.TWO_THREE;
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StabilizerItem } from '../validation/contract/stabilizer_domain_schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* True when the item rests on the floor (contract y is height after wire map).
|
|
4
|
+
* @param item - Stabilizer item in contract coordinates.
|
|
5
|
+
* @returns True when height origin is on the floor.
|
|
6
|
+
*/
|
|
7
|
+
export declare function is_item_on_cm_floor(item: StabilizerItem): boolean;
|
|
8
|
+
//# sourceMappingURL=is_item_on_cm_floor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is_item_on_cm_floor.d.ts","sourceRoot":"","sources":["../../../src/core/filters/is_item_on_cm_floor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AAIxF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAGjE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const CM_FLOOR_HEIGHT_EPSILON = 1e-6;
|
|
2
|
+
/**
|
|
3
|
+
* True when the item rests on the floor (contract y is height after wire map).
|
|
4
|
+
* @param item - Stabilizer item in contract coordinates.
|
|
5
|
+
* @returns True when height origin is on the floor.
|
|
6
|
+
*/
|
|
7
|
+
export function is_item_on_cm_floor(item) {
|
|
8
|
+
return item.y <= CM_FLOOR_HEIGHT_EPSILON;
|
|
9
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StabilizerContainer } from '../validation/contract/stabilizer_domain_schema.js';
|
|
2
2
|
/**
|
|
3
|
-
* True when any two items penetrate in the
|
|
4
|
-
* (x + width, y +
|
|
3
|
+
* True when any two items penetrate in the contract frame
|
|
4
|
+
* (x + width, y + height, z + depth) by more than a face-touch epsilon.
|
|
5
5
|
* @param containers - Layout to inspect.
|
|
6
6
|
* @returns True when a pair occupies the same volume beyond float error.
|
|
7
7
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const PENETRATION_EPSILON = 1e-6;
|
|
2
2
|
/**
|
|
3
|
-
* True when any two items penetrate in the
|
|
4
|
-
* (x + width, y +
|
|
3
|
+
* True when any two items penetrate in the contract frame
|
|
4
|
+
* (x + width, y + height, z + depth) by more than a face-touch epsilon.
|
|
5
5
|
* @param containers - Layout to inspect.
|
|
6
6
|
* @returns True when a pair occupies the same volume beyond float error.
|
|
7
7
|
*/
|
|
@@ -20,7 +20,7 @@ function container_has_penetrating_overlap(container) {
|
|
|
20
20
|
return false;
|
|
21
21
|
}
|
|
22
22
|
function items_penetrate(left, right) {
|
|
23
|
-
return interval_penetration({ min: left.x, max: left.x + left.width }, { min: right.x, max: right.x + right.width }) && interval_penetration({ min: left.y, max: left.y + left.
|
|
23
|
+
return interval_penetration({ min: left.x, max: left.x + left.width }, { min: right.x, max: right.x + right.width }) && interval_penetration({ min: left.y, max: left.y + left.height }, { min: right.y, max: right.y + right.height }) && interval_penetration({ min: left.z, max: left.z + left.depth }, { min: right.z, max: right.z + right.depth });
|
|
24
24
|
}
|
|
25
25
|
function interval_penetration(left, right) {
|
|
26
26
|
return Math.min(left.max, right.max) - Math.max(left.min, right.min) > PENETRATION_EPSILON;
|
|
@@ -38,5 +38,8 @@ function map_item_to_wire(item) {
|
|
|
38
38
|
if (item.attributes === undefined) {
|
|
39
39
|
return rename_keys(omit_keys(item, ['attributes']), CONTRACT_TO_ITEM_WIRE);
|
|
40
40
|
}
|
|
41
|
+
if (typeof item.attributes.w === 'number') {
|
|
42
|
+
return { ...item.attributes, x: item.x, y: item.z, z: item.y };
|
|
43
|
+
}
|
|
41
44
|
return { ...item.attributes, x: item.x, y: item.y, z: item.z };
|
|
42
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map_wire_input_to_contract_shape.d.ts","sourceRoot":"","sources":["../../../../src/core/validation/contract/map_wire_input_to_contract_shape.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"map_wire_input_to_contract_shape.d.ts","sourceRoot":"","sources":["../../../../src/core/validation/contract/map_wire_input_to_contract_shape.ts"],"names":[],"mappings":"AAWA,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiBnE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CONTAINER_WIRE_TO_CONTRACT, ENV_WIRE_TO_CONTRACT } from './cargo_movement_wire_field_map.js';
|
|
2
2
|
import { map_wire_item_to_contract } from './map_wire_item_to_contract.js';
|
|
3
|
+
import { uniquify_contract_item_ids } from './uniquify_contract_item_ids.js';
|
|
3
4
|
import { is_record, omit_keys, pick_keys, rename_keys } from './wire_mapper_record_utils.js';
|
|
4
5
|
const CONTAINER_CONTRACT_KEYS = ['sequence', 'width', 'height', 'length'];
|
|
5
6
|
const ENV_CONTRACT_KEYS = ['axles'];
|
|
@@ -32,7 +33,7 @@ function map_container_to_contract(value) {
|
|
|
32
33
|
}
|
|
33
34
|
const mapped = rename_keys(value, CONTAINER_WIRE_TO_CONTRACT);
|
|
34
35
|
if (Array.isArray(value.items)) {
|
|
35
|
-
mapped.items = value.items.map(map_wire_item_to_contract);
|
|
36
|
+
mapped.items = uniquify_contract_item_ids(value.items.map(map_wire_item_to_contract));
|
|
36
37
|
}
|
|
37
38
|
return pick_container_contract_keys(mapped);
|
|
38
39
|
}
|
|
@@ -22,11 +22,7 @@ export function map_wire_item_to_contract(value) {
|
|
|
22
22
|
return contract;
|
|
23
23
|
}
|
|
24
24
|
function read_contract_item_fields(source) {
|
|
25
|
-
const contract =
|
|
26
|
-
x: source.x,
|
|
27
|
-
y: source.y,
|
|
28
|
-
z: source.z
|
|
29
|
-
};
|
|
25
|
+
const contract = read_contract_position(source);
|
|
30
26
|
for (const [contract_key, wire_keys] of Object.entries(ITEM_CONTRACT_WIRE_KEYS)) {
|
|
31
27
|
const value = read_first_present(source, wire_keys);
|
|
32
28
|
if (value !== undefined) {
|
|
@@ -35,6 +31,18 @@ function read_contract_item_fields(source) {
|
|
|
35
31
|
}
|
|
36
32
|
return contract;
|
|
37
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* BWA `to_json` stores length on `y` and height on `z`. Candidate geometry
|
|
36
|
+
* uses the opposite (y = height, z = length), so CM wire items are swapped.
|
|
37
|
+
* @param source - Wire or English item record.
|
|
38
|
+
* @returns Contract x/y/z.
|
|
39
|
+
*/
|
|
40
|
+
function read_contract_position(source) {
|
|
41
|
+
if (typeof source.w === 'number') {
|
|
42
|
+
return { x: source.x, y: source.z, z: source.y };
|
|
43
|
+
}
|
|
44
|
+
return { x: source.x, y: source.y, z: source.z };
|
|
45
|
+
}
|
|
38
46
|
/**
|
|
39
47
|
* Coerces blocks-wall numeric group ids to the string contract type.
|
|
40
48
|
* @param contract_key - English contract field name.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes contract item_id unique when the same SKU id repeats.
|
|
3
|
+
* Original `_id` stays in attributes for the wire round-trip.
|
|
4
|
+
* @param items - Mapped contract items.
|
|
5
|
+
* @returns Items with unique `item_id` values.
|
|
6
|
+
*/
|
|
7
|
+
export declare function uniquify_contract_item_ids(items: unknown[]): unknown[];
|
|
8
|
+
//# sourceMappingURL=uniquify_contract_item_ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uniquify_contract_item_ids.d.ts","sourceRoot":"","sources":["../../../../src/core/validation/contract/uniquify_contract_item_ids.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAKtE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { is_record } from './wire_mapper_record_utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Makes contract item_id unique when the same SKU id repeats.
|
|
4
|
+
* Original `_id` stays in attributes for the wire round-trip.
|
|
5
|
+
* @param items - Mapped contract items.
|
|
6
|
+
* @returns Items with unique `item_id` values.
|
|
7
|
+
*/
|
|
8
|
+
export function uniquify_contract_item_ids(items) {
|
|
9
|
+
const counts = count_item_ids(items);
|
|
10
|
+
const seen = new Map();
|
|
11
|
+
return items.map(item => uniquify_one_item(item, counts, seen));
|
|
12
|
+
}
|
|
13
|
+
function count_item_ids(items) {
|
|
14
|
+
const counts = new Map();
|
|
15
|
+
for (const item of items) {
|
|
16
|
+
const item_id = read_item_id(item);
|
|
17
|
+
if (item_id === undefined) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
counts.set(item_id, (counts.get(item_id) ?? 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
return counts;
|
|
23
|
+
}
|
|
24
|
+
function uniquify_one_item(item, counts, seen) {
|
|
25
|
+
const item_id = read_item_id(item);
|
|
26
|
+
if (item_id === undefined || (counts.get(item_id) ?? 0) <= 1 || !is_record(item)) {
|
|
27
|
+
return item;
|
|
28
|
+
}
|
|
29
|
+
const occurrence = seen.get(item_id) ?? 0;
|
|
30
|
+
seen.set(item_id, occurrence + 1);
|
|
31
|
+
return { ...item, item_id: `${item_id}#${occurrence}` };
|
|
32
|
+
}
|
|
33
|
+
function read_item_id(item) {
|
|
34
|
+
if (!is_record(item) || typeof item.item_id !== 'string') {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return item.item_id;
|
|
38
|
+
}
|