Skip to main content

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

  1. active,
  2. on-hold
  3. draft
  4. stopped or revoked

statuses.

These statuses affect Task-list generation

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 status field, 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.

Pros

This approach is scalable and generic, capable of tracking changes to any field across various resources.

Cons

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_by extension).

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 MedicationRequest or ServiceRequest resource 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 MedicationRequest and ServiceRequest resources, 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

{
"amount": [
{
"change": 22,
"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"
}
}
]
}