Skip to content

Conversation

thomasyopes
Copy link
Contributor

@thomasyopes thomasyopes commented Jun 6, 2025

Issues:

Description

Testing

Check each PR.

Release Plan

  • ⚠️ Points to master
  • Merge this

Summary by CodeRabbit

  • New Features

    • Introduced unified appointment retrieval support for multiple EHR systems, including AthenaHealth, Elation, and Healthie.
    • Added new endpoints and utilities for fetching FHIR Bundles by resource type across supported EHRs.
    • Enhanced authentication with token-based (client credentials) support for select EHR integrations.
  • Bug Fixes

    • Improved handling of wait times during paginated data retrieval to ensure accurate delays.
  • Refactor

    • Centralized and standardized appointment and bundle fetching logic across EHR providers.
    • Updated type definitions and internal structures for better consistency and maintainability.
  • Chores

    • Removed deprecated endpoints and legacy bundle refresh functionality.
    • Improved type safety and validation for secrets management and authentication flows.

Thomas Yopes added 30 commits May 20, 2025 14:34
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Thomas Yopes and others added 12 commits June 5, 2025 04:36
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
… 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-199

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
feat(ehr): adding athena / elation / healthie to lambda + canvas get resources
Copy link

coderabbitai bot commented Jun 6, 2025

Walkthrough

This change refactors appointment and bundle retrieval logic for multiple EHR systems (AthenaHealth, Elation, Healthie, Canvas) to use handler-based abstractions and token-based client creation. It introduces new client creation utilities, updates type signatures, removes legacy refresh endpoints, and aligns types and schemas for improved consistency and token support across EHR integrations.

Changes

File(s) / Path(s) Change Summary
.../ehr/athenahealth/command/process-patients-from-appointments.ts,
.../ehr/elation/command/process-patients-from-appointments.ts,
.../ehr/healthie/command/process-patients-from-appointments.ts
Refactored appointment fetching to use handler-based pattern and token-based client creation; updated function signatures and internal logic.
.../ehr/athenahealth/shared.ts,
.../ehr/elation/shared.ts,
.../ehr/canvas/shared.ts
Updated client creation utilities to use createEhrClientWithClientCredentials instead of legacy method; internal implementation change only.
.../ehr/shared/utils/client.ts Renamed and updated createEhrClient to createEhrClientWithClientCredentials; updated type parameters and internal logic.
.../ehr/shared/command/clients/get-two-legged-client.ts Added new utility to obtain two-legged (client credentials) EHR clients with token and environment.
.../ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts Enhanced job logic to retrieve and pass tokenId for EHRs requiring client credentials.
.../ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts,
.../ehr/shared/utils/bundle/functions.ts,
.../ehr/shared/utils/bundle/types.ts,
.../ehr/shared/utils/job.ts
Updated types and function signatures to use singular EhrSource instead of plural; removed/updated bundle function types and refresh logic.
.../ehr/canvas/command/bundle/refresh-ehr-bundle.ts,
.../ehr/shared/command/bundle/refresh-ehr-bundles.ts,
.../ehr/api/bundle/refresh-ehr-bundle.ts
Removed legacy refresh bundle functions and related types.
.../ehr/command/get-appointments/ehr-get-appointments-direct.ts,
.../ehr/command/get-appointments/ehr-get-appointments.ts
Expanded appointment method enum and handler mapping to support AthenaHealth, Elation, Healthie; updated types and internal logic.
.../ehr/athenahealth/command/get-appointments.ts,
.../ehr/athenahealth/command/get-appointments-from-subscription-events.ts,
.../ehr/elation/command/get-appointments.ts,
.../ehr/healthie/command/get-appointments.ts
Added new appointment fetching functions for AthenaHealth, Elation, and Healthie EHRs.
.../ehr/athenahealth/shared.ts,
.../ehr/elation/shared.ts,
.../ehr/healthie/shared.ts
Added new client creation utilities for AthenaHealth, Elation, and Healthie EHRs.
.../ehr/canvas/command/get-bundle-by-resource-type.ts,
.../ehr/command/get-bundle-by-resource-type.ts
Added new bundle retrieval by resource type for Canvas; introduced handler-based mapping for future EHRs.
.../ehr/job/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts,
.../lambdas/src/shared/ehr.ts
Added optional tokenId property to request types and schemas.
.../ehr/environment.ts Introduced types and utilities for EHRs with client credentials; added type guards and updated related types.
.../ehr/secrets.ts Added dynamic secrets management, type guards, and Zod schemas for OAuth and API key secrets.
.../ehr/shared.ts,
.../ehr/athenahealth/index.ts,
.../ehr/canvas/index.ts,
.../ehr/elation/index.ts,
.../ehr/healthie/index.ts
Updated sleep function usage to accept milliseconds from duration objects; changed exported paginateWaitTime type.
.../routes/internal/ehr/patient.ts Removed POST route handler for refreshing EHR bundles.
.../routes/internal/ehr/practice.ts Updated import paths for secrets and dynamic secrets utilities.
.../util/config.ts Added a blank line for formatting.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant HandlerFactory
    participant TokenClientUtil
    participant EHRClient
    participant EHRApi

    Caller->>HandlerFactory: buildEhrGetAppointmentsHandler()
    HandlerFactory->>TokenClientUtil: createEhrClientWithClientCredentials()
    TokenClientUtil->>EHRClient: instantiate client with tokenId, env
    HandlerFactory-->>Caller: handler with getAppointments()
    Caller->>HandlerFactory: handler.getAppointments(method, params)
    HandlerFactory->>EHRApi: fetch appointments (using tokenId, practiceId, etc.)
    EHRApi-->>HandlerFactory: appointments[]
    HandlerFactory-->>Caller: appointments[]
