Skip to content

Conversation

thomasyopes
Copy link
Contributor

@thomasyopes thomasyopes commented Aug 28, 2025

Ref: ENG-000

Description

  • add validation on all athena commands based on department id

Testing

  • Local
    • athena get appointments properly filters on department id
    • athena sync patient works with department IDs empty
    • athena sync blocks with department IDs non-empty and not matching department id
    • athena sync allows with department IDs non-empty and matching department id
  • Staging
    • athena get appointments properly filters on department id
    • athena sync patient works with department IDs empty
    • athena sync blocks with department IDs non-empty and not matching department id
    • athena sync allows with department IDs non-empty and matching department id
  • Sandbox
    • N/A
  • Production
    • athena get appointments properly filters on department id
    • athena sync patient works with department IDs empty
    • athena sync blocks with department IDs non-empty and not matching department id
    • athena sync allows with department IDs non-empty and matching department id

Release Plan

  • Merge this

Summary by CodeRabbit

  • New Features

    • Added a reusable department validation and a helper to retrieve/parse EHR secondary mappings.
  • Bug Fixes

    • Upfront department validation for Athena patient sync and all Athena write-backs so invalid dept/patient combos fail fast with clearer errors.
  • Refactor

    • Standardized secondary-mapping retrieval/parsing for webhook subscriptions (Elation, Healthie).
    • Added a guard to skip mapping lookups for EHRs that don’t support secondary mappings.
  • Chores

    • Updated EHR mapping types and added a typed pairing for environment + credentials.

Ref: ENG-000

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

coderabbitai bot commented Aug 28, 2025

Walkthrough

Adds a generic CX-mapping parsing helper and department-id validation, applies that validation to Athena sync and multiple Athena write-back commands, refactors Elation/Healthie webhook subscription flows to use the new helper, and updates EHR mapping/type surfaces and guards for sources without secondary mappings.

Changes

