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 byworker,manual_retryorreconciliation) with the candidate keepingorthanc_job_ids,last_retrieval_state,last_retrieval_errorandlast_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-infoendpoint 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
versionnumbers 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 byprocessing_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 raiseRECONCILIATION_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.