|
Documentation: Automated inference pipeline

Operate

Automated inference pipeline

Ingestion jobs, the candidate state machine, processing runs, the live worklist and result output modes.

Ingestion jobs

An ingestion job tells the control plane which studies to pull and which model to run on them. Jobs are created by owners and admins in the viewer or through POST /v1/inference/ingestion/job/create with dicomModality, containerId, modelId, modelName, modelVersion, modalities (the Orthanc peers to query), intervalInMinutes, scheduleStartTimestamp and scheduleEndTimestamp. Related endpoints: GET /jobs, PUT /job/{id}/update, POST /job/{id}/start, POST /job/{id}/stop, DELETE /job/{id}/remove and POST /jobs/import (CSV) under /v1/inference/ingestion. Every transition is logged with tenant_id, ingestion_job_id, candidate_id, study_instance_uid and request_id; retrieval logs add the Orthanc job IDs and callback logs the study-service job IDs.

Candidate lifecycle

State Meaning
DISCOVERED A remote C-FIND result matched an active ingestion job
GROWING Seen again with changed series or instance counts
STABLE Unchanged for the configured stability window
RETRIEVAL_QUEUED Queued for the retrieval worker
RETRIEVING A retrieval attempt is issuing or waiting on Orthanc jobs
RETRIEVED Present in local Orthanc
DISAPPEARED The study is no longer returned by the remote PACS (reported in the worklist ingestionStatus)
PROCESSING_QUEUED Handed to the processing layer; expected model jobs recorded
PROCESSING At least one expected model job is running
PROCESSED Every expected model job completed
PARTIAL At least one completed and at least one failed, none still queued or running
FAILED Retrieval failed, or every expected processing job failed
CLEANUP_QUEUED, CLEANED Retention cleanup states (planned; the cleanup worker is not yet shipped)

The candidate status column itself holds only the discovery and retrieval values (DISCOVERED, GROWING, STABLE, RETRIEVAL_QUEUED, RETRIEVED, DISAPPEARED, FAILED). Processing is a sibling rollup on the same row, processing_status with processing_status_at (queued, running, completed, partial, failed), so RETRIEVING, the PROCESSING_* states and PARTIAL above are the plan-level view of those two columns together, and the cleanup states are not built yet.

  • The retrieval worker waits for all C-MOVE jobs to finish before dispatch, and studies already in Orthanc are dispatched without a new C-MOVE. The ingestion plan records retrieval attempts in their own table (queued, running, succeeded, failed, timed_out, cancelled; triggered by worker, manual_retry or reconciliation) with the candidate keeping orthanc_job_ids, last_retrieval_state, last_retrieval_error and last_retrieval_checked_at; a manual retry creates a new attempt rather than overwriting prior error context.
  • Before dispatch, api-pacs inspects the registered inference container, starts it if stopped, and polls its model-info endpoint every 500 ms for up to 30 seconds; a container that never becomes ready records a failed execution with a dispatch attention flag.
  • Each dispatch creates an idempotent processing run with one execution per expected model, and only one non-terminal run per study can exist at a time (a partial unique index; a second request returns HTTP 409). Run N+1 preserves prior history, each run freezes its expected-model plan, and executions carry optimistic version numbers that consumers use to discard older events. study-service callbacks are validated and applied atomically and in order; out-of-order, replayed or regressive state is ignored and logged.
  • The reconciliation worker runs once at startup and then every 5 minutes, selects a bounded batch of active runs that are stale (pending after 2 minutes, queued after 10, running after 65 by default) or already need attention, looks the execution up in study-service by stored study_service_job_id, then by processing_run_id, then by candidate, and passes recovered state through the same transition handler as live callbacks. Resolved warnings are cleared, manual ones persist, and three consecutive failures raise RECONCILIATION_FAILED. Per-cycle counts (checked, repaired, failed, unresolved) are exported.

Worklist vocabulary

