8000 198 ecw v2 by thomasyopes · Pull Request #3874 · metriport/metriport · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

198 ecw v2 #3874

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 23 commits into from
May 23, 2025
Merged

198 ecw v2 #3874

merged 23 commits into from
May 23, 2025

Conversation

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

Issues:

Dependencies

Description

  • implements an initial eCW client
  • implements JWT routing
  • implements sync patient command
  • implements EHR dash routing

Testing

  • Local
    • run patient syncing flow e2e from eCW sandbox
  • Staging
    • run patient syncing flow e2e from eCW sandbox
  • Sandbox
    • N/A
  • Production
    • run patient syncing flow e2e from eCW sandbox

Release Plan

  • Upstream dependencies are met/released
  • Merge this

Summary by CodeRabbit

  • New Features

    • Added integration for the eClinicalWorks EHR system, enabling synchronization of patient data into the platform.
    • Introduced new API endpoints for patient synchronization and JWT token management specific to eClinicalWorks.
    • Added support for validating and handling eClinicalWorks-specific JWT tokens.
    • Provided new middleware and route handlers for eClinicalWorks authentication and patient/document access.
    • Implemented a dedicated API client for eClinicalWorks with environment-specific configuration and patient data retrieval.
    • Added utility functions to create and validate eClinicalWorks API clients per practice.
    • Added functions to retrieve JWT tokens by ID without expiration checks.
  • Bug Fixes

    • Corrected an error message to accurately reference the "departmentId" field during patient synchronization.
  • Other Improvements

    • Updated shared types and configuration to support the new eClinicalWorks EHR source.
    • Extended existing utilities and schemas to recognize and validate eClinicalWorks-related data.
    • Enhanced route processing to include token ID in authorization workflows for eClinicalWorks.

husainsyed and others added 11 commits May 16, 2025 12:55
refs. metriport/metriport-internal#198

Signed-off-by: Syed Husain <syed@metriport.com>
Ref: ENG-198

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

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

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>
refs. metriport/metriport-internal#198

Signed-off-by: Syed Husain <syed@metriport.com>
Ref: ENG-198

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…triport into 198-ecw-patientflow

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

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

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Copy link
coderabbitai bot commented May 20, 2025

Warning

Rate limit exceeded

@thomasyopes has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ddeeb2 and 4684373.

📒 Files selected for processing (2)
  • packages/infra/config/env-config.ts (1 hunks)
  • packages/infra/lib/api-stack/api-service.ts (1 hunks)

Walkthrough

The changes introduce comprehensive support for the "eclinicalworks" EHR integration throughout the codebase. This includes new API client modules, JWT token schema and handling, synchronization workflows, Express routes and middleware, configuration methods, and updates to shared interfaces and enums. The integration enables secure patient data synchronization and access via new endpoints and middleware.

Changes

