Skip to content

Add inbound fax handling feature OTR-1928#6546

Open
dmabram wants to merge 3 commits intodevelopfrom
feature/inbound-fax-handling
Open

Add inbound fax handling feature OTR-1928#6546
dmabram wants to merge 3 commits intodevelopfrom
feature/inbound-fax-handling

Conversation

@dmabram
Copy link
Copy Markdown
Contributor

@dmabram dmabram commented Mar 15, 2026

Summary

  • Add inbound fax handling workflow: receive, match to patient, and file faxes into patient records
  • New zambdas: handle-inbound-fax (webhook receiver), file-inbound-fax (attach to patient), delete-inbound-fax
  • EHR page for matching inbound faxes to patients and filing them
  • Task integration for tracking unprocessed inbound faxes
  • Includes unit tests, component tests, E2E tests, and a seed script

Test plan

  • Verify inbound fax webhook creates a task with fax metadata
  • Navigate to an inbound fax task in the EHR
  • Match the fax to a patient via search
  • File the fax — verify it appears in the patient's documents
  • Delete an unneeded fax — verify task is resolved
  • Run unit tests: cd packages/zambdas && npx vitest run test/unit/inbound-fax
  • Run component tests: cd apps/ehr && npx vitest --config vitest.config.component-tests.ts tests/component/InboundFaxMatch

🤖 Generated with Claude Code

@dmabram dmabram changed the title Add inbound fax handling feature Add inbound fax handling feature OTR-1928 Mar 16, 2026
@dmabram dmabram requested a review from Copilot March 16, 2026 00:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an inbound fax workflow to the EHR by creating Tasks from inbound fax Communications, providing a UI to match/file/delete faxes, and integrating provider notifications and automated tests.

Changes:

  • Add new zambdas for inbound fax handling: subscription receiver plus EHR-auth endpoints to file/delete.
  • Add EHR UI route/page to match a fax to a patient and file it into a selected patient document folder.
  • Add task/notification plumbing (new fax task type + provider notification type) and corresponding unit/component/E2E tests plus a seed script.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
scripts/seed-inbound-fax.ts Adds a local seeding script to create a fake inbound fax Communication + Task.
packages/zambdas/test/unit/inbound-fax-validation.test.ts Adds unit tests for inbound-fax request parameter validation.
packages/zambdas/test/unit/inbound-fax-helpers.test.ts Adds tests for inbound-fax helper parsing logic (sender/pages/pdf URL).
packages/zambdas/src/subscriptions/communication/handle-inbound-fax/validateRequestParameters.ts Validates inbound fax Communication inputs for the subscription zambda.
packages/zambdas/src/subscriptions/communication/handle-inbound-fax/index.ts Creates inbound-fax Tasks and provider notification Communications on inbound fax receipt.
packages/zambdas/src/ehr/file-inbound-fax/validateRequestParameters.ts Validates inputs for filing an inbound fax into a patient folder.
packages/zambdas/src/ehr/file-inbound-fax/index.ts Creates a DocumentReference for the fax PDF, adds it to a patient folder List, and completes the Task.
packages/zambdas/src/ehr/delete-inbound-fax/validateRequestParameters.ts Validates inputs for deleting an inbound fax.
packages/zambdas/src/ehr/delete-inbound-fax/index.ts Deletes the fax PDF from Z3 (best-effort), deletes the Communication, and cancels the Task.
packages/utils/lib/types/data/tasks/types.ts Introduces FAX_TASK constants for category/code/input names.
packages/utils/lib/types/api/practitioner.types.ts Adds inbound_fax provider notification type.
config/oystehr/zambdas.json Registers new zambdas (subscription + EHR-auth endpoints).
apps/ehr/tests/e2e/specs/inbound-fax.spec.ts Adds Playwright coverage for the inbound fax match page.
apps/ehr/tests/component/InboundFaxMatch.test.tsx Adds component tests for inbound fax matching UI behavior.
apps/ehr/src/features/visits/in-person/hooks/useTasks.ts Adds inbound-fax Task rendering and action link routing.
apps/ehr/src/features/tasks/common.ts Adds “Fax” label for the inbound-fax task category.
apps/ehr/src/features/notifications/notifications.queries.ts Includes inbound-fax notifications in provider notifications query.
apps/ehr/src/features/inbound-fax/pages/InboundFaxMatch.tsx Implements the inbound fax match + file/delete UI page.
apps/ehr/src/api/api.ts Adds client helpers to execute the new zambdas.
apps/ehr/src/App.tsx Adds a new route for the inbound fax match page.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/seed-inbound-fax.ts Outdated
Comment on lines +24 to +33
const folderList = await oystehr.fhir.get<List>({
resourceType: 'List',
id: folderId,
});