Axis Values
Run phase QUEUED, PROCESSING, TERMINAL
Run outcome SUCCESS, SUCCESS_WITH_SKIPS, PARTIAL_SUCCESS, NO_RESULT, FAILED, CANCELLED
Run trigger AUTO, MANUAL_REPROCESS, LEGACY_IMPORT (runs reconstructed from the archive before run tracking existed)
Execution status pending, queued, running, completed, failed, skipped, cancelled, with per-run counters expectedModels, pendingModels, queuedModels, runningModels, completedModels, failedModels, skippedModels, cancelledModels, activeModels
Skip reason codes NO_USABLE_DICOM, UNSUPPORTED_MODALITY, REQUIRED_SERIES_MISSING, MODEL_NOT_APPLICABLE, PREREQUISITE_NOT_MET, MODEL_DISABLED (plus an optional message)
Attention codes EMPTY_MODEL_PLAN, INVALID_EXECUTION_STATE, RETRIEVAL_FAILED, DISPATCH_FAILED, EXPECTED_JOB_MISSING (no study-service job was ever correlated), PENDING_STALE, QUEUE_STALE, PROCESSING_STALE, CALLBACK_DEAD_LETTERED, STUDY_SERVICE_JOB_MISSING (a stored job ID no longer resolves), STATE_CONFLICT, RECONCILIATION_FAILED, EMPTY_EXPECTED_PLAN
Worklist ingestionStatus DISCOVERED, GROWING, STABLE, RETRIEVAL_QUEUED, RETRIEVED, DISAPPEARED, FAILED

Live worklist

Endpoint (/v1/inference) Purpose
GET /worklist/status Current study statuses for the tenant: the authoritative REST snapshot, paginated with limit (default 25, maximum 100) and zero-based offset, returning hasMore; studyInstanceUID may be repeated to scope the snapshot to the visible page
GET /worklist/events Server-sent events stream of versioned study_status.updated notifications, published through Redis Pub/Sub so any api-pacs replica can deliver them; heartbeat comments every 20 seconds; no durable replay or Last-Event-ID, so clients refresh the REST snapshot after a reconnect
GET /worklist/studies/{studyInstanceUID}/runs Processing-run history for a study: trigger, pinned model version, startedAt and completedAt, outcome, skip code or error message
POST /worklist/studies/{studyInstanceUID}/reprocess Manual reprocessing run (owner or admin; counts against the user quota; 409 while a run is active). A manual run rechecks local Orthanc and queues a fresh PACS retrieval if the study has expired from the cache, keeping the execution pending until retrieval confirms, even when the originating ingestion job is no longer running
GET /processing/runs/{runId}, GET /processing/runs/{runId}/executions/{executionId}/result Run detail and a completed execution result, tenant-scoped. Results are served with Cache-Control: no-store; result is opaque model-specific JSON; 409 when the execution is not result-ready, 422 when a completed execution holds no valid JSON, 503 when the result service is down; internal study-service job IDs are never exposed
GET /quota The authenticated user allowance and concurrency

The worklist separates three axes: lifecycle (phase), outcome, and attention flags. A model that did not run carries an explicit skip code or failure reason rather than a silent gap. Tenant users can view the worklist; reprocessing stays admin-only. Manual dispatches carry a dispatch_intent and a processing_execution_id: a replay returns the same job with already_present=true, a reused execution ID with different correlation returns 409, a foreign already_present response records DISPATCH_FAILED and refunds the quota unit, and an accepted dispatch with wrong correlation records STATE_CONFLICT and keeps the reservation charged.

Result output modes

Mode What the container returns
JSON A JSON object rendered by the viewer
OHIF_ANNOTATIONS Annotations (segmentations, measurements) drawn in the OHIF viewer
HTML An HTML report page
WEB_APP A packaged web app served under the container /app prefix; the response carries webappPath and webappDataBase64
PDF A PDF file

AI-generated DICOM series can be written back to Orthanc with POST /v1/orthanc/modality/{modalityID}/study/{studyInstanceUID}/series/store (multipart: file, seriesInstanceUIDs as a JSON-encoded array, patientID, patientName, modelName, modelVersion; the file part must carry Content-Type: application/pdf or application/dicom; audited as stored_custom_series). Readers rate any result with a thumbs up or down (feedbackType APPROVE or REJECT); each opens the model fixed questionnaire, approveFeedbackQuestionnaires or rejectFeedbackQuestionnaires (GET /v1/inference/model/{modelId}/feedback, PUT /v1/inference/model/feedback/update, DELETE /v1/inference/model/{modelId}/feedback/remove).

Planned retention cleanup (ingestion plan phase 4, not yet shipped)

The design records cleanup jobs (queued, running, completed, failed, skipped, held) driven by tenant configuration, per-job overrides, study age, processing terminal state, manual holds and legal holds, with three safety rules: never delete while processing, never delete on manual hold, and treat deletion of already-missing Orthanc resources as idempotent. Until it ships, candidate rows persist and Orthanc cache expiry is the only DICOM deletion.

PACS AI Logo status Status Terms and Conditions Privacy Policy Privacy Impact Assessment Document (EFVP)

© 2026 HeartWise AI Lab, Montreal Heart Institute. All rights reserved.