Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9810d93
Add order rejected and cancelled states, add Mermaid diagram
trawlinson-kainos Jun 8, 2026
0a96612
First draft of order accepted, rejected and cancelled FHIR structures.
trawlinson-kainos Jun 8, 2026
9bbe90f
Adding statusReasons, and using full UUIDs in examples.
trawlinson-kainos Jun 8, 2026
2813c1d
Remove order rejection
trawlinson-kainos Jun 22, 2026
5f05e40
Syntax errors / spacing in YAML files.
trawlinson-kainos Jun 23, 2026
717aa17
Model statusReason as a codeable concept (text field) rather than jus…
trawlinson-kainos Jun 23, 2026
ca14d25
Fix typos that error the pre-commit
trawlinson-kainos Jun 23, 2026
08a125b
Another extra markdown empty line
trawlinson-kainos Jun 23, 2026
224a17d
Remove bussinessStatus enum - this is being modified in another PR
trawlinson-kainos Jun 24, 2026
c82ed27
Removing of order accepted and rejected examples, replace with receiv…
trawlinson-kainos Jun 24, 2026
6f7e68e
Update changelog.md
trawlinson-kainos Jun 24, 2026
8d8635d
Move order cancellation to a different verb on the same endpoint
trawlinson-kainos Jun 24, 2026
bc36317
Fix whitespace
trawlinson-kainos Jun 24, 2026
383760b
Apply suggestion from @lewisbirks
trawlinson-kainos Jun 24, 2026
2e14b5d
Correct name of order cancelled file
trawlinson-kainos Jun 24, 2026
c4d47dd
Change to use 'PATCH' as the HTTP verb for cancelling
trawlinson-kainos Jun 24, 2026
1db4b13
Use 'delete' as the HTTP verb
trawlinson-kainos Jun 25, 2026
9251fee
Update changelog
lewisbirks Jun 26, 2026
b6b8943
Merge branch 'main' into feat/add-cancellation-process
lewisbirks Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions docs/status-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,77 @@
- `order-received`
- `order-accepted`
- `dispatched`
- `cancelled`
Comment thread
trawlinson-kainos marked this conversation as resolved.
- `received-at-lab`
- `test-processed`
- `complete`

## Allowed Transitions

```text
order-received -> order-accepted -> dispatched -> received-at-lab -> test-processed -> complete
The following state machine shows the allowed transitions for HomeTest orders.

The states in green are states that are controlled by the suppliers - i.e. the entry to that state comes from an update from the supplier.

The states in blue are states that are controlled within HomeTest.

```mermaid
---
title: Order State Machine
---
stateDiagram-v2
classDef supplierSent fill:#00A499, color:#fff
classDef homeTestSent fill:#dceefb
state eligibility_check <<choice>>

orderEligibilityCheck: Supplier Order Eligibility Check
orderReceived: order-received
orderAccepted: order-accepted
orderRejected: No order created
receivedAtLab: received-at-lab
testProcessed: test-processed

