-
Notifications
You must be signed in to change notification settings - Fork 72
feat(ehr): sollis contribute ehr summaries + remove bad encounters #4346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Ref: ENG-634 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
WalkthroughAdds 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
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 }
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
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
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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>
There was a problem hiding this 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 earlierThe 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 S3For 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 diagnosticsParity 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 operationAlign 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 parityPre-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
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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 contextAdding 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 asasync 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-breakingOptional 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
packages/api/src/command/medical/document/get-upload-url-and-create-doc-ref.ts
Outdated
Show resolved
Hide resolved
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
Outdated
Show resolved
Hide resolved
packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
Outdated
Show resolved
Hide resolved
packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts
Show resolved
Hide resolved
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>
There was a problem hiding this 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 defaultSourcesMake 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.
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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 goalsThis is consistent with enabling Healthie-related mappings and should broaden default coverage with no functional change here.
88-93
: Healthie integration verified – no changes requiredAll checks pass for adding
EhrSources.healthie
todefaultSources
:
EhrSources
enum (packages/shared/src/interface/external/ehr/source.ts) defineshealthie
.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[]
), andPatientMappingSource
derives fromehrSources
(which includeshealthie
), 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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()
}: {
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
Show resolved
Hide resolved
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}`); |
There was a problem hiding this comment.
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:
- 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);
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/api/src/external/ehr/shared/command/bundle/contribute-resource-diff-bundle.ts
Show resolved
Hide resolved
for (const entry of bundle.entry) { | ||
if (!entry.resource || !entry.resource.id) continue; | ||
if (doesResourceReferToEncounter(entry.resource, encounterReferences)) { | ||
resourceIdsToRemove.add(entry.resource.id); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)}`); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
There was a problem hiding this 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.
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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 appropriateImports 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 supportsattachAppointmentType
&fetchEncounterSummary
ThegetResourceBundleByResourceId
signature inpackages/core/src/external/ehr/athenahealth/index.ts
defines both flags as optional booleans (defaulting tofalse
) and forwards them intodangerouslyAdjustEncountersInBundle
. No changes required.
packages/core/src/external/ehr/athenahealth/command/get-resource-bundle-by-resource-id.ts
Outdated
Show resolved
Hide resolved
const fileExists = await s3Utils.fileExists(s3BucketName, key); | ||
if (!fileExists) return undefined; | ||
return s3Utils.getSignedUrl({ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
…-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>
There was a problem hiding this 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 idempotentTwo 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 narrowPer repo style, prefer truthy checks rather than
'in'
. The current scope (examiningencounter.reference
andcontext.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 producingType/undefined
for entries missingid
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 onid
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 anid
Changing the signature to
export function buildResourceReference<T extends Resource & { id: string }>(resource: T): stringwill 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 onentry.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 guaranteedid
.packages/core/src/external/fhir/shared/references.ts (1)
113-114
: Requireresource.id
for reference-building inputsThese constructors always create typed references from a full resource reference string
${type}/${id}
. Strengthen the XOR input types to ensureid
is present at compile time and avoid emittingType/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 statusThe 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 subjectThe 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 yourgetUploadUrlAndCreateDocRef
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.
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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 goodImport 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 lookupCentralizing 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 correctThis 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 removeLogic 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 dropsNow 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 fromvalueRelatedArtifact.display
.All occurrences of the predecessor extension are created via
createPredecessorExtensionRelatedArtifact
, which sets the ID in thedisplay
field. Therefore, returningvalueRelatedArtifact.display
here is intentional and aligns with existing semantics.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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>
There was a problem hiding this 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 IDsRepeated 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 churnA 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 narrowSwitch 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 APIThe core Athena integration stores Encounter HTML under the Encounter’s current
id
, whereas this bundle flow always uses thepredecessor
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 currententry.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.
📒 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
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 toundefined
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 (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- 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: LGTMGood encapsulation. Keeping the URL constant private while exposing a reader is the right API surface.
1499-1515
: Flag plumbing for Encounter adjustments is well-scopedAdding 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: LGTMSymmetric flag support for getResourceBundleByResourceId ensures consistent behavior across both entry points.
Also applies to: 1668-1677
2001-2035
: Conditional Encounter summary capture: LGTMChecks 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: LGTMHard-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: LGTMLinking the DocumentReference back to Encounter improves traceability and downstream queries.
Ref: ENG-634 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
There was a problem hiding this 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-sdkAlternatively, 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 beforenpm 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
– Anyactions/setup-node
steps in your CI are updated tonode-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.
📒 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 afterpackages/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 abuild
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.
Ref: ENG-634
Issues:
Description
Testing
Release Plan
Summary by CodeRabbit
New Features
Enhancements
Refactor
Chores
Documentation