Skip to content

feat: paperwork flows — compose base-intake and per-service forms at booking (OTR-2309 pt 2/2)#8131

Open
dmabram wants to merge 1 commit into
otr-2309-formsfrom
otr-2309-flows
Open

feat: paperwork flows — compose base-intake and per-service forms at booking (OTR-2309 pt 2/2)#8131
dmabram wants to merge 1 commit into
otr-2309-formsfrom
otr-2309-flows

Conversation

@dmabram

@dmabram dmabram commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Part 2 of 2 for OTR-2309, stacked on #8130 (otr-2309-forms) — re-target to release/1.36 after part 1 merges. Practices design their own paperwork flows: forms every booking on a base intake includes, plus per-service form bundles, composed at booking time.

Model

  • Base intake = the canonical a booking resolves to: In-person (full), Virtual (full), or Consent-only (lite).
  • Service flow = named bundle { slug, base: standard | consent-only, ordered formIds }, assigned to service categories (a service has at most one flow; assignment is reconciled).
  • Compose: a booking's forms = base-intake forms (every booking on that intake) + service-flow forms (only that flow's services), base-first, deduped keep-first.

What this adds

Data model — flows persist as FHIR Lists (tagged practice-paperwork-flow). Three fixed base flows, one per base canonical (marked by a paperwork-flow-canonical extension), auto-created idempotently; identity fixed, only their form lists edit. Service flows reference services via the ServiceCategory config blob (paperworkFlowId).

Booking time (create-appointment) — the slot's service category resolves its flow; consent-only base maps to the lite intake canonical (explicit staff paperwork-subtype still wins); flow id stamped on Encounter + Appointment. Channel-agnostic: patient booking and EHR /visits/add behave identically. Flow-lookup failures fall back to the mode default and are logged, never blocking the booking.

Form resolution (get-practice-managed-questionnaires) — composes base + service forms with same-base isolation (a consent-only booking never inherits standard-intake forms; a Virtual-only form never appears in-person). Insertion point inside the intake is server-determined (client substring heuristic removed; boundary cached per appointment, statuses fetched fresh at the boundary). The standalone send-form path is untouched. Flow searches paginate.

Admin (Paperwork Flows tab) — Base intake cards (In-person / Virtual / Consent-only) with auto-saving add/remove/reorder (debounced reorder, sequenced saves, stable unmount flush; failed saves surface an error and reconcile to server state). Service flows table + dialog (name, auto-slug via shared slugify, base, forms, applies-to-services); "New flow" scoped to service flows. CRUD zambdas carry base-flow guard rails (not creatable/deletable; delete refuses when the guard fetch fails).

Patient experience — composed forms render as pages inside the intake before finalization; completing them marks responses complete and the intake skips completed forms (re-editing returns one to in-progress until finished again). All part-1 guarantees (resume, required validation, error surfacing, no visible scores, answer fidelity) apply to in-intake forms.

Verification

  • Branch tree is byte-identical to the fully verified fixed feature tip; tsc ×5 + eslint clean; unit: zambdas 1913/1913 (incl. flow List round-trip, base-flow catalog, composeFormIds ordering/dedup, consent-only canonical mapping); utils 505/507 (2 candidApi flakes pass in isolation, untouched).
  • Browser e2e with answer-fidelity assertions (real booking for a live patient session): compose served [ACE (base), SWYC (service flow)]; ACE saved as valueCoding codes 1,0,1,0… = displays Yes,No,… matching the exact clicks; SWYC 4 pages with [10,12,6,2] answers — each page only its own, zero duplication, zero raw-code strings; both QRs completed, no re-entry loop (completed forms skipped; re-edited form correctly revisited); resume pre-seeded all saved answers; EHR visit page shows display text (": Somewhat", not ": 2"); base-card auto-save persisted via the UI.

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

🤖 Generated with Claude Code

…booking

Practices design their own paperwork flows: which forms every booking on a base
intake includes, plus per-service form bundles, composed at booking time.

Data model (FHIR List, tagged practice-paperwork-flow):
- Service flow: { slug, name, base: standard | consent-only, ordered formIds },
  referenced by ServiceCategory config (paperworkFlowId); assigning a service to
  a flow is reconciled across categories (single-valued field).
- Base flow: 3 fixed Lists, one per base intake canonical (in-person / virtual /
  consent-only), marked by a paperwork-flow-canonical extension; auto-created
  idempotently (ensureBaseFlows); identity is fixed, only their form list edits.

Booking time (create-appointment):
- The slot's service category resolves its paperworkFlowId; a consent-only flow
  base maps to the consent-form-only subtype (lite intake canonical); the flow id
  is stamped on the Encounter and Appointment. Flow-lookup failures fall back to
  the mode default and are logged, never silently.

Form resolution (get-practice-managed-questionnaires):
- A booking's forms = base-intake forms (the base flow bound to the resolved
  canonical) + service-flow forms, base-first, de-duped keep-first
  (composeFormIds). Same-base isolation falls out: a consent-only booking never
  inherits standard-intake forms. Flow searches are paginated. The standalone
  send-form path is unchanged.

Admin (Paperwork Flows tab):
- Base intake cards (In-person / Virtual / Consent-only) with auto-saving
  add/remove/reorder of attached forms — debounced reorder, sequenced saves with
  a stable unmount flush, and failed saves surfacing an error and reconciling to
  refetched server state.
- Service flows table + dialog (name, auto-slug via shared slugify, base, forms,
  applies-to-services) with "New flow" scoped to that section.
- admin-{list,create,update,delete}-paperwork-flow zambdas with base-flow guard
  rails (not creatable/deletable; only formIds editable; delete refuses to
  proceed when the guard fetch fails).

Unit tests: flow List round-trip, base-flow catalog, composeFormIds
ordering/dedup, canonical mapping for the consent-only base.

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