The Pause Prescription Chronicles: A Tale of Status, Timestamps, and Redis
How It Works
This document explains how we implemented the Pause Prescription feature and why we made certain decisions (Extended cut and Behind the Scenes addition). For context, see the original requirements here: Req: Pause Prescription.
Previously, prescription statuses (MedicationRequest and ServiceRequest) were always "unknown." We needed to support
active,on-holddraftstoppedorrevoked
statuses.
active prescriptions generate tasks for the active duration, while others do not.
Challenge
The status field in FHIR resources MedicationRequest and ServiceRequest only shows the current status. It doesn't
track past status changes or when those changes occurred. We needed to know the history of status changes and their
timestamps to accurately generate Task-lists.
Approaches
1. Custom Extension
We considered adding an extension to track status changes and their timestamps.
This would have allowed us to calculate the duration of each status, similar to how we track last_changed_by.
While straightforward and performant, this approach had two key drawbacks:
- It was specific to the
statusfield, requiring similar implementations for other fields. - It was not FHIR-compliant.
Therefore, we decided against this approach.
At the bottom you find an example of how this extension could have looked like.
2. FHIR Provenance Resource
We explored using the FHIR Provenance resource. This would involve creating a new Provenance resource each time a status changes, storing the status and timestamp,
and linking it to the relevant MedicationRequest or ServiceRequest. We could then calculate status durations
from these Provenance resources. However, this approach had limitations:
- It was not a generic solution, requiring similar implementations for other fields.
- It was not performance efficient.
3. Resource History 🏆
We chose to use the FHIR resource's history. FHIR tracks changes, storing timestamps (meta.lastUpdated) and version IDs.
This allowed us to determine status durations.
This approach is scalable and generic, capable of tracking changes to any field across various resources.
It is not performance-optimal, as it requires processing each resource's history.
Our implementation involved:
- Iterating through each history version.
- Identifying field changes between versions.
- Extracting the change, timestamp (
meta.lastUpdated), and user (last_changed_byextension).
The resulting historical changes are stored with change, changed_at, and changed_by fields, in ascending order.
Each field has at least one entry (the creation event), with subsequent entries for later changes.
Performance optimization is discussed later.
Resource History
Optimization: Enhancing Performance with Redis Caching
The process of calculating historical changes, which involves iterating through each version of a resource's history and computing the differences between them, presented a significant performance bottleneck. Retrieving resource history for every operation is a time-intensive process, and the calculations themselves add to the computational overhead. To address this, we implemented a Redis caching strategy.
Rationale for Redis Caching
- Performance Improvement: By caching the calculated historical changes, we eliminate the need to repeatedly fetch and process resource history. This dramatically reduces latency and improves overall system responsiveness.
- Reduced Database Load: Caching minimizes the number of queries to the FHIR database, decreasing the load on the database server.
Caching Strategy Details
- Cache Storage: Historical change data for each resource is stored as a JSON object within Redis, using the resource ID as the key.
- Cache Update Mechanism:
- The cache is populated when a new
MedicationRequestorServiceRequestresource is created, capturing the initial state of all relevant fields. - Subsequently, any updates to these resources trigger a recalculation of the historical changes, and the Redis cache is updated accordingly.
- Cache Persistence: Given that historical changes represent immutable records, and are only added to, the cached data is configured to persist indefinitely. This eliminates the need for cache invalidation or eviction policies.
Scalability Considerations
- Resource Coverage: Currently, the caching mechanism is implemented for
MedicationRequestandServiceRequestresources, which constitute the primary use cases for tracking prescription status changes. However, the design is extensible, allowing for the inclusion of other resource types as needed. - Distributed Caching Requirement: The projected volume of cached data is substantial, estimated to be approximately 70% of the total FHIR database size. Therefore, deploying a distributed Redis cluster is essential to ensure horizontal scalability and high availability.
Example of Cached Data
- Short Example
- Full Example
{
"amount": [
{
"change": 22,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
}
{
"last_computed_history_version": 8,
"amount": [
{
"change": 22,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"category": [
{
"change": "TABLETS",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"code": [
{
"change": "9256ac0a-8453-4005-b586-1d303d914de8",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"description": [
{
"change": "Jonosteril",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"dosage_form": [
{
"change": "8fbd85cf-54c8-4f02-8011-17ee85a6ccb9",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"end_date": [
{
"change": "2100-01-01T00:00:00Z",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": "2025-06-28T22:00:00Z",
"changed_at": "2025-03-26T11:57:24.484+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": "2025-04-03T12:11:49.078Z",
"changed_at": "2025-04-03T14:11:58.255+02:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"frequency": [
{
"change": "feadd04c-14ae-43f8-983c-71eaf431cd90",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"frequency_times": [
{
"change": [],
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"instructions": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": "erewrfseew",
"changed_at": "2025-03-26T11:57:07.706+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"is_approved": [
{
"change": false,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": true,
"changed_at": "2025-03-24T13:58:51.467+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": false,
"changed_at": "2025-03-26T11:57:07.706+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": true,
"changed_at": "2025-03-26T11:57:08.741+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": false,
"changed_at": "2025-03-26T11:57:24.484+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": true,
"changed_at": "2025-03-26T11:57:24.653+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": false,
"changed_at": "2025-04-03T14:11:58.255+02:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": true,
"changed_at": "2025-04-03T14:11:58.948+02:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"method": [
{
"change": "ORAL",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"rate": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"solution_code": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"start_date": [
{
"change": "2025-03-24T12:58:16.131Z",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"status": [
{
"change": "active",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
},
{
"change": "stopped",
"changed_at": "2025-04-03T14:11:58.255+02:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"status_reason": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_amount": [
{
"change": "mg",
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_amount_code": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_rate": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_rate_code": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_volume": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"unit_volume_code": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
],
"volume": [
{
"change": null,
"changed_at": "2025-03-24T13:58:51.306+01:00",
"changed_by": "da46528c-4848-4c48-9728-5465a187656f"
}
]
}
Appendix
Example of custom extension
{
"extension": [
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status-history",
"extension": [
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status",
"valueString": "active"
},
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status-changed-at",
"valueDateTime": "2025-03-24T12:58:51.282Z"
}
]
},
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status-history",
"extension": [
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status",
"valueString": "on-hold"
},
{
"url": "https://fhir.mona.icu/StructureDefinition/prescription-status-changed-at",
"valueDateTime": "2025-03-26T14:28:31.532Z"
}
]
},
{
"url": "https://fhir.mona.icu/StructureDefinition/lastChangedBy",
"valueReference": {
"reference": "Practitioner/da46528c-4848-4c48-9728-5465a187656f"
}
}
]
}