8000 RELEASE cavnas diff part 2 (with jobs!) by thomasyopes · Pull Request #3714 · metriport/metriport · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

RELEASE cavnas diff part 2 (with jobs!) #3714

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

Merged
merged 99 commits into from
Apr 24, 2025
Merged

RELEASE cavnas diff part 2 (with jobs!) #3714

merged 99 commits into from
Apr 24, 2025

Conversation

thomasyopes
Copy link
Contributor
@thomasyopes thomasyopes commented Apr 23, 2025

Issues:

Description

Testing

Check each PR.

Release Plan

  • ⚠️ Points to master
  • ⚠️ This contains a DB migration
  • Merge this

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced a comprehensive job management system for patient-related tasks, including job creation, initialization, status updates, and completion tracking.
    • Added asynchronous resource difference (diff) bundle creation for EHR integrations, with endpoints to start jobs and retrieve their results.
    • Implemented API endpoints to manage and monitor patient job progress, including job initialization, updating totals, and setting entry statuses.
    • Enabled retrieval of pre-signed URLs for resource diff bundles, supporting secure and efficient data access.
  • Improvements
    • Enhanced job tracking with detailed status management, counters for successful/failed entries, and robust error handling.
    • Extended API documentation for improved clarity on endpoint responses.
    • Added support for job-specific bundle retrieval and storage, enabling tracking and access by job ID.
  • Bug Fixes
    • Improved error handling and validation for job status transitions and input parameters.
  • Chores
    • Standardized configuration constants for SQS message handling.
    • Refactored code to use shared utility modules for common functionality.

Thomas Yopes added 30 commits April 17, 2025 05:46
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>
…port into 2850-canvas-diff-workflow

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>
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>
Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…port into 2850-canvas-diff-workflow

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…port into 2850-canvas-diff-workflow

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…port into 2850-canvas-diff-workflow

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
…port into 2850-canvas-diff-workflow

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>
…port into 2850-canvas-diff-workflow

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>
…port into 2850-canvas-diff-workflow

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>
…port into 2850-canvas-diff-workflow

Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Copy link
coderabbitai bot commented Apr 23, 2025

Walkthrough

This set of changes introduces a comprehensive patient job management system and refactors the workflow for resource diff bundle creation and retrieval, particularly for Canvas EHR integrations. It adds a new database model and migration for tracking patient jobs, implements job lifecycle management (creation, initialization, status updates, completion), and exposes new internal API routes for job operations. The resource diff bundle endpoints are refactored to operate asynchronously via job tracking, with endpoints for job initiation and result retrieval. Supporting utilities, types, and schemas are added or updated across the shared, API, and core packages to enable robust job tracking, status validation, and S3 bundle management keyed by job IDs.

Changes

Files/Paths Change Summary
packages/api/src/command/job/patient/complete.ts, .../create.ts, .../get.ts, .../initialize.ts, .../set-entry-status.ts, .../update-total.ts, .../shared.ts Introduced patient job management functions: create, get, initialize, set entry status, complete, and update total; added supporting types.
packages/api/src/models/patient-job.ts, .../db.ts, .../sequelize/migrations/2025-04-16_00_create-patient-job.ts Added Sequelize model and migration for patient_job table; registered model in DB setup.
packages/api/src/routes/internal/medical/patient-job.ts, .../patient.ts Added new internal API routes for patient job lifecycle management and registered them.
packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts, .../fetch-resource-diff-bundle-pre-signed-urls.ts, .../refresh-ehr-bundles.ts Refactored Canvas bundle fetching: changed return types to include resource types and pre-signed URLs; updated function and type signatures.
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts, .../start-job.ts Added/updated job-based resource diff bundle creation and payload retrieval functions; job creation now returns job ID.
packages/api/src/external/ehr/canvas/shared.ts Added utility to generate job type strings for resource diff bundles by direction.
packages/api/src/external/ehr/create-resource-diff-bundles.ts Added orchestration for creating resource diff jobs for EHR integrations.
packages/api/src/routes/ehr/canvas/patient.ts Refactored endpoints: POST now starts a resource diff job and returns job ID; added GET endpoints to fetch latest or specific job payloads.
packages/api/src/command/medical/document/document-conversion-status.ts, .../patient/consolidated-recreate.ts, .../patient/consolidated-webhook.ts Updated consolidated document and webhook logic to support new job-based diff bundle creation and error handling; minor import path changes.
packages/api/src/routes/internal/ehr/athenahealth/index.ts, .../patient.ts, .../canvas/index.ts, .../canvas/patient.ts, .../elation/index.ts, .../elation/patient.ts Minor doc comment and whitespace changes for clarity and consistency.
packages/core/src/external/ehr/api/fetch-bundle.ts, .../initialize-job.ts, .../set-entry-status.ts, .../update-job-total.ts Added/updated EHR API utilities for job-based bundle fetching, job initialization, entry status updates, and total updates.
packages/core/src/external/ehr/bundle/bundle-shared.ts, .../commands/create-or-replace-bundle.ts, .../commands/fetch-bundle.ts, .../commands/update-bundle.ts Extended S3 bundle keying and fetching logic to support job IDs; added pre-signed URL fetching.
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts, .../steps/compute/ehr-compute-resource-diff-bundles-local.ts, .../steps/compute/ehr-compute-resource-diff-bundles-cloud.ts, .../steps/start/ehr-start-resource-diff-bundles-local.ts Updated resource diff bundle creation logic to include job IDs, job status tracking, and error handling; refactored SQS batching constants to shared module.
packages/core/src/external/ehr/canvas/index.ts Refactored Canvas resource diff bundle retrieval to return pre-signed URLs and accept job IDs.
packages/core/src/external/ehr/shared.ts Added dayjs duration plugin for bundle TTL calculations.
packages/core/src/util/sqs.ts Added shared SQS batching constants.
packages/lambdas/src/ehr-compute-resource-diff-bundles.ts, .../ehr-start-resource-diff-bundles.ts Updated validation schemas to require job IDs for resource diff bundle computation and start steps.
packages/shared/src/domain/job/job-status.ts, .../patient-job.ts, .../types.ts, .../index.ts Added job status management utilities, types for patient jobs and job entry statuses, and exported them.
packages/shared/src/interface/external/ehr/fhir-resource.ts Updated supported resource types and schema for stricter typing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant DB
    participant S3
    participant CanvasEHR

    Client->>API: POST /ehr/canvas/patient/:id/resource/diff (start job)
    API->>DB: Create PatientJob (status: waiting)
    API-->>Client: { jobId }

    Client->>API: GET /ehr/canvas/patient/:id/resource/diff/:jobId (poll for result)
    API->>DB: Get PatientJob by jobId
    alt Job status == completed
        API->>CanvasEHR: Fetch pre-signed URLs for resource diff bundles
        API-->>Client: { job metadata, preSignedUrls }
    else Job not completed
        API-->>Client: { job metadata }
    end

    Note over API,CanvasEHR: (Background worker)
    API->>CanvasEHR: Start resource diff bundle job (async)
    CanvasEHR->>S3: Store resource diff bundles (keyed by jobId)
    CanvasEHR->>API: Update job entry statuses, mark job as completed
Loading

Possibly related PRs

  • 2850 canvas diff job #3682: Directly related; both PRs implement and modify patient job management, job lifecycle functions, and resource diff job workflows as part of the same feature set.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c61f8c6 and d197068.