[*] --> orderEligibilityCheck
orderEligibilityCheck --> eligibility_check
eligibility_check --> orderReceived:::homeTestSent : if Eligible
eligibility_check --> orderRejected:::homeTestSent : if Not Eligible
orderReceived --> orderAccepted:::supplierSent
orderRejected --> [*]
orderAccepted --> dispatched:::supplierSent
dispatched --> receivedAtLab:::supplierSent
dispatched --> cancelled:::homeTestSent
cancelled --> [*]
receivedAtLab --> testProcessed:::supplierSent
testProcessed --> complete:::homeTestSent
complete --> [*]
```

## Order Creation and Completion

New orders are only created within the HomeTest platform.

Orders can only be marked as 'complete' by the HomeTest platform, usually on receipt of a test result update from the test supplier.
This means that while `order-received` and `complete` are valid status, they are reserved for use within the HomeTest platform itself.
Only the status of `order-accepted`, `dispatched`, `received-at-lab` and `test processed` should be sent by test suppliers.

This means that while `order-received`, `cancelled` and `complete` are valid order statuses, they shouldn't be sent as order updates by suppliers. Only the status of `order-accepted`, `dispatched`, `received-at-lab` and `test-processed` should be sent by test suppliers (marked in green on the diagram above)

## Order Acceptance and Rejection

Before an order is formally created, a 'draft' order is sent to the supplier. This known as the 'Supplier Order Eligibility Check', and is the moment where a supplier decides whether to accept to reject the order.

If an order is rejected in the supplier eligibility check, a HomeTest order is not created, and the user is directed to other avenues. For example, this is a direction to the user's closest sexual health clinic for HIV tests.

If the order is accepted through the supplier eligibility check, the order is sent to the supplier and must then move through to `dispatched`, and it cannot be later cancelled by the test supplier. In other words, a test kit MUST be dispatched if the eligibility check has passed successfully.

## Order Cancellation

Users can cancel an order only when it is in the `dispatched` state.
Further updates to a 'cancelled' order are rejected by HomeTest, and an error is raised. Results for cancelled orders not currently handled by the HomeTest API, and are also rejected if they're received for a cancelled order.

## Rules

1. **Monotonic progression**: transitions **MUST** move forward only.
2. **Idempotent updates**: re-sending the same status is allowed and **MUST NOT** error.
3. **No skips**: skipping intermediate states is **SHOULD NOT**. If a supplier cannot emit all states, they **MUST** document and obtain approval.
4. **Terminal**: `complete` is terminal; no further transitions allowed.
4. **Terminal**: `order-rejected`, `cancelled` and `complete` are terminal states; no further transitions allowed. No updates to results using `POST /result` are permitted in these states.

## Error Semantics

Expand Down
31 changes: 31 additions & 0 deletions examples/fhir/order_cancelled.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
Comment thread
trawlinson-kainos marked this conversation as resolved.
"resourceType": "ServiceRequest",
"id": "7cb0623e-9cd7-4495-aa66-715c04a81903",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">ServiceRequest: HIV antigen test order cancelled by user</div>"
Comment thread
trawlinson-kainos marked this conversation as resolved.
},
"status": "revoked",
"intent": "order",
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "31676001",
"display": "HIV antigen test"
}
],
"text": "HIV antigen test"
},
"subject": {
"reference": "Patient/1d6efc98-78e7-4049-9d4f-e651a95d9727"
},
"requester": {
"reference": "Organization/ORG001"
},
"performer": [
{
"reference": "Organization/SUP001"
}
]
}
3 changes: 3 additions & 0 deletions examples/fhir/task_update_dispatched.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@
"lastModified": "2025-11-04T10:35:00Z",
"businessStatus": {
"text": "dispatched"
},
"statusReason" : {
Comment thread
lewisbirks marked this conversation as resolved.
"text": "Test kit dispatched to patient"
}
}
27 changes: 27 additions & 0 deletions examples/fhir/task_update_received-at-lab.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"resourceType": "Task",
"identifier": [
{
"system": "https://fhir.hometest.nhs.uk/Id/order-uid",
"value": "550e8400-e29b-41d4-a716-446655440000",
"use": "official"
}
],
"basedOn": [
{
"reference": "ServiceRequest/550e8400-e29b-41d4-a716-446655440000"
}
],
"status": "in-progress",
"intent": "order",
"for": {
"reference": "Patient/123e4567-e89b-12d3-a456-426614174000"
},
"lastModified": "2025-11-04T10:35:00Z",
"businessStatus": {
"text": "received-at-lab"
},
"statusReason" : {
"text": " Test kit has been received at the lab and is being processed"
}
}
27 changes: 27 additions & 0 deletions examples/fhir/task_update_test-processed.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"resourceType": "Task",
"identifier": [
{
"system": "https://fhir.hometest.nhs.uk/Id/order-uid",
"value": "550e8400-e29b-41d4-a716-446655440000",
"use": "official"
}
],
"basedOn": [
{
"reference": "ServiceRequest/550e8400-e29b-41d4-a716-446655440000"
}
],
"status": "in-progress",
"intent": "order",
"for": {
"reference": "Patient/123e4567-e89b-12d3-a456-426614174000"
},
"lastModified": "2025-11-04T10:35:00Z",
"businessStatus": {
"text": "test-processed"
},
"statusReason" : {
"text": " Test kit has been processed"
}
}
17 changes: 16 additions & 1 deletion schemas/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ All notable changes to the NHS Home Test Supplier Integration Framework API sche
- [Version 1.1.2 - May 18, 2026 - Additional DataAbsent Result reason](#version-112---may-18-2026---additional-dataabsent-result-reason)
- [Version 1.1.3 - June 1, 2026 - Change handling of non-definitive results](#version-113---june-1-2026---change-handling-of-non-definitive-results)
- [Version 1.1.4 - June 10, 2026 - Resolve OpenAPI spec Spectral validation warnings](#version-114---june-10-2026---resolve-openapi-spec-spectral-validation-warnings)
- [Version 1.1.5 - June 15, 2026 - FHIR Example File Compliance Fixes](#version-115---june-15-2026---fhir-example-file-compliance-fixes)
- [Version 1.1.5 - June 15, 2026 - FHIR Example File Compliance Fixes\*\*](#version-115---june-15-2026---fhir-example-file-compliance-fixes)
- [Version 1.1.6 - June 22, 2026 - Add order cancellation\*\*](#version-116---june-22-2026---add-order-cancellation)

---

Expand Down Expand Up @@ -360,3 +361,17 @@ Changes to schemas/fhir-schemas/:
- Changed `link.self` URL from `/results?order_uid=...` to `Bundle?identifier=...` (resource-type-qualified URL required for FHIR type checking)
Comment thread
trawlinson-kainos marked this conversation as resolved.
- Added `search.mode: match` to the entry (required for searchset bundles)
- Added `text` narrative to the inner Observation resource

---

## Version 1.1.6 - June 22, 2026 - Add order cancellation**

1. Add order cancellation process
- Allow 'revoked' as a status of the ServiceRequest
- Use 'DELETE' verb on the /order endpoint when orders are being cancelled. This allows specific errors to be defined, and helps to separate cancellation from creating a new order.
- Add documentation for rejection of further updates to cancelled orders

2. Clarify the order eligibility check and other order states
- Remove mentions of order rejection
- Add diagram for order states
- Add documentation for order cancellation, and order acceptance (via eligibility check)
52 changes: 40 additions & 12 deletions schemas/home-test-supplier-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ openapi: 3.0.3
info:
title: Home Test Supplier API
description: API for supplier domain operations - managing test results and order status updates
version: 1.1.4
version: 1.1.6
contact:
name: NHS England - Digital Prevention Services Portfolio - Home Test Team
email: england.hometest@nhs.net
Expand Down Expand Up @@ -71,13 +71,19 @@ paths:
application/fhir+json:
schema:
$ref: '#/components/schemas/FHIRTask'
examples:
FHIRTaskExample:
$ref: '#/components/examples/FHIRTaskExample'
responses:
'200':
description: Status updated successfully
content:
application/fhir+json:
schema:
$ref: '#/components/schemas/FHIRTask'
examples:
FHIRTaskExample:
$ref: '#/components/examples/FHIRTaskExample'
'400':
$ref: '#/components/responses/BadRequest'
'404':
Expand Down Expand Up @@ -365,19 +371,25 @@ components:
status:
type: string
description: Current status of the task - FHIR standard values (use businessStatus for domain-specific states)
enum: [draft, requested, received, accepted, rejected, ready, cancelled, in-progress, on-hold, failed, completed, entered-in-error]
enum: [accepted, in-progress]
example: "in-progress"
businessStatus:
allOf:
- $ref: '#/components/schemas/FHIRCodeableConcept'
- description: Domain-specific business status, expected to be one of "dispatched","received-at-lab", or "test-processed" for HomeTest.
example:
text: "dispatched"
Comment thread
trawlinson-kainos marked this conversation as resolved.
intent:
type: string
description: Indicates the "level" of actionability associated with the Task
enum: [unknown, proposal, plan, order, original-order, reflex-order, filler-order, instance-order, option]
enum: [order]
example: "order"
statusReason:
allOf:
- $ref: '#/components/schemas/FHIRCodeableConcept'
- description: Reason for current status
example:
text: "Sample collected and being processed"
text: "Test kit dispatched to patient"
for:
allOf:
- $ref: '#/components/schemas/FHIRReference'
Expand Down Expand Up @@ -406,13 +418,6 @@ components:
- description: Responsible individual for the task
example:
reference: "Organization/SUP001"
businessStatus:
allOf:
- $ref: '#/components/schemas/FHIRCodeableConcept'
- description: Domain-specific business status (should be one of&#58; "order-accepted", "dispatched", "received-at-lab", "test-processed")
example:
text: "dispatched"

FHIROperationOutcome:
type: object
description: FHIR OperationOutcome resource for reporting errors and warnings
Expand Down Expand Up @@ -470,7 +475,6 @@ components:
items:
type: string
example: ["Observation.status"]

FHIRReference:
type: object
description: A reference from one resource to another (FHIR Reference datatype)
Expand Down Expand Up @@ -621,6 +625,30 @@ components:
status: "not-done"
reasonReference:
- reference: "urn:uuid:550e8400-e29b-41d4-a716-446655440001"
FHIRTaskExample:
summary: Example FHIR Task for a 'dispatched' update, see examples folder for further examples.
value:
resourceType: "Task"
id: "task-550e8400-e29b-41d4-a716-446655440000"
identifier:
- system: "https://fhir.hometest.nhs.uk/Id/order-uid"
value: "550e8400-e29b-41d4-a716-446655440000"
basedOn:
- reference: "ServiceRequest/550e8400-e29b-41d4-a716-446655440000"
status: "in-progress"
businessStatus:
text: "dispatched"
intent: "order"
for:
reference: "Patient/123e4567-e89b-12d3-a456-426614174000"
authoredOn: "2025-11-04T10:30:00Z"
lastModified: "2025-11-04T10:35:00Z"
requester:
reference: "Organization/ORG001"
display: "HomeTest"
owner:
reference: "Organization/SUP001"
display: "Test Supplier Ltd"
responses:
BadRequest:
description: Bad request - invalid parameters
Expand Down
Loading
Loading