File(s) / Path(s) Change Summary
packages/api/src/command/jwt-token.ts Added functions to retrieve JWT tokens by ID without expiration checks.
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts Introduced patient synchronization logic for EClinicalWorks, including mapping and API client instantiation.
packages/api/src/external/ehr/eclinicalworks/shared.ts Added helper to create an EClinicalWorks API client per practice using environment configuration.
packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts Added middleware to parse and validate JWT tokens for EClinicalWorks, handling patient and document routes.
packages/api/src/routes/ehr/eclinicalworks/patient.ts Introduced router with GET/POST endpoints for synchronizing EClinicalWorks patients into Metriport.
packages/api/src/routes/ehr/eclinicalworks/routes/dash.ts Added dashboard router for EClinicalWorks, wiring up patient, document, settings, and auth middleware.
packages/api/src/routes/ehr/index.ts Registered EClinicalWorks routes and middleware in the main EHR router.
packages/api/src/routes/ehr/shared.ts Updated processCxId to accept a parsing callback with an additional token ID argument.
packages/api/src/routes/internal/jwt-token/eclinicalworks.ts Added internal router for checking and saving JWT tokens for EClinicalWorks.
packages/api/src/shared/config.ts Added method to retrieve EClinicalWorks environment from config.
packages/core/src/external/ehr/eclinicalworks/index.ts Introduced EClinicalWorks API client with environment support and patient retrieval.
packages/lambdas/src/shared/ehr.ts Corrected error message for invalid departmentId in patient sync parsing.
packages/shared/src/interface/external/ehr/eclinicalworks/index.ts Re-exported all JWT token exports for EClinicalWorks.
packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts Defined JWT token schema and type for EClinicalWorks source.
packages/shared/src/interface/external/ehr/source.ts Added eclinicalworks to the EhrSources enum.
packages/api/src/external/ehr/shared/utils/bundle/functions.ts Added eclinicalworks as a key with undefined bundle functions.
packages/api/src/external/ehr/shared/utils/jwt-token.ts Added EClinicalWorks support to dashboard JWT token sources and union type.
packages/api/src/external/ehr/shared/utils/mappings.ts Added eclinicalworks to secondary mappings schema map with undefined value.
packages/api/src/external/ehr/shared/utils/client.ts Extended environment union type and renamed client type alias for two-legged auth clients.
packages/api/src/routes/internal/jwt-token/index.ts Registered eclinicalworks JWT token internal routes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API_Router
    participant Middleware
    participant SyncLogic
    participant EClinicalWorksAPI
    participant MetriportDB

    Client->>API_Router: Request (GET/POST /ehr/eclinicalworks/patient/:id)
    API_Router->>Middleware: Validate & extract cxId, practiceId, tokenId, patientId
    Middleware->>SyncLogic: syncEclinicalworksPatientIntoMetriport(params)
    SyncLogic->>MetriportDB: Check for existing patient mapping
    alt Mapping exists
        MetriportDB-->>SyncLogic: Return Metriport patient ID
    else No mapping
        SyncLogic->>EClinicalWorksAPI: Fetch patient data (using JWT token)
        EClinicalWorksAPI-->>SyncLogic: Return patient data
        SyncLogic->>MetriportDB: Create/retrieve Metriport patient
        SyncLogic->>MetriportDB: Create patient mapping
    end
    SyncLogic-->>API_Router: Return Metriport patient ID
    API_Router
8000
-->>Client: Respond with patient ID (JSON)
Loading
sequenceDiagram
    participant Client
    participant API_Router
    participant Middleware
    participant JWTTokenLogic
    participant DB

    Client->>API_Router: GET /internal/token/eclinicalworks (with Authorization)
    API_Router->>Middleware: requestLogger, asyncHandler
    Middleware->>JWTTokenLogic: checkJwtToken(token, source)
    JWTTokenLogic->>DB: Retrieve and validate JWT token
    JWTTokenLogic-->>Middleware: Return token status
    Middleware-->>API_Router: Pass response
    API_Router-->>Client: Respond with status JSON

    Client->>API_Router: POST /internal/token/eclinicalworks (with Authorization & body)
    API_Router->>Middleware: requestLogger, asyncHandler
    Middleware->>JWTTokenLogic: saveJwtToken(token, source, data)
    JWTTokenLogic->>DB: Save JWT token
    JWTTokenLogic-->>Middleware: OK
    Middleware-->>API_Router: Pass response
    API_Router-->>Client: Respond with 200 OK
Loading
✨ 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.

… 198-ecw-v2

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

🧹 Nitpick comments (9)
packages/api/src/external/ehr/eclinicalworks/shared.ts (1)

9-20: Clear environment validation with descriptive errors

The implementation correctly retrieves and validates the EClinicalWorks environment. Consider enhancing the error message on line 15 to include what valid environments are expected.

-    throw new MetriportError("Invalid EClinicalWorks environment", undefined, { environment });
+    throw new MetriportError("Invalid EClinicalWorks environment", undefined, { 
+      environment, 
+      validEnvironments: eclinicalworksEnv 
+    });
packages/api/src/routes/ehr/eclinicalworks/patient.ts (3)

11-19: JSDoc comments need correction.

The JSDoc parameter documentation mentions req.query.aud but the code uses req.query.tokenId. This inconsistency should be fixed.

 * @param req.params.id The ID of Eclinicalworks Patient.
 * @param req.query.practiceId The ID of Eclinicalworks Practice.
-* @param req.query.aud The ID of Eclinicalworks Aud.
+* @param req.query.tokenId The ID of Eclinicalworks token.
 * @returns Metriport Patient if found.

39-47: JSDoc comments need correction.

The same JSDoc parameter inconsistency exists in the POST endpoint documentation.

 * @param req.params.id The ID of Eclinicalworks Patient.
 * @param req.query.practiceId The ID of Eclinicalworks Practice.
-* @param req.query.aud The ID of Eclinicalworks Aud.
+* @param req.query.tokenId The ID of Eclinicalworks token.
 * @returns Metriport Patient if found.

20-65: Consider refactoring duplicate code in GET and POST handlers.

