8000 feat(ehr): adding athena / elation / healthie to lambda + canvas get resources by thomasyopes · Pull Request #3882 · metriport/metriport · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(ehr): adding athena / elation / healthie to lambda + canvas get resources #3882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Jun 6, 2025

Conversation

thomasyopes
Copy link
Contributor
@thomasyopes thomasyopes commented May 21, 2025

Issues:

Dependencies

Description

  • adding Elation / Athena / Healthie get-appointments core command
  • moving Canvas refresh from API to refresh lambda directly

Testing

  • Local
    • Athena / Elation / Healthie get appointments work
    • Canvas refresh resources works from refresh lambda with input token
  • Staging
    • Athena / Elation / Healthie get appointments work
    • Canvas refresh resources endpoint works
  • Sandbox
    • N/A
  • Production
    • Athena / Elation / Healthie get appointments work
    • Canvas refresh resources endpoint works

Release Plan

  • Upstream dependencies are met/released
  • Merge this

Summary by CodeRabbit

  • New Features
    • Added unified appointment fetching functions for AthenaHealth, Elation, and Healthie EHR systems, supporting flexible date ranges and department filtering.
    • Introduced new functions to retrieve FHIR Bundles by resource type from supported EHR sources.
    • Enhanced support for client credentials and token-based authentication across EHR integrations.
    • Added functionality to fetch appointments from AthenaHealth subscription events.
  • Improvements
    • Streamlined appointment and bundle fetching logic to use handler-based and client credentials approaches.
    • Expanded internal type safety and validation for EHR environment and secret management.
    • Refined error handling and validation for appointment requests with detailed context.
    • Updated pagination delays to use explicit millisecond durations for consistent timing.
  • Removals
    • Removed the ability to refresh EHR bundles via API and internal endpoints.
    • Deprecated and deleted related types and functions for EHR bundle refresh operations.
    • Removed POST route for EHR bundle refresh from internal API.
  • Chores
    • Updated and clarified internal type definitions and schema validations for EHR integrations.
    • Reorganized imports and improved code structure for EHR client creation and secret management.
    • Added new utility modules for managing EHR client credentials and secrets.
    • Adjusted type aliases and function signatures to unify EHR source handling and authentication models.

Thomas Yopes added 5 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>
Copy link
coderabbitai bot commented May 21, 2025

Walkthrough

This set of changes removes legacy EHR bundle refresh functions and related routes, refactors appointment fetching to use a new handler-based client-credential architecture with explicit tokenId and environment parameters, introduces new core modules for client creation and secret management, updates types and interfaces to support these changes, and adds new functions for appointment and bundle retrieval across multiple EHR sources.

Changes

File(s) / Group 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 approach with explicit tokenId, environment, and client creation via new utility functions. Updated function signatures and types accordingly.
.../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 EHR bundle refresh functions and related logic.
.../ehr/shared/utils/bundle/functions.ts
.../ehr/shared/utils/bundle/types.ts
.../ehr/shared/utils/job.ts
Updated types and function signatures to use EhrSource instead of EhrSources. Removed refreshEhrBundle from bundle functions/types.
.../ehr/shared/job/bundle/create-resource-diff-bundles/start-jobs-across-ehrs.ts
.../ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts
Updated to use EhrSource and to conditionally include tokenId for sources with client credentials.
.../ehr/shared/command/clients/get-two-legged-client.ts New module to create EHR clients with client credentials, returning client, tokenId, and environment.
.../ehr/athenahealth/shared.ts
.../ehr/canvas/shared.ts
.../ehr/elation/shared.ts
Switched client creation to use createEhrClientWithClientCredentials.
.../ehr/shared/utils/client.ts Renamed and updated createEhrClient to createEhrClientWithClientCredentials; changed types and generics to match client credentials model.
.../ehr/command/get-secrets.ts
.../ehr/secrets.ts
Updated and refactored secrets management: new types, schemas, and type guards for dynamic secrets; removed redundant type guard.
.../ehr/environment.ts Added types/constants for EHR sources with client credentials and updated client types accordingly.
.../ehr/athenahealth/command/get-appointments-from-subscription-events.ts
.../ehr/athenahealth/command/get-appointments.ts
.../ehr/canvas/command/get-bundle-by-resource-type.ts
.../ehr/elation/command/get-appointments.ts
.../ehr/healthie/command/get-appointments.ts
Added new appointment and bundle fetching functions for Athena, Elation, Healthie, and Canvas, supporting explicit environment and tokenId.
.../ehr/command/get-appointments/ehr-get-appointments-direct.ts
.../ehr/command/get-appointments/ehr-get-appointments.ts
Expanded appointment method enums and handler map to support new sources and methods. Updated types accordingly.
.../ehr/canvas/shared.ts
.../ehr/athenahealth/shared.ts
.../ehr/elation/shared.ts
Updated import paths and client creation logic for secrets and environment handling.
.../ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts Replaced legacy bundle refresh with new getBundleByResourceType logic, supporting tokenId and explicit identifiers.
.../ehr/job/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts
.../lambdas/src/shared/ehr.ts
Added optional tokenId to relevant types and schemas.
.../routes/internal/ehr/patient.ts Removed POST route for EHR bundle refresh.
.../routes/internal/ehr/practice.ts Separated imports for secrets and dynamic secrets type guard.
.../util/config.ts Added a blank line (no functional change).

Sequence Diagram(s)

EHR Client Creation and Appointment Fetching (Handler-Based)

sequenceDiagram
    participant API_Service
    participant ClientFactory
    participant SecretsManager
    participant TokenService
    participant EHR_Client
    participant AppointmentsHandler

    API_Service->>ClientFactory: createClientWithClientCredentials(cxId, practiceId)
    ClientFactory->>SecretsManager: getSecrets(cxId, practiceId, ehrSource)
    SecretsManager-->>ClientFactory: secrets (env, credentials)
    alt Needs token
        ClientFactory->>TokenService: getTokenInfo(tokenId)
        TokenService-->>ClientFactory: tokenInfo
    end
    ClientFactory->>EHR_Client: create(credentials, env, tokenInfo)
    ClientFactory-->>API_Service: { client, tokenId, environment }

    API_Service->>AppointmentsHandler: getAppointments({ client, tokenId, environment, ... })
    AppointmentsHandler->>EHR_Client: fetchAppointments(params)
    EHR_Client-->>AppointmentsHandler: appointments
    AppointmentsHandler-->>API_Service: appointments
