-
Notifications
You must be signed in to change notification settings - Fork 72
feat(canvas): initial diff #3608
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: #2850 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
WalkthroughThis update introduces a comprehensive infrastructure for managing, diffing, and refreshing FHIR resource bundles between Metriport and Canvas EHR systems. It adds new API endpoints and internal routes for initiating and retrieving resource diff bundles, as well as for refreshing and fetching EHR bundles by resource type. The core logic includes robust S3 key management, resource deduplication, and caching mechanisms, with new utility functions and type definitions for FHIR resources. Cloud infrastructure is expanded with new Lambda functions, SQS queues, and an S3 bucket for bundle storage, all integrated into the deployment stack and configuration. Error handling and logging are streamlined, and new factories and handlers are introduced for both local and cloud-based processing of resource diff and bundle refresh workflows. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant SQS
participant Lambda
participant S3
Client->>API: POST /ehr/canvas/patient/:id/resource-diff-bundle?direction=metriport-only
API->>SQS: Send start resource diff bundle message
SQS->>Lambda: Trigger Lambda (start resource diff)
Lambda->>Lambda: Start resource diff bundle logic
Lambda->>SQS: Send compute resource diff bundle message
SQS->>Lambda: Trigger Lambda (compute resource diff)
Lambda->>S3: Write resource diff bundle to S3
Client->>API: GET /ehr/canvas/patient/:id/resource-diff-bundle?direction=metriport-only
API->>S3: Fetch resource diff bundle from S3
S3-->>API: Return bundle (if exists)
API-->>Client: Respond with bundle JSON
sequenceDiagram
participant Client
participant API
participant SQS
participant Lambda
participant S3
Client->>API: POST /internal/ehr/canvas/patient/refresh-ehr-bundles
API->>SQS: Send refresh EHR bundles message
SQS->>Lambda: Trigger Lambda (refresh EHR bundles)
Lambda->>API: Fetch fresh bundles by resource type
API->>S3: Write refreshed bundle to S3
Client->>API: GET /internal/ehr/canvas/patient/ehr-bundle?resourceType=...
API->>S3: Fetch bundle from S3 (optionally cached)
S3-->>API: Return bundle (if exists)
API-->>Client: Respond with bundle JSON
sequenceDiagram
participant Lambda
participant S3
Lambda->>S3: Fetch existing bundle (by key)
alt Bundle exists and fresh
S3-->>Lambda: Return bundle
else Bundle missing or stale
S3-->>Lambda: Return undefined
end
Lambda->>S3: Write or update bundle (if needed)
|
Ref: #2850 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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 (32)
packages/shared/src/medical/fhir/resources.ts (1)
84-88
: Consider refining the type predicate implementation.The type predicate function implementation casts the input to the target type before checking if it exists in the array. This approach contradicts the coding guidelines which state that type predicates should "only [be] applicable to narrow down the type, not to force a complete type conversion."
A more appropriate implementation would be:
export function isResourceTypeForConsolidation( resourceType: string ): resourceType is ResourceTypeForConsolidation { - return resourceTypeForConsolidation.includes(resourceType as ResourceTypeForConsolidation); + return resourceTypeForConsolidation.some(type => type === resourceType); }This implementation avoids the type casting and performs a proper runtime check that aligns better with the purpose of type predicates.
packages/shared/src/interface/external/ehr/resource-diff.ts (1)
5-7
: Type guard implementation is correct but could be improvedThe type guard correctly checks if a string is a valid
ResourceDiffDirection
value, but the type assertion can be avoided.export function isResourceDiffDirection(direction: string): direction is ResourceDiffDirection { - return Object.values(ResourceDiffDirection).includes(direction as ResourceDiffDirection); + return Object.values(ResourceDiffDirection).includes(direction as any); }Alternatively, for even better type safety:
export function isResourceDiffDirection(direction: string): direction is ResourceDiffDirection { - return Object.values(ResourceDiffDirection).includes(direction as ResourceDiffDirection); + return Object.values(ResourceDiffDirection).some(value => value === direction); }packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-factory.ts (1)
6-13
: Good use of factory pattern for environment-specific implementation.The factory function correctly determines which implementation to return based on the environment, following the coding guidelines for keeping functions small and deterministic.
Consider adding a brief comment explaining why a waitTime of 0 is used for the local implementation.
export function buildEhrComputeResourceDiffHandler(): EhrComputeResourceDiffHandler { if (Config.isDev()) { + // Local implementation with no delay for development environment const waitTimeAtTheEndInMillis = 0; return new EhrComputeResourceDiffLocal(waitTimeAtTheEndInMillis); } const ehrComputeResourceDiffQueueUrl = Config.getEhrComputeResourceDiffQueueurl(""); return new EhrComputeResourceDiffCloud(ehrComputeResourceDiffQueueUrl); }
packages/core/src/external/ehr/api/save-resource-diff.ts (2)
35-41
: ValidatematchedResourceIds
array before joining.The code joins the
matchedResourceIds
array with commas without validation. Consider adding validation to ensure the array contains valid IDs before joining.- const queryParams = new URLSearchParams({ - cxId, - patientId, - resourceId, - direction, - matchedResourceIds: matchedResourceIds.join(","), - }); + const queryParams = new URLSearchParams({ + cxId, + patientId, + resourceId, + direction, + matchedResourceIds: matchedResourceIds.filter(Boolean).join(","), + });
43-47
: Consider returning the response data.The function only verifies that response data exists but doesn't return it. If the caller never needs the response data, this is fine, but if they might need it in the future, consider modifying the return type and returning the response data.
-export async function saveResourceDiff({ +export async function saveResourceDiff<T = any>({ ehr, cxId, patientId, resourceId, direction, matchedResourceIds, }: { ehr: EhrSource; cxId: string; patientId: string; resourceId: string; direction: ResourceDiffDirection; matchedResourceIds: string[]; -}): Promise<void> { +}): Promise<T> { const { log, debug } = out(`Ehr saveResourceDiff - cxId ${cxId}`); const api = axios.create({ baseURL: Config.getApiurl("") }); const queryParams = new URLSearchParams({ cxId, patientId, resourceId, direction, matchedResourceIds: matchedResourceIds.join(","), }); const saveResourceDiffUrl = `/internal/ehr/${ehr}/patient/save-resource-diff?${queryParams.toString()}`; try { const response = await api.post(saveResourceDiffUrl); if (!response.data) throw new Error(`No body returned from ${saveResourceDiffUrl}`); debug(`${saveResourceDiffUrl} resp: ${JSON.stringify(response.data)}`); + return response.data; } catch (error) {packages/infra/lib/api-stack/api-service.ts (1)
80-156
: Consider refactoring large function.This function is over 500 lines long and handles many different concerns. Consider breaking it down into smaller, more focused functions for better maintainability.
For example, you could extract queue setup, environment variable setup, and access permissions into separate helper functions.
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts (1)
68-68
: Consider returning computation results.The function returns void, which means callers can't know what differences were found. Consider returning the result of the computation.
If
ehrResourceDiffHandler.computeResourceDiff()
returns useful information, modify this function to return that data to the caller.packages/api/src/models/resource-mapping-reversed.ts (1)
20-48
: Add database constraints and indexes for better performance.The model definition doesn't include any unique constraints or indexes, which could impact database integrity and query performance.
static setup: ModelSetup = (sequelize: Sequelize) => { ResourceMappingReversedModel.init( { ...BaseModel.attributes(), cxId: { type: DataTypes.UUID, + allowNull: false, }, patientId: { type: DataTypes.UUID, + allowNull: false, }, patientMappingExternalId: { type: DataTypes.STRING, + allowNull: false, }, resourceId: { type: DataTypes.STRING, + allowNull: false, }, source: { type: DataTypes.STRING, + allowNull: false, }, externalId: { type: DataTypes.STRING, + allowNull: false, }, }, { ...BaseModel.modelOptions(sequelize), tableName: ResourceMappingReversedModel.NAME, + indexes: [ + { + unique: true, + fields: ["patient_id", "resource_id", "source"], + name: "resource_mapping_reversed_unique_idx", + }, + { + fields: ["patient_mapping_external_id"], + name: "resource_mapping_reversed_patient_mapping_idx", + }, + ], } ); };packages/core/src/external/ehr/api/compute-resource-diff.ts (1)
9-18
: JSDoc parameters don't match function signatureThe JSDoc comment includes parameters
resourceId
andmatchedResourceIds
which are not present in the actual function signature. Instead, the function accepts aresource
parameter which isn't documented.Update the JSDoc to match the actual function parameters:
/** * Sends a request to the API to sync a patient with Metriport. * * @param ehr - The EHR source. * @param cxId - The CX ID. * @param patientId - The patient ID. - * @param resourceId - The resource ID. + * @param practiceId - The practice ID. + * @param resource - The FHIR resource. * @param direction - The direction of the resource diff. - * @param matchedResourceIds - The matched resource IDs. */packages/api/src/sequelize/migrations/2025-04-03_00_create-resource-mapping-reversed.ts (1)
16-17
: Comment is misleading for sequential operationsThe comment suggests using Promise.all for independent operations, but the migration operations are inherently sequential (constraints depend on table creation), so awaiting them one after another is correct.
Either remove this comment or clarify that in this specific case, sequential operations are required:
-// Use 'Promise.all' when changes are independent of each other +// Operations must be sequential as later operations depend on the table being created firstpackages/api/src/routes/ehr/canvas/patient.ts (3)
63-69
: Enhance API documentation with more detailsThe JSDoc comment could be more descriptive about what the resource diff process does, the purpose of the direction parameter, and what clients should expect.
/** * POST /ehr/canvas/patient/:id/resource-diff * - * Starts the resource diff process + * Starts the resource diff process to identify differences between resources in the + * specified direction (EHR to Metriport or Metriport to EHR) * @param req.params.id The ID of Canvas Patient. + * @param req.query.practiceId The ID of the Canvas Practice. + * @param req.query.direction The direction of the diff (DIFF_EHR or DIFF_METRIPORT). * @returns 200 OK */
87-93
: Enhance API documentation with response detailsThe JSDoc comment should include information about the response structure to improve API usability.
/** * GET /ehr/canvas/patient/:id/resource-diff * - * Retrieves the resource diff + * Retrieves the resource differences between Canvas and Metriport * @param req.params.id The ID of Canvas Patient. + * @param req.query.direction The direction of the diff (DIFF_EHR or DIFF_METRIPORT). - * @returns Resource diff + * @returns Array of ResourceMappingReversedMapped objects containing resource IDs and mapping information */
94-108
: Consider adding query parameter validation for robustnessWhile the direction parameter is validated, there's no validation for the existence of other required parameters (unlike in the POST route where you validate practiceId).
router.get( "/:id/resource-diff", handleParams, requestLogger, asyncHandler(async (req: Request, res: Response) => { const cxId = getCxIdOrFail(req); const canvasPatientId = getFrom("params").orFail("id", req); const direction = getFromQueryOrFail("direction", req); if (!isResourceDiffDirection(direction)) { throw new BadRequestError("Invalid direction", undefined, { direction }); } + // Optional: If practiceId is needed for context in getCanvasResourceDiffFromEhr + // const canvasPracticeId = getFromQueryOrFail("practiceId", req); const resourceIds = await getCanvasResourceDiffFromEhr({ cxId, canvasPatientId, direction }); return res.status(httpStatus.OK).json(resourceIds); }) );packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts (1)
14-19
: Consider simplifying the SQS client initialization logic.
You could unify the assignment into a single statement for brevity.- if (!sqsClient) { - this.sqsClient = new SQSClient({ region: region ?? Config.getAWSRegion() }); - } else { - this.sqsClient = sqsClient; - } + this.sqsClient = sqsClient ?? new SQSClient({ region: region ?? Config.getAWSRegion() });packages/lambdas/src/ehr-process-resource-diff.ts (2)
23-43
: Consider processing all SQS messages in a single invocation.
Currently, only the first message in the event is handled. If multiple messages arrive, reviewing them all might be needed.- const message = getSingleMessageOrFail(event.Records, lambdaName); - if (!message) return; - // process the single message + for (const record of event.Records) { + if (!record.body) continue; + const parsedBody = parseBody(record.body); + // process each message + }
64-73
: Handle JSON.parse errors gracefully.
Catching parsing errors can help generate a more direct failure message if the payload isn't valid JSON.-function parseBody(body?: unknown): ProcessResourceDiffRequest { - if (!body) throw new MetriportError(`Missing message body`); - const bodyString = typeof body === "string" ? (body as string) : undefined; - if (!bodyString) throw new MetriportError(`Invalid body`); - const bodyAsJson = JSON.parse(bodyString); - return ehrResourceDiffSchema.parse(bodyAsJson) as ProcessResourceDiffRequest; +function parseBody(body?: unknown): ProcessResourceDiffRequest { + if (!body) throw new MetriportError("Missing message body"); + const bodyString = typeof body === "string" ? body : undefined; + if (!bodyString) throw new MetriportError("Invalid body"); + try { + const bodyAsJson = JSON.parse(bodyString); + return ehrResourceDiffSchema.parse(bodyAsJson) as ProcessResourceDiffRequest; + } catch (error) { + throw new MetriportError("Failed to parse message body as JSON", error); + } }packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (1)
11-12
: Constant definition is appropriately named.
MAX_SQS_MESSAGE_SIZE
is descriptive. Consider moving it to a central constants file if this limit might be reused.packages/api/src/routes/internal/ehr/canvas/patient.ts (2)
64-92
: New/compute-resource-diff
endpoint logic is well-structured.
- Validates direction via
isResourceDiffDirection
before proceeding.- Uses
fhirResourceSchema
for request body validation, conforming to typed FHIR.- Non-blocking
computeCanvasResourceDiff
call is wrapped in acatch
, ensuring asynchronous errors are handled byprocessAsyncError
.Consider returning a JSON payload for more transparency (e.g., a short success message) instead of an empty 200.
94-122
: New/save-resource-diff
endpoint structure follows a similar pattern.
- Direction validation is consistent with the prior endpoint.
- The function
saveCanvasResourceDiff
is called asynchronously with error handling.- Efficient parameter parsing from query is good; ensure callers always pass arrays for
matchedResourceIds
.Same comment applies about possibly returning a JSON message or object confirming success.
packages/core/src/external/ehr/resource-diff/utils.ts (1)
1-19
: Imports are extensive yet domain-focused.They rely on multiple deduplication functions, indicating a modular approach. Keep evaluating if all resource-specific modules are truly needed to maintain minimal overhead.
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
16-43
: Consider adding validation for edge cases.The function correctly handles the
DIFF_EHR
direction and provides an informative error for theDIFF_METRIPORT
direction. However, there are a few considerations:
- There's no handling for a case where
direction
doesn't match either of the defined enum values.- The
matchedResourceIds.sort().join(",")
approach assumes IDs won't contain commas, which could lead to parsing ambiguity.For the sorting and joining of IDs, consider:
- externalId: matchedResourceIds.sort().join(","), + externalId: matchedResourceIds.sort().join("|"), // Or another delimiter less likely to appear in IDsFor handling unexpected directions:
} else if (direction === ResourceDiffDirection.DIFF_METRIPORT) { throw new MetriportError("Cannot save resources in this direction", undefined, { direction }); + } else { + throw new MetriportError("Invalid resource diff direction", undefined, { direction }); }packages/core/src/external/ehr/canvas/index.ts (1)
352-393
: Robust implementation of FHIR resource retrieval.The new method follows functional programming principles with good error handling, particularly for the NotFoundError case. A few recommendations:
- Consider strengthening validation by checking if
bundle.entry
exists before accessing it.- The error message for invalid resource types could be more descriptive.
- throw new MetriportError(`Invalid resource type found`, undefined, { + throw new MetriportError(`Invalid resource type found when expecting ${resourceType}`, undefined, { invalidResourceType: invalidResource.resource.resourceType, });Also, add a check for bundle.entry:
const bundle = await this.makeRequest<FhirResourceBundle>({ // ... }); + if (!bundle.entry || !Array.isArray(bundle.entry)) { + return []; + } const invalidResource = bundle.entry.find(packages/api/src/domain/resource-mapping-reversed.ts (2)
4-10
: Type guard implementation could be improved.The type guard function casts
source
toResourceMappingReversedSource
before checking if it's included, which could bypass TypeScript's type safety.export function isResourceMappingReversedSource( source: string ): source is ResourceMappingReversedSource { - return resourceMappingSource.includes(source as ResourceMappingReversedSource); + return resourceMappingSource.includes(source as any); }
12-19
: Add documentation for domain type.While the type is well-structured, it lacks documentation explaining what a "reversed" resource mapping represents. This would improve maintainability for new developers.
+/** + * Represents a mapping between a resource in the Metriport system and its corresponding + * representation in an external system in a reversed direction (from external to Metriport). + */ export type ResourceMappingReversedPerSource = { externalId: string; cxId: string; patientId: string; patientMappingExternalId: string; resourceId: string; source: ResourceMappingReversedSource; };packages/api/src/external/ehr/canvas/command/start-resource-diff.ts (1)
14-38
: Consider adding explicit error handling.The function effectively orchestrates the resource diff process but relies on called functions for error handling. For improved robustness, consider adding specific error handling within this function.
export async function startCanvasResourceDiff({ cxId, canvasPracticeId, canvasPatientId, direction, }: StartResourceDiffParams): Promise<void> { + try { const existingPatient = await getPatientMappingOrFail({ cxId, externalId: canvasPatientId, source: EhrSources.canvas, }); const metriportPatient = await getPatientOrFail({ cxId, id: existingPatient.patientId, }); const ehrResourceDiffHandler = buildEhrStartResourceDiffHandler(); await ehrResourceDiffHandler.startResourceDiff({ ehr: EhrSources.canvas, cxId, practiceId: canvasPracticeId, metriportPatientId: metriportPatient.id, ehrPatientId: canvasPatientId, direction, }); + } catch (error) { + throw new MetriportError("Failed to start Canvas resource diff", error as Error, { + cxId, + canvasPatientId, + direction + }); + } }packages/lambdas/src/ehr-start-resource-diff.ts (2)
22-22
: Consider avoiding arrow function forhandler
.According to the coding guidelines, arrow functions should be avoided in modules. Converting this export to a traditional function expression or named function would align better with the project's guidelines.
-export const handler = Sentry.AWSLambda.wrapHandler(async (event: SQSEvent) => { +export async function handler(event: SQSEvent) {
28-28
: Use standardized logging interface.The guidelines advise against using
console.log
outside of utils/infra/shared. Replace it without().log
, a custom logger, orprefixedLog
for consistent logging, error tracking, and improved maintainability.- console.log(`Running with unparsed body: ${message.body}`); + log(`Running with unparsed body: ${message.body}`);packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
3-9
: Consider stronger typing instead ofz.any()
.Although FHIR resources can vary widely, relying on
z.any()
bypasses type safety. Narrowing down the schema — or breaking it up if feasible — can yield earlier detection of incompatible resource fields during validation.z.record(z.string(), z.any()) +// Potentially replace z.any() with respective FHIR field types or partials
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts (1)
47-49
: Rename loop variable for clarity.When iterating over each sub-chunk, consider using a more descriptive name than
chunk
, which is the same name as the Lodash function. A concise rename can improve readability.-for (const chunk of chunks) { - await Promise.all(chunk.map(params => computeResourceDiff(params))); +for (const diffBatch of chunks) { + await Promise.all(diffBatch.map(params => computeResourceDiff(params)));packages/infra/lib/ehr-nested-stack.ts (1)
58-76
: New “startResourceDiff” configuration looks consistent.You've reused the same pattern of queue & lambda config as in other setups. This method ensures a clear separation of concerns. Consider factoring out repeating logic if more diffs are expected in the future.
packages/api/src/command/mapping/resource-reversed.ts (2)
16-44
: Upsert-like logic increateOrUpdateResourceMappingReversed
.This function updates an existing record or creates a new one if it doesn’t exist. If multiple concurrent requests for the same resource arrive, consider an explicit transaction or an upsert method to avoid duplicates. Otherwise, it looks good.
133-143
:deleteAllResourceMappingReverseds
for bulk deletion.Function is straightforward. Consider returning a count of deleted rows to confirm the bulk operation if needed, but it’s optional.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (34)
packages/api/src/command/mapping/resource-reversed.ts
(1 hunks)packages/api/src/command/medical/patient/consolidated-get.ts
(1 hunks)packages/api/src/domain/resource-mapping-reversed.ts
(1 hunks)packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
(1 hunks)packages/api/src/external/ehr/canvas/command/get-resource-diff.ts
(1 hunks)packages/api/src/external/ehr/canvas/command/save-resource-diff.ts
(1 hunks)packages/api/src/external/ehr/canvas/command/start-resource-diff.ts
(1 hunks)packages/api/src/models/db.ts
(3 hunks)packages/api/src/models/resource-mapping-reversed.ts
(1 hunks)packages/api/src/routes/ehr/canvas/patient.ts
(2 hunks)packages/api/src/routes/internal/ehr/canvas/patient.ts
(2 hunks)packages/api/src/sequelize/migrations/2025-04-03_00_create-resource-mapping-reversed.ts
(1 hunks)packages/core/src/external/ehr/api/compute-resource-diff.ts
(1 hunks)packages/core/src/external/ehr/api/save-resource-diff.ts
(1 hunks)packages/core/src/external/ehr/canvas/index.ts
(4 hunks)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-factory.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-factory.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/utils.ts
(1 hunks)packages/core/src/util/config.ts
(1 hunks)packages/infra/lib/api-stack.ts
(3 hunks)packages/infra/lib/api-stack/api-service.ts
(4 hunks)packages/infra/lib/ehr-nested-stack.ts
(6 hunks)packages/lambdas/src/ehr-process-resource-diff.ts
(1 hunks)packages/lambdas/src/ehr-start-resource-diff.ts
(1 hunks)packages/shared/src/interface/external/ehr/fhir-resource.ts
(1 hunks)packages/shared/src/interface/external/ehr/index.ts
(1 hunks)packages/shared/src/interface/external/ehr/resource-diff.ts
(1 hunks)packages/shared/src/medical/fhir/resources.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/shared/src/interface/external/ehr/resource-diff.ts
packages/shared/src/interface/external/ehr/index.ts
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-factory.ts
packages/shared/src/medical/fhir/resources.ts
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-factory.ts
packages/api/src/models/db.ts
packages/lambdas/src/ehr-process-resource-diff.ts
packages/api/src/models/resource-mapping-reversed.ts
packages/core/src/external/ehr/api/save-resource-diff.ts
packages/api/src/external/ehr/canvas/command/get-resource-diff.ts
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts
packages/api/src/command/medical/patient/consolidated-get.ts
packages/infra/lib/api-stack.ts
packages/api/src/external/ehr/canvas/command/start-resource-diff.ts
packages/api/src/routes/ehr/canvas/patient.ts
packages/core/src/external/ehr/resource-diff/utils.ts
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
packages/api/src/domain/resource-mapping-reversed.ts
packages/core/src/util/config.ts
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts
packages/core/src/external/ehr/canvas/index.ts
packages/shared/src/interface/external/ehr/fhir-resource.ts
packages/api/src/sequelize/migrations/2025-04-03_00_create-resource-mapping-reversed.ts
packages/lambdas/src/ehr-start-resource-diff.ts
packages/api/src/routes/internal/ehr/canvas/patient.ts
packages/core/src/external/ehr/api/compute-resource-diff.ts
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts
packages/infra/lib/api-stack/api-service.ts
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts
packages/infra/lib/ehr-nested-stack.ts
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
packages/api/src/command/mapping/resource-reversed.ts
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts
🧠 Learnings (1)
packages/api/src/sequelize/migrations/2025-04-03_00_create-resource-mapping-reversed.ts (1)
Learnt from: leite08
PR: metriport/metriport#3598
File: packages/api/src/sequelize/migrations/2025-04-03_00_drop-cq-directory-entry.ts:96-99
Timestamp: 2025-04-03T18:27:13.730Z
Learning: When reviewing database migration files, the `down` function should exactly replicate the original table schema (without necessarily adding "improvements" like default values) to ensure proper rollback functionality, even if that schema doesn't follow all best practices.
🧬 Code Definitions (18)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-factory.ts (4)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
EhrComputeResourceDiffHandler
(16-18)packages/core/src/util/config.ts (1)
Config
(9-180)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts (1)
EhrComputeResourceDiffLocal
(9-36)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (1)
EhrComputeResourceDiffCloud
(13-51)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-factory.ts (4)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts (1)
EhrStartResourceDiffHandler
(13-15)packages/core/src/util/config.ts (1)
Config
(9-180)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts (1)
EhrStartResourceDiffLocal
(13-56)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts (1)
EhrStartResourceDiffCloud
(6-30)
packages/lambdas/src/ehr-process-resource-diff.ts (2)
packages/lambdas/src/ehr-start-resource-diff.ts (1)
handler
(22-52)packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
fhirResourceSchema
(3-9)
packages/api/src/models/resource-mapping-reversed.ts (1)
packages/api/src/domain/resource-mapping-reversed.ts (2)
ResourceMappingReversed
(21-21)ResourceMappingReversedSource
(5-5)
packages/core/src/external/ehr/api/save-resource-diff.ts (1)
packages/core/src/util/config.ts (1)
Config
(9-180)
packages/api/src/external/ehr/canvas/command/get-resource-diff.ts (1)
packages/api/src/command/mapping/resource-reversed.ts (2)
ResourceMappingReversedMapped
(108-112)getMappedResourceIdsByPatientMappingExternalId
(114-131)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (2)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (2)
EhrComputeResourceDiffHandler
(16-18)ComputeResourceDiffRequest
(7-14)packages/core/src/util/config.ts (1)
Config
(9-180)
packages/api/src/external/ehr/canvas/command/start-resource-diff.ts (1)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-factory.ts (1)
buildEhrStartResourceDiffHandler
(6-13)
packages/api/src/routes/ehr/canvas/patient.ts (3)
packages/shared/src/interface/external/ehr/resource-diff.ts (1)
isResourceDiffDirection
(5-7)packages/api/src/external/ehr/canvas/command/start-resource-diff.ts (1)
startCanvasResourceDiff
(14-38)packages/api/src/external/ehr/canvas/command/get-resource-diff.ts (1)
getCanvasResourceDiffFromEhr
(17-41)
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts (5)
packages/shared/src/interface/external/ehr/fhir-resource.ts (2)
FhirResource
(10-10)fhirResourceSchema
(3-9)packages/shared/src/medical/fhir/resources.ts (1)
isResourceTypeForConsolidation
(84-88)packages/api/src/command/medical/patient/consolidated-get.ts (2)
GetConsolidatedPatientData
(50-58)getConsolidatedPatientData
(411-432)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-factory.ts (1)
buildEhrComputeResourceDiffHandler
(6-13)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
ResourceWithId
(5-5)
packages/core/src/util/config.ts (1)
packages/api/src/shared/config.ts (1)
getEnvVarOrFail
(14-14)
packages/core/src/external/ehr/canvas/index.ts (1)
packages/shared/src/interface/external/ehr/fhir-resource.ts (3)
FhirResources
(13-13)FhirResourceBundle
(24-24)fhirResourceBundleSchema
(20-23)
packages/lambdas/src/ehr-start-resource-diff.ts (2)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts (1)
EhrStartResourceDiffLocal
(13-56)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts (1)
StartResourceDiffRequest
(4-11)
packages/api/src/routes/internal/ehr/canvas/patient.ts (4)
packages/shared/src/interface/external/ehr/resource-diff.ts (1)
isResourceDiffDirection
(5-7)packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
fhirResourceSchema
(3-9)packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts (1)
computeCanvasResourceDiff
(29-78)packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
saveCanvasResourceDiff
(16-44)
packages/core/src/external/ehr/api/compute-resource-diff.ts (5)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (1)
computeResourceDiff
(28-50)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts (1)
computeResourceDiff
(12-35)packages/core/src/external/ehr/resource-diff/utils.ts (1)
computeResourceDiff
(39-174)packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
FhirResource
(10-10)packages/core/src/util/config.ts (1)
Config
(9-180)
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
packages/api/src/command/mapping/resource-reversed.ts (1)
createOrUpdateResourceMappingReversed
(16-44)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts (7)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts (2)
EhrStartResourceDiffHandler
(13-15)StartResourceDiffRequest
(4-11)packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
fhirResourceSchema
(3-9)packages/core/src/external/ehr/canvas/index.ts (1)
supportedCanvasDiffResources
(44-44)packages/core/src/external/ehr/api/compute-resource-diff.ts (1)
computeResourceDiff
(19-59)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (1)
computeResourceDiff
(28-50)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts (1)
computeResourceDiff
(12-35)packages/core/src/external/ehr/resource-diff/utils.ts (1)
computeResourceDiff
(39-174)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts (2)
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts (2)
EhrStartResourceDiffHandler
(13-15)StartResourceDiffRequest
(4-11)packages/core/src/util/config.ts (1)
Config
(9-180)
🪛 GitHub Actions: PR Check - Lambdas
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
[error] 2-2: error TS2307: Cannot find module '@***/core/external/ehr/resource-diff/process/ehr-process-resource-diff' or its corresponding type declarations.
🪛 GitHub Actions: PR Check - Core
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
[error] 2-2: error TS2307: Cannot find module '@***/core/external/ehr/resource-diff/process/ehr-process-resource-diff' or its corresponding type declarations.
🪛 GitHub Actions: PR Check - API
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
[error] 2-2: error TS2307: Cannot find module '@***/core/external/ehr/resource-diff/process/ehr-process-resource-diff' or its corresponding type declarations.
🪛 GitHub Actions: PR Check - Infra
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
[error] 2-2: error TS2307: Cannot find module '@***/core/external/ehr/resource-diff/process/ehr-process-resource-diff' or its corresponding type declarations.
🪛 GitHub Actions: PR Check - Shared
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
[error] 2-2: error TS2307: Cannot find module '@***/core/external/ehr/resource-diff/process/ehr-process-resource-diff' or its corresponding type declarations.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (62)
packages/shared/src/interface/external/ehr/index.ts (1)
3-4
: LGTM: Clean addition of module exportsThe addition of these exports follows the existing pattern and properly makes the resource diff and FHIR resource modules available to other parts of the application.
packages/api/src/models/db.ts (2)
40-40
: LGTM: Import follows existing patternThe import of
ResourceMappingReversedModel
is correctly placed with other model imports.
71-71
: LGTM: Model setup added correctlyThe
ResourceMappingReversedModel.setup
has been properly added to the models array, following the existing pattern for model initialization.packages/shared/src/interface/external/ehr/resource-diff.ts (1)
1-4
: LGTM: Well-defined enum with descriptive valuesThe
ResourceDiffDirection
enum is well-defined with clear, descriptive values that represent the direction of resource differences.packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-factory.ts (2)
1-5
: LGTM: Proper importsAll necessary imports are included for the factory implementation.
6-13
:✅ Verification successful
Factory implementation follows good design patterns
The factory function correctly implements the Factory pattern, returning different implementations based on the environment. This promotes good separation of concerns and testability.
However, the
waitTimeAtTheEndInMillis
parameter is set to 0, which effectively makes it a no-op. Consider if this is intentional or if a more meaningful default value should be used.
🏁 Script executed:
#!/bin/bash # Check how waitTimeAtTheEndInMillis is used in the local implementation rg -A 5 "waitTimeInMillis" --type tsLength of output: 15603
Action Required: Confirm Wait Time Default is Intentional
The factory implementation is solid and follows good design patterns by returning different implementations based on the environment. After verifying across similar modules (e.g., the PatientImportQuery factory and other local handlers), it appears that setting the wait time to 0 for local instances is a consistent, intentional design choice to avoid unnecessary delays in development. If this default is truly desired, consider adding a brief comment in the code explaining that a zero delay is purposefully used in local deployments. No further changes are necessary.
packages/core/src/util/config.ts (1)
161-167
: LGTM: Added environment variable getters for resource diff queues.These methods follow the established pattern in the Config class and properly use
getEnvVarOrFail
to ensure required environment variables are present.packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff.ts (1)
1-15
: Well-structured type and interface definition.The
StartResourceDiffRequest
type andEhrStartResourceDiffHandler
interface are clearly defined with appropriate fields and follow TypeScript best practices. The interface follows a command pattern that encapsulates the resource diff operation.packages/api/src/command/medical/patient/consolidated-get.ts (1)
50-58
:✅ Verification successful
LGTM: Exported type for reuse.
Making the
GetConsolidatedPatientData
type exportable is appropriate for reuse in other modules. This follows the TypeScript best practices in the coding guidelines.Consider creating a ticket to track the TODO on line 56 regarding the removal of
forceDataFromFhir
when contributed data is part of get consolidated from S3.
🌐 Web query:
What are TypeScript best practices for exporting types?
💡 Result:
TypeScript's best practices for exporting types emphasize clarity, runtime safety, and compatibility with tooling. Here are key recommendations:
Use explicit type exports
- Always use
export type
for type-only exports to ensure they're elided from runtime code[1][3][4].interface ButtonProps { onClick: () => void } export type { ButtonProps }; // ✅ Correct- Separate type exports from value exports when re-exporting[1][2]:
// ❌ Incorrect export { Button, ButtonProps }; // ✅ Correct export { Button }; export type { ButtonProps };Maintain export clarity
- Avoid mixed type/value exports using ESLint's
consistent-type-exports
rule[1][4]:// With fixMixedExportsWithInlineTypeSpecifier: true export { x, type T }; // ✅ Autofix available- Prefer named exports over default exports for types to improve import clarity[2][4]:
// ❌ Avoid default type exports export default interface Foo {...} // ✅ Use named exports export interface Foo {...}Tooling integration
- Enable type-aware ESLint rules like
@typescript-eslint/consistent-type-exports
[1]- Use inline type specifiers for mixed imports/exports (TS 4.5+)[1][4]:
import { type Foo, Bar } from './module'; // ✅ Clear type/value distinctionModule design considerations
- Minimize exported API surface - only export types needed externally[2][4]
- Use file-scoped modules instead of namespaces for type organization[2][4]
- Avoid mutable type exports as they can lead to runtime inconsistencies[2]
For projects using
--isolatedModules
, follow these practices strictly as the compiler requires explicit type exports[1][3]. While modern bundlers can automatically elide type-only exports, explicitexport type
usage improves codebase readability and tooling compatibility[3][4][6].Citations:
- 1: https://typescript-eslint.io/rules/consistent-type-exports
- 2: https://google.github.io/styleguide/tsguide.html
- 3: https://www.typescriptlang.org/docs/handbook/modules/reference.html
- 4: https://www.typescriptlang.org/docs/handbook/2/modules.html
- 5: https://docs.aws.amazon.com/prescriptive-guidance/latest/best-practices-cdk-typescript-iac/typescript-best-practices.html
- 6: https://www.youtube.com/watch?v=0-8v7XkKiHc
Approval: Exported Type Follows TypeScript Best Practices
The exportedGetConsolidatedPatientData
type uses the recommended explicit type export pattern, aligning well with TypeScript best practices for clarity and maintaining a clean runtime output. The usage ofexport type
here is appropriate for ensuring that type-only artifacts are elided during the compilation process.
- Please create a ticket to address the TODO on line 56 regarding the future removal of
forceDataFromFhir
once contributed data is integrated from S3.packages/core/src/external/ehr/api/save-resource-diff.ts (2)
8-32
: Good job with function documentation and parameter typing.The function has a clear JSDoc comment explaining its purpose and parameters. The parameters are properly typed with a TypeScript interface. This is excellent for code readability and maintainability.
47-59
: Good error handling implementation.The error handling follows best practices by capturing the original error as the cause, logging the error, and providing detailed context information for debugging.
packages/infra/lib/api-stack/api-service.ts (4)
102-103
: LGTM: New resource diff queue parameters added correctly.The resource diff queue parameters are properly integrated into the existing parameter list, following the established pattern for other queues.
140-141
: LGTM: Parameter type definitions match function signature.The parameter types for the new queues are correctly defined as
IQueue
, maintaining type safety.
264-265
: LGTM: Environment variables properly set for resource diff queues.The environment variables for the resource diff queues follow the naming pattern of other queue URLs.
419-428
: LGTM: Queue access permissions correctly granted.Access to the new queues is properly granted using the
provideAccessToQueue
function, consistent with how other queues are handled.packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts (1)
29-68
: Good job on implementing the resource comparison logic.The function properly retrieves patient data, validates resource types, and fetches the appropriate resources based on the diffing direction. The error handling is also well-implemented.
packages/api/src/models/resource-mapping-reversed.ts (1)
8-19
: LGTM: Model properly extends BaseModel and implements interface.The model correctly extends BaseModel and implements the ResourceMappingReversed interface, ensuring it conforms to the expected contract. The properties are properly declared with their types.
packages/core/src/external/ehr/api/compute-resource-diff.ts (1)
47-58
: LGTM! Error handling follows best practicesThe error handling correctly logs the error with context and throws a MetriportError with appropriate additional information, following the coding guidelines.
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts (1)
22-35
: LGTM! Clean implementation of resource diff computationThe implementation follows the functional programming style recommended in the coding guidelines - it's deterministic with clear inputs and outputs, and handles the process in a clean sequence of steps.
packages/api/src/sequelize/migrations/2025-04-03_00_create-resource-mapping-reversed.ts (1)
75-89
: Down migration looks correctThe down migration correctly reverses all changes made in the up migration, including constraint removal, index removal, and table deletion, in the proper order. This follows the best practice mentioned in the retrieved learning about ensuring proper rollback functionality.
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-cloud.ts (2)
21-23
: Implementation looks good.
The method logic is straightforward and aligns with best practices for SQS message sending.
24-28
:❓ Verification inconclusive
Confirm that the targeted queue is a FIFO queue.
Thefifo: true
option requires the queue name to end with.fifo
in AWS SQS. Verify that the queue is indeed FIFO.
🏁 Script executed:
#!/bin/bash # Description: Confirm that the SQS queue used here ends with .fifo when using fifo: true rg "EHR_START_RESOURCE_DIFF_QUEUE_URL"Length of output: 261
Action Required: Verify that the SQS queue is configured as a FIFO queue.
The SQS client is explicitly sending messages with
{ fifo: true }
, which requires that the queue name end with.fifo
. Although the environment variableEHR_START_RESOURCE_DIFF_QUEUE_URL
is sourced from configuration (seepackages/core/src/util/config.ts
) and is set fromehrStartResourceDiffQueue.queueUrl
in the infra code (packages/infra/lib/api-stack/api-service.ts
), please confirm that the queue is instantiated with a name ending in.fifo
.
- Action Item: In
packages/infra/lib/api-stack/api-service.ts
, verify that when the queue is created (e.g., via anew sqs.Queue
), itsqueueName
explicitly includes the.fifo
suffix.- Action Item: Confirm that the environment variable
EHR_START_RESOURCE_DIFF_QUEUE_URL
indeed points to that FIFO queue.packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (2)
5-14
: Check if partial properties are suitable.
In some cases, certain fields might be missing or defaulted. Verify if all properties should be strictly required or if you want optional fields.
16-18
: Interface structure is consistent.
The contract forcomputeResourceDiff
is concise and complements the domain logic.packages/infra/lib/api-stack.ts (2)
370-373
: Integration of resource diff Lambdas looks consistent.
The new references tostartResourceDiffLambda
andcompleteResourceDiffLambda
fit the EHR resource difference flow.
595-596
: Environment variables set correctly.
ProvidingAPI_URL
to these new Lambdas ensures they can call back into the API service if needed.packages/lambdas/src/ehr-process-resource-diff.ts (2)
31-31
: Potential mismatch with AI summary about property naming.
The code referencespatientId
, whereas the summary mentionsmetriportPatientId
andehrPatientId
. Verify that the correct property is being used.Likely an incorrect or invalid review comment.
55-62
: Zod schema is well-defined.
The usage ofz.nativeEnum
and clear field validation is appropriate for guaranteeing a strict payload structure.packages/api/src/external/ehr/canvas/command/get-resource-diff.ts (3)
1-10
: Imports look consistent and well-organized.They align with the domain boundaries, bringing in only the necessary external and local modules. There are no obvious naming or namespace conflicts here.
11-15
: Type definition is clear and strongly typed.The
GetResourceDiffParams
type concisely declares required fields. Ensure all call sites provide these fields.
17-41
:❓ Verification inconclusive
Function is well-structured and uses clear early returns.
- ``: Verify that all valid directions are indeed checked. For any future extension, consider handling an unexpected direction more explicitly or adding a default case to aid debugging.
- The error thrown for
DIFF_METRIPORT
ensures no silent failures.- Consider adding logging for the empty array return at line 40 to confirm no diff was found.
🏁 Script executed:
#!/bin/bash # Searching for getCanvasResourceDiffFromEhr usage to ensure its return values are handled properly rg -A 5 "getCanvasResourceDiffFromEhr"Length of output: 1911
Review on getCanvasResourceDiffFromEhr: Direction Handling and Debug Logging
The function is well-structured with clear early returns:
- For
DIFF_EHR
, it returns the expected resource mappings.- For
DIFF_METRIPORT
, throwing an error prevents silent failure.- The default branch returns an empty array, which is handled by the caller in the API route.
Please consider:
- Explicitly handling unexpected
direction
values (e.g., through a default case that logs a warning or error) to aid in debugging future extensions.- Adding logging when returning an empty array to clearly indicate when no diff was found.
These adjustments would improve debuggability and overall robustness if new directions are introduced.
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (2)
1-10
: Imports comply with layering and domain context.Dependencies are minimal, referencing shared code and config.
13-52
: Class structure neatly encapsulates the SQS logic.
- The constructor pattern ensures
sqsClient
is either injected or created on-demand, which improves testability.- The payload size check is a good safeguard against oversize messages.
- Consider logging successful requests for tracking. Otherwise, all essential error-handling is in place.
packages/api/src/routes/internal/ehr/canvas/patient.ts (1)
2-2
: Added imports for EHR resource-diff functionality.These appear relevant to the new endpoints below. No concerns with naming or references here.
Also applies to: 8-8, 10-10
packages/core/src/external/ehr/resource-diff/utils.ts (2)
20-38
: Error handling imports and the localResourceWithId
type are consistent.Use of
MetriportError
centralizes error creation. Type import from./compute/ehr-compute-resource-diff
is aligned with the existing interface.
39-174
:❓ Verification inconclusive
Logic for
computeResourceDiff
is clear but warrants careful testing.
- The short-circuit return if resource already exists prevents duplicates.
- Switch statements effectively branch to specialized deduplication functions.
- Returning an empty array if deduplication reduces the resource set ensures no diff is reported.
- ``: Verify correctness of the condition at line 172. If any dedup results in equal or fewer items, does that always mean no new resource? Confirm with tests or logs.
🏁 Script executed:
#!/bin/bash # Searching for calls to computeResourceDiff to see if the caller depends on the length check logic rg -A 5 "computeResourceDiff"Length of output: 8989
Review Comment: Verify Deduplication Condition in
computeResourceDiff
The overall structure is clear and the specialized deduplication functions are consistently used. Notably:
- The early return when a resource already exists avoids duplicate entries.
- The switch-case branches cleanly delegate to the relevant deduplication function.
- The function returns an empty array when the deduplicated list’s length increases, implying a unique addition.
However, please confirm that the condition at line 172—
if (deduplicatedResources.length > deduplicatedResourceInitial.length) return [];—is robust. Specifically:
- Validate that when deduplication yields an equal or reduced count (as might happen when a new resource is merged with an existing one), returning the diff (i.e., all existing resource IDs) correctly signals that no additional unique resource was effectively added.
- Review how this return value is consumed downstream (e.g., in
ehr-start-resource-diff-local.ts
and compute modules) to ensure that an empty array versus a non-empty diff is interpreted as intended.- Add targeted tests or logging to confirm the behavior under edge cases.
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
8-14
: LGTM: Clear type definition.The
SaveResourceDiffParams
type has well-defined parameters with appropriate types.packages/core/src/external/ehr/canvas/index.ts (2)
13-27
: Good addition of required imports.The imports are correctly structured to add the new dependencies needed for the FHIR resource handling functionality.
44-44
:✅ Verification successful
Verify this constant's usage.
The
supportedCanvasDiffResources
constant is defined but not used within this file. Ensure it's properly utilized in related functionality.
🏁 Script executed:
#!/bin/bash # Find where supportedCanvasDiffResources is used rg "supportedCanvasDiffResources" --type tsLength of output: 473
Constant usage verified in project.
The constant
supportedCanvasDiffResources
is imported and used inpackages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
to check if a resource type is included, ensuring proper functionality. No changes are required.packages/api/src/domain/resource-mapping-reversed.ts (1)
21-21
: LGTM: Clean interface extension.The interface properly extends BaseDomain and incorporates the resource mapping properties.
packages/api/src/external/ehr/canvas/command/start-resource-diff.ts (1)
7-12
: LGTM: Well-defined parameter type.The parameters are well-structured with appropriate types.
packages/lambdas/src/ehr-start-resource-diff.ts (1)
63-72
: Looks good!The Zod-based validation and structured error handling in
parseBody
adhere to best practices. The approach is clear and helps ensure data correctness.packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
1-25
: All set here!The definitions for FHIR resource schemas are well-organized, and the use of Zod encourages safe parsing and validation.
packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts (1)
13-55
: No concerns with the logic.The class effectively batches and processes EHR resource diffs. The clean separation of directions and final wait time is clear and follows the design patterns well.
packages/infra/lib/ehr-nested-stack.ts (9)
16-17
: All good adding new wait times for resource diff.These values are consistent with the existing pattern of per-minute thresholds (e.g.,
waitTimePatientSync
) and follow the same approach for controlling throughput.
31-31
: Shorter alarm threshold may cause more frequent alerts.You reduced
alarmMaxAgeOfOldestMessage
from two days to two hours. Ensure that this shorter window won't lead to false alarms, especially if there's a temporary spike in the queue.
50-50
: Watch out for a tighter time window for Elation link alarms.Similarly, the
alarmMaxAgeOfOldestMessage
reduction to two hours might be aggressive. Validate whether this is sufficient for your Elation link flows.
77-95
: “completeResourceDiff” configuration introduces a higher alarm threshold.Raising
maxMessageCountAlarmThreshold
from 5,000 to 15,000 drastically delays alerts when there's a backlog. Make sure that’s intended, and that you won’t miss earlier warnings if the queue grows quickly.
99-100
: Exporting new diff configs.Exposing
startResourceDiff
andcompleteResourceDiff
is consistent with how the other queues are declared. Looks good.
141-144
: Adding new class fields for resource diff.These additional Lambda and Queue properties align with your new resource diff logic. No issues, just ensure references to these new fields are properly tested.
171-190
: Setup methods for resource diff queues.These calls to
setupStartResourceDiff
andsetupCompleteResourceDiff
keep the stack constructor tidy. Double-check that environment variables and alarms are configured as expected.
310-367
: Implementation of “setupStartResourceDiff.”This is consistent with the existing pattern. The code is clear, respects the established naming, and includes FIFO and a DLQ. Great job.
369-426
: Implementation of “setupCompleteResourceDiff.”Same consistent pattern for the second queue. Using identical structure ensures maintainability.
packages/api/src/command/mapping/resource-reversed.ts (9)
1-2
: Imports for UUID and NotFoundError are appropriate.You’re bringing in
uuidv7
and a sharedNotFoundError
, which aligns with the approach of raising typed errors.
3-8
: Domain-related imports are correctly scoped.Referencing
ResourceMappingReversed
andResourceMappingReversedPerSource
from the domain is consistent with your layering approach.
9-15
: Type definitions for reversed mapping queries.Using
Omit
here keeps the code DRY and ensures partial usage ofResourceMappingReversedParams
. This is a neat approach.
46-57
:getResourceMappingReversed
returns undefined when not found.That’s a consistent design choice for optional lookups. No issues.
59-75
:getResourceMappingReversedOrFail
throwsNotFoundError
properly.Good approach for strict lookups—keeps the calling code simpler.
77-88
:getResourceMappingModelReversed
returns the full model.It’s purposeful that here you’re returning the model itself rather than
dataValues
. That offers more flexibility if you need instance-level methods.
90-106
:getResourceMappingModelReversedOrFail
similarly throws on misses.Keeps a consistent code pattern and fosters clarity for the caller.
108-113
: Typed structure for reversed mappings.
ResourceMappingReversedMapped
is well-defined. Helps keep calls typed and straightforward.
114-131
:getMappedResourceIdsByPatientMappingExternalId
retrieving multiple resource mappings.A clear approach: fetch all records, map them to typed objects. No concerns here.
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
Outdated
Show resolved
Hide resolved
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
Outdated
Show resolved
Hide resolved
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts
Outdated
Show resolved
Hide resolved
Ref: #2850 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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 (3)
packages/core/src/external/ehr/resource-diff/utils.ts (3)
40-149
: Consider breaking down the complex function into smaller unitsThe
computeResourceDiff
function handles multiple responsibilities: validation, deduplication, and derivation checking. Consider refactoring it into smaller, more focused functions for better maintainability and testing.Example refactoring approach:
function validateResourceTypes( existingResources: ResourceWithId[], newResource: ResourceWithId ): void { // Validation logic here } function deduplicateResources( resources: ResourceWithId[], resourceType: string ): Resource[] { // Deduplication switch logic here } export function computeResourceDiff({ existingResources, newResource, }: { existingResources: ResourceWithId[]; newResource: ResourceWithId; }): string[] { validateResourceTypes(existingResources, newResource); if (existingResources.some(resource => resource.id === newResource.id)) return []; const resources = existingResources.concat([newResource]); const deduplicatedResources = deduplicateResources(resources, newResource.resourceType); // Rest of the function... }
151-157
: Add documentation for the helper functionThe
resourceIsDerivedFromExistingResource
function performs an important check, but its purpose and behavior aren't immediately clear. Consider adding a JSDoc comment to explain its purpose.+/** + * Checks if a resource is derived from another resource by looking for + * a specific extension in the resource's extension array. + * + * @param resource - The resource to check + * @returns true if the resource has the artifact related extension, false otherwise + */ function resourceIsDerivedFromExistingResource(resource: Resource): boolean {
152-152
: Improve type safety with type guardInstead of using
in
operator directly, consider using a proper type guard for better type safety and readability.- if (!("extension" in resource)) return false; + if (!resource.extension || !Array.isArray(resource.extension)) return false;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (2)
packages/core/src/external/ehr/resource-diff/utils.ts
(1 hunks)packages/core/src/fhir-deduplication/shared.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/core/src/fhir-deduplication/shared.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/core/src/external/ehr/resource-diff/utils.ts
🧬 Code Definitions (1)
packages/core/src/external/ehr/resource-diff/utils.ts (5)
packages/core/src/external/ehr/api/compute-resource-diff.ts (1)
computeResourceDiff
(19-59)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-cloud.ts (1)
computeResourceDiff
(28-50)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff-local.ts (1)
computeResourceDiff
(12-35)packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
ResourceWithId
(5-5)packages/core/src/fhir-deduplication/shared.ts (1)
artifactRelatedArtifactUrl
(42-43)
🔇 Additional comments (1)
packages/core/src/external/ehr/resource-diff/utils.ts (1)
40-149
:❓ Verification inconclusive
Verify correct handling of all FHIR resource types
The function handles many FHIR resource types, but make sure all relevant types for your application are covered. Consider adding a test to verify that all supported resource types are handled appropriately.
🏁 Script executed:
#!/bin/bash # Check if there are any FHIR resource types imported but not handled in the switch statement # First, get all imported resource types imported_types=$(cat packages/core/src/external/ehr/resource-diff/utils.ts | grep -P "import \{[\s\S]*\} from \"@medplum/fhirtypes\";" | grep -oP "(?<=\s)[A-Z][a-zA-Z]+(?=,|\n|\})") # Then, get all resource types handled in the switch statement handled_types=$(cat packages/core/src/external/ehr/resource-diff/utils.ts | grep -P "case \"[A-Z][a-zA-Z]+\":" | grep -oP "(?<=\")[A-Z][a-zA-Z]+(?=\")") # Compare the two lists echo "Imported resource types:" echo "$imported_types" | sort echo echo "Handled resource types in switch statement:" echo "$handled_types" | sort echo echo "Resource types imported but not handled:" comm -23 <(echo "$imported_types" | sort) <(echo "$handled_types" | sort) echo echo "Resource types handled but not imported:" comm -13 <(echo "$imported_types" | sort) <(echo "$handled_types" | sort)Length of output: 2212
Action Required: Ensure Comprehensive FHIR Resource Type Coverage
The current implementation handles these resource types:
- AllergyIntolerance
- Composition
- Condition
- Encounter
- FamilyMemberHistory
- Immunization
- Location
- Medication
- MedicationAdministration
- MedicationRequest
- MedicationStatement
- Observation
- Organization
- Practitioner
- Procedure
- RelatedPerson
However, our automated check to compare imported FHIR types versus the ones handled was inconclusive due to an error with the missing
comm
command (and no imported types were identified via our script). To mitigate any risk of unsupported resource types slipping through, please manually verify that all FHIR resource types relevant to our application are accounted for. Additionally, consider adding a dedicated test to auto-verify that the switch statement remains aligned with the complete set of supported resource types.
… 2850-canvas-diff Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
… 2850-canvas-diff Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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 (4)
packages/api/src/command/mapping/resource-reversed.ts (4)
16-47
: Consider relying on Sequelize’s built-in timestamps forupdatedAt
.Manually setting
existing.updatedAt = new Date();
alongsideexisting.changed("externalId", true);
may be redundant iftimestamps
is enabled. Typically, assigningexisting.externalId = externalId
automatically marks the attribute as changed. You might simplify by removing these explicit updates and rely on Sequelize’s built-in behavior.
49-60
: Prefer using.get({ plain: true })
instead ofdataValues
.Returning
dataValues
works, but using.get({ plain: true })
or.toJSON()
can improve clarity and avoid accidental references to the underlying Sequelize model fields.
117-134
: Potential pagination.If this endpoint is called for a large dataset, consider adding pagination or filtering to protect against excessive data loads.
136-146
: Optional use of transactions or concurrency checks.
destroy
queries all matching rows. If concurrency or partial updates are a concern, consider wrapping in a transaction or ensuring DB constraints align with your usage.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (1)
packages/api/src/command/mapping/resource-reversed.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/api/src/command/mapping/resource-reversed.ts
🧬 Code Definitions (1)
packages/api/src/command/mapping/resource-reversed.ts (3)
packages/api/src/domain/resource-mapping-reversed.ts (2)
ResourceMappingReversedPerSource
(12-19)ResourceMappingReversed
(21-21)packages/api/src/models/resource-mapping-reversed.ts (1)
ResourceMappingReversedModel
(8-49)packages/shared/src/index.ts (1)
NotFoundError
(38-38)
🔇 Additional comments (9)
packages/api/src/command/mapping/resource-reversed.ts (9)
1-2
: Looks good.Imports for
uuidv7
andNotFoundError
are appropriately placed.
3-6
: Well-structured domain imports.No issues identified with the domain/type imports.
7-7
: Model import is clear and concise.All good here.
9-10
: Straightforward type alias.No issues with
ResourceMappingReversedParams
definition.
11-15
: Appropriate usage ofOmit
utility.The type
ResourceMappingReversedLookUpParams
neatly excludes unneeded properties.
62-78
: Clear error handling.Throwing a
NotFoundError
if the resource is missing is a clean way to handle retrieval failures.
80-91
: Returning the model instance.This function is consistent with
getResourceMappingModelReversed
usage. No concerns here.
93-109
: *Good use of a dedicated OrFail variant.Mirrors the logic from
getResourceMappingReversedOrFail
, providing a model-based not-found check.
111-115
: Structured type for mapped resource.
ResourceMappingReversedMapped
is clear and matches typical usage for returning minimal fields.
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
♻️ Duplicate comments (4)
packages/core/src/external/ehr/resource-diff/utils.ts (4)
49-49
: Fix typo in variable nameThere's a typo in the variable name
mewResourceType
- it should benewResourceType
.- const mewResourceType = newResource.resourceType; + const newResourceType = newResource.resourceType;
68-68
: Same typo in switch statementThe same typo from above is used in the switch condition.
- switch (mewResourceType) { + switch (newResourceType) {
141-141
: Same typo in error messageThe same typo appears in the error message template string.
- throw new BadRequestError(`Unsupported resource type: ${mewResourceType}`, undefined, { + throw new BadRequestError(`Unsupported resource type: ${newResourceType}`, undefined, {
142-142
: Same typo in error detailsThe typo also affects the error details object.
- resourceType: mewResourceType, + resourceType: newResourceType,
🧹 Nitpick comments (10)
packages/core/src/external/ehr/api/save-resource-diff.ts (1)
37-43
: Consider potential issue with array joining.When joining
matchedResourceIds
with commas, there could be potential issues if any of the IDs themselves contain commas.Consider using a different delimiter or URL-safe encoding if you expect complex IDs:
- matchedResourceIds: matchedResourceIds.join(","), + matchedResourceIds: JSON.stringify(matchedResourceIds), // Or another approach that handles special characterspackages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
33-33
: Fix typo in variable name.There's a typo in the variable name
fonud
which should befound
.- const fonud = matchedResourceIds.length > 0; + const found = matchedResourceIds.length > 0;packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
3-9
: Well-structured schema definition with an important considerationThe schema definition using
z.intersection
allows for flexible FHIR resource definitions by combining fixed fields with an open record type. This approach is common for handling FHIR resources where certain properties are required while accommodating additional arbitrary properties.However, be mindful that using
z.any()
in the record type removes type safety for those additional properties. While this flexibility is often necessary for FHIR resources, it may be worth considering if there are common optional properties that could be explicitly typed for better validation.packages/core/src/external/ehr/canvas/index.ts (2)
43-59
: Document the roadmap for supported resourcesThe array contains many commented-out resource types with only "DiagnosticReport" currently enabled. This suggests a phased implementation approach, but lacks documentation explaining the current state and future plans.
export const supportedCanvasDiffResources = [ + // Current implementation only supports DiagnosticReport. + // The commented resources below represent the planned roadmap for future support. /* "AllergyIntolerance", "Condition", */ "DiagnosticReport", /* "Encounter", */ //"Medication", /* "MedicationStatement", "MedicationRequest", //"Observation", "Procedure", */ ];
373-416
: Well-structured method with comprehensive error handlingThe
getFhirResourcesByResourceType
method is well-implemented with proper parameter typing, URL construction, and error handling. It correctly handles the NotFoundError case by returning an empty array and properly validates the resource types in the response.Two suggestions for improvement:
- Consider adding a more descriptive error message in the MetriportError that includes the expected resource type
- The error handling could be more specific to capture different error scenarios
throw new MetriportError(`Invalid resource type found`, undefined, { invalidResourceType: invalidResource.resource.resourceType, + expectedResourceType: resourceType, });
try { // ... existing code } catch (error) { if (error instanceof NotFoundError) return []; + if (error instanceof MetriportError) { + // Re-throw MetriportError instances (including our own validation errors) + throw error; + } + // Handle other types of errors (network, parsing, etc.) + throw new MetriportError(`Failed to fetch ${resourceType} resources`, error, additionalInfo); }packages/core/src/external/ehr/resource-diff/utils.ts (5)
42-48
: Add JSDoc comment to describe the function's purpose and return valueAdd documentation to explain what this function does, what its parameters represent, and what the returned string array signifies.
+/** + * Computes the difference between existing resources and a new resource. + * Determines if a new resource should be kept after deduplication with existing resources. + * @param existingResources An array of existing resources with IDs + * @param newResource A new resource with ID to check against existing resources + * @returns An array of existing resource IDs if the new resource should be ignored, empty array if the new resource should be kept + */ export function computeResourceDiff({ existingResources, newResource, }: { existingResources: ResourceWithId[]; newResource: ResourceWithId; }): string[] {
66-144
: Extract deduplication logic to a separate functionThe switch statement is lengthy and makes the function harder to read. Consider extracting it to a separate function for better readability and maintainability.
const resources = existingResources.concat([newResource]); - let deduplicatedResources: Resource[]; - switch (mewResourceType) { - case "AllergyIntolerance": - deduplicatedResources = deduplicateAllergyIntolerances( - resources as AllergyIntolerance[] - ).combinedResources; - break; - // ...other cases... - default: - throw new BadRequestError(`Unsupported resource type: ${mewResourceType}`, undefined, { - resourceType: mewResourceType, - }); - } + const deduplicatedResources = deduplicateResources(resources, newResourceType); // ... later in the file +/** + * Deduplicates resources based on their resource type + * @param resources The resources to deduplicate + * @param resourceType The type of resources being deduplicated + * @returns The deduplicated resources + */ +function deduplicateResources(resources: Resource[], resourceType: string): Resource[] { + switch (resourceType) { + case "AllergyIntolerance": + return deduplicateAllergyIntolerances( + resources as AllergyIntolerance[] + ).combinedResources; + // ...other cases... + default: + throw new BadRequestError(`Unsupported resource type: ${resourceType}`, undefined, { + resourceType, + }); + } +}
158-164
: Add JSDoc comment for helper functionAdd documentation to explain the purpose of the
resourceIsDerivedFromExistingResource
function and what it checks for.+/** + * Determines if a resource is derived from an existing resource by checking for a specific extension. + * @param resource The resource to check + * @returns True if the resource is derived from an existing resource, false otherwise + */ function resourceIsDerivedFromExistingResource(resource: Resource): boolean { if (!("extension" in resource)) return false; const derivedFrom = resource.extension.find( extension => extension.url === artifactRelatedArtifactUrl ); return derivedFrom !== undefined; }
159-159
: Use truthy syntax for checking extensionsAccording to the coding guidelines, prefer truthy syntax instead of using the
in
operator.- if (!("extension" in resource)) return false; + if (!resource.extension) return false;
146-155
: Consider adding more descriptive variable namesThe return logic here is a bit hard to follow at first glance. Consider using more descriptive variable names to make the intent clearer.
const existingResourceIds = existingResources.map(resource => resource.id); - const newResourceInDeduplicatedResources = deduplicatedResources.find( + const newResourceSurvivedDeduplication = deduplicatedResources.find( resource => resource.id === newResource.id ); - if (!newResourceInDeduplicatedResources) return existingResourceIds; + if (!newResourceSurvivedDeduplication) return existingResourceIds; const newResourceIsDerivedFromExistingResource = resourceIsDerivedFromExistingResource( - newResourceInDeduplicatedResources + newResourceSurvivedDeduplication ); - if (newResourceIsDerivedFromExistingResource) return existingResourceIds; + // If resource is derived, we should use existing resources + if (newResourceIsDerivedFromExistingResource) return existingResourceIds; + // New resource should be kept return [];
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (9)
packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
(1 hunks)packages/api/src/external/ehr/canvas/command/save-resource-diff.ts
(1 hunks)packages/api/src/routes/ehr/canvas/patient.ts
(2 hunks)packages/api/src/routes/internal/ehr/canvas/patient.ts
(2 hunks)packages/core/src/external/ehr/api/save-resource-diff.ts
(1 hunks)packages/core/src/external/ehr/canvas/index.ts
(4 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
(1 hunks)packages/core/src/external/ehr/resource-diff/utils.ts
(1 hunks)packages/shared/src/interface/external/ehr/fhir-resource.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts
- packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/core/src/external/ehr/api/save-resource-diff.ts
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts
packages/core/src/external/ehr/resource-diff/utils.ts
packages/api/src/routes/internal/ehr/canvas/patient.ts
packages/api/src/routes/ehr/canvas/patient.ts
packages/core/src/external/ehr/canvas/index.ts
packages/shared/src/interface/external/ehr/fhir-resource.ts
🧬 Code Graph Analysis (6)
packages/core/src/external/ehr/api/save-resource-diff.ts (5)
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
SaveResourceDiffParams
(8-14)packages/shared/src/interface/external/ehr/source.ts (1)
EhrSource
(7-7)packages/core/src/util/log.ts (3)
out
(29-34)debug
(24-27)log
(9-22)packages/core/src/util/config.ts (1)
Config
(9-186)packages/shared/src/index.ts (1)
MetriportError
(37-37)
packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (5)
packages/core/src/external/ehr/api/save-resource-diff.ts (1)
SaveResourceDiffParams
(8-15)packages/api/src/command/mapping/patient.ts (1)
getPatientMappingOrFail
(48-62)packages/api/src/command/medical/patient/get-patient.ts (1)
getPatientOrFail
(289-293)packages/api/src/command/mapping/resource-reversed.ts (1)
createOrUpdateResourceMappingReversed
(16-47)packages/shared/src/index.ts (1)
MetriportError
(37-37)
packages/core/src/external/ehr/resource-diff/utils.ts (7)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
ResourceWithId
(5-5)packages/shared/src/index.ts (1)
BadRequestError
(36-36)packages/core/src/fhir-deduplication/resources/encounter.ts (1)
deduplicateEncounters
(38-47)packages/core/src/fhir-deduplication/resources/immunization.ts (1)
deduplicateImmunizations
(27-39)packages/core/src/fhir-deduplication/resources/observation.ts (1)
deduplicateObservations
(20-32)packages/core/src/fhir-deduplication/resources/procedure.ts (1)
deduplicateProcedures
(48-57)packages/core/src/fhir-deduplication/shared.ts (1)
artifactRelatedArtifactUrl
(42-43)
packages/api/src/routes/internal/ehr/canvas/patient.ts (6)
packages/api/src/routes/util.ts (1)
getFromQueryAsArrayOrFail
(79-83)packages/shared/src/interface/external/ehr/resource-diff.ts (1)
isResourceDiffDirection
(5-7)packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
fhirResourceSchema
(3-9)packages/api/src/external/ehr/canvas/command/compute-resource-diff.ts (1)
computeCanvasResourceDiff
(33-120)packages/core/src/util/error/shared.ts (1)
processAsyncError
(56-64)packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (1)
saveCanvasResourceDiff
(16-45)
packages/api/src/routes/ehr/canvas/patient.ts (6)
packages/api/src/routes/util.ts (4)
asyncHandler
(10-28)getCxIdOrFail
(140-144)getFrom
(131-133)getFromQueryOrFail
(61-65)packages/shared/src/interface/external/ehr/resource-diff.ts (1)
isResourceDiffDirection
(5-7)packages/shared/src/index.ts (1)
BadRequestError
(36-36)packages/api/src/external/ehr/canvas/command/start-resource-diff.ts (1)
startCanvasResourceDiff
(14-38)packages/core/src/util/error/shared.ts (1)
processAsyncError
(56-64)packages/api/src/external/ehr/canvas/command/get-resource-diff.ts (1)
getCanvasResourceDiffFromEhr
(17-41)
packages/core/src/external/ehr/canvas/index.ts (2)
packages/shared/src/interface/external/ehr/fhir-resource.ts (3)
FhirResources
(13-13)FhirResourceBundle
(24-24)fhirResourceBundleSchema
(20-23)packages/shared/src/index.ts (2)
MetriportError
(37-37)NotFoundError
(38-38)
🔇 Additional comments (26)
packages/core/src/external/ehr/api/save-resource-diff.ts (4)
8-15
: Type definition looks good.The
SaveResourceDiffParams
type is well-defined with all required parameters clearly documented.
17-26
: Documentation is thorough and helpful.The function documentation clearly describes the purpose and all parameters, which is excellent for maintainability.
27-44
: Well-structured function implementation.The implementation properly creates an Axios instance, builds query parameters, and constructs the URL following best practices. Good use of the
out
utility for logging.
45-61
: Robust error handling.The error handling is excellent - you're using try-catch, logging the error, and throwing a
MetriportError
with appropriate context information. This follows the error handling best practices from the coding guidelines.packages/api/src/routes/ehr/canvas/patient.ts (5)
64-70
: Good API documentation.The endpoint documentation clearly describes the purpose and parameters, which is essential for API consumers.
71-88
: Well-implemented POST endpoint for resource diff.The implementation correctly validates inputs and handles errors. The pattern of starting an async task without waiting for its completion is appropriate for potentially long-running operations.
80-82
: Good error handling for direction validation.The direction validation is properly implemented with clear error messaging.
83-86
: Non-blocking asynchronous processing.The function initiates a potentially long-running process without blocking the response, which is good for API responsiveness. The error handling with
processAsyncError
ensures any issues are properly logged.
90-111
: Well-implemented GET endpoint for resource diff.The implementation follows the same pattern as the POST endpoint, with proper input validation and error handling. Returns the resource IDs with the appropriate HTTP status code.
packages/api/src/routes/internal/ehr/canvas/patient.ts (6)
64-70
: Clear endpoint documentation.The documentation effectively describes the endpoint's purpose and parameters.
71-92
: Well-structured compute resource diff endpoint.The implementation validates all required inputs, checks for valid direction, and parses the request body against the FHIR resource schema. The non-blocking async pattern is appropriate here.
82-83
: Appropriate request body validation.Using the
fhirResourceSchema
to parse and validate the request body ensures the data conforms to the expected structure.
94-100
: Good endpoint documentation.The documentation clearly describes the purpose of the save-resource-diff endpoint.
101-122
: Well-implemented save resource diff endpoint.The implementation extracts and validates all required parameters, handles validation errors properly, and uses the non-blocking pattern for the async operation.
118-119
: Good data cleaning.Filtering out empty strings from
matchedResourceIds
prevents potential issues with empty IDs.packages/api/src/external/ehr/canvas/command/save-resource-diff.ts (5)
8-14
: Well-defined type.The
SaveResourceDiffParams
type clearly specifies all the required parameters.
16-31
: Good patient retrieval implementation.The function correctly retrieves the existing patient mapping and the corresponding Metriport patient, with proper error handling via the
OrFail
functions.
32-44
: Direction handling logic looks good.The function correctly handles different directions with appropriate actions for each case.
39-40
: Clear status representation.Using "found" or "not-found" as external ID values clearly indicates the status of the resource match.
42-44
: Appropriate error handling.The error message for unsupported direction is static and includes dynamic data in the
additionalInfo
prop, which follows the error handling guidelines.packages/shared/src/interface/external/ehr/fhir-resource.ts (3)
12-13
: Good array type definitionThe array schema is properly defined and exported with its corresponding type, making it easy to validate lists of FHIR resources.
15-18
: Clear wrapper schema definitionThe wrapper schema is well-defined for handling wrapped FHIR resources, which is a common pattern in FHIR bundle entries.
20-24
: Correctly defined bundle schema with optional entry arrayThe bundle schema correctly defines the FHIR Bundle structure with the mandatory resourceType and optional entry property. The optional entry field is appropriate as some bundles might not contain entries.
Note that code using this schema will need null checks when accessing the
entry
array to handle cases where it's undefined.packages/core/src/external/ehr/canvas/index.ts (3)
13-13
: Good addition of NotFoundError importAdding the NotFoundError import improves error handling capabilities in the Canvas API.
23-27
: Well-structured imports for FHIR resource typesThe imports are properly organized and include all the necessary FHIR resource types from the shared interface.
60-65
: Well-implemented type guard functionThe type guard function is correctly implemented to provide proper type narrowing, which enhances type safety when working with resource types.
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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/core/src/external/ehr/canvas/index.ts (1)
367-410
: Well-implemented FHIR resource retrieval method.The
getFhirResourcesByResourceType
method follows good practices:
- Strong typing with the
SupportedCanvasDiffResource
type- Proper URL parameter handling
- Resource validation to ensure correct types
- Appropriate error handling, particularly for NotFoundError
- Follows functional programming style with deterministic behavior
One minor suggestion for improvement:
Consider extracting the resource validation logic into a separate helper function to improve readability and maintainability:
async getFhirResourcesByResourceType({ cxId, patientId, resourceType, extraParams, }: { cxId: string; patientId: string; resourceType: SupportedCanvasDiffResource; extraParams?: Record<string, string>; }): Promise<FhirResources> { const { debug } = out( `Canvas getFhirResourcesByResourceType - cxId ${cxId} practiceId ${this.practiceId} patientId ${patientId} resourceType ${resourceType}` ); const params = { ...(extraParams ?? {}), patient: `Patient/${patientId}` }; const urlParams = new URLSearchParams(params); const resourceTypeUrl = `/${resourceType}?${urlParams.toString()}`; const additionalInfo = { cxId, practiceId: this.practiceId, patientId, resourceType }; try { const bundle = await this.makeRequest<FhirResourceBundle>({ cxId, patientId, s3Path: `fhir-resources-${resourceType}`, method: "GET", url: resourceTypeUrl, schema: fhirResourceBundleSchema, additionalInfo, debug, useFhir: true, }); - const invalidResource = bundle.entry?.find( - resource => resource.resource.resourceType !== resourceType - ); - if (invalidResource) { - throw new MetriportError(`Invalid resource type found`, undefined, { - invalidResourceType: invalidResource.resource.resourceType, - }); - } + this.validateResourceTypes(bundle, resourceType); return bundle.entry?.map(resource => resource.resource) ?? []; } catch (error) { if (error instanceof NotFoundError) return []; throw error; } } + + private validateResourceTypes(bundle: FhirResourceBundle, expectedType: string): void { + const invalidResource = bundle.entry?.find( + resource => resource.resource.resourceType !== expectedType + ); + if (invalidResource) { + throw new MetriportError(`Invalid resource type found`, undefined, { + invalidResourceType: invalidResource.resource.resourceType, + }); + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (2)
packages/core/src/external/ehr/canvas/index.ts
(4 hunks)packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/core/src/external/ehr/resource-diff/start/ehr-start-resource-diff-local.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/core/src/external/ehr/canvas/index.ts
🧬 Code Graph Analysis (1)
packages/core/src/external/ehr/canvas/index.ts (2)
packages/shared/src/interface/external/ehr/fhir-resource.ts (3)
FhirResources
(13-13)FhirResourceBundle
(24-24)fhirResourceBundleSchema
(20-23)packages/shared/src/index.ts (2)
MetriportError
(37-37)NotFoundError
(38-38)
🔇 Additional comments (3)
packages/core/src/external/ehr/canvas/index.ts (3)
13-13
: Good addition of NotFoundError import.The import modification now includes
NotFoundError
, which is used in the new functionality for proper error handling of resource not found scenarios.
23-27
: Proper import organization for FHIR resources.The added imports for FHIR resource types are necessary for the new functionality and follow the code organization principles.
43-59
: Clean implementation of resource type validation.The implementation of a constant array with supported resource types, along with a type and type guard function, provides strong type safety while maintaining a single source of truth for supported resources.
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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/resource-diff/utils.ts (2)
68-68
:⚠️ Potential issueFix typo in variable name
There's a typo in the variable name
mewResourceType
- it should benewResourceType
.- switch (mewResourceType) { + switch (newResourceType) {
140-144
:⚠️ Potential issueFix typo in error message and details
The same typo appears in the error message and context object.
- throw new BadRequestError(`Unsupported resource type: ${mewResourceType}`, undefined, { - resourceType: mewResourceType, + throw new BadRequestError(`Unsupported resource type: ${newResourceType}`, undefined, { + resourceType: newResourceType,
🧹 Nitpick comments (6)
packages/core/src/external/ehr/resource-diff/utils.ts (2)
158-164
: Consider adding JSDoc comments for the helper functionThe
resourceIsDerivedFromExistingResource
function is well-implemented, but adding JSDoc comments would improve code documentation and clarity.+/** + * Determines if a resource is derived from an existing resource by checking + * its extensions for the artifact-related artifact URL. + * @param resource - The FHIR resource to check + * @returns true if the resource is derived from an existing resource + */ function resourceIsDerivedFromExistingResource(resource: Resource): boolean {
158-164
: Consider using type predicate for safer property accessUsing a type guard with a truthy check on
resource.extension
would be more type-safe than using"extension" in resource
.function resourceIsDerivedFromExistingResource(resource: Resource): boolean { - if (!("extension" in resource)) return false; + if (!resource.extension || !Array.isArray(resource.extension)) return false; const derivedFrom = resource.extension.find( extension => extension.url === artifactRelatedArtifactUrl ); return derivedFrom !== undefined; }packages/lambdas/src/ehr-compute-resource-diff.ts (2)
17-22
: Validate parseInt usage.
If the environment variable is invalid or non-numeric,parseInt
may produceNaN
. Ensure a check forNaN
to avoid potential runtime issues.const waitTimeInMillis = parseInt(waitTimeInMillisRaw); + if (isNaN(waitTimeInMillis)) { + throw new MetriportError("Invalid WAIT_TIME_IN_MILLIS value", { + additionalInfo: { waitTimeInMillisRaw }, + }); + }
29-29
: Use the recommended logging utility.
The coding guidelines advise against usingconsole.log
outside packages likeinfra
orshared
. Switch to a standardized logger such asout().log
for consistency.- console.log(`Running with unparsed body: ${message.body}`); + out().log(`Running with unparsed body: ${message.body}`); - console.log(`${msg}: ${errorToString(error)}`); + out().log(`${msg}: ${errorToString(error)}`);Also applies to: 45-45
packages/infra/lib/ehr-nested-stack.ts (2)
77-95
: Use kebab-case for file entries.
The entry"ehr-Compute-resource-diff"
uses uppercase “C.” Rename to align with the kebab-case standard.- entry: "ehr-Compute-resource-diff", + entry: "ehr-compute-resource-diff",
369-426
: Rename entry for compute resource diff.
Again, consider replacing the uppercase “C” in"ehr-Compute-resource-diff"
to use kebab-case.- entry: "ehr-Compute-resource-diff", + entry: "ehr-compute-resource-diff",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (5)
packages/core/src/external/ehr/resource-diff/utils.ts
(1 hunks)packages/infra/lib/api-stack.ts
(3 hunks)packages/infra/lib/api-stack/api-service.ts
(4 hunks)packages/infra/lib/ehr-nested-stack.ts
(6 hunks)packages/lambdas/src/ehr-compute-resource-diff.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/infra/lib/api-stack/api-service.ts
- packages/infra/lib/api-stack.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/lambdas/src/ehr-compute-resource-diff.ts
packages/core/src/external/ehr/resource-diff/utils.ts
packages/infra/lib/ehr-nested-stack.ts
🧬 Code Graph Analysis (2)
packages/core/src/external/ehr/resource-diff/utils.ts (3)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
ResourceWithId
(5-5)packages/shared/src/index.ts (1)
BadRequestError
(36-36)packages/core/src/fhir-deduplication/shared.ts (1)
artifactRelatedArtifactUrl
(42-43)
packages/infra/lib/ehr-nested-stack.ts (2)
packages/infra/lib/shared/sqs.ts (1)
createQueue
(50-103)packages/infra/lib/api-stack/fhir-converter-connector.ts (1)
createLambda
(203-282)
🔇 Additional comments (17)
packages/core/src/external/ehr/resource-diff/utils.ts (4)
1-40
: Good organization of importsThe imports are well-organized, categorized by external dependencies, internal deduplication functions, and shared utilities. This structure makes the file easy to navigate.
42-60
: LGTM! Robust validation of input resourcesThe function properly validates that all existing resources match the new resource type before proceeding with deduplication, preventing type mismatches and providing clear error context.
69-139
: LGTM! Comprehensive resource type handlingThe switch statement handles all the supported FHIR resource types with appropriate deduplication functions. The structure makes it easy to add new resource types in the future.
146-156
: LGTM! Clear resource diff determination logicThe function properly checks if the new resource exists in the deduplicated results and whether it's derived from existing resources. The return values clearly communicate whether there's a change (empty array) or not (array of existing resource IDs).
packages/lambdas/src/ehr-compute-resource-diff.ts (5)
1-13
: Imports are consistent and well-structured.
No issues found in the import statements.
14-16
: Initialization order is valid.
Sentry capture initialization is placed correctly for accurate error tracking.
23-53
: Overall logic is correct.
The Sentry-wrapped handler, error catching, and rethrow asMetriportError
are well-structured.
55-63
: Schema definition is appropriate.
Using Zod andz.nativeEnum
is suitable for type safety.
64-73
: Body parsing logic is comprehensive.
Throws early for missing or invalid body, ensuring predictable failure paths.packages/infra/lib/ehr-nested-stack.ts (8)
16-17
: Consistent queue wait durations.
BothstartResourceDiff
andcomputeResourceDiff
share the same 1-second wait time, which may be suitable for high throughput.
31-31
: Reduced alarm threshold for syncPatient queue.
From a longer period toDuration.hours(2)
. Confirm if more frequent alerts are acceptable.
50-50
: Reduced alarm threshold for elationLinkPatient queue.
Similarly, confirm that 2 hours is the intended maximum age for messages before triggering an alarm.
58-76
: New startResourceDiff settings.
Configuration mirrors the existing queue-Lambda pattern, which helps maintain consistency.
99-100
: Properly exports configuration.
ReturningstartResourceDiff
andComputeResourceDiff
fromsettings()
is consistent with the existing structure.
141-144
: Additional Lambda/Queue properties.
Declaring these new resource diff combos as readonly is consistent with other definitions.
171-190
: Wiring up startResourceDiff.
Setup pattern is analogous tosetupSyncPatient
andsetupElationLinkPatient
. Looks consistent.
310-367
: setupStartResourceDiff method.
Implements the same queue and Lambda creation logic used elsewhere, maintaining uniform code style.
Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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/core/src/external/ehr/resource-diff/utils.ts (3)
54-59
: Consider adding more descriptive error messagesThe current error messaging is brief. For better debuggability, consider adding more context about what the error means and potential resolution steps.
- throw new BadRequestError("Invalid existing resource types", undefined, { + throw new BadRequestError("Invalid existing resource types - all resources must have the same type as the new resource", undefined, {
68-144
: Consider extracting the resource deduplication logic into a separate functionThe switch statement is large and may grow as more resource types are supported. Consider refactoring this into a separate function to improve maintainability.
- let deduplicatedResources: Resource[]; - switch (newResourceType) { - case "AllergyIntolerance": - deduplicatedResources = deduplicateAllergyIntolerances( - resources as AllergyIntolerance[] - ).combinedResources; - break; - // ... other cases ... - } + const deduplicatedResources = deduplicateResources(resources, newResourceType); // Add this function below: + function deduplicateResources(resources: ResourceWithId[], resourceType: string): Resource[] { + switch (resourceType) { + case "AllergyIntolerance": + return deduplicateAllergyIntolerances( + resources as AllergyIntolerance[] + ).combinedResources; + // ... other cases ... + } + }
146-155
: Logic looks good but could benefit from more explanatory commentsThe logic that determines whether to return existing resource IDs or an empty array is crucial to the function's purpose but might not be immediately clear to maintainers. Consider adding a comment explaining the logic.
const existingResourceIds = existingResources.map(resource => resource.id); + // If the new resource has been deduplicated away or is derived from existing ones, + // return the existing resource IDs to indicate the resource already exists const newResourceInDeduplicatedResources = deduplicatedResources.find( resource => resource.id === newResource.id ); if (!newResourceInDeduplicatedResources) return existingResourceIds; const newResourceIsDerivedFromExistingResource = resourceIsDerivedFromExistingResource( newResourceInDeduplicatedResources ); if (newResourceIsDerivedFromExistingResource) return existingResourceIds; + // Return an empty array to indicate this is a truly new resource return [];
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (1)
packages/core/src/external/ehr/resource-diff/utils.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/core/src/external/ehr/resource-diff/utils.ts
🧬 Code Graph Analysis (1)
packages/core/src/external/ehr/resource-diff/utils.ts (7)
packages/core/src/external/ehr/resource-diff/compute/ehr-compute-resource-diff.ts (1)
ResourceWithId
(5-5)packages/shared/src/index.ts (1)
BadRequestError
(36-36)packages/core/src/fhir-deduplication/resources/encounter.ts (1)
deduplicateEncounters
(38-47)packages/core/src/fhir-deduplication/resources/immunization.ts (1)
deduplicateImmunizations
(27-39)packages/core/src/fhir-deduplication/resources/observation.ts (1)
deduplicateObservations
(20-32)packages/core/src/fhir-deduplication/resources/procedure.ts (1)
deduplicateProcedures
(48-57)packages/core/src/fhir-deduplication/shared.ts (1)
artifactRelatedArtifactUrl
(42-43)
🔇 Additional comments (2)
packages/core/src/external/ehr/resource-diff/utils.ts (2)
42-156
: Well implemented resource differencing with comprehensive type handling.The
computeResourceDiff
function effectively implements a robust workflow for determining if a new resource is truly unique after deduplication. It handles each FHIR resource type appropriately with specific deduplication strategies.
158-164
: Helper function correctly checks derived relationshipsThe
resourceIsDerivedFromExistingResource
function correctly checks for the presence of the relevant extension URL to determine if a resource is derived from an existing one.
Ref: ENG-47 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.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/infra/lib/ehr-nested-stack.ts (1)
175-182
: 🛠️ Refactor suggestionConsider adding a RemovalPolicy to the S3 bucket.
The S3 bucket will default to
RemovalPolicy.DESTROY
, which could lead to accidental data loss during stack updates or removals. Since this bucket contains important EHR data, it's recommended to add a more protective removal policy.import { Duration, NestedStack, NestedStackProps } from "aws-cdk-lib"; +import { RemovalPolicy } from "aws-cdk-lib"; import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import { Function as Lambda } from "aws-cdk-lib/aws-lambda"; import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; import * as s3 from "aws-cdk-lib/aws-s3"; import { Queue } from "aws-cdk-lib/aws-sqs"; import { Construct } from "constructs"; // ... const ehrBundleBucket = new s3.Bucket(this, "EhrBundleBucket", { bucketName: props.config.ehrBundleBucketName, publicReadAccess: false, encryption: s3.BucketEncryption.S3_MANAGED, versioned: true, + removalPolicy: RemovalPolicy.RETAIN, });
🧹 Nitpick comments (6)
packages/infra/lib/ehr-nested-stack.ts (6)
94-96
: Fix variable naming inconsistency.The variable
ComputeResourceDiffBundlesLambdaTimeout
uses PascalCase instead of camelCase, which is inconsistent with other similar variables in the codebase and violates the naming convention specified in your coding guidelines.-const ComputeResourceDiffBundlesLambdaTimeout = waitTimeComputeResourceDiff.plus( +const computeResourceDiffBundlesLambdaTimeout = waitTimeComputeResourceDiff.plus( Duration.minutes(5) );
120-120
: Maintain naming consistency for timeout variables.Similarly, keep the same naming pattern for all timeout variables.
-const RefreshEhrBundlesLambdaTimeout = waitTimeRefreshBundle.plus(Duration.minutes(5)); +const refreshEhrBundlesLambdaTimeout = waitTimeRefreshBundle.plus(Duration.minutes(5));
100-103
: Update Lambda timeout reference in configuration.Make sure to update the reference to the timeout variable in the Lambda settings.
lambda: { memory: 1024, - timeout: ComputeResourceDiffBundlesLambdaTimeout, + timeout: computeResourceDiffBundlesLambdaTimeout, },
125-127
: Update Lambda timeout reference in configuration.Same issue with the refreshEhrBundles Lambda settings.
lambda: { memory: 1024, - timeout: RefreshEhrBundlesLambdaTimeout, + timeout: refreshEhrBundlesLambdaTimeout, },
108-110
: Update Queue visibility timeout reference.Update the reference to the timeout variable in the queue configuration.
visibilityTimeout: Duration.seconds( - ComputeResourceDiffBundlesLambdaTimeout.toSeconds() * 2 + 1 + computeResourceDiffBundlesLambdaTimeout.toSeconds() * 2 + 1 ),
132-132
: Update Queue visibility timeout reference.Update the reference to the timeout variable in the refreshEhrBundles queue configuration.
- visibilityTimeout: Duration.seconds(RefreshEhrBundlesLambdaTimeout.toSeconds() * 2 + 1), + visibilityTimeout: Duration.seconds(refreshEhrBundlesLambdaTimeout.toSeconds() * 2 + 1),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
📒 Files selected for processing (3)
packages/api/src/external/ehr/canvas/command/bundle/create-resource-diff-bundles.ts
(1 hunks)packages/core/src/external/ehr/bundle/create-resource-diff-bundles/utils.ts
(1 hunks)packages/infra/lib/ehr-nested-stack.ts
(8 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/core/src/external/ehr/bundle/create-resource-diff-bundles/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/api/src/external/ehr/canvas/command/bundle/create-resource-diff-bundles.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/infra/lib/ehr-nested-stack.ts
🔇 Additional comments (7)
packages/infra/lib/ehr-nested-stack.ts (7)
373-374
: LGTM! Properly included bucket name in environment variables.Good job including the S3 bucket name as an environment variable for the startResourceDiffBundles Lambda. This ensures the Lambda has access to the correct bucket information at runtime.
430-431
: LGTM! Properly included bucket name in environment variables.Good job including the S3 bucket name as an environment variable for the computeResourceDiffBundles Lambda. This ensures the Lambda has access to the correct bucket information at runtime.
386-387
: LGTM! Appropriate permissions granted to Lambda.Appropriate permissions have been granted to the startResourceDiffBundles Lambda - it can send messages to the computeResourceDiffBundles queue and write to the S3 bucket.
440-440
: LGTM! Appropriate permissions granted to Lambda.The computeResourceDiffBundles Lambda has been granted read/write permissions to the S3 bucket, which is appropriate for its function.
116-117
: Good use of concurrency limits for resource-intensive Lambda.Setting a maximum concurrency of 10 for the computeResourceDiffBundles Lambda is a good practice to avoid overloading downstream services. This helps control resource usage and costs.
38-39
: LGTM! Consistent alarm configuration across queues.Good job setting consistent alarm thresholds across all queues. Reducing the max age of oldest message to 2 hours (from what appears to have been much longer) for all queues improves alert responsiveness.
Also applies to: 59-60, 80-81, 105-106, 129-130
445-492
:✅ Verification successful
Verify that refreshBundle Lambda doesn't need S3 bucket access.
Unlike
startResourceDiffBundlesLambda
andcomputeResourceDiffBundlesLambda
, therefreshEhrBundlesLambda
doesn't have S3 bucket permissions granted. Does this Lambda need to read or write to the S3 bucket directly?
🏁 Script executed:
#!/bin/bash # Look for S3 operations in the refreshBundle Lambda handler cd packages/lambdas/src grep -r "S3" --include="ehr-refresh-ehr-bundles.ts" . # Also check if it imports any modules that might interact with S3 grep -r "import" --include="ehr-refresh-ehr-bundles.ts" . # Check if it calls any functions that might use S3 cd ../../core/src/external/ehr/bundle grep -r "S3\|s3\|bucket" --include="*.ts" .Length of output: 4579
No S3 permissions needed for refreshEhrBundles Lambda
Verified that neither the handler (ehr-refresh-ehr-bundles.ts
) nor any of its imported core modules (refresh‑ehr‑bundles
or its “local” variant) reference S3, buckets, or S3Utils. This Lambda only orchestrates bundle refresh requests via SQS and does not read from or write to the bundle bucket directly.
Ref: #2850
Dependencies
Description
Testing
Release Plan
Summary by CodeRabbit
New Features
Infrastructure
Bug Fixes
Chores
Documentation