Skip to content

Conversation

thomasyopes
Copy link
Contributor

@thomasyopes thomasyopes commented Aug 11, 2025

Ref: ENG-634

Issues:

Description

  • only capture account type ID and save encounter summaries if required
  • only save encounter summary if one is not already saved
  • when contributing, remove encounters and resources that reference the encounter if needed
  • when contribution, try to upload the encounter summary if requested

Testing

  • Local
    • conditionals for summaries / appointment type ID work
    • removing bad encounters creates clean bundle
  • Staging
    • conditionals for summaries / appointment type ID work
    • removing bad encounters creates clean bundle
    • encounter summary docs are uploaded correctly
  • Sandbox
    • N/A
  • Production
    • conditionals for summaries / appointment type ID work
    • removing bad encounters creates clean bundle
    • encounter summary docs are uploaded correctly

Release Plan

  • update CX secondary mappings
  • Merge this

Summary by CodeRabbit

  • New Features

    • Centralized medical document upload: creates temporary document references and returns presigned upload URLs.
    • EHR document retrieval with presigned download URLs.
    • New bundle variant to track removed data contributions.
  • Enhancements

    • Athena: opt-in encounter summaries, appointment-type attachment, and exclusion of encounters by blacklisted appointment types.
    • New Athena secondary mapping flags for blacklist and summaries.
    • Healthie added to default patient mapping sources.
  • Refactor

    • Medical document upload routes delegate to centralized handler.
  • Chores

    • Added an extra local build step during development image creation.
  • Documentation

    • Upload example updated to show fileContent and include Content-Type: application/pdf.

Ref: ENG-634

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

linear bot commented Aug 11, 2025

Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

Adds a centralized command to create/upsert a FHIR DocumentReference and return a presigned S3 upload URL; routes delegate to that command. Introduces S3-backed EHR document fetch/pre-signed-url APIs, Athena secondary-mapping driven encounter filters and optional encounter-summary uploads, a new resource-diff bundle type/key, several helper renames, and small API/array expansions.

Changes

Cohort / File(s) Summary
Medical document upload orchestration
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts, packages/api/src/routes/medical/document.ts
New getUploadUrlAndCreateDocRef composes a DocumentReference (uses org lookup), upserts it to FHIR, and obtains a presigned S3 PUT URL; route handlers validate payload and delegate to this command, removing inline S3/FHIR orchestration.
Athena enhancements & secondary mappings
packages/core/src/external/ehr/athenahealth/index.ts, .../command/get-bundle-by-resource-type.ts, .../command/get-resource-bundle-by-resource-id.ts, packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts, packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts
Adds athena secondary mappings integration, introduces flags attachAppointmentType and fetchEncounterSummary; supports appointment-type blacklists (prune Encounters + referencing resources) and optional encounter-summary creation/upload; schema extended with contributionEncounterAppointmentTypesBlacklist and contributionEncounterSummariesEnabled.
EHR document fetch utilities & related tweaks
packages/core/src/external/ehr/document/command/fetch-document.ts, packages/core/src/external/ehr/document/command/create-or-replace-document.ts, packages/core/src/external/ehr/bundle/command/fetch-bundle.ts
Adds fetchDocument and fetchDocumentPreSignedUrl for S3-backed document retrieval and signed URLs (with rich error context); minor guard formatting change and added parameters (s3BucketName / key) to bundle fetch APIs.
Bundle type additions
packages/core/src/external/ehr/bundle/bundle-shared.ts
Adds BundleType.RESOURCE_DIFF_DATA_CONTRIBUTION_REMOVED, createFileKeyResourceDiffDataContributionRemoved, updates isResourceDiffBundleType and createKeyMap to support the new variant.
FHIR helper rename / references
packages/core/src/external/fhir/shared/index.ts, packages/core/src/external/fhir/shared/references.ts
Renames exported helper buildEntryReferencebuildResourceReference and updates call sites to use the new name.
Patient mapping sources
packages/api/src/command/mapping/patient.ts
Adds EhrSources.healthie to the defaultSources used when computing patient source mappings.
Unit conversion
packages/core/src/external/ehr/unit-conversion.ts
Expands isBpm to accept additional respiratory-rate unit strings ("breaths/min", "breaths per minute").
Dev image build
packages/api/Dockerfile.dev
Adds a build step for packages/fhir-sdk during the dev Docker image build.
Docs
docs/medical-api/api-reference/document/post-upload-url.mdx
Example updated to use fileContent variable and include Content-Type: application/pdf in the PUT example; minor wording tweak.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API_Route as API Route (/document)
  participant UploadCmd as getUploadUrlAndCreateDocRef
  participant OrgSvc as Organization Service
  participant FHIR
  participant S3

  Client->>API_Route: POST /upload-url { cxId, patientId, docRefDraft }
  API_Route->>UploadCmd: cxId, patientId, docRefDraft
  UploadCmd->>OrgSvc: getOrganizationOrFail(cxId)
  UploadCmd->>UploadCmd: compose DocumentReference (docRefId, s3Key, bucket)
  par Parallel
    UploadCmd->>FHIR: upsert DocumentReference
    UploadCmd->>S3: get presigned PUT URL (bucket,key)
  end
  UploadCmd-->>API_Route: { documentReferenceId, uploadUrl }
  API_Route-->>Client: { documentReferenceId, uploadUrl }
Loading
sequenceDiagram
  participant AthenaClient
  participant CxMapping as CxMapping Service
  participant BundleFetcher
  participant ContributionFlow
  participant DocSvc as Document Service

  AthenaClient->>CxMapping: getSecondaryMappings(practiceId)
  CxMapping-->>AthenaClient: secondaryMappings (blacklist?, summaries?)
  AthenaClient->>BundleFetcher: fetch bundle { attachAppointmentType?, fetchEncounterSummary? }
  BundleFetcher-->>AthenaClient: Bundle (with optional appointment ext. and/or ensured summaries)
  alt blacklist present
    ContributionFlow->>ContributionFlow: remove blacklisted Encounters + referencing resources
  end
  alt summaries enabled
    ContributionFlow->>DocSvc: fetch predecessor HTML, create DocRefs, upload summaries
  end
  ContributionFlow-->>AthenaClient: continue contribution processing
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 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 unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch eng-634-athena-contribution-update

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

Thomas Yopes added 8 commits August 11, 2025 11:33
Ref: ENG-634

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

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

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

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

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

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

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

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

🧹 Nitpick comments (9)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)

53-55: Guard-with-braces change is fine; minor nit on log message earlier

The added braces improve readability. Minor nit: the log prefix on Line 49 still says "createOrReplaceCcda" while this function handles generic documents. Consider updating it for clarity.

packages/core/src/external/ehr/bundle/command/fetch-bundle.ts (1)

164-166: Nit: error message says 'Ehr' but the operation is S3

For consistency with fetchBundle(), prefer "S3" in the message.

Apply:

-    const msg = "Failure while fetching bundle pre-signed URL @ Ehr";
+    const msg = "Failure while fetching bundle pre-signed URL @ S3";
packages/core/src/external/ehr/document/command/fetch-document.ts (4)

52-52: Fix log prefix copy/paste: 'fetchBundle' -> 'fetchDocument'

The log label is misleading for this function.

-  const { log } = out(`Ehr fetchBundle - ehr ${ehr} cxId ${cxId} ehrPatientId ${ehrPatientId}`);
+  const { log } = out(
+    `Ehr fetchDocument - ehr ${ehr} cxId ${cxId} ehrPatientId ${ehrPatientId}`
+  );

81-95: Add documentType to error context for better diagnostics

Parity with fetchDocumentPreSignedUrl and easier triage for invalid/misrouted document types.

     throw new MetriportError(msg, error, {
       ehr,
       cxId,
       metriportPatientId,
       ehrPatientId,
       resourceType,
       jobId,
       key,
       s3BucketName,
       extension: createKeyAndExtension.extension,
+      documentType,
       context: "ehr.fetchDocument",
     });

152-152: Nit: error message says 'Ehr' but this is an S3 operation

Align wording for clarity.

-    const msg = "Failure while fetching document pre-signed URL @ Ehr";
+    const msg = "Failure while fetching document pre-signed URL @ S3";

154-165: Include extension in error context for observability parity

Pre-signed URL failures benefit from knowing the desired content type.

     throw new MetriportError(msg, error, {
       ehr,
       cxId,
       metriportPatientId,
       ehrPatientId,
       documentType,
       resourceType,
       jobId,
       key,
       s3BucketName,
+      extension: createKeyAndExtension.extension,
       context: "ehr.fetchDocumentPreSignedUrl",
     });
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (2)

221-272: Consider increasing parallel executions for better performance.

The function is set to execute with numberOfParallelExecutions: 1, which means uploads happen sequentially. Consider increasing this value for better performance, especially when dealing with multiple encounter summaries.

 await executeAsynchronously(
   encounterSummaries,
   async encounterSummary => {
     // ... existing logic ...
   },
   {
-    numberOfParallelExecutions: 1,
+    numberOfParallelExecutions: 5, // Or use a constant like parallelRequests
   }
 );

64-91: Consider extracting Athena-specific logic to a separate function.

The Athena-specific logic is growing complex within the main contribution function. Consider extracting it to improve readability and testability.