📒 Files selected for processing (6)
  • packages/api/src/command/medical/document/document-conversion-status.ts (1 hunks)
  • packages/api/src/command/medical/patient/consolidated-recreate.ts (3 hunks)
  • packages/api/src/external/ehr/create-resource-diff-bundles.ts (1 hunks)
  • packages/infra/lib/ehr-nested-stack.ts (3 hunks)
  • packages/shared/src/domain/job/job-status.ts (1 hunks)
  • packages/shared/src/domain/job/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/shared/src/domain/job/job-status.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/api/src/command/medical/document/document-conversion-status.ts
  • packages/api/src/command/medical/patient/consolidated-recreate.ts
  • packages/api/src/external/ehr/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, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('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 and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().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
  • packages/shared/src/domain/job/types.ts
🔇 Additional comments (6)
packages/shared/src/domain/job/types.ts (3)

1-2: Clean record type definitions.

These record types provide a flexible structure for job parameters, allowing both string and boolean values. The naming (Cx vs Ops) suggests a good separation of concerns between client and operational parameters.


4-6: Well-defined string literal types.

Good use of string literal constants with as const to create a strongly-typed union. This approach provides compile-time safety when working with job entry statuses throughout the codebase.


8-10: Type guard function properly implemented.

The type guard function is correctly implemented with the proper return type annotation for runtime validation. It follows functional programming principles - small, deterministic, with single input and output.

packages/infra/lib/ehr-nested-stack.ts (3)

19-19: Confirm the impact of removing rate limiting

Removing the wait time (setting to 0ms) effectively eliminates the rate limit that was previously in place for computing resource diffs. This aligns with the move to an asynchronous job-based architecture but could potentially increase load on downstream systems.

Consider if any downstream systems might be impacted by the increased throughput, and ensure they can handle the load without rate limiting in place.


81-81: Lower alarm threshold supports the new job-based workflow

Reducing the maxMessageCountAlarmThreshold from 5,000 to 1,000 indicates a change in expected queue behavior with the new job-based architecture. This provides earlier alerting if messages start accumulating.

This change aligns well with the move to a job-based workflow for resource diff bundles, where different queue behavior is expected.


129-129: Lower alarm threshold appropriate for refreshEhrBundles queue

Similar to the startResourceDiffBundles queue, reducing this threshold from a higher value to 1,000 provides earlier notification of potential issues in the queue processing.

This change appropriately adjusts monitoring sensitivity to match the expectations of the new asynchronous job-based workflow.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

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

Copy link
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (3)
packages/core/src/external/ehr/shared.ts (1)

15-27: ⚠️ Potential issue

Fixed missing dayjs plugin initialization.

The code was previously using dayjs.duration() (lines 29 and 276) without properly importing and extending the duration plugin, which could lead to runtime errors.

This fix properly imports and extends the dayjs duration plugin, aligning with the coding guidelines for date and time management:

  • "Always use buildDayjs() to create dayjs instances"
  • "Prefer dayjs.duration(...) to create duration consts"
packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (1)

60-73: 💡 Verification agent

🧩 Analysis chain

Consider handling potential duplicates in merged bundles.

When merging entries from multiple resource bundles, there's no check for duplicate resources. This could potentially lead to the same resource appearing multiple times in the final bundle.


🏁 Script executed:

#!/bin/bash
# Description: Check if the code handles duplicate resources when merging bundles

# Look for any checks of duplicates in bundle entry merging
rg -A 5 -B 5 "bundle.entry.push\(" --glob "**/*.ts"

# Look for any distinct or filter operations on bundle entries
rg -A 5 -B 5 "entry.*filter\(" --glob "**/*.ts"

Length of output: 36908


Handle duplicate resources when merging bundles

The current loop in packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (lines 60–73) blindly pushes all entries from each resourceBundle into the final bundle. If the same resource appears in more than one resourceBundle call, you’ll get duplicates in the result.

Suggested fixes:

  • After merging, filter bundle.entry by a unique key (e.g. ${resourceType}/${id}) to drop duplicates.
  • You can reuse the deduplication logic in packages/utils/src/fhir-deduplication, or add:
   for (const resourceType of resourceTypes) {
     const resourceBundle = await canvasApi.getBundleByResourceType({ … });
     bundle.entry.push(...resourceBundle.entry);
   }

+  // remove duplicate resources by resourceType/id
+  const seen = new Set<string>();
+  bundle.entry = bundle.entry.filter(entry => {
+    const res = entry.resource;
+    if (!res?.resourceType || !res.id) return true;
+    const key = `${res.resourceType}/${res.id}`;
+    if (seen.has(key)) return false;
+    seen.add(key);
+    return true;
+  });

   return { bundle, resourceTypes };

• File: packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts
• Location: immediately after the loop that pushes resourceBundle.entry

packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts (1)

36-104: 🛠️ Refactor suggestion

Add error handling for job operations.

The function lacks try-catch blocks for handling errors during job initialization, bundle creation, or job total updates. An error in any of these operations could cause the job to fail without updating its status.

 async startResourceDiffBundlesMetriportOnly({
   ehr,
   cxId,
   practiceId,
   metriportPatientId,
   ehrPatientId,
   jobId,
 }: StartResourceDiffBundlesRequest): Promise<void> {
-  await initializeJob({ cxId, jobId });
-  const consolidated = await getConsolidated({ cxId, patientId: metriportPatientId });
-  if (!consolidated || !consolidated.bundle?.entry || consolidated.bundle.entry.length < 1) {
-    return;
-  }
+  try {
+    await initializeJob({ cxId, jobId });
+    const consolidated = await getConsolidated({ cxId, patientId: metriportPatientId });
+    if (!consolidated || !consolidated.bundle?.entry || consolidated.bundle.entry.length < 1) {
+      return;
+    }
   
   // ... rest of function ...
   
+    const total = computeResourceDiffParamsWithExistingResources.length;
+    await updateJobTotal({ cxId, jobId, total });
+    await this.next.computeResourceDiffBundlesMetriportOnly(
+      computeResourceDiffParamsWithExistingResources
+    );
+    if (this.waitTimeInMillis > 0) await sleep(this.waitTimeInMillis);
+  } catch (error) {
+    // Import this at the top of the file
+    // import { setPatientJobEntryStatus } from "../../../../api/set-entry-status";
+    await setPatientJobEntryStatus({ cxId, jobId, entryStatus: "failed" })
+      .catch(e => {
+        const { log } = out("EhrStartResourceDiffBundlesLocal");
+        log(`Failed to set job status to failed: ${errorToString(e)}`);
+      });
+    throw error;
+  }
 }
🧹 Nitpick comments (21)
packages/lambdas/src/ehr-compute-resource-diff-bundles.ts (1)

30-32: Consider extracting the jobId for consistency and clarity.

While the schema now requires jobId, it's not being destructured from the parsed body. Consider adding it to the destructuring statement for clarity, even if it's only used internally by the computeResourceDiffBundlesMetriportOnly method.

-  const { ehr, cxId, practiceId, metriportPatientId, ehrPatientId, newResource } = parsedBody;
+  const { ehr, cxId, practiceId, metriportPatientId, ehrPatientId, newResource, jobId } = parsedBody;
packages/core/src/external/ehr/api/fetch-bundle.ts (1)

38-38: Minor log prefix inconsistency with function name

The log prefix has been changed from "Ehr fetchEhrBundle" to "Ehr fetchBundle", which doesn't match the function name fetchEhrBundle. While this is a minor issue, consistent naming helps with log readability and debugging.

-  const { log, debug } = out(`Ehr fetchBundle - cxId ${cxId}`);
+  const { log, debug } = out(`Ehr fetchEhrBundle - cxId ${cxId}`);
packages/api/src/command/job/patient/update-total.ts (2)

24-28: Consider making total field required in the job creation flow

Since you validate that total must be greater than 0 here, it would be better to ensure the total is properly set during job creation rather than defaulting to 0 (as seen in create.ts). This would provide more consistency in the job lifecycle.


16-31: Unused parameter in type definition

The UpdateJobTotalParams type includes a forceTotalUpdate parameter that isn't used in this function implementation. Consider either using this parameter or removing it from the type definition.

packages/core/src/external/ehr/api/set-entry-status.ts (1)

26-26: Consider using path constants for API endpoint construction

Instead of hardcoding the path as a string, consider extracting it to a constant or using a more structured approach for API endpoint construction. This would make the code more maintainable if the API paths change.

+const PATIENT_JOB_BASE_PATH = '/internal/patient/job';
-const updateJobUrl = `/internal/patient/job/${jobId}/set-entry-status?${queryParams.toString()}`;
+const updateJobUrl = `${PATIENT_JOB_BASE_PATH}/${jobId}/set-entry-status?${queryParams.toString()}`;
packages/api/src/command/job/patient/create.ts (2)

13-52: Consider adding logging for job creation

Unlike other job-related functions in this PR (such as complete.ts), this function doesn't include logging. Consider adding logging to track job creation for consistency and easier debugging.

+import { out } from "@metriport/core/util/log";

 export async function createPatientJob({
   cxId,
   patientId,
   jobType,
   jobGroupId,
   requestId,
   limitedToOneRunningJob = false,
 }: CreatePatientJobParams): Promise<PatientJob> {
+  const { log } = out(`createPatientJob - patientId ${patientId} cxId ${cxId} jobType ${jobType}`);
   if (limitedToOneRunningJob) {
     const runningJob = await getLatestPatientJob({
       cxId,
       patientId,
       jobType,
       jobGroupId,
       status: ["waiting", "processing"],
     });
     if (runningJob) {
+      log(`Found existing running job: ${runningJob.id}`);
       throw new BadRequestError("Only one job can be running at a time", undefined, {
         cxId,
         patientId,
         jobType,
         jobGroupId,
         runningJobId: runningJob.id,
       });
     }
   }
+  log(`Creating new job with type ${jobType}`);
   const created = await PatientJobModel.create({
     id: uuidv7(),
     cxId,
     patientId,
     jobType,
     jobGroupId,
     requestId,
     status: jobInitialStatus,
     total: 0,
     successful: 0,
     failed: 0,
   });
   return created.dataValues;
 }

39-50: Consider adding initial timestamps

The job creation doesn't set a createdAt timestamp. Although Sequelize might handle this automatically, it would be clearer to explicitly set it for consistency with other fields.

packages/api/src/command/job/patient/complete.ts (2)

25-25: Misaligned logging context

The log context is updateJobTracking but the function is named completePatientJob. Consider updating the logging context to match the function name for clarity and consistency.

-  const { log } = out(`updateJobTracking - jobId ${jobId} cxId ${cxId}`);
+  const { log } = out(`completePatientJob - jobId ${jobId} cxId ${cxId}`);

34-36: Consider adding statusReason to the updated fields

The function updates the status but doesn't provide a way to set a reason for the status change. Consider adding an optional statusReason parameter to provide more context about why the job was completed.

-export async function completePatientJob({
-  jobId,
-  cxId,
-  forceStatusUpdate = false,
-  onCompleted,
-}: CompleteJobParams): Promise<PatientJob> {
+export async function 
8000
completePatientJob({
+  jobId,
+  cxId,
+  forceStatusUpdate = false,
+  onCompleted,
+  statusReason,
+}: CompleteJobParams & { statusReason?: string }): Promise<PatientJob> {

  // ...existing code...

-  const fieldsToUpdate: Partial<Pick<PatientJob, "status" | "finishedAt">> = {
+  const fieldsToUpdate: Partial<Pick<PatientJob, "status" | "statusReason" | "finishedAt">> = {
     status: newStatus,
+    ...(statusReason && { statusReason }),
   };
packages/api/src/command/job/patient/set-entry-status.ts (1)

40-43: Consider a more type-safe approach for handling Sequelize raw results

While the current approach with disabling ESLint and using any is explained in the comment, consider defining a type interface for the raw DB response to make the code more maintainable and type-safe in the long run.

-  // Using any because Sequelize doesn't map the columns to the model, even using mapToModel/model
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const updatedRaw = (updatedRows as unknown as any[] | undefined)?.[0];
+  // Define a type for the raw DB response to improve type safety
+  type PatientJobRawRow = Record<keyof typeof patientJobRawColumnNames, unknown>;
+  const updatedRaw = (updatedRows as unknown as PatientJobRawRow[] | undefined)?.[0];
packages/api/src/sequelize/migrations/2025-04-16_00_create-patient-job.ts (1)

79-92: Consider adding database-level validation for JSONB fields.

While the JSONB fields are appropriate for flexible data storage, consider whether any validation constraints should be applied at the database level for consistency, especially for paramsCx and paramsOps which have specific type definitions in the shared package.

packages/core/src/external/ehr/bundle/commands/update-bundle.ts (1)

26-26: Make fallback behavior for jobId more explicit in the code.

The JSDoc states "If not provided, the tag 'latest' will be used" for jobId, but this fallback logic isn't visible in this function. For clarity and maintainability, consider implementing this fallback explicitly in the function rather than relying on downstream implementations.

-  const key = createKey({ ehr, cxId, metriportPatientId, ehrPatientId, resourceType, jobId });
+  // If jobId is not provided, use 'latest' tag as default
+  const effectiveJobId = jobId ?? 'latest';
+  const key = createKey({ ehr, cxId, metriportPatientId, ehrPatientId, resourceType, jobId: effectiveJobId });
packages/api/src/models/patient-job.ts (1)

46-101: Consider adding database constraints in the model definition.

While the database migration defines constraints like allowNull and defaultValue, these aren't reflected in the model definition. Consider adding these constraints to the model for consistency and to ensure the ORM enforces the same rules as the database.

Example for the total field:

total: {
  type: DataTypes.INTEGER,
+  allowNull: false,
+  defaultValue: 0,
},
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (1)

33-46: Consider separating duplicate resource checking logic.

The code for checking duplicate resources is quite complex and might benefit from being extracted into a separate function for better readability and maintainability.

-        const existingResourcesToUse: FhirResource[] =
-          existingResources ??
-          (await getExistingResourcesFromApi({
-            ehr,
-            cxId,
-            practiceId,
-            patientId: ehrPatientId,
-            resourceType,
-          }));
-        const isDuplicate = resourceIsDuplicateOfExistingResources({
-          existingResources: existingResourcesToUse,
-          newResource,
-        });
+        const existingResourcesToUse: FhirResource[] = await getExistingResourcesOrUseProvided({
+          existingResources,
+          ehr,
+          cxId,
+          practiceId,
+          patientId: ehrPatientId,
+          resourceType,
+        });
+        const isDuplicate = resourceIsDuplicateOfExistingResources({
+          existingResources: existingResourcesToUse,
+          newResource,
+        });

Add this function at the bottom of the file:

async function getExistingResourcesOrUseProvided({
  existingResources,
  ehr,
  cxId,
  practiceId,
  patientId,
  resourceType,
}: {
  existingResources?: FhirResource[];
  ehr: string;
  cxId: string;
  practiceId: string;
  patientId: string;
  resourceType: string;
}): Promise<FhirResource[]> {
  if (existingResources) return existingResources;
  
  return getExistingResourcesFromApi({
    ehr,
    cxId,
    practiceId,
    patientId,
    resourceType,
  });
}
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (1)

57-67: Consider documenting the asynchronous behavior in the function's JSDoc.

The function initiates an asynchronous process but the JSDoc doesn't explicitly mention this behavior. This could lead to confusion for developers who might expect the function to complete all processing before returning.

 /**
  * Starts the resource diff job to produce the resource type bundles containing
  * the resources in Metriport that are not in Canvas, or vice versa.
+ * 
+ * Note: This function starts an asynchronous job and returns immediately with the job ID.
+ * The actual bundle creation happens in the background.
  *
  * @param cxId - The cxId of the patient.
  * @param canvasPracticeId - The canvas practice id of the patient.
  * @param canvasPatientId - The canvas patient id of the patient.
  * @param direction - The direction of the resource diff bundles to create.
  * @param requestId - The request id of the job. (optional, defaults to a new UUID)
+ * @returns A promise that resolves to the created job ID.
  */
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts (1)

67-87: Consider parallel bundle fetching for improved performance.

The function fetches bundles sequentially for each resource type, which could be slow with many resource types. Consider using Promise.all to fetch bundles concurrently.

-    for (const resourceType of resourceTypes) {
-      const [existingResourcesBundle] = await Promise.all([
-        fetchEhrBundleFromApi({
-          ehr,
-          cxId,
-          practiceId,
-          patientId: ehrPatientId,
-          resourceType,
-          useCachedBundle: false,
-        }),
-        createOrReplaceBundleOnS3({
-          ehr,
-          cxId,
-          metriportPatientId,
-          ehrPatientId,
-          bundleType: BundleType.RESOURCE_DIFF_METRIPORT_ONLY,
-          bundle: getDefaultBundle(),
-          resourceType,
-          jobId,
-        }),
-      ]);
-      this.fetchedBundles.set(resourceType, existingResourcesBundle.bundle);
-    }
+    const fetchPromises = Array.from(resourceTypes).map(async resourceType => {
+      const [existingResourcesBundle] = await Promise.all([
+        fetchEhrBundleFromApi({
+          ehr,
+          cxId,
+          practiceId,
+          patientId: ehrPatientId,
+          resourceType,
+          useCachedBundle: false,
+        }),
+        createOrReplaceBundleOnS3({
+          ehr,
+          cxId,
+          metriportPatientId,
+          ehrPatientId,
+          bundleType: BundleType.RESOURCE_DIFF_METRIPORT_ONLY,
+          bundle: getDefaultBundle(),
+          resourceType,
+          jobId,
+        }),
+      ]);
+      return { resourceType, bundle: existingResourcesBundle.bundle };
+    });
+    
+    const results = await Promise.all(fetchPromises);
+    results.forEach(({ resourceType, bundle }) => {
+      this.fetchedBundles.set(resourceType, bundle);
+    });
packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts (1)

55-56: Consider simplifying this variable assignment.

This intermediate variable assignment doesn't add much value and could be simplified.

- const metriportPatientId = existingPatient.patientId;
+ const { patientId: metriportPatientId } = existingPatient;
packages/api/src/external/ehr/create-resource-diff-bundles.ts (2)

39-62: Consider additional error handling for async job creation.

The function uses .catch(processAsyncError) to handle errors from the job creation, but doesn't provide a way to know if the job was successfully created. Since this function returns void, the caller has no way to know if all jobs were successfully created.

Consider returning a result that indicates whether the job creation was initiated successfully, such as:

-async function createCanvasResourceDiffBundlesJob({
+async function createCanvasResourceDiffBundlesJob({
   cxId,
   canvasPatientId,
   direction,
   requestId,
-}: {
+}: {
   cxId: string;
   canvasPatientId: string;
   direction: ResourceDiffDirection;
   requestId: string;
-}): Promise<void> {
+}): Promise<boolean> {
   const cxMappings = await getCxMappingsByCustomer({ cxId, source: EhrSources.canvas });
   const cxMapping = cxMappings[0];
   if (!cxMapping) throw new MetriportError("Canvas CX mapping not found", undefined, { cxId });
   if (cxMappings.length > 1)
     throw new MetriportError("Multiple Canvas CX mappings found", undefined, { cxId });
-  createResourceDiffBundlesJob({
+  return createResourceDiffBundlesJob({
     cxId,
     canvasPracticeId: cxMapping.externalId,
     canvasPatientId,
     direction,
     requestId,
-  }).catch(processAsyncError(`createResourceDiffBundlesJob`));
+  })
+    .then(jobId => {
+      return true;
+    })
+    .catch(err => {
+      processAsyncError(`createResourceDiffBundlesJob`)(err);
+      return false;
+    });
 }

51-52: Add validation for empty array before accessing first element.

The code directly accesses the first element of the cxMappings array without first checking if the array is empty. While it throws an error afterward if cxMapping is falsy, following the coding guidelines of building deterministic functions, it's better to explicitly check array length first.

-  const cxMapping = cxMappings[0];
-  if (!cxMapping) throw new MetriportError("Canvas CX mapping not found", undefined, { cxId });
+  if (cxMappings.length === 0) throw new MetriportError("Canvas CX mapping not found", undefined, { cxId });
+  const cxMapping = cxMappings[0];
packages/shared/src/domain/job/types.ts (1)

3-4: Consider using more specific type definitions.

The JobParamsCx and JobParamsOps types use very generic Record<string, string | boolean> definitions. While this provides flexibility, it could lead to less type safety. Consider defining more specific interfaces if the structure of these parameters is known.

If the parameters have a known structure, you could define them more specifically:

export type JobParamsCx = {
  customerId?: string;
  // other known fields
  [key: string]: string | boolean | undefined;
};

export type JobParamsOps = {
  // known operational parameters
  [key: string]: string | boolean | undefined;
};
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts (1)

58-99: Function leverages patient mapping to retrieve the latest job.

The getLatestResourceDiffBundlesJobPayload function:

  1. Properly resolves the Metriport patient ID from the Canvas patient ID
  2. Uses the job type factory function for consistent job type identification
  3. Sets appropriate filtering parameters to find the latest relevant job
  4. Handles the case where no job exists by returning undefined
  5. Conditionally fetches pre-signed URLs based on job status

One minor optimization to consider: caching the patient mapping result if this function is called frequently, as the mapping lookup adds an extra database query.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2998a2a and c61f8c6.

📒 Files selected for processing (52)
  • packages/api/src/command/job/patient/complete.ts (1 hunks)
  • packages/api/src/command/job/patient/create.ts (1 hunks)
  • packages/api/src/command/job/patient/get.ts (1 hunks)
  • packages/api/src/command/job/patient/initialize.ts (1 hunks)
  • packages/api/src/command/job/patient/set-entry-status.ts (1 hunks)
  • packages/api/src/command/job/patient/update-total.ts (1 hunks)
  • packages/api/src/command/job/shared.ts (1 hunks)
  • packages/api/src/command/medical/document/document-conversion-status.ts (1 hunks)
  • packages/api/src/command/medical/patient/consolidated-recreate.ts (3 hunks)
  • packages/api/src/command/medical/patient/consolidated-webhook.ts (1 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (3 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts (1 hunks)
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundles.ts (1 hunks)
  • packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts (1 hunks)
  • packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (2 hunks)
  • packages/api/src/external/ehr/canvas/shared.ts (2 hunks)
  • packages/api/src/external/ehr/create-resource-diff-bundles.ts (1 hunks)
  • packages/api/src/models/db.ts (2 hunks)
  • packages/api/src/models/patient-job.ts (1 hunks)
  • packages/api/src/routes/ehr/canvas/patient.ts (3 hunks)
  • packages/api/src/routes/internal/ehr/athenahealth/index.ts (1 hunks)
  • packages/api/src/routes/internal/ehr/athenahealth/patient.ts (4 hunks)
  • packages/api/src/routes/internal/ehr/canvas/index.ts (1 hunks)
  • packages/api/src/routes/internal/ehr/canvas/patient.ts (2 hunks)
  • packages/api/src/routes/internal/ehr/elation/index.ts (1 hunks)
  • packages/api/src/routes/internal/ehr/elation/patient.ts (1 hunks)
  • packages/api/src/routes/internal/medical/patient-job.ts (1 hunks)
  • packages/api/src/routes/internal/medical/patient.ts (1 hunks)
  • packages/api/src/sequelize/migrations/2025-04-16_00_create-patient-job.ts (1 hunks)
  • packages/core/src/command/write-to-storage/s3/write-to-s3-cloud.ts (2 hunks)
  • packages/core/src/external/ehr/api/fetch-bundle.ts (2 hunks)
  • packages/core/src/external/ehr/api/initialize-job.ts (1 hunks)
  • packages/core/src/external/ehr/api/set-entry-status.ts (1 hunks)
  • packages/core/src/external/ehr/api/update-job-total.ts (1 hunks)
  • packages/core/src/external/ehr/bundle/bundle-shared.ts (3 hunks)
  • packages/core/src/external/ehr/bundle/commands/create-or-replace-bundle.ts (3 hunks)
  • packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts (9 hunks)
  • packages/core/src/external/ehr/bundle/commands/update-bundle.ts (5 hunks)
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts (1 hunks)
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts (3 hunks)
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (3 hunks)
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts (5 hunks)
  • packages/core/src/external/ehr/canvas/index.ts (5 hunks)
  • packages/core/src/external/ehr/shared.ts (2 hunks)
  • packages/core/src/util/sqs.ts (1 hunks)
  • packages/lambdas/src/ehr-compute-resource-diff-bundles.ts (1 hunks)
  • packages/lambdas/src/ehr-start-resource-diff-bundles.ts (1 hunks)
  • packages/shared/src/domain/job/job-status.ts (1 hunks)
  • packages/shared/src/domain/job/patient-job.ts (1 hunks)
  • packages/shared/src/domain/job/types.ts (1 hunks)
  • packages/shared/src/index.ts (1 hunks)
  • packages/shared/src/interface/external/ehr/fhir-resource.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, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('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 and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().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/routes/internal/ehr/elation/index.ts
  • packages/api/src/routes/internal/ehr/athenahealth/index.ts
  • packages/api/src/routes/internal/ehr/canvas/index.ts
  • packages/api/src/models/db.ts
  • packages/core/src/external/ehr/shared.ts
  • packages/api/src/routes/internal/medical/patient.ts
  • packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundles.ts
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts
  • packages/api/src/routes/internal/ehr/canvas/patient.ts
  • packages/api/src/external/ehr/canvas/shared.ts
  • packages/api/src/command/medical/document/document-conversion-status.ts
  • packages/core/src/external/ehr/bundle/commands/create-or-replace-bundle.ts
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts
  • packages/shared/src/domain/job/patient-job.ts
  • packages/lambdas/src/ehr-start-resource-diff-bundles.ts
  • packages/shared/src/index.ts
  • packages/core/src/command/write-to-storage/s3/write-to-s3-cloud.ts
  • packages/core/src/external/ehr/api/fetch-bundle.ts
  • packages/lambdas/src/ehr-compute-resource-diff-bundles.ts
  • packages/api/src/command/medical/patient/consolidated-recreate.ts
  • packages/api/src/command/job/patient/update-total.ts
  • packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts
  • packages/api/src/command/medical/patient/consolidated-webhook.ts
  • packages/api/src/routes/internal/ehr/athenahealth/patient.ts
  • packages/core/src/external/ehr/api/set-entry-status.ts
  • packages/api/src/command/job/patient/create.ts
  • packages/api/src/routes/internal/ehr/elation/patient.ts
  • packages/api/src/sequelize/migrations/2025-04-16_00_create-patient-job.ts
  • packages/core/src/external/ehr/api/update-job-total.ts
  • packages/api/src/command/job/patient/set-entry-status.ts
  • packages/shared/src/interface/external/ehr/fhir-resource.ts
  • packages/api/src/external/ehr/create-resource-diff-bundles.ts
  • packages/core/src/external/ehr/api/initialize-job.ts
  • packages/api/src/models/patient-job.ts
  • packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts
  • packages/core/src/external/ehr/bundle/bundle-shared.ts
  • packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts
  • packages/shared/src/domain/job/types.ts
  • packages/core/src/util/sqs.ts
  • packages/api/src/command/job/patient/complete.ts
  • packages/api/src/routes/internal/medical/patient-job.ts
  • packages/shared/src/domain/job/job-status.ts
  • packages/api/src/routes/ehr/canvas/patient.ts
  • packages/api/src/command/job/patient/initialize.ts
  • packages/api/src/command/job/shared.ts
  • packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts
  • packages/core/src/external/ehr/canvas/index.ts
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts
  • packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts
  • packages/api/src/command/job/patient/get.ts
  • packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts
  • packages/core/src/external/ehr/bundle/commands/update-bundle.ts
🧠 Learnings (5)
packages/core/src/external/ehr/api/fetch-bundle.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/core/src/external/ehr/canvas/index.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts (1)
Learnt from: thomasyopes
PR: metriport/metriport#3608
File: packages/core/src/external/ehr/canvas/index.ts:451-469
Timestamp: 2025-04-21T17:07:30.574Z
Learning: The `getMetriportOnlyBundleByResourceType` method in CanvasApi returns `Promise<Bundle | undefined>` because the Metriport-only bundle is a computed artifact that may not exist yet. In contrast, `getBundleByResourceType` returns `Promise<Bundle>` because it can always fetch from the EHR API if the S3 cached bundle doesn't exist.
🧬 Code Graph Analysis (27)
packages/api/src/models/db.ts (1)
packages/api/src/models/patient-job.ts (1)
  • PatientJobModel (28-102)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts (2)
packages/core/src/util/sqs.ts (2)
  • SQS_MESSAGE_BATCH_SIZE (2-2)
  • SQS_MESSAGE_BATCH_MILLIS_TO_SLEEP (3-3)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/api/src/command/medical/document/document-conversion-status.ts (1)
packages/api/src/command/medical/patient/consolidated-recreate.ts (1)
  • recreateConsolidated (20-52)
packages/shared/src/domain/job/patient-job.ts (2)
packages/shared/src/domain/job/job-status.ts (1)
  • JobStatus (4-4)
packages/shared/src/domain/job/types.ts (2)
  • JobParamsCx (3-3)
  • JobParamsOps (4-4)
packages/core/src/command/write-to-storage/s3/write-to-s3-cloud.ts (2)
packages/core/src/util/sqs.ts (2)
  • SQS_MESSAGE_BATCH_SIZE (2-2)
  • SQS_MESSAGE_BATCH_MILLIS_TO_SLEEP (3-3)
packages/shared/src/index.ts (1)
  • sleep (13-13)
packages/core/src/external/ehr/api/fetch-bundle.ts (1)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • SupportedResourceType (6-6)
packages/api/src/command/medical/patient/consolidated-recreate.ts (3)
packages/core/src/domain/conversion/fhir-to-medical-record.ts (1)
  • ConsolidationConversionType (2-2)
packages/api/src/external/ehr/create-resource-diff-bundles.ts (1)
  • createResourceDiffBundles (23-37)
packages/core/src/util/error/shared.ts (1)
  • processAsyncError (56-64)
packages/api/src/command/job/patient/update-tot A3E2 al.ts (5)
packages/core/src/external/ehr/api/update-job-total.ts (1)
  • UpdateJobTotalParams (7-10)
packages/api/src/command/job/shared.ts (1)
  • UpdateJobTotalParams (30-35)
packages/shared/src/domain/job/patient-job.ts (1)
  • PatientJob (4-22)
packages/api/src/command/job/patient/get.ts (1)
  • getPatientJobModelOrFail (27-31)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (2)
packages/core/src/external/ehr/canvas/index.ts (1)
  • SupportedCanvasDiffResource (71-71)
packages/api/src/command/mapping/patient.ts (1)
  • getPatientMappingOrFail (48-62)
packages/core/src/external/ehr/api/update-job-total.ts (3)
packages/api/src/command/job/shared.ts (1)
  • UpdateJobTotalParams (30-35)
packages/core/src/external/ehr/api/api-shared.ts (1)
  • ApiBaseParams (3-9)
packages/shared/src/index.ts (1)
  • MetriportError (40-40)
packages/api/src/command/job/patient/set-entry-status.ts (5)
packages/core/src/external/ehr/api/set-entry-status.ts (1)
  • setPatientJobEntryStatus (18-42)
packages/api/src/command/job/shared.ts (2)
  • UpdateJobCountParams (8-13)
  • UpdateJobCountResponse (15-22)
packages/api/src/models/patient-job.ts (2)
  • PatientJobModel (28-102)
  • patientJobRawColumnNames (9-26)
packages/shared/src/index.ts (1)
  • MetriportError (40-40)
packages/api/src/command/job/patient/complete.ts (1)
  • completePatientJob (19-46)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
packages/shared/src/medical/fhir/resources.ts (1)
  • resourceTypeForConsolidation (76-80)
packages/api/src/external/ehr/create-resource-diff-bundles.ts (5)
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (2)
  • CreateResourceDiffBundlesParams (11-17)
  • createResourceDiffBundlesJob (29-72)
packages/api/src/command/mapping/patient.ts (1)
  • getPatientMappings (64-74)
packages/api/src/command/mapping/cx.ts (1)
  • getCxMappingsByCustomer (99-110)
packages/shared/src/index.ts (1)
  • MetriportError (40-40)
packages/core/src/util/error/shared.ts (1)
  • processAsyncError (56-64)
packages/core/src/external/ehr/api/initialize-job.ts (3)
packages/api/src/command/job/shared.ts (1)
  • InitializeJobParams (24-28)
packages/core/src/external/ehr/api/api-shared.ts (1)
  • ApiBaseParams (3-9)
packages/shared/src/index.ts (1)
  • MetriportError (40-40)
packages/api/src/models/patient-job.ts (4)
packages/shared/src/domain/job/patient-job.ts (1)
  • PatientJob (4-22)
packages/shared/src/domain/job/job-status.ts (1)
  • JobStatus (4-4)
packages/shared/src/domain/job/types.ts (2)
  • JobParamsCx (3-3)
  • JobParamsOps (4-4)
packages/api/src/models/_default.ts (1)
  • ModelSetup (17-17)
packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts (3)
packages/core/src/external/ehr/bundle/bundle-shared.ts (3)
  • BundleKeyBaseParams (64-74)
  • getS3UtilsInstance (49-51)
  • createKeyMap (58-62)
packages/shared/src/index.ts (3)
  • BadRequestError (39-39)
  • errorToString (42-42)
  • MetriportError (40-40)
packages/core/src/external/aws/s3.ts (1)
  • fileExists (198-207)
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (4)
packages/api/src/command/job/patient/create.ts (1)
  • createPatientJob (13-52)
packages/api/src/external/ehr/canvas/shared.ts (1)
  • getCreateCanvasResourceDiffBundlesJobType (49-51)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-factory.ts (1)
  • buildEhrStartResourceDiffBundlesHandler (6-13)
packages/core/src/util/error/shared.ts (1)
  • processAsyncError (56-64)
packages/shared/src/domain/job/types.ts (1)
packages/shared/src/domain/job/job-status.ts (1)
  • JobStatus (4-4)
packages/api/src/command/job/patient/complete.ts (5)
packages/api/src/command/job/shared.ts (1)
  • CompleteJobParams (37-42)
packages/shared/src/domain/job/patient-job.ts (1)
  • PatientJob (4-22)
packages/api/src/command/job/patient/get.ts (1)
  • getPatientJobModelOrFail (27-31)
packages/shared/src/domain/job/job-status.ts (1)
  • validateNewJobStatus (19-62)
packages/shared/src/common/date.ts (1)
  • buildDayjs (67-69)
packages/api/src/routes/internal/medical/patient-job.ts (7)
packages/api/src/routes/helpers/request-logger.ts (1)
  • requestLogger (10-63)
packages/api/src/routes/util.ts (3)
  • asyncHandler (10-28)
  • getFrom (131-133)
  • getFromQueryOrFail (61-65)
packages/api/src/command/job/patient/initialize.ts (1)
  • initializePatientJob (18-39)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/api/src/command/job/patient/update-total.ts (1)
  • updatePatientJobTotal (16-31)
packages/shared/src/domain/job/types.ts (1)
  • isValidJobEntryStatus (14-21)
packages/core/src/external/ehr/api/set-entry-status.ts (1)
  • setPatientJobEntryStatus (18-42)
packages/shared/src/domain/job/job-status.ts (1)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/api/src/routes/ehr/canvas/patient.ts (5)
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (1)
  • createResourceDiffBundlesJob (29-72)
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 (7-9)
packages/shared/src/index.ts (1)
  • BadRequestError (39-39)
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts (2)
  • getLatestResourceDiffBundlesJobPayload (67-99)
  • getResourceDiffBundlesJobPayload (37-56)
packages/api/src/command/job/patient/initialize.ts (6)
packages/core/src/external/ehr/api/initialize-job.ts (1)
  • InitializeJobParams (7-9)
packages/api/src/command/job/shared.ts (1)
  • InitializeJobParams (24-28)
packages/shared/src/domain/job/patient-job.ts (1)
  • PatientJob (4-22)
packages/api/src/command/job/patient/get.ts (1)
  • getPatientJobModelOrFail (27-31)
packages/shared/src/domain/job/job-status.ts (1)
  • validateNewJobStatus (19-62)
packages/shared/src/common/date.ts (1)
  • buildDayjs (67-69)
packages/api/src/command/job/shared.ts (4)
packages/shared/src/domain/job/types.ts (1)
  • JobEntryStatus (11-11)
packages/shared/src/domain/job/job-status.ts (1)
  • JobStatus (4-4)
packages/core/src/external/ehr/api/initialize-job.ts (1)
  • InitializeJobParams (7-9)
packages/core/src/external/ehr/api/update-job-total.ts (1)
  • UpdateJobTotalParams (7-10)
packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts (4)
packages/api/src/command/job/patient/get.ts (4)
  • PatientJobPayload (69-72)
  • getPatientJobByIdOrFail (12-16)
  • createPatientJobPayload (74-82)
  • getLatestPatientJob (40-61)
packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts (2)
  • FetchResourceDiffBundlePreSignedUrlsResult (22-25)
  • fetchCanvasResourceDiffBundlePreSignedUrls (41-83)
packages/api/src/command/mapping/patient.ts (1)
  • getPatientMappingOrFail (48-62)
packages/api/src/external/ehr/canvas/shared.ts (1)
  • getCreateCanvasResourceDiffBundlesJobType (49-51)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (3)
packages/shared/src/interface/external/ehr/fhir-resource.ts (1)
  • FhirResource (15-15)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/utils.ts (1)
  • resourceIsDuplicateOfExistingResources (42-155)
packages/core/src/external/ehr/api/set-entry-status.ts (1)
  • setPatientJobEntryStatus (18-42)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts (3)
packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles.ts (1)
  • StartResourceDiffBundlesRequest (3-3)
packages/core/src/external/ehr/api/initialize-job.ts (1)
  • initializeJob (16-35)
packages/core/src/external/ehr/api/update-job-total.ts (1)
  • updateJobTotal (18-38)
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
  • GitHub Check: infra-api-lambdas / deploy
  • GitHub Check: mllp-server / deploy
  • GitHub Check: api / deploy
  • GitHub Check: Analyze (javascript)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
🔇 Additional comments (119)
packages/api/src/routes/internal/ehr/elation/index.ts (1)

4-4: Approve whitespace change for import grouping

The added blank line after the import block improves readability and keeps consistency across internal EHR routers.

packages/api/src/routes/internal/ehr/canvas/patient.ts (2)

21-21: Approve JSDoc return update for /appointments endpoint

The @returns 200 OK annotation clearly documents the successful response status, aligning with TSDoc conventions and improving API docs.


42-42: Approve JSDoc return update for root / endpoint

Updating the @returns tag to reflect the HTTP 200 status accurately describes the endpoint’s behavior and enhances the documentation.

packages/core/src/util/sqs.ts (1)

1-3: Centralize SQS messaging parameters into constants

Extracting these values eliminates magic numbers and ensures consistent configuration across SQS integrations (batch size, message size limit, and throttle delay).

packages/api/src/routes/internal/ehr/canvas/index.ts (1)

3-3: Approve whitespace addition for import consistency

The blank line after the patient import enhances readability and matches formatting in sister modules.

packages/api/src/routes/internal/ehr/athenahealth/index.ts (1)

3-3: Approve blank line for consistent import separation

This simple formatting tweak aligns with the code style used in the other internal EHR route index files.

packages/api/src/routes/internal/ehr/elation/patient.ts (1)

16-21: Documentation improvement is consistent with project standards.

The addition of @returns 200 OK to the JSDoc improves clarity and aligns with similar documentation patterns in related internal EHR route handlers.

packages/api/src/models/db.ts (2)

41-41: Added import for the new PatientJobModel.

This import supports the new patient job management functionality.


69-69: Registered PatientJobModel with Sequelize.

The PatientJobModel is now properly registered in the database initialization process, enabling tracking of patient jobs with fields for status, timestamps, and job metadata.

packages/core/src/external/ehr/bundle/create-resource-diff-bundles/create-resource-diff-bundle-shared.ts (1)

9-9: Added required jobId to CreateResourceDiffBundlesBaseRequest type.

This change supports the new job-based architecture for resource diff bundles, enabling tracking of bundle creation processes through persistent job records.

The jobId will be used across the system to track job status, store and retrieve bundles in S3, and provide asynchronous access to bundle creation results.

packages/lambdas/src/ehr-start-resource-diff-bundles.ts (1)

51-51: Required field added to schema to support job tracking

The schema now requires a jobId string field, aligning with the introduction of job tracking for resource diff bundles. This change ensures that all resource diff bundle creation requests will be explicitly associated with a job for tracking purposes.

packages/api/src/routes/internal/medical/patient.ts (1)

107-107: New patient job routes properly integrated

The addition of the patient job routes import and mounting them at the /job path extends the internal API surface to support the newly implemented job tracking functionality for patient-related operations.

Also applies to: 115-115

packages/api/src/routes/internal/ehr/athenahealth/patient.ts (1)

20-20: Documentation improvement for route handlers

Added return annotations to JSDoc comments for route handlers, improving API documentation clarity. This is a good practice that aligns with documentation standards in other similar files.

Also applies to: 37-37, 54-54, 72-72

packages/api/src/command/medical/document/document-conversion-status.ts (1)

113-113: Enable resource diff bundle creation during document conversion

The addition of isDq: true to the recreateConsolidated function calls ensures that resource diff bundles are created as part of the document conversion workflow. This properly integrates the document conversion process with the new job-based resource diff bundle system.

Also applies to: 120-120

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

5-5: Good addition of ResourceDiffDirection import.

This import supports the new job-based asynchronous resource diff workflow you're implementing.


49-51: Well-designed utility function for job type generation.

The function is pure, deterministic, and follows functional programming principles with a single input and output. The naming is clear and follows camelCase convention as per guidelines.

packages/api/src/external/ehr/canvas/command/bundle/refresh-ehr-bundles.ts (1)

10-13: Improved documentation clarity.

The updated JSDoc comment provides more explicit details about the function's behavior, which helps maintainers understand exactly what the function does.

packages/lambdas/src/ehr-compute-resource-diff-bundles.ts (1)

50-50: Schema correctly updated for job tracking.

The addition of the required jobId field supports the new job-based asynchronous workflow for resource diff bundles.

packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-cloud.ts (3)

5-9: Good centralization of SQS constants.

Importing these constants from a shared utility module follows the DRY principle and improves maintainability. This follows the best practice of moving literals to constants and centralizing their definition.


37-37: Proper use of the centralized SQS_MESSAGE_BATCH_SIZE constant.

Replacing the hardcoded value with the imported constant improves consistency and maintainability.


49-49: Proper use of the centralized SQS_MESSAGE_BATCH_MILLIS_TO_SLEEP constant.

Replacing the hardcoded value with the imported constant improves consistency and maintainability.

packages/api/src/command/medical/patient/consolidated-webhook.ts (2)

3-5: Good refactoring to centralized imports.

The changes to import paths reflect a move toward more centralized shared modules, which improves maintainability and consistency. This aligns well with the coding guidelines for minimizing coupling.


14-14: Successfully updated logging to follow project standards.

The logger initialization has been properly updated to use the standardized out() pattern instead of the previous Util.log approach, which aligns with the guideline to "try to use out().log instead" of console logging.

packages/shared/src/index.ts (1)

36-38: Appropriate expansion of shared types.

These exports properly expose the new job-related domain entities for use throughout the application. This modular approach follows the guideline of organizing code in layers (Onion Pattern) by exposing domain types in the shared package for use by other layers.

packages/api/src/command/medical/patient/consolidated-recreate.ts (3)

6-7: Well-structured imports for new functionality.

The imports correctly bring in the necessary types and functions to support the new resource diff bundle creation functionality.


24-24: Good parameter addition with default value.

The addition of the optional isDq parameter with a default value of false is a clean way to add new functionality while maintaining backward compatibility with existing callers.

Also applies to: 29-29


42-48: Correctly implemented asynchronous job with error handling.

This implementation properly follows the guideline to use .catch() with processAsyncError for handling errors in asynchronous, non-awaited Promise calls. The conditional execution based on the isDq flag elegantly integrates the new resource diff functionality with minimal changes to the existing code flow.

packages/core/src/command/write-to-storage/s3/write-to-s3-cloud.ts (2)

5-9: Good refactoring to centralized constants.

The import of SQS-related constants from a centralized location follows the coding guidelines for moving literals to constants and improves consistency across the codebase.


34-34: Successfully applied centralized constants.

The usage of the imported constants in place of local values maintains consistent behavior while improving maintainability by centralizing configuration values.

Also applies to: 42-42

packages/core/src/external/ehr/bundle/bundle-shared.ts (3)

16-16: Good addition of an optional jobId parameter

The addition of the optional jobId parameter to the CreateBundlePrefixParams type is well-defined and follows the TypeScript best practices by explicitly typing it as string | undefined.


25-29: Well-implemented changes to support job-specific bundle prefixes

The implementation correctly integrates the jobId parameter into the bundle prefix generation, with a sensible default of "latest" when no job ID is provided. This maintains backward compatibility while enabling the new job-tracking functionality.


71-71: Consistent type definition for BundleKeyBaseParams

The addition of the jobId parameter to the BundleKeyBaseParams type is consistent with the earlier changes and maintains the type safety throughout the bundle key generation process.

packages/core/src/external/ehr/bundle/commands/create-or-replace-bundle.ts (3)

21-21: Well-documented parameter

The JSDoc comment clearly explains the purpose of the jobId parameter and its default behavior, which aids developers in understanding how to use this function.


32-32: Properly added jobId parameter to function signature

The jobId parameter is correctly added to the function signature, maintaining consistency with the type definition and JSDoc.


49-49: Correct propagation of jobId parameter

The jobId parameter is properly passed to the createKey function, ensuring that the job-specific bundle key is generated correctly.

packages/core/src/external/ehr/api/fetch-bundle.ts (2)

34-37: Enhanced return type with additional resourceTypes information

The return type has been correctly updated to include both the bundle and the associated resource types, which provides more context about the returned data and improves type safety.


52-55: Well-structured return value

The return value has been properly restructured to match the updated return type, extracting both the bundle and resourceTypes from the response data.

packages/shared/src/domain/job/patient-job.ts (2)

1-3: Clean imports for job-related types

The imports are minimal and focused on what's needed for the PatientJob type definition, following good practices for keeping dependencies clean.


4-22: Well-structured PatientJob type definition

The PatientJob type is well-defined with comprehensive fields covering job identification, metadata, status tracking, timestamps, and outcome metrics. The use of unknown for the data field provides type safety while allowing flexibility in the data structure.

This type will serve as a good foundation for the job management system being implemented.

packages/api/src/command/job/patient/update-total.ts (1)

16-31: Good implementation of the patient job total update function.

The function correctly validates that total is greater than 0 and resets the counters when updating. The warning comment about not updating status to avoid race conditions is an important precaution.

packages/core/src/external/ehr/api/set-entry-status.ts (2)

18-42: Well-structured API client function with proper error handling.

The function correctly constructs the request URL, handles errors, and provides detailed context in error messages.


23-23: Log context includes both jobId and cxId

Good practice including both jobId and cxId in the logging context for easier debugging and tracing.

packages/api/src/command/job/patient/create.ts (1)

21-38: Correctly enforces the single running job constraint

The implementation properly checks for existing running jobs with the same criteria and throws a clear error message when the constraint is violated.

packages/api/src/command/job/patient/complete.ts (1)

19-46: Properly handles job completion with status validation

The function correctly validates status transitions, updates the finish timestamp when appropriate, and conditionally executes the completion callback.

packages/core/src/external/ehr/api/update-job-total.ts (3)

7-10: Good type definition with reuse of existing types

The type UpdateJobTotalParams effectively reuses the existing ApiBaseParams type through Pick to extract the cxId field, demonstrating good practice in type composition.


12-17: Clear function documentation

The function documentation clearly explains the purpose and each parameter of the function, which helps with maintainability and understanding.


18-38: Well-structured API client with proper error handling

The implementation follows good practices for API client code:

  • Uses structured logging with contextual information
  • Properly constructs the URL with query parameters
  • Validates the response data before proceeding
  • Implements thorough error handling with detailed context in the MetriportError
  • Follows the functional programming style recommended in the guidelines
packages/api/src/command/job/patient/set-entry-status.ts (4)

7-23: Excellent documentation explaining concurrency considerations

The detailed JSDoc comments clearly explain the critical aspect of concurrent-safe counter updates and the reasoning behind the implementation approach. This is an excellent example of documenting "why" rather than "how".


30-39: Robust implementation of concurrent-safe counter updates

The use of Sequelize's increment method is a good approach for handling concurrent updates without race conditions. The spreading of arrays based on the entry status is an elegant way to conditionally increment the right counter.


44-52: Clean mapping from raw DB column values to response object

The code efficiently maps from raw DB column values to a structured response object using the column name constants, which improves maintainability if column names change.


53-59: Good job completion logic with boundary case handling

The completion logic correctly checks if the job should be completed based on the total count and current successful/failed counts, handling the boundary case where they equal or exceed the total.

packages/core/src/external/ehr/api/initialize-job.ts (3)

7-9: Clean type definition reusing existing type

The type definition for InitializeJobParams efficiently uses Pick to extract only the needed property from ApiBaseParams, following good type composition practices.


11-15: Clear function documentation

The JSDoc comments clearly explain the purpose and parameters of the function, aiding in code understanding and maintainability.


16-35: Well-structured API client with proper error handling

The implementation is clean and follows best practices:

  • Uses structured logging with context
  • Properly constructs the URL with query parameters
  • Validates the response data
  • Implements thorough error handling with detailed context in the MetriportError
  • Follows functional programming principles with a single, focused purpose
packages/api/src/command/job/patient/initialize.ts (3)

6-17: Comprehensive function documentation

The JSDoc comments provide excellent documentation including parameters, return values, and potential exceptions, which significantly enhances maintainability and developer experience.


18-33: Clean implementation with proper status transition validation

The code correctly handles:

  • Job retrieval with appropriate error handling
  • Status transition validation
  • Conditional fields update based on the status change
  • Warning comment about race conditions

The implementation is well-structured and follows the functional approach recommended in the guidelines.


34-39: Efficient update and return of job data

The function efficiently updates only the necessary fields and returns the updated job data, following good practices for database operations.

packages/api/src/sequelize/migrations/2025-04-16_00_create-patient-job.ts (2)

9-102: Well-structured migration with proper transaction handling F438 .

The migration for creating the patient_job table is well-organized with appropriate column definitions, constraints, and indexing. The transaction usage ensures that all operations either complete fully or roll back, maintaining database integrity.


104-113: Well-implemented down migration.

The down migration correctly removes the index before dropping the table, ensuring clean rollback functionality. The comment explaining that data won't be reintroduced is helpful for future developers.

packages/shared/src/interface/external/ehr/fhir-resource.ts (2)

2-6: Good type safety improvement with resource type enumeration.

Converting from a generic string type to an explicit enum of supported resource types improves type safety and validation. The approach of creating a constant array and deriving a union type from it is a good TypeScript pattern.


11-11: Proper use of Zod enumeration for runtime validation.

Using z.enum(supportedResourceTypes) correctly implements runtime validation of resource types, ensuring consistency between TypeScript type checking and runtime behavior.

packages/core/src/external/ehr/bundle/commands/update-bundle.ts (1)

53-53: Good propagation of jobId throughout the function.

The jobId parameter is consistently propagated to the key creation, fetch operation, and error metadata, which maintains proper context throughout the entire operation chain and enables better error debugging.

Also applies to: 63-63, 86-88

packages/api/src/models/patient-job.ts (2)

9-26: Good practice: Defining raw column names as constants.

Defining raw column names as constants improves maintainability and reduces the risk of SQL errors from mistyped column names. The comment referencing where these constants are used helps other developers understand their purpose.


28-45: Model correctly implements PatientJob interface.

The PatientJobModel properly declares all fields to match the PatientJob interface from the shared package, ensuring type consistency across the codebase.

packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/compute/ehr-compute-resource-diff-bundles-local.ts (4)

6-6: Good addition of job status management.

This import brings in the function needed to update the job entry status, which aligns with the new job management system.


30-30: Proper parameter integration for job tracking.

Adding the jobId parameter properly integrates this function with the job management system.


33-63: Good error handling for job status tracking.

The implementation of try-catch with appropriate job status updates ensures that job status is properly recorded regardless of success or failure.


88-88: Fix for correct resource extraction from bundle.

This fixes a potential issue by correctly accessing the entry's resource property from the bundle.

packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/start-job.ts (4)

2-3: Good error handling for asynchronous operations.

The addition of processAsyncError is appropriate for handling errors in asynchronous operations, following the coding guidelines to use .catch() when not awaiting a Promise.


29-35: Function name and return type accurately reflect new job-based approach.

Renaming the function to include "Job" and changing the return type to Promise<string> clearly indicates this function's purpose has changed to creating and returning a job ID.


46-55: Well-structured job creation with appropriate parameters.

The job creation includes all necessary parameters and uses limitedToOneRunningJob: true to prevent multiple jobs running concurrently for the same patient and job type.


57-67: Good asynchronous processing pattern with improved error handling.

The asynchronous execution with .catch(processAsyncError(...)) follows the coding guidelines and allows the function to return quickly while the job runs in the background.

packages/api/src/external/ehr/canvas/command/bundle/fetch-ehr-bundle.ts (4)

21-24: Good return type structure.

The new FetchCanvasBundleResult type clearly defines the structure of the function's return value, making it easier for consumers to understand what they're getting.


36-39: Clear documentation of return value.

The updated JSDoc accurately describes the function's behavior and return value.


53-53: Simplified patient ID retrieval.

Directly using existingPatient.patientId instead of fetching the patient again improves efficiency.


74-74: Return structure matches the documented behavior.

The return statement correctly provides both the bundle and the resource types, as described in the function's documentation.

packages/core/src/external/ehr/bundle/create-resource-diff-bundles/steps/start/ehr-start-resource-diff-bundles-local.ts (6)

10-11: Good job management integration.

Adding the imports for job initialization and total updates properly integrates this function with the job management system.


34-35: Proper parameter addition for job tracking.

Adding the jobId parameter ensures proper tracking throughout the job lifecycle.


36-36: Appropriate job initialization.

Starting by initializing the job establishes the proper lifecycle management.


85-86: Consistent job ID propagation.

Passing the jobId to the createOrReplaceBundleOnS3 function ensures consistent job tracking.


88-88: Simplified bundle storage.

Storing only the .bundle property instead of the entire response clarifies what's being stored and used later.


98-99: Good job progress tracking.

Updating the job total helps track progress and provides better visibility into job status.

packages/api/src/routes/internal/medical/patient-job.ts (3)

1-11: LGTM! Clean imports following package structure.

The imports are well-organized, importing domain types from shared packages and local command functions from appropriate locations.


14-31: LGTM! Well-implemented initialize job endpoint.

The endpoint follows best practices:

  • Clear JSDoc documentation
  • Proper use of middleware (requestLogger, asyncHandler)
  • Parameter validation with appropriate error handling
  • Clean separation of routing and business logic

61-87: LGTM! Well-structured entry status update endpoint.

The endpoint correctly:

  • Validates input parameters
  • Checks entry status validity using the imported validator
  • Returns a standard HTTP status response
packages/api/src/command/job/shared.ts (1)

3-43: LGTM! Well-structured type definitions for job management.

The type definitions are:

  • Well-organized and consistent
  • Clearly named with descriptive parameters
  • Properly supporting optional operations with flags like forceStatusUpdate
  • Supporting callback patterns with Promise-based onCompleted functions
  • Following TypeScript best practices by leveraging shared domain types

These types provide a strong foundation for the job management system and ensure type safety across the implementation.

packages/api/src/external/ehr/canvas/command/bundle/fetch-resource-diff-bundle-pre-signed-urls.ts (3)

16-20: Refactored function signature aligns with job-oriented architecture.

The renamed function and parameter reordering appropriately reflect the new job-based approach. Moving from returning full bundles to pre-signed URLs will improve performance and reduce memory usage.

Also applies to: 41-50


22-25: LGTM! Clear return type for pre-signed URLs.

The new return type properly defines the structure for returning both pre-signed URLs and their corresponding resource types.


62-82: LGTM! Efficient URL collection with resource type tracking.

The implementation efficiently:

  • Builds the list of resource types to process
  • Calls the Canvas API to get pre-signed URLs for each resource type
  • Tracks which resource types have corresponding URLs
  • Filters out resource types without URLs in the response

This approach ensures that clients only receive valid resource types with available URLs.

packages/shared/src/domain/job/job-status.ts (2)

3-9: LGTM! Well-defined job status constants and helper functions.

Good job using TypeScript's const assertions for creating the type from the literal values. The isJobDone helper function clearly expresses the intent of checking for terminal states.


19-62: LGTM! Comprehensive state transition validation.

The validateNewJobStatus function enforces a clear state machine for job status transitions:

  • Prevents returning to "waiting" state
  • Only allows "processing" from "waiting" or self-transitions
  • Only allows "completed" from "processing"
  • Prevents changing from "completed" to "failed"

The error messages are descriptive and include current and new status for debugging.

packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts (7)

3-4: Excellent code organization importing dayjs with plugins.

The imports correctly include dayjs and its duration plugin. Following the dayjs documentation, plugins must be imported separately before extending.


9-9: Properly extending dayjs with the duration plugin.

Correctly extending dayjs with the duration plugin before using dayjs.duration.


15-15: Good use of dayjs.duration for improved readability.

Using dayjs.duration to define the expiration time for pre-signed URLs improves readability compared to raw seconds. This follows the coding guidelines for date and time handling.


47-48: Code follows Onion Pattern and functional programming principles.

The function properly implements the shared key creation pattern, delegating to specialized key creation functions while maintaining the separation of concerns. The function is well-structured, deterministic, and follows the functional programming approach outlined in the coding guidelines.


62-73: Error handling follows coding guidelines.

The error handling properly uses MetriportError with a static message and adds dynamic data to the additionalInfo property, which follows the error handling guidelines. It also correctly preserves the original error as the cause.


131-182: Well-implemented pre-signed URL function following DRY principles.

The fetchBundlePreSignedUrl function is well-implemented:

  1. It reuses logic from fetchBundle while avoiding code duplication
  2. It properly handles errors and uses appropriate logging
  3. It has comprehensive JSDoc comments
  4. It correctly uses the S3Utils instance to generate the pre-signed URL
  5. The context-specific information is included in error metadata

This implementation aligns with the learnings about returning undefined when an artifact doesn't exist.


165-165:

✅ Verification successful

Verify bundleUrlDuration conversion to seconds.

Ensure that bundleUrlDuration.asSeconds() returns an integer value as required by the S3 pre-signed URL duration parameter. If it returns a floating-point number, it might need to be rounded or floored.


🏁 Script executed:

#!/bin/bash
# Check how asSeconds is used in other places in the codebase
rg "asSeconds\(\)" --type ts

Length of output: 2391


🏁 Script executed:

#!/bin/bash
# Locate the declaration and assignment of bundleUrlDuration for context
rg "bundleUrlDuration" -n -C3 packages/core/src/external/ehr/bundle/commands/fetch-bundle.ts

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Search for getSignedUrl definition in the core AWS S3 utility
rg "getSignedUrl" -n -C3 packages/core/src/external/aws/s3.ts

Length of output: 1811


bundleUrlDuration.asSeconds() returns an integer

bundleUrlDuration is defined as dayjs.duration(1, "hour"), so .asSeconds() yields 3600 (an integer). This matches how other fixed-duration values are used throughout the repo, and the S3 presigner’s Expires parameter accepts a numeric value. No rounding or flooring is needed here.

packages/api/src/routes/ehr/canvas/patient.ts (5)

7-11: Well-structured imports following the functional organization pattern.

The imports are well-organized and follow the project's functional organization pattern, importing specific functions only when needed rather than entire modules. This reduces coupling and promotes modularity.


78-100: Proper refactoring to support asynchronous job-based workflow.

The endpoint has been correctly refactored to:

  1. Start the resource diff job asynchronously
  2. Return the job ID instead of waiting for completion
  3. Maintain consistent parameter validation
  4. Update the JSDoc comments to reflect the changes

This is a good architectural change that improves scalability and responsiveness.


102-133: Well-implemented endpoint for retrieving latest job information.

This new endpoint follows RESTful principles and is consistent with the application's pattern for:

  1. Parameter extraction and validation
  2. Error handling
  3. Response formatting

The implementation correctly handles fetching the latest job information and returns pre-signed URLs for completed jobs.


146-168: Correctly implemented endpoint for retrieving specific job information.

The endpoint for retrieving a specific job has been properly updated to:

  1. Extract the jobId from parameters
  2. Pass it to the appropriate service function
  3. Return job information with pre-signed URLs for completed jobs

This implementation properly supports the new job-based workflow.


92-98:

❌ Incorrect review comment

Consider handling potential failure in job creation.

The code does not have any specific handling if createResourceDiffBundlesJob fails. Since this function starts an asynchronous process, it's important to properly communicate failures to the client.


🏁 Script executed:

#!/bin/bash
# Check how createResourceDiffBundlesJob handles errors
rg -A 10 "createResourceDiffBundlesJob" --type ts | grep -i "throw\|error\|catch"

Length of output: 206


I’d like to confirm how errors are propagated both in the route handler and in the job–creation utility. Let’s grab a bit more context:


🏁 Script executed:

#!/bin/bash

echo "=== patient.ts (top 50 lines) ==="
sed -n '1,50p' packages/api/src/routes/ehr/canvas/patient.ts || true

echo
echo "=== createResourceDiffBundlesJob call (±5 lines) ==="
rg -n "createResourceDiffBundlesJob" -C 5 packages/api/src/routes/ehr/canvas/patient.ts || true

echo
echo "=== create-resource-diff-bundles.ts (top 200 lines) ==="
sed -n '1,200p' packages/api/src/external/ehr/create-resource-diff-bundles.ts || true

echo
echo "=== processAsyncError definition (±5 lines) ==="
rg -n "processAsyncError" -C 5 packages/api/src || true

Length of output: 73194


No additional route‐level error handling needed

The asyncHandler wrapper already forwards any promise rejections from
createResourceDiffBundlesJob (e.g. invalid direction or missing mapping)
to Express’s error middleware, resulting in a 4xx/5xx response.
Failures in the underlying job scheduling call are intentionally caught
and logged via processAsyncError inside the start‐job implementation.

No changes required here.

Likely an incorrect or invalid review comment.

packages/shared/src/domain/job/types.ts (2)

6-12: Good use of const assertions and type aliases.

The code correctly uses as const to create literal types and then builds a clear type hierarchy of job status types. This follows TypeScript best practices and makes the code more maintainable.


14-21: Well-implemented type predicate for job entry status validation.

The isValidJobEntryStatus function correctly implements a TypeScript type predicate, which helps with type narrowing. The function is clear, concise, and follows the functional programming principles from the coding guidelines.

packages/api/src/command/job/patient/get.ts (7)

6-10: Well-structured function for retrieving patient jobs.

The implementation follows a clean pattern of using a helper function (getPatientJobModel) and returning the data values. The undefined return type is explicitly declared, which helps with type safety.


12-16: Good error handling approach.

This function follows the best practice of providing a dedicated "OrFail" version that throws a descriptive error with relevant context information. Including the params in the additional error info is helpful for debugging.


18-25: Clean database query implementation.

The function is focused and performs a single responsibility - retrieving a Sequelize model instance. The use of explicit return types improves code readability and type safety.


33-38: Well-defined type using TypeScript's Pick utility.

This type definition is clean and reuses the base PatientJob type, allowing for flexible status filtering with either a single status or an array of statuses.


40-61: Robust implementation for retrieving the latest patient job.

The function properly handles:

  1. Status parameter normalization
  2. Dynamic query construction for optional filters
  3. Sorting to get the latest job first
  4. Proper null-checking before returning results

63-67: Utility function follows functional programming principles.

This helper function is pure, deterministic, and handles the three possible states of the status parameter (undefined, single value, array) clearly.


69-82: Generic payload type and creator function enhances code reusability.

The PatientJobPayload type and createPatientJobPayload function provide a consistent pattern for returning job metadata with optional response data. This generic approach promotes consistency across different job types.

packages/core/src/external/ehr/canvas/index.ts (4)

32-32: Import aligns with resource diff direction functionality.

The added import is required for the type used in the modified method signatures.


42-42: Import updated to reflect function signature change.

The import now correctly references fetchBundlePreSignedUrl which aligns with the changed return type in the modified methods.


460-486: Method renamed to accurately reflect its new return value and behavior.

The method name change from getResourceDiffByResourceType to getResourceDiffBundlePreSignedUrlByResourceType clearly indicates that it now returns a pre-signed URL instead of the actual bundle. The addition of the jobId parameter supports the new job-based workflow, and the return type change from Bundle to string | undefined properly reflects that a URL is being returned.

This change is consistent with the broader shift to asynchronous processing of resource diffs via jobs.


576-596: Private helper method updated to match new signature and behavior.

The getBundlePreSignedUrl method aligns with the public API changes by:

  1. Adding the jobId parameter to support job-based bundle retrieval
  2. Changing the return type to string | undefined to match the pre-signed URL return value
  3. Calling fetchBundlePreSignedUrl instead of a previous bundle fetching function

The code maintains consistency and supports the new asynchronous workflow.

packages/api/src/external/ehr/canvas/job/create-resource-diff-bundles/get-job-payload.ts (3)

1-15: Well-organized imports with clear namespace grouping.

The imports are logically grouped:

  1. Shared interface types
  2. Command/utility functions from the job management system
  3. Patient mapping utility
  4. Canvas-specific functions and types

This organization follows best practices for clear dependency management.


16-25: Clear type definitions with appropriate reuse of generic types.

The GetResourceDiffBundlesJobPayloadParams type clearly defines the required parameters for retrieving job payloads. The ResourceDiffBundlesJobPayload type reuses the generic PatientJobPayload type, which promotes type consistency across the job management system.


26-56: Well-documented function with proper error handling.

The getResourceDiffBundlesJobPayload function:

  1. Has comprehensive JSDoc comments documenting parameters, return values, and errors
  2. Uses the "OrFail" version of job retrieval to provide clear error handling
  3. Conditionally fetches additional data based on job status
  4. Returns a consistent payload structure in all cases

This implementation ensures robust error handling and predictable response structures.

Comment on lines +49 to +51
if (isNaN(+total)) {
throw new BadRequestError("Total must be a number");
}
Copy link
@coderabbitai coderabbitai bot Apr 23, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Total validation should also check for positive values.

While you check that total is a number, you should also validate that it's greater than 0 since the updatePatientJobTotal function will throw an error if the total is less than 1.

if (isNaN(+total)) {
  throw new BadRequestError("Total must be a number");
}
+if (+total < 1) {
+  throw new BadRequestError("Total must be greater than 0");
+}
📝 Committable suggestion

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

Suggested change
if (isNaN(+total)) {
throw new BadRequestError("Total must be a number");
}
if (isNaN(+total)) {
throw new BadRequestError("Total must be a number");
}
if (+total < 1) {
throw new BadRequestError("Total must be greater than 0");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is checked within the command

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

The search found the implementation of updatePatientJobTotal. Let me check if it includes validation for positive values:

@thomasyopes You're absolutely right. I've confirmed that validation for positive values is already implemented in the updatePatientJobTotal function itself:

// From packages/api/src/command/job/patient/update-total.ts
if (total <= 0) {
  throw new BadRequestError("Total must be greater than 0", {
    cxId,
    jobId,
    total,
  });
}

So the additional check in the route would indeed be redundant. Thanks for pointing this out!


🏁 Script executed:

#!/bin/bash
# Find and display the implementation of updatePatientJobTotal
rg -A 20 "function updatePatientJobTotal" --context 30 -g "*.ts"

Length of output: 2846


🏁 Script executed:

#!/bin/bash
# Look at the specific file mentioned in the import
cat packages/api/src/command/job/patient/update-total.ts

Length of output: 1195


@thomasyopes You're absolutely right. I can see that the validation is already implemented in the command function:

// In updatePatientJobTotal
if (total < 1) throw new BadRequestError("Total must be greater than 0");

So adding the same check in the router would indeed be redundant. Thanks for the clarification!

Comment on lines 30 to 36
const requestId = uuidv7();
for (const patientMapping of patientMappings) {
if (patientMapping.source === EhrSources.canvas) {
const canvasPatientId = patientMapping.externalId;
await createCanvasResourceDiffBundlesJob({ cxId, canvasPatientId, direction, requestId });
}
}
Copy link
@coderabbitai coderabbitai bot Apr 23, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify exception handling for empty patient mappings.

The code retrieves patient mappings and iterates through them, but there's no specific error handling if patientMappings is empty (after fixing the condition above). Consider adding appropriate error handling or logging.


🏁 Script executed:

#!/bin/bash
# Verify if there are any calls to this function elsewhere that might expect an error or specific behavior for empty mappings
rg -A 3 -B 3 "createResourceDiffBundles" --type ts

Length of output: 6718


🏁 Script executed:

#!/bin/bash
# Display the content of create-resource-diff-bundles.ts with line numbers
nl -ba packages/api/src/external/ehr/create-resource-diff-bundles.ts | sed -n '1,100p'

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Display the first 200 lines of the file to inspect patientMappings handling
sed -n '1,200p' packages/api/src/external/ehr/create-resource-diff-bundles.ts

Length of output: 2381


Fix empty patientMappings handling
The current guard if (patientMappings.length < 0) return; will never fire (array length is never negative). You should bail or log when no mappings are returned:

• File: packages/api/src/external/ehr/create-resource-diff-bundles.ts
• Lines around 30–36

Suggested diff:

-  if (patientMappings.length < 0) return;
+  if (patientMappings.length === 0) {
+    // No integrations found for this patient – log or handle as needed
+    // e.g. logger.warn(`No patient mappings for cxId=${cxId}, patientId=${patientId}`);
+    return;
+  }
📝 Committable suggestion

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

Suggested change
const requestId = uuidv7();
for (const patientMapping of patientMappings) {
if (patientMapping.source === EhrSources.canvas) {
const canvasPatientId = patientMapping.externalId;
await createCanvasResourceDiffBundlesJob({ cxId, canvasPatientId, direction, requestId });
}
}
const requestId = uuidv7();
- if (patientMappings.length < 0) return;
+ if (patientMappings.length === 0) {
+ // No integrations found for this patient – log or handle as needed
+ // e.g. logger.warn(`No patient mappings for cxId=${cxId}, patientId=${patientId}`);
+ return;
+ }
for (const patientMapping of patientMappings) {
if (patientMapping.source === EhrSources.canvas) {
const canvasPatientId = patientMapping.externalId;
await createCanvasResourceDiffBundlesJob({ cxId, canvasPatientId, direction, requestId });
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link

Choose a reason for hiding this comment

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

@thomasyopes Great! Thanks for implementing the fix for the patientMappings length check. Let me know if you need any further assistance with this PR.

Comment on lines 23 to 25
export function isValidJobStatus(status: string): status is JobStatus {
return status === "waiting" || status === "processing" || status === "completed";
}
Copy link
@coderabbitai coderabbitai bot Apr 23, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add missing job status check for 'failed'.

The isValidJobStatus function checks for 'waiting', 'processing', and 'completed', but doesn't check for 'failed'. This may be an oversight if 'failed' is a valid job status, as it is for job entry status.

export function isValidJobStatus(status: string): status is JobStatus {
-  return status === "waiting" || status === "processing" || status === "completed";
+  return status === "waiting" || status === "processing" || status === "completed" || status === "failed";
}

Let's verify this by looking at the JobStatus definition:


🏁 Script executed:

#!/bin/bash
# Check the definition of JobStatus to see all valid values
rg "const jobStatus.*=|export type JobStatus" --type ts

Length of output: 405


Add missing ‘failed’ case to isValidJobStatus

The JobStatus type (defined in packages/shared/src/domain/job/job-status.ts) includes "failed", but isValidJobStatus only checks for "waiting", "processing", and "completed". To keep the runtime guard in sync with the type, please update it as follows:

• File: packages/shared/src/domain/job/types.ts
• Lines: 23–25

export function isValidJobStatus(status: string): status is JobStatus {
-  return status === "waiting" || status === "processing" || status === "completed";
+  return (
+    status === "waiting" ||
+    status === "processing" ||
+    status === "completed" ||
+    status === "failed"
+  );
}
📝 Committable suggestion

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

Suggested change
export function isValidJobStatus(status: string): status is JobStatus {
return status === "waiting" || status === "processing" || status === "completed";
}
export function isValidJobStatus(status: string): status is JobStatus {
return (
status === "waiting" ||
status === "processing" ||
status === "completed" ||
status === "failed"
);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link

Choose a reason for hiding this comment

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

@thomasyopes Great! Thank you for implementing the fix. The isValidJobStatus function will now correctly validate all possible job statuses.

Thomas Yopes and others added 5 commits April 23, 2025 16:15
Ref: ENG-47

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-47

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-47

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
Ref: ENG-47

Ref: #1040
Signed-off-by: Thomas Yopes <thomasyopes@Thomass-MBP.attlocal.net>
@thomasyopes thomasyopes added this pull request to the merge queue Apr 24, 2025
Merged via the queue into master with commit 5be881b Apr 24, 2025
70 checks passed
This was referenced Jun 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0