Patient questionnaires

Schedule, Track, and Score Patient Questionnaires on FHIR

Run recurring patient questionnaire programs (PHQ-9, KCCQ-12, post-surgical milestones, study protocols) as standard FHIR workflows. Scheduling, timezone handling, reminder delivery, completion tracking, and threshold alerts come from the server, with the data already in a format your EHR or registry can consume. The patient app stays a thin frontend; the backend, audit trail, and access control are shared with the rest of your FHIR product.

What you can build

  • PHQ-9, KCCQ-12, or custom protocols at scale

    Define the schedule once as a reusable PlanDefinition. Apply it to every enrolled patient via `$apply`. Change the cadence without a code deployment.

  • Standard instruments without re-typing them

    Import PHQ-9 (LOINC 44249-1) and other standard instruments directly into FHIR Questionnaires through the Web UI. Codes and answer options are preserved, not lost in translation.

  • Tasks that materialize on the right day

    Fire Arrow creates Task resources up to a configurable horizon, transitions them to `ready` at the patient's local time (with DST handling), and exposes them through standard FHIR search.

  • Reminders that fit your delivery channel

    Subscribe to ready Tasks over a REST hook, email, WebSocket, or Azure Storage Queue. Push notifications, SMS, in-app messages, call-center systems all consume the same event stream.

  • Trend dashboards on the clinical timeline

    QuestionnaireResponses appear next to the rest of the patient's clinical data. Care teams see longitudinal change in one place instead of a separate reporting tool.

  • Threshold-based alerts in your workflow

    A subscription on `QuestionnaireResponse` runs your scoring and escalation logic. PHQ-9 score above 20 triggers a critical-band alert; above 15 a moderate-severe alert. Where the response goes next is your call.

  • Data ready for the next system

    Everything (the instrument, the protocol, the schedule, the responses) is standard FHIR R4 from the start. Hand it to an EHR, a registry, an analytics platform, or a study database without an export converter.

How it works

  1. 1
    Define the questionnaire

    Start from a published instrument like PHQ-9 or KCCQ-12 by importing it from LOINC, or build a custom one. Either way the result is a standard FHIR Questionnaire with the original codes intact, referenced everywhere downstream by a single stable identifier.

  2. 2
    Publish it so other resources can lock onto it

    Mark the questionnaire as active and assign it a stable identifier. The protocol in the next step points at this exact published version, so a later edit to a draft cannot quietly change what patients are answering.

  3. 3
    Describe the protocol once, reuse it for every patient

    Define the recurring schedule as a FHIR PlanDefinition: which questionnaire, how often, and for how long (for example, PHQ-9 every two weeks for six months). The cadence lives in data, so moving from biweekly to weekly is a configuration change, not a code release.

  4. 4
    Enroll a patient on the protocol

    Apply the protocol to a specific patient with their start date and timezone. The server generates that patient's own CarePlan and anchors every future occurrence to their local time, so a 9 AM reminder still fires at 9 AM after a daylight-saving transition.

  5. 5
    Turn on scheduling and due-event notifications

    A single call activates the schedule and registers your patient app to be notified whenever an occurrence becomes due. From here the server handles timezone, daylight-saving, and recurrence on its own. No cron jobs, no background workers on your side.

  6. 6
    Patient app renders the questionnaire when it is due

    When the next occurrence is due, the server pushes a notification. Your patient app fetches the linked questionnaire, presents it to the patient, and posts the answers back. The to-do is closed and the clinical record is updated in the same step. Care teams see the response on the patient's timeline immediately.

  7. 7
    Score and alert in a separate flow

    A second subscription delivers every submitted response to your scoring logic. Compute the score (PHQ-9 critical band, KCCQ-12 trend, or anything custom), compare it to your thresholds, and route the result through whatever escalation path you already have: clinician task, in-app message, dashboard flag. Reminders and clinical alerts stay independent, so a change to one does not disturb the other.

What you get out of the box

Capability With Fire Arrow Building it yourself
Standardized instrument definition LOINC import directly into a FHIR Questionnaire. PHQ-9 example: LOINC 44249-1 with the published item structure preserved. Manually rebuild the instrument in a form builder, lose the LOINC codes and answer-set structure along the way.
Reusable protocol PlanDefinition + `$apply`. The protocol is versioned FHIR data. Changing frequency, instrument, or duration is a data update. Per-patient cron entries or hardcoded intervals in a backend service. Protocol changes require a developer, a code change, and a redeployment.
Recurring scheduling CarePlan event engine with rolling horizon, DST/timezone handling, and bounded Task generation. Custom cron jobs and background workers. Re-implement timezone handling, DST transitions, and recurrence logic. Find the bugs in production.
Operational tracking Tasks are first-class FHIR resources. State is queryable through standard FHIR search, auditable, and search-narrowed by role. Bespoke task table or job queue with custom UI, no built-in audit, and a separate access-control story.
Notification delivery Subscription channels for REST hook, email, WebSocket, and Azure Storage Queue. Per-channel retry, failure tracking, and TTL. Custom job runner with retry/backoff, failure tracking, and dead-letter handling. Build idempotency into every consumer.
Response-based alerting Subscription on `QuestionnaireResponse` hands events to your evaluator. CQL infrastructure available where standardized scoring helps. Polling on response tables or custom database triggers. Maintain scoring logic in application code.
Access control across patients, clinicians, services Same rule set covers all roles. Patients use PatientCompartment; clinicians use LegitimateInterest scoped by managing organization. Authorization designed and implemented per surface, often as a separate project late in the development cycle.
Search narrowing on questionnaire workflows Patient app searching `Task?status=ready&owner=Patient/{id}` is narrowed at the database. Clinician searching across patients is narrowed by their organization. Per-query filter in the API layer. Forget one parameter and the search returns cross-tenant results.
Data portability Questionnaire, PlanDefinition, CarePlan, Task, QuestionnaireResponse stored as FHIR resources. Hand them to any FHIR-capable system. Custom schema plus an export step that maps fields manually for every downstream system.
Multi-node operation Distributed locking (JDBC-based) for materialization and subscription processing. Concurrent execution stays consistent. Build leader election or coordination on top of your scheduler.

