Belgian Cancer Registry on FHIR
0.1.0 - ci-build
Belgian Cancer Registry on FHIR - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
This page describes a FHIR-native submission channel for the clinical cancer
registration (Stream 1 on the Data flow page). Instead of manual
web entry or HIS batch extraction through WBCR, a hospital system submits the
completed registration form to a national FHIR server, which validates it
asynchronously and reports the result back on a FHIR Task.
This is a forward-looking design proposal. It is draft and must be confirmed with BCR before any production use.
status = ready — to track the obligation to register the case; its focus points back at the Condition.in-progress.Task.output as an OperationOutcome, and the validation Task's status is set to completed or failed.completed).| Actor | Role |
|---|---|
| Coordinating physician / HIS | Completes the form, submits, corrects and resubmits. |
| Belgian Cancer Registry | Imposes the registration obligation; requester of the registration Task. |
| National FHIR validation service | Receives submissions, runs background validation, owns the validation Task. |
A submission that can fail and be resubmitted looks, at first glance, like it
needs a Task whose status cycles back to an earlier state. It does not — and
the FHIR community is consistent that it should not. The Task state machine in
the specification is
illustrative and effectively forward-only: once a Task reaches a terminal state
(completed, failed, cancelled, rejected), it stays there. The idiomatic
way to "go back and redo" is a new Task, not a rewound status.
Two status axes are kept separate:
QuestionnaireResponse.status — state of the form data. This first version does not rely on it (in particular it does not use amended): a correction is simply a new submission with a fresh BCRValidationTask.Task.status — state of the validation/registration workflow.And the workflow is split into two Tasks so neither one ever has to move backwards:
flowchart BT
C["<b>Condition</b><br/>the cancer case"]
R["<b>BCRRegistrationTask</b><br/>registration obligation · long-lived"]
V1["<b>BCRValidationTask</b><br/>attempt 1 — failed"]
V2["<b>BCRValidationTask</b><br/>attempt 2 — completed"]
R -->|focus| C
V1 -->|partOf| R
V2 -->|partOf| R
click C "Condition-ExampleBCRCancerCondition.html" "Cancer case (example)" _blank
click R "StructureDefinition-bcr-registration-task.html" "BCRRegistrationTask profile" _blank
click V1 "Task-ExampleBCRValidationTaskFailed.html" "Failed attempt (example)" _blank
click V2 "Task-ExampleBCRValidationTaskAccepted.html" "Accepted attempt (example)" _blank
classDef case fill:#fef3c7,stroke:#d97706,color:#7c2d12;
classDef parent fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#1e1b4b;
classDef attempt fill:#f0fdf4,stroke:#16a34a,color:#14532d;
class C case
class R parent
class V1,V2 attempt
Read the arrows as the FHIR reference: BCRRegistrationTask.focus → Condition
(the case the obligation is about) and BCRValidationTask.partOf →
BCRRegistrationTask. Each validation attempt is a sub-task of the single
registration obligation — not the reverse.
All of the back-and-forth of repeated attempts lives in the history of
validation Tasks — a new BCRValidationTask per attempt — never in a
backwards status transition or a cycling field on the registration Task. The
registration Task carries only its forward-only status; its current situation
(correction required, partially or fully accepted) is derived from the most
recent validation attempt. This also preserves a complete audit trail (attempt 1
failed, attempt 2 accepted), which matters for a legally mandated registry.
sequenceDiagram
autonumber
participant HIS as Physician / HIS
participant SVR as National FHIR validation service
links HIS: {"Cancer case (Condition)": "Condition-ExampleBCRCancerCondition.html", "Submitted QuestionnaireResponse": "QuestionnaireResponse-ExampleBCRSubmittedQuestionnaireResponse.html", "Result Subscription": "Subscription-ExampleBCRValidationSubscription.html"}
links SVR: {"Registration Task": "Task-ExampleBCRRegistrationTask.html", "Validation Task attempt 1 (failed)": "Task-ExampleBCRValidationTaskFailed.html", "Validation Task attempt 2 (accepted)": "Task-ExampleBCRValidationTaskAccepted.html", "Validation Outcome (errors)": "OperationOutcome-ExampleBCRValidationOutcomeFailed.html", "Validation Outcome (accepted)": "OperationOutcome-ExampleBCRValidationOutcomeAccepted.html"}
HIS->>SVR: POST Condition (open the cancer case)
SVR->>SVR: Create RegistrationTask (focus = Condition)
Note over SVR: RegistrationTask.status = ready
HIS->>HIS: Complete form (QuestionnaireResponse)
loop until a final submission is accepted
HIS->>SVR: POST Bundle (BCRValidationTask + QuestionnaireResponse)
Note over SVR: First submission moves RegistrationTask.status from ready to in-progress<br/>ValidationTask.status = in-progress (background)<br/>ValidationTask.input carries submission-intent, partial or final
alt validation fails
SVR-->>HIS: Subscription notify, ValidationTask.status = failed
Note over SVR: statusReason = validation-failed<br/>output = OperationOutcome (errors)<br/>RegistrationTask.status stays in-progress (no rewind)
HIS->>HIS: Correct the form and resubmit (a new ValidationTask)
else validation passes, partial submission
SVR-->>HIS: Subscription notify, ValidationTask.status = completed
Note over SVR: RegistrationTask.status stays in-progress (case not yet closed)
else validation passes, final submission
SVR-->>HIS: Subscription notify, ValidationTask.status = completed
Note over SVR: output = OperationOutcome (info or warnings) + registration-id<br/>RegistrationTask.status = completed (closed by registry or hospital)
end
end
opt correction after completion
HIS->>SVR: POST Bundle (BCRValidationTask + corrected QuestionnaireResponse)
Note over SVR: RegistrationTask.status stays completed (forward-only)<br/>the new ValidationTask records the correction
end
Tip: click a participant box (Physician / HIS or the validation service) to open a menu of the related example resources.
The registration obligation has a single, forward-only status; each
validation attempt is a separate short-lived Task with its own status. The
registration Task is never rewound — the back-and-forth of repeated attempts is
carried by the history of validation Tasks.
BCRRegistrationTask.status — forward-only; never rewinds:
stateDiagram-v2
direction LR
state "ready" as ready
state "in-progress" as inprogress
state "completed" as completed
state "cancelled / failed" as terminal
[*] --> ready: BCR creates Task from Condition
ready --> inprogress: first QuestionnaireResponse submitted
inprogress --> completed: successful final validation (registry or hospital)
ready --> terminal: withdrawn / error
inprogress --> terminal: withdrawn / error
completed --> [*]
terminal --> [*]
BCRValidationTask.status — one short-lived machine per submission attempt.
The client POSTs the Task as requested; on receipt the server moves it straight
to in-progress and validates in the background. A failed attempt is terminal,
and a correction is simply a new Task:
stateDiagram-v2
direction LR
state "in-progress" as inprogress
[*] --> requested: client POSTs the validation Task
requested --> inprogress: server receives it, background validation starts
inprogress --> completed: validation passed
inprogress --> failed: blocking errors
completed --> [*]
failed --> [*]
The registration Task carries only its forward-only status; the "derived
situation" column is not stored on the Task — it is inferred from the most
recent validation attempt (its status, submission-intent and output).
| Stage / result | BCRValidationTask.status |
BCRRegistrationTask.status |
Derived situation | Task.output |
|---|---|---|---|---|
| Case opened (Condition posted) | — | ready |
awaiting data | — |
| Submitted, validating | in-progress |
in-progress |
under validation | — |
| Valid, partial submission | completed |
in-progress |
partially accepted (case still open) | OperationOutcome (information) |
| Valid, final submission | completed |
completed |
accepted | OperationOutcome (information) + registration-id |
| Valid, warnings only (final) | completed |
completed |
accepted with warnings | OperationOutcome (warning) + registration-id |
| Invalid (blocking errors) | failed |
in-progress |
correction required | OperationOutcome (error) |
| System/processing error | failed |
in-progress |
correction required | OperationOutcome (exception) |
| Correction after completion | completed / failed |
completed (unchanged) |
correction → accepted | OperationOutcome (as above) |
R4 note: Task.statusReason is a CodeableConcept in R4 (it only became a
CodeableReference in R5), so it carries a coded reason
while the human-readable detail lives in the OperationOutcome referenced from
Task.output.
The registration Task already exists (the server created it from the posted
Condition), so each form submission just references it via partOf. The
hospital submits the validation Task and the QuestionnaireResponse atomically
as a transaction Bundle to the national server. Each validation Task declares
its submission-intent (partial or final):
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:qr-1",
"resource": { "resourceType": "QuestionnaireResponse", "status": "completed", "...": "..." },
"request": { "method": "POST", "url": "QuestionnaireResponse" }
},
{
"fullUrl": "urn:uuid:task-v-1",
"resource": {
"resourceType": "Task",
"meta": { "profile": ["https://www.ehealth.fgov.be/standards/fhir/registries/bcr/StructureDefinition/bcr-validation-task"] },
"status": "requested",
"intent": "order",
"code": { "coding": [{ "system": "https://www.ehealth.fgov.be/standards/fhir/registries/bcr/CodeSystem/bcr-task-code", "code": "validate-submission" }] },
"partOf": [{ "reference": "Task/registration-task-1" }],
"focus": { "reference": "urn:uuid:qr-1" },
"input": [
{
"type": { "coding": [{ "system": "https://www.ehealth.fgov.be/standards/fhir/registries/bcr/CodeSystem/bcr-task-io", "code": "questionnaire-response" }] },
"valueReference": { "reference": "urn:uuid:qr-1" }
},
{
"type": { "coding": [{ "system": "https://www.ehealth.fgov.be/standards/fhir/registries/bcr/CodeSystem/bcr-task-io", "code": "submission-intent" }] },
"valueCodeableConcept": { "coding": [{ "system": "https://www.ehealth.fgov.be/standards/fhir/registries/bcr/CodeSystem/bcr-submission-intent", "code": "final" }] }
}
]
},
"request": { "method": "POST", "url": "Task" }
}
]
}
The created validation Task is the durable async job handle — validation
itself happens in the background, so the server can return immediately with the
Task already in in-progress. (The synchronous $validate operation is not
appropriate here: it is structural and blocking, whereas this validation is
asynchronous and applies cross-field oncology business rules.)
The hospital is notified when a validation attempt reaches a terminal state. The example uses a classic R4 rest-hook Subscription:
criteria: Task?part-of=Task/<registration-task>&status=completed,failed
channel: rest-hook → https://his.hospital-x.example/fhir/bcr/validation-callback
On notification the hospital reads the validation Task and its output
OperationOutcome; on failure it shows the issues (each issue.expression is a
FHIRPath into the submitted QuestionnaireResponse, so the UI can point the user
at the exact field), the physician corrects the form, and a new validation Task
is submitted.
Two resources are worth watching, and the hospital can use a Subscription or polling for either:
completed / failed), as above, from which the current situation is derived;status = completed). Watch it with a second Subscription (criteria: Task/<registration-task>) or by polling GET Task/<registration-task>.Alternatives: polling GET Task/<id> until terminal; or, for production
robustness, topic-based subscriptions via the Subscriptions R5 Backport
IG (would add a SubscriptionTopic and a dependency — out of scope for this
draft).
A complete, resolvable example graph — attempt 1 fails on a missing topography, the physician corrects, attempt 2 is accepted:
Condition the registration Task focuses on)correction-required mid-flow)| Profiles | Terminology |
|---|---|
| BCRRegistrationTask | BCR Task Code |
| BCRValidationTask | BCR Task Status Reason |
| BCRValidationOutcome | BCR Task Input/Output Type |
| BCR Submission Intent |