Cohort / File(s) Summary
Athena write-back validations
`packages/api/src/external/ehr/athenahealth/command/write-back/*(allergy
condition
Athena patient sync validation
packages/api/src/external/ehr/athenahealth/command/sync-patient.ts
Import and invoke validateDepartmentId(...) at function start to pre-validate department/practice/patient IDs before Athena API usage.
Athena shared utilities
packages/api/src/external/ehr/athenahealth/shared.ts
Add exported validateDepartmentId which uses getCxMappingAndParsedSecondaryMappings<AthenaSecondaryMappings> to fetch parsed secondary mappings and throws BadRequestError if mappings specify department IDs and the provided athenaDepartmentId is not present.
Shared CX mapping helper
packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
New generic helper getCxMappingAndParsedSecondaryMappings<T extends EhrCxMappingSecondaryMappings>({ ehr, practiceId }) that fetches the CX mapping, ensures secondaryMappings exists, parses it with the per-EHR schema, and returns { parsedSecondaryMappings, cxMapping }.
Elation webhook subscription refactor
packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
Replace manual CX mapping retrieval/parsing with getCxMappingAndParsedSecondaryMappings<ElationSecondaryMappings> and merge parsedSecondaryMappings / parsedSecondaryMappings.webhooks into mapping updates.
Healthie webhook subscription refactor
packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
Replace manual retrieval/parsing with getCxMappingAndParsedSecondaryMappings<HealthieSecondaryMappings> and use returned parsedSecondaryMappings when updating mappings/webhooks.
Core EHR mapping types
packages/core/src/external/ehr/mappings.ts
Add ehrSourceWithSecondaryMappings array, EhrSourceWithSecondaryMappings type, isEhrSourceWithSecondaryMappings guard; constrain ehrCxMappingSecondaryMappingsSchemaMap keys to those sources; expose ehrCxMappingSecondaryMappingsSchemaMapGeneral that includes eclinicalworks: undefined.
Domain CX mapping schema map
packages/api/src/domain/cx-mapping.ts
Use ehrCxMappingSecondaryMappingsSchemaMapGeneral when building secondaryMappingsSchemaMap (import updated); no signature changes.
EHR environment types
packages/core/src/external/ehr/environment.ts
Add generic type EhrEnvAndClientCredentials<Env extends EhrEnv> pairing environment with client credentials.
Write-back bundle filter guard
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
Add early guard isEhrSourceWithSecondaryMappings(ehr) in getWriteBackFilters to short-circuit for EHRs without secondary mappings, avoiding schema lookup for unsupported sources.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Cmd as Write/Sync Command
  participant Shared as validateDepartmentId
  participant Athena as Athena API

  Caller->>Cmd: invoke(...cxId, practiceId, patientId, departmentId)
  Cmd->>Shared: validateDepartmentId({ cxId, athenaPracticeId, athenaPatientId, athenaDepartmentId })
  alt department not allowed
    Shared-->>Cmd: throw BadRequestError
    Cmd-->>Caller: error
  else valid
    Shared-->>Cmd: ok
    Cmd->>Athena: create client / perform API call
    Athena-->>Cmd: result
    Cmd-->>Caller: result
  end
Loading
sequenceDiagram
  autonumber
  actor Svc as Webhook Setup (Elation/Healthie)
  participant Helper as getCxMappingAndParsedSecondaryMappings
  participant Map as CX Mapping Store
  participant EHR as EHR API

  Svc->>Helper: get<EHRSecondaryMappings>({ ehr, practiceId })
  Helper->>Map: getCxMappingOrFail({ source: ehr, externalId: practiceId })
  Map-->>Helper: cxMapping
  Helper->>Helper: parse secondaryMappings via schema
  Helper-->>Svc: { parsedSecondaryMappings, cxMapping }

  Svc->>EHR: create client and subscribe(...)
  EHR-->>Svc: subscription
  Svc->>Map: setSecondaryMappingsOnCxMappingById(merge parsedSecondaryMappings + webhooks)
  Map-->>Svc: updated mapping
  Svc-->>Caller: return subscription
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 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.


📜 Recent 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 519966f and 4bf6759.

📒 Files selected for processing (1)
  • packages/api/src/external/ehr/athenahealth/shared.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/external/ehr/athenahealth/shared.ts
⏰ 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). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch eng-000-enforce-athena-department-ids

🪧 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

🧹 Nitpick comments (8)
packages/core/src/external/ehr/mappings.ts (1)

20-21: Prefer deriving the excluded literal from EhrSources to avoid typo drift.

Using the raw string risks divergence if the constant value ever changes.

Apply:

-export type EhrSourceWithSecondaryMappings = Exclude<EhrSource, "eclinicalworks">;
+export type EhrSourceWithSecondaryMappings = Exclude<
+  EhrSource,
+  (typeof EhrSources)["eclinicalworks"]
+>;
packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (1)

18-18: Add unit tests for validateDepartmentId paths
All Athena write-back commands already invoke validateDepartmentId. Introduce lightweight unit tests covering both allowed and blocked department‐ID scenarios to ensure fail‐fast behavior.

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

18-18: Fail-fast validation reads well and avoids unnecessary client instantiation.

If this is called in batches, consider caching parsed secondary mappings inside validateDepartmentId to reduce repeated fetch/parse work.

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

18-18: Early validation before API usage is correct.

Consider test coverage for both empty and non-empty departmentIds config.

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

37-50: Add compensating cleanup on mapping update failure.

If updating CX mappings fails after creating the subscription, you may orphan a webhook in Healthie. Consider wrapping the update in try/catch and deleting the just-created subscription on failure.

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

27-41: Consider compensating delete on failure.

If setSecondaryMappingsOnCxMappingById throws after subscription creation, you’ll leave an orphaned webhook in Elation. Add a cleanup step on failure.

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

10-16: Fix JSDoc to match behavior.

This helper only supports EHRs with secondary mappings and throws if none are present; it never returns null.

 /**
- * Get the cx mapping and parsed secondary mappings for a given practice ID for all EHRs
+ * Get the cx mapping and parsed secondary mappings for a given practice ID
+ * (only EHRs with secondary mappings are supported).
@@
- * @returns The secondary mappings for the practice or null if the EHR doesn't have secondary mappings.
+ * @returns The cx mapping and parsed secondary mappings for the practice.
  */

34-38: Unchecked cast to T.

Safe in practice given the schema map, but consider tightening types with a keyed map per EhrSource to avoid as T.

📜 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 dc1a470 and 1c4be32.

📒 Files selected for processing (14)
  • 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/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/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/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.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
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/core/src/external/ehr/mappings.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/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/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.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
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/core/src/external/ehr/mappings.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/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/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/shared/command/mapping/get-cx-mapping-and-secondary-mappings.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.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
  • packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
🧠 Learnings (22)
📓 Common learnings
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.
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/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/lab.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/allergy.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts
  • packages/api/src/external/ehr/athenahealth/shared.ts
  • packages/api/src/external/ehr/athenahealth/command/write-back/condition.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-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/lab.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/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-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-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/write-back/condition.ts
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.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
  • packages/api/src/external/ehr/athenahealth/command/sync-patient.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
📚 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-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-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/api/src/external/ehr/athenahealth/command/sync-patient.ts
  • packages/core/src/external/ehr/mappings.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-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-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-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-05-30T03:42:53.380Z
Learnt from: keshavsaharia
PR: metriport/metriport#3885
File: packages/core/src/external/surescripts/schema/load.ts:65-71
Timestamp: 2025-05-30T03:42:53.380Z
Learning: In Surescripts schema files like `packages/core/src/external/surescripts/schema/load.ts`, the `key` property in field definitions is only used for direct one-to-one field mappings. When multiple fields are derived from the same source property but with different transformations (like extracting date vs. time portions), they should not have `key` properties as they represent transformations rather than direct mappings.

Applied to files:

  • packages/core/src/external/ehr/mappings.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
📚 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
📚 Learning: 2025-05-01T16:10:32.104Z
Learnt from: thomasyopes
PR: metriport/metriport#3771
File: packages/core/src/util/webhook.ts:60-66
Timestamp: 2025-05-01T16:10:32.104Z
Learning: Code explicitly copied from external documentation (like Healthie's webhook implementation) should not be modified to maintain compatibility with third-party systems.

Applied to files:

  • packages/api/src/external/ehr/healthie/command/subscribe-to-webhook.ts
🧬 Code graph analysis (12)
packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/command/write-back/vitals.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/command/write-back/note.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
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 (22-26)
  • EhrSourceWithSecondaryMappings (20-20)
  • ehrCxMappingSecondaryMappingsSchemaMap (27-34)
packages/api/src/domain/cx-mapping.ts (1)
  • CxMapping (27-27)
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/allergy.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/command/write-back/procedure.ts (1)
packages/api/src/external/ehr/athenahealth/shared.ts (1)
  • validateDepartmentId (50-78)
packages/api/src/external/ehr/athenahealth/shared.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/athenahealth/cx-mapping.ts (1)
  • AthenaSecondaryMappings (14-14)
packages/shared/src/index.ts (1)
  • BadRequestError (42-42)
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/core/src/external/ehr/mappings.ts (5)
packages/shared/src/interface/external/ehr/source.ts (1)
  • EhrSource (11-11)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • AthenaSecondaryMappings (14-14)
packages/shared/src/interface/external/ehr/canvas/cx-mapping.ts (1)
  • CanavsSecondaryMappings (5-5)
packages/shared/src/interface/external/ehr/elation/cx-mapping.ts (1)
  • ElationSecondaryMappings (23-23)
packages/shared/src/interface/external/ehr/healthie/cx-mapping.ts (1)
  • HealthieSecondaryMappings (24-24)
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). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (19)
packages/core/src/external/ehr/mappings.ts (1)

28-28: Replace all z.Schema usages with ZodSchema and enforce exhaustiveness

  • In packages/core/src/external/ehr/mappings.ts:
    • Remove the index signature using z.Schema and instead declare the map without an explicit type, adding
    import type { ZodSchema } from "zod";
    then append
    } satisfies Record<EhrSourceWithSecondaryMappings, ZodSchema<EhrCxMappingSecondaryMappings>>;
  • In packages/lambdas/src/shared/parse-body.ts (line 43): change
    export function parseBody<T>(schema: z.Schema<T>, )
    to
    export function parseBody<T>(schema: ZodSchema<T>, )
    and import the type from zod.
  • In packages/core/src/external/ehr/shared.ts (line 120): update
    schema: z.Schema<T>;
    to
    schema: ZodSchema<T>;
  • In packages/api/src/domain/cx-mapping.ts (line 16): replace
    export const secondaryMappingsSchemaMap: { [key in CxMappingSource]: z.Schema | undefined } = {}
    with a map using satisfies Record<CxMappingSource, ZodSchema | undefined> and import ZodSchema.
  • After making these changes, run
    rg -nP --type=ts '\bz\.Schema\b'
    to confirm no z.Schema remains.
⛔ Skipped due to learnings
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.
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.
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.
Learnt from: thomasyopes
PR: metriport/metriport#4164
File: packages/core/src/external/ehr/healthie/index.ts:1031-1076
Timestamp: 2025-07-09T17:18:28.920Z
Learning: In packages/core/src/external/ehr/healthie/index.ts, the `interpretation` field in `labObservationResult` is defined as a required enum in the Zod schema (`z.enum(["high", "low", "normal"])`), making null-safety checks unnecessary when calling `toTitleCase(labObservationResult.interpretation)`.
packages/api/src/external/ehr/athenahealth/command/write-back/immunization.ts (1)

3-3: Good addition: central preflight validation imported close to the client factory.

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

3-3: Consistent import with the shared validation—keeps the pattern uniform.

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

2-2: Import looks correct and aligns with other write-back modules.


19-19: Validation placement is correct.

No functional change beyond the guard—safe and clear.

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

3-3: Importing the shared validator here keeps behavior consistent across commands.

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

3-3: All Athena write-back and sync-patient handlers include preflight department validation — approved
Verified every handler imports and awaits validateDepartmentId before any API call.

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

3-3: Pre-validation pattern applied consistently — LGTM

Import + early await mirror other handlers; minimizes wasted client instantiation.

Also applies to: 18-18

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

3-3: Early department gating for allergy write-back — LGTM

Matches cross-file approach; no API side effects on invalid department.

Also applies to: 18-18

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

3-3: Lab write-back now gated by department — LGTM

Consistent with the PR’s enforcement strategy.

Also applies to: 18-18

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

14-14: Department validation added to sync flow — LGTM

Placing validation up front ensures sync is blocked when department isn’t allowed, per objectives.

Also applies to: 38-38


38-38: Confirm intended behavior for already-mapped patients

Validation runs before checking existing mappings; this will block returns for patients already mapped if their current department isn’t allowed. Confirm this is desired product behavior.

I can adjust the flow (validate only on first-time sync) if needed; say the word and I’ll draft a targeted diff and tests.

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

21-25: Good switch to the shared parsing helper.

Reduces duplication and centralizes schema validation. Generics keep types aligned with Healthie mappings.


41-49: Persisting webhooks via parsedSecondaryMappings looks correct.

Merging the parsed mappings and extending webhooks preserves existing entries while adding the new event.

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

50-78: All Athena flows pass a valid department ID
TypeScript enforces athenaDepartmentId as a required string in every validateDepartmentId call (in sync-patient.ts and all write-back commands), and appointments always include departmentid per prior learning.


50-78: Harden validation against optional/empty department IDs.

Guard for optional schemas and empty input to avoid runtime errors and clarify behavior.

Apply:

 export async function validateDepartmentId({
@@
 }): Promise<void> {
-  const { parsedSecondaryMappings } =
+  const { parsedSecondaryMappings } =
     await getCxMappingAndParsedSecondaryMappings<AthenaSecondaryMappings>({
       ehr: EhrSources.athena,
       practiceId: athenaPracticeId,
     });
-  const departmentIds = parsedSecondaryMappings.departmentIds;
-  if (departmentIds.length > 0 && !departmentIds.includes(athenaDepartmentId)) {
+  const departmentIds = parsedSecondaryMappings.departmentIds ?? [];
+  // If no department provided or no restriction configured, allow.
+  if (!athenaDepartmentId || departmentIds.length === 0) return;
+  if (!departmentIds.includes(athenaDepartmentId)) {
     throw new BadRequestError(
       "AthenaHealth patient is not in a department that is enabled for syncing",
       {
         cxId,
         athenaPracticeId,
         athenaPatientId,
         athenaDepartmentId,
       }
     );
   }
 }
⛔ Skipped due to learnings
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.
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.
packages/api/src/external/ehr/elation/command/subscribe-to-webhook.ts (2)

20-24: Nice consolidation to the generic mapping helper.

Keeps Elation webhook setup consistent with other EHRs and centralizes schema parsing.


31-39: Webhook mappings merge is correct.

Merges existing parsed mappings and extends the webhooks map with the new entry without clobbering others.

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

25-38: Helper logic LGTM.

Lookup, strict error on missing mappings, and schema-validated parse are appropriate here.

Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.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: 0

🧹 Nitpick comments (2)
packages/api/src/domain/cx-mapping.ts (2)

6-6: Import looks good; minor nit: avoid copying ehrSources.

Use the shared constant directly to skip the spread + cast.

Apply:

-const cxMappingSource = [...ehrSources] as const;
+const cxMappingSource = ehrSources;

14-15: Prefer undefined over null for internal types per guidelines.

This domain type is internal; align with our “no nulls inside the app” rule.

-export type CxMappingSecondaryMappings = EhrCxMappingSecondaryMappings | null;
+export type CxMappingSecondaryMappings = EhrCxMappingSecondaryMappings | undefined;

Note: this may require follow-ups where null is currently used.

📜 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 1c4be32 and f9d3ee2.

📒 Files selected for processing (2)
  • packages/api/src/domain/cx-mapping.ts (1 hunks)
  • packages/api/src/external/ehr/athenahealth/shared.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/external/ehr/athenahealth/shared.ts
🧰 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/domain/cx-mapping.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use types whenever possible

Files:

  • packages/api/src/domain/cx-mapping.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/domain/cx-mapping.ts
🧠 Learnings (1)
📓 Common learnings
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.
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.
🧬 Code graph analysis (1)
packages/api/src/domain/cx-mapping.ts (2)
packages/shared/src/interface/external/ehr/source.ts (1)
  • ehrSources (10-10)
packages/core/src/external/ehr/mappings.ts (2)
  • EhrCxMappingSecondaryMappings (22-26)
  • ehrCxMappingSecondaryMappingsSchemaMap (27-34)
⏰ 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). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/api/src/domain/cx-mapping.ts (1)

15-18: Scripts provided for verification. Once results are available, we can verify TS version support for satisfies and the existing schema map types to finalize the review comment.

Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.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: 0

Caution

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

⚠️ Outside diff range comments (3)
packages/core/src/external/ehr/environment.ts (3)

9-10: Constrain env unions by credential model (keep OAuth vs API-key separation).

Per the existing type-guard and architecture, restrict env unions to prevent mixing API-key providers with client-credential flows.

Apply after Line 10:

+export type EhrEnvWithClientCredentials = AthenaEnv | ElationEnv | CanvasEnv;
+export type EhrEnvWithApiKey = HealthieEnv | EClinicalWorksEnv;

23-27: EhrEnvAndClientCredentials is too broad; tighten Env and make fields readonly.

Currently Env extends EhrEnv admits Healthie/eCW envs (API key auth). Constrain to OAuth envs and mark fields as readonly to encourage immutability. Also, avoid widening secret handling surface area; see optional hardening below.

-export type EhrEnvAndClientCredentials<Env extends EhrEnv> = {
-  environment: Env;
-  clientKey: string;
-  clientSecret: string;
-};
+export type EhrEnvAndClientCredentials<Env extends EhrEnvWithClientCredentials> = {
+  readonly environment: Env;
+  readonly clientKey: string;
+  readonly clientSecret: string;
+};

Optional hardening (incremental): introduce a branded Secret type to reduce accidental logging/serialization and use it for clientSecret:

// near the other type aliases
export type SecretString = string & { readonly __brand: "SecretString" };
// then change: clientSecret: SecretString;

34-37: Symmetry: Restrict API-key helper to API-key envs and make fields readonly.

Prevents misuse (e.g., pairing CanvasEnv with an apiKey).

-export type EhrEnvAndApiKey<Env extends EhrEnv> = {
-  environment: Env;
-  apiKey: string;
-};
+export type EhrEnvAndApiKey<Env extends EhrEnvWithApiKey> = {
+  readonly environment: Env;
+  readonly apiKey: string;
+};
🧹 Nitpick comments (3)
packages/core/src/external/ehr/environment.ts (2)

22-22: Formatting nit: extra blank line is fine.


29-33: Make twoLeggedAuthTokenInfo an optional property
Change the declaration from

twoLeggedAuthTokenInfo: JwtTokenInfo | undefined;

to

twoLeggedAuthTokenInfo?: JwtTokenInfo;

and leave the generic bound as Env extends EhrEnv—don’t introduce EhrEnvWithClientCredentials (it isn’t defined).

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

48-53: Avoid future drift: build the general map programmatically.

This prevents missing keys when new EHR sources are added and keeps the two maps in sync.

Apply this diff:

-export const ehrCxMappingSecondaryMappingsSchemaMapGeneral: {
-  [key in EhrSource]: z.Schema<EhrCxMappingSecondaryMappings> | undefined;
-} = {
-  ...ehrCxMappingSecondaryMappingsSchemaMap,
-  [EhrSources.eclinicalworks]: undefined,
-};
+export const ehrCxMappingSecondaryMappingsSchemaMapGeneral: {
+  [key in EhrSource]: z.Schema<EhrCxMappingSecondaryMappings> | undefined;
+} = Object.values(EhrSources).reduce(
+  (acc, source) => {
+    acc[source as EhrSource] =
+      ehrCxMappingSecondaryMappingsSchemaMap[
+        source as EhrSourceWithSecondaryMappings
+      ];
+    return acc;
+  },
+  {} as { [key in EhrSource]: z.Schema<EhrCxMappingSecondaryMappings> | undefined }
+);
📜 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 f9d3ee2 and 9952649.

📒 Files selected for processing (4)
  • packages/api/src/domain/cx-mapping.ts (2 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)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/domain/cx-mapping.ts
🧰 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/core/src/external/ehr/environment.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/ehr/environment.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.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/core/src/external/ehr/environment.ts
  • packages/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
🧠 Learnings (12)
📓 Common learnings
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-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-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/environment.ts
  • packages/core/src/external/ehr/mappings.ts
📚 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/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.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/core/src/external/ehr/mappings.ts
  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-04-21T03:47:54.332Z
Learnt from: lucasdellabella
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/resource-diff/steps/start/ehr-start-resource-diff-local.ts:0-0
Timestamp: 2025-04-21T03:47:54.332Z
Learning: In the EHR resource diff implementation, bundles should always have an `entry` property, and code should be allowed to throw if `entry` is missing rather than using null-coalescing operators, as this represents an unexpected state that should be caught and addressed.

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-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-27T23:19:52.647Z
Learnt from: thomasyopes
PR: metriport/metriport#4478
File: packages/core/src/external/ehr/healthie/command/get-resource-bundle-by-resource-id.ts:19-20
Timestamp: 2025-08-27T23:19:52.647Z
Learning: While Healthie is technically included in the ehrGetBundleByResourceTypeMap routing infrastructure, the refresh job flow that uses useCachedBundle: false is never actually invoked for Healthie EHR sources in practice. The BadRequestError validation in Healthie's getResourceBundleByResourceId method is appropriate for its real-world usage patterns.

Applied to files:

  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-08-27T23:18:38.619Z
Learnt from: thomasyopes
PR: metriport/metriport#4478
File: packages/infra/lib/ehr-nested-stack.ts:183-188
Timestamp: 2025-08-27T23:18:38.619Z
Learning: In packages/infra/lib/ehr-nested-stack.ts, the writeBackResourceDiffBundles queue is intentionally configured with maxReceiveCount: 1 and visibilityTimeout: Duration.seconds(5) for fail-fast behavior, as confirmed by thomasyopes.

Applied to files:

  • packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
📚 Learning: 2025-08-11T23:36:57.573Z
Learnt from: thomasyopes
PR: metriport/metriport#4315
File: packages/core/src/external/ehr/healthie/index.ts:622-657
Timestamp: 2025-08-11T23:36:57.573Z
Learning: In the Healthie EHR integration's `getResourceBundleByResourceId` method, returning an empty bundle when a resource doesn't exist (by using `fetchResourcesFromEhr: () => Promise.resolve([])`) is intentional design. Empty bundles are an acceptable state for non-existent resources rather than throwing NotFoundError.

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
🧬 Code graph analysis (2)
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/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)
⏰ 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). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/core/src/external/ehr/mappings.ts (2)

20-31: Solid type-guard surface; minimal and clear.

The curated allowlist + runtime type-guard is fine and consistent with existing patterns.


39-46: Schema map narrowing is correct.

Keying by EhrSourceWithSecondaryMappings removes undefined branches and tightens types. LGTM.

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

41-45: Correct imports for new gating.

Importing isEhrSourceWithSecondaryMappings and the narrowed schema map is appropriate.


302-327: Verify early-return bypass of filters for unmapped EHRs

  • getWriteBackFilters returns undefined for sources without secondary mappings (e.g., eClinicalWorks), causing no write-back filters to be applied. Confirm this aligns with intended behavior and that no write-back handlers exist for these sources.
  • (Optional) Add a debug log in getWriteBackFilters to make this branch observable.

Thomas Yopes added 3 commits August 28, 2025 16:52
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>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

50-81: Add focused unit tests for validateDepartmentId.

Cover: (1) empty departmentIds → allows; (2) non-empty without match → throws BadRequestError; (3) non-empty with match → allows; (4) malformed IDs → throws MetriportError.

I can draft tests with mocked getCxMappingAndParsedSecondaryMappings and table-driven cases if you want.

📜 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 d7e19a6 and 519966f.

📒 Files selected for processing (1)
  • packages/api/src/external/ehr/athenahealth/shared.ts (2 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/athenahealth/shared.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use types whenever possible

Files:

  • packages/api/src/external/ehr/athenahealth/shared.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/athenahealth/shared.ts
🧠 Learnings (6)
📓 Common learnings
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-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/shared.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
📚 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/shared.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-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/shared.ts
🧬 Code graph analysis (1)
packages/api/src/external/ehr/athenahealth/shared.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/athenahealth/cx-mapping.ts (1)
  • AthenaSecondaryMappings (14-14)
packages/shared/src/index.ts (1)
  • BadRequestError (42-42)
⏰ 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). (2)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (2)
packages/api/src/external/ehr/athenahealth/shared.ts (2)

4-8: Imports look correct and aligned with usage.

Good addition of EhrSources, AthenaSecondaryMappings, and the shared mapping helper.


50-66: Right place to enforce dept validation; failure mode aligns with mapping hard-fail stance.

Fetching parsedSecondaryMappings up front and validating before commands is solid.

});
const departmentIds = parsedSecondaryMappings.departmentIds;
const strippedPracticeId = athenaPracticeId.replace("a-1.Practice-", "");
const strippedDeparmentId = athenaDepartmentId.replace(`a-${strippedPracticeId}.Department-`, "");
Copy link
Contributor

Choose a reason for hiding this comment

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

This was the only part of the review I wasn't able to thoroughly verify, but in my experience with Athena this is consistent with how I remember the key formats to be.

Ref: ENG-000

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
@thomasyopes thomasyopes added this pull request to the merge queue Aug 29, 2025
Merged via the queue into develop with commit 8394e8d Aug 29, 2025
16 checks passed
@thomasyopes thomasyopes deleted the eng-000-enforce-athena-department-ids branch August 29, 2025 16:15
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