> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Cost modeling

> Represent cost in real currency with `cost`, piecewise-linear cost functions, and custom objectives via `customCost`.

<Tip>
  `cost`, `minimizeCosts`, and custom objectives are deliberately powerful, but only a minority of plans need them. Before reaching for this page, check whether a standard objective (`minimizeDistance`, `minimizeWorkingDuration`, `minimizeResources`, and so on) already expresses the trade-off you're after — see [Built-in objectives](/concepts/objectives#built-in-objectives).
</Tip>

## The `cost` object

A resource's `cost` object accumulates several independent contributions into a single currency figure that the `minimizeCosts` objective then optimizes: `using` (a flat cost per resource used), `workedHours`, `km`, `costsByStopTag`, `costsByCapacity`, `costPerCapacityPerTravelledKm`, and `costPerCapacityPerTravelledHour`.

These fields reformulate, in a real currency, the same underlying quantities that `minimizeDistance`, `minimizeWorkingDuration`, and `minimizeResources` already count in raw units (km, hours, vehicles). This isn't a strict one-to-one equivalence — it's a different objective, built on a monetary version of the same data, not a guaranteed substitute for the raw-unit objectives.

## Piecewise-linear cost functions

Every cost field preceding this section except `using` accepts a `CostFloorsAndCoeffs`-shaped object: a `constantCost` base, a `costCoeff` rate applied past `costFloor`, and an optional steeper `overcostCoeff` rate past a second, higher `overcostFloor` — the same shape as a salary with a base, a free allowance, and an overtime-style rate beyond it. [Modeling advanced constraints](/guides/advanced-constraints#tolerated-capacity-overflow) already has a worked `costsByCapacity` example using this object, to make capacity overflow on delivery-only tours economically unattractive rather than infeasible.

`costFloor` is the threshold below which `costCoeff` doesn't apply; `constantCost` is a fixed amount charged regardless of it — so a guaranteed minimum comes from `constantCost`, not from `costFloor`. `costsByCapacity` works on any capacity the stops carry (`weight`, `revenue`, a counter such as `nbDeliveries`…); the resource doesn't need to declare it. The cost is computed on the quantities handled at the tour's stops, not on the vehicle's load: each stop adds the absolute value of its quantity for that capacity, whatever its `kind` or sign. Loading at departure isn't a stop and doesn't count, so a delivery counts once; a pickup and a delivery of the same 100 units count 200. With `stopTags`, only stops carrying one of those tags count. `costFloor` and `overcostFloor` then apply once, to that tour total. On a delivery-only tour, the total equals the load at departure.

## Guaranteeing a minimum per-tour revenue via `costsByCapacity`

Create a capacity that only counts deliveries rather than any real load — for example `deliveredUnits`. The cost doesn't need it declared on the resource; declare it there with a ceiling high enough never to bind only if you want to read it back in the tour's `filledCapacities` values. Set it on every delivery stop (`1` to count stops, or the number of units delivered there) and on no other stop, then put a `constantCost`/`costFloor` on that capacity so a tour that ends below the threshold keeps paying a flat penalty:

```json theme={null}
{
  "resources": [
    {
      "id": "subcontractor-van-1",
      "capacities": { "weight": 800, "deliveredUnits": 500 },
      "cost": {
        "costsByCapacity": {
          "deliveredUnits": { "constantCost": 200, "costFloor": 50, "costCoeff": -4 }
        }
      }
    }
  ],
  "orders": [
    {
      "id": "order-1",
      "stops": [
        { "type": "single", "id": "delivery-1", "position": { "lon": 2.3522, "lat": 48.8566 }, "kind": "delivery", "capacities": { "weight": 12, "deliveredUnits": 1 } }
      ]
    }
  ],
  "objectives": ["maximizeMandatoryStops", "minimizeCosts"]
}
```

With these figures, a tour pays a flat 200 up to 50 delivered units; past that, each extra unit lowers the cost by 4, so it reaches 0 at 100 units. Nothing stops it there: past 100 units the contribution keeps falling below zero and acts as a bonus, so size `costCoeff` and `costFloor` accordingly.

## Rebuilding the default objective list with `customCost`

A `CustomObjective` (`type: "custom"`) lets you optimize a cost expression built from `costsByResourceTag` instead of a built-in `ObjectivesEnum` entry — for example scoping a custom fuel-cost objective to just the resources carrying a given tag:

```json theme={null}
{
  "objectives": [
    {
      "type": "custom",
      "name": "minimizeFuelCost",
      "direction": "minimize",
      "costsByResourceTag": {
        "subcontractorA": { "km": { "costCoeff": 0.18 } }
      }
    }
  ]
}
```

`name` must not collide with a built-in `ObjectivesEnum` value (the schema enforces this). `costsByResourceTag` maps a `Cost`-shaped object to a resource tag, so a custom objective can apply to a subset of the fleet rather than every resource.

## Modeling revenue or margin per stop

`costsByStopTag` with negative `constantCost` values represents a revenue (a negative cost) rather than a charge, combined with `minimizeCosts`. It sits in the resource's `cost`, and each entry applies once for every visited stop carrying that tag:

```json theme={null}
{
  "resources": [
    {
      "id": "van-1",
      "cost": {
        "costsByStopTag": {
          "standard": { "constantCost": -30 },
          "premium": { "constantCost": -63 }
        }
      }
    }
  ],
  "orders": [
    { "id": "order-1", "stops": [{ "type": "single", "id": "stop-1", "position": { "lon": 2.3522, "lat": 48.8566 }, "tags": ["standard"] }] },
    { "id": "order-2", "stops": [{ "type": "single", "id": "stop-2", "position": { "lon": 2.37, "lat": 48.84 }, "tags": ["premium"] }] }
  ],
  "objectives": ["maximizeMandatoryStops", "minimizeCosts"]
}
```

Here `van-1` earns 30 for serving `stop-1` and 63 for serving `stop-2`.

<Warning>
  If you'd rather reason entirely in positive revenue figures, use a `CustomObjective` with `direction: "maximize"` and positive `costsByStopTag` values instead of negating them under the built-in `minimizeCosts` — there is no built-in `maximizeCosts` objective in `ObjectivesEnum`, only `minimizeCosts`. The two approaches aren't interchangeable as they stand. `objectives` is a strict priority order, so put the custom objective at the exact position `minimizeCosts` held (third in the default list, after `maximizeMandatoryStops` and `minimizeDelay`); anywhere else, the engine trades revenue off against different objectives and can return a different plan. Also adjust whatever thresholds you've set elsewhere to the sign change.
</Warning>

## See also

* [Objectives and how they're ranked](/concepts/objectives#the-default-list) — where `minimizeCosts` sits in the default objective order.
* [Modeling advanced constraints](/guides/advanced-constraints#tolerated-capacity-overflow) — the existing `costsByCapacity` worked example for tolerated overflow.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.