Loading
sequenceDiagram
    participant JobRunner
    participant TwoLeggedClientUtil
    participant EHRClient
    participant BundleRetriever

    JobRunner->>TwoLeggedClientUtil: getTwoLeggedClientWithTokenIdAndEnvironment(ehr, cxId, practiceId)
    TwoLeggedClientUtil->>EHRClient: instantiate with tokenId, env
    TwoLeggedClientUtil-->>JobRunner: { client, tokenId, environment }
    JobRunner->>BundleRetriever: refreshEhrBundles({ ..., tokenId })
    BundleRetriever->>EHRClient: getBundleByResourceType({ ..., tokenId })
    EHRClient-->>BundleRetriever: Bundle
    BundleRetriever-->>JobRunner: done
Loading

Possibly related PRs

  • metriport/metriport#3626: Refactors AthenaHealth appointment fetching to use a handler factory with token-based client creation and adds appointment type filtering; both PRs modify AthenaHealth appointment processing.
  • metriport/metriport#3882: Refactors AthenaHealth, Elation, and Healthie appointment fetching to use handler-based and token-based client creation, directly related to the main PR’s changes.
  • metriport/metriport#3873: Introduces handler-based pattern and token-aware client creation for Canvas appointments, aligning with the refactor pattern of the main PR.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error code ERR_SSL_WRONG_VERSION_NUMBER
npm error errno ERR_SSL_WRONG_VERSION_NUMBER
npm error request to https://10.0.0.28:4873/punycode/-/punycode-2.3.1.tgz failed, reason: C01C6623387F0000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:354:
npm error
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-06T17_37_59_899Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2921bc and e7d1f1b.

