Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,59 @@ What changed in `@packvium/engine` on npm, release by release. The format follow
[Keep a Changelog](https://keepachangelog.com/1.1.0/) and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.7.0]

A validator and a scene converter join the package, `quality` looks ahead in its block solver,
and the JavaScript `quality` beam does the same search for about a third of the work. No request
or result field changes.

### Added

- **`validate(request, result)`, `MalformedResult` and `ValidatorUnavailableError`.** The
reference validator: a list of issues (`code`, `detail`), empty when the result is valid, and
`MalformedResult` for a result of the wrong shape. It needs the compiled addon; without it,
`validate` throws `ValidatorUnavailableError` (`code: 'validator_unavailable'`) until the
JavaScript engine's own port lands.
- **`@packvium/engine/scene.js`**: `resultToScenes(request, result)` returns one
`packvium-visualization-scene/v1` per packed container; `artifactToScene(artifact, index)` and
`sceneItemId` build one from an operational artifact. TypeScript types included.

### Changed

- **Every schema enum value is refused as `not_allowed`.** A value outside its list in
`allowed_rotations`, the output and dimensional-weight units, `shape_type`,
`ground_contact_rule` or `configuration.solvers` throws `InvalidRequestError` with reason
`not_allowed` at its JSON Pointer, where the JavaScript engine threw a `TypeError`. **`ticks`
as an output unit is now refused**; the schema never listed it.
- **`quality` looks ahead in the block solver.** Each round completes its leading
blocks greedily and commits the best, so the answer is never worse than the greedy pass. On
our benchmark 87 of 89 answers improved and none got worse. Under a tight effort budget the
block start can now report `truncated` / `effort_limit`. `fast` and `balanced` are unchanged.
- **`quality` does the same search for less.** On our 11 pinned instances it takes 37%
of 1.6.0's CPU, `balanced` 95% and `fast` 99%, with byte-identical output on every
conformance fixture.
- **Candidates are ranked by a lexicographic key**, far corner z, y, x, then origin and
rotation; on hull fixtures 10 equal-score layouts changed.
- **A start that repeats another's item-type order runs a neighbouring order instead**,
kept only on a strict win; its record id ends in `+swap-N`. No measured answer changed.
- **Axle-balanced candidate points are offered as a last resort.** On the conformance
and benchmark requests one answer improved and none got worse; on a synthetic axle corpus of
120 requests 27 improved and 4 got worse.
- **`no_eligible_container` is reported `proven`, and `insufficient_support` is reported**
(`observed`) where the support rule is the only blocker.
- **A `nesting_height` not below the item's height is refused as `invalid_value`**, and padded
unit codes such as `' in '` are accepted, as the native addon accepts them.

### Fixed

- **Native-addon errors carry `code`, `reason` and `field`** and are thrown as the same classes
the JavaScript engine throws; they were a plain `Error`.
- **`rebalanceWeight`'s `maxMoves` outside `0..=4294967295` is refused** on both backends, not
wrapped into range.
- **An addon of another release is ignored** and the JavaScript engine answers.
- **`volume_utilization` and `support_ratio` round half-even exactly**, the support ratio is
converted one way everywhere, and the default clock is monotonic.

## [1.6.0]

Boxes cut to measure, flat bags, stacking groups and a volume-first objective in the request.
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type: software
authors:
- name: "Packvium contributors"
website: "https://packvium.com"
version: "1.6.0"
version: "1.7.0"
license: MIT
url: "https://packvium.com"
repository-code: "https://github.com/toxakara/packvium-node"
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,31 @@ A request that is valid but does not fit completely is not an error: the result
left out, and why, in `unpacked_items`. [`examples/errors.mjs`](examples/errors.mjs) walks every
case above.

## Checking a result

*New in 1.7.0.*

`validate(request, result)` checks a result against the request it answers, from the JSON alone:
geometry, support and load, accounting, the result's facts and its score. It is the same
reference validator every Packvium package exports, so it returns the same list.

The validator runs only in the compiled native addon, and the published package does not ship
one yet, so after a plain `npm install` `validate` throws `ValidatorUnavailableError` (code
`validator_unavailable`) instead of answering. The JavaScript fallback's validator is planned
for 1.8. Check `backend()` first:

```js
import { MalformedResult, backend, pack, validate } from '@packvium/engine';

if (backend() === 'rust') {
const issues = validate(request, pack(request)); // [] -- valid
// [{ code: 'collision', detail: 'cube#1 with cube#2 in box#1' }] for two boxes in one place
}
```

It judges any engine's result, not only this package's. A result without the documented shape
throws `MalformedResult`, whose `kind` is `contract` or `shape`.

## Quotes, policy and catalog versions

`commerce` has three functions, all deterministic and all over one document you supply —
Expand Down Expand Up @@ -207,7 +232,7 @@ and execute without a project around it, and each prints what it teaches.
| File | What it shows |
| --- | --- |
| [`basic.mjs`](examples/basic.mjs) | Pack an order, read placements, and see why an item was refused. |
| [`objectives.mjs`](examples/objectives.mjs) | All six objectives on scenes where they genuinely disagree about which container to open. |
| [`objectives.mjs`](examples/objectives.mjs) | Six of the seven objectives (all but `maximum_volume`) on scenes where they genuinely disagree about which container to open. |
| [`constraints.mjs`](examples/constraints.mjs) | Rotations and `keep_upright`, support ratio, top load, stacking caps, incompatible tags and atomic groups — each with and without the rule — and `explainUnpackedItem` turning a refusal into a sentence. |
| [`limits.mjs`](examples/limits.mjs) | Wheel arches as `obstacles` with `additional_boxes`, `max_items` and `tag_limits` per container, and what happens when container stock or `max_containers` runs out. |
| [`trucking.mjs`](examples/trucking.mjs) | A multi-drop van: `stop_index` and a rear door in `access_directions`, two `axles` with load limits, and a safe loading order fed into `buildExecutionPlan`. |
Expand All @@ -229,20 +254,27 @@ node examples/basic.mjs

## Subpath modules

The main entry point has packing, sequencing, explanations, rebalancing and `commerce`. Four
The main entry point has packing, sequencing, explanations, rebalancing and `commerce`. Five
more modules are separate imports, so an application that only packs never loads them:

```js
import { buildExecutionPlan, canonicalPlanJson } from '@packvium/engine/execution.js';
import { buildOperationalArtifact, canonicalArtifactJson } from '@packvium/engine/artifacts.js';
import { exportCsv, exportJson, exportWorkOrderHtml } from '@packvium/engine/artifact-exports.js';
import { deriveRevision, rootRevision, verifyRevisionChain } from '@packvium/engine/revisions.js';
import { artifactToScene, resultToScenes, sceneItemId } from '@packvium/engine/scene.js';
```

`execution.js` turns a result into numbered work-order steps. It never computes a loading order
itself: pass one from `safeLoadingOrder()` as `loadingOrders`, or the plan says its order is
`unavailable` rather than guessing. `artifacts.js` and `artifact-exports.js` package a result for a
system with no engine; `revisions.js` records changes against an approved plan.
`scene.js` turns a result into one drawable scene per container
(`packvium-visualization-scene/v1`): every length an exact tick string beside a display-only `mm`
value, items already rotated, each id spelled from the placement's type, position and orientation. `resultToScenes(request, result)`
returns them all; `artifactToScene(artifact, index)` reads one from an operational artifact, with a
loading sequence when the artifact's plan carries one. `sceneItemId(reference)` spells a plan
step's placement the way its scene item is named, to match one to the other.

## Features

Expand Down Expand Up @@ -281,7 +313,7 @@ Documentation, the constraint reference and the benchmarks are at
| --- | --- | --- |
| Python — [`packvium`](https://pypi.org/project/packvium/) | `pip install packvium` | [packvium-python](https://github.com/toxakara/packvium-python) |
| PHP — [`packvium/packvium`](https://packagist.org/packages/packvium/packvium) | `composer require packvium/packvium` | [packvium-php](https://github.com/toxakara/packvium-php) |
| Rust — [`packvium`](https://crates.io/crates/packvium) | `packvium = "1.0"` | [packvium-rust](https://github.com/toxakara/packvium-rust) |
| Rust — [`packvium`](https://crates.io/crates/packvium) | `packvium = "1.7"` | [packvium-rust](https://github.com/toxakara/packvium-rust) |
| Node.js — [`@packvium/engine`](https://www.npmjs.com/package/@packvium/engine) | `npm install @packvium/engine` | [packvium-node](https://github.com/toxakara/packvium-node) |
| Browser / WebAssembly — [`@packvium/browser`](https://www.npmjs.com/package/@packvium/browser) | `npm install @packvium/browser` | [packvium-wasm](https://github.com/toxakara/packvium-wasm) |
| PHP FFI bridge — [`packvium/native-bridge`](https://packagist.org/packages/packvium/native-bridge) | `composer require packvium/native-bridge` | [packvium-php-bridge](https://github.com/toxakara/packvium-php-bridge) |
Expand Down Expand Up @@ -312,7 +344,7 @@ CodeMeta form.
@software{packvium_node,
author = {{Packvium contributors}},
title = {Packvium for Node.js},
version = {1.6.0},
version = {1.7.0},
license = {MIT},
url = {https://packvium.com}
}
Expand Down
2 changes: 1 addition & 1 deletion artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const FORMAT = 'packvium-operational-artifact/v1';
* The engine's own name is deliberately not recorded: four correct builders naming themselves
* would emit four different documents.
*/
export const SUITE_VERSION = '1.6.0';
export const SUITE_VERSION = '1.7.0';

/** The deterministic part of `result.algorithm`; `duration_ms` is wall-clock time. */
const SOLVER_FIELDS = ['profile', 'solver', 'seed', 'time_limit_reached', 'effort_limit_reached'];
Expand Down
2 changes: 1 addition & 1 deletion codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"@type": "SoftwareSourceCode",
"name": "Packvium for Node.js",
"description": "Deterministic 3D bin-packing and cartonization engine for pallets, containers, trucks and shipping cartons. Exact integer geometry, reproducible results.",
"version": "1.6.0",
"version": "1.7.0",
"license": "https://spdx.org/licenses/MIT",
"author": [
{
Expand Down
25 changes: 13 additions & 12 deletions docs/COMMERCE-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,11 @@ may carry only `tariffs`. Field-level rules:
than leaving it to be inferred from which fields the payload happens to carry.
- An omitted optional field and an explicit JSON `null` mean the same thing: absent.
Writing `"minimum_charge_minor": null` is exactly writing nothing.
- Every integer is exact and, except where a model explicitly allows zero, positive.
The per-field bounds are the ones the models already enforce — see
CATALOG-VERSIONING.md and POLICY-RULES.md.
- Every integer is exact. Dimensions, an item's `weight_g`, `max_payload_g`,
`max_stack_height_mm` and `dimensional_weight_divisor` must be positive; `effective_at`,
`published_at`, `cost_minor`, `minimum_charge_minor`, `fuel_surcharge_permille`,
`flat_charge_minor`, `permille_of_base` and each zone rate in
`cost_per_dimensional_kg_minor` may be zero but not negative.

### Input errors versus rejections

Expand Down Expand Up @@ -322,13 +324,12 @@ original paths and re-export the relocated definitions, so every workspace test,
against the exact same objects. There is one definition of `rate_tariff` in the Python
tree, and the installed wheel contains it. `commerce/rating/objective.py` — the solver
adapter — is untouched and stays a workspace module: it registers in-process scorer and
selector objects, which EXTENDING.md explains are deliberately not
cross-language features.
selector objects, which are deliberately not cross-language features: an in-process
object cannot cross the JSON boundary every other engine is driven through.

## Conformance standard

Held to the standard every other capability in this project is held to, per
TESTING-AND-RELEASE.md:
Held to the standard every other capability in this project is held to:

- **Python and PHP: byte-identical.** Both are ports of one contract; canonical JSON
(sorted keys, `,`/`:` separators, no trailing whitespace) of the result document must
Expand Down Expand Up @@ -364,15 +365,15 @@ TESTING-AND-RELEASE.md:
| `catalog_version_info` | `O(h + e log e)` | `O(e)` |

The `e log e` term is sorting the three id lists; every other bound is a linear scan of
the relevant history. These match the bounds already published for the underlying
models in ALGORITHMS-AND-COMPLEXITY.md — the wrapper
adds parsing and serialization, both linear in the payload, and nothing else.
the relevant history. These are the bounds of the underlying models; the wrapper adds
parsing and serialization, both linear in the payload, and nothing else.

## Limitations

- The document is supplied by the caller. Nothing here fetches, scrapes or embeds any
real carrier's published rates; live rate-card ingestion is still out of scope and
still tracked in LIMITATIONS-AND-ROADMAP.md.
real carrier's published rates. Live rate-card ingestion exists, but in the carrier
connectors, not here: CARRIER-RATE-INGESTION.md builds a
table from a feed, and this API is handed the result like any other document.
- `catalog_version_info` returns metadata and id lists, not the resolved master-data
records. Exporting the full snapshot is a larger surface with its own wire-format
question and is not part of this epic.
Expand Down
4 changes: 2 additions & 2 deletions docs/GUARANTEES.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ silently — if you need them, they belong in your own layer above this library.

## Status of this release

Version `1.6.0` freezes the public API. Field names, status codes, the objective
Version `1.7.0` freezes the public API. Field names, status codes, the objective
vector, the numeric policy and the validation rules do not change without a major
version, so any `1.x` is a safe upgrade from any earlier `1.x`. A caret or tilde
constraint on `1.0` is enough; an exact pin is no longer required.
Expand All @@ -61,7 +61,7 @@ What the freeze does not cover: which of several equally valid packings a solver
returns. That is bounded by the objective vector, not by the placement list, and a minor
release may return a different arrangement with the same or a better score.

The algorithm complexities documented in `ALGORITHMS-AND-COMPLEXITY.md` are asymptotic
The algorithm complexities Packvium states, in these docs and in the source, are asymptotic
design bounds held by review, not per-release measurements. They are not a wall-clock
performance contract either: constants, input shape and host all move the real number.
Use them to choose a solver profile, not to predict a duration.
Loading
Loading