Creating a Job

TL;DR

A Job needs an id and a location. Everything else — demands, time windows, skills, eligibility, mandatory/penalty/revenue — is optional and lets you express increasingly specific business rules. This page walks through each field group with realistic examples.

Location: index vs explicit

Every job needs exactly one of two ways to say where it is:

  • location_index — an integer row/column index into the request's distance/time matrices. Use this when you supply your own travel.distance_matrix/time_matrix.
  • location — an inline coordinate object (lat/lon), used when FG-API computes matrices for you from coordinates.
{ "id": "job-4821", "location_index": 3 }
{ "id": "job-4821", "location": { "lat": 52.3791, "lon": 4.9003 } }

Demands and service time

demands can be a plain integer (single default capacity dimension) or a dict of named dimensions matching the resource's capacities keys:

{
  "id": "job-4821",
  "location_index": 3,
  "demands": { "weight_kg": 120, "volume_m3": 0.8 },
  "service_time": 600,
  "setup_time": 120
}

service_time is the time spent at the stop once the vehicle arrives (and after waiting, if any). setup_time is extra time charged before service can start (e.g. unloading equipment) and is tracked separately in the response.

Time windows

A job can carry one or more time_windows. Each window is { start, end, early_penalty?, late_penalty? }, in the same time unit as the rest of the request (commonly seconds from midnight).

"time_windows": [
  { "start": 32400, "end": 34200 }
]
Hard vs soft windows

With no penalty fields, a window is hard: the solver must arrive inside [start, end] or the job is dropped (reported as time_window_infeasible). Setting late_penalty makes the upper bound soft — the solver may arrive late and pays (arrival - end) × late_penalty in route cost instead of failing. Setting early_penalty does the same for the lower bound: the solver may arrive early and pays (start - arrival) × early_penalty. Setting both makes the window fully soft (a V-shaped cost curve with its minimum inside [start, end]), and the solver is free to arrive any time at a cost.

"time_windows": [
  { "start": 32400, "end": 34200, "late_penalty": 10 }
]

Arriving 5 minutes (300s) late here adds 300 × 10 = 3000 to route cost — the solver compares that against the cost of serving the job some other way, or dropping it and paying its penalty.

Required skills and skill levels

required_skills is a list of skill names a resource must have to be eligible. skill_levels raises the bar to a minimum proficiency per skill (matched against the resource's skill_levels):

{
  "id": "job-4821",
  "location_index": 3,
  "required_skills": ["crane", "hazmat"],
  "skill_levels": { "crane": 3 }
}

A resource is eligible only if it has every required skill at or above the requested level. On the resource side, skill_multipliers can scale this job's service_time up or down depending on which resource is assigned (e.g. a more proficient crew finishes faster).

Vehicle eligibility

allowed_resource_ids restricts a job to a specific subset of resources, independent of skills:

{ "id": "job-4821", "location_index": 3, "allowed_resource_ids": ["truck-1", "truck-2"] }

mandatory, penalty, and revenue

By default mandatory is true: the solver must serve the job or the request is infeasible. Setting mandatory: false lets the solver drop the job and pay penalty instead — useful when you'd rather get a partial solution than no solution at all.

{
  "id": "job-4821",
  "location_index": 3,
  "mandatory": false,
  "penalty": 50000,
  "revenue": 80
}
Worked example: when does the solver drop a job?

Say penalty=50000 and revenue=80 on a job that would cost an extra 12,000 in detour distance/time to insert into the only feasible route. The solver compares: insert it (cost +12,000, but +80 revenue, net +11,920) versus drop it (cost +50,000 penalty, no revenue, net +50,000). Since 11,920 < 50,000, the solver inserts the job — the penalty is far larger than the actual detour cost, so it behaves almost like a hard requirement. If the detour cost were instead 60,000 (e.g. the job is geographically isolated), dropping and paying the 50,000 penalty becomes cheaper, and the solver drops it. revenue is a small offset, not typically the deciding factor — penalty is what governs whether a job survives when capacity or time is tight.

Day (multi-day pinning)

In a multi-day problem (problem.num_days > 1), a job can be pinned to a specific day with day (0-indexed). Omitting day lets the solver choose any day a feasible resource is available:

{ "id": "job-4821", "location_index": 3, "day": 1 }

departure_time: pinning a job as the first stop

departure_time lets an operator say "this vehicle leaves the depot for this specific job at this specific time" — effectively pinning the job as the very first stop of a resource's day. The mechanism has five steps:

  1. Set departure_time and exactly one allowed_resource_ids entry. departure_time requires the job to be restricted to a single resource — FG-API needs to know which vehicle is leaving, so ambiguity is rejected.
  2. FG-API computes depot-to-job travel time. Using the assigned resource's depot location and the job's location, FG-API computes the real travel time between them.
  3. A tight time_windows entry is auto-injected. The window is built around departure_time + travel_time, so the job's effective arrival window is pinned close to the computed value rather than left open.
  4. The resource's time_window.start should match departure_time. If the resource is also free to leave earlier, the solver may use that slack elsewhere; aligning the two avoids surprises.
  5. The solver schedules everything else around the pin. The pinned job's arrival is now effectively fixed; the solver optimizes the rest of that resource's route (and every other resource) around it.
{
  "id": "job-4821",
  "location_index": 3,
  "allowed_resource_ids": ["truck-1"],
  "departure_time": 28800
}
Requires coordinate-based mode

departure_time only works when FG-API is computing travel from GPS coordinates (coordinate-based locations), since it needs to derive a real depot-to-job travel time. It is not meaningful when you supply your own precomputed distance/time matrices.

Full parameter table

NameTypeRequiredDefaultDescription
idstringrequiredUnique job identifier.
location / location_indexobject / intrequiredExactly one of the two must be set.
demandsdict | intoptional0Capacity consumed, single value or per-dimension dict.
service_timeintoptional0Seconds spent servicing the stop.
setup_timeintoptional0Extra seconds charged before service starts.
time_windowslist[TimeWindow]optionalnoneOne or more allowed arrival windows; hard unless penalties set.
required_skillslist[string]optional[]Skills a resource must have to be eligible.
skill_levelsdictoptional{}Minimum proficiency per required skill.
allowed_resource_idslist[string]optionalallRestricts eligible resources.
mandatorybooloptionaltrueIf false, solver may drop the job and pay penalty.
penaltynumberoptional100000Cost charged if the job is dropped (only relevant when mandatory=false).
revenuenumberoptional0Value credited when the job is served; offsets routing cost in scoring.
dayintoptionalanyPins the job to a specific planning day in multi-day problems.
lockedbooloptionalfalsePrevents the solver from moving this job during reoptimization.
lifobooloptionalfalseEnforces last-in-first-out unloading order relative to other stops.
max_wait_before_serviceintoptionalglobalOverrides how long a vehicle may arrive early and wait for this job.
departure_timeintoptionalnonePins the job as the first stop of a single resource's day; requires exactly one allowed_resource_ids entry.
Next steps

FieldGenius VRP API documentation. Generated from the engineering source of truth (.tex docs and app/ source).