+async function processAthenaContribution({
+  bundle,
+  cxId,
+  metriportPatientId,
+  ehrPatientId,
+  ehr,
+  athenaPracticeId,
+}: {
+  bundle: Bundle;
+  cxId: string;
+  metriportPatientId: string;
+  ehrPatientId: string;
+  ehr: EhrSources;
+  athenaPracticeId: string;
+}): Promise<Bundle> {
+  const cxMappingLookupParams = { externalId: athenaPracticeId, source: EhrSources.athena };
+  const cxMapping = await getCxMappingOrFail(cxMappingLookupParams);
+  if (!cxMapping.secondaryMappings) {
+    throw new MetriportError("Athena secondary mappings not found", undefined, {
+      externalId: athenaPracticeId,
+      source: EhrSources.athena,
+    });
+  }
+  const secondaryMappings = athenaSecondaryMappingsSchema.parse(cxMapping.secondaryMappings);
+  let processedBundle = bundle;
+  if (secondaryMappings.contributionEncounterAppointmentTypesBlacklist) {
+    processedBundle = await removeEncounterEntriesWithBlacklistedAppointmentType({
+      bundle: processedBundle,
+      blacklistedAppointmentTypes:
+        secondaryMappings.contributionEncounterAppointmentTypesBlacklist,
+    });
+  }
+  if (secondaryMappings.contributionEncounterSummariesEnabled) {
+    await uploadEncounterSummaries({
+      ehr,
+      bundle: processedBundle,
+      cxId,
+      metriportPatientId,
+      ehrPatientId,
+    });
+  }
+  return processedBundle;
+}

 export async function contributeResourceDiffBundle({
   // ... parameters ...
 }): Promise<void> {
   // ... existing code up to line 63 ...
   if (!bundle?.bundle.entry || bundle.bundle.entry.length < 1) return;
   if (await isAthenaCustomFieldsEnabledForCx(cxId)) {
     const athenaPracticeId = getAthenaPracticeIdFromPatientId(patientMapping.externalId);
-    const cxMappingLookupParams = { externalId: athenaPracticeId, source: EhrSources.athena };
-    const cxMapping = await getCxMappingOrFail(cxMappingLookupParams);
-    // ... rest of the Athena-specific logic ...
+    bundle.bundle = await processAthenaContribution({
+      bundle: bundle.bundle,
+      cxId,
+      metriportPatientId,
+      ehrPatientId,
+      ehr,
+      athenaPracticeId,
+    });
   }
   await handleDataContribution({
     // ... existing parameters ...
   });
 }
packages/core/src/external/ehr/athenahealth/index.ts (1)

1939-2053: Consider refactoring the complex nested conditionals for better readability.

The method has grown complex with nested conditionals for handling appointment types and encounter summaries. Consider extracting the logic into separate helper methods.

+  private async attachAppointmentTypeToEncounter(
+    entry: EhrStrictFhirResourceBundleEntry,
+    cxId: string,
+    athenaPatientId: string,
+    encounterId: string
+  ): Promise<Encounter> {
+    const encounter = await this.getEncounter({
+      cxId,
+      patientId: athenaPatientId,
+      encounterId,
+    });
+    const appointment = await this.getAppointment({
+      cxId,
+      patientId: athenaPatientId,
+      appointmentId: encounter.appointmentid,
+    });
+    entry.resource.extension = [
+      ...(entry.resource.extension ?? []),
+      {
+        url: encounterAppointmentExtensionUrl,
+        valueString: appointment.appointmenttypeid,
+      },
+    ];
+    return encounter;
+  }
+
+  private async processEncounterSummary(
+    encounter: Encounter,
+    cxId: string,
+    metriportPatientId: string,
+    athenaPatientId: string,
+    encounterId: string
+  ): Promise<void> {
+    if (encounter.status !== athenaClosedStatus) return;
+    
+    const existingEncounterSummary = await fetchDocument({
+      ehr: EhrSources.athena,
+      cxId,
+      metriportPatientId,
+      ehrPatientId: athenaPatientId,
+      documentType: DocumentType.HTML,
+      resourceType: "Encounter",
+      resourceId: encounterId,
+    });
+    if (existingEncounterSummary) return;
+    
+    const encounterSummary = await this.getEncounterSummary({
+      cxId,
+      patientId: athenaPatientId,
+      encounterId,
+    });
+    await createOrReplaceDocument({
+      ehr: EhrSources.athena,
+      cxId,
+      metriportPatientId,
+      ehrPatientId: athenaPatientId,
+      documentType: DocumentType.HTML,
+      payload: encounterSummary,
+      resourceType: "Encounter",
+      resourceId: encounterId,
+    });
+  }

   private async dangerouslyAdjustEncountersInBundle({
     // ... parameters ...
   }): Promise<void> {
     // ... existing code up to line 1966 ...
         const encounterId = entry.resource.id;
         try {
           let encounter: Encounter | undefined;
           if (attachAppointmentType) {
-            encounter = await this.getEncounter({
-              cxId,
-              patientId: athenaPatientId,
-              encounterId,
-            });
-            const appointment = await this.getAppointment({
-              cxId,
-              patientId: athenaPatientId,
-              appointmentId: encounter.appointmentid,
-            });
-            entry.resource.extension = [
-              ...(entry.resource.extension ?? []),
-              {
-                url: encounterAppointmentExtensionUrl,
-                valueString: appointment.appointmenttypeid,
-              },
-            ];
+            encounter = await this.attachAppointmentTypeToEncounter(
+              entry,
+              cxId,
+              athenaPatientId,
+              encounterId
+            );
           }
           if (fetchEncounterSummary) {
             encounter =
               encounter ??
               (await this.getEncounter({
                 cxId,
                 patientId: athenaPatientId,
                 encounterId,
               }));
-            if (encounter.status === athenaClosedStatus) {
-              // ... rest of the summary processing logic ...
-            }
+            await this.processEncounterSummary(
+              encounter,
+              cxId,
+              metriportPatientId,
+              athenaPatientId,
+              encounterId
+            );
           }
         } catch (error) {
           // ... error handling ...
         }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c941c3 and 4cb6cba.

📒 Files selected for processing (9)
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1 hunks)
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (3 hunks)
  • packages/api/src/routes/medical/document.ts (4 hunks)
  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts (2 hunks)
  • packages/core/src/external/ehr/athenahealth/index.ts (7 hunks)
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.ts (2 hunks)
  • packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1 hunks)
  • packages/core/src/external/ehr/document/command/fetch-document.ts (1 hunks)
  • packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.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/core/src/external/ehr/document/command/create-or-replace-document.ts
  • packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts
  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts
  • packages/core/src/external/ehr/document/command/fetch-document.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/routes/medical/document.ts
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/ehr/document/command/create-or-replace-document.ts
  • packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts
  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts
  • packages/core/src/external/ehr/document/command/fetch-document.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/routes/medical/document.ts
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.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/document/command/create-or-replace-document.ts
  • packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts
  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts
  • packages/core/src/external/ehr/document/command/fetch-document.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/routes/medical/document.ts
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.ts
🧠 Learnings (16)
📚 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/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts
  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/api/src/routes/medical/document.ts
📚 Learning: 2025-05-28T19:22:09.281Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:27-49
Timestamp: 2025-05-28T19:22:09.281Z
Learning: In packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-04-21T17:07:30.574Z
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/core/src/external/ehr/document/command/fetch-document.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
📚 Learning: 2025-05-28T19:20:47.442Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:0-0
Timestamp: 2025-05-28T19:20:47.442Z
Learning: In packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/athenahealth/command/get-bundle-by-resource-type.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/command/medical/document/get-upload-url-and-create-doc-ref.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/api/src/routes/medical/document.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/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T02:32:27.527Z
Learnt from: lucasdellabella
PR: metriport/metriport#3907
File: packages/core/src/external/fhir/adt-encounters.ts:118-143
Timestamp: 2025-05-28T02:32:27.527Z
Learning: In packages/core/src/external/fhir/adt-encounters.ts, conversion bundles stored by saveAdtConversionBundle do not require version ID assertions or versioning, unlike the sourced encounter data stored by putAdtSourcedEncounter which does require versionId validation.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.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/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-07-27T00:40:32.149Z
Learnt from: leite08
PR: metriport/metriport#4255
File: packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts:19-20
Timestamp: 2025-07-27T00:40:32.149Z
Learning: User leite08 acknowledged the duplicate inputBundle spread issue in packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts and will fix it in a follow-up PR rather than in the current patch release PR #4255.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-04-23T21:19:08.443Z
Learnt from: thomasyopes
PR: metriport/metriport#3709
File: packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle.ts:62-73
Timestamp: 2025-04-23T21:19:08.443Z
Learning: In the metriport codebase, factory functions like `fetchResourceDiffBundle` intentionally let errors bubble up to the caller rather than handling them at the factory function level. This is a consistent pattern across factory implementations.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-08T19:41:36.533Z
Learnt from: thomasyopes
PR: metriport/metriport#3788
File: packages/api/src/external/ehr/shared/utils/bundle.ts:83-93
Timestamp: 2025-05-08T19:41:36.533Z
Learning: In the Metriport codebase, the team prefers to let errors bubble up naturally in some cases rather than adding explicit error handling at every layer, as demonstrated in the refreshEhrBundle function in the bundle.ts file.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-25T01:55:42.627Z
Learnt from: thomasyopes
PR: metriport/metriport#4090
File: packages/core/src/command/conversion-fhir/conversion-fhir-cloud.ts:30-30
Timestamp: 2025-06-25T01:55:42.627Z
Learning: In packages/core/src/command/conversion-fhir/conversion-fhir-cloud.ts, the FHIR converter Lambda endpoint is controlled by the team and guaranteed to return valid JSON in the expected Bundle<Resource> format, so JSON.parse() error handling and type validation are not necessary for this specific endpoint.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-13T20:24:16.668Z
Learnt from: thomasyopes
PR: metriport/metriport#3997
File: packages/api/src/routes/internal/medical/patient-job.ts:28-29
Timestamp: 2025-06-13T20:24:16.668Z
Learning: In packages/api/src/routes/internal/medical/patient-job.ts, mounting patientJobsRouter at the root path before other route handlers is intentional behavior, even though it could potentially consume requests before later handlers.

Applied to files:

  • packages/api/src/routes/medical/document.ts
📚 Learning: 2025-06-18T21:05:22.256Z
Learnt from: RamilGaripov
PR: metriport/metriport#3976
File: packages/api/src/routes/medical/patient.ts:541-543
Timestamp: 2025-06-18T21:05:22.256Z
Learning: In packages/api/src/routes/medical/patient.ts, inline schema definitions like cohortIdSchema are acceptable and don't need to be moved to separate schema files when the user prefers to keep them inline.

Applied to files:

  • packages/api/src/routes/medical/document.ts
📚 Learning: 2025-05-27T15:54:47.774Z
Learnt from: lucasdellabella
PR: metriport/metriport#3866
File: packages/api/src/routes/internal/medical/organization.ts:95-98
Timestamp: 2025-05-27T15:54:47.774Z
Learning: The conversion of the GET `/internal/organization` endpoint from returning a single organization to returning an array of organizations (batch retrieval) in `packages/api/src/routes/internal/medical/organization.ts` was an intentional breaking change planned by the team.

Applied to files:

  • packages/api/src/routes/medical/document.ts
