Skip to content

Conversation

thomasyopes
Copy link
Contributor

@thomasyopes thomasyopes commented Aug 29, 2025

Issues:

Description

Testing

Check each PR.

Release Plan

  • ⚠️ Points to master
  • Merge this

Summary by CodeRabbit

  • New Features

    • Added pre-validation of Athena department IDs for patient sync and write-backs, providing faster, clearer errors.
    • Improved webhook subscription reliability for Elation and Healthie via unified mapping handling.
    • Enhanced handling of EHRs with/without secondary mappings for more accurate behavior.
  • Refactor

    • Consolidated retrieval and parsing of secondary mappings into a single helper.
    • Streamlined internal mapping logic and reduced redundant lookups.
  • Chores

    • EHR client credentials now include a client secret, aligning with stricter integration requirements.

Thomas Yopes and others added 8 commits August 28, 2025 10:36
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
…-enforce-athena-department-ids

Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
…ment-ids

feat(ehr): extra athenahealth validation on department id
Copy link

coderabbitai bot commented Aug 29, 2025

Walkthrough

Refactors EHR secondary-mapping utilities and types, introduces a generic helper to fetch and parse CX secondary mappings, adds department validation for Athena flows, updates webhook subscription flows (Elation, Healthie) to use the new helper, adjusts a domain import, and extends client credential types.

Changes

Cohort / File(s) Summary
Core mappings refactor
packages/core/src/external/ehr/mappings.ts
Adds EhrSourceWithSecondaryMappings, type guard, narrows ehrCxMappingSecondaryMappingsSchemaMap to supported sources, introduces ehrCxMappingSecondaryMappingsSchemaMapGeneral for all sources (including undefined entries).
Write-back bundle guard
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
Uses isEhrSourceWithSecondaryMappings to short-circuit when EHR lacks secondary mappings; imports updated accordingly.
Environment credentials type
packages/core/src/external/ehr/environment.ts
Extends EhrEnvAndClientCredentials to include clientSecret alongside clientKey and environment.
CX mapping domain import
packages/api/src/domain/cx-mapping.ts
Replaces usage of ehrCxMappingSecondaryMappingsSchemaMap with ehrCxMappingSecondaryMappingsSchemaMapGeneral; no behavior change beyond symbol swap.
Generic CX mapping + parsing helper
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
Adds getCxMappingAndParsedSecondaryMappings: fetches CX mapping, ensures secondaryMappings exist, parses via per-EHR schema, returns { parsedSecondaryMappings, cxMapping }.
Athena shared validation
packages/api/src/external/ehr/athenahealth/shared.ts
Adds validateDepartmentId: derives enabled department IDs from parsed secondary mappings and validates provided department against them; throws BadRequestError when invalid.
Athena pre-validation usage
packages/api/src/external/ehr/athenahealth/command/sync-patient.ts, .../write-back/allergy.ts, .../write-back/condition.ts, .../write-back/immunization.ts, .../write-back/lab.ts, .../write-back/medication.ts, .../write-back/note.ts, .../write-back/procedure.ts, .../write-back/vitals.ts
Adds await validateDepartmentId(...) at function start before creating Athena client; imports updated; existing signatures unchanged.
Elation webhook refactor
packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
Replaces manual CX mapping retrieval/parsing with getCxMappingAndParsedSecondaryMappings; updates merging to use parsedSecondaryMappings; removes explicit schema parsing and prior helper usage.
Healthie webhook refactor
packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
Similar refactor to Elation: uses getCxMappingAndParsedSecondaryMappings; updates updates/merging accordingly; removes manual schema parsing and old helper.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Cmd as Athena cmd (write-back/sync)
  participant Shared as validateDepartmentId
  participant Map as getCxMappingAndParsedSecondaryMappings
  participant Mappings as CX Mapping Store

  Caller->>Cmd: invoke(...)
  Cmd->>Shared: validateDepartmentId({ cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId })
  Shared->>Map: getCxMappingAndParsedSecondaryMappings<Epic/Athena...>({ ehr: athena, practiceId })
  Map->>Mappings: getCxMappingOrFail({ externalId: practiceId, source: athena })
  Mappings-->>Map: cxMapping (with secondaryMappings)
  Map-->>Shared: { parsedSecondaryMappings, cxMapping }
  Shared->>Shared: normalize IDs and check department enabled
  alt invalid department
    Shared-->>Cmd: throw BadRequestError
    Cmd-->>Caller: error
  else valid
    Shared-->>Cmd: ok
    Cmd->>Cmd: createAthenaClient and proceed
    Cmd-->>Caller: success
  end
