Hard constraints
A hard constraint rules out any solution that violates it. If the engine cannot find a way to serve a stop without breaking one, that stop is left unserved rather than the constraint being bent. Typical hard constraints in the Kardinal model:authorizedTimeWindows— the engine will never plan a stop outside these windows. If none of a stop’s authorized windows can be reached, the stop is unserviceable. This is why the documentation recommends making authorized windows as wide as realistically possible: they define the outer bound of what’s even considered a valid visit, not a target.requiredSkills— a resource must have every skill an order requires, or it cannot be assigned to it (e.g. a tailgate-truck requirement).- Capacities — a stop’s
capacitiesmust fit within what a resource can carry (and what it has left after prior stops); there is no partial match. - Order structure —
successiveStops(stops of an order must be visited back-to-back),maxStopSpan(maximum time between a pickup and its delivery), andremovalStrategy: "lifo"(a resource can only unload the last thing it loaded) are all structural constraints the engine cannot relax. workingTimeWindow/maxWorkingDuration/maxDistanceInKmon a resource — bounds on when and how much a resource can work, which the engine will not exceed.
A “contractual” window is not automatically a hard one
It’s tempting to model a client-facing delivery window asauthorizedTimeWindows because it’s contractual — but “contractual” and “hard” answer two different questions. The question that decides which one to use isn’t “is this window written into an agreement?”, it’s “what should happen if the fleet can’t hit it?”
- If missing the window is undesirable but not disqualifying — a school delivery running 20 minutes late is still worth making — model it as
preferredTimeWindows. The engine will try to hit it and count any miss asdelay, but it won’t drop the stop just because it can’t make the window exactly. - Only use
authorizedTimeWindowswhen a visit outside the window is truly unserviceable — a site that’s physically closed outside those hours, a security checkpoint that won’t admit a vehicle early, a customer who will refuse the delivery.
authorizedTimeWindows; the second is preferredTimeWindows built from the exact same hours. Don’t infer which one applies from the mere presence of an opening-hours column, and don’t treat “the site has hours” and “the site enforces those hours as a hard cutoff” as the same claim — they aren’t. If nothing in the data or the client context states the actual dispatch tolerance (an explicit penalty for a late arrival, a fact like “closes and locks the gate”, a confirmed refusal policy), that tolerance is exactly the kind of open question this page already asks you to check (“what should happen if the fleet can’t hit it?”) — treat it as unresolved and default to preferredTimeWindows rather than assuming hard.
Getting this wrong in the hard direction has a silent, expensive failure mode: every stop whose contractual window can’t be reached exactly is dropped and reported as an unaffectedStopIds entry instead of being delivered late. If most or all of your delivery windows come from a contract or SLA, default to preferredTimeWindows and confirm with the business which windows, if any, are truly non-negotiable — don’t assume “contractual” implies “hard.” See Handling infeasibility for how to recognize this failure pattern once it happens.
Soft constraints
A soft constraint has a cost, not a wall. The engine will try to satisfy it, but will accept a violation if that’s what it takes to do better on a higher-ranked objective.preferredTimeWindows— if a stop can’t be reached inside its preferred window, it isn’t dropped: the gap between the planned time and the window is counted as delay, which theminimizeDelayobjective then tries to reduce. The engine automatically intersects authorized and preferred windows, so a preferred window is only ever a tighter target inside an authorized one, never a way to widen it.lateDeparture: false(the default) — the engine prefers starting tasks as early as possible, which can create idle time between non-contiguous windows; this is a scheduling preference, not a rule.priorityon resources and orders — priority is a soft, graduated dial rather than a binary include/exclude flag. Lower numbers matter more (priority0outranks priority3); the engine will drop any number of lower-priority resources or orders to protect higher-priority ones, but nothing is hard-coded as “always excluded.” Marking an order"optional": trueis a simpler, coarser version of the same idea.
How the engine arbitrates conflicts
Two mechanisms do the arbitration, and both come from how the objectives are ordered:- Lexicographic objectives decide what “better” means when trade-offs are unavoidable — for instance, whether protecting mandatory stops matters more than minimizing delay, or the reverse, depending on how you’ve ordered your
objectiveslist. - Priority elimination decides who gets sacrificed first when something has to give — the lowest-priority resources or orders absorb the impact before higher-priority ones are touched.
Why a stop, not always the problem, becomes infeasible
Kardinal’s model rarely declares an entire plan infeasible as a single event. Instead, the engine returns a solution that is valid for everything it could place, and reports the rest explicitly:unaffectedStopIds— stops that could not be planned within the hard constraints (no authorized time window reachable, no resource with matching skills/capacity/availability, etc.).unusedResourceIds— resources that ended up with nothing assigned to them, generally becauseminimizeResourcesoutranks using them, or because none of the remaining orders match their constraints.tours[].isValid— a per-resource flag reflecting whether that specific tour respects all hard constraints.
200 with a solution, just one where some stops or resources are left out. Diagnosing which hard constraint caused a given stop to end up in unaffectedStopIds is covered in the Handling infeasibility how-to guide.