Loading

Resource Diff Bundle Creation (With Optional Token)

sequenceDiagram
    participant JobStarter
    participant ClientFactory
    participant ResourceDiffHandler

    JobStarter->>ClientFactory: getTwoLeggedClientWithTokenIdAndEnvironment(ehr, cxId, practiceId)
    ClientFactory-->>JobStarter: { client, tokenId, environment }
    loop resourceTypes
        JobStarter->>ResourceDiffHandler: refreshEhrBundles({ ..., tokenId, ... })
        ResourceDiffHandler->>ClientFactory: getBundleByResourceType({ ..., tokenId, ... })
        ClientFactory-->>ResourceDiffHandler: bundle
    end
Loading

Secret Retrieval and Validation

sequenceDiagram
    participant Service
    participant SecretsManager
    participant SchemaValidator

    Service->>SecretsManager: getSecrets(ehr, cxId, practiceId)
    SecretsManager->>SchemaValidator: validate(secrets)
    alt Valid
        SchemaValidator-->>SecretsManager: secrets (typed)
        SecretsManager-->>Service: secrets
    else Invalid
        SchemaValidator-->>SecretsManager: error
        SecretsManager-->>Service: throw BadRequestError
    end
Loading

Possibly related PRs

  • RELEASE healthie v0 #3771: Improves error handling in AthenaHealth appointment fetching; related as it modifies AthenaHealth appointment code but does not refactor client creation or handler usage.

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: C02C9F3A007F0000: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-06T16_52_23_720Z-debug-0.log


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3592c20 and 47c2f23.

📒 Files selected for processing (7)
  • packages/core/src/external/ehr/athenahealth/index.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/index.ts (2 hunks)
  • packages/core/src/external/ehr/elation/index.ts (1 hunks)
  • packages/core/src/external/ehr/environment.ts (1 hunks)
  • packages/core/src/external/ehr/healthie/index.ts (1 hunks)
  • packages/core/src/external/ehr/secrets.ts (1 hunks)
  • packages/core/src/external/ehr/shared.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/core/src/external/ehr/secrets.ts
  • packages/core/src/external/ehr/canvas/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/external/ehr/environment.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/elation/index.ts
  • packages/core/src/external/ehr/healthie/index.ts
  • packages/core/src/external/ehr/shared.ts
🧬 Code Graph Analysis (2)
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/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)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • 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)
🔇 Additional comments (3)
packages/core/src/external/ehr/shared.ts (1)

52-52: LGTM! This addresses previous reviewer feedback.

Good change to keep paginateWaitTime as a dayjs Duration object instead of converting to milliseconds at creation time. This maintains type safety and semantic clarity, with conversion happening only when needed at call sites.

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

273-273: LGTM! Necessary conversion for Duration compatibility.

The explicit conversion to milliseconds is required since paginateWaitTime is now a dayjs Duration object and the sleep function expects a numeric millisecond value.

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

266-266: LGTM! Consistent with other EHR integrations.

The explicit conversion to milliseconds maintains consistency with similar changes across other EHR providers (Healthie, Athena, Canvas) and correctly handles the new Duration type.

✨ 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.

Thomas Yopes added 2 commits May 21, 2025 13:14
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>
@thomasyopes thomasyopes changed the title 199 elation contribution 1.5.1 feat(ehr): adding athena / elation / healthie to lambda + canvas get resources May 21, 2025
Thomas Yopes added 2 commits May 21, 2025 13:46
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>
@thomasyopes thomasyopes marked this pull request as ready for review May 21, 2025 21:19
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: 6

🔭 Outside diff range comments (1)
packages/infra/lib/ehr-nested-stack.ts (1)

203-214: ⚠️ Potential issue

Overly-permissive CORS policy on EhrBundleBucket

The bucket allows GET from * (any origin). While publicReadAccess is false, exposing CORS to every origin still lets any website consume presigned URLs without restriction and may violate data-residency or privacy constraints.

Consider scoping this down – e.g. to your front-end domain(s) – or omitting the CORS block entirely until a concrete cross-origin requirement emerges.

-  cors: [
-    {
-      allowedOrigins: ["*"],
-      allowedMethods: [s3.HttpMethods.GET],
-    },
-  ],
+  // ⚠️ Review CORS before production. Lock down to specific origins if required.
+  cors: [
+    {
+      allowedOrigins: ["https://app.example.com"], // tighten as needed
+      allowedMethods: [s3.HttpMethods.GET],
+    },
+  ],
🧹 Nitpick comments (19)
packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-cloud.ts (1)

3-4: Consider using TypeScript path aliases
Deeply nested relative imports can be fragile. As an optional refactor, you could define a path mapping in tsconfig.json (e.g., @metriport/core/util/config) to simplify these imports and improve maintainability.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-factory.ts (1)

1-1: Optional: adopt path aliases for Config
To avoid deep relative imports, consider defining a TypeScript path alias (e.g., @metriport/core/util/config) in tsconfig.json.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-factory.ts (1)

1-1: Optional: simplify with TS path mappings
Consider using a project-level alias in tsconfig.json for util/config to reduce the nesting in relative imports.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts (1)

3-4: Import path update – correct adjustment.
The relative import paths for Config and SQSClient have been updated to reflect the new directory structure and they resolve correctly. Consider introducing path aliases (via tsconfig.json) to avoid deep relative paths in the future.

packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-factory.ts (1)

1-1: Import path update – valid change.
The Config import path has been shortened appropriately to match the reorganized directory structure. Consider using a path alias for even clearer module resolution.

packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-cloud.ts (1)

2-3: Import path corrections – approved.
Paths for Config, out, and Lambda utilities have been shortened one level and align with the new folder layout. As an enhancement, consider adopting path aliases to reduce relative traversal.

Also applies to: 8-8

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

26-28: Consider returning early for empty results case

The function could benefit from handling the case where no appointments are found, perhaps by logging this information or returning early.

  const appointments = await client.getAppointments({ cxId, fromDate, toDate });
+ if (!appointments.length) {
+   return [];
+ }
  return appointments;
packages/core/src/external/ehr/healthie/command/get-appointments.ts (1)

25-30: API parameters naming differs from function parameters

The function receives fromDate and toDate but uses startAppointmentDate and endAppointmentDate when calling the client. While functional, this mapping could be made more explicit for maintainability.

  const appointments = await client.getAppointments({
    cxId,
-   startAppointmentDate: fromDate,
-   endAppointmentDate: toDate,
+   startAppointmentDate: fromDate, // Maps from fromDate parameter
+   endAppointmentDate: toDate, // Maps from toDate parameter
  });
packages/core/src/external/ehr/athenahealth/command/get-appointments.ts (1)

6-33: Function implementation looks good with proper error handling.

The implementation correctly validates required parameters with detailed error context, creates the client using the shared utility, and properly handles optional parameters.

One observation: The function renames parameters from fromDate/toDate to startAppointmentDate/endAppointmentDate when calling the client. While this works, it creates a slight mapping complexity that future developers will need to understand.

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

7-31: Add return type annotation to the function

The function implementation is good, but would benefit from an explicit return type annotation for better type safety and documentation.

export async function createAthenaHealthClient({
  environment,
  cxId,
  practiceId,
  tokenId,
}: {
  environment: string;
  cxId: string;
  practiceId: string;
  tokenId?: string;
-}) {
+}): Promise<ReturnType<typeof AthenaHealthApi.create>> {

Also, consider adding error handling around the client creation to provide more context if AthenaHealthApi.create fails.

packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1)

18-18: Address the TODO comment

The TODO comment suggests moving to capture.wrapHandler() which is a more centralized approach for error handling. This should be implemented before finalizing this PR to maintain consistent error handling across all Lambda functions.