The protocol is data, not code

In a conventional implementation, monitoring logic lives in application code: a cron schedule, a configuration file, hardcoded intervals in a backend service. A protocol change (different cadence, different instrument, different duration) requires a developer, a code change, and a redeployment.

With `PlanDefinition`, the protocol is a versioned FHIR resource. A clinician or administrator can update the cadence, switch the instrument, or change the duration without touching code. The same protocol can be applied to a population through programmatic `$apply` calls.

Every occurrence is its own searchable record

Each scheduled occurrence is captured as a small FHIR Task that links back to the patient and to the protocol it came from, and carries the state of the work: due, in progress, completed, or missed. Because Tasks are standard FHIR data, a dashboard like "patients with an overdue PHQ-9 in the last seven days" is one FHIR query, not a custom reporting pipeline.

Tasks live under the same authorisation rules as the rest of the FHIR data, so nothing has to be reimplemented per app. A patient signed in to the patient app sees only their own Tasks. A clinician sees only the Tasks belonging to their organisation. A service account that sends reminders gets only the operations it needs.

Reminders and clinical alerts stay independent

"Patient has a questionnaire due" and "patient just submitted a concerning score" are different events with different audiences. Treating them as two notification streams keeps the reminder pipeline and the clinical-alert pipeline from being tangled into one piece of code.

One stream tells the patient app when an occurrence is due, which drives reminders. A second stream tells your scoring service when a response comes back, which drives alerts. Swapping the push-notification provider, adding a new scoring rule, or changing the escalation path can each happen on its own without disturbing the other side.

Operational controls, with sensible defaults

Two settings tune the scheduling engine: how far ahead occurrences are prepared (thirty days by default) and how often the engine checks. A cap on the number of open occurrences per activity keeps a multi-year programme from accumulating thousands of pending records at once.

Notifications cannot pile up unnoticed. Every subscription has a required end date and a configurable maximum lifetime, so a forgotten one expires on its own. Delivery failures are tracked, and the server can auto-disable a subscription after too many consecutive failures or after a configurable stretch with no successful delivery. Renewing or tearing one down is a single call.

Timezone is the one setting worth getting right at enrollment. With it, recurring reminders fire at the patient's local wall-clock time and survive daylight-saving transitions. Without it, the server still creates the work, but it cannot put it in front of the patient at the right hour.

Example deployments

  • Depression monitoring with PHQ-9

    PHQ-9 every two weeks for six months. The instrument is imported once from LOINC, the protocol defines the cadence, and a scoring rule routes critical-band scores (≥20) and moderate-severe scores (≥15) into the care team's existing escalation workflow.

  • Cardiology symptom surveillance

    KCCQ-12 monthly during heart-failure follow-up. Same shape as the PHQ-9 case with a different instrument and a different cadence; worsening scores can trigger an appointment, a clinician review, or an in-app message, whichever the team already uses.

  • Post-surgical recovery tracking

    Check-ins at the two-week, six-week, and twelve-week milestones after a procedure. Each occurrence is anchored to the patient's procedure date rather than to a fixed recurring interval, so the schedule still works when a procedure slips by a few days.

  • Clinical study symptom surveys

    Recurring questionnaires across a study population on a single protocol. The same data can be sliced into different views (operational staff, investigators, sponsors) by configuring access rules, with no duplicate pipeline per audience.

FAQ

Can patients answer Questionnaires from a mobile app?

Yes. The app is a normal FHIR client with the patient's bearer token. It reads ready Tasks for the patient, renders the linked Questionnaire, and POSTs a QuestionnaireResponse on submission. PatientCompartment ensures it only ever sees the patient's own Tasks.

How are scoring rules expressed?

FHIR supports standard scoring extensions on Questionnaire items (such as `ordinalValue`). Scores can be calculated client-side, server-side via a custom operation, or in a downstream consumer of a QuestionnaireResponse subscription. For standardized scoring, the FHIR Clinical Quality Language (CQL) infrastructure is available.

Can I change the schedule for an enrolled patient?

Yes. Update the CarePlan and future Tasks within the scheduling horizon are revised. Already-completed Tasks stay as they are. Updates to the underlying PlanDefinition can be re-applied programmatically across the enrolled population.

How does Fire Arrow handle timezones and DST?

Timezone is resolved from the CarePlan extension (set during `$apply`) or from the Patient resource. DST transitions are handled centrally so that recurring patient communication fires at the correct local wall-clock time without per-application bug fixes.

Does it work for non-clinical workflows like study questionnaires?

Yes. The mechanics are the same: Questionnaire defines the form, PlanDefinition defines the schedule, CarePlan applies it to a participant, Tasks materialize on the timeline, and QuestionnaireResponses capture the answers. Identity filters and property filters can serve different views to operational staff, investigators, and sponsors.

What is the operational footprint?

Fire Arrow Server runs as a Docker container with PostgreSQL alongside. The materialization job, subscription delivery, and the rest of the workflow run inside the same backend. Multi-node deployments use distributed JDBC-based locking so concurrent materialization stays consistent.