@jetta/axle-load-calculator 0.2.0 → 0.3.2

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/README.md CHANGED
@@ -17,19 +17,57 @@ npm run build
17
17
  ## Usage
18
18
 
19
19
  ```typescript
20
- import { calculate_weight_on_axles_physics } from '@jetta/axle-load-calculator'
20
+ import {
21
+ calculate_weight_on_axles_physics,
22
+ UnsupportedVehicleTypeError,
23
+ type WeightByAxisRequestInput,
24
+ type WeightByAxisSuccessOutput
25
+ } from '@jetta/axle-load-calculator'
26
+ import { ZodError } from 'zod'
27
+
28
+ const request: WeightByAxisRequestInput = { envs: [...] }
29
+
30
+ try {
31
+ const response: WeightByAxisSuccessOutput = calculate_weight_on_axles_physics(request)
32
+ // response.envs with axle weights
33
+ } catch (error) {
34
+ if (error instanceof ZodError) {
35
+ // invalid request shape (null, array, missing containers, etc.)
36
+ } else if (error instanceof UnsupportedVehicleTypeError) {
37
+ // unsupported tipoVeiculo
38
+ }
39
+ }
40
+ ```
21
41
 
22
- const response_json = calculate_weight_on_axles_physics(input_json_string)
42
+ **Contract:**
23
43
 