if (!folderList) {
return {
statusCode: 404,
body: JSON.stringify({ error: `Folder List/${folderId} not found` }),
};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. The fhir.get() call is now wrapped in try/catch to properly handle 404s. Also added task ownership verification (category + basedOn) and folder-patient ownership validation (subject.reference check).

Comment on lines +507 to +510
action = {
name: 'Match',
link: `/inbound-fax/${communicationId}/match`,
};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. The action assignment is now wrapped in if (communicationId) to prevent broken /inbound-fax/undefined/match links.

{presignedPdfUrl ? (
<Box
component="iframe"
src={presignedPdfUrl}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. Added title="Inbound fax PDF preview" to the iframe.

Comment on lines +91 to +96
// Mark the task as completed
await oystehr.fhir.patch({
resourceType: 'Task',
id: taskId,
operations: [replaceOperation('/status', 'completed')],
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. The handler now fetches the Task via try/catch, verifies groupIdentifier.value === FAX_TASK.category, and confirms basedOn references the provided Communication before completing it.

Comment on lines +3 to +27

/**
* Tests for the helper functions used in handle-inbound-fax/index.ts.
* Since these are module-private functions, we replicate them here to validate the logic.
*/

const FAX_PAGES_EXTENSION_URL = 'https://extensions.fhir.oystehr.com/fax-pages';

function getSenderFaxNumber(communication: Communication): string {
const senderRef = communication.sender?.reference;
if (senderRef?.startsWith('#')) {
return senderRef.replace('#', '');
}
return senderRef ?? 'unknown';
}

function getPageCount(communication: Communication): number | undefined {
const ext = communication.extension?.find((e) => e.url === FAX_PAGES_EXTENSION_URL);
return ext?.valueInteger;
}

function getPdfUrl(communication: Communication): string | undefined {
return communication.payload?.[0]?.contentAttachment?.url;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. The test file now imports the actual exported getSenderFaxNumber, getPageCount, and getPdfUrl functions from handle-inbound-fax/index.ts instead of re-implementing them. Tests also updated to cover the contained Device identifier resolution pattern.

}
};

const readyToSubmit = !!confirmedSelectedPatient && !!selectedFolder && !!documentName.trim();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. readyToSubmit now includes both pdfUrl and taskId checks.

Comment on lines +17 to +21
try {
const { secrets, taskId, patientId, folderId, documentName, pdfUrl } = validateRequestParameters(input);

m2mToken = await checkOrCreateM2MClientToken(m2mToken, secrets);
const oystehr = createOystehrClient(m2mToken, secrets);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. communicationId is now destructured and used to verify that the Task's basedOn references the correct Communication before filing.

Comment on lines +23 to +27
// Fetch the target folder List
const folderList = await oystehr.fhir.get<List>({
resourceType: 'List',
id: folderId,
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. After fetching the folder List, we now verify folderList.subject?.reference === Patient/${patientId}`` before proceeding.

id: 'comm-123',
status: 'completed',
received: '2026-03-13T10:00:00Z',
sender: { reference: '#+15551234567' },
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Fixed in b39fdb4. The test fixture now uses a proper contained Device pattern with sender.reference: "#fax-sender" and a contained Device resource with identifier: [{system: "phone", value: "+15551234567"}].

@dmabram dmabram force-pushed the feature/inbound-fax-handling branch 2 times, most recently from b39fdb4 to 13d1f4b Compare March 16, 2026 18:19
Daniel Abrams and others added 3 commits March 24, 2026 19:43
Adds complete inbound fax workflow: subscription zambda creates tasks from
incoming faxes, match page allows viewing PDF, searching/matching to a patient,
selecting a folder, and filing or deleting the fax. Includes provider
notifications, unit tests, component tests, E2E tests, and a seed script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…te tests

- file-inbound-fax: use try/catch for fhir.get instead of unreachable falsy
  check, verify task is inbound-fax category, verify task basedOn matches
  communication, validate folder belongs to patient, use communicationId
- delete-inbound-fax: verify task category and basedOn before deleting
- handle-inbound-fax: fix sensitive data logging, resolve contained Device
  identifiers for sender fax number, validate pdfUrl before creating task,
  export helper functions for direct test imports
- useTasks: guard against undefined communicationId in task action links
- InboundFaxMatch: add pdfUrl/taskId to readyToSubmit, add iframe title
- Tests: import actual exported helpers instead of re-implementing them,
  update fixtures to use contained Device pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dmabram dmabram force-pushed the feature/inbound-fax-handling branch from 13d1f4b to 8d759c3 Compare March 24, 2026 23:52
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.

2 participants