The GET and POST handlers contain identical logic. Consider extracting the common functionality into a shared helper function to reduce code duplication and improve maintainability.

+// Helper function to handle both GET and POST requests
+async function handlePatientSync(req: Request, res: Response) {
+  const cxId = getCxIdOrFail(req);
+  const eclinicalworksPatientId = getFrom("params").orFail("id", req);
+  const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req);
+  const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req);
+  const patientId = await syncEclinicalworksPatientIntoMetriport({
+    cxId,
+    eclinicalworksPracticeId,
+    eclinicalworksPatientId,
+    eclinicalworksTokenId,
+  });
+  return res.status(httpStatus.OK).json(patientId);
+}

// GET endpoint
router.get(
  "/:id",
  handleParams,
  requestLogger,
-  asyncHandler(async (req: Request, res: Response) => {
-    const cxId = getCxIdOrFail(req);
-    const eclinicalworksPatientId = getFrom("params").orFail("id", req);
-    const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req);
-    const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req);
-    const patientId = await syncEclinicalworksPatientIntoMetriport({
-      cxId,
-      eclinicalworksPracticeId,
-      eclinicalworksPatientId,
-      eclinicalworksTokenId,
-    });
-    return res.status(httpStatus.OK).json(patientId);
-  })
+  asyncHandler(handlePatientSync)
);

// POST endpoint
router.post(
  "/:id",
  handleParams,
  requestLogger,
-  asyncHandler(async (req: Request, res: Response) => {
-    const cxId = getCxIdOrFail(req);
-    const eclinicalworksPatientId = getFrom("params").orFail("id", req);
-    const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req);
-    const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req);
-    const patientId = await syncEclinicalworksPatientIntoMetriport({
-      cxId,
-      eclinicalworksPracticeId,
-      eclinicalworksPatientId,
-      eclinicalworksTokenId,
-    });
-    return res.status(httpStatus.OK).json(patientId);
-  })
+  asyncHandler(handlePatientSync)
);
packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts (1)

33-37: Avoid passing the resolved value to next()

Promise.then(next) forwards the resolved value (here undefined) as the first argument to next().
Even though next(undefined) behaves like next(), the intent is less clear and can trip lint rules that disallow sending non-Error values to Express’ error handler.

-export function processCxIdDash(req: Request, res: Response, next: NextFunction) {
-  processCxIdShared(req, eclinicalworksDashSource, parseEclinicalworksPracticeIdDash)
-    .then(next)
-    .catch(next);
-}
+export function processCxIdDash(req: Request, res: Response, next: NextFunction) {
+  processCxIdShared(req, eclinicalworksDashSource, parseEclinicalworksPracticeIdDash)
+    .then(() => next())
+    .catch(next);
+}

Apply the same pattern to the two helpers below for consistency.

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

26-33: Redundant Axios instance creation

this.axiosFhirInstance = axios.create({}) in the constructor is overwritten a few lines later in initialize().
Removing the first instantiation avoids an unnecessary allocation and keeps the intent clear.

-    this.axiosFhirInstance = axios.create({});
+    // axiosFhirInstance will be initialised in `initialize`

43-48: Consider setting an Accept header instead of Content-Type for GETs

Content-Type is mainly relevant for requests with a body.
For GET requests against FHIR endpoints an Accept: application/fhir+json header better communicates the expected response format and avoids confusing upstream proxies that look at Content-Type.

-        "Content-Type": "application/x-www-form-urlencoded",
+        Accept: "application/fhir+json",
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts (1)

66-71: Un-awaited promise suppression might swallow critical sync errors

queryDocumentsAcrossHIEs(...).catch(processAsyncError()) is fire-and-forget, which is fine, but consider adding monitoring/logging that differentiates between expected background failures and unexpected ones to avoid silent data gaps.

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

225-234: Avoid variable shadowing for clearer intent

Using getEnv both as a parameter object and as the inner function name is confusing. Renaming the destructured result improves readability:

-  const [environment, twoLeggedAuthTokenInfo] = await Promise.all([
-    getEnv.getEnv(getEnv.params),
+  const [envConfig, twoLeggedAuthTokenInfo] = await Promise.all([
+    getEnv.getEnv(getEnv.params),
   ]);
 ...
-    ...environment,
+    ...envConfig,

This tiny change eliminates mental overhead when scanning the async flow.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 741f3bc and a88c3b6.

📒 Files selected for processing (16)
  • packages/api/src/command/jwt-token.ts (2 hunks)
  • packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts (1 hunks)
  • packages/api/src/external/ehr/eclinicalworks/shared.ts (1 hunks)
  • packages/api/src/external/ehr/shared.ts (1 hunks)
  • packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts (1 hunks)
  • packages/api/src/routes/ehr/eclinicalworks/patient.ts (1 hunks)
  • packages/api/src/routes/ehr/eclinicalworks/routes/dash.ts (1 hunks)
  • packages/api/src/routes/ehr/index.ts (2 hunks)
  • packages/api/src/routes/ehr/shared.ts (1 hunks)
  • packages/api/src/routes/internal/jwt-token/eclinicalworks.ts (1 hunks)
  • packages/api/src/shared/config.ts (1 hunks)
  • packages/core/src/external/ehr/eclinicalworks/index.ts (1 hunks)
  • packages/lambdas/src/shared/ehr.ts (1 hunks)
  • packages/shared/src/interface/external/ehr/eclinicalworks/index.ts (1 hunks)
  • packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts (1 hunks)
  • packages/shared/src/interface/external/ehr/source.ts (1 hunks)
🧰 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/lambdas/src/shared/ehr.ts
  • packages/api/src/routes/ehr/index.ts
  • packages/shared/src/interface/external/ehr/eclinicalworks/index.ts
  • packages/api/src/shared/config.ts
  • packages/api/src/external/ehr/eclinicalworks/shared.ts
  • packages/shared/src/interface/external/ehr/source.ts
  • packages/api/src/routes/ehr/eclinicalworks/routes/dash.ts
  • packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts
  • packages/api/src/routes/internal/jwt-token/eclinicalworks.ts
  • packages/api/src/routes/ehr/shared.ts
  • packages/api/src/command/jwt-token.ts
  • packages/api/src/routes/ehr/eclinicalworks/patient.ts
  • packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts
  • packages/core/src/external/ehr/eclinicalworks/index.ts
  • packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts
  • packages/api/src/external/ehr/shared.ts
🧬 Code Graph Analysis (7)
packages/api/src/external/ehr/eclinicalworks/shared.ts (3)
packages/api/src/shared/config.ts (1)
  • getEClinicalWorksEnv (351-353)
packages/core/src/external/ehr/eclinicalworks/index.ts (2)
  • EClinicalWorksEnv (15-15)
  • isEClinicalWorksEnv (16-18)
packages/api/src/external/ehr/shared.ts (1)
  • EhrPerPracticeParams (186-186)
packages/api/src/routes/ehr/eclinicalworks/routes/dash.ts (2)
packages/api/src/routes/ehr/shared.ts (4)
  • documentDownloadUrlRegex (48-48)
  • processPatientRoute (91-100)
  • processEhrPatientId (135-154)
  • processDocumentRoute (102-111)
packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts (3)
  • processPatientRoute (39-41)
  • tokenEhrPatientIdQueryParam (12-12)
  • processDocumentRoute (43-45)
packages/api/src/routes/internal/jwt-token/eclinicalworks.ts (2)
packages/api/src/external/ehr/shared/utils/jwt-token.ts (2)
  • checkJwtToken (68-82)
  • saveJwtToken (84-101)
packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts (2)
  • eclinicalworksDashSource (4-4)
  • eclinicalworksDashJwtTokenDataSchema (5-9)
packages/api/src/routes/ehr/shared.ts (3)
packages/api/src/domain/jwt-token.ts (1)
  • JwtTokenData (21-21)
packages/api/src/command/jwt-token.ts (1)
  • getJwtToken (26-35)
packages/shared/src/common/date.ts (1)
  • buildDayjs (70-72)
packages/api/src/command/jwt-token.ts (2)
packages/api/src/domain/jwt-token.ts (1)
  • JwtToken (30-30)
packages/shared/src/index.ts (1)
  • NotFoundError (41-41)
packages/api/src/routes/ehr/eclinicalworks/patient.ts (1)
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts (1)
  • syncEclinicalworksPatientIntoMetriport (25-79)
packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts (3)
packages/api/src/domain/jwt-token.ts (1)
  • JwtTokenData (21-21)
packages/api/src/routes/ehr/shared.ts (3)
  • ParseResponse (17-20)
  • processPatientRoute (91-100)
  • processDocumentRoute (102-111)
packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts (1)
  • eclinicalworksDashSource (4-4)
🪛 GitHub Actions: PR Check - Core
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts

[error] 13-13: TypeScript error TS2307: Cannot find module '../../shared-fhir' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - API
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts

[error] 13-13: TypeScript error TS2307: Cannot find module '../../shared-fhir' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - Lambdas
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts

[error] 13-13: TypeScript error TS2307: Cannot find module '../../shared-fhir' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - Shared
packages/api/src/external/ehr/eclinicalworks/command/sync-patient.ts

[error] 13-13: TypeScript error TS2307: Cannot find module '../../shared-fhir' or its corresponding type declarations.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (26)
packages/shared/src/interface/external/ehr/source.ts (1)

6-6: LGTM: Adding EClinicalWorks to EHR sources

The addition of EClinicalWorks as a new EHR source is straightforward and follows the existing pattern in the enum. Since the ehrSources array and EhrSource type are derived from this enum, this change automatically makes EClinicalWorks available throughout the system.

packages/api/src/shared/config.ts (1)

351-353: LGTM: Configuration method for EClinicalWorks environment

This method follows the established pattern for EHR environment configuration and correctly uses the optional getEnvVar helper.

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

37-37: Fixed error message for departmentId validation

Good catch - the error message now correctly refers to "departmentId" instead of what was likely "patientId" before, making the error more accurate and helpful for debugging.

packages/shared/src/interface/external/ehr/eclinicalworks/index.ts (1)

1-1: LGTM: Clean export pattern for JWT token functionality

This follows the common pattern of using index files to re-export functionality from specific modules, providing a cleaner public API for the EClinicalWorks integration.

packages/api/src/routes/ehr/index.ts (1)

11-12: Integration of EClinicalWorks follows consistent pattern!

The implementation for EClinicalWorks follows the same pattern as other EHR integrations, with proper middleware for customer ID processing and access checks.

Also applies to: 32-32

packages/api/src/command/jwt-token.ts (3)

3-3: Code organization improvement

Moving the Op import from Sequelize to the imports section is a good organizational change, maintaining consistency with other imports.


52-61: LGTM: Token retrieval by ID

This function follows the established pattern for token retrieval without checking expiration, with proper typing and error handling.


63-70: LGTM: Token retrieval with error handling

This implementation correctly follows the existing pattern for throwing NotFoundError when a token doesn't exist, providing appropriate context in the error.

packages/api/src/routes/ehr/shared.ts (1)

25-32: Modification to support token ID in parse function

The updated function signature properly passes both token data and token ID to the parse function, enabling implementations to use the token ID when needed (specifically for EClinicalWorks integration).

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

22-24: LGTM: Clear type extension

The type definition correctly extends the base EHR practice parameters with the required auth token parameter.


26-35:

✅ Verification successful

Clean client creation implementation

The client creation function follows functional programming principles with good error handling. The await on line 30 might be unnecessary if EClinicalWorksApi.create isn't actually async - verify in the implementation.


🏁 Script executed:

#!/bin/bash
# Check if EClinicalWorksApi.create is truly async
ast-grep --pattern $'EClinicalWorksApi.create = async ({ practiceId, environment, authToken }: EClinicalWorksCreateParams): Promise<EClinicalWorksApi> =>'

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Locate the file defining the EClinicalWorksApi class
file=$(rg -l "class EClinicalWorksApi" -g '*.ts')
if [ -z "$file" ]; then
  echo "EClinicalWorksApi class not found."
  exit 1
fi
echo "EClinicalWorksApi defined in: $file"

# Show the create method signature and surrounding lines
rg -n -C 2 "create" "$file"

Length of output: 987


Await on EClinicalWorksApi.create is required

Confirmed that EClinicalWorksApi.create is declared as an async static method and internally awaits initialize(). Keeping the await in createEClinicalWorksClient is correct.

packages/api/src/routes/internal/jwt-token/eclinicalworks.ts (4)

1-11: Imports and module structure look good.

The imports are well-organized and appropriate for the functionality this module implements. You're correctly importing the eClinicalWorks JWT token schema from shared interfaces and using standard Express routing patterns.


15-34: GET endpoint implementation looks correct.

The endpoint properly extracts the authorization token, performs appropriate error handling when the token is missing, and correctly calls the checkJwtToken function with the eClinicalWorks source identifier. The response structure follows good REST practices.


36-39: Zod schema definition is appropriate.

The schema correctly validates the required fields for creating a JWT token, leveraging the imported eClinicalWorks JWT token data schema.


41-63: POST endpoint implements proper validation and token saving.

The implementation correctly validates the request token, checks the request body against the schema, and saves the JWT token with appropriate parameters. Error handling is well-implemented.

packages/shared/src/interface/external/ehr/eclinicalworks/jwt-token.ts (3)

1-4: Imports are appropriate and concise.

The file correctly imports Zod for schema validation and the EHR sources enumeration.


4-9: JWT token schema definition looks good.

The schema correctly defines the required fields for eClinicalWorks JWT tokens with proper typing. Using a literal type for the source field ensures that only the correct source value can be used.


10-10: Type inference from Zod schema follows best practices.

Using z.infer to derive the TypeScript type from the Zod schema ensures type consistency between runtime validation and compile-time type checking.

packages/api/src/routes/ehr/eclinicalworks/routes/dash.ts (5)

1-14: Imports are comprehensive and appropriate.

The module imports all necessary components for the eClinicalWorks dashboard routes, including middleware, handlers, and utility functions.


17-17: Skip paths definition is appropriate.

The array correctly specifies which paths should skip the eClinicalWorks ID check, specifically for document download URLs.


19-27: Patient route middleware chain looks good.

The middleware chain for patient routes correctly applies parameter handling, route processing, EHR patient ID validation, and patient authorization before passing control to the patient handler.


28-37: Document route middleware chain is properly implemented.

The middleware chain for document routes correctly processes document routes, applies EHR patient ID validation with appropriate skip paths, and passes control to the document handler.


38-38: Settings route is properly configured.

The settings route is correctly set up to use the settings handler.

packages/api/src/routes/ehr/eclinicalworks/patient.ts (2)

1-8: Imports are appropriate for the functionality.

The module correctly imports the necessary dependencies for Express routing, HTTP status handling, and eClinicalWorks patient synchronization.


20-37: GET endpoint implementation looks good.

The endpoint correctly extracts required parameters, calls the synchronization function, and returns the patient ID. The use of helper functions for parameter extraction and validation is a good practice.

packages/api/src/routes/ehr/eclinicalworks/auth/middleware.ts (1)

18-30: Confirm the correct identifier is returned as externalId

parseEclinicalworksPracticeIdDash returns the practiceId as externalId.
If downstream logic (e.g. replaceIdInQueryParams in routes/ehr/shared.ts) expects the patient’s external ID, this could break patient-level mapping and cause data to be fetched for the wrong resource.

Please double-check the contract of processCxIdShared and ensure that externalId should indeed be the practice identifier.

Thomas Yopes added 9 commits May 20, 2025 09:04
Ref: ENG-198

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

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

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

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

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

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
… 198-ecw-v2

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

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
… 198-ecw-v2

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Comment on lines +39 to +66
/**
* POST /ehr/eclinicalworks/patient/:id
*
* Tries to retrieve the matching Metriport patient
* @param req.params.id The ID of Eclinicalworks Patient.
* @param req.query.practiceId The ID of Eclinicalworks Practice.
* @param req.query.tokenId The ID of Eclinicalworks Token.
* @returns Metriport Patient if found.
*/
router.post(
"/:id",
handleParams,
requestLogger,
asyncHandler(async (req: Request, res: Response) => {
const cxId = getCxIdOrFail(req);
const eclinicalworksPatientId = getFrom("params").orFail("id", req);
const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req);
const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req);
const patientId = await syncEClinicalWorksPatientIntoMetriport({
cxId,
eclinicalworksPracticeId,
eclinicalworksPatientId,
eclinicalworksTokenId,
});
return res.status(httpStatus.OK).json(patientId);
})
);

Copy link
Member

Choose a reason for hiding this comment

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

Why do we have the same logic but one is post and the other get?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We wanted to eventually migrate to POST as it creates a mapping on our end (so not a pure GET) but frontend needs an update to roll this out completely

import ElationApi, { ElationEnv } from "@metriport/core/external/ehr/elation/index";
import { HealthieEnv } from "@metriport/core/external/ehr/healthie/index";
Copy link
Member

Choose a reason for hiding this comment

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

Did we forget to do this before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

@thomasyopes thomasyopes requested a review from Orta21 May 23, 2025 21:21
Ref: ENG-198

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

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
@thomasyopes thomasyopes added this pull request to the merge queue May 23, 2025
Merged via the queue into develop with commit 33790e0 May 23, 2025
22 checks passed
@thomasyopes thomasyopes deleted the 198-ecw-v2 branch May 23, 2025 22:10
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.

3 participants
0