Solver as a Service
Sign Up Now →Sign Up Now

Pinning and repair: fix some placements, solve the rest

Preview

A planner often knows part of the answer already. Some shipments are loaded, some bookings are confirmed, and only the rest is open. Pinning says which placements must hold and lets the solver place everything else around them. Repair is the same request run the other way: keep yesterday’s plan except where a disruption forces a change.

Whether a problem type can be pinned is declared in its manifest, in the decision block next to the entities and groups being pinned:

decision:
  shape: assignment
  entities: {name: shipments, source: shipments, identity: id}
  groups: {name: containers, source: containers, identity: id}
  domain:
    multiplicity: one_to_many
    total_over_entities: true
    total_over_groups: false
  pinning:
    modes: [frozen, forbid, prefer]
    granularity: entity
    prior_plan: true
    change_cost: {default: 10.0, min: 0.0}
    ui: {label: "Lock to container"}

GET /api/v1/manifests/{problem_type} returns this block, so a UI or an agent can tell before calling whether it may offer a lock and what to call it. The packaged container_logistics, booking_consolidation and order_fulfillment manifests declare it.

A request pins placements in its problem data. Each pin names an entity, a group and a mode:

  • frozen: the entity must be in this group.

  • forbid: the entity must not be in this group.

  • prefer: keep the entity in its prior_plan group unless moving it is worth change_cost.

  • free: solve the entity normally. Use it to release one entity from pin_default.

prior_plan maps entity ids to group ids, and pin_default sets the mode for every prior-plan entity without its own pin. This request keeps the prior plan, moves only what it must, and keeps S1 off container C1:

{
  "containers": [
    {"id": "C1", "destination_port": "PORT_A", "departure_day": 1, "arrival_day": 5, "capacity_volume": 10.0, "capacity_weight": 1000.0},
    {"id": "C2", "destination_port": "PORT_A", "departure_day": 2, "arrival_day": 6, "capacity_volume": 10.0, "capacity_weight": 1000.0},
    {"id": "C3", "destination_port": "PORT_B", "departure_day": 1, "arrival_day": 4, "capacity_volume": 10.0, "capacity_weight": 1000.0}
  ],
  "shipments": [
    {"id": "S1", "destination_port": "PORT_A", "ready_day": 0, "required_by_day": 10, "volume": 3.0, "weight": 100.0},
    {"id": "S2", "destination_port": "PORT_A", "ready_day": 0, "required_by_day": 10, "volume": 3.0, "weight": 100.0},
    {"id": "S3", "destination_port": "PORT_A", "ready_day": 0, "required_by_day": 10, "volume": 3.0, "weight": 100.0},
    {"id": "S4", "destination_port": "PORT_B", "ready_day": 0, "required_by_day": 10, "volume": 3.0, "weight": 100.0}
  ],
  "prior_plan": {"S1": "C1", "S2": "C1", "S3": "C2", "S4": "C3"},
  "pin_default": "prefer",
  "change_cost": 1000.0,
  "pins": [
    {"entity": "S2", "group": "C1", "mode": "frozen"},
    {"entity": "S1", "group": "C1", "mode": "forbid"}
  ]
}

The answer moves S1 to C2, the only other container bound for its port, and keeps every other placement. The result adds a pins section:

  • frozen_honored, forbid_honored, prefer_kept and prefer_moved count what happened to each kind of pin.

  • changes_vs_prior counts entities that left their prior-plan group.

  • placement_provenance marks every entity frozen, kept, moved, new (not in the prior plan), free or unplaced.

A frozen pin is held by construction: the solver starts with the entity in its group and refuses every move that would take it out. The pins are also scored as eligibility rules, so the feasibility certificate checks them like any other rule. A pin never overrides a rule. If the frozen pins alone break one, for example by overfilling a container or sending a shipment to the wrong port, the request is refused before any search runs.

Every refusal is a typed 422 naming each offending id:

  • pinning_not_supported: the manifest declares no pinning, or not this mode or prior_plan. Pins are never silently ignored.

  • invalid_pin: an unknown entity or group, an incomplete pin, a reserved _pin attribute name in the records, or a prefer pin with no change_cost declared or sent.

  • conflicting_pins: one entity pinned two incompatible ways.

  • invalid_prior_plan: a prior plan that names entities or groups not in the problem. A stale prior plan is refused, not trimmed.

  • pinned_set_infeasible: the frozen pins alone break a hard or eligibility rule. The error names each rule and group.

Pinning runs on the manifest engine, for one group per entity. Rostering keeps its own frozen_assignments input for now, and routing problems (vrp, field_service) cannot be pinned yet.