Loading
sequenceDiagram
  autonumber
  actor Caller
  participant Sub as subscribeToWebhook (Elation/Healthie)
  participant Helper as getCxMappingAndParsedSecondaryMappings
  participant Store as CX Mapping Store
  participant EHR as EHR API

  Caller->>Sub: subscribeToWebhook(practiceId, resources)
  Sub->>Helper: getCxMappingAndParsedSecondaryMappings<T>({ ehr, practiceId })
  Helper->>Store: getCxMappingOrFail(...)
  Store-->>Helper: cxMapping + secondaryMappings
  Helper-->>Sub: { parsedSecondaryMappings, cxMapping }
  Sub->>EHR: create/update webhook subscriptions
  Sub->>Store: setSecondaryMappingsOnCxMappingById(merge parsedSecondaryMappings.webhooks)
  Sub-->>Caller: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit 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

Status, Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts (1)

31-37: Guard against undefined webhooks to avoid runtime spread error.

If parsedSecondaryMappings.webhooks can be undefined, object spread will throw. Use nullish coalescing.

Apply:

-      webhooks: {
-        ...parsedSecondaryMappings.webhooks,
+      webhooks: {
+        ...(parsedSecondaryMappings.webhooks ?? {}),
         [subscription.resource]: {
           url: subscription.target,
           signingKey: subscription.signing_pub_key,
         },
       },
🧹 Nitpick comments (8)
packages/core/src/external/ehr/mappings.ts (1)

48-53: General map is useful — minor naming consistency nit.

eclinicalworks explicitly undefined is clear. Small nit: consider pluralizing to ehrSourcesWithSecondaryMappings for consistency with ehrSourcesWithClientCredentials (optional).

-export const ehrSourceWithSecondaryMappings = [
+export const ehrSourcesWithSecondaryMappings = [
   EhrSources.athena,
   EhrSources.elation,
   EhrSources.canvas,
   EhrSources.healthie,
 ] as const;
-export type EhrSourceWithSecondaryMappings = (typeof ehrSourceWithSecondaryMappings)[number];
+export type EhrSourceWithSecondaryMappings = (typeof ehrSourcesWithSecondaryMappings)[number];
 export function isEhrSourceWithSecondaryMappings(
   ehr: string
 ): ehr is EhrSourceWithSecondaryMappings {
-  return ehrSourceWithSecondaryMappings.includes(ehr as EhrSourceWithSecondaryMappings);
+  return ehrSourcesWithSecondaryMappings.includes(ehr as EhrSourceWithSecondaryMappings);
 }
packages/api/src/domain/cx-mapping.ts (1)

4-5: Switch to general map looks right — tighten the map’s value type.

Use the specific schema type to preserve compile-time safety.

-import {
-  EhrCxMappingSecondaryMappings,
-  ehrCxMappingSecondaryMappingsSchemaMapGeneral,
-} from "@metriport/core/external/ehr/mappings";
+import {
+  EhrCxMappingSecondaryMappings,
+  ehrCxMappingSecondaryMappingsSchemaMapGeneral,
+} from "@metriport/core/external/ehr/mappings";

 export type CxMappingSecondaryMappings = EhrCxMappingSecondaryMappings | null;
-export const secondaryMappingsSchemaMap: { [key in CxMappingSource]: z.Schema | undefined } = {
-  ...ehrCxMappingSecondaryMappingsSchemaMapGeneral,
-};
+export const secondaryMappingsSchemaMap: {
+  [key in CxMappingSource]: z.Schema<EhrCxMappingSecondaryMappings> | undefined;
+} = {
+  ...ehrCxMappingSecondaryMappingsSchemaMapGeneral,
+};

Also applies to: 15-17

packages/api/src/external/ehr/athenahealth/command/write-back/note.ts (1)

19-27: Nice early failure; consider centralizing this pattern.

These validate-then-client lines are duplicated across Athena write-backs. Consider a small wrapper to reduce repetition while keeping call sites simple.

Example wrapper usage in this file:

-  await validateDepartmentId({ cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId });
-  const api = await createAthenaClient({ cxId, practiceId: athenaPracticeId });
+  const api = await withValidatedDepartment(
+    { cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId },
+    () => createAthenaClient({ cxId, practiceId: athenaPracticeId })
+  );

(Wrapper could live under ../../shared.)

packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (1)

18-26: Pre-validation is correct; please cover with tests.

Add cases mirroring note/immunization to ensure failures short-circuit before API calls.

If you introduce the suggested wrapper, update here too to remove duplication.

packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1)

34-39: Minor typing improvement to avoid as T cast (optional).

You can type the schema as z.Schema to remove the post-parse cast.

Apply:

+import type { z } from "zod";
@@
-  const schema = ehrCxMappingSecondaryMappingsSchemaMap[ehr];
+  const schema = ehrCxMappingSecondaryMappingsSchemaMap[ehr] as z.Schema<T>;
   return {
-    parsedSecondaryMappings: schema.parse(cxMapping.secondaryMappings) as T,
+    parsedSecondaryMappings: schema.parse(cxMapping.secondaryMappings),
     cxMapping,
   };
packages/api/src/external/ehr/athenahealth/shared.ts (2)

4-7: Unify EhrSources import path for consistency

Other files (e.g., Healthie) import EhrSources from the interface path. Align here to reduce import churn.

-import { BadRequestError, EhrSources, MetriportError } from "@metriport/shared";
+import { BadRequestError, MetriportError } from "@metriport/shared";
+import { EhrSources } from "@metriport/shared/interface/external/ehr/source";

72-84: Confirm “allow-all when unset” semantics and enrich context

  • Confirm that empty departmentIds means “no restriction.” If intentional, add a short comment.
  • Consider adding allowed departmentIds to additionalInfo to speed up support triage.
-  if (departmentIds.length > 0 && !departmentIds.includes(strippedDepartmentId)) {
+  // Empty list => no restriction (intended).
+  if (departmentIds.length > 0 && !departmentIds.includes(strippedDepartmentId)) {
     throw new BadRequestError(
       "AthenaHealth patient is not in a department that is enabled",
       undefined,
       {
         cxId,
         athenaPracticeId,
         athenaPatientId,
         athenaDepartmentId,
+        allowedDepartmentIds: departmentIds,
       }
     );
   }
packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts (1)

41-49: Avoid accidental overwrite on concurrent updates

setSecondaryMappingsOnCxMappingById appears to write the whole object. If multiple webhook subscriptions run concurrently, last-write-wins may drop keys. Prefer a read–merge–write with server-side merge/patch or optimistic concurrency (ETag/version).

If supported, patch only webhooks:

   await setSecondaryMappingsOnCxMappingById({
     cxId,
     id: cxMapping.id,
-    secondaryMappings: {
-      ...parsedSecondaryMappings,
-      webhooks: {
-        ...parsedSecondaryMappings.webhooks,
-        [eventType]: { url, secretKey },
-      },
-    },
+    secondaryMappings: {
+      webhooks: {
+        ...parsedSecondaryMappings.webhooks,
+        [eventType]: { url, secretKey },
+      },
+    }, // ensure server merges instead of replacing
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3488fcd and 8394e8d.

📒 Files selected for processing (17)
  • packages/api/src/domain/cx-mapping.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts (2 hunks)
  • packages/api/src/external/ehr/athenahealth/shared.ts (2 hunks)
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts (2 hunks)
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts (3 hunks)
  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1 hunks)
  • packages/core/src/external/ehr/environment.ts (1 hunks)
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (2 hunks)
  • packages/core/src/external/ehr/mappings.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: 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 const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want 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)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use /** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible

Files:

  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/core/src/external/ehr/environment.ts
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/api/src/domain/cx-mapping.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use types whenever possible

Files:

  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/core/src/external/ehr/environment.ts
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/api/src/domain/cx-mapping.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
**/*.ts

⚙️ CodeRabbit configuration file

**/*.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
  • U...

Files:

  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/core/src/external/ehr/environment.ts
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/api/src/domain/cx-mapping.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
🧠 Learnings (25)
📓 Common learnings
Learnt from: thomasyopes
PR: metriport/metriport#4483
File: packages/api/src/external/ehr/athenahealth/shared.ts:0-0
Timestamp: 2025-08-29T16:12:38.209Z
Learning: In Athena secondary mappings (packages/api/src/external/ehr/athenahealth/shared.ts), departmentIds is parsed via zod schema validation, so it will not be undefined and doesn't need null coalescing (departmentIds ?? []).
Learnt from: thomasyopes
PR: metriport/metriport#4478
File: package.json:14-16
Timestamp: 2025-08-27T23:20:44.517Z
Learning: thomasyopes prefers maintaining consistency with existing codebase patterns over introducing new approaches, even when the new approach might be technically cleaner (e.g., using workspace commands vs cd patterns).
Learnt from: thomasyopes
PR: metriport/metriport#4478
File: packages/core/src/external/ehr/elation/index.ts:639-657
Timestamp: 2025-08-27T23:18:23.105Z
Learning: thomasyopes prefers allowing mutation in reduce accumulators when it's the intended pattern, rather than forcing immutable approaches that may be unnecessarily complex.
📚 Learning: 2025-06-06T16:45:31.832Z
Learnt from: thomasyopes
PR: metriport/metriport#3970
File: packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts:17-17
Timestamp: 2025-06-06T16:45:31.832Z
Learning: The writeMedicationToChart function in packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts returns a response that is not currently used by any consumers, so changes to its return type are not breaking changes in practice.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-08-29T16:12:38.209Z
Learnt from: thomasyopes
PR: metriport/metriport#4483
File: packages/api/src/external/ehr/athenahealth/shared.ts:0-0
Timestamp: 2025-08-29T16:12:38.209Z
Learning: In Athena secondary mappings (packages/api/src/external/ehr/athenahealth/shared.ts), departmentIds is parsed via zod schema validation, so it will not be undefined and doesn't need null coalescing (departmentIds ?? []).

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/note.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-05-20T21:26:26.804Z
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/api/src/routes/internal/medical/patient-consolidated.ts:141-174
Timestamp: 2025-05-20T21:26:26.804Z
Learning: The functionality introduced in packages/api/src/routes/internal/medical/patient-consolidated.ts is planned to be refactored in downstream PR #3857, including improvements to error handling and validation.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-06-25T18:42:07.231Z
Learnt from: keshavsaharia
PR: metriport/metriport#4075
File: packages/core/src/external/surescripts/fhir/medication-request.ts:76-83
Timestamp: 2025-06-25T18:42:07.231Z
Learning: In packages/core/src/external/surescripts/fhir/medication-request.ts, the getDispenseNote and getDosageInstruction functions are intentionally identical because the dashboard requires both the note and dosageInstruction fields of MedicationRequest to be populated with detail.directions for proper note display functionality.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
📚 Learning: 2025-05-28T19:06:02.261Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/api/src/external/ehr/athenahealth/command/process-patients-from-appointments.ts:178-180
Timestamp: 2025-05-28T19:06:02.261Z
Learning: In Athena appointments, the `patientid` and `departmentid` fields will always be defined, so null checks are not necessary when calling `createPatientId()` and `createDepartmentId()` on these fields.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-07-09T17:18:16.731Z
Learnt from: thomasyopes
PR: metriport/metriport#4164
File: packages/core/src/external/ehr/healthie/index.ts:939-953
Timestamp: 2025-07-09T17:18:16.731Z
Learning: In packages/core/src/external/ehr/healthie/index.ts, the immunization.cvx_code field is required and non-nullable according to the zod schema validation (z.coerce.string()), so null checks are not needed in the convertImmunizationToFhir method.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts
📚 Learning: 2025-08-15T00:00:45.080Z
Learnt from: keshavsaharia
PR: metriport/metriport#4370
File: packages/shared/src/domain/patient.ts:5-5
Timestamp: 2025-08-15T00:00:45.080Z
Learning: The patientSchema in packages/shared/src/domain/patient.ts is used in a limited subsystem scope ("SS") and is not the same as other Patient schemas referenced throughout the codebase, making additive optional field changes like externalId safe and non-breaking.

Applied to files:

  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-08-25T17:29:10.848Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/command/get-client-token-info.ts:20-21
Timestamp: 2025-08-25T17:29:10.848Z
Learning: In the EHR integration architecture, OAuth-based providers (Canvas, Athena, Elation) are separated from API-key based providers (Healthie, eClinicalWorks) using type guards like `isEhrSourceWithClientCredentials()`. The `EhrSourceWithClientCredentials` union type only includes OAuth providers, allowing JWT token info retrieval flows to be cleanly separated from API-key authentication flows.

Applied to files:

  • packages/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/environment.ts
📚 Learning: 2025-08-13T21:37:33.680Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:25-29
Timestamp: 2025-08-13T21:37:33.680Z
Learning: In Athena EHR integration (packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts), thomasyopes prefers hard-failing when secondary mappings fetch fails rather than graceful degradation, because these mappings control critical behaviors like contributionEncounterAppointmentTypesBlacklist and contributionEncounterSummariesEnabled. Running with default values when mappings are unavailable could lead to incorrect processing.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-04-16T00:25:25.196Z
Learnt from: RamilGaripov
PR: metriport/metriport#3676
File: packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts:1-10
Timestamp: 2025-04-16T00:25:25.196Z
Learning: The circular dependency between shared.ts (importing getPatientIdsOrFail) and adt/utils.ts (using unpackPidFieldOrFail) will be addressed in a follow-up PR.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/shared.ts
📚 Learning: 2025-06-11T21:39:26.805Z
Learnt from: thomasyopes
PR: metriport/metriport#4000
File: packages/core/src/external/ehr/athenahealth/index.ts:504-507
Timestamp: 2025-06-11T21:39:26.805Z
Learning: In AthenaHealth write-back (`packages/core/src/external/ehr/athenahealth/index.ts`), only the condition statuses “relapse” and “recurrence” are currently mapped to AthenaHealth problem statuses (“CHRONIC”); other FHIR clinicalStatus values (e.g., “active”, “resolved”, “inactive”, “remission”) are not yet supported.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts
📚 Learning: 2025-08-26T17:43:13.189Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/api/get-client-key-and-secret.ts:0-0
Timestamp: 2025-08-26T17:43:13.189Z
Learning: In packages/core/src/external/ehr/api/get-client-key-and-secret.ts, the masking logic has been updated to be more secure than initially suggested. The getNumberOfCharactersToShow function correctly implements complete masking (0 characters) for short secrets and proportional masking for longer ones, following the "fail secure" principle better than showing a minimum number of characters.

Applied to files:

  • packages/core/src/external/ehr/environment.ts
📚 Learning: 2025-05-27T16:10:48.223Z
Learnt from: lucasdellabella
PR: metriport/metriport#3866
File: packages/core/src/command/hl7v2-subscriptions/hl7v2-roster-generator.ts:142-151
Timestamp: 2025-05-27T16:10:48.223Z
Learning: In packages/core/src/command/hl7v2-subscriptions/hl7v2-roster-generator.ts, the user prefers to let axios errors bubble up naturally rather than adding try-catch blocks that re-throw with MetriportError wrappers, especially when the calling code already has retry mechanisms in place via simpleExecuteWithRetries.

Applied to files:

  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
📚 Learning: 2025-08-25T18:29:06.278Z
Learnt from: thomasyopes
PR: metriport/metriport#4458
File: packages/api/src/external/ehr/elation/command/sync-patient.ts:0-0
Timestamp: 2025-08-25T18:29:06.278Z
Learning: The `inputMetriportPatientId` parameter in `syncElationPatientIntoMetriport` and `syncHealthiePatientIntoMetriport` functions is meant for optional validation in `getOrCreateMetriportPatient`, not as a required parameter that call sites need to explicitly map. It can remain undefined/unmapped at call sites and the function will work correctly without it.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-07-30T14:51:29.865Z
Learnt from: RamilGaripov
PR: metriport/metriport#4187
File: packages/api/src/command/medical/patient/map-patient.ts:41-49
Timestamp: 2025-07-30T14:51:29.865Z
Learning: In the syncElationPatientIntoMetriport and syncHealthiePatientIntoMetriport functions, the patientId parameter is intended to receive the Metriport patient ID for demographic validation purposes via confirmPatientMatch, not the external system patient ID. The external patient ID should be passed through the specific external ID parameters (elationPatientId, healthiePatientId, etc.).

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-07-30T14:51:35.852Z
Learnt from: RamilGaripov
PR: metriport/metriport#4187
File: packages/api/src/command/medical/patient/map-patient.ts:50-59
Timestamp: 2025-07-30T14:51:35.852Z
Learning: In the EHR sync functions like syncHealthiePatientIntoMetriport and syncElationPatientIntoMetriport, the optional patientId parameter is intended to receive the Metriport patient ID (not the external patient ID) for patient match confirmation purposes via the confirmPatientMatch function.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-03-11T20:42:46.516Z
Learnt from: thomasyopes
PR: metriport/metriport#3427
File: packages/core/src/external/ehr/api/sync-patient.ts:16-55
Timestamp: 2025-03-11T20:42:46.516Z
Learning: In the patient synchronization architecture, the flow follows this pattern: (1) `ehr-sync-patient-cloud.ts` sends messages to an SQS queue, (2) the `ehr-sync-patient` Lambda consumes these messages, and (3) the Lambda uses the `syncPatient` function to make the API calls to process the patient data.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-05-19T13:53:09.828Z
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/core/src/command/consolidated/search/fhir-resource/search-semantic.ts:36-36
Timestamp: 2025-05-19T13:53:09.828Z
Learning: In the `getConsolidatedPatientData` function from `metriport/core/command/consolidated/consolidated-get`, only the `patient` parameter is required. Other parameters like `requestId`, `resources`, `dateFrom`, `dateTo`, `fromDashboard`, and `forceDataFromFhir` are all optional.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-06-03T21:02:23.374Z
Learnt from: leite08
PR: metriport/metriport#3953
File: packages/core/src/command/consolidated/search/fhir-resource/__tests__/search-consolidated-setup.ts:28-30
Timestamp: 2025-06-03T21:02:23.374Z
Learning: The `makePatient()` function returns `PatientWithId` type which requires the `id` field to be present, ensuring `patient.id` is never undefined.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-08-25T23:28:41.385Z
Learnt from: keshavsaharia
PR: metriport/metriport#4459
File: packages/core/src/external/quest/fhir/patient.ts:22-24
Timestamp: 2025-08-25T23:28:41.385Z
Learning: FHIR resources should have their ID field determined by `uuidv7()` generated UUIDs. The import should be: `import { uuidv7 } from "metriport/shared/util/uuid-v7";`. External system IDs should not be used directly as FHIR resource IDs, even when sanitized, but should instead be preserved in the identifier field for reference mapping.

Applied to files:

  • packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
📚 Learning: 2025-06-19T22:44:49.393Z
Learnt from: thomasyopes
PR: metriport/metriport#4061
File: packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/run-job.ts:34-47
Timestamp: 2025-06-19T22:44:49.393Z
Learning: In packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/run-job.ts, the team prefers to keep refreshEhrBundles operations grouped using fire-and-forget pattern rather than awaiting all operations with Promise.allSettled(). This allows individual refresh operations to run independently without blocking the job runner.

Applied to files:

  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-08-25T17:29:25.387Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts:143-151
Timestamp: 2025-08-25T17:29:25.387Z
Learning: In packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts, the filterObservations function with vital.latestOnly=true is designed to keep only the most recent observation per LOINC code across all dates, not per date+LOINC pair. This behavior intentionally collapses multiple days into a single latest reading per code.

Applied to files:

  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.

Applied to files:

  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-05-01T16:10:45.273Z
Learnt from: thomasyopes
PR: metriport/metriport#3771
File: packages/core/src/util/webhook.ts:34-45
Timestamp: 2025-05-01T16:10:45.273Z
Learning: The webhook signature verification code in packages/core/src/util/webhook.ts is copied directly from Healthie's documentation and should not be modified to maintain exact compliance with their implementation.

Applied to files:

  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
🧬 Code graph analysis (16)
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (4)
packages/core/src/external/ehr/mappings.ts (3)
  • EhrCxMappingSecondaryMappings (33-37)
  • EhrSourceWithSecondaryMappings (26-26)
  • ehrCxMappingSecondaryMappingsSchemaMap (39-46)
packages/api/src/domain/cx-mapping.ts (1)
  • CxMapping (26-26)
packages/api/src/command/mapping/cx.ts (1)
  • getCxMappingOrFail (54-63)
packages/shared/src/index.ts (1)
  • MetriportError (43-43)
packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/note.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/core/src/external/ehr/mappings.ts (5)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (2)
  • AthenaSecondaryMappings (14-14)
  • athenaSecondaryMappingsSchema (4-13)
packages/shared/src/interface/external/ehr/canvas/cx-mapping.ts (2)
  • CanavsSecondaryMappings (5-5)
  • canvasSecondaryMappingsSchema (4-4)
packages/shared/src/interface/external/ehr/elation/cx-mapping.ts (2)
  • ElationSecondaryMappings (23-23)
  • elationSecondaryMappingsSchema (10-22)
packages/shared/src/interface/external/ehr/healthie/cx-mapping.ts (2)
  • HealthieSecondaryMappings (24-24)
  • healthieSecondaryMappingsSchema (10-23)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (11-11)
packages/api/src/external/ehr/athenahealth/shared.ts (2)
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1)
  • getCxMappingAndParsedSecondaryMappings (17-39)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • AthenaSecondaryMappings (14-14)
packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts (3)
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1)
  • getCxMappingAndParsedSecondaryMappings (17-39)
packages/shared/src/interface/external/ehr/elation/cx-mapping.ts (1)
  • ElationSecondaryMappings (23-23)
packages/api/src/command/mapping/cx.ts (1)
  • setSecondaryMappingsOnCxMappingById (155-172)
packages/api/src/domain/cx-mapping.ts (1)
packages/core/src/external/ehr/mappings.ts (1)
  • ehrCxMappingSecondaryMappingsSchemaMapGeneral (48-53)
packages/api/src/external/ehr/athenahealth/command/sync-patient.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-84)
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
packages/core/src/external/ehr/mappings.ts (1)
  • isEhrSourceWithSecondaryMappings (27-31)
packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts (2)
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1)
  • getCxMappingAndParsedSecondaryMappings (17-39)
packages/shared/src/interface/external/ehr/healthie/cx-mapping.ts (1)
  • HealthieSecondaryMappings (24-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
  • GitHub Check: ihe_stack / deploy
  • GitHub Check: infra-api-lambdas / deploy
  • GitHub Check: api / deploy
  • GitHub Check: mllp-server / deploy
🔇 Additional comments (22)
packages/core/src/external/ehr/environment.ts (1)

23-27: Ensure clientSecret is passed at all call sites and avoid logging it.

  • Verified constructors/use-sites in
    • packages/core/src/external/ehr/environment.ts
    • packages/core/src/external/ehr/elation/environment.ts
    • packages/core/src/external/ehr/canvas/environment.ts
    • packages/core/src/external/ehr/athenahealth/environment.ts
    • packages/core/src/external/ehr/command/get-secrets.ts
    • packages/api/src/external/ehr/shared/utils/client.ts
    now require clientSecret.
  • Confirm none of these functions or their callers log or emit the secret in metrics.
  • Add a credentials-masking helper for any debug output that touches these values.
packages/core/src/external/ehr/mappings.ts (2)

20-31: Good introduction of “with secondary mappings” guard.

Solid separation of sources that actually support secondary mappings. This enables safe short-circuiting downstream.


39-46: Narrowed schema map is correct — types align with supported EHRs.

The keyed map to z.Schema looks right and pairs well with the guard.

packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (2)

41-44: Importing the guard is correct — keeps lookups scoped to capable EHRs.

Ensures we don’t attempt schema lookups for sources that don’t support secondary mappings.


302-327: Early return for non-supporting EHRs is appropriate — behavior aligns with prior hard-fail policy.

  • For EHRs without secondary mappings: returns undefined filters, leading to permissive write-back (intended).
  • For supporting EHRs: still hard-fails if schema/mappings are absent or writeBackEnabled=false — consistent with earlier Athena stance.

If desired, add a unit test to assert that eclinicalworks skips mappings while athena requires them. I can draft tests.

packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts (1)

3-3: Pre-validating departmentId before client init is implemented consistently
validateDepartmentId is now applied in all Athena write-back commands (allergy, condition, immunization, lab, medication, note, vitals, procedure), ensuring fail-fast behavior. departmentIds is guaranteed by zod, so no nullish fallback is required downstream.

packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts (1)

3-3: Pre-validation added in the right spot; parameters look correct.

Validating the Athena department before creating the client is a good fail-fast guard and aligns with the secondary mapping contract. LGTM.

Also applies to: 18-18

packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts (1)

3-3: Good fail-fast department validation prior to API calls.

The new validation call and import are correct and consistent with the shared validator. No issues spotted.

Also applies to: 18-18

packages/api/src/external/ehr/athenahealth/command/write-back/condition.ts (1)

3-3: Solid placement of department validation.

Pre-validating department IDs before client creation is correct and keeps the flow consistent across write-backs.

Also applies to: 18-18

packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts (1)

3-3: LGTM on department validation integration.

Import and invocation are accurate; this will short-circuit invalid contexts cleanly.

Also applies to: 18-18

packages/api/src/external/ehr/athenahealth/command/sync-patient.ts (1)

14-14: Validation added correctly at the start of sync flow.

Early department verification before any Athena calls is appropriate and maintains consistent error semantics with other commands.

Also applies to: 38-38

packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (2)

3-3: Pre-flight validation import looks right.

Consistent with other Athena write-back flows. No issues.


18-25: ValidateDepartmentId: add tests and confirm ID-prefix logic

  • Add unit tests for validateDepartmentId(): when departmentIds includes the stripped ID (no throw), when it excludes it (throws BadRequestError), and when departmentIds is empty (no-op).
  • The code strips "a-1.Practice-" then \a-${strippedPracticeId}.Department-``; verify that all Athena practice/department IDs in prod/staging conform to that format or extend the logic accordingly.
packages/api/src/external/ehr/athenahealth/command/write-back/note.ts (1)

2-2: Importing validateDepartmentId here keeps enforcement consistent across write-backs.

Good alignment with the shared validation strategy.

packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (1)

3-3: Shared import update LGTM.

Matches the new validation flow.

packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts (2)

1-9: Good simplification via getCxMappingAndParsedSecondaryMappings.

Reduces local parsing and keeps types safer. Import paths look correct.


20-24: Typed helper usage is correct.

Generic specialization to ElationSecondaryMappings is appropriate here.

packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts (1)

26-33: Error semantics are good.

Static message, useful context. Aligns with error-handling guidelines.

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

61-66: Good use of the generic mapping helper

Fetching and parsing secondary mappings via the shared helper is clean and consistent.

packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts (3)

2-2: LGTM on type import

Using HealthieSecondaryMappings clarifies intent.


8-10: LGTM on mapping helper migration

Switch to getCxMappingAndParsedSecondaryMappings improves reuse and validation.


21-25: Verify schema guarantees webhooks presence

We spread parsedSecondaryMappings.webhooks; ensure the schema always defines it (object, not undefined), otherwise default to {}.

-  const { parsedSecondaryMappings, cxMapping } =
+  const { parsedSecondaryMappings, cxMapping } =
     await getCxMappingAndParsedSecondaryMappings<HealthieSecondaryMappings>({
       ehr: EhrSources.healthie,
       practiceId: healthiePracticeId,
     });
+  // If schema doesn't guarantee it, ensure a default:
+  // const webhooks = parsedSecondaryMappings.webhooks ?? {};

Comment on lines +67 to +71
const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
const strippedDepartmentId = athenaDepartmentId.replace(
`a-${strippedPracticeId}.Department-`,
""
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make department ID parsing format-safe (avoid false positives)

Using replace() can silently mis-parse if the expected prefix isn’t present. Prefer an explicit prefix check + slice.

-  const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
-  const strippedDepartmentId = athenaDepartmentId.replace(
-    `a-${strippedPracticeId}.Department-`,
-    ""
-  );
+  const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
+  const expectedDepartmentPrefix = `a-${strippedPracticeId}.Department-`;
+  if (!athenaDepartmentId.startsWith(expectedDepartmentPrefix)) {
+    throw new BadRequestError(
+      "Invalid Athena department ID format",
+      undefined,
+      { cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId }
+    );
+  }
+  const strippedDepartmentId = athenaDepartmentId.slice(expectedDepartmentPrefix.length);
📝 Committable suggestion

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

Suggested change
const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
const strippedDepartmentId = athenaDepartmentId.replace(
`a-${strippedPracticeId}.Department-`,
""
);
const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
const expectedDepartmentPrefix = `a-${strippedPracticeId}.Department-`;
if (!athenaDepartmentId.startsWith(expectedDepartmentPrefix)) {
throw new BadRequestError(
"Invalid Athena department ID format",
undefined,
{ cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId }
);
}
const strippedDepartmentId = athenaDepartmentId.slice(expectedDepartmentPrefix.length);
🤖 Prompt for AI Agents
In packages/api/src/external/ehr/athenahealth/shared.ts around lines 67–71, the
department ID is parsed using replace(), which can silently mis-parse if the
expected prefix isn’t present; update this to compute the expected prefix (e.g.
`const prefix = \`a-${strippedPracticeId}.Department-\``), check with
startsWith(prefix) and then extract the suffix with slice(prefix.length); if the
department string does not start with the prefix, handle it explicitly (throw an
error, return null/undefined, or log and bail) rather than silently returning a
wrong value.

Comment on lines +10 to +16
/**
* Get the cx mapping and parsed secondary mappings for a given practice ID for all EHRs
*
* @param ehr - The EHR source.
* @param practiceId - The practice id of the EHR integration.
* @returns The secondary mappings for the practice or null if the EHR doesn't have secondary mappings.
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix JSDoc: return description is inaccurate.

This function never returns null; it throws when mappings are absent and only accepts EHRs that support secondary mappings.

Apply:

- * @returns The secondary mappings for the practice or null if the EHR doesn't have secondary mappings.
+ * @returns Parsed secondary mappings and the cxMapping.
+ * @throws MetriportError if secondary mappings are missing for the given practice/EHR.
📝 Committable suggestion

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

Suggested change
/**
* Get the cx mapping and parsed secondary mappings for a given practice ID for all EHRs
*
* @param ehr - The EHR source.
* @param practiceId - The practice id of the EHR integration.
* @returns The secondary mappings for the practice or null if the EHR doesn't have secondary mappings.
*/
/**
* Get the cx mapping and parsed secondary mappings for a given practice ID for all EHRs
*
* @param ehr - The EHR source.
* @param practiceId - The practice id of the EHR integration.
* @returns Parsed secondary mappings and the cxMapping.
* @throws MetriportError if secondary mappings are missing for the given practice/EHR.
*/
🤖 Prompt for AI Agents
In
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
around lines 10 to 16, the JSDoc @returns is incorrect (it claims the function
can return null) — update the docblock to remove the null return description and
instead state that the function returns the parsed secondary mappings for the
practice (and that it will throw if mappings are absent and only supports EHRs
that provide secondary mappings); keep param descriptions as-is but ensure
@returns accurately reflects the actual return type and behavior (throwing on
missing mappings).

@thomasyopes thomasyopes added this pull request to the merge queue Aug 29, 2025
Merged via the queue into master with commit 7cc289b Aug 29, 2025
68 checks passed
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