Pinning and repair: fix some placements, solve the rest
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 itsprior_plangroup unless moving it is worthchange_cost. -
free: solve the entity normally. Use it to release one entity frompin_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_keptandprefer_movedcount what happened to each kind of pin. -
changes_vs_priorcounts entities that left their prior-plan group. -
placement_provenancemarks every entityfrozen,kept,moved,new(not in the prior plan),freeorunplaced.
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 orprior_plan. Pins are never silently ignored. -
invalid_pin: an unknown entity or group, an incomplete pin, a reserved_pinattribute name in the records, or apreferpin with nochange_costdeclared 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.