🧬 Code Graph Analysis (7)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)
packages/shared/src/index.ts (1)
  • BadRequestError (42-42)
packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts (2)
packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
  • getSecondaryMappings (17-41)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • athenaSecondaryMappingsSchema (4-13)
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (4)
packages/mllp-server/src/utils.ts (1)
  • s3Utils (18-18)
packages/core/src/external/aws/s3.ts (1)
  • S3Utils (140-570)
packages/shared/src/util/uuid-v7.ts (1)
  • uuidv7 (369-371)
packages/core/src/domain/document/filename.ts (1)
  • createDocumentFilePath (11-18)
packages/core/src/external/ehr/document/command/fetch-document.ts (2)
packages/core/src/external/ehr/document/document-shared.ts (2)
  • DocumentKeyBaseParams (47-57)
  • createKeyAndExtensionMap (35-41)
packages/shared/src/index.ts (2)
  • BadRequestError (42-42)
  • MetriportError (43-43)
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (7)
packages/core/src/command/feature-flags/domain-ffs.ts (1)
  • isAthenaCustomFieldsEnabledForCx (131-134)
packages/api/src/command/mapping/cx.ts (1)
  • getCxMappingOrFail (54-63)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • athenaSecondaryMappingsSchema (4-13)
packages/core/src/external/ehr/athenahealth/index.ts (1)
  • encounterAppointmentExtensionUrl (170-171)
packages/core/src/external/ehr/document/command/fetch-document.ts (1)
  • fetchDocument (40-96)
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1)
  • getUploadUrlAndCreateDocRef (14-53)
packages/core/src/external/fhir/shared/extensions/derived-from.ts (1)
  • artifactRelatedArtifactUrl (6-7)
packages/core/src/external/ehr/athenahealth/index.ts (4)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • EhrStrictFhirResourceBundle (49-49)
packages/shared/src/interface/external/ehr/athenahealth/encounter.ts (1)
  • Encounter (7-7)
packages/core/src/external/ehr/document/command/fetch-document.ts (1)
  • fetchDocument (40-96)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)
  • createOrReplaceDocument (33-89)
packages/api/src/routes/medical/document.ts (2)
packages/api-sdk/src/index.ts (1)
  • UploadDocumentResult (52-52)
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1)
  • getUploadUrlAndCreateDocRef (14-53)
⏰ 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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (10)
packages/core/src/external/ehr/bundle/command/fetch-bundle.ts (2)

89-101: Good addition: include s3BucketName in error context

Adding s3BucketName to MetriportError improves observability when diagnosing cross-bucket issues.


75-82: Incorrect argument order assumption for getFileInfoFromS3: signature is (key, bucket)
The method is defined as

async getFileInfoFromS3(
  key: string,
  bucket: string
)

and every existing call—including in fetch-bundle.ts—correctly passes (key, bucket). Swapping the arguments as proposed would break metadata lookup. Please ignore this suggestion.

Likely an incorrect or invalid review comment.

packages/core/src/external/ehr/document/command/fetch-document.ts (1)

70-75: No change needed: correct argument order for getFileInfoFromS3

  • The signature of getFileInfoFromS3 is (key: string, bucket: string), so calling s3Utils.getFileInfoFromS3(key, s3BucketName) is correct.
  • getFileContentsAsString(bucket: string, key: string) intentionally uses the opposite parameter order.
  • Swapping the arguments would break metadata lookup by passing bucket as the key and vice versa.

Likely an incorrect or invalid review comment.

packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)

10-11: LGTM: schema extensions are clear and non-breaking

Optional fields for the blacklist and encounter summaries enable gated behavior without impacting existing consumers.

packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1)

1-54: LGTM! Clean refactoring that centralizes document upload logic.

The module effectively consolidates the document upload workflow, separating concerns between validation (in the routes) and orchestration (in this module). The parallel execution of FHIR upsert and S3 presigned URL generation is a good optimization.

packages/api/src/routes/medical/document.ts (2)

232-241: Good refactoring that centralizes document upload logic.

The wrapper function properly maintains validation at the route level while delegating core functionality to the shared module. This separation of concerns improves testability and reusability.


261-261: LGTM! Consistent usage of the shared function across endpoints.

Both endpoints now use the centralized function, maintaining backward compatibility while improving code maintainability.

Also applies to: 284-284

packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (1)

104-153: Dangerous mutation of bundle - consider refactoring for immutability.

The function name includes "dangerously" which correctly indicates it mutates the bundle, but this violates the coding guideline to "avoid modifying objects received as parameter". Consider refactoring to return a new bundle instead of mutating the input.

Consider returning a new bundle to avoid mutation:

