Skip to content

feat: practice-managed custom forms — builder, scored renderer, send to patients (OTR-2309 pt 1/2)#8130

Open
dmabram wants to merge 1 commit into
developfrom
otr-2309-forms
Open

feat: practice-managed custom forms — builder, scored renderer, send to patients (OTR-2309 pt 1/2)#8130
dmabram wants to merge 1 commit into
developfrom
otr-2309-forms

Conversation

@dmabram

@dmabram dmabram commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Part 1 of 2 for OTR-2309 (part 2, paperwork flows, is a stacked PR on this branch). Practices can author their own form questionnaires and send them to patients, independent of the canonical intake paperwork.

What this adds

Form authoring (EHR admin → Questionnaires tab)

  • Create/edit/soft-delete (retire) practice-managed FHIR Questionnaires; import standard instruments as JSON with a lossless builder round-trip; live preview + interactive test dialog.
  • Builder supports pages, choice/text/date/numeric/boolean/display items, conditional display (enableWhen, all/any), and scoring via calculated expressions on hidden items.
  • A form's canonical url/name is assigned at creation and stable on rename, so existing responses never orphan.

Send to patients (EHR visit details)

  • "Send Form" in the visit's top action row → filterable picker → SMS link to the patient (visit-scoped /forms/:appointmentId/:questionnaireId or patient-scoped /forms/patient/:patientId/:questionnaireId); link also copyable.

Patient completion (intake app)

  • Page-by-page save with Back/Continue; in-progress responses resume with saved answers pre-filled; required items are enforced with visible errors; save/submit failures surface with retry (no false "submitted" state); patients never see numeric scoring artifacts (option-prefix digits suppressed, no live totals).
  • Final submission completes the QuestionnaireResponse, renders a PDF into the patient's Paperwork folder, and creates a staff follow-up Task (task failure → Sentry, not silent).

Provider review

  • Completed responses render on the visit page and response viewer with display text (never raw answer codes), including computed scores.

Security & correctness

  • get-/save-practice-managed-response enforce caller→patient authorization (EHR users pass implicitly); writes authorize against the stored QR's own subject; unauthorized → explicit ACCESS_DENIED the patient app renders.
  • Saved responses are structurally faithful: each page item holds exactly that page's answers as valueCoding (+ display); answers to enableWhen-disabled items are pruned.
  • User-actionable failures throw structured APIErrors (no on-call page); deliberate fallbacks are logged/captureException'd, never swallowed; catalog listings paginate (no silent truncation past one page).

Verification

  • tsc clean across utils / ui-components / zambdas / ehr / intake; eslint clean (max-warnings 0).
  • Unit: zambdas 1910/1910 passing (8 suite-collection failures are pre-existing missing config/.env/local.json machine secrets, identical on base); utils fhir 185/185.
  • Browser e2e (Chrome, real local zambdas + live patient session): admin builder/list, Send Form dialog, standalone form render → required-validation block (2 visible errors on empty submit) → submit; QR verified in FHIR: completed, answers verbatim (yes / free text / no); EHR shows display text; completed forms on visits render via the existing-QR path.

Functional requirements: practice-forms-functional-requirements.md Part 1 (local docs).

Attaching forms to the booking intake itself is intentionally out of scope here — that lands in part 2 (paperwork flows).

🤖 Generated with Claude Code

…to patients

Practices can author their own form questionnaires and send them to patients,
independent of the canonical intake paperwork.

Authoring (EHR admin):
- Questionnaires admin tab: list/create/edit/delete practice-managed FHIR
  Questionnaires (tagged practice-managed), with import-JSON and soft delete
  (retired status; existing responses still render). The canonical url/name are
  preserved on edit so renaming a live form never orphans prior responses.
- Form builder: paged items, answer options, conditional display (enableWhen),
  calculated expressions on hidden items for scoring, live preview + test dialog.
- admin-{list,get,create,update,delete}-questionnaire zambdas (structured
  APIErrors for user-actionable failures; paginated catalog listing).

Patient-facing:
- send-patient-form zambda texts the patient a link (visit-scoped
  /forms/:appointmentId/:questionnaireId or patient-scoped
  /forms/patient/:patientId/:questionnaireId).
- StandaloneFormPage (intake app) renders and submits the form outside any
  booking flow: required-field validation, per-page answers scoped correctly,
  in-progress responses resume, save/submit failures surface to the patient,
  answers to enableWhen-disabled items are pruned, and numeric scoring artifacts
  (option prefixes, live totals) never display to patients. Computed expression
  values save to the QuestionnaireResponse; finalize files a PDF into the
  patient's Paperwork folder and can spawn follow-up tasks (failures there are
  reported to Sentry, not swallowed).
- get/save/finalize practice-managed-response zambdas enforce caller→patient
  authorization (EHR users pass implicitly); writes authorize against the QR's
  own subject.

EHR visit details:
- "Send Form" action in the top action row opens a filterable form picker (with
  distinct error vs empty states).
- Completed forms render on the visit page and in the response viewer.

Attaching forms to the intake paperwork itself is intentionally out of scope
here — that lands separately as paperwork flows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant