On-Demand Cartonization
Intro to Box Type Generators
The modern Distribution Center often contains machines that can build custom boxes in real time based on a customer's specific order. These machines help reduce waste and product damage, as well as reduce the chance of incurring dimensional weight fees. The Paccurate API is able to not only replicate this on-demand logic, but greatly improve upon it by integrating cost-awareness. Paccurate's optimized boxes can then be used to inform the box-on-demand machine what size carton to build for the order. Read more about how Paccurate helps Hunter Douglas control their box-cutting machines in this case study.
Using Paccurate with box-on-demand machines requires the use of boxTypeGenerators in the API. Box type generators incorporate many inputs, all of which we will explore here.
Initializing the Box Type Generator List
At its core, boxTypeGenerators is an array of BoxTypeGenerator objects that include the same attributes as any provided boxType, along with the configuration options for the dynamic box creation. Each Box Type Generator (BTG) supports 3 top-level inputs:
- boxTypeDefaults: a box object that contains the default properties of the generated box. Here you would define a name, refId, maxWeight or any additional box property that the generated box should inherit.
- operation: a string, either "cartesian" or "pack-as-is". The "cartesian" option leverages all of the inputs provided in the options object, while pack-as-is is used to generate a box with the exact dimensions of an item placed inside of it (most often used as an optional SIOC carton).
- options: an object that includes several configuration options which will be outlined below. This is where limits, ranges, and pricing thresholds are defined.
Here is an example BTG list with two different configured boxTypeGenerator objects:
{"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "generated-100",
"refId": 100,
"weightTare": 0,
},
"operation": "cartesian",
"options": {
"yRange": {
"min": 3,
"max": 8,
"deriveFromItems": true
},
"xList": [
5.5,
6.5
],
"zRange": {
"min": 9,
"max": 190
}
}
},
{
"boxTypeDefaults": {
"name": "generated-200",
"refId": 200,
"weightMax": 100,
"weightTare": 0,
"itemsPerBoxMax": 3,
"itemsInlineMax": [
1,
2,
2
],
"itemsInlineOverhang": {
"y": 24
}
},
"operation": "cartesian",
"options": {
"yRange": {
"min": 3,
"max": 8,
"deriveFromItems": true
},
"xList": [
5.5,
5.63,
7,
6.5
],
"zRange": {
"min": 9,
"max": 190,
"fitForFirstItem": true
},
"priceComponents": [
{
"key": "oversize",
"metric": "length-plus-girth",
"limit": 1,
"prices": [
0,
1,
53710
],
"thresholds": [
0,
105,
165
]
}]
}
}}
We'll explore what all of the settings mean in further detail below.
Configuring the Box Type Generator
It's easiest to build a Box Type Generator configuration step by step: base properties, operation, and custom properties.
Default Box Properties
The boxTypeDefaults object contains box properties that each generated box will inherit. At minimum, boxTypeDefaults must have a refId property. The API will reject a request if the defaults are missing the refId. It is recommended to also include a name; this will assist in troubleshooting the pack response and help differentiate between different BTG configurations. Below is an example of a typical boxTypeDefaults configuration for a boxTypeGenerator. Some common box packing properties that may be relevant for on-demand boxes can be found in our guide to Item and Box Limits. For more information around what can be added to boxTypeDefaults, visit the BoxProperties page in our schema.
{
"boxTypeDefaults": {
"name": "generated-box",
"refId": 200,
"weightMax": 100,
"weightTare": 0.5
}
}
Choosing an operation
For On-demand cartonization, you will want to use "operation":"cartesian" which leverages all of the inputs and limits. For more information on "operation":"pack-as-is", please visit our guide on Ship-in-own-container.
Generator Parameters & Thresholds
The options for BTGs have 9 available attributes, each with their own subset of parameters. These attributes can be broken out into 4 categories: axis values, metric limits, price thresholds, and trimming. At minimum, a BTG requires an axis value for each axis [x, y, z]. The limits and thresholds offer fine-grain controls to accurately represent things like surcharges and operational constraints. The trimming option determines the level of precision when returning a box dimension.
Axis Value Definitions
There are two ways for Paccurate to adjust a box dimension in a given axis: range and list. In sum, these comprise 6 of the available options: xRange, xList, yRange, yList, zRange, and zList.
🚧 Important
You cannot use multiple value definitions for the same axis. For example, providing both xRange and xList will ignore the list value and use the range. You can use different value definitions for each axis; a range to derive X, a list to derive Y and a list to derive Z, for example.
List: The syntax for a list is straightforward -- it is a list of numbers representing available values for the axis. For example: "xList":[2,4,6] tells the generated box it can only have a height of 2, 4, or 6. No other values would be attempted or returned. Below is an example of how a box making machine with a fixed footprint (12x16) would be represented this way in the API (using lists):
{
"boxTypeGenerators":[
{
"boxTypeDefaults":{
"name":"variable-height-machine"
"refId":100
},
"operation":"cartesian",
"options":{
"xList":[6, 8, 10, 12, 14],
"yList":[12],
"zList":[16]
}
}
]
}
In the example, there are 5 available heights provided to the list. The footprint is fixed, as yList and zList values only include one number.
Range: The syntax for a range definition is more complex. An axis range has 5 configurable attributes:
- min: a number that defines the minimum axis length to generate.
- max: a number that defines the maximum axis length to generate.
- deriveFromItems: a boolean value that when set to true intelligently selects the axis' length based upon the placed item's axis dimension.
- increment : subdivisions between possible values. For example, with a range of 1-10, setting the increment of .5 would generate possible dimensions of 1, 1.5, 2, 2.5 etc.
- fitForFirstItem: a boolean value that if true selects the axis length based upon the first item placed in the generated box. This overrides deriveFromItems.
The config below uses ranges to generate each axis length. The height uses min, max and increment, while the length and width use deriveFromItems.
{
"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "generated-12-18-24",
"refId": 100,
"weightMax": 50,
"weightTare": 0
},
"operation": "cartesian",
"options": {
"xRange": {
"min": 3,
"max": 12,
"increment": 0.25
},
"yRange": {
"min": 10,
"max": 18,
"deriveFromItems": true
},
"zRange": {
"min": 16,
"max": 24,
"deriveFromItems": true
}
}
}
]
}
The generated boxes from the above would have one of 4 different heights, since the generator is incrementing from the min 3 to the max of 12 in units of 3. The length and width values are derived from the items that are added to the box. Pass in some items to this configuration, and you get the results below.
When deciding whether to use lists vs ranges, there are performance impacts to consider. Lists provide a fixed number of possible values to generate on each axis, while ranges allow the potential values to change with varying degrees of precision. Providing an increment to a range helps to control the amount of possible lengths that can be generated, thus improving processing time. It's encouraged to experiment with these settings and mix and match ranges and lists to get performant results when time is critical.
Metric Limits
In addition to the axis length limits we just covered, BTGs support a list of limits for calculated metrics of the generated box size. These limits add boundaries for the generated box sizes not to exceed or fall below. Each limit object has three values:
- metric: a string representing the calculated value to apply the limits to. Available options are volume, surface-area, longest-dimension, middle-dimension, length-plus-girth, and girth
- max: a number representing the maximum acceptable value for the metric
- min: a number representing the minimum acceptable value for the metric
{
"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "generated-12-18-24",
"refId": 100,
"weightMax": 50,
"weightTare": 0
},
"operation": "cartesian",
"options": {
"xRange": {
"min": 3,
"max": 12,
"increment": 0.25
},
"yRange": {
"min": 10,
"max": 18,
"deriveFromItems": true
},
"zRange": {
"min": 16,
"max": 24,
"deriveFromItems": true
},
"limits": [
{
"metric": "length-plus-girth",
"max": 108
},
{
"metric": "volume",
"max": 900
}
]
}
}
]
}
In the configuration above, there is a maximum length-plus-girth (longest dimension value + 2x shortest dimension + 2x second shortest dimension), and a maximum volume value of 900 cubic inches.
Pricing Components
One of the most powerful aspects of boxTypeGenerators is being able to leverage cost as the dimensions of a generated box change. The priceComponents parameter enables this cost-awareness around the calculated box metrics in addition to volume and weight. Things like additional handling fees and oversize charges can be captured here and used in the final generated box size. Each pricing component is comprised of 4 values:
- key: a string associated with a specific cost for the component's corresponding metric. The key does not have to be unique, as it is possible for other metrics to contribute to the same charge.
- metric: a string representing the calculated value to monitor for price implications. Available options are volume, surface-area, longest-dimension, middle-dimension, length-plus-girth, and girth.
- thresholds: a list of number thresholds of corresponding metric above which corresponding prices are triggered.
- prices: a list of integer price values to assign when corresponding thresholds are exceeded.
While the limits option creates box size restrictions on the calculated metrics, pricingComponents uses the threshold values that, when exceeded, incur the additional fee from the corresponding item in the prices attribute.
{
[
{
"boxTypeDefaults": {
"name": "generated-12-24-16",
"refId": 100,
"weightMax": 50,
"weightTare": 0
},
"operation": "cartesian",
"options": {
"xRange": {
"min": 3,
"max": 12,
"deriveFromItems": true
},
"yRange": {
"min": 3,
"max": 24,
"deriveFromItems": true
},
"zRange": {
"min": 3,
"max": 16,
"deriveFromItems": true
},
"limits": [
{
"metric": "longest-dimension",
"min": 3,
"max": 20
}
],
"priceComponents": [
{
"key": "size-change",
"metric": "length-plus-girth",
"thresholds": [
3,
40,
60
],
"prices": [
1,
200,
500
]
}
]
}
}
]
}
Given the configuration above, a box with a length-plus-girth value of 45 would cost $2.00, while a value of 60 or more would cost $5.00. Also included is a limit for longest-dimension of 20.
Below is how Paccurate packs the config above:
As you can see, the single item is less expensive at $2.00, while all of the other items exceed the 40 length + girth threshold, and thus cost $5.00 to ship. These costs are in addition to any other pricing information provided to a request, such as a base box cost, rate table, or dim weight.
Trimming dimensions
By default, when a box size is generated, Paccurate will trim down the dimensions to the bounds of the item inside the box. For example, if a generated box size was 8 x 10 x 12, but the boundaries of the items inside were at most 6.75 x 9.66 x 11.3, Paccurate returns a box size of 6.75 x 9.66 x 11.3. This feature can be turned off in the BTG's options by setting noTrimToMaxExtent to true. If you have configured your generator to make boxes with rounded/fixed dimensional increments, you would want to set "noTrimToMaxExtent":true to make sure the output boxes match the increments you had input. This default feature allow Paccurate to restrict the number of virtual boxes to choose from while still returning a box that precisely contains the item's inner dimensions, enabling precision without sacrificing processing speed.
Targeting & Performance
Depending on your use case for Box Type Generators, it is often essential to control which items are eligible to be packed using the generator. Additionally, if you have multiple generators, you may only want specific items to use a specific generator. This is where the boxTypeDefaults come into play: since each BTG has a specific refId, it is just a matter of creating the appropriate exclude condition for an item, and using the BTG's refId for the targetBoxRefIds array.
Given the example request used earlier, if we add an exclude rule for the smaller items, they are now packed using a boxType in the provided array as opposed to in the BTG. The total cost increases to $20.76 from $17.00.
Here is the full body of the request, including the exclude rule and boxTypes array.
{
"itemSets": [
{
"refId": 1,
"quantity": 3,
"dimensions": {
"x": 10.75,
"y": 11.25,
"z": 10.25
},
"weight": 4,
"name": "Handcrafted Soft Sausages",
"color": "#7a1632"
},
{
"refId": 2,
"quantity": 5,
"dimensions": {
"x": 3,
"y": 4,
"z": 4
},
"weight": 2,
"name": "Generic Wooden Bacon",
"color": "#7c3c74",
"sequence": "NOBOX"
},
{
"refId": 3,
"quantity": 3,
"dimensions": {
"x": 13,
"y": 7.25,
"z": 8.5
},
"weight": 3,
"name": "Awesome Metal Tuna",
"color": "#31724d"
},
{
"refId": 4,
"quantity": 9,
"dimensions": {
"x": 9.75,
"y": 4.5,
"z": 3.75
},
"weight": 2,
"name": "Rustic Soft Bike",
"color": "#382675"
}
],
"boxTypes": [
{
"refId": 0,
"weightMax": 150,
"name": "Example Box 1",
"dimensions": {
"x": 4,
"y": 6,
"z": 8
}
}
],
"rules": [
{
"operation": "exclude",
"itemMatch": {
"property": "sequence",
"expression": "NOBOX"
},
"targetBoxRefIds": [
100
]
}
],
"boxTypeChoiceGoal": "most-items",
"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "generated-12-24-16",
"refId": 100,
"weightMax": 50,
"weightTare": 0
},
"operation": "cartesian",
"options": {
"xRange": {
"min": 3,
"max": 12,
"deriveFromItems": true
},
"yRange": {
"min": 3,
"max": 24,
"deriveFromItems": true
},
"zRange": {
"min": 3,
"max": 16,
"deriveFromItems": true
},
"limits": [
{
"metric": "longest-dimension",
"min": 3,
"max": 24
}
],
"priceComponents": [
{
"key": "size-change",
"metric": "length-plus-girth",
"thresholds": [
3,
40,
60
],
"prices": [
100,
200,
500
]
}
]
}
}
]
}
Take a look at the How To Construct A Rule article and exclude rules examples for more details on how to create and target items and boxes.
A final consideration for when implementing Box Type Generators is performance. Depending on the average quantity of items per order, range of size options, and number of generators in the request, processing times can increase significantly. There are a few ways to control this: first, ensuring that only items that should use the generator are eligible, using the exclude rules mentioned above. Second, if using a range axis value definition, you can decrease the increment value to reduce the number of options available to the range. Third, and perhaps most straightforward, is a request-level option called generatedBoxTypesMax. This option sets a limit for virtual box types to be generated, which is especially helpful if when using wide ranges that could produce thousands of box size options on each request.
For example, setting the max box types to 100 in the example above returns the 20 items in .5 seconds vs just over 3 seconds
Ship in Own Container with Optional Overboxing
For items that can either be packed with other items or shipped in its own packaging, the approach for constructing the request is twofold:
- Create a boxTypeGenerator with "operation":"pack-as-is"
- Create an exclude rule that ensures only relevant items can use the "pack-as-is" generator.
Creating the SIOC Generator
As discussed in the above guide, there are two possible values for a BTG's operation: "cartesian" and "pack-as-is". In the SIOC context we want to use "pack-as-is". The pack as is operation will generate a box that matches the dimensions of the item packed inside of it.
{
"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "pack-as-is-option",
"refId": 1000,
"weightMax": 150,
"itemsPerBoxMax": 1
},
"operation": "pack-as-is"
}
]
}Creating the exclude rule
With the generator created, the next step is making sure that only items that can be shipped as-is are able to use the BTG. This can be done on a per item id rule, or by mapping the SIOC eligibility to an item property like name or sequence.
{
"rules": [
{
"operation": "exclude",
"itemMatch": {
"property": "sequence",
"negate": true,
"expression": "SIOC"
},
"targetBoxRefIds": [
1000
]
}
],
}In the example above, we are leveraging the itemMatch feature of rule creation to make a rule that any item whose sequence does not (note the "negate":true) include the string "SIOC" is excluded from the box with the refId of 1000. The pack-as-is generator has the refID of 1000 in its boxTypeDefaults, enabling it to be targeted in this exclude rule.
Putting it all together
With the generator and the rules created, a full request would look something like this:
{
"itemSets": [
{
"refID": 40002401,
"color": "#ed7707",
"name": "Stand",
"weight": 17,
"dimensions": {
"x": 4.38,
"y": 28,
"z": 23.38
},
"quantity": 1,
"sequence": "SIOC"
},
{
"refID": 460225,
"color": "#5500ff",
"name": "18\\\\" White Butcher Paper",
"weight": 26,
"dimensions": {
"x": 18,
"y": 8.13,
"z": 8.13
},
"quantity": 1
},
{
"refID": 28078103,
"color": "#aaff00",
"name": "Masking Tape 1\\\\" x 60 yd-Case",
"weight": 3,
"dimensions": {
"x": 9.25,
"y": 10,
"z": 9.75
},
"quantity": 2
},
{
"refID": 250370,
"color": "#ff00ff",
"name": "Heavy Duty Black MeatLug",
"weight": 6,
"dimensions": {
"x": 8.75,
"y": 18.5,
"z": 24.5
},
"quantity": 1
}
],
"boxTypes": [
{
"weightMax": 65,
"name": "12x12x4",
"refId": 5,
"dimensions": {
"x": 12,
"y": 12.25,
"z": 4.25
}
},
{
"weightMax": 65,
"name": "Lug",
"refId": 15,
"dimensions": {
"x": 16,
"y": 22,
"z": 25
}
},
{
"weightMax": 65,
"name": "Large Box",
"refId": 20,
"dimensions": {
"x": 22,
"y": 30,
"z": 25
}
}
],
"rules": [
{
"operation": "exclude",
"itemMatch": {
"property": "sequence",
"negate": true,
"expression": "SIOC"
},
"targetBoxRefIds": [
1000
]
}
],
"boxTypeGenerators": [
{
"boxTypeDefaults": {
"name": "pack-as-is-fallback",
"price": -1,
"refId": 1000,
"weightMax": 150,
"itemsPerBoxMax": 1
},
"operation": "pack-as-is"
}
],
"boxTypeChoiceGoal": "most-items"
}In the above sample, there are 4 types of items, one of which is eligible for SIOC. Given the packing goal of "most-items", the whole order packs in one container.
Conclusion
Box Type Generators are a very powerful part of the Paccurate API. They consider and emulate the operational an financial constraints of real box-on-demand machines, and generate a packing solution optimized for those goals and constraints. Those packing solutions, available in the API response can then be used to instruct the hardware on what boxes to create to fulfill the order. We've learned how to create BTGs, set default information, configure range options, incorporate size limits, add pricing components, and even how to optimize performance and create rules to ensure the right items are being sent to the Box Type Generators. Thank you for reading, and as always, any questions, please reach out at [email protected], or visit our forums.