-async function dangerouslyRemoveEncounterEntriesWithBlacklistedAppointmentType({
+async function removeEncounterEntriesWithBlacklistedAppointmentType({
   bundle,
   blacklistedAppointmentTypes,
 }: {
   bundle: Bundle;
   blacklistedAppointmentTypes: string[];
-}): Promise<void> {
-  if (!bundle.entry) return;
+}): Promise<Bundle> {
+  if (!bundle.entry) return bundle;
   const encounters: Encounter[] = bundle.entry
     .filter(entry => entry.resource?.resourceType === "Encounter")
     .map(entry => entry.resource as Encounter);
   // ... rest of the logic ...
-  bundle.entry = bundle.entry.filter((entry: BundleEntry<Resource>) => {
+  const filteredEntries = bundle.entry.filter((entry: BundleEntry<Resource>) => {
     if (!entry.resource) return true;
     if (!entry.resource.id) return true;
     return !resourcesToRemove.has(entry.resource.id);
   });
+  return {
+    ...bundle,
+    entry: filteredEntries,
+  };
}

Then update the calling code:

-    dangerouslyRemoveEncounterEntriesWithBlacklistedAppointmentType({
-      bundle: bundle.bundle,
-      blacklistedAppointmentTypes:
-        secondaryMappings.contributionEncounterAppointmentTypesBlacklist,
-    });
+    bundle.bundle = await removeEncounterEntriesWithBlacklistedAppointmentType({
+      bundle: bundle.bundle,
+      blacklistedAppointmentTypes:
+        secondaryMappings.contributionEncounterAppointmentTypesBlacklist,
+    });
⛔ Skipped due to learnings
Learnt from: lucasdellabella
PR: metriport/metriport#4020
File: packages/fhir-sdk/src/index.ts:308-308
Timestamp: 2025-08-04T17:59:05.674Z
Learning: In packages/fhir-sdk/src/index.ts, the FhirBundleSdk constructor intentionally mutates the input bundle's total property to ensure data consistency between bundle.total and bundle.entry.length. This is an accepted exception to the "avoid modifying objects received as parameter" guideline for cleanup purposes.
Learnt from: thomasyopes
PR: metriport/metriport#3891
File: packages/core/src/external/ehr/job/create-resource-diff-bundles/steps/contribute/ehr-contribute-resource-diff-bundles-direct.ts:0-0
Timestamp: 2025-06-20T20:16:15.111Z
Learning: In the codebase, functions prefixed with "dangerously" (like `dangerouslyAddEhrDataSourceExtension`) intentionally mutate input parameters and may use `any` types due to FHIR Resource typing constraints. The "dangerously" prefix serves as a clear indicator that the function violates typical immutability guidelines for specific technical reasons.
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.
Learnt from: leite08
PR: metriport/metriport#4193
File: packages/carequality-sdk/src/client/carequality-fhir.ts:198-208
Timestamp: 2025-07-16T00:54:56.156Z
Learning: User leite08 accepted the risk of parameter mutation in packages/carequality-sdk/src/client/carequality-fhir.ts when modifying org.active = false directly on input parameters, choosing to proceed despite the violation of the "Avoid modifying objects received as parameter" coding guideline.
packages/core/src/external/ehr/athenahealth/index.ts (2)

169-171: LGTM! Well-defined extension URL for appointment types.

The extension URL follows HL7 FHIR conventions and clearly indicates its purpose.


1495-1496: Good defensive defaults for the new optional flags.

The default values of false ensure backward compatibility and prevent unintended behavior changes.

Also applies to: 1504-1505, 1598-1599, 1607-1608

Ref: ENG-634

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

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 (1)
packages/api/src/command/mapping/patient.ts (1)

88-93: Strengthen typing and prevent accidental mutation of defaultSources

Make the defaults immutable and typed to the enum. This improves safety (no in-place mutations downstream) and keeps types tight across the API surface.

Apply this patch:

-export const defaultSources = [
-  EhrSources.athena,
-  EhrSources.canvas,
-  EhrSources.elation,
-  EhrSources.healthie,
-];
+export const defaultSources = Object.freeze([
+  EhrSources.athena,
+  EhrSources.canvas,
+  EhrSources.elation,
+  EhrSources.healthie,
+] as const);

And update the getSourceMapForPatient signature to accept a readonly typed list:

-  sources?: string[];
+  sources?: readonly EhrSources[];

This keeps the default value assignable and ensures callers pass valid EHR sources without mutating the shared default.

Also applies to: 99-101

📜 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 settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d1f37c0 and 739fc2a.

📒 Files selected for processing (4)
  • packages/api/src/command/mapping/patient.ts (1 hunks)
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (3 hunks)
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.ts (3 hunks)
  • packages/core/src/external/ehr/document/command/fetch-document.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/bundle/command/fetch-bundle.ts
  • packages/core/src/external/ehr/document/command/fetch-document.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/command/mapping/patient.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/api/src/command/mapping/patient.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/command/mapping/patient.ts
🧠 Learnings (1)
📚 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/command/mapping/patient.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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/api/src/command/mapping/patient.ts (2)

88-93: LGTM: Including Healthie in defaultSources aligns with the PR’s goals

This is consistent with enabling Healthie-related mappings and should broaden default coverage with no functional change here.


88-93: Healthie integration verified – no changes required

All checks pass for adding EhrSources.healthie to defaultSources:

  • EhrSources enum (packages/shared/src/interface/external/ehr/source.ts) defines healthie.
  • parseExternalId (external-id.ts) uses a generic fallback for non-Athena sources, so it correctly returns Healthie’s ID unchanged.
  • PatientSourceIdentifierMap is a string-indexed map ([key in string]: string[]), and PatientMappingSource derives from ehrSources (which includes healthie), so Healthie is a valid key.

@@ -137,12 +137,13 @@ const docRef: Partial<DocumentReference> = {

const resp = await metriport.createDocumentReference("018a80c4-292a-7486-a1234-76yuhe23yu14", docRef);

// Upload the document using this url in a PUT request, something along these lines:
// Upload the document using this url in a PUT request, something along these lines for an HTML:
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe use pdf as an example? who would upload html? 😅
if you end up updating it, dont forget about the content type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

docRefDraft: DocumentReference;
}): Promise<UploadDocumentResult> {
const medicalDocumentsUploadBucketName = Config.getMedicalDocumentsUploadBucketName();
const docRefId = uuidv7();
Copy link
Contributor

Choose a reason for hiding this comment

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

i get that we're keeping it as close to the original as possible, but i'd prefer if we allowed this method to accept an optional docRefId, i.e.

export async function getUploadUrlAndCreateDocRef({
  cxId,
  patientId,
  docRefDraft,
  docRefId = uuidv7()
}: {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


async function upsertOnFHIRServer() {
// Make a temporary DocumentReference on the FHIR server.
console.log("Creating a temporary DocumentReference on the FHIR server with ID:", docRef.id);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: agree w/ rabbit - console.log() is not something we wanna use anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 156 to 169
const encountersToRemove = encounters.filter((encounter: Encounter) => {
if (!encounter.extension || encounter.extension.length < 1) return true;
const appointmentTypeExtension = encounter.extension.find(
(ext: Extension) => ext.url === encounterAppointmentExtensionUrl
);
if (!appointmentTypeExtension || !appointmentTypeExtension.valueString) return true;
return blacklistedAppointmentTypes.includes(appointmentTypeExtension.valueString);
});
const resourceIdsToRemove = new Set<string>();
for (const encounter of encountersToRemove) {
if (!encounter.id) continue;
resourceIdsToRemove.add(encounter.id);
}
const encounterReferences = encountersToRemove.map(encounter => `Encounter/${encounter.id}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

a couple of nits here:

  1. let's just chain it:
const encounterIdsToRemove = encounters.filter(...current filtering logic).map(e => e.id);

then, you can use an existing func to build references - buildEntryReference:
const encounterReferences = encountersIdsToRemove.map(buildEntryReference);

Copy link
Contributor

Choose a reason for hiding this comment

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

side note: buildEntryReference is a bad name, it should be buildResourceReference

Copy link
Contributor Author

Choose a reason for hiding this comment

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

buildResourceReference takes a resource, not an ID. So it can't "quite" chain, but done.

const encountersToRemove = encounters.filter((encounter: Encounter) => {
if (!encounter.extension || encounter.extension.length < 1) return true;
const appointmentTypeExtension = encounter.extension.find(
(ext: Extension) => ext.url === encounterAppointmentExtensionUrl
Copy link
Contributor

Choose a reason for hiding this comment

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

would appreciate a filtering function to be defined in the same place where this extension is defined, so the logic here can be clearer, i.e.
encounter.extension.find(isAppointmentTypeExtension)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

for (const entry of bundle.entry) {
if (!entry.resource || !entry.resource.id) continue;
if (doesResourceReferToEncounter(entry.resource, encounterReferences)) {
resourceIdsToRemove.add(entry.resource.id);
Copy link
Contributor

Choose a reason for hiding this comment

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

ah, i see why you structured it this way now.. This a set that we continue growing throughout the func...

nit: rename it to have Set in the name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Although I feel like add is only a method on Sets so it's fairly obvious it's a set in theory.

} catch (error) {
out(
`dangerouslyRemoveEncounterEntriesWithBlacklistedAppointmentType - metriportPatientId ${metriportPatientId} ehrPatientId ${ehrPatientId} resourceType ${resourceType}`
).log(`Error creating resource diff removed bundle. Cause: ${errorToString(error)}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

i was so confused why we're saving this using the resourcesToRemove, and the only answer i got was from looking at the catch log.. let's please put this in a separate func

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return resourceIdsToRemove.has(entry.resource.id);
}
);
bundle.entry = resourcesToKeep;
Copy link
Contributor

Choose a reason for hiding this comment

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

is there any good reason to keep this func "dangerous"? why not just rebuild and return a new bundle?

Copy link
Contributor Author

@thomasyopes thomasyopes Aug 18, 2025

Choose a reason for hiding this comment

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

This function has early returns... which I find easier to follow when they operate as "do nothing" early exits. Making this non-dangerous means we're doing things like returning the whole bundle entries back, which I feel like makes it harder to understand.

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like this isn't a good enough argument for keeping mutative functions around.. But i'll leave it to your discretion. AFAIK, our pattern is: less mutation = more good

Ref: ENG-634

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

📜 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 739fc2a and 583391c.

📒 Files selected for processing (1)
  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.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/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.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/athenahealth/command/get-resource-bundle-by-resource-id.ts
🧠 Learnings (7)
📚 Learning: 2025-08-13T21:37:33.618Z
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.618Z
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/athenahealth/command/get-resource-bundle-by-resource-id.ts
📚 Learning: 2025-08-14T20:43:26.255Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:221-233
Timestamp: 2025-08-14T20:43:26.255Z
Learning: In Athena EHR integration, encounter references are housed specifically in the 'encounter' and 'context' fields as checked by the doesResourceReferToEncounter function in packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts. No broadening of encounter reference detection is needed since the current implementation matches Athena's exact data structure.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts
📚 Learning: 2025-05-28T19:22:09.281Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:27-49
Timestamp: 2025-05-28T19:22:09.281Z
Learning: In packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.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/athenahealth/command/get-resource-bundle-by-resource-id.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/athenahealth/command/get-resource-bundle-by-resource-id.ts
📚 Learning: 2025-04-21T17:07:30.574Z
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts
📚 Learning: 2025-05-28T19:20:47.442Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:0-0
Timestamp: 2025-05-28T19:20:47.442Z
Learning: In packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts
🧬 Code Graph Analysis (1)
packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts (2)
packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
  • getSecondaryMappings (17-41)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • athenaSecondaryMappingsSchema (4-13)
⏰ 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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts (3)

2-4: LGTM: wiring secondary mappings and EHR source is appropriate

Imports are correct and align with the intent to drive behavior via Athena secondary mappings. This also matches the preference to hard-fail when mappings fetch fails (per learnings), which is acceptable here.


38-43: Fix truthiness check (empty blacklist) and gate options by resource type

  • Empty arrays are truthy in JS/TS, so the current check enables attachAppointmentType even when the blacklist is empty, causing unnecessary appointment-type fetches.
  • Both flags are Encounter-specific; gate on resourceType to avoid accidentally enabling options for other resource types.

Apply this diff:

-    ...(mappings?.contributionEncounterAppointmentTypesBlacklist && {
-      attachAppointmentType: true,
-    }),
-    ...(mappings?.contributionEncounterSummariesEnabled && {
-      fetchEncounterSummary: true,
-    }),
+    ...(resourceType === "Encounter" &&
+      ((mappings?.contributionEncounterAppointmentTypesBlacklist?.length ?? 0) > 0) && {
+        attachAppointmentType: true,
+      }),
+    ...(resourceType === "Encounter" &&
+      mappings?.contributionEncounterSummariesEnabled === true && {
+        fetchEncounterSummary: true,
+      }),

This ensures:

  • attachAppointmentType is only set when there is at least one blocked appointment type configured.
  • Both flags apply only to Encounter resources.
⛔ Skipped due to learnings
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.034Z
Learning: In Athena EHR integration, when contributionEncounterAppointmentTypesBlacklist is present (even if empty), attachAppointmentType should be enabled to start tracking appointment type IDs for future use, rather than only enabling when the array has items.
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.034Z
Learning: For contributionEncounterSummariesEnabled in Athena secondary mappings, strict boolean checking (=== true) is unnecessary because Zod schema validation ensures the value is boolean or undefined, making truthy checks sufficient.

31-44: Verified: AthenaHealth client supports attachAppointmentType & fetchEncounterSummary
The getResourceBundleByResourceId signature in packages/core/src/external/ehr/athenahealth/index.ts defines both flags as optional booleans (defaulting to false) and forwards them into dangerouslyAdjustEncountersInBundle. No changes required.

Comment on lines +145 to +147
const fileExists = await s3Utils.fileExists(s3BucketName, key);
if (!fileExists) return undefined;
return s3Utils.getSignedUrl({
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure, but is this the most efficient way to do this? we can't just try to get a signed url and return undefined if the file doesnt exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer this current way over using try catch personally... it's explicit about what you're trying to do. Were someone to change the behavior of getSignedUrl to throw a different type of error (this exact bug has happened before), code relying on that error would break.

Your suggestion is good in situations where the client isn't something we own: sequelize, axios, etc. Then the returned errors are quite stable.

Thomas Yopes added 4 commits August 18, 2025 11:32
…-athena-contribution-update

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

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

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

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

♻️ Duplicate comments (2)
packages/core/src/external/ehr/athenahealth/index.ts (1)

1977-1996: Skip missing appointment IDs and make extension write idempotent

Two improvements:

  • Guard encounter.appointmentid to avoid an unnecessary API call with an undefined ID.
  • Update or insert the extension (and skip when appointmenttypeid is falsy) to avoid duplicate entries.

Apply this diff:

-          if (attachAppointmentType) {
-            encounter = await this.getEncounter({
+          if (attachAppointmentType) {
+            encounter = await this.getEncounter({
               cxId,
               patientId: athenaPatientId,
               encounterId,
             });
-            const appointment = await this.getAppointment({
-              cxId,
-              patientId: athenaPatientId,
-              appointmentId: encounter.appointmentid,
-            });
-            entry.resource.extension = [
-              ...(entry.resource.extension ?? []),
-              {
-                url: encounterAppointmentExtensionUrl,
-                valueString: appointment.appointmenttypeid,
-              },
-            ];
+            const appointmentId = encounter.appointmentid;
+            if (appointmentId) {
+              const appointment = await this.getAppointment({
+                cxId,
+                patientId: athenaPatientId,
+                appointmentId,
+              });
+              const appointmentTypeId = appointment.appointmenttypeid;
+              if (appointmentTypeId) {
+                const extensions = entry.resource.extension ?? [];
+                const idx = extensions.findIndex(ext => ext.url === encounterAppointmentExtensionUrl);
+                if (idx >= 0) {
+                  extensions[idx] = {
+                    url: encounterAppointmentExtensionUrl,
+                    valueString: appointmentTypeId,
+                  };
+                } else {
+                  extensions.push({
+                    url: encounterAppointmentExtensionUrl,
+                    valueString: appointmentTypeId,
+                  });
+                }
+                entry.resource.extension = extensions;
+              }
+            }
           }

Note: This revisits a prior suggestion to avoid duplicate extensions and skip undefined values; surfacing it here only because the current code still appends unconditionally.

packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (1)

205-220: Prefer truthy property checks over 'in' and keep detection scope narrow

Per repo style, prefer truthy checks rather than 'in'. The current scope (examining encounter.reference and context.reference) matches Athena’s data shape per prior guidance; keep it that way.

Apply this minimal refactor:

-function doesResourceReferenceEncounter(
-  resource: Resource,
-  encounterReferences: string[]
-): boolean {
-  if ("encounter" in resource) {
-    const encounter = resource.encounter;
-    if (!encounter || !encounter.reference) return false;
-    return encounterReferences.includes(encounter.reference);
-  }
-  if ("context" in resource) {
-    const context = resource.context;
-    if (!context || !("reference" in context) || !context.reference) return false;
-    return encounterReferences.includes(context.reference);
-  }
-  return false;
-}
+function doesResourceReferenceEncounter(resource: Resource, encounterReferences: string[]): boolean {
+  const encRef = (resource as any)?.encounter?.reference as string | undefined;
+  if (encRef && encounterReferences.includes(encRef)) return true;
+  const ctxRef = (resource as any)?.context?.reference as string | undefined;
+  return Boolean(ctxRef && encounterReferences.includes(ctxRef));
+}
🧹 Nitpick comments (5)
packages/core/src/external/fhir/shared/index.ts (2)

300-305: Avoid producing Type/undefined for entries missing id

When an entry’s resource lacks an id, buildResourceReference will yield "<type>/undefined", which is not a valid FHIR reference and can cause subtle mismatches. Guard on id before constructing the reference.

Apply this diff:

   const entry = bundle.entry.find(entry => {
-    const entryReference = entry.resource ? buildResourceReference(entry.resource) : undefined;
+    const entryReference = entry.resource?.id
+      ? buildResourceReference(entry.resource)
+      : undefined;
     return entryReference === reference;
   });

307-309: Tighten buildResourceReference signature and update all call sites to guarantee an id

Changing the signature to

export function buildResourceReference<T extends Resource & { id: string }>(resource: T): string

will break existing calls unless they only pass resources with a known id. You’ll need to update these sites:

• packages/core/src/external/fhir/shared/index.ts (around line 301)
– Guard on entry.resource?.id before calling.
• packages/core/src/external/fhir/shared/references.ts (around lines 113, 122, 131)
– Change the input types to require { resource: Resource & { id: string } }.

Apply a diff like this:

--- a/packages/core/src/external/fhir/shared/index.ts
+++ b/packages/core/src/external/fhir/shared/index.ts
@@
-export function buildResourceReference(resource: Resource): string {
+export function buildResourceReference<T extends Resource & { id: string }>(
+  resource: T
+): string {
   return `${resource.resourceType}/${resource.id}`;
 }

 # Before:
-const entryReference = entry.resource ? buildResourceReference(entry.resource) : undefined;
+// After: only call when we know `id` is present
+const entryReference =
+  entry.resource?.id
+    ? buildResourceReference(entry.resource)
+    : undefined;
--- a/packages/core/src/external/fhir/shared/references.ts
+++ b/packages/core/src/external/fhir/shared/references.ts
@@
-type EncounterLocationInput = XOR<{ locationId: string }, { resource: Resource }>;
+type EncounterLocationInput = XOR<
+  { locationId: string },
+  { resource: Resource & { id: string } }
>;

@@
-type EncounterDiagnosisInput = XOR<{ conditionId: string }, { resource: Resource }>;
+type EncounterDiagnosisInput = XOR<
+  { conditionId: string },
+  { resource: Resource & { id: string } }
>;

Review any other buildResourceReference calls to ensure you only pass in resources with a guaranteed id.

packages/core/src/external/fhir/shared/references.ts (1)

113-114: Require resource.id for reference-building inputs

These constructors always create typed references from a full resource reference string ${type}/${id}. Strengthen the XOR input types to ensure id is present at compile time and avoid emitting Type/undefined.

Proposed type refinements (outside the selected lines):

// Prefer requiring id where a reference string is constructed
type EncounterParticipantInput = XOR<{ practitionerId: string }, { resource: Resource & { id: string } }>;
type EncounterLocationInput = XOR<{ locationId: string }, { resource: Resource & { id: string } }>;
type EncounterDiagnosisInput = XOR<{ conditionId: string }, { resource: Resource & { id: string } }>;

No runtime changes needed in the return lines; this is purely a safety improvement.

Also applies to: 122-123, 131-132

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

1997-2031: Encounter-summary upload path is robust; consider early-continue for non-closed status

The summary fetch/write is correctly gated and idempotent via the S3 presence check. Minor readability improvement: return early when encounter.status !== athenaClosedStatus to reduce nesting.

Example:

-            if (encounter.status === athenaClosedStatus) {
+            if (encounter.status !== athenaClosedStatus) return;
+            {
               const existingEncounterSummary = await fetchDocument({
                 ehr: EhrSources.athena,
                 cxId,
                 metriportPatientId,
                 ehrPatientId: athenaPatientId,
                 documentType: DocumentType.HTML,
                 resourceType: "Encounter",
                 resourceId: encounterId,
               });
               if (existingEncounterSummary) return;
               const encounterSummary = await this.getEncounterSummary({
                 cxId,
                 patientId: athenaPatientId,
                 encounterId,
               });
               await createOrReplaceDocument({
                 ehr: EhrSources.athena,
                 cxId,
                 metriportPatientId,
                 ehrPatientId: athenaPatientId,
                 documentType: DocumentType.HTML,
                 payload: encounterSummary,
                 resourceType: "Encounter",
                 resourceId: encounterId,
               });
-            }
+            }
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (1)

331-370: DocumentReference draft is sensible; set Content-Type explicitly (good) and consider optional subject

The draft includes an appropriate LOINC type and ties to the Encounter via context.encounter. If available from the upstream utility, adding subject: { reference: "Patient/{id}" } can help downstream consumers; otherwise, current shape is acceptable.

Would you like me to wire in subject using metriportPatientId, assuming your getUploadUrlAndCreateDocRef supports it?

📜 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 d4cad36 and 4b24482.

📒 Files selected for processing (6)
  • docs/medical-api/api-reference/document/post-upload-url.mdx (1 hunks)
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1 hunks)
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (3 hunks)
  • packages/core/src/external/ehr/athenahealth/index.ts (9 hunks)
  • packages/core/src/external/fhir/shared/index.ts (1 hunks)
  • packages/core/src/external/fhir/shared/references.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/medical-api/api-reference/document/post-upload-url.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.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/fhir/shared/index.ts
  • packages/core/src/external/fhir/shared/references.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/fhir/shared/index.ts
  • packages/core/src/external/fhir/shared/references.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.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/fhir/shared/index.ts
  • packages/core/src/external/fhir/shared/references.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
🧠 Learnings (26)
📚 Learning: 2025-06-18T18:50:40.968Z
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/core/src/external/surescripts/fhir/shared.ts:87-93
Timestamp: 2025-06-18T18:50:40.968Z
Learning: The `getResourceFromResourceMap` function in `packages/core/src/external/surescripts/fhir/shared.ts` works correctly as implemented. The comparison `resourceValue === resourceMap[key]` where `resourceValue = resource[key]` is intentional and functions as designed, despite appearing to compare different types.

Applied to files:

  • packages/core/src/external/fhir/shared/index.ts
📚 Learning: 2025-08-14T20:43:26.281Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:221-233
Timestamp: 2025-08-14T20:43:26.281Z
Learning: In Athena EHR integration, encounter references are housed specifically in the 'encounter' and 'context' fields as checked by the doesResourceReferToEncounter function in packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts. No broadening of encounter reference detection is needed since the current implementation matches Athena's exact data structure.

Applied to files:

  • packages/core/src/external/fhir/shared/references.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.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/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.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/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:36:10.069Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.069Z
Learning: For contributionEncounterSummariesEnabled in Athena secondary mappings, strict boolean checking (=== true) is unnecessary because Zod schema validation ensures the value is boolean or undefined, making truthy checks sufficient.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:38:43.394Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:187-284
Timestamp: 2025-08-13T21:38:43.394Z
Learning: In the Metriport system, orphaned DocumentReferences (where the DocumentReference is created but the associated file upload fails) are acceptable and don't require cleanup logic. The system is designed to handle these gracefully.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-07-27T00:40:32.149Z
Learnt from: leite08
PR: metriport/metriport#4255
File: packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts:19-20
Timestamp: 2025-07-27T00:40:32.149Z
Learning: User leite08 acknowledged the duplicate inputBundle spread issue in packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts and will fix it in a follow-up PR rather than in the current patch release PR #4255.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:36:10.069Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.069Z
Learning: In Athena EHR integration, when contributionEncounterAppointmentTypesBlacklist is present (even if empty), attachAppointmentType should be enabled to start tracking appointment type IDs for future use, rather than only enabling when the array has items.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
📚 Learning: 2025-08-04T17:59:05.674Z
Learnt from: lucasdellabella
PR: metriport/metriport#4020
File: packages/fhir-sdk/src/index.ts:308-308
Timestamp: 2025-08-04T17:59:05.674Z
Learning: In packages/fhir-sdk/src/index.ts, the FhirBundleSdk constructor intentionally mutates the input bundle's total property to ensure data consistency between bundle.total and bundle.entry.length. This is an accepted exception to the "avoid modifying objects received as parameter" guideline for cleanup purposes.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-19T18:24:41.632Z
Learnt from: thomasyopes
PR: metriport/metriport#3870
File: packages/core/src/external/ehr/bundle/job/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts:55-91
Timestamp: 2025-05-19T18:24:41.632Z
Learning: In EHR bundles related to resource diff computations, bundle creation operations that are for auditing purposes should not stop the main job flow if they fail. These operations typically use try/catch blocks that log errors without rethrowing them, which is the intended behavior.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-18T00:18:59.144Z
Learnt from: leite08
PR: metriport/metriport#4043
File: packages/core/src/command/consolidated/consolidated-create.ts:0-0
Timestamp: 2025-06-18T00:18:59.144Z
Learning: In the consolidated bundle creation process (packages/core/src/command/consolidated/consolidated-create.ts), the team prefers strict failure behavior where if any component (including pharmacy resources) fails to be retrieved, the entire consolidation should fail rather than gracefully degrading. This ensures data consistency and prevents incomplete bundles.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-19T18:25:32.688Z
Learnt from: thomasyopes
PR: metriport/metriport#3870
File: packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts:26-57
Timestamp: 2025-05-19T18:25:32.688Z
Learning: In the startCreateResourceDiffBundlesJob function (and similar functions in the codebase), the preferred approach is to let errors bubble up naturally rather than adding explicit try/catch blocks at multiple levels of the call stack.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-08T19:41:36.533Z
Learnt from: thomasyopes
PR: metriport/metriport#3788
File: packages/api/src/external/ehr/shared/utils/bundle.ts:83-93
Timestamp: 2025-05-08T19:41:36.533Z
Learning: In the Metriport codebase, the team prefers to let errors bubble up naturally in some cases rather than adding explicit error handling at every layer, as demonstrated in the refreshEhrBundle function in the bundle.ts file.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-31T21:58:28.502Z
Learnt from: leite08
PR: metriport/metriport#3940
File: packages/core/src/command/consolidated/search/fhir-resource/search-consolidated.ts:82-86
Timestamp: 2025-05-31T21:58:28.502Z
Learning: In packages/core/src/command/consolidated/search/fhir-resource/search-consolidated.ts, the mutable array operations using push() on resourcesMutable and hydratedMutable (lines 82-86 and 100-103) have been explicitly accepted as exceptions to the immutability guidelines after previous discussion.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T02:32:27.527Z
Learnt from: lucasdellabella
PR: metriport/metriport#3907
File: packages/core/src/external/fhir/adt-encounters.ts:118-143
Timestamp: 2025-05-28T02:32:27.527Z
Learning: In packages/core/src/external/fhir/adt-encounters.ts, conversion bundles stored by saveAdtConversionBundle do not require version ID assertions or versioning, unlike the sourced encounter data stored by putAdtSourcedEncounter which does require versionId validation.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.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/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-07-16T00:54:56.156Z
Learnt from: leite08
PR: metriport/metriport#4193
File: packages/carequality-sdk/src/client/carequality-fhir.ts:198-208
Timestamp: 2025-07-16T00:54:56.156Z
Learning: User leite08 accepted the risk of parameter mutation in packages/carequality-sdk/src/client/carequality-fhir.ts when modifying org.active = false directly on input parameters, choosing to proceed despite the violation of the "Avoid modifying objects received as parameter" coding guideline.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T19:22:09.281Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:27-49
Timestamp: 2025-05-28T19:22:09.281Z
Learning: In packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T19:20:47.442Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:0-0
Timestamp: 2025-05-28T19:20:47.442Z
Learning: In packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-15T18:57:28.999Z
Learnt from: lucasdellabella
PR: metriport/metriport#4382
File: packages/utils/src/hl7v2-notifications/reprocess-adt-conversion-bundles/common.ts:36-48
Timestamp: 2025-08-15T18:57:28.999Z
Learning: In utility scripts like packages/utils/src/hl7v2-notifications/reprocess-adt-conversion-bundles/common.ts, user lucasdellabella prefers to let bundle parsing errors fail fast rather than adding error handling, as it provides immediate visibility into data issues during script execution.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-04T17:59:02.571Z
Learnt from: lucasdellabella
PR: metriport/metriport#4020
File: packages/fhir-sdk/src/index.ts:312-326
Timestamp: 2025-08-04T17:59:02.571Z
Learning: In packages/fhir-sdk/src/index.ts, the inconsistent error handling between the `total` getter (which throws an error) and the `entry` getter (which returns an empty array) when bundle.entry is undefined is intentional design by user lucasdellabella, serving different purposes where some operations need to fail fast while others can gracefully degrade.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-04-21T17:07:30.574Z
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
  • packages/core/src/external/ehr/athenahealth/index.ts
📚 Learning: 2025-04-23T21:19:02.589Z
Learnt from: thomasyopes
PR: metriport/metriport#3709
File: packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts:63-72
Timestamp: 2025-04-23T21:19:02.589Z
Learning: In the metriport codebase, factory functions like `fetchCanvasBundle` in the Canvas EHR subsystem intentionally let errors bubble up to the caller rather than handling them at the factory function level.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-05T16:28:33.728Z
Learnt from: thomasyopes
PR: metriport/metriport#3960
File: packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts:11-19
Timestamp: 2025-06-05T16:28:33.728Z
Learning: In the EhrGetAppointmentsDirect class in packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts, type casting `as T[]` for the handler return value is considered safe and doesn't require additional runtime validation by the user thomasyopes.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
🧬 Code Graph Analysis (3)
packages/core/src/external/fhir/shared/index.ts (1)
packages/fhir-sdk/src/index.ts (2)
  • entry (319-325)
  • Resource (58-58)
packages/core/src/external/fhir/shared/references.ts (1)
packages/core/src/external/fhir/shared/index.ts (1)
  • buildResourceReference (307-309)
packages/core/src/external/ehr/athenahealth/index.ts (5)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • EhrStrictFhirResourceBundle (49-49)
packages/shared/src/interface/external/ehr/athenahealth/encounter.ts (1)
  • Encounter (7-7)
packages/fhir-sdk/src/index.ts (1)
  • entry (319-325)
packages/core/src/external/ehr/document/command/fetch-document.ts (1)
  • fetchDocument (40-97)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)
  • createOrReplaceDocument (33-89)
⏰ 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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
packages/core/src/external/fhir/shared/references.ts (1)

12-12: Rename alignment looks good

Import swap to buildResourceReference is consistent with the shared helper rename and keeps references uniform.

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

172-181: Good encapsulation for appointment-type extension lookup

Centralizing the extension URL and the lookup helper improves readability and reuse across the codebase.

packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (4)

70-103: Athena-specific gating and hard-fail on missing secondary mappings looks correct

This follows the intended behavior: only apply the new pruning/upload features for Athena and fail fast when secondary mappings are unavailable.


153-169: Efficient computation of encounter IDs and references to remove

Logic is clear: filter Encounters by blacklist/extension presence, collect IDs and references once, and use them for downstream pruning. Nice use of buildResourceReference to keep reference format consistent.


176-183: Partition predicate is correct after tightening — avoids unintended drops

Now only entries with a concrete resource.id present in the removal set are moved out. This prevents accidental removal of entries without IDs.


418-426: No changes needed: getPredecessorExtensionValue correctly retrieves the predecessor ID from valueRelatedArtifact.display.

All occurrences of the predecessor extension are created via createPredecessorExtensionRelatedArtifact, which sets the ID in the display field. Therefore, returning valueRelatedArtifact.display here is intentional and aligns with existing semantics.

Ref: ENG-634

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Thomas Yopes added 2 commits August 19, 2025 12:00
Ref: ENG-634

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

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
units === "br/min"
units === "br/min" ||
units === "breaths/min" ||
units === "breaths per minute"
Copy link
Contributor

Choose a reason for hiding this comment

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

Confirming that these are the unhandled units causing these recent Sentry alerts.

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/core/src/external/ehr/unit-conversion.ts (2)

200-209: Consider covering a few additional common variants ("beats per minute", singular forms, "resp/min").

These show up frequently in vendor feeds. Low-risk to include and keeps logic consistent with existing equality checks.

Apply this diff to broaden coverage:

 function isBpm(units: string): boolean {
   return (
     units === "bpm" ||
     units === "/min" ||
     units === "beats/min" ||
+    units === "beats per minute" ||
+    units === "beat/min" ||
     units === "per minute" ||
     units === "br/min" ||
+    units === "breath/min" ||
     units === "breaths/min" ||
-    units === "breaths per minute"
+    units === "breaths per minute" ||
+    units === "resp/min"
   );
 }

If you'd like, I can also refactor this to use a Set of canonical synonyms to improve readability and reduce future edit noise.


49-61: Minor: remove redundant re-computation of valueNumber inside the "lb_av" branch.

You already computed valueNumber at function scope (Line 23). Re-declaring it here shadows the outer binding for no gain.

Apply this diff:

   if (targetUnits === "lb_av") {
     // https://hl7.org/fhir/R4/valueset-ucum-bodyweight.html
-    const valueNumber = convertValueToNumber(baseValue);
     if (isLbs(baseInputUnits)) {
       return { ...baseParams, value: valueNumber };
     }
     if (isKg(baseInputUnits)) {
       return { ...baseParams, value: convertKgToLbs(valueNumber) };
     }
     if (isG(baseInputUnits)) {
       return { ...baseParams, value: convertGramsToLbs(valueNumber) };
     }
   }
📜 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 42a532e and 655bd41.

📒 Files selected for processing (2)
  • docs/medical-api/api-reference/document/post-upload-url.mdx (1 hunks)
  • packages/core/src/external/ehr/unit-conversion.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/medical-api/api-reference/document/post-upload-url.mdx
🧰 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/unit-conversion.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/ehr/unit-conversion.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/unit-conversion.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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/core/src/external/ehr/unit-conversion.ts (1)

206-208: Good expansion of per-minute synonyms for respiratory rate.

Recognizing "breaths/min" and "breaths per minute" will reduce false "Unknown units" warnings and aligns with real-world EHR data.

Ref: ENG-634

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

♻️ Duplicate comments (1)
packages/core/src/external/ehr/athenahealth/index.ts (1)

1989-2000: Make extension write idempotent and skip undefined appointment type IDs

Repeated runs can duplicate the appointment-type extension; also avoid writing when appointmenttypeid is missing.

Apply this diff:

-            const appointment = await this.getAppointment({
+            const appointment = await this.getAppointment({
               cxId,
-              patientId: athenaPatientId,
-              appointmentId: encounter.appointmentid,
+              patientId: athenaPatientId,
+              appointmentId: encounter.appointmentid,
             });
-            entry.resource.extension = [
-              ...(entry.resource.extension ?? []),
-              {
-                url: encounterAppointmentExtensionUrl,
-                valueString: appointment.appointmenttypeid,
-              },
-            ];
+            const appointmentTypeId = appointment.appointmenttypeid;
+            if (appointmentTypeId) {
+              const existing = entry.resource.extension ?? [];
+              const withoutExisting = existing.filter(
+                ext => ext.url !== encounterAppointmentExtensionUrl
+              );
+              entry.resource.extension = [
+                ...withoutExisting,
+                {
+                  url: encounterAppointmentExtensionUrl,
+                  valueString: appointmentTypeId,
+                },
+              ];
+            }
🧹 Nitpick comments (3)
packages/core/src/external/ehr/athenahealth/index.ts (1)

1989-1992: Guard against missing encounter.appointmentid to avoid 404 churn

A quick guard prevents unnecessary network calls when an encounter has no appointment association.

Apply this diff:

-            const appointment = await this.getAppointment({
+            if (!encounter.appointmentid) return;
+            const appointment = await this.getAppointment({
               cxId,
               patientId: athenaPatientId,
               appointmentId: encounter.appointmentid,
             });
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (2)

205-220: Prefer truthy property checks over 'in' and keep scope narrow

Switch to truthy checks per guidelines without broadening supported shapes.

Apply this diff:

-function doesResourceReferenceEncounter(
-  resource: Resource,
-  encounterReferences: string[]
-): boolean {
-  if ("encounter" in resource) {
-    const encounter = resource.encounter;
-    if (!encounter || !encounter.reference) return false;
-    return encounterReferences.includes(encounter.reference);
-  }
-  if ("context" in resource) {
-    const context = resource.context;
-    if (!context || !("reference" in context) || !context.reference) return false;
-    return encounterReferences.includes(context.reference);
-  }
-  return false;
-}
+function doesResourceReferenceEncounter(
+  resource: Resource,
+  encounterReferences: string[]
+): boolean {
+  const encRef = (resource as any)?.encounter?.reference as string | undefined;
+  if (encRef && encounterReferences.includes(encRef)) return true;
+  const ctxRef = (resource as any)?.context?.reference as string | undefined;
+  if (ctxRef && encounterReferences.includes(ctxRef)) return true;
+  return false;
+}

298-327: Align Encounter HTML document keys between Core and API

The core Athena integration stores Encounter HTML under the Encounter’s current id, whereas this bundle flow always uses the predecessor extension value for both fetching and writing. If those differ, you’ll miss existing summaries (skipping uploads) or overwrite the wrong key.

• File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
– Lines ~298–327: fetchDocument({ resourceId: predecessor })
– Lines ~371–383: createOrReplaceDocument({ resourceId: predecessor })

Optional refactor: introduce a sourceResourceId, attempt a fallback fetch with the current entry.resource.id, and use that resolved ID when creating the document.

--- a/packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
+++ b/packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
@@ -298,8 +298,12 @@ export async function contributeResourceDiffBundle(...) {
-  const encounterSummariesToUpload: { encounter: Encounter; summary: string }[] = [];
+  const encounterSummariesToUpload: {
+    encounter: Encounter;
+    summary: string;
+    sourceResourceId: string;
+  }[] = [];

   for (const entry of bundle.entry) {
     // ...
     const predecessor = getPredecessorExtensionValue(entry.resource);
     if (!predecessor) continue;
-    const uploadedSummary = await fetchDocument({ /* … resourceId: predecessor, jobId */ });
+    const uploadedSummary = await fetchDocument({
+      /* … resourceId: predecessor, jobId */
+    });
     if (uploadedSummary) continue;
 
-    const summary = await fetchDocument({ /* … resourceId: predecessor */ });
-    if (!summary) continue;
-    encounterSummariesToUpload.push({ encounter: entry.resource, summary: summary.file });
+    // Try predecessor, then fall back to current encounter ID
+    let sourceResourceId = predecessor;
+    let summary = await fetchDocument({ /* … resourceId: predecessor */ });
+    if (!summary) {
+      sourceResourceId = entry.resource.id!;
+      summary = await fetchDocument({ /* … resourceId: sourceResourceId */ });
+      if (!summary) continue;
+    }
+    encounterSummariesToUpload.push({
+      encounter: entry.resource,
+      summary: summary.file,
+      sourceResourceId,
+    });
 
     // …
@@ -371,7 +384,7 @@ export async function contributeResourceDiffBundle(...) {
       await createOrReplaceDocument({
         // …
-        resourceId: predecessor,
+        resourceId: encounterSummary.sourceResourceId,
         jobId: uploadJobId,
       });

This ensures you fetch and write against the same key where the HTML actually lives.

📜 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 655bd41 and d227750.

📒 Files selected for processing (3)
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (3 hunks)
  • packages/core/src/external/ehr/athenahealth/index.ts (9 hunks)
  • packages/core/src/external/ehr/document/command/fetch-document.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/external/ehr/document/command/fetch-document.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/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
🧠 Learnings (24)
📚 Learning: 2025-08-14T20:43:26.281Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:221-233
Timestamp: 2025-08-14T20:43:26.281Z
Learning: In Athena EHR integration, encounter references are housed specifically in the 'encounter' and 'context' fields as checked by the doesResourceReferToEncounter function in packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts. No broadening of encounter reference detection is needed since the current implementation matches Athena's exact data structure.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:36:10.069Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.069Z
Learning: In Athena EHR integration, when contributionEncounterAppointmentTypesBlacklist is present (even if empty), attachAppointmentType should be enabled to start tracking appointment type IDs for future use, rather than only enabling when the array has items.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T02:32:27.527Z
Learnt from: lucasdellabella
PR: metriport/metriport#3907
File: packages/core/src/external/fhir/adt-encounters.ts:118-143
Timestamp: 2025-05-28T02:32:27.527Z
Learning: In packages/core/src/external/fhir/adt-encounters.ts, conversion bundles stored by saveAdtConversionBundle do not require version ID assertions or versioning, unlike the sourced encounter data stored by putAdtSourcedEncounter which does require versionId validation.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-05T16:28:33.728Z
Learnt from: thomasyopes
PR: metriport/metriport#3960
File: packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts:11-19
Timestamp: 2025-06-05T16:28:33.728Z
Learning: In the EhrGetAppointmentsDirect class in packages/core/src/external/ehr/command/get-appointments/ehr-get-appointments-direct.ts, type casting `as T[]` for the handler return value is considered safe and doesn't require additional runtime validation by the user thomasyopes.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
📚 Learning: 2025-04-21T17:07:30.574Z
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T19:22:09.281Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:27-49
Timestamp: 2025-05-28T19:22:09.281Z
Learning: In packages/core/src/external/ehr/command/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-28T19:20:47.442Z
Learnt from: thomasyopes
PR: metriport/metriport#3882
File: packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts:0-0
Timestamp: 2025-05-28T19:20:47.442Z
Learning: In packages/core/src/external/ehr/lambdas/get-bundle-by-resource-type/ehr-get-bundle-by-resource-type-cloud.ts, the EHR get bundle by resource type Lambda endpoint is guaranteed to return valid JSON, so JSON.parse() error handling is not necessary for this specific endpoint.

Applied to files:

  • packages/core/src/external/ehr/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/athenahealth/index.ts
  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:36:10.069Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:36-41
Timestamp: 2025-08-13T21:36:10.069Z
Learning: For contributionEncounterSummariesEnabled in Athena secondary mappings, strict boolean checking (=== true) is unnecessary because Zod schema validation ensures the value is boolean or undefined, making truthy checks sufficient.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-13T21:38:43.394Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:187-284
Timestamp: 2025-08-13T21:38:43.394Z
Learning: In the Metriport system, orphaned DocumentReferences (where the DocumentReference is created but the associated file upload fails) are acceptable and don't require cleanup logic. The system is designed to handle these gracefully.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-07-27T00:40:32.149Z
Learnt from: leite08
PR: metriport/metriport#4255
File: packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts:19-20
Timestamp: 2025-07-27T00:40:32.149Z
Learning: User leite08 acknowledged the duplicate inputBundle spread issue in packages/core/src/command/analytics-platform/fhir-to-csv/command/fhir-to-csv/fhir-to-csv-direct.ts and will fix it in a follow-up PR rather than in the current patch release PR #4255.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-04T17:59:05.674Z
Learnt from: lucasdellabella
PR: metriport/metriport#4020
File: packages/fhir-sdk/src/index.ts:308-308
Timestamp: 2025-08-04T17:59:05.674Z
Learning: In packages/fhir-sdk/src/index.ts, the FhirBundleSdk constructor intentionally mutates the input bundle's total property to ensure data consistency between bundle.total and bundle.entry.length. This is an accepted exception to the "avoid modifying objects received as parameter" guideline for cleanup purposes.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-19T18:24:41.632Z
Learnt from: thomasyopes
PR: metriport/metriport#3870
File: packages/core/src/external/ehr/bundle/job/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts:55-91
Timestamp: 2025-05-19T18:24:41.632Z
Learning: In EHR bundles related to resource diff computations, bundle creation operations that are for auditing purposes should not stop the main job flow if they fail. These operations typically use try/catch blocks that log errors without rethrowing them, which is the intended behavior.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-06-18T00:18:59.144Z
Learnt from: leite08
PR: metriport/metriport#4043
File: packages/core/src/command/consolidated/consolidated-create.ts:0-0
Timestamp: 2025-06-18T00:18:59.144Z
Learning: In the consolidated bundle creation process (packages/core/src/command/consolidated/consolidated-create.ts), the team prefers strict failure behavior where if any component (including pharmacy resources) fails to be retrieved, the entire consolidation should fail rather than gracefully degrading. This ensures data consistency and prevents incomplete bundles.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-19T18:25:32.688Z
Learnt from: thomasyopes
PR: metriport/metriport#3870
File: packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/start-job.ts:26-57
Timestamp: 2025-05-19T18:25:32.688Z
Learning: In the startCreateResourceDiffBundlesJob function (and similar functions in the codebase), the preferred approach is to let errors bubble up naturally rather than adding explicit try/catch blocks at multiple levels of the call stack.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-08T19:41:36.533Z
Learnt from: thomasyopes
PR: metriport/metriport#3788
File: packages/api/src/external/ehr/shared/utils/bundle.ts:83-93
Timestamp: 2025-05-08T19:41:36.533Z
Learning: In the Metriport codebase, the team prefers to let errors bubble up naturally in some cases rather than adding explicit error handling at every layer, as demonstrated in the refreshEhrBundle function in the bundle.ts file.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-05-31T21:58:28.502Z
Learnt from: leite08
PR: metriport/metriport#3940
File: packages/core/src/command/consolidated/search/fhir-resource/search-consolidated.ts:82-86
Timestamp: 2025-05-31T21:58:28.502Z
Learning: In packages/core/src/command/consolidated/search/fhir-resource/search-consolidated.ts, the mutable array operations using push() on resourcesMutable and hydratedMutable (lines 82-86 and 100-103) have been explicitly accepted as exceptions to the immutability guidelines after previous discussion.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.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/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-07-16T00:54:56.156Z
Learnt from: leite08
PR: metriport/metriport#4193
File: packages/carequality-sdk/src/client/carequality-fhir.ts:198-208
Timestamp: 2025-07-16T00:54:56.156Z
Learning: User leite08 accepted the risk of parameter mutation in packages/carequality-sdk/src/client/carequality-fhir.ts when modifying org.active = false directly on input parameters, choosing to proceed despite the violation of the "Avoid modifying objects received as parameter" coding guideline.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-08-15T18:57:28.999Z
Learnt from: lucasdellabella
PR: metriport/metriport#4382
File: packages/utils/src/hl7v2-notifications/reprocess-adt-conversion-bundles/common.ts:36-48
Timestamp: 2025-08-15T18:57:28.999Z
Learning: In utility scripts like packages/utils/src/hl7v2-notifications/reprocess-adt-conversion-bundles/common.ts, user lucasdellabella prefers to let bundle parsing errors fail fast rather than adding error handling, as it provides immediate visibility into data issues during script execution.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
📚 Learning: 2025-04-23T21:19:02.589Z
Learnt from: thomasyopes
PR: metriport/metriport#3709
File: packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts:63-72
Timestamp: 2025-04-23T21:19:02.589Z
Learning: In the metriport codebase, factory functions like `fetchCanvasBundle` in the Canvas EHR subsystem intentionally let errors bubble up to the caller rather than handling them at the factory function level.

Applied to files:

  • packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
🧬 Code Graph Analysis (2)
packages/core/src/external/ehr/athenahealth/index.ts (4)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • EhrStrictFhirResourceBundle (49-49)
packages/shared/src/interface/external/ehr/athenahealth/encounter.ts (1)
  • Encounter (7-7)
packages/core/src/external/ehr/document/command/fetch-document.ts (1)
  • fetchDocument (40-97)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)
  • createOrReplaceDocument (33-89)
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (12)
packages/api/src/command/mapping/cx.ts (1)
  • getCxMappingOrFail (54-63)
packages/shared/src/index.ts (2)
  • MetriportError (43-43)
  • errorToString (45-45)
packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
  • athenaSecondaryMappingsSchema (4-13)
packages/core/src/external/ehr/athenahealth/index.ts (1)
  • getEncounterAppointmentTypeIdExtension (175-180)
packages/core/src/external/fhir/shared/index.ts (1)
  • buildResourceReference (307-309)
packages/core/src/external/ehr/bundle/command/create-or-replace-bundle.ts (1)
  • createOrReplaceBundle (32-96)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • createBundleFromResourceList (56-61)
packages/core/src/external/ehr/document/command/fetch-document.ts (1)
  • fetchDocument (40-97)
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts (1)
  • getUploadUrlAndCreateDocRef (15-56)
packages/core/src/external/ehr/document/command/create-or-replace-document.ts (1)
  • createOrReplaceDocument (33-89)
packages/lambdas/src/shared/capture.ts (1)
  • capture (19-118)
packages/core/src/external/fhir/shared/extensions/derived-from.ts (1)
  • artifactRelatedArtifactUrl (6-7)
⏰ 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). (4)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (7)
packages/core/src/external/ehr/athenahealth/index.ts (4)

172-180: Helper to read Encounter appointment-type extension: LGTM

Good encapsulation. Keeping the URL constant private while exposing a reader is the right API surface.


1499-1515: Flag plumbing for Encounter adjustments is well-scoped

Adding attachAppointmentType and fetchEncounterSummary with safe defaults and gating the adjustment only for Encounter bundles is clean and minimizes blast radius.

Also applies to: 1567-1576


1603-1621: Resource-by-ID path parity with flags: LGTM

Symmetric flag support for getResourceBundleByResourceId ensures consistent behavior across both entry points.

Also applies to: 1668-1677


2001-2035: Conditional Encounter summary capture: LGTM

Checks existing HTML in S3, gates on CLOSED status, and persists via createOrReplaceDocument. Error handling is appropriate and non-intrusive.

packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts (3)

70-103: Athena-specific contribution path with strict secondary mapping requirement: LGTM

Hard-failing when mappings are missing aligns with prior decisions and avoids silent misprocessing. The two opt-ins (blacklist removal and summary upload) are gated correctly.


133-193: Encounter pruning and cascade removal: implementation looks solid

  • Filters Encounters lacking the extension or with blacklisted types.
  • Cascades removal to referencing resources.
  • Emits an audit “removed” bundle via a dedicated helper.

Good use of partition and mutation-before-audit write to ensure downstream consumers see the pruned bundle immediately.


334-363: DocumentReference enrichment with context.encounter: LGTM

Linking the DocumentReference back to Encounter improves traceability and downstream queries.

Ref: ENG-634

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 (3)
packages/api/Dockerfile.dev (3)

12-18: Optional: reduce image layers and speed builds.

You can collapse the multiple build RUNs into a single layer to shrink image size and improve cache behavior a bit:

-RUN npm run build -w packages/shared
-RUN npm run build -w packages/fhir-sdk
-RUN npm run build -w packages/commonwell-sdk
-RUN npm run build -w packages/ihe-gateway-sdk
-RUN npm run build -w packages/api-sdk
-RUN npm run build -w packages/core
-RUN npm run build -w packages/carequality-sdk
+RUN \
+  npm run build -w packages/shared && \
+  npm run build -w packages/fhir-sdk && \
+  npm run build -w packages/commonwell-sdk && \
+  npm run build -w packages/ihe-gateway-sdk && \
+  npm run build -w packages/api-sdk && \
+  npm run build -w packages/core && \
+  npm run build -w packages/carequality-sdk

Alternatively, if the root has an aggregate build that respects workspace graph, using it can simplify this section. If you want, I can help wire up a root build that builds all workspaces in dependency order.


4-10: Optional: improve Docker layer caching for faster dev builds.

Copying the entire packages/ tree before npm ci invalidates the install layer on any source change. A common pattern is:

  • COPY only the root and workspaces’ package.json/package-lock.json first
  • run npm ci
  • then COPY the full sources and run the builds

This greatly speeds iterative rebuilds.

I can provide a refactor of the Dockerfile.dev to stage the COPYs with minimal changes if you want to pursue this optimization.


2-2: Consider upgrading Dockerfile.dev base image to Node.js 20.x (optional)

Node 18 reaches EOL in 2025; moving to the latest LTS reduces security risk and keeps us aligned with supported runtimes.

• No engines.node is specified in package.json
• No GitHub Actions workflows currently pin a Node version
• Please verify:
– Local build and tests pass under Node 20
– Any actions/setup-node steps in your CI are updated to node-version: '20.x'

If all checks out, update the Dockerfile.dev:

-FROM node:18
+FROM node:20
📜 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 d227750 and e7238d6.

📒 Files selected for processing (1)
  • packages/api/Dockerfile.dev (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts:221-233
Timestamp: 2025-08-14T20:43:26.281Z
Learning: In Athena EHR integration, encounter references are housed specifically in the 'encounter' and 'context' fields as checked by the doesResourceReferToEncounter function in packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts. No broadening of encounter reference detection is needed since the current implementation matches Athena's exact data structure.
📚 Learning: 2025-03-19T13:58:17.253Z
Learnt from: leite08
PR: metriport/metriport#3463
File: packages/api-sdk/src/medical/models/patient.ts:1-1
Timestamp: 2025-03-19T13:58:17.253Z
Learning: When changes are made to SDK packages (`api-sdk`, `commonwell-sdk`, `carequality-sdk`) or `packages/shared` in the Metriport codebase, alpha versions need to be published to NPM before merging the PR.

Applied to files:

  • packages/api/Dockerfile.dev
📚 Learning: 2025-08-17T18:46:42.110Z
Learnt from: leite08
PR: metriport/metriport#4399
File: packages/scripts/deploy-api.sh:29-30
Timestamp: 2025-08-17T18:46:42.110Z
Learning: In the Metriport codebase, `/dist` directories for SDK packages (like `packages/fhir-sdk/dist`) are generated at build time and not committed to the repository. Deployment scripts correctly reference these build artifacts that exist during the CI/CD pipeline packaging step.

Applied to files:

  • packages/api/Dockerfile.dev
⏰ 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). (4)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/api/Dockerfile.dev (4)

13-13: Good catch: build fhir-sdk before downstream SDKs.

Adding packages/fhir-sdk to the local dependency build sequence is appropriate given new FHIR types/utilities usage and aligns with our SDK build expectations. The placement after packages/shared and before other SDKs looks correct.


7-7: Double-check .env handling in images.

Copying packages/api/.env into the image is convenient for local dev, but make sure this file contains dev-safe values only and never production secrets. Also ensure it’s excluded from any production Dockerfiles.

If needed, consider templating (e.g., .env.example) and mounting a local .env via bind volume for dev, rather than baking it into the image.


1-24: Align with CI/CD expectations for built SDK artifacts.

This addition is consistent with our deployment practice where SDK dist artifacts are generated at build time and not committed. Keeping this build step in the dev Dockerfile helps avoid “module not found” issues locally when new SDKs are introduced.


13-13: Confirm fhir-sdk build step readiness

  • packages/fhir-sdk/package.json defines a build script:
    "build": "tsc -p ."
  • No other workspace packages declare a dependency on fhir-sdk.
  • Please run npm run build -w packages/fhir-sdk to ensure the build completes successfully as part of the new Dockerfile.dev step.

@thomasyopes thomasyopes added this pull request to the merge queue Aug 19, 2025
Merged via the queue into develop with commit cd0b8d6 Aug 19, 2025
19 checks passed
@thomasyopes thomasyopes deleted the eng-634-athena-contribution-update branch August 19, 2025 22:58
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