Changelog

Version history for the FieldGenius VRP API, drawn from the project's internal history log. Dates reflect when each change shipped during initial development (April-June 2026). Earlier phases (core solver bootstrap, adapter architecture, CI/CD packaging) are summarized briefly; later releases are described in more detail because they map directly to documented, externally visible behavior.

Two repositories

FieldGenius development spans two repositories: fieldgenius-api (the core VRP solver service, the subject of this documentation site) and derudder-adapter (a thin client-specific adapter that is not part of this public API surface). Entries below are tagged by which repository they affect.

v2.4.0 — Recurring Jobs: Seconds-Only Wire Format (7 September 2026)

fieldgenius-api

Breaking change: recurring-jobs endpoints no longer accept or return minutes

The /api/v1/recurring-jobs endpoints previously converted service_time and tw (time window) values between minutes on the wire and seconds internally. That conversion has been removed. service_time and tw.start/tw.end are now sent and received in seconds, matching every other timing field in the API (time_limit_seconds, a regular job's service_time, and so on). A request that previously sent "service_time": 20 to mean 20 minutes must now send 1200. Any integration still sending minutes-scaled values will register patterns with durations and time windows 60 times shorter than intended.

  • Affects POST /api/v1/recurring-jobs, PATCH /api/v1/recurring-jobs/{id}, GET /api/v1/recurring-jobs/{id}, and POST /api/v1/recurring-jobs/expand.
  • Fleet shift/time-window values supplied via a pattern's resources array are also interpreted as seconds now, consistent with the rest of the change.
  • This removes a class of bug where a value was silently converted twice (once in the recurring-jobs router, once elsewhere) or not at all, depending on the code path.
  • The Recurring Jobs endpoint reference has been updated to document seconds throughout, including corrected example payloads.

v2.4.0 — Gateway Authentication (August 2026)

fieldgenius-api

Breaking change: every /api/v1/* request now requires two headers

All requests to the VRP, async jobs, and travel-matrix routers (/api/v1/optimize/*, /api/v1/jobs/*, /api/v1/travel) must now include both X-API-Key (identifying the calling application) and X-Client-Key (identifying that application's specific end customer, scoped to it). Previously these routes had no authentication at all. A request missing either header now receives 401 Unauthorized; a request with an invalid key receives 403 Forbidden. /health is unaffected and remains open. Existing API keys were migrated so already-deployed integrations did not need to be reissued new credentials, but every caller now needs to send both headers where previously none were required.

  • Caller identity is verified against a shared callers table, with each end-customer key scoped to the specific application key that owns it — a leaked X-Client-Key cannot be replayed on its own.
  • Both headers are re-verified independently by the API on every request; a request is never trusted on an upstream application's say-so alone.
  • Async job records created via /optimize/async and /optimize/replan/async now carry a client_id, recording which caller submitted each job.
  • This authentication layer does not apply to the separate /api/v1/recurring-jobs endpoints, which continue to use their own X-API-Key-based tenant scheme (see the Recurring Jobs endpoint reference).

v2.4.0 — Request Result Cache (July 2026)

fieldgenius-api

  • Identical requests to /optimize/sync, /optimize/async, /optimize/reoptimize, /optimize/evaluate, and /optimize/replan/async are now recognized and served from a cache instead of re-solving from scratch. Two textually-identical requests (same body, same solver logic version) return the same result; the second call is effectively instant.
  • An async request that duplicates one already in progress is attached to that same job instead of starting a redundant solve.
  • Controlled by RESULT_CACHE_ENABLED (on by default) and RESULT_CACHE_TTL_SECONDS (default one hour, after which a repeat request re-solves). Requires a REDIS_URL to be configured. If the cache is ever unreachable or misbehaving, requests simply solve normally — caching failures never surface as errors to callers.
  • /optimize/replan (synchronous) is not covered by this cache; its async counterpart, /optimize/replan/async, is.

v2.4.0 — Live Solve Progress Tracking (31 July 2026)

fieldgenius-api

  • Async solves can now be watched while they run. Set options.live_tracking = true on a POST /optimize/async request, and subsequent GET /jobs/{job_id} polls return a progress field (current best cost, served/unserved job counts) while the job is still running. Default is false; requests that don't opt in are unaffected.
  • POST /jobs/{job_id}/cancel on a live-tracked job now returns the best solution found so far as result, instead of always returning null, provided the solver had reached at least one snapshot before the cancel request arrived. The salvaged result is marked with a warning identifying it as a partial, mid-solve solution.
  • Not available for the synchronous endpoints (/optimize/sync, /optimize/replan, /optimize/reoptimize), which hold a single open connection with no cancel mechanism to attach progress or salvage to.

v2.4.0 — Durable Job Queue (16 July 2026)

fieldgenius-api

  • Async solve jobs (/optimize/async, /optimize/replan/async) are now persisted to a database as they are created, started, and completed, instead of living only in the API process's memory. If the API process restarts or crashes while jobs are queued or running, those jobs are automatically recovered and resubmitted on startup, and a client polling GET /jobs/{job_id} continues to get a valid answer under the same job ID — no client-side retry logic needed.
  • Off by default, controlled by JOB_PERSISTENCE_ENABLED. When disabled, behavior is unchanged from before this feature existed.
  • A job recovered after a crash is retried automatically, capped by JOB_MAX_AUTO_RETRIES (default 1), so a request that reliably crashes the process cannot loop forever across restarts.
  • This feature covers the async endpoints only; synchronous requests (/optimize/sync, /optimize/replan, /optimize/reoptimize) hold one HTTP connection for the life of the request and have no "come back later" step for a durable record to serve.

v2.3.0 — Multi-Phase Solver (9 June 2026)

fieldgenius-api

  • Added sequential solver phases: options.phases accepts a list of SolverPhase objects, each with its own time_limit_seconds, objective, and local_search_metaheuristic. Each phase warm-starts from the previous phase's solution.
  • Phase failure resilience: if a phase returns no solution, the solver retains the previous best and continues. The final response always contains the best solution found across all phases.
  • Backward compatible: phases defaults to null; requests that do not set it are unaffected.
  • This is the mechanism the auto-optimizer (options.auto_configure) builds on when it generates compaction/intensification/diversification phase plans.

v2.1.0 — Mandatory Job Disjunction Fix and Out-of-Region Validation (8 June 2026)

fieldgenius-api

Bug fixed: false INFEASIBLE for mandatory jobs with tight time windows

The solver requires every node in the routing model to be registered with a disjunction call so constraint propagation can correctly reason about reachable nodes. Mandatory jobs were registered without a disjunction, which blocked propagation under tight time windows and caused the solver to incorrectly report the problem as infeasible. Reproducible on Solomon and Homberger R/RC benchmark families.

  • Fix: all jobs, mandatory and optional alike, now receive a disjunction registration. Mandatory jobs use a fixed penalty of 100,000,000 so the solver never voluntarily drops them, while propagation still functions correctly.
  • Added a post-solve guard in the orchestrator: if any mandatory job is missing from the solution, the response status is escalated to infeasible.
  • Benchmark impact: five previously infeasible (0% served) instances now solve correctly.
  • Out-of-region coordinate validation: a bounding box check (Belgium: latitude 49.4-51.6, longitude 2.3-6.5) now runs before every matrix computation. Any out-of-region coordinate returns a structured 422 validation error identifying the offending location index, coordinates, and label. Implemented in app/services/vrp/zone.py. This check only applies to GPS-coordinate (profile-based) requests; requests that supply explicit distance_matrix/time_matrix are unaffected.
  • CI/CD: Docker image pruning (docker image prune -af) and disk-usage logging (docker system df) added to the deploy job to prevent disk exhaustion from accumulated unused images.

v1.1.0 – v1.3.0 — CVRPLIB Benchmark Expansion (2 June 2026)

fieldgenius-api

  • Added a 55-instance CVRPLIB benchmark suite across four families: Solomon 25, Solomon 100, Homberger 200, Homberger 400.
  • Known-optimal values recorded per instance; gap percentage reported in both terminal output and generated HTML reports.
  • Version progression from v1.0.0 to v1.3.0 was driven entirely by solver tuning informed by this benchmark suite.
  • Git LFS configured for large benchmark JSON fixture files.

v1.0.0 — First Stable Release (1 June 2026)

fieldgenius-api derudder-adapter

Bug fixed: scheduled_start semantics

A client reported that jobs with a scheduled_start time always came back infeasible, even when the time window looked correct. The adapter had translated scheduled_start into a hard time_windows entry; the API then computed travel time and tried to meet the pinned arrival exactly, which was usually impossible. The correct semantics are that scheduled_start is a departure time, not an arrival time. This is the same departure-time mechanism documented for the Job.departure_time field in the OptimizationRequest schema.

Bug fixed: infeasible status swallowed

The adapter returned HTTP 200 with an empty body when the solver found no solution. Fixed by adding a status field that propagates through to callers, so an unsolvable request now visibly returns "status": "infeasible" instead of an empty success response.

  • Version bumped to 1.0.0.
  • Matrix service limits restored to support travel matrices of 100+ locations.
  • The solver timeout grace period (the extra time the API waits beyond options.time_limit_seconds before giving up, currently 120 seconds) was restored after having been accidentally reduced to 10 seconds.
  • Benchmark README auto-update wired into CI.

Pre-1.0.0 — Initial Development (23 April - 27 May 2026)

fieldgenius-api derudder-adapter

Summarized; see the project's internal history log for full detail on each phase.

  • 23 April: Core solver bootstrap. Appointments, driving/work breaks, break visibility in responses.
  • 24 April: Extended constraints. max_wait_before_service, job cancellation, auto-matrix computation from GPS coordinates.
  • 27 April: Adapter bootstrap. derudder-adapter architecture established, separated from the core solver, initial documentation.
  • 30 April: Deployment prep. Dual-scenario CI/CD pipeline, Docker packaging.
  • 11-18 May: Logging and departure-time handling. departure_time mechanism introduced, FG request/response dumps for debugging, a missing intervention-type price bug fixed (client-reported: caused an HTTP 500, fixed by defaulting to zero price plus a warning log).
  • 19-20 May: Contract tests. Adapter contract test suite added to CI as a regression gate.
  • 20-26 May: Auth and identifiers. Dual ID scheme, parent_id support, API key authentication, dedicated unserved list in the response (client-reported: unserved jobs were previously silently discarded with no way for the caller to re-queue them).
  • 26 May: Benchmarks. Solomon C101 baseline established.
  • 27 May: Two more client-reported bugs fixed: a UTC offset bug that caused shifts to end at 18:00 Brussels time instead of the intended 16:00 (shift moved to 05:00-14:00 UTC to compensate), and mandatory jobs silently vanishing when two jobs shared identical GPS coordinates (fixed by assigning a unique location index per job, i.e. location deduplication was masking distinct jobs as one).

Documentation note

Source code is the source of truth

This documentation site is generated primarily from the project's internal documentation set, cross-checked against the actual request/response models and route handlers in fieldgenius-api/app/. Where the two disagreed (for example, the API reference document names error codes NOT_FOUND/CONFLICT that do not exist in app/core/errors.py, where the real names are JOB_ID_NOT_FOUND and JOB_ALREADY_TERMINAL), this site follows the source code. See Errors for the verified list.