-// TODO move to capture.wrapHandler()
-export const handler = Sentry.AWSLambda.wrapHandler(async (event: SQSEvent) => {
+export const handler = capture.wrapHandler(async (event: SQSEvent) => {
packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (2)

5-7: Function signature is correct, but consider adding JSDoc

The function signature looks good but adding JSDoc with param descriptions would improve code maintainability and developer experience.

+/**
+ * Fetches a FHIR Bundle filtered by resource type from Canvas EHR
+ * 
+ * @param params - The request parameters including environment, patient IDs, and resource type
+ * @returns A Promise resolving to a FHIR Bundle containing the requested resources
+ */
export async function getBundleByResourceType(
  params: GetBundleByResourceTypeClientRequest
): Promise<Bundle> {

18-23: Use object spread correctly for optional properties

The code uses a conditional spread for tokenId which is a good pattern, but there's a more concise way to handle this with the nullish coalescing operator.

  const client = await createCanvasClient({
    environment,
    cxId,
    practiceId,
-    ...(tokenId && { tokenId }),
+    tokenId,
  });

Note: This assumes createCanvasClient handles undefined values properly. If it does not, the current approach is acceptable.

packages/infra/lib/ehr-nested-stack.ts (1)

42-50: Duplicate hard-coded memory/timeout values – extract to a shared constant

The new get-bundle-by-resource-type Lambda repeats the same 4096 MB / 12 min configuration used elsewhere. Duplicating these literals makes fleet-wide tuning error-prone.

Create a small helper (or extend settings()) that centralises typical “heavy EHR Lambda” settings and reuse it here and in getAppointments, computeResourceDiffBundles, etc.
This keeps infra DRY and guarantees consistent scaling behaviour.

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

180-181: Avoid instantiating an unused API client just to obtain the environment

createHealthieClientWithEnvironment returns both a client and the env, but only environment is consumed. Constructing the client may trigger auth/token fetch logic and unnecessary network I/O for every practice.

If all you need is the environment payload, expose a lighter helper (e.g. getHealthieEnvironment) or modify the factory to allow { createClient: false }.

-const { environment } = await createHealthieClientWithEnvironment({ cxId, practiceId });
+const { environment } = await getHealthieEnvironment({ cxId, practiceId }); // pseudo-code

This will shave latency and memory for large process-patients runs.

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

32-41: Freeze and type-check the handler map to prevent silent runtime gaps

Because ehrGetBundleByResourceTypeMap is declared as Record<string, …> the compiler cannot warn when a new EhrSource is later added without adding the corresponding handler. Marking the map as const (or using satisfies) keeps the object immutable and enforces exhaustiveness.

-export const ehrGetBundleByResourceTypeMap: GetBundleByResourceTypeMethodsMap = {
+export const ehrGetBundleByResourceTypeMap = {
   [EhrSources.canvas]: {
     [GetBundleByResourceTypeMethods.canvasGetBundleByResourceType]:
       getBundleByResourceTypeCanvas,
   },
-};
+} as const satisfies GetBundleByResourceTypeMethodsMap;

This prevents accidental mutation and gives you a compiler error the moment a new source or method is introduced without a handler.

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

50-65: Make the handler map exhaustive at compile-time

Using Record<string, …> tolerates typos and lets new sources slip in without handlers. Tie the keys to EhrSource to surface gaps during compilation:

-export type AppointmentMethodsMap = Record<
-  string,
-  Record<string, (params: GetAppointmentsClientRequest) => Promise<Appointment[]>>
->;
+export type AppointmentMethodsMap = {
+  [S in EhrSource]?: {
+    [M in AppointmentMethods]?: (
+      params: GetAppointmentsClientRequest,
+    ) => Promise<Appointment[]>;
+  };
+};

(or cast the constant with satisfies AppointmentMethodsMap as shown in the bundle file).
This enforces completeness and prevents silent runtime undefined.

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

7-31: Consider adding explicit return type annotation for improved type safety

The function correctly creates an Elation Health client, but would benefit from an explicit return type annotation to enhance type safety and documentation. This is especially important for API-facing functions.

-export async function createElationHealthClient({
+export async function createElationHealthClient({
   environment,
   cxId,
   practiceId,
   tokenId,
 }: {
   environment: string;
   cxId: string;
   practiceId: string;
   tokenId?: string;
-}) {
+}): Promise<ReturnType<typeof ElationHealthApi.create>> {

24-24: Consider using spread operator for conditional assignment

For conditional inclusion of the tokenId parameter, consider using the spread operator pattern that appears in other files in this PR for consistency.

-  const twoLeggedAuthTokenInfo = tokenId ? await getTokenInfo(tokenId) : undefined;
+  const twoLeggedAuthTokenInfo = tokenId 
+    ? await getTokenInfo(tokenId) 
+    : undefined;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c813a26 and 588a6aa.

📒 Files selected for processing (42)
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts (5 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts (2 hunks)
  • packages/api/src/external/ehr/canvas/command/process-patients-from-appointments.ts (1 hunks)
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (4 hunks)
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts (5 hunks)
  • packages/api/src/external/ehr/healthie/shared.ts (1 hunks)
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts (1 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/shared.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/elation/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/elation/shared.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/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-factory.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-factory.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/appointment/get-appointments/ehr-get-appointments-local.ts (0 hunks)
  • packages/core/src/external/ehr/lambdas/appointment/get-appointments/ehr-get-appointments.ts (0 hunks)
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-factory.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-local.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-factory.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-local.ts (1 hunks)
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/util/config.ts (1 hunks)
  • packages/infra/lib/api-stack.ts (3 hunks)
  • packages/infra/lib/api-stack/api-service.ts (4 hunks)
  • packages/infra/lib/ehr-nested-stack.ts (6 hunks)
  • packages/lambdas/src/ehr-compute-resource-diff-bundles.ts (1 hunks)
  • packages/lambdas/src/ehr-get-appointments.ts (1 hunks)
  • packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1 hunks)
  • packages/lambdas/src/ehr-refresh-ehr-bundles.ts (1 hunks)
  • packages/lambdas/src/shared/ehr.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/core/src/external/ehr/lambdas/appointment/get-appointments/ehr-get-appointments-local.ts
  • packages/core/src/external/ehr/lambdas/appointment/get-appointments/ehr-get-appointments.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/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-cloud.ts
  • packages/lambdas/src/ehr-get-appointments.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-factory.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-factory.ts
  • packages/core/src/external/ehr/canvas/command/get-appointments.ts
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-factory.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts
  • packages/core/src/util/config.ts
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-cloud.ts
  • packages/lambdas/src/ehr-compute-resource-diff-bundles.ts
  • packages/lambdas/src/ehr-refresh-ehr-bundles.ts
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-factory.ts
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts
  • packages/core/src/external/ehr/healthie/index.ts
  • packages/core/src/external/ehr/healthie/command/get-appointments.ts
  • packages/api/src/external/ehr/canvas/command/process-patients-from-appointments.ts
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-local.ts
  • packages/infra/lib/api-stack.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments.ts
  • packages/api/src/external/ehr/healthie/shared.ts
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-local.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts
  • packages/lambdas/src/ehr-get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/elation/shared.ts
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/healthie/shared.ts
  • packages/infra/lib/api-stack/api-service.ts
  • packages/core/src/external/ehr/athenahealth/shared.ts
  • packages/core/src/external/ehr/elation/command/get-appointments.ts
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts
  • packages/lambdas/src/shared/ehr.ts
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts
  • packages/infra/lib/ehr-nested-stack.ts
  • packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments.ts
🧠 Learnings (4)
packages/lambdas/src/ehr-refresh-ehr-bundles.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3788
File: packages/api/src/external/ehr/shared/utils/bundle.ts:83-93
Timestamp: 2025-05-08T19:41:36.533Z
Learning: In the Metriport codebase, the team prefers to let errors bubble up naturally in some cases rather than adding explicit error handling at every layer, as demonstrated in the refreshEhrBundle function in the bundle.ts file.
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/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.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/lambdas/get-bundle-by-resource-type/ehr-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.
🧬 Code Graph Analysis (10)
packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-local.ts (3)
packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts (3)
  • EhrGetBundleByResourceTypeHandler (19-21)
  • GetBundleByResourceTypeRequest (6-17)
  • getEhrGetBundleByResourceTypeHandler (43-55)
packages/api/src/routes/medical/schemas/fhir.ts (1)
  • Bundle (23-23)
packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1)
  • handler (19-39)
packages/api/src/external/ehr/healthie/shared.ts (2)
packages/api/src/external/ehr/shared/utils/client.ts (1)
  • EhrPerPracticeParams (30-30)
packages/core/src/external/ehr/healthie/index.ts (1)
  • HealthieEnv (47-47)
packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-local.ts (2)
packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments.ts (4)
  • EhrGetAppointmentsHandler (25-27)
  • Appointment (39-43)
  • GetAppointmentsRequest (13-23)
  • getEhrGetAppointmentsHandler (67-76)
packages/lambdas/src/ehr-get-appointments.ts (1)
  • handler (19-39)
packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts (2)
packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments.ts (1)
  • GetAppointmentsClientRequest (29-29)
packages/core/src/external/ehr/athenahealth/shared.ts (1)
  • createAthenaHealthClient (7-31)
packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (5)
packages/lambdas/src/shared/capture.ts (1)
  • capture (18-103)
packages/lambdas/src/shared/sqs.ts (1)
  • getSingleMessageOrFail (62-83)
packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-local.ts (1)
  • EhrGetBundleByResourceTypeLocal (8-17)
packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts (1)
  • GetBundleByResourceTypeRequest (6-17)
packages/lambdas/src/shared/ehr.ts (1)
  • ehrGetBundleByResourceTypeSchema (104-115)
packages/core/src/external/ehr/elation/shared.ts (4)
packages/core/src/external/ehr/elation/index.ts (1)
  • isElationEnv (57-59)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/core/src/external/ehr/api/get-token-info.ts (1)
  • getTokenInfo (17-39)
packages/core/src/external/ehr/shared.ts (1)
  • getOauthSecrets (53-72)
packages/core/src/external/ehr/healthie/shared.ts (3)
packages/core/src/external/ehr/healthie/index.ts (1)
  • isHealthieEnv (48-50)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/core/src/external/ehr/shared.ts (2)
  • GetSecretsApiKeyResult (48-48)
  • getSecretsApiKeySchema (45-47)
packages/core/src/external/ehr/athenahealth/shared.ts (4)
packages/core/src/external/ehr/athenahealth/index.ts (1)
  • isAthenaEnv (96-98)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/core/src/external/ehr/api/get-token-info.ts (1)
  • getTokenInfo (17-39)
packages/core/src/external/ehr/shared.ts (1)
  • getOauthSecrets (53-72)
packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts (3)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (8-8)
packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1)
  • handler (19-39)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments.ts (4)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (8-8)
packages/shared/src/interface/external/ehr/healthie/appointment.ts (1)
  • AppointmentAttendee (10-10)
packages/lambdas/src/ehr-get-appointments.ts (1)
  • handler (19-39)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
🔇 Additional comments (48)
packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-cloud.ts (1)

3-4: Import path adjustment is correct
The relative imports for Config and SQSClient have been shortened to match the updated directory layout and are consistent with the other steps in this job.

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

4-4: Import path updated to centralized handler
This change correctly points to the new lambdas/get-appointments module for GetAppointmentsClientRequest. The logic remains unchanged and aligns with the consolidated handler-based approach.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-factory.ts (1)

1-1: Config import path shortened
The relative path to Config has been shortened by one directory level to reflect the reorganized folder structure.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-factory.ts (1)

1-1: Config import path update
The import path has been shortened to mirror the new directory hierarchy; no logic changes were made.

packages/lambdas/src/ehr-get-appointments.ts (1)

1-2: Handler import path corrections
The imports now correctly reference the consolidated lambdas/get-appointments handlers via the @metriport/core package. Confirm these paths resolve properly in the TypeScript build and in downstream consumers.

packages/lambdas/src/ehr-refresh-ehr-bundles.ts (1)

1-2: Import path adjustment – looks good.
The absolute imports for RefreshEhrBundlesRequest and EhrRefreshEhrBundlesLocal now point to the updated jobs/create-resource-diff-bundles/steps/refresh directory. No issues spotted.

packages/lambdas/src/ehr-compute-resource-diff-bundles.ts (1)

1-2: Adjusted import paths – approved.
The imports for ComputeResourceDiffBundlesRequest and EhrComputeResourceDiffBundlesLocal correctly reflect the new jobs/.../compute directory structure.

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

2-2: Import path updated correctly.

The import path for buildEhrRefreshEhrBundlesHandler has been updated to reflect the new directory structure where the bundle job was moved to the new jobs folder, aligning with the PR objectives. This change maintains consistency across the codebase.

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

1-2: Import paths updated correctly for handler-based approach.

The updated import paths appropriately reference the new location of appointment-related functionality in the lambdas directory, which is consistent with the architectural changes described in the PR objectives for retrieving appointments from various EHR systems.


108-118: Implementation follows handler pattern correctly.

The code properly uses the new handler-based approach for retrieving appointments, which explicitly passes environment and tokenId parameters. This aligns with the architectural changes implemented across other EHR integrations (Athena, Elation, Healthie).

packages/core/src/util/config.ts (1)

218-220: New config method added for bundle retrieval Lambda.

The new getEhrGetBundleByResourceTypeLambdaName method correctly follows the established pattern for retrieving environment variables. This addition supports the new Lambda-based EHR bundle retrieval functionality mentioned in the PR objectives.

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

3-6: Import paths updated to reflect new directory structure.

The import paths have been correctly updated to follow the new directory structure, where bundle-related functionality has been moved to the jobs folder. This maintains consistency with the overall codebase restructuring described in the PR objectives.

packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (1)

4-11: Import paths updated in line with directory restructuring.

The relative import paths have been updated to reduce directory depth, presumably reflecting a reorganization of the project structure. The changes maintain the same imported modules while adjusting their locations.

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

40-40: Improved TypeScript type definition for optional property.

Changed apiKey from explicit string | undefined to the cleaner TypeScript optional property syntax apiKey?: string. This change maintains the same behavior while using more idiomatic TypeScript syntax.

packages/infra/lib/api-stack.ts (3)

425-425: Added new EHR Lambda for retrieving bundles by resource type.

The new Lambda function for getting bundles by resource type is correctly destructured from the EHR nested stack.


561-561: Lambda properly passed to API service creation.

The new Lambda is correctly passed to the API service creation function, following the existing pattern for Lambda integration.


667-667: Lambda included in API_URL environment variable recipients.

The Lambda is appropriately added to the list of Lambda functions that receive the API_URL environment variable, ensuring it can make requests back to the API when needed.

packages/infra/lib/api-stack/api-service.ts (4)

116-116: Added new Lambda parameter to createAPIService function.

The new EHR Lambda for retrieving bundles by resource type is correctly included in the function parameters.


163-163: Type declaration added for new Lambda parameter.

The type interface for createAPIService input parameters is properly updated to include the new Lambda.


308-309: Lambda function name added to container environment variables.

The Lambda function name is correctly injected into the Fargate service environment variables, following the existing naming convention with the EHR_GET_BUNDLE_BY_RESOURCE_TYPE_LAMBDA_NAME key.


455-455: Invoke permissions granted to Fargate task role.

The appropriate Lambda invoke permissions are granted to the Fargate task role, allowing the API service to call the new Lambda function.

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

1-12: Clean implementation of the factory pattern

This implementation nicely abstracts the environment-specific handling logic, returning the appropriate handler based on runtime configuration. The function is small, focused, and follows the functional programming style recommended in the guidelines.

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

10-19: Clear and comprehensive error handling

Good validation of required parameters with a detailed error message. The error structure follows the guidelines by using a static message with dynamic data in additionalInfo.


20-25: Proper client creation with optional parameters

The client creation logic is clean and handles the optional tokenId parameter correctly using object spread syntax. This approach is elegant and aligns with the coding guidelines.

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

10-19: Good error validation pattern

The input validation follows the same pattern as other EHR implementations, providing consistency across the codebase. The error message is clear and includes appropriate context.


20-24: Clean client initialization

Good use of object destructuring and proper environment handling in the client creation process.

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

2-3: Import path correction

The updated import path for AppointmentMethods and the addition of GetBundleByResourceTypeMethods ensure proper module resolution.


104-115: Well-structured schema definition

The new schema ehrGetBundleByResourceTypeSchema is comprehensive and follows the established pattern in the file. It properly validates all required fields and uses appropriate Zod types.

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

1-17: Clean implementation of the handler interface!

This class correctly implements the EhrGetBundleByResourceTypeHandler interface, following the handler pattern that's being established across the EHR integrations. The pattern of extracting the EHR type and method, then delegating to the appropriate handler is clean and maintainable.

packages/core/src/external/ehr/lambdas/get-appointments/ehr-get-appointments-local.ts (1)

1-17: Clean implementation with a minor type safety concern

This class correctly implements the EhrGetAppointmentsHandler interface, following the handler pattern being established across the EHR integrations.

There's a potential type safety issue with casting the result to T[]. If the handler returns a type incompatible with T[], TypeScript won't catch this error at compile time. Consider if there's a way to make this safer or document the assumption being made.

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

103-113: Good refactoring to return both client and environment.

The function createHealthieClientWithEnvironment has been properly updated to return both the client and environment in a consistent object structure. This change aligns with the broader architectural pattern established across other EHR integrations.


115-120: Well-implemented backward compatibility function.

The new createHealthieClient function maintains backward compatibility for existing code that only needs the client. This is an excellent approach that allows for incremental adoption of the new pattern while ensuring existing code continues to work.

packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1)

19-25:

✅ Verification successful

Verify early return behavior

When no message is found, the function returns undefined. Ensure this behavior is expected and properly handled by the caller. Consider returning an explicit response object with a status code to maintain consistent response formats.


🏁 Script executed:

#!/bin/bash
# Check how other Lambda functions handle early returns
rg -A 2 -B 2 "return undefined|return;|!message \S* return" --type=ts packages/lambdas/src/

Length of output: 12654


Consistent early-return pattern across Lambda handlers

I’ve verified that this Lambda follows the same if (!message) return; pattern as all other SQS-driven handlers in packages/lambdas/src (e.g. elation-link-patient.ts, healthie-link-patient.ts, ehr-sync-patient.ts, etc.). These functions are wrapped with Sentry.AWSLambda.wrapHandler, so returning undefined simply ends execution without error, which is the intended behavior. No explicit HTTP-like response object is expected by the caller.

Action: No changes required here.

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

24-31: LGTM - Client usage is consistent with other implementations

The function correctly calls the Canvas client's getBundleByResourceType method with all the required parameters, and returns the bundle as is.

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

7-33: Function implementation follows best practices

The function correctly validates the environment before proceeding, throws appropriate errors with context, and creates the client with async secret retrieval. It adheres to the coding guidelines with proper typing, error handling, and functional style.

However, consider adding JSDoc comments to improve code documentation:

+/**
+ * Creates a Healthie API client for the specified environment and practice
+ * 
+ * @param environment - The Healthie environment to connect to
+ * @param cxId - The customer ID for secret retrieval
+ * @param practiceId - The Healthie practice ID
+ * @returns A Promise resolving to a configured Healthie API client
+ * @throws {BadRequestError} When the environment is invalid
+ */
export async function createHealthieClient({
  environment,
  cxId,
  practiceId,
}: {
  environment: string;
  cxId: string;
  practiceId: string;
}) {
packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (5)

2-3: Good use of specific imports

The imports follow best practices by importing specific modules rather than entire packages, which helps with tree-shaking and bundle size optimization.


13-13: Appropriate type import

The BookedAppointment import is correctly added to support type checking for the refactored appointment handling logic.


27-27: Updated import for client creation

The import correctly switches from the previously used client creation function to the new one that also returns environment information.


149-152: Destructuring used appropriately

The code correctly destructures both tokenId and environment from the client creation function, which aligns with the new handler-based pattern.


158-168: Well-structured handler usage

The code now uses the handler-based approach correctly, passing all necessary parameters including the EHR source, environment, token ID, and method enum.

One small suggestion - consider using explicit type parameters for better clarity:

-const handler = buildEhrGetAppointmentsHandler();
+const handler = buildEhrGetAppointmentsHandler<BookedAppointment>();
packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts (2)

154-157: Validate tokenId and environment before proceeding

createAthenaClientWithTokenIdAndEnvironment() can theoretically return undefined values (e.g. if token refresh fails).
Passing them unchecked into the handler would surface only deep inside the call-chain and be hard to trace.

-const { tokenId, environment, client } =
-  await createAthenaClientWithTokenIdAndEnvironment({ cxId, practiceId });
+const {
+  tokenId,
+  environment,
+  client,
+} = await createAthenaClientWithTokenIdAndEnvironment({ cxId, practiceId });
+
+if (!tokenId || !environment) {
+  log("Missing tokenId or environment – skipping appointment fetch");
+  return { error: new Error("Missing auth context") };
+}

A cheap guard here avoids multiple nested catches further down the stack.


214-224: Confirm startRange / endRange are Date instances, not dayjs objects

GetAppointmentsRequest expects plain Date types, yet getLookForwardTimeRange() returns dayjs instances in several older helpers. Passing the wrong type compiles (due to structural typing) but will break JSON serialisation in the Lambda call.

const { startRange, endRange } = getLookForwardTimeRange({ lookForward: appointmentsLookForward });
log(`Getting appointments from ${startRange} to ${endRange}`);
return await handler.getAppointments<BookedAppointment>({
  ...handlerParams,
  method: AppointmentMethods.athenaGetAppointments,
  fromDate: startRange.toDate?.() ?? startRange,
  toDate: endRange.toDate?.() ?? endRange,
});

Please verify the helper returns Date or convert explicitly.

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

7-31: Well-structured client creation with proper error handling

The implementation follows good practices by:

  1. Validating the environment before proceeding
  2. Including contextual information in error messages
  3. Properly handling optional token retrieval
  4. Using async/await consistently
  5. Following the established factory pattern for client creation
packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts (2)

14-30: Good refactoring to handler-based architecture

The refactoring from direct client usage to a handler-based approach improves consistency across EHR integrations and enables serverless execution via Lambda. This architectural change:

  1. Makes environment and token handling more explicit
  2. Standardizes the approach across different EHR providers
  3. Improves separation of concerns

27-27: Improved parameter naming for better generalization

Renaming canvasPatientId to ehrPatientId makes the code more generic and reusable across different EHR providers, which aligns with the broader refactoring goals of this PR.

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

5-22: Consider adding explicit return type annotation

The function would benefit from an explicit return type annotation for improved clarity and type safety:

export async function getAppointmentsFromSubscriptionEvents(
  params: GetAppointmentsClientRequest
-): Promise<BookedAppointment[]> {
+): Promise<BookedAppointment[]> {

Though the return type is already correctly specified, I'm highlighting this as good practice that should be maintained.


13-20: Clean conditional property inclusion pattern

The use of spread operators with logical AND for conditional property inclusion (e.g., ...(tokenId && { tokenId })) is a clean pattern that:

  1. Keeps the code concise
  2. Makes it clear which properties are optional
  3. Avoids undefined values in the resulting object

This pattern is used consistently for all optional parameters, which is excellent.


1-22: Consistent approach to client creation and appointment fetching

This implementation follows the same architectural pattern established across other EHR providers:

  1. Takes standardized parameters
  2. Creates a client using the appropriate factory function
  3. Calls the specific client method
  4. Returns typed results

This consistency makes the codebase more maintainable and easier to understand.

Thomas Yopes added 2 commits May 28, 2025 12:03
…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>
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: 3

🧹 Nitpick comments (1)
packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-direct.ts (1)

31-35: Improve type safety of the validation function.

The type guard implementation could be more robust and follows functional programming principles well.

 function isGetBundleByResourceTypeMethod(method: string): method is GetBundleByResourceTypeMethods {
-  return Object.values(GetBundleByResourceTypeMethods).includes(
-    method as GetBundleByResourceTypeMethods
-  );
+  return (Object.values(GetBundleByResourceTypeMethods) as string[]).includes(method);
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 588a6aa and 0d77525.

📒 Files selected for processing (26)
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts (5 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts (2 hunks)
  • packages/api/src/external/ehr/canvas/command/process-patients-from-appointments.ts (1 hunks)
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts (4 hunks)
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts (5 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/canvas/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-factory.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-direct.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-factory.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/elation/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/healthie/command/get-appointments.ts (1 hunks)
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (1 hunks)
  • packages/core/src/util/config.ts (1 hunks)
  • packages/infra/lib/api-stack.ts (3 hunks)
  • packages/infra/lib/api-stack/api-service.ts (4 hunks)
  • packages/infra/lib/ehr-nested-stack.ts (6 hunks)
  • packages/lambdas/src/ehr-get-bundle-by-resource-type.ts (1 hunks)
  • packages/lambdas/src/shared/ehr.ts (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • packages/api/src/external/ehr/canvas/command/process-patients-from-appointments.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-cloud.ts
  • packages/core/src/external/ehr/jobs/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-factory.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-factory.ts
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • packages/core/src/external/ehr/canvas/command/get-appointments.ts
  • packages/lambdas/src/shared/ehr.ts
  • packages/core/src/util/config.ts
  • packages/infra/lib/api-stack.ts
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundle.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments-from-subscription-events.ts
  • packages/core/src/external/ehr/athenahealth/command/get-appointments.ts
  • packages/infra/lib/api-stack/api-service.ts
  • packages/infra/lib/ehr-nested-stack.ts
  • packages/api/src/external/ehr/healthie/command/process-patients-from-appointments.ts
  • packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts
  • packages/lambdas/src/ehr-get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/elation/command/get-appointments.ts
  • packages/core/src/external/ehr/healthie/command/get-appointments.ts
  • packages/api/src/external/ehr/elation/command/process-patients-from-appointments.ts
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.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/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts
  • packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-direct.ts
🧠 Learnings (1)
packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-direct.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.
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (16)
packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts (7)

1-13: LGTM: Clean import organization

The imports are well-organized with proper aliasing to avoid naming conflicts and meaningful import paths.


15-27: LGTM: Well-implemented handler class with proper error handling

The class follows the coding guidelines correctly:

  • PascalCase naming
  • Proper async/await usage
  • Destructuring parameters
  • Static error messages with dynamic data in additionalInfo
  • Type-safe implementation with generic constraint

The type casting on line 25 is acceptable here since the handler is validated and type-safe through the mapping system.


29-35: LGTM: Well-defined enum following naming conventions

The enum follows the coding guidelines with:

  • PascalCase enum name
  • camelCase enum values
  • Descriptive naming that clearly indicates the EHR source and action

37-39: LGTM: Proper type predicate implementation

The type guard function correctly uses TypeScript's type predicate feature to narrow down the type, following the guideline about type predicates being used for type narrowing.


41-44: LGTM: Clean type definition

The type definition uses proper TypeScript Record type and follows PascalCase naming convention for types.


46-61: LGTM: Well-structured mapping object

The mapping object follows the coding guidelines:

  • camelCase constant naming
  • Consistent structure across EHR sources
  • Clear separation of concerns for different appointment methods

63-72: LGTM: Robust handler retrieval with proper error handling

The function demonstrates good practices:

  • camelCase naming
  • Optional chaining for safe property access
  • Static error message with dynamic data in additionalInfo
  • Clear return type specification
  • Defensive programming with null/undefined checks
packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts (2)

16-25: LGTM: Well-designed constructor with proper dependency injection.

The constructor follows good practices with optional dependency injection for the Lambda client and proper fallback to default AWS region configuration.


16-25: LGTM: Well-structured constructor with proper dependency injection.

The constructor follows good dependency injection patterns with optional parameters and sensible defaults. The use of nullish coalescing operator (??) aligns with the coding guidelines preference over the OR operator.

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

11-25: LGTM: Clean implementation with proper validation and error handling.

The method correctly validates the method parameter and delegates to the appropriate handler with proper error context.


27-29: Consider future extensibility of the enum.

Currently only supports Canvas, but the pattern is well-designed for adding other EHR sources. The naming convention follows the coding guidelines.


37-46: Well-structured handler map with clear type definitions.

The map structure properly separates concerns by EHR source and method, making it easy to extend for additional sources and methods.


11-25: LGTM: Excellent implementation following functional programming principles.

The class method properly uses destructuring, validates input parameters, and maintains separation of concerns. The error handling follows the coding guidelines with proper use of BadRequestError and additionalInfo for dynamic data.


27-35: LGTM: Proper enum and type guard implementation.

The enum follows PascalCase naming convention and the type guard function is implemented correctly using Object.values() for runtime validation.


37-46: LGTM: Well-designed handler map with proper typing.

The type definition and handler map follow good patterns for extensibility. The nested Record structure provides type safety while allowing for easy addition of new EHR sources and methods.


48-60: LGTM: Robust handler retrieval with proper error handling.

The function uses optional chaining appropriately and provides clear error messages with relevant context in additionalInfo. This follows the coding guidelines for error handling.

Thomas Yopes added 7 commits May 28, 2025 12:13
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>
F438
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>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to lambda directly

};

const bundleFunctionsByEhr: Record<EhrSources, BundleFunctions | undefined> = {
const bundleFunctionsByEhr: Record<EhrSource, BundleFunctions | undefined> = {
[EhrSources.canvas]: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is being fully deleted in the next PR so no need to flatten it here #3883

…riport/metriport into 199-elation-contribution-1.5.1

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Thomas Yopes added 2 commits June 4, 2025 04:36
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>
@thomasyopes thomasyopes requested a review from leite08 June 4, 2025 12:37
Thomas Yopes added 7 commits June 4, 2025 08:03
…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>
…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
2684


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>
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: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 53fbb23 and f6588b7.

📒 Files selected for processing (4)
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts (3 hunks)
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (1 hunks)
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/external/ehr/canvas/command/get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/refresh/ehr-refresh-ehr-bundles-local.ts
  • packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.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/command/get-bundle-by-resource-type.ts
🧠 Learnings (1)
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.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (4)
packages/core/src/external/ehr/command/get-bundle-by-resource-type.ts (4)

1-4: LGTM - Clean imports following coding guidelines.

The imports are well-organized and follow the established patterns. The aliasing of the Canvas import to getBundleByResourceTypeCanvas improves clarity.


6-17: Well-structured type definitions.

The type definitions follow TypeScript best practices with meaningful names and proper use of the Omit utility type for the client request variant.


19-25: Clean implementation following functional programming principles.

The function properly uses destructuring, async/await, and the handler pattern. The implementation is concise and follows the coding guidelines for functional programming style.


39-47: Excellent error handling following coding guidelines.

The error handling implementation correctly follows the guidelines:

  • Uses a static error message
  • Passes dynamic data (ehr) to additionalInfo
  • Proper type safety with the handler lookup and validation

Thomas Yopes added 3 commits June 5, 2025 05:59
…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>
Base automatically changed from 199-elation-contribution-1.5 to develop June 5, 2025 15:59
Copy link
Member
@leite08 leite08 left a comment

Choose a reason for hiding this comment

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

Minor things, approving preemptively assuming we'll address them

import AthenaHealthApi, { AthenaEnv } from "./athenahealth";
import CanvasApi, { CanvasEnv } from "./canvas";
import { EClinicalWorksEnv } from "./eclinicalworks";
import ElationApi, { ElationEnv } from "./elation";
import { HealthieEnv } from "./healthie";

export type EhrEnv = AthenaEnv | ElationEnv | CanvasEnv | HealthieEnv | EClinicalWorksEnv;

const EhrSourcesWithClientCredentials = [
Copy link
Member

Choose a reason for hiding this comment

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

consts should start w/ lowecase

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

import { EhrSources } from "@metriport/shared/interface/external/ehr/source";
import { z } from "zod";

const EhrSourcesWithDynamicSecrets = [
Copy link
Member

Choose a reason for hiding this comment

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

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -31,7 +32,9 @@ const MAX_AGE = dayjs.duration(24, "hours");
const region = Config.getAWSRegion();
const responsesBucket = Config.getEhrResponsesBucketName();

export const paginateWaitTime = dayjs.duration(5, "seconds").asMilliseconds();
export const paginateWaitTime = dayjs.duration(1, "seconds").asMilliseconds();
Copy link
Member

Choose a reason for hiding this comment

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

I know this was approved on the previous PR, but if you send a new commit can you change this so we don't convert to millis when we create the const, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Thomas Yopes added 2 commits June 6, 2025 09:46
… 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>
@thomasyopes thomasyopes added this pull request to the merge queue Jun 6, 2025
Merged via the queue into develop with commit e7d1f1b Jun 6, 2025
18 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jun 25, 2025
6 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
0