@jetta/axle-load-calculator 0.3.2 → 0.3.4

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 ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@jetta/axle-load-calculator` are documented in this file.
4
+
5
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - Cargo collision check after axle loads are written (`CollisionChecker`), using `lt` from `@jetta/number-compare` (default epsilon `1e-9`).
12
+ - Public `ItemOverlapError` thrown by `calculate_weight_on_axles_physics` when two allocated items share volume.
13
+
14
+ ### Fixed
15
+
16
+ - Face-touching boxes (IEEE ULP on edges such as `4.075 + 0.815` vs `4.89`) are no longer treated as overlap, so callers can keep axle kg instead of skipping the gate.
17
+
18
+ ## [0.3.2] - 2026-07-23
19
+
20
+ ### Fixed
21
+
22
+ - Same-grouper container loads are merged for mass-center so results match MS.
23
+
24
+ ## [0.3.0] - 2026-06-02
25
+
26
+ ### Changed
27
+
28
+ - Public API accepts objects only, returns objects, and throws domain errors instead of mixed string/object I/O.
29
+
30
+ ### Removed
31
+
32
+ - Collision test artifacts and unused overlap error after `CollisionChecker` removal.
33
+
34
+ ### Added
35
+
36
+ - npm coverage script with 80% line, function, branch, and statement thresholds.
37
+
38
+ ## [0.2.1] - 2026-06-02
39
+
40
+ ### Removed
41
+
42
+ - `CollisionChecker` and its call from the weight-by-axis task.
43
+
44
+ ## [0.2.0] - 2026-06-02
45
+
46
+ ### Added
47
+
48
+ - First published library surface: `calculate_weight_on_axles_physics`, domain exceptions, Zod request validation, and in-process physics plus orchestration.
49
+
50
+ ### Changed
51
+
52
+ - Package entry points use lowercase `index`.
53
+ - Weight calculation accepted both JSON strings and objects (superseded in 0.3.0).
54
+
55
+ ## [0.1.3] - 2026-06-02
56
+
57
+ ### Changed
58
+
59
+ - Package is public (`private: false`) for npm publish.
60
+
61
+ ## [0.1.2] - 2026-06-02
62
+
63
+ ### Added
64
+
65
+ - Bitbucket Pipelines for CI/CD.
66
+
67
+ ## [0.1.0] - 2026-06-02
68
+
69
+ ### Added
70
+
71
+ - Project scaffolding, transport and vehicle models, deflection and tare, axle-weight services, weight-by-axis orchestration, and golden deflection fixtures.
package/README.md CHANGED
@@ -55,6 +55,7 @@ try {
55
55
  | `UnsupportedVehicleTypeError` | Unknown `tipoVeiculo` |
56
56
  | `UnrecognizedSuspensionTypeError` | Invalid vanderleia suspension type |
57
57
  | `GrouperNotFoundError` | Merged-container grouper not found |
58
+ | `ItemOverlapError` | Two allocated items occupy the same volume |
58
59
 
59
60
  Import `ZodError` directly from [`zod`](https://github.com/colinhacks/zod).
60
61
 
@@ -72,6 +73,7 @@ Also exported from `@jetta/axle-load-calculator` for `instanceof` checks when us
72
73
  ## Dependencies
73
74
 
74
75
  - [zod](https://github.com/colinhacks/zod) — request validation at the public boundary
76
+ - [`@jetta/number-compare`](https://www.npmjs.com/package/@jetta/number-compare) — float-safe interval tests in cargo collision
75
77
 
76
78
  ## Scripts
77
79
 
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Rejects an env whose allocated items occupy the same volume.
3
+ * Face-touching boxes are allowed; comparison uses `@jetta/number-compare`.
4
+ * @param env - Env vehicle with containers and items.
5
+ * @throws {ItemOverlapError} When two items share volume on all three axes.
6
+ */
7
+ export declare function check_cargo_collision(env: Record<string, unknown>): void;
8
+ //# sourceMappingURL=CollisionChecker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollisionChecker.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/CollisionChecker.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWxE"}
@@ -0,0 +1,60 @@
1
+ import { lt } from '@jetta/number-compare';
2
+ import { ItemOverlapError } from './ItemOverlapError.js';
3
+ /**
4
+ * Rejects an env whose allocated items occupy the same volume.
5
+ * Face-touching boxes are allowed; comparison uses `@jetta/number-compare`.
6
+ * @param env - Env vehicle with containers and items.
7
+ * @throws {ItemOverlapError} When two items share volume on all three axes.
8
+ */
9
+ export function check_cargo_collision(env) {
10
+ const containers = env.containers;
11
+ if (!containers) {
12
+ return;
13
+ }
14
+ for (const container of containers) {
15
+ check_container_items(container);
16
+ }
17
+ }
18
+ function check_container_items(container) {
19
+ const items = container.items;
20
+ if (!items || items.length <= 1) {
21
+ return;
22
+ }
23
+ throw_if_any_pair_overlaps(items);
24
+ }
25
+ function throw_if_any_pair_overlaps(items) {
26
+ for (let index = 0; index < items.length - 1; index++) {
27
+ for (let other = index + 1; other < items.length; other++) {
28
+ if (items_overlap(items[index], items[other])) {
29
+ throw new ItemOverlapError();
30
+ }
31
+ }
32
+ }
33
+ }
34
+ function items_overlap(left, right) {
35
+ const left_box = item_box(left);
36
+ const right_box = item_box(right);
37
+ return intervals_overlap(left_box.x, right_box.x)
38
+ && intervals_overlap(left_box.y, right_box.y)
39
+ && intervals_overlap(left_box.z, right_box.z);
40
+ }
41
+ /**
42
+ * Maps CM / Java item JSON onto axis intervals.
43
+ * Height lives on JSON `z`; length on JSON `y` + `comprimento`.
44
+ */
45
+ function item_box(item) {
46
+ const origin_x = Number(item.x ?? 0);
47
+ const origin_y = Number(item.y ?? 0);
48
+ const origin_z = Number(item.z ?? 0);
49
+ const width = Number(item.largura ?? item.width ?? 0);
50
+ const height = Number(item.altura ?? item.height ?? 0);
51
+ const length = Number(item.comprimento ?? item.length ?? 0);
52
+ return {
53
+ x: { min: origin_x, max: origin_x + width },
54
+ y: { min: origin_z, max: origin_z + height },
55
+ z: { min: origin_y, max: origin_y + length }
56
+ };
57
+ }
58
+ function intervals_overlap(left, right) {
59
+ return lt(left.min, right.max) && lt(right.min, left.max);
60
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Raised when two allocated items occupy the same volume.
3
+ */
4
+ export declare class ItemOverlapError extends Error {
5
+ constructor();
6
+ }
7
+ //# sourceMappingURL=ItemOverlapError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ItemOverlapError.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/ItemOverlapError.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;;CAO1C"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Raised when two allocated items occupy the same volume.
3
+ */
4
+ export class ItemOverlapError extends Error {
5
+ constructor() {
6
+ super('Item overlap error');
7
+ this.name = 'ItemOverlapError';
8
+ }
9
+ }
@@ -12,6 +12,7 @@ import type { WeightByAxisSuccessOutput } from './WeightByAxisResponse.js';
12
12
  * @throws {import('../physics/transports/MissingContainerError.js').MissingContainerError} When a container lookup fails.
13
13
  * @throws {import('../physics/items/UnknownItemIdError.js').UnknownItemIdError} When an item template id is unknown.
14
14
  * @throws {import('../physics/models/deflection/DeflectionModelNotImplementedError.js').DeflectionModelNotImplementedError} When no deflection model exists.
15
+ * @throws {import('./ItemOverlapError.js').ItemOverlapError} When two allocated items occupy the same volume.
15
16
  */
16
17
  export declare function run_weight_by_axis_task(request_data: WeightByAxisRequestInput): WeightByAxisSuccessOutput;
17
18
  //# sourceMappingURL=WeightByAxisTask.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WeightByAxisTask.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/WeightByAxisTask.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,wBAAwB,EAChC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAE1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACnC,YAAY,EAAE,wBAAwB,GACvC,yBAAyB,CAY3B"}
1
+ {"version":3,"file":"WeightByAxisTask.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/WeightByAxisTask.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,wBAAwB,EAChC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAE1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACnC,YAAY,EAAE,wBAAwB,GACvC,yBAAyB,CAa3B"}
@@ -1,4 +1,5 @@
1
1
  import { calculate_loaded_weights_for_env } from './AxleWeightPhysics.js';
2
+ import { check_cargo_collision } from './CollisionChecker.js';
2
3
  import { build_success_output } from './OutputBuilder.js';
3
4
  import { parse_weight_by_axis_request_data } from './RequestSchema.js';
4
5
  /**
@@ -13,6 +14,7 @@ import { parse_weight_by_axis_request_data } from './RequestSchema.js';
13
14
  * @throws {import('../physics/transports/MissingContainerError.js').MissingContainerError} When a container lookup fails.
14
15
  * @throws {import('../physics/items/UnknownItemIdError.js').UnknownItemIdError} When an item template id is unknown.
15
16
  * @throws {import('../physics/models/deflection/DeflectionModelNotImplementedError.js').DeflectionModelNotImplementedError} When no deflection model exists.
17
+ * @throws {import('./ItemOverlapError.js').ItemOverlapError} When two allocated items occupy the same volume.
16
18
  */
17
19
  export function run_weight_by_axis_task(request_data) {
18
20
  const request = parse_weight_by_axis_request_data(request_data);
@@ -21,6 +23,7 @@ export function run_weight_by_axis_task(request_data) {
21
23
  for (const env of envs) {
22
24
  const loaded_weights = calculate_loaded_weights_for_env(env);
23
25
  apply_loaded_weights_to_env(env, loaded_weights);
26
+ check_cargo_collision(env);
24
27
  output_envs.push(strip_items_and_bands(env));
25
28
  }
26
29
  return build_success_output(output_envs);
package/build/index.d.ts CHANGED
@@ -7,8 +7,9 @@ import { UnsupportedVehicleTypeError } from './core/physics/models/UnsupportedVe
7
7
  import { ContainerSequenceNotFoundError } from './core/physics/transports/ContainerSequenceNotFoundError.js';
8
8
  import { GrouperNotFoundError } from './core/physics/transports/GrouperNotFoundError.js';
9
9
  import { MissingContainerError } from './core/physics/transports/MissingContainerError.js';
10
+ import { ItemOverlapError } from './core/orchestration/ItemOverlapError.js';
10
11
  export type { WeightByAxisRequest, WeightByAxisRequestInput, WeightByAxisResponse, WeightByAxisSuccessOutput };
11
- export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
12
+ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, ItemOverlapError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
12
13
  /**
13
14
  * Calculates loaded cargo weight per axle for each transport in the payload.
14
15
  * @param request - Request with `{ envs, config? }`.
@@ -17,6 +18,7 @@ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, Gro
17
18
  * @throws {UnsupportedVehicleTypeError} When physics rejects the vehicle type.
18
19
  * @throws {UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
19
20
  * @throws {GrouperNotFoundError} When a merged-container grouper is missing.
21
+ * @throws {ItemOverlapError} When two allocated items occupy the same volume.
20
22
  * Other exported error classes are for lower-level physics modules, not thrown here.
21
23
  */
22
24
  export declare function calculate_weight_on_axles_physics(request: WeightByAxisRequestInput): WeightByAxisSuccessOutput;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,mBAAmB,EACnB,wBAAwB,EAC3B,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EACR,oBAAoB,EACpB,yBAAyB,EAC5B,MAAM,8CAA8C,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,wEAAwE,CAAA;AAC3H,OAAO,EAAE,+BAA+B,EAAE,MAAM,0DAA0D,CAAA;AAC1G,OAAO,EAAE,2BAA2B,EAAE,MAAM,sDAAsD,CAAA;AAClG,OAAO,EAAE,8BAA8B,EAAE,MAAM,6DAA6D,CAAA;AAC5G,OAAO,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAA;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAA;AAE1F,YAAY,EACR,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,yBAAyB,EAC5B,CAAA;AAED,OAAO,EACH,8BAA8B,EAC9B,kCAAkC,EAClC,oBAAoB,EACpB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,EAClB,2BAA2B,EAC9B,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC7C,OAAO,EAAE,wBAAwB,GAClC,yBAAyB,CAG3B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,mBAAmB,EACnB,wBAAwB,EAC3B,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EACR,oBAAoB,EACpB,yBAAyB,EAC5B,MAAM,8CAA8C,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,wEAAwE,CAAA;AAC3H,OAAO,EAAE,+BAA+B,EAAE,MAAM,0DAA0D,CAAA;AAC1G,OAAO,EAAE,2BAA2B,EAAE,MAAM,sDAAsD,CAAA;AAClG,OAAO,EAAE,8BAA8B,EAAE,MAAM,6DAA6D,CAAA;AAC5G,OAAO,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAA;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAA;AAC1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAE3E,YAAY,EACR,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,yBAAyB,EAC5B,CAAA;AAED,OAAO,EACH,8BAA8B,EAC9B,kCAAkC,EAClC,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,EAClB,2BAA2B,EAC9B,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC7C,OAAO,EAAE,wBAAwB,GAClC,yBAAyB,CAG3B"}
package/build/index.js CHANGED
@@ -6,7 +6,8 @@ import { UnsupportedVehicleTypeError } from './core/physics/models/UnsupportedVe
6
6
  import { ContainerSequenceNotFoundError } from './core/physics/transports/ContainerSequenceNotFoundError.js';
7
7
  import { GrouperNotFoundError } from './core/physics/transports/GrouperNotFoundError.js';
8
8
  import { MissingContainerError } from './core/physics/transports/MissingContainerError.js';
9
- export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
9
+ import { ItemOverlapError } from './core/orchestration/ItemOverlapError.js';
10
+ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, ItemOverlapError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
10
11
  /**
11
12
  * Calculates loaded cargo weight per axle for each transport in the payload.
12
13
  * @param request - Request with `{ envs, config? }`.
@@ -15,6 +16,7 @@ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, Gro
15
16
  * @throws {UnsupportedVehicleTypeError} When physics rejects the vehicle type.
16
17
  * @throws {UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
17
18
  * @throws {GrouperNotFoundError} When a merged-container grouper is missing.
19
+ * @throws {ItemOverlapError} When two allocated items occupy the same volume.
18
20
  * Other exported error classes are for lower-level physics modules, not thrown here.
19
21
  */
20
22
  export function calculate_weight_on_axles_physics(request) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetta/axle-load-calculator",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Merged weight_by_axis physics and orchestration library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -22,6 +22,7 @@
22
22
  "coverage": "c8 --check-coverage --lines 80 --functions 80 --branches 80 --statements 80 --reporter=html --reporter=text npm run test"
23
23
  },
24
24
  "dependencies": {
25
+ "@jetta/number-compare": "^3.0.3",
25
26
  "zod": "^3.24.0"
26
27
  },
27
28
  "devDependencies": {