24
- const request_object = JSON.parse(input_json_string)
25
- const response_from_object = calculate_weight_on_axles_physics(request_object)
26
- ```
44
+ - Input: object `{ envs, config? }` with required `containers` on each env (cloned before processing)
45
+ - Success: `{ envs }` with `containers[].pesoEixos` filled; `items` and `faixas` cleared on containers
46
+ - Failure: throws (see **Errors** below)
47
+
48
+ ## Errors
49
+
50
+ ### Thrown by `calculate_weight_on_axles_physics`
51
+
52
+ | Class | When |
53
+ |-------|------|
54
+ | `ZodError` (from `zod`, not re-exported) | Invalid request shape (`null`, array, missing `containers`, etc.) |
55
+ | `UnsupportedVehicleTypeError` | Unknown `tipoVeiculo` |
56
+ | `UnrecognizedSuspensionTypeError` | Invalid vanderleia suspension type |
57
+ | `GrouperNotFoundError` | Merged-container grouper not found |
58
+
59
+ Import `ZodError` directly from [`zod`](https://github.com/colinhacks/zod).
60
+
61
+ ### Exported for lower-level use
27
62
 
28
- **Contract:** same as `WeightByAxisTask.run()` in ms-faixas-porcentuais:
63
+ Also exported from `@jetta/axle-load-calculator` for `instanceof` checks when using internal physics modules (not thrown by `calculate_weight_on_axles_physics`):
29
64
 
30
- - Input: JSON string or object `{ envs, config? }` (object input is cloned before processing)
31
- - Success: `envs[].conteiners[].pesoEixos` filled; `items` and `faixas` cleared on containers
32
- - Error: `{ msg, apiError }`
65
+ | Class | When |
66
+ |-------|------|
67
+ | `ContainerSequenceNotFoundError` | Container sequence missing on transport |
68
+ | `MissingContainerError` | Container lookup failed |
69
+ | `UnknownItemIdError` | Unknown item template id |
70
+ | `DeflectionModelNotImplementedError` | No deflection model for the vehicle |
33
71
 
34
72
  ## Dependencies
35
73
 
@@ -2,6 +2,7 @@ import { CenterOfMass } from '../physics/items/CenterOfMass.js';
2
2
  /**
3
3
  * Computes mass center per grouper using ms-faixas Java Item / MassCenter rules.
4
4
  * Longitudinal position uses JSON `y` (maps to Ponto3D.z in Java CubeRepresentation).
5
+ * Same-grouper containers are merged; empty necks do not shift later beds (MS parity).
5
6
  * @param env_vehicle - Single env vehicle object from weight_by_axis payload.
6
7
  * @returns Map of grouper id to center of mass.
7
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"JavaMassCenter.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/JavaMassCenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAE/D;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAe9B"}
1
+ {"version":3,"file":"JavaMassCenter.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/JavaMassCenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAE/D;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAQ9B"}
@@ -2,46 +2,99 @@ import { CenterOfMass } from '../physics/items/CenterOfMass.js';
2
2
  /**
3
3
  * Computes mass center per grouper using ms-faixas Java Item / MassCenter rules.
4
4
  * Longitudinal position uses JSON `y` (maps to Ponto3D.z in Java CubeRepresentation).
5
+ * Same-grouper containers are merged; empty necks do not shift later beds (MS parity).
5
6
  * @param env_vehicle - Single env vehicle object from weight_by_axis payload.
6
7
  * @returns Map of grouper id to center of mass.
7
8
  */
8
9
  export function mass_center_by_grouper_from_env(env_vehicle) {
9
- const result = {};
10
10
  const containers = env_vehicle.containers;
11
11
  if (!containers) {
12
- return result;
12
+ return {};
13
13
  }
14
+ return accumulate_grouper_mass_centers(sort_containers_by_sequence(containers));
15
+ }
16
+ /**
17
+ * @param containers - Containers sorted by sequencia.
18
+ * @returns Merged center of mass map keyed by grouper.
19
+ */
20
+ function accumulate_grouper_mass_centers(containers) {
21
+ const result = {};
22
+ const longitudinal_offset_by_grouper = {};
14
23
  for (const container of containers) {
15
- const grouper = Number(container.grouper);
16
- // Java AxleWeightConnector overwrites per container in loop order (last wins).
17
- result[grouper] = compute_container_mass_center(container);
24
+ absorb_container_into_grouper(result, longitudinal_offset_by_grouper, container);
18
25
  }
19
26
  return result;
20
27
  }
28
+ /**
29
+ * @param result - Accumulated grouper centers (mutated).
30
+ * @param offsets - Longitudinal offset per grouper (mutated).
31
+ * @param container - Env container to absorb.
32
+ */
33
+ function absorb_container_into_grouper(result, offsets, container) {
34
+ const grouper = Number(container.grouper);
35
+ const sequence_offset = offsets[grouper] ?? 0;
36
+ const container_center = compute_container_mass_center(container, sequence_offset);
37
+ result[grouper] = result[grouper]
38
+ ? merge_mass_centers(result[grouper], container_center)
39
+ : container_center;
40
+ if (container_center.mass > 0) {
41
+ offsets[grouper] = sequence_offset + Number(container.comprimento ?? 0);
42
+ }
43
+ }
44
+ /**
45
+ * @param containers - Env containers.
46
+ * @returns Containers sorted by sequencia ascending.
47
+ */
48
+ function sort_containers_by_sequence(containers) {
49
+ return [...containers].sort((left, right) => Number(left.sequencia) - Number(right.sequencia));
50
+ }
51
+ /**
52
+ * @param left - Existing grouper center of mass.
53
+ * @param right - Additional container center of mass.
54
+ * @returns Combined center of mass.
55
+ */
56
+ function merge_mass_centers(left, right) {
57
+ const merged = new CenterOfMass();
58
+ merged.mass = left.mass + right.mass;
59
+ if (merged.mass === 0) {
60
+ merged.z = 0;
61
+ return merged;
62
+ }
63
+ merged.z = (left.mass * left.z + right.mass * right.z) / merged.mass;
64
+ return merged;
65
+ }
21
66
  /**
22
67
  * @param container - Container object with items array.
68
+ * @param sequence_offset - Longitudinal offset from prior same-grouper containers.
23
69
  * @returns Center of mass for container items.
24
70
  */
25
- function compute_container_mass_center(container) {
71
+ function compute_container_mass_center(container, sequence_offset) {
26
72
  const center_of_mass = new CenterOfMass();
27
73
  const items = container.items;
28
74
  if (!items || items.length === 0) {
29
- center_of_mass.mass = 0;
30
- center_of_mass.z = 0;
31
75
  return center_of_mass;
32
76
  }
33
- const container_offset = read_container_longitudinal_offset(container);
77
+ const moments = accumulate_item_moments(items, sequence_offset, container);
78
+ center_of_mass.mass = moments.mass;
79
+ center_of_mass.z = moments.mass === 0 ? 0 : moments.accumulated / moments.mass;
80
+ return center_of_mass;
81
+ }
82
+ /**
83
+ * @param items - Container items.
84
+ * @param sequence_offset - Prior loaded length in grouper.
85
+ * @param container - Container for faixa offset.
86
+ * @returns Accumulated mass and first moment.
87
+ */
88
+ function accumulate_item_moments(items, sequence_offset, container) {
89
+ const container_offset = sequence_offset + read_container_longitudinal_offset(container);
34
90
  let accumulated = 0;
35
91
  let mass = 0;
36
92
  for (const item of items) {
37
93
  const weight = read_item_weight(item);
38
- const position = container_offset + read_item_longitudinal_position(item);
39
- accumulated += weight * position;
94
+ accumulated += weight * (container_offset + read_item_longitudinal_position(item));
40
95
  mass += weight;
41
96
  }
42
- center_of_mass.mass = mass;
43
- center_of_mass.z = mass === 0 ? 0 : accumulated / mass;
44
- return center_of_mass;
97
+ return { accumulated, mass };
45
98
  }
46
99
  /**
47
100
  * @param container - Container JSON.
@@ -1,11 +1,7 @@
1
+ import type { WeightByAxisSuccessOutput } from './WeightByAxisResponse.js';
1
2
  /**
2
3
  * @param envs - Output env list.
3
- * @returns Success JSON string.
4
+ * @returns Success response object.
4
5
  */
5
- export declare function build_success_output(envs: Record<string, unknown>[]): string;
6
- /**
7
- * @param error - Thrown error.
8
- * @returns Error JSON string matching ms-faixas format.
9
- */
10
- export declare function build_error_output(error: unknown): string;
6
+ export declare function build_success_output(envs: Record<string, unknown>[]): WeightByAxisSuccessOutput;
11
7
  //# sourceMappingURL=OutputBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OutputBuilder.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/OutputBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAG5E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIzD"}
1
+ {"version":3,"file":"OutputBuilder.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/OutputBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAE1E;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,yBAAyB,CAG/F"}
@@ -1,15 +1,7 @@
1
1
  /**
2
2
  * @param envs - Output env list.
3
- * @returns Success JSON string.
3
+ * @returns Success response object.
4
4
  */
5
5
  export function build_success_output(envs) {
6
- return JSON.stringify({ envs });
7
- }
8
- /**
9
- * @param error - Thrown error.
10
- * @returns Error JSON string matching ms-faixas format.
11
- */
12
- export function build_error_output(error) {
13
- const message = error instanceof Error ? error.message : String(error);
14
- return JSON.stringify({ msg: message, apiError: message });
6
+ return { envs };
15
7
  }
@@ -3,54 +3,45 @@ declare const request_schema: z.ZodObject<{
3
3
  envs: z.ZodArray<z.ZodObject<{
4
4
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
5
5
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
6
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
7
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
6
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
8
7
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
9
8
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
10
9
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
11
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
12
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
10
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
13
11
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
14
12
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
15
13
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
16
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
17
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
14
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
18
15
  }, z.ZodTypeAny, "passthrough">>, "many">;
19
16
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
17
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
21
18
  envs: z.ZodArray<z.ZodObject<{
22
19
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
23
20
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
24
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
25
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
21
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
26
22
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
27
23
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
28
24
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
29
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
30
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
25
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
31
26
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
32
27
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
33
28
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
34
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
35
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
29
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
36
30
  }, z.ZodTypeAny, "passthrough">>, "many">;
37
31
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
32
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
39
33
  envs: z.ZodArray<z.ZodObject<{
40
34
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
41
35
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
42
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
43
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
36
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
44
37
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
45
38
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
46
39
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
47
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
48
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
40
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
49
41
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
50
42
  _id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
51
43
  eixos: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
52
- conteiners: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
53
- containers: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
44
+ containers: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
54
45
  }, z.ZodTypeAny, "passthrough">>, "many">;
55
46
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
56
47
  }, z.ZodTypeAny, "passthrough">>;
@@ -61,12 +52,6 @@ export type WeightByAxisRequestInput = z.input<typeof request_schema>;
61
52
  * @param data - JSON string or plain `{ envs, config? }` object.
62
53
  * @returns Parsed request object.
63
54
  */
64
- export declare function parse_weight_by_axis_request_data(data: string | WeightByAxisRequestInput): WeightByAxisRequest;
65
- /**
66
- * Parses and validates weight-by-axis JSON at the library boundary.
67
- * @param json_data - Raw request string.
68
- * @returns Parsed request object.
69
- */
70
- export declare function parse_weight_by_axis_request(json_data: string): WeightByAxisRequest;
55
+ export declare function parse_weight_by_axis_request_data(data: WeightByAxisRequestInput): WeightByAxisRequest;
71
56
  export {};
72
57
  //# sourceMappingURL=RequestSchema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RequestSchema.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/RequestSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAGJ,CAAA;AAEhB,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAChE,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAErE;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC7C,IAAI,EAAE,MAAM,GAAG,wBAAwB,GACxC,mBAAmB,CAIrB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAGnF"}
1
+ {"version":3,"file":"RequestSchema.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/RequestSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAQvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAGJ,CAAA;AAEhB,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAChE,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAErE;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC7C,IAAI,EAAE,wBAAwB,GAC/B,mBAAmB,CAGrB"}
@@ -2,8 +2,7 @@ import { z } from 'zod';
2
2
  const env_schema = z.object({
3
3
  _id: z.union([z.string(), z.number()]).optional(),
4
4
  eixos: z.array(z.record(z.unknown())).min(1),
5
- conteiners: z.array(z.record(z.unknown())).optional(),
6
- containers: z.array(z.record(z.unknown())).optional()
5
+ containers: z.array(z.record(z.unknown())).min(1)
7
6
  }).passthrough();
8
7
  const request_schema = z.object({
9
8
  envs: z.array(env_schema).min(1),
@@ -15,27 +14,5 @@ const request_schema = z.object({
15
14
  * @returns Parsed request object.
16
15
  */
17
16
  export function parse_weight_by_axis_request_data(data) {
18
- const raw = normalize_request_raw(data);
19
- return request_schema.parse(raw);
20
- }
21
- /**
22
- * Parses and validates weight-by-axis JSON at the library boundary.
23
- * @param json_data - Raw request string.
24
- * @returns Parsed request object.
25
- */
26
- export function parse_weight_by_axis_request(json_data) {
27
- return parse_weight_by_axis_request_data(json_data);
28
- }
29
- /**
30
- * @param data - Request string or object.
31
- * @returns Raw value for Zod parsing.
32
- */
33
- function normalize_request_raw(data) {
34
- if (typeof data === 'string') {
35
- return JSON.parse(data);
36
- }
37
- if (data !== null && typeof data === 'object' && !Array.isArray(data)) {
38
- return structuredClone(data);
39
- }
40
- throw new Error('Invalid weight-by-axis request type');
17
+ return request_schema.parse(data);
41
18
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Successful weight-by-axis response payload.
3
+ */
4
+ export type WeightByAxisSuccessOutput = {
5
+ envs: Record<string, unknown>[];
6
+ };
7
+ /**
8
+ * Weight-by-axis task success response.
9
+ */
10
+ export type WeightByAxisResponse = WeightByAxisSuccessOutput;
11
+ //# sourceMappingURL=WeightByAxisResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WeightByAxisResponse.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/WeightByAxisResponse.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAClC,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,8 +1,17 @@
1
1
  import { type WeightByAxisRequestInput } from './RequestSchema.js';
2
+ import type { WeightByAxisSuccessOutput } from './WeightByAxisResponse.js';
2
3
  /**
3
4
  * Runs weight-by-axis for ms-faixas input (WeightByAxisTask.run contract).
4
- * @param request_data - JSON string or `{ envs, config? }` object.
5
- * @returns Response JSON string.
5
+ * @param request_data - `{ envs, config? }` object.
6
+ * @returns Response object with envs and axle weights.
7
+ * @throws {import('zod').ZodError} When request validation fails.
8
+ * @throws {import('../physics/models/UnsupportedVehicleTypeError.js').UnsupportedVehicleTypeError} When physics rejects the vehicle type.
9
+ * @throws {import('../physics/models/UnrecognizedSuspensionTypeError.js').UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
10
+ * @throws {import('../physics/transports/GrouperNotFoundError.js').GrouperNotFoundError} When a merged-container grouper is missing.
11
+ * @throws {import('../physics/transports/ContainerSequenceNotFoundError.js').ContainerSequenceNotFoundError} When a container sequence is missing.
12
+ * @throws {import('../physics/transports/MissingContainerError.js').MissingContainerError} When a container lookup fails.
13
+ * @throws {import('../physics/items/UnknownItemIdError.js').UnknownItemIdError} When an item template id is unknown.
14
+ * @throws {import('../physics/models/deflection/DeflectionModelNotImplementedError.js').DeflectionModelNotImplementedError} When no deflection model exists.
6
15
  */
7
- export declare function run_weight_by_axis_task(request_data: string | WeightByAxisRequestInput): string;
16
+ export declare function run_weight_by_axis_task(request_data: WeightByAxisRequestInput): WeightByAxisSuccessOutput;
8
17
  //# sourceMappingURL=WeightByAxisTask.d.ts.map
@@ -1 +1 @@
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;AAE3B;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,wBAAwB,GAAG,MAAM,CAoB/F"}
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,28 +1,29 @@
1
1
  import { calculate_loaded_weights_for_env } from './AxleWeightPhysics.js';
2
- import { build_success_output, build_error_output } from './OutputBuilder.js';
3
- import { check_cargo_collision } from './CollisionChecker.js';
2
+ import { build_success_output } from './OutputBuilder.js';
4
3
  import { parse_weight_by_axis_request_data } from './RequestSchema.js';
5
4
  /**
6
5
  * Runs weight-by-axis for ms-faixas input (WeightByAxisTask.run contract).
7
- * @param request_data - JSON string or `{ envs, config? }` object.
8
- * @returns Response JSON string.
6
+ * @param request_data - `{ envs, config? }` object.
7
+ * @returns Response object with envs and axle weights.
8
+ * @throws {import('zod').ZodError} When request validation fails.
9
+ * @throws {import('../physics/models/UnsupportedVehicleTypeError.js').UnsupportedVehicleTypeError} When physics rejects the vehicle type.
10
+ * @throws {import('../physics/models/UnrecognizedSuspensionTypeError.js').UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
11
+ * @throws {import('../physics/transports/GrouperNotFoundError.js').GrouperNotFoundError} When a merged-container grouper is missing.
12
+ * @throws {import('../physics/transports/ContainerSequenceNotFoundError.js').ContainerSequenceNotFoundError} When a container sequence is missing.
13
+ * @throws {import('../physics/transports/MissingContainerError.js').MissingContainerError} When a container lookup fails.
14
+ * @throws {import('../physics/items/UnknownItemIdError.js').UnknownItemIdError} When an item template id is unknown.
15
+ * @throws {import('../physics/models/deflection/DeflectionModelNotImplementedError.js').DeflectionModelNotImplementedError} When no deflection model exists.
9
16
  */
10
17
  export function run_weight_by_axis_task(request_data) {
11
- try {
12
- const request = parse_weight_by_axis_request_data(request_data);
13
- const envs = request.envs;
14
- const output_envs = [];
15
- for (const env of envs) {
16
- const loaded_weights = calculate_loaded_weights_for_env(env);
17
- apply_loaded_weights_to_env(env, loaded_weights);
18
- check_cargo_collision(env);
19
- output_envs.push(strip_items_and_bands(env));
20
- }
21
- return build_success_output(output_envs);
22
- }
23
- catch (error) {
24
- return build_error_output(error);
18
+ const request = parse_weight_by_axis_request_data(request_data);
19
+ const envs = request.envs;
20
+ const output_envs = [];
21
+ for (const env of envs) {
22
+ const loaded_weights = calculate_loaded_weights_for_env(env);
23
+ apply_loaded_weights_to_env(env, loaded_weights);
24
+ output_envs.push(strip_items_and_bands(env));
25
25
  }
26
+ return build_success_output(output_envs);
26
27
  }
27
28
  /**
28
29
  * Writes loaded axle and container pesoEixos fields on the env vehicle.
@@ -38,10 +39,7 @@ function apply_loaded_weights_to_env(env, loaded_weights) {
38
39
  axle.pesoCarregado = cargo;
39
40
  axle.pesoCarregadoFinal = tare + cargo;
40
41
  }
41
- const containers = env.conteiners ?? env.containers;
42
- if (!Array.isArray(containers)) {
43
- return;
44
- }
42
+ const containers = env.containers;
45
43
  apply_to_containers(containers, loaded_weights, axles);
46
44
  }
47
45
  /**
@@ -96,8 +94,7 @@ function build_peso_eixo_entry(axle, cargo) {
96
94
  */
97
95
  function strip_items_and_bands(env) {
98
96
  const clone = JSON.parse(JSON.stringify(env));
99
- const key = clone.conteiners ? 'conteiners' : 'containers';
100
- const containers = clone[key];
97
+ const containers = clone.containers;
101
98
  for (const container of containers) {
102
99
  container.items = [];
103
100
  container.faixas = [];
package/build/index.d.ts CHANGED
@@ -1,15 +1,23 @@
1
1
  import type { WeightByAxisRequest, WeightByAxisRequestInput } from './core/orchestration/RequestSchema.js';
2
- export type { WeightByAxisRequest, WeightByAxisRequestInput };
2
+ import type { WeightByAxisResponse, WeightByAxisSuccessOutput } from './core/orchestration/WeightByAxisResponse.js';
3
+ import { UnknownItemIdError } from './core/physics/items/UnknownItemIdError.js';
4
+ import { DeflectionModelNotImplementedError } from './core/physics/models/deflection/DeflectionModelNotImplementedError.js';
5
+ import { UnrecognizedSuspensionTypeError } from './core/physics/models/UnrecognizedSuspensionTypeError.js';
6
+ import { UnsupportedVehicleTypeError } from './core/physics/models/UnsupportedVehicleTypeError.js';
7
+ import { ContainerSequenceNotFoundError } from './core/physics/transports/ContainerSequenceNotFoundError.js';
8
+ import { GrouperNotFoundError } from './core/physics/transports/GrouperNotFoundError.js';
9
+ import { MissingContainerError } from './core/physics/transports/MissingContainerError.js';
10
+ export type { WeightByAxisRequest, WeightByAxisRequestInput, WeightByAxisResponse, WeightByAxisSuccessOutput };
11
+ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
3
12
  /**
4
13
  * Calculates loaded cargo weight per axle for each transport in the payload.
5
- * Same contract as WeightByAxisTask.run() in ms-faixas-porcentuais.
6
- * @param json_data - Raw JSON string with `{ envs, config? }`.
7
- * @returns JSON string with `envs` and axle weights, or error payload.
14
+ * @param request - Request with `{ envs, config? }`.
15
+ * @returns Object with `envs` and axle weights.
16
+ * @throws {import('zod').ZodError} When request validation fails (import from `zod`).
17
+ * @throws {UnsupportedVehicleTypeError} When physics rejects the vehicle type.
18
+ * @throws {UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
19
+ * @throws {GrouperNotFoundError} When a merged-container grouper is missing.
20
+ * Other exported error classes are for lower-level physics modules, not thrown here.
8
21
  */
9
- export declare function calculate_weight_on_axles_physics(json_data: string): string;
10
- /**
11
- * @param request - Parsed request with `{ envs, config? }`.
12
- * @returns JSON string with `envs` and axle weights, or error payload.
13
- */
14
- export declare function calculate_weight_on_axles_physics(request: WeightByAxisRequestInput): string;
22
+ export declare function calculate_weight_on_axles_physics(request: WeightByAxisRequestInput): WeightByAxisSuccessOutput;
15
23
  //# sourceMappingURL=index.d.ts.map
@@ -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;AAE9C,YAAY,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,CAAA;AAE7D;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;AAE5E;;;GAGG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAAA"}
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"}
package/build/index.js CHANGED
@@ -1,8 +1,22 @@
1
1
  import { run_weight_by_axis_task } from './core/orchestration/WeightByAxisTask.js';
2
+ import { UnknownItemIdError } from './core/physics/items/UnknownItemIdError.js';
3
+ import { DeflectionModelNotImplementedError } from './core/physics/models/deflection/DeflectionModelNotImplementedError.js';
4
+ import { UnrecognizedSuspensionTypeError } from './core/physics/models/UnrecognizedSuspensionTypeError.js';
5
+ import { UnsupportedVehicleTypeError } from './core/physics/models/UnsupportedVehicleTypeError.js';
6
+ import { ContainerSequenceNotFoundError } from './core/physics/transports/ContainerSequenceNotFoundError.js';
7
+ import { GrouperNotFoundError } from './core/physics/transports/GrouperNotFoundError.js';
8
+ import { MissingContainerError } from './core/physics/transports/MissingContainerError.js';
9
+ export { ContainerSequenceNotFoundError, DeflectionModelNotImplementedError, GrouperNotFoundError, MissingContainerError, UnrecognizedSuspensionTypeError, UnknownItemIdError, UnsupportedVehicleTypeError };
2
10
  /**
3
- * @param input - JSON string or `{ envs, config? }` object.
4
- * @returns JSON string with `envs` and axle weights, or error payload.
11
+ * Calculates loaded cargo weight per axle for each transport in the payload.
12
+ * @param request - Request with `{ envs, config? }`.
13
+ * @returns Object with `envs` and axle weights.
14
+ * @throws {import('zod').ZodError} When request validation fails (import from `zod`).
15
+ * @throws {UnsupportedVehicleTypeError} When physics rejects the vehicle type.
16
+ * @throws {UnrecognizedSuspensionTypeError} When vanderleia suspension type is invalid.
17
+ * @throws {GrouperNotFoundError} When a merged-container grouper is missing.
18
+ * Other exported error classes are for lower-level physics modules, not thrown here.
5
19
  */
6
- export function calculate_weight_on_axles_physics(input) {
7
- return run_weight_by_axis_task(input);
20
+ export function calculate_weight_on_axles_physics(request) {
21
+ return run_weight_by_axis_task(request);
8
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetta/axle-load-calculator",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Merged weight_by_axis physics and orchestration library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "publish:alpha": "npm version --tag-version-prefix=alpha-v",
20
20
  "publish:stable": "npm version",
21
21
  "prerelease:alpha": "npm version prerelease --tag-version-prefix=alpha-v",
22
- "coverage": "c8 --reporter=html --reporter=text npm run test"
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
25
  "zod": "^3.24.0"
@@ -1,6 +0,0 @@
1
- /**
2
- * Validates allocated items do not overlap (Java CollisionChecker parity for tests).
3
- * @param env_vehicle - Env vehicle with containers and items.
4
- */
5
- export declare function check_cargo_collision(env_vehicle: Record<string, unknown>): void;
6
- //# sourceMappingURL=CollisionChecker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CollisionChecker.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/CollisionChecker.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYhF"}
@@ -1,69 +0,0 @@
1
- import { ItemOverlapError } from './ItemOverlapError.js';
2
- /**
3
- * Validates allocated items do not overlap (Java CollisionChecker parity for tests).
4
- * @param env_vehicle - Env vehicle with containers and items.
5
- */
6
- export function check_cargo_collision(env_vehicle) {
7
- const key = env_vehicle.conteiners ? 'conteiners' : 'containers';
8
- const containers = env_vehicle[key];
9
- if (!containers) {
10
- return;
11
- }
12
- for (const container of containers) {
13
- check_container_items(container);
14
- }
15
- }
16
- /**
17
- * Pairwise overlap check for all items in a container.
18
- * @param container - Container with items.
19
- */
20
- function check_container_items(container) {
21
- const items = container.items;
22
- if (!items || items.length <= 1) {
23
- return;
24
- }
25
- for (let i = 0; i < items.length - 1; i++) {
26
- for (let j = i + 1; j < items.length; j++) {
27
- if (items_intersect(items[i], items[j])) {
28
- throw new ItemOverlapError();
29
- }
30
- }
31
- }
32
- }
33
- /**
34
- * @param left - First item.
35
- * @param right - Second item.
36
- * @returns True when 3D boxes overlap (Java Item.intersect axes swap).
37
- */
38
- function items_intersect(left, right) {
39
- const left_box = item_intervals(left);
40
- const right_box = item_intervals(right);
41
- return intervals_overlap(left_box.x, right_box.x)
42
- && intervals_overlap(left_box.y, right_box.y)
43
- && intervals_overlap(left_box.z, right_box.z);
44
- }
45
- /**
46
- * @param item - Item JSON.
47
- * @returns Axis intervals using Java coordinate mapping.
48
- */
49
- function item_intervals(item) {
50
- const x0 = Number(item.x ?? 0);
51
- const width = Number(item.largura ?? item.width ?? 0);
52
- const json_y = Number(item.y ?? 0);
53
- const json_z = Number(item.z ?? 0);
54
- const length = Number(item.comprimento ?? item.length ?? 0);
55
- const height = Number(item.altura ?? item.height ?? 0);
56
- return {
57
- x: [x0, x0 + width],
58
- y: [json_z, json_z + height],
59
- z: [json_y, json_y + length]
60
- };
61
- }
62
- /**
63
- * @param left - Interval.
64
- * @param right - Interval.
65
- * @returns True when open intervals overlap.
66
- */
67
- function intervals_overlap(left, right) {
68
- return left[0] < right[1] && right[0] < left[1];
69
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Raised when allocated cargo items overlap in the same container.
3
- */
4
- export declare class ItemOverlapError extends Error {
5
- /**
6
- */
7
- constructor();
8
- }
9
- //# sourceMappingURL=ItemOverlapError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ItemOverlapError.d.ts","sourceRoot":"","sources":["../../../src/core/orchestration/ItemOverlapError.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC;OACG;;CAMN"}
@@ -1,11 +0,0 @@
1
- /**
2
- * Raised when allocated cargo items overlap in the same container.
3
- */
4
- export class ItemOverlapError extends Error {
5
- /**
6
- */
7
- constructor() {
8
- super('Item overlap error');
9
- this.name = 'ItemOverlapError';
10
- }
11
- }