Conversation
There was a problem hiding this comment.
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.
| 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` }), | ||
| }; |
There was a problem hiding this comment.
🤖 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).
| action = { | ||
| name: 'Match', | ||
| link: `/inbound-fax/${communicationId}/match`, | ||
| }; |
There was a problem hiding this comment.
🤖 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} |
There was a problem hiding this comment.
🤖 From Claude: Fixed in b39fdb4. Added title="Inbound fax PDF preview" to the iframe.
| // Mark the task as completed | ||
| await oystehr.fhir.patch({ | ||
| resourceType: 'Task', | ||
| id: taskId, | ||
| operations: [replaceOperation('/status', 'completed')], | ||
| }); |
There was a problem hiding this comment.
🤖 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.
|
|
||
| /** | ||
| * 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; | ||
| } | ||
|
|
There was a problem hiding this comment.
🤖 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(); |
There was a problem hiding this comment.
🤖 From Claude: Fixed in b39fdb4. readyToSubmit now includes both pdfUrl and taskId checks.
| try { | ||
| const { secrets, taskId, patientId, folderId, documentName, pdfUrl } = validateRequestParameters(input); | ||
|
|
||
| m2mToken = await checkOrCreateM2MClientToken(m2mToken, secrets); | ||
| const oystehr = createOystehrClient(m2mToken, secrets); |
There was a problem hiding this comment.
🤖 From Claude: Fixed in b39fdb4. communicationId is now destructured and used to verify that the Task's basedOn references the correct Communication before filing.
| // Fetch the target folder List | ||
| const folderList = await oystehr.fhir.get<List>({ | ||
| resourceType: 'List', | ||
| id: folderId, | ||
| }); |
There was a problem hiding this comment.
🤖 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' }, |
There was a problem hiding this comment.
🤖 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"}].
b39fdb4 to
13d1f4b
Compare
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>
13d1f4b to
8d759c3
Compare
Summary
handle-inbound-fax(webhook receiver),file-inbound-fax(attach to patient),delete-inbound-faxTest plan
cd packages/zambdas && npx vitest run test/unit/inbound-faxcd apps/ehr && npx vitest --config vitest.config.component-tests.ts tests/component/InboundFaxMatch🤖 Generated with Claude Code