📒 Files selected for processing (42)
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts (5 hunks)
  • packages/api/src/external/ehr/athenahealth/shared.ts (1 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts (0 hunks)
  • packages/api/src/external/ehr/canvas/shared.ts (1 hunks)
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (4 hunks)
  • packages/api/src/external/ehr/elation/shared.ts (2 hunks)
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts (5 hunks)
  • packages/api/src/external/ehr/shared/command/bundle/refresh-ehr-bundles.ts (0 hunks)
  • packages/api/src/external/ehr/shared/command/clients/get-two-legged-client.ts (1 hunks)
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts (3 hunks)
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts (2 hunks)
  • packages/api/src/external/ehr/shared/utils/bundle/functions.ts (2 hunks)
  • packages/api/src/external/ehr/shared/utils/bundle/types.ts (1 hunks)
  • packages/api/src/external/ehr/shared/utils/client.ts (3 hunks)
  • packages/api/src/external/ehr/shared/utils/job.ts (2 hunks)
  • packages/api/src/routes/internal/ehr/patient.ts (0 hunks)
  • packages/api/src/routes/internal/ehr/practice.ts (1 hunks)
  • packages/core/src/external/ehr/api/bundle/refresh-ehr-bundle.ts (0 hunks)
  • packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts (1 hunks)
  • packages/core/src/external/ehr/athenahealth/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/athenahealth/index.ts (1 hunks)
  • packages/core/src/external/ehr/athenahealth/shared.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/index.ts (2 hunks)
  • packages/core/src/external/ehr/canvas/shared.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (2 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (2 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-secrets.ts (3 hunks)
  • packages/core/src/external/ehr/elation/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/elation/index.ts (1 hunks)
  • packages/core/src/external/ehr/elation/shared.ts (1 hunks)
  • packages/core/src/external/ehr/environment.ts (1 hunks)
  • packages/core/src/external/ehr/healthie/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/healthie/index.ts (1 hunks)
  • packages/core/src/external/ehr/healthie/shared.ts (1 hunks)
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts (1 hunks)
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts (3 hunks)
  • packages/core/src/external/ehr/secrets.ts (1 hunks)
  • packages/core/src/external/ehr/shared.ts (1 hunks)
  • packages/core/src/util/config.ts (1 hunks)
  • packages/lambdas/src/shared/ehr.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • packages/api/src/routes/internal/ehr/patient.ts
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts
  • packages/api/src/external/ehr/shared/command/bundle/refresh-ehr-bundles.ts
  • packages/core/src/external/ehr/api/bundle/refresh-ehr-bundle.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...

**/*.ts: - Use the Onion Pattern to organize a package's code in layers

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
  • Error handling
    • Pass the original error as the new one’s cause so the stack trace is persisted
    • Error messages should have a static message - add dynamic data to MetriportError's additionalInfo prop
    • Avoid sending multiple events to Sentry for a single error
  • Global constants and variables
    • Move literals to constants declared after imports when possible (avoid magic numbers)
    • Avoid shared, global objects
  • Avoid using console.log and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().log()
    • don't create multi-line strings when using JSON.stringify()
  • Use eslint to enforce code style
  • Use prettier to format code
  • max column length is 100 chars
  • multi-line comments use /** */
  • scripts: top-level comments go after the import
  • packages/core/src/external/ehr/canvas/shared.ts
  • packages/core/src/util/config.ts
  • packages/lambdas/src/shared/ehr.ts
  • packages/core/src/external/ehr/elation/index.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/canvas/shared.ts
  • packages/core/src/external/ehr/shared.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/core/src/external/ehr/canvas/index.ts
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts
  • packages/api/src/external/ehr/elation/shared.ts
  • packages/core/src/external/ehr/healthie/command/get-appointments.ts
  • packages/api/src/routes/internal/ehr/practice.ts
  • packages/core/src/external/ehr/athenahealth/shared.ts
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/elation/command/get-appointments.ts
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts
  • packages/api/src/external/ehr/shared/utils/job.ts
  • packages/core/src/external/ehr/healthie/shared.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts
  • packages/core/src/external/ehr/healthie/index.ts
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments.ts
  • packages/api/src/external/ehr/shared/utils/bundle/types.ts
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts
  • packages/core/src/external/ehr/elation/shared.ts
  • packages/api/src/external/ehr/shared/utils/bundle/functions.ts
  • packages/core/src/external/ehr/environment.ts
  • packages/api/src/external/ehr/shared/utils/client.ts
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts
  • packages/core/src/external/ehr/command/get-secrets.ts
  • packages/api/src/external/ehr/shared/command/clients/get-two-legged-client.ts
  • packages/core/src/external/ehr/secrets.ts
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts
🧠 Learnings (4)
packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (3)
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:27-49
Timestamp: 2025-05-28T19:22:09.281Z
Learning: In packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:0-0
Timestamp: 2025-05-28T19:20:47.442Z
Learning: In packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3960
File: packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts:11-19
Timestamp: 2025-06-05T16:28:33.694Z
Learning: In the EhrGetAppointmentsDirect class in packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts, type casting `as T[]` for the handler return value is considered safe and doesn't require additional runtime validation by the user thomasyopes.
🧬 Code Graph Analysis (23)
packages/core/src/external/ehr/elation/index.ts (2)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/core/src/external/ehr/shared.ts (1)
  • paginateWaitTime (52-52)
packages/api/src/external/ehr/athenahealth/shared.ts (2)
packages/core/src/external/ehr/environment.ts (1)
  • EhrPerPracticeParams (38-38)
packages/core/src/external/ehr/athenahealth/index.ts (1)
  • AthenaEnv (153-153)
packages/api/src/external/ehr/canvas/shared.ts (2)
packages/core/src/external/ehr/environment.ts (1)
  • EhrPerPracticeParams (38-38)
packages/core/src/external/ehr/canvas/index.ts (1)
  • CanvasEnv (82-82)
packages/core/src/external/ehr/athenahealth/index.ts (2)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/core/src/external/ehr/shared.ts (1)
  • paginateWaitTime (52-52)
packages/core/src/external/ehr/canvas/index.ts (2)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/core/src/external/ehr/shared.ts (1)
  • paginateWaitTime (52-52)
packages/api/src/external/ehr/elation/shared.ts (2)
packages/core/src/external/ehr/elation/index.ts (1)
  • ElationEnv (56-56)
packages/core/src/external/ehr/environment.ts (1)
  • EhrPerPracticeParams (38-38)
packages/core/src/external/ehr/healthie/command/get-appointments.ts (7)
packages/core/src/external/ehr/healthie/index.ts (1)
  • getAppointments (221-303)
packages/core/src/external/ehr/canvas/command/get-appointments.ts (1)
  • getAppointments (7-28)
packages/core/src/external/ehr/athenahealth/command/get-appointments.ts (1)
  • getAppointments (6-32)
packages/core/src/external/ehr/elation/command/get-appointments.ts (1)
  • getAppointments (6-27)
packages/shared/src/interface/external/ehr/healthie/appointment.ts (1)
  • AppointmentWithAttendee (11-13)
packages/shared/src/index.ts (1)
  • BadRequestError (40-40)
packages/core/src/external/ehr/healthie/shared.ts (1)
  • createHealthieClient (7-32)
packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1)
packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (1)
  • getBundleByResourceType (19-25)
packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts (1)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (9-9)
packages/api/src/external/ehr/shared/utils/job.ts (1)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (9-9)
packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts (2)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (1)
  • GetAppointmentsClientRequest (16-16)
packages/core/src/external/ehr/athenahealth/shared.ts (1)
  • createAthenaHealthClient (8-38)
packages/core/src/external/ehr/healthie/index.ts (2)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/core/src/external/ehr/shared.ts (1)
  • paginateWaitTime (52-52)
packages/api/src/external/ehr/shared/utils/bundle/types.ts (1)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (9-9)
packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (3)
packages/api/src/external/ehr/elation/shared.ts (1)
  • createElationClientWithTokenIdAndEnvironment (98-107)
packages/api/src/external/ehr/shared/utils/appointment.ts (1)
  • getLookForwardTimeRange (32-43)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-factory.ts (1)
  • buildEhrGetAppointmentsHandler (6-12)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (2)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (1)
  • GetAppointmentsClientRequest (16-16)
packages/shared/src/index.ts (1)
  • BadRequestError (40-40)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (1)
packages/shared/src/interface/external/ehr/healthie/appointment.ts (1)
  • AppointmentWithAttendee (11-13)
packages/api/src/external/ehr/shared/utils/bundle/functions.ts (1)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (9-9)
packages/core/src/external/ehr/environment.ts (6)
packages/core/src/external/ehr/athenahealth/index.ts (1)
  • AthenaEnv (153-153)
packages/core/src/external/ehr/elation/index.ts (1)
  • ElationEnv (56-56)
packages/core/src/external/ehr/canvas/index.ts (1)
  • CanvasEnv (82-82)
packages/core/src/external/ehr/healthie/index.ts (1)
  • HealthieEnv (46-46)
packages/core/src/external/ehr/eclinicalworks/index.ts (1)
  • EClinicalWorksEnv (15-15)
packages/shared/src/domain/jwt-token.ts (1)
  • JwtTokenInfo (1-5)
packages/api/src/external/ehr/shared/utils/client.ts (1)
packages/core/src/external/ehr/environment.ts (3)
  • EhrEnv (9-9)
  • EhrClientWithClientCredentials (27-27)
  • EhrClientWithClientCredentialsParams (28-31)
packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts (3)
packages/shared/src/interface/external/ehr/healthie/appointment.ts (1)
  • AppointmentWithAttendee (11-13)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-factory.ts (1)
  • buildEhrGetAppointmentsHandler (6-12)
packages/api/src/external/ehr/shared/utils/appointment.ts (1)
  • getLookForwardTimeRange (32-43)
packages/core/src/external/ehr/command/get-secrets.ts (3)
packages/core/src/external/ehr/environment.ts (3)
  • EhrPerPracticeParams (38-38)
  • EhrEnvAndClientCredentials (22-26)
  • EhrEnv (9-9)
packages/core/src/external/ehr/secrets.ts (1)
  • EhrSourceWithDynamicSecrets (10-10)
packages/shared/src/index.ts (1)
  • BadRequestError (40-40)
packages/api/src/external/ehr/shared/command/clients/get-two-legged-client.ts (5)
packages/core/src/external/ehr/environment.ts (4)
  • EhrPerPracticeParams (38-38)
  • EhrSourceWithClientCredentials (16-16)
  • EhrClientWithClientCredentials (27-27)
  • EhrEnv (9-9)
packages/api/src/external/ehr/canvas/shared.ts (1)
  • createCanvasClientWithTokenIdAndEnvironment (7-16)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • createAthenaClientWithTokenIdAndEnvironment (7-16)
packages/api/src/external/ehr/elation/shared.ts (1)
  • createElationClientWithTokenIdAndEnvironment (98-107)
packages/shared/src/index.ts (1)
  • BadRequestError (40-40)
packages/core/src/external/ehr/secrets.ts (1)
packages/core/src/external/ehr/shared.ts (4)
  • getSecretsOauthSchema (60-64)
  • GetSecretsOauthResult (65-65)
  • getSecretsApiKeySchema (67-70)
  • GetSecretsApiKeyResult (71-71)
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
  • GitHub Check: ihe_stack / deploy
  • GitHub Check: mllp-server / deploy
  • GitHub Check: infra-api-lambdas / deploy
  • GitHub Check: api / deploy
  • GitHub Check: Analyze (javascript)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (79)
packages/core/src/util/config.ts (1)

215-215: No-op formatting change
The added blank line has no impact on functionality or style.

packages/core/src/external/ehr/shared.ts (1)

52-52:

✅ Verification successful

Verify all consumers handle the Duration type correctly.

The change from milliseconds to dayjs.Duration improves type safety, but this is a breaking change. Ensure all EHR clients calling sleep(paginateWaitTime) have been updated to use sleep(paginateWaitTime.asMilliseconds()).


🏁 Script executed:

#!/bin/bash
# Description: Verify all usages of paginateWaitTime call .asMilliseconds()
# Expected: All sleep calls should use .asMilliseconds()

echo "Searching for paginateWaitTime usage patterns..."
rg -A 2 -B 2 "paginateWaitTime" --type ts

echo -e "\nSearching for sleep calls with paginateWaitTime..."
rg "sleep.*paginateWaitTime" --type ts

Length of output: 4856


All EHR clients now use .asMilliseconds()

All occurrences of sleep(paginateWaitTime) have been updated to sleep(paginateWaitTime.asMilliseconds()) in the Healthie, Elation, Canvas, and Athenahealth integrations. No further action is required.

packages/core/src/external/ehr/secrets.ts (1)

4-13: LGTM! Well-structured type definitions and type guard.

The readonly tuple with const assertion and type guard function provide excellent type safety for identifying EHR sources requiring dynamic secrets.

packages/core/src/external/ehr/canvas/shared.ts (1)

5-5: LGTM! Import path correctly updated for centralized secrets.

The import path change aligns with the new secrets.ts module for centralized EHR secrets management.

packages/lambdas/src/shared/ehr.ts (1)

83-83: LGTM! Schema extension supports token-based authentication.

The optional tokenId field properly supports the token-based authentication pattern being implemented across EHR integrations.

packages/core/src/external/ehr/job/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts (1)

5-5: LGTM! Clean addition of optional tokenId property.

The addition of the optional tokenId property follows TypeScript conventions and maintains backward compatibility while supporting the new token-based authentication flow.

packages/api/src/external/ehr/athenahealth/shared.ts (2)

5-5: LGTM! Import updated for client credentials authentication.

The import change from createEhrClient to createEhrClientWithClientCredentials aligns with the broader refactoring to support token-based authentication flows.


10-10: LGTM! Function call updated consistently with import change.

The function call is properly updated to use the new client credentials-based authentication method, maintaining the same interface and return type.

packages/core/src/external/ehr/healthie/index.ts (1)

273-273: LGTM! Correct adaptation to Duration type change.

The change from sleep(paginateWaitTime) to sleep(paginateWaitTime.asMilliseconds()) correctly adapts to the type change where paginateWaitTime is now a dayjs.Duration object instead of a numeric millisecond value. This ensures the sleep function receives the expected numeric millisecond value.

packages/api/src/routes/internal/ehr/practice.ts (1)

1-2: LGTM! Improved modularity with separated imports.

The separation of getSecrets and isEhrSourceWithDynamicSecrets into different modules reflects better code organization and separation of concerns. The imports are correctly updated to match the new module structure.

packages/core/src/external/ehr/elation/index.ts (1)

266-266: LGTM! Correct duration conversion for sleep function.

This change properly converts the dayjs.Duration object to milliseconds before passing to the sleep function, ensuring type compatibility and correct timing behavior.

packages/core/src/external/ehr/athenahealth/index.ts (1)

1595-1595: LGTM! Consistent duration conversion across EHR implementations.

This change aligns with the standardization of sleep duration handling across all EHR integrations, properly converting the dayjs duration to milliseconds.

packages/core/src/external/ehr/canvas/index.ts (2)

777-777: LGTM! Proper duration conversion in FHIR resource pagination.

This change correctly converts the dayjs duration to milliseconds for the sleep function in the FHIR resource pagination helper.


883-883: LGTM! Consistent duration handling in appointment pagination.

This change mirrors the FHIR resource pagination fix, ensuring consistent sleep duration handling across all Canvas pagination helpers.

packages/api/src/external/ehr/canvas/shared.ts (2)

5-5: LGTM! Updated import for client credentials authentication.

This import change aligns with the architectural shift to client credentials-based authentication across EHR integrations.


10-10: LGTM! Consistent client creation with credentials support.

This function call update completes the transition to client credentials authentication while maintaining the same interface and return type.

packages/api/src/external/ehr/elation/shared.ts (2)

22-22: LGTM: Import updated for client credentials authentication.

The import change from createEhrClient to createEhrClientWithClientCredentials aligns with the architectural shift to client credentials-based authentication flows.


101-106: LGTM: Function call updated consistently with import change.

The function call correctly uses the new createEhrClientWithClientCredentials function while maintaining the same interface and return type.

packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts (2)

4-4: LGTM: Import updated to include both EhrSource and EhrSources.

The addition of EhrSource to the existing import allows for more precise typing in the function parameter.


48-48: LGTM: Type refinement from EhrSources to EhrSource.

The parameter type change from EhrSources to EhrSource is more semantically correct since the function operates on a single EHR source, not multiple sources.

packages/api/src/external/ehr/shared/utils/job.ts (3)

2-2: LGTM: Import updated to use EhrSource for better type precision.

The change from EhrSources to EhrSource aligns with the codebase-wide type refinement for representing single EHR sources.


7-7: LGTM: Type definitions updated to use EhrSource.

The type changes in StartCreateResourceDiffBundlesJobParams and GetResourceDiffBundlesJobPayloadParams are semantically correct since these represent operations on individual EHR sources.

Also applies to: 15-15


25-25: LGTM: Function parameter type updated consistently.

The parameter type change from EhrSources to EhrSource maintains consistency with the updated type definitions and accurately reflects that the function operates on a single EHR source.

packages/core/src/external/ehr/athenahealth/shared.ts (2)

17-29: LGTM: OAuth secrets retrieval and environment validation.

The implementation correctly fetches OAuth secrets and validates the environment with appropriate error handling.


30-38: LGTM: Token handling and client creation.

The optional token information retrieval and client creation logic is well-implemented, following the expected pattern for two-legged authentication.

packages/core/src/external/ehr/healthie/command/get-appointments.ts (1)

1-27:

✅ Verification successful

Well-structured appointment retrieval function following established patterns.

The implementation correctly follows the same validation and client creation pattern used by other EHR sources. However, I notice this implementation doesn't extract or use the tokenId parameter from GetAppointmentsClientRequest, unlike the Canvas, AthenaHealth, and Elation implementations.

Please verify if Healthie intentionally doesn't support token-based authentication, as other EHR sources extract and use tokenId:


🏁 Script executed:

#!/bin/bash
# Verify if Healthie should support tokenId like other EHR sources
echo "Checking if other EHR appointment functions use tokenId..."
rg -A 10 "const.*tokenId.*=.*params" packages/core/src/external/ehr/*/command/get-appointments.ts

echo "Checking if createHealthieClient supports tokenId..."
rg -A 5 "createHealthieClient" packages/core/src/external/ehr/healthie/shared.ts

Length of output: 4095


No tokenId support required for Healthie client

The Healthie client’s createHealthieClient signature only accepts cxId and practiceId—it does not take a tokenId, unlike the Canvas, AthenaHealth, and Elation clients. This indicates that Healthie’s API integration doesn’t rely on a per-request tokenId parameter. No changes are needed.

packages/core/src/external/ehr/healthie/shared.ts (1)

7-32: LGTM! Clean client creation function following established patterns.

The implementation correctly follows the client creation pattern with proper environment validation and error handling. The use of getSecretsApiKeySchema instead of OAuth schema suggests Healthie uses API key authentication rather than OAuth, which explains why this implementation differs from other EHR sources that support token-based authentication.

packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts (2)

62-70: Excellent token-based authentication implementation.

The conditional token retrieval logic is well-implemented, properly checking if the EHR source requires client credentials before fetching the token. This follows the architecture pattern for supporting different authentication mechanisms across EHR sources.


75-75: Clean conditional tokenId inclusion.

The conditional spread syntax ...(tokenId ? { tokenId } : {}) is the correct approach for optionally including the tokenId parameter, following TypeScript best practices.

packages/core/src/external/ehr/elation/shared.ts (1)

8-38: Well-implemented client creation with token support.

The function properly implements optional token-based authentication following the OAuth pattern. The conditional token info retrieval (tokenId ? await getTokenInfo(tokenId) : undefined) and proper environment validation demonstrate good defensive programming practices.

packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1)

1-31: LGTM! Clean implementation following established patterns.

The function correctly implements the Canvas bundle retrieval handler with proper parameter destructuring, optional token support, and appropriate client usage. The mapping of ehrPatientId to canvasPatientId is clear and the return type aligns with the expected interface.

packages/core/src/external/ehr/elation/command/get-appointments.ts (1)

6-27: Excellent input validation and error handling.

The function implements thorough validation for required date parameters with comprehensive error context. The BadRequestError includes helpful debugging information including method name, EHR source, and all relevant parameters. The optional tokenId handling is consistent with the broader refactor pattern.

packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts (3)

6-6: Appropriate import update for the refactor.

The import correctly reflects the migration from the deprecated refreshEhrBundle to the new getBundleByResourceType function.


18-18: Good addition of tokenId parameter.

The tokenId parameter addition supports the new token-based authentication pattern across EHR integrations.


36-45: Migration preserves intended refresh behavior.

The replacement of refreshEhrBundle with getBundleByResourceType correctly maintains the refresh functionality by explicitly setting useCachedBundle: false. The addition of metriportPatientId and conditional tokenId parameters aligns with the new handler interface.

packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (3)

2-3: Good imports for the handler-based refactor.

The new imports correctly support the migration to handler-based appointment fetching with proper method enumeration and typed appointment interfaces.

Also applies to: 13-13


27-27: Appropriate client creation function update.

The change to createElationClientWithTokenIdAndEnvironment aligns with the token-based authentication pattern established across EHR integrations.


149-166: Clean handler-based appointment fetching implementation.

The refactor from direct client usage to the handler pattern is well-executed. The explicit method specification (AppointmentMethods.elationGetAppointments) provides clear intent, and the tokenId integration follows the established authentication pattern. The function maintains the same error handling and date range logic while improving architectural consistency.

packages/core/src/external/ehr/athenahealth/command/get-appointments.ts (1)

6-32: LGTM! Well-implemented appointment retrieval function.

The function follows good practices with comprehensive parameter validation, meaningful error messages with context, and clean conditional parameter passing using spread operators.

packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts (1)

5-21: Verify if parameter validation is intentionally omitted.

This function lacks the fromDate/toDate validation present in the regular getAppointments function. While this might be intentional for subscription events, please confirm this is the expected behavior to ensure consistency across the API.

packages/api/src/external/ehr/shared/utils/bundle/types.ts (1)

2-2: LGTM! Appropriate type refinement.

The change from EhrSources to EhrSource improves type precision and aligns with the broader refactoring to use singular EHR source types.

Also applies to: 5-5

packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (3)

1-1: LGTM! Well-organized imports with clear aliasing.

The import aliases (BookedAppointmentAthena, BookedAppointmentElation) effectively prevent naming conflicts while maintaining clarity about the source EHR system.

Also applies to: 3-4


22-26: LGTM! Appropriate union type expansion.

The Appointment union type correctly includes all supported EHR appointment types, enabling type-safe handling across different systems.


28-35: LGTM! Comprehensive appointment method enumeration.

The enum expansion appropriately covers all EHR systems including multiple methods for AthenaHealth (regular and subscription events), following a consistent naming convention.

packages/api/src/external/ehr/shared/utils/bundle/functions.ts (4)

6-6: LGTM: Type import updated correctly.

The change from EhrSources to EhrSource aligns with the refactoring from plural to singular type naming, which is more semantically correct for representing individual EHR sources.


9-9: LGTM: Removed unused type import.

The removal of RefreshEhrBundleParamsForClient is consistent with the broader refactoring that eliminates the refresh bundle functionality in favor of the new handler-based approach.


38-46: LGTM: Type consistency maintained.

The change from EhrSources to EhrSource for the record type is consistent with the import changes and provides better type safety for individual EHR source lookups.


48-48: LGTM: Function signature updated consistently.

The parameter type change from EhrSources to EhrSource maintains consistency with the type refactoring throughout the file.

packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (4)

6-15: LGTM: Well-structured request types.

The request types are properly defined with clear separation between the main request and client request types. The optional tokenId parameter supports the new token-based authentication flows.


19-25: LGTM: Clean handler delegation pattern.

The function properly delegates to the appropriate handler based on the EHR source, following the established pattern used in other EHR command modules.


39-47: LGTM: Proper error handling with informative messages.

The error handling follows the coding guidelines with static error messages and dynamic data in the additionalInfo property.


31-37:

❓ Verification inconclusive

Verify implementation timeline for remaining EHR sources.

Currently only Canvas has an implementation while other EHR sources are undefined. This is acceptable for the current refactoring phase.


🏁 Script executed:

#!/bin/bash
# Description: Check if other EHR sources have corresponding get-bundle-by-resource-type implementations

echo "Searching for get-bundle-by-resource-type implementations across EHR sources..."

# Search for Canvas implementation (should exist)
echo "Canvas implementation:"
fd "get-bundle-by-resource-type" packages/core/src/external/ehr/canvas

# Search for other EHR implementations
echo -e "\nAthena implementation:"
fd "get-bundle-by-resource-type" packages/core/src/external/ehr/athenahealth

echo -e "\nElation implementation:"
fd "get-bundle-by-resource-type" packages/core/src/external/ehr/elation

echo -e "\nHealthie implementation:"
fd "get-bundle-by-resource-type" packages/core/src/external/ehr/healthie

Length of output: 782


Confirm implementation plan for remaining EHR sources

Only Canvas currently has a getBundleByResourceType implementation. The other sources are still mapped to undefined in:

  • packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (lines 31–37)

Please outline your timeline or approach for adding these implementations (Athena, Elation, Healthie, eClinicalWorks), or update this map when they become available.

packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (5)

2-6: LGTM: New EHR handler imports added consistently.

The imports for AthenaHealth, Elation, and Healthie appointment handlers are properly added and follow the established naming conventions with aliasing to avoid conflicts.


25-27: LGTM: Type renames improve clarity.

The renames from GetAppointments to GetAppointmentsFn and AppointmentMethodsMap to AppointmentFnMap make the types more descriptive and follow the Fn suffix convention for function types.


29-37: LGTM: Handler mapping expanded appropriately.

The mapping now includes handlers for AthenaHealth (both variants), Elation, and Healthie, with proper undefined placeholders for eClinicalWorks. This maintains consistency with the supported EHR sources.


39-39: LGTM: Function visibility appropriately restricted.

Removing the export keyword makes this function internal to the module, which is appropriate since it's only used within this file and doesn't need to be part of the public API.


42-42: LGTM: Error message improved for clarity.

The error message is more specific and follows the coding guidelines with a static message and dynamic data in the additionalInfo property.

packages/core/src/external/ehr/environment.ts (3)

11-21: LGTM: Well-designed client credentials support.

The constant array, type alias, and type guard function provide a clean way to identify EHR sources that require client credentials. The type guard follows TypeScript best practices for runtime type checking.


27-31: LGTM: Improved type definitions for client credentials.

The new types EhrClientWithClientCredentials and EhrClientWithClientCredentialsParams are more specific than the previous EhrClientTwoLeggedAuth types and properly include token information and practice ID requirements.


2-2:

❓ Verification inconclusive

Verify consistency of EhrSources import.

The import uses EhrSources (plural) while other files in this PR are transitioning to EhrSource (singular). Ensure this is intentional.


🏁 Script executed:

#!/bin/bash
# Description: Check usage of EhrSources vs EhrSource in this file and related files

echo "Checking EhrSources usage in environment.ts:"
rg "EhrSources\." packages/core/src/external/ehr/environment.ts

echo -e "\nChecking if EhrSource should be used instead:"
rg "EhrSource[^s]" packages/core/src/external/ehr/environment.ts

echo -e "\nChecking usage in ehrSourcesWithClientCredentials definition:"
rg -A 5 "ehrSourcesWithClientCredentials" packages/core/src/external/ehr/environment.ts

Length of output: 1447


Please confirm EhrSources vs. EhrSource consistency

It looks like environment.ts intentionally uses the EhrSources enum (e.g. EhrSources.canvas) to build the ehrSourcesWithClientCredentials array, and then defines the singular EhrSourceWithClientCredentials type from its values. If other files have switched to importing a singular EhrSource type, double-check that:

environment.ts should indeed import the enum as EhrSources
• All references elsewhere expecting a single source use the EhrSource type (or the new enum member type)

Once you verify that files importing the singular form aren’t broken by this plural import here, you can resolve this.

packages/api/src/external/ehr/shared/utils/client.ts (2)

2-3: LGTM! Clear type naming improvement.

The rename from EhrClientTwoLeggedAuth to EhrClientWithClientCredentials is more descriptive and aligns with the authentication pattern being used.


38-52: Function rename improves clarity.

The function rename from createEhrClient to createEhrClientWithClientCredentials makes the authentication method explicit and aligns with the broader refactoring to client credentials authentication.

packages/api/src/external/ehr/shared/command/clients/get-two-legged-client.ts (4)

1-11: Well-structured imports and dependencies.

The imports are organized logically and include all necessary types and functions for the unified client creation interface.


20-31: Excellent abstraction with proper documentation.

The function provides a clean, unified interface for obtaining EHR clients with token IDs. The JSDoc documentation clearly explains the parameters and purpose.


45-49: Consider adding explicit type annotation for better clarity.

While the mapping works correctly, consider adding an explicit type annotation to improve readability:

-const clientWithTokenIdAndEnvironmentMethodsBy: GetClientWithTokenIdAndEnvironmentFnMap = {
+const clientWithTokenIdAndEnvironmentMethodsBy: GetClientWithTokenIdAndEnvironmentFnMap = {

Actually, the type annotation is already present. The implementation looks good.


55-59: Proper error handling with context.

The error handling follows best practices by using BadRequestError with contextual information about the unsupported EHR source.

packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts (3)

1-2: Good adoption of handler-based appointment retrieval.

The new imports align with the standardized handler pattern for appointment operations across EHR sources.


153-156:

❓ Verification inconclusive

Verify token-based client creation integration.

The client creation now returns both tokenId and client, which aligns with the new authentication flow. Ensure this change is consistent across all EHR sources and that the token is properly utilized downstream.


🏁 Script executed:

#!/bin/bash
# Description: Verify that other EHR sources use similar token-based client creation pattern

# Search for similar client creation patterns in other EHR sources
rg -A 5 "createElationClientWithTokenIdAndEnvironment|createCanvasClientWithTokenIdAndEnvironment" 

Length of output: 6822


Ensure Consistent Token & Client Handling Across EHR Integrations

I’ve confirmed that all create*ClientWithTokenIdAndEnvironment helpers now return { client, tokenId, environment }. In the Athena flow you’re correctly capturing both:

  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts
    const { tokenId, client } = await createAthenaClientWithTokenIdAndEnvironment({ cxId, practiceId });

However, the Elation and Canvas process scripts only destruct the token:

  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts
    const { tokenId } = await createElationClientWithTokenIdAndEnvironment({ cxId, practiceId });
  • packages/api/src/external/ehr/canvas/command/process-patients-from-appointments.ts
    const { tokenId } = await createCanvasClientWithTokenIdAndEnvironment({ cxId, practiceId });

Please verify that downstream operations in those modules don’t require the client instance. If they do (e.g., fetching appointments or syncing patients), update them to destruct and use both client and tokenId consistently.


193-240:

✅ Verification successful

Significant refactoring to handler pattern - verify handler implementation.

The getAppointmentsFromApi function has been completely refactored to use the handler pattern with buildEhrGetAppointmentsHandler. While this standardizes the approach across EHR sources, it's a significant change that needs verification.

Key changes:

  • Uses handler.getAppointments instead of direct API calls
  • Parameters renamed from startRange/endRange to fromDate/toDate
  • Token ID passed to handler

🏁 Script executed:

#!/bin/bash
# Description: Verify the handler implementation supports the expected methods and parameters

# Check if buildEhrGetAppointmentsHandler is properly implemented
ast-grep --pattern 'export function buildEhrGetAppointmentsHandler'

# Check AppointmentMethods enum definition
ast-grep --pattern 'export enum AppointmentMethods'

# Verify athena-specific appointment methods exist
rg "athenaGetAppointments|athenaGetAppointmentFromSubscriptionEvents" -A 3

Length of output: 3146


🏁 Script executed:

#!/bin/bash
# Locate the buildEhrGetAppointmentsHandler implementation and verify it exposes getAppointments
rg -n "buildEhrGetAppointmentsHandler" -C 5

Length of output: 12196


🏁 Script executed:

#!/bin/bash
# Inspect the handler interface for getAppointments signature
rg -n "export (interface|type) EhrGetAppointmentsHandler" -A5 packages/core/src/external/ehr/command/get-appointments

# Inspect the Direct implementation to ensure it defines getAppointments<T>
rg -n "class EhrGetAppointmentsDirect" -A10 packages/core/src/external/ehr/command/get-appointments

# Inspect the Cloud implementation to ensure it defines getAppointments<T>
rg -n "class EhrGetAppointmentsCloud" -A10 packages/core/src/external/ehr/command/get-appointments

Length of output: 3736


🏁 Script executed:

#!/bin/bash
# Inspect the GetAppointmentsRequest type to verify expected parameters
rg -n "export interface GetAppointmentsRequest" -A10 packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Search for the GetAppointmentsRequest type across the repository
rg -n "GetAppointmentsRequest" -A5 .

# If not found, print the top of the handler file to inspect its contents
head -n 80 packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts

Length of output: 9099


getAppointmentsFromApi refactoring validated

I’ve confirmed that:

  • buildEhrGetAppointmentsHandler() returns an EhrGetAppointmentsHandler with a getAppointments<T>(GetAppointmentsRequest) signature.
  • GetAppointmentsRequest includes fromDate/toDate, tokenId, cxId, practiceId, and departmentIds.
  • AppointmentMethods enum defines both athenaGetAppointments and athenaGetAppointmentFromSubscriptionEvents.
  • Both EhrGetAppointmentsDirect and EhrGetAppointmentsCloud correctly implement getAppointments and honor the provided parameters.

No further changes required. Approving this refactoring.

packages/core/src/external/ehr/command/get-secrets.ts (4)

2-2: Good addition of explicit EhrSources import.

Adding the explicit import for EhrSources improves clarity and follows good import practices.


13-13: Improved type organization with centralized import.

Moving EhrSourceWithDynamicSecrets to a dedicated secrets module improves code organization and makes type definitions more discoverable.


26-26: Consistent type naming with singular form.

The change from EhrSourcesWithDynamicSecrets to EhrSourceWithDynamicSecrets (plural to singular) is more consistent with TypeScript naming conventions where types typically represent a single entity.

Also applies to: 33-33, 36-36, 38-38, 40-40, 42-42, 49-49


52-52: Minor improvement in error message clarity.

The error message change to "Could not find handler to get secrets" is slightly clearer than the previous version.

packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts (6)

1-2: LGTM: Proper handler abstractions imported.

The new imports correctly introduce the handler-based approach for appointment processing, replacing direct API client usage.


28-28: LGTM: Proper import organization.

Moving shared types to the shared module improves modularity and follows good organizational practices.


182-182: LGTM: Explicit parameter passing improves clarity.

Making the practiceId parameter explicit enhances readability and follows the coding guidelines for clear function signatures.

Also applies to: 204-204


198-200: LGTM: Proper type extension maintains type safety.

The type definition correctly extends the base parameters while adding the log function parameter.


208-236: LGTM: Handler implementation follows the refactored pattern correctly.

The handler-based approach is implemented properly:

  • Uses the factory to create handlers
  • Maintains existing conditional logic for lookup modes
  • Passes correct parameters including the Healthie-specific appointment method
  • Preserves the same error handling boundaries

The handler creation on each call follows the factory pattern and maintains isolation between requests.


1-268: Excellent refactor: Successfully implements handler-based pattern.

This refactor successfully transitions from direct API client usage to the handler-based approach while:

  • Maintaining all existing business logic and error handling
  • Following TypeScript coding guidelines consistently
  • Improving modularity through proper abstraction layers
  • Supporting the new token-based authentication pattern
  • Preserving functional programming principles

The changes align well with the broader EHR integration standardization effort mentioned in the AI summary.

🧰 Tools
🪛 Biome (1.9.4)

[error] 60-60: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

Comment on lines +15 to +26
export const getSecretsOauthSchema = z.object({
environment: z.string(),
clientKey: z.string(),
clientSecret: z.string(),
});
export type GetSecretsOauthResult = z.infer<typeof getSecretsOauthSchema>;

export const getSecretsApiKeySchema = z.object({
environment: z.string(),
apiKey: z.string(),
});
export type GetSecretsApiKeyResult = z.infer<typeof getSecretsApiKeySchema>;
Copy link

@coderabbitai coderabbitai bot Jun 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove schema duplication between files.

These schemas duplicate the definitions in shared.ts (lines 60-71). Consider removing the schemas from shared.ts and importing them from this centralized secrets module to maintain single source of truth.

# In shared.ts, remove the duplicate schemas and import from secrets:
+import { getSecretsOauthSchema, getSecretsApiKeySchema } from "./secrets";
-export const getSecretsOauthSchema = z.object({
-  environment: z.string(),
-  clientKey: z.string(),
-  clientSecret: z.string(),
-});
-export type GetSecretsOauthResult = z.infer<typeof getSecretsOauthSchema>;
-
-export const getSecretsApiKeySchema = z.object({
-  environment: z.string(),
-  apiKey: z.string(),
-});
-export type GetSecretsApiKeyResult = z.infer<typeof getSecretsApiKeySchema>;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/core/src/external/ehr/secrets.ts around lines 15 to 26, the OAuth
and API key schemas are duplicated in shared.ts (lines 60-71). To fix this,
remove the schema definitions from shared.ts and instead import the existing
schemas from this secrets.ts module wherever needed. This centralizes the schema
definitions and avoids duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in downstream PR.

Copy link

Choose a reason for hiding this comment

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

@thomasyopes Perfect! Thanks for addressing the schema duplication concern in the downstream PR. That keeps the codebase cleaner and maintains a single source of truth for the schema definitions.

Comment on lines +8 to +16
export async function createAthenaHealthClient({
cxId,
practiceId,
tokenId,
}: {
cxId: string;
practiceId: string;
tokenId?: string;
}) {
Copy link

@coderabbitai coderabbitai bot Jun 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for required parameters.

The function should validate that cxId and practiceId are non-empty strings to fail fast with clear error messages.

export async function createAthenaHealthClient({
  cxId,
  practiceId,
  tokenId,
}: {
  cxId: string;
  practiceId: string;
  tokenId?: string;
}) {
+  if (!cxId?.trim()) {
+    throw new BadRequestError("cxId is required");
+  }
+  if (!practiceId?.trim()) {
+    throw new BadRequestError("practiceId is required");
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function createAthenaHealthClient({
cxId,
practiceId,
tokenId,
}: {
cxId: string;
practiceId: string;
tokenId?: string;
}) {
export async function createAthenaHealthClient({
cxId,
practiceId,
tokenId,
}: {
cxId: string;
practiceId: string;
tokenId?: string;
}) {
if (!cxId?.trim()) {
throw new BadRequestError("cxId is required");
}
if (!practiceId?.trim()) {
throw new BadRequestError("practiceId is required");
}
// …rest of implementation…
}
🤖 Prompt for AI Agents
In packages/core/src/external/ehr/athenahealth/shared.ts around lines 8 to 16,
the createAthenaHealthClient function lacks validation for required parameters
cxId and practiceId. Add checks to ensure cxId and practiceId are non-empty
strings at the start of the function, and throw clear errors if either is
missing or empty to enable fail-fast behavior with informative messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We control these inputs, we don't need this validation.

Copy link

Choose a reason for hiding this comment

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

@thomasyopes Understood! That makes sense for internal functions where you control the inputs. I'll keep that in mind for future reviews.

   (\   /)
  ( ._.)
 o_(")(")

✏️ Learnings added
Learnt from: thomasyopes
PR: metriport/metriport#3973
File: packages/core/src/external/ehr/athenahealth/shared.ts:8-16
Timestamp: 2025-06-06T18:17:23.549Z
Learning: For internal functions in the Metriport codebase where inputs are controlled by the development team, input validation for required parameters like cxId and practiceId is not needed and should be avoided to keep functions simple.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@thomasyopes thomasyopes added this pull request to the merge queue Jun 6, 2025
Merged via the queue into master with commit a7550d6 Jun 6, 2025
69 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Aug 26, 2025
19 tasks
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