Skip to content

Conversation

RamilGaripov
Copy link
Contributor

@RamilGaripov RamilGaripov commented Apr 15, 2025

Ticket: #metriport/metriport-internal#2887

Description

  • Introducing the HL7 to FHIR Converter lambda logic for "create" messages
  • Introducing a stub for the internal API route to send the WH with the presigned URL to the FHIR Bundle file on S3

Testing

  • Local
    • Test the logic using a script to convert an ADT and have it be uploaded to S3
  • Staging
    • Test the logic using a script to convert an ADT and have it be uploaded to S3 staging bucket
  • Production
    • Test the logic using a script to convert an ADT and have it be uploaded to S3 production bucket

Release Plan

  • Merge this

Summary by CodeRabbit

  • New Features

    • Introduced conversion of HL7v2 messages to FHIR bundles, with support for uploading results to S3 and triggering webhook notifications.
    • Added a new internal API endpoint to receive HL7 FHIR bundle notifications via webhook.
    • Implemented validation for HL7 notification requests, including presigned S3 URL checks.
    • Enhanced local and utility scripts for batch processing and conversion of HL7v2 messages.
    • Added new functions to generate file keys for FHIR bundles.
    • Added a stub webhook processor for handling incoming HL7 FHIR bundle notifications.
  • Bug Fixes

    • Improved handling of optional and missing name fields when processing practitioner data, ensuring more accurate FHIR output.
  • Refactor

    • Streamlined parameter typing and code structure for S3 upload utilities to improve maintainability.
  • Chores

    • Removed an unused dependency from the utils package for improved package management.

…only. also added api route stub

refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
Copy link

coderabbitai bot commented Apr 15, 2025

Walkthrough

This update introduces new functionality for converting HL7v2 messages to FHIR bundles, uploading them to AWS S3, and notifying internal APIs via webhooks. It adds a conversion module, validation schemas, and a webhook endpoint for processing presigned S3 URLs. Supporting utilities and type definitions are included for S3 operations and HL7 message handling. The update also includes scripts for batch processing HL7 files and error handling improvements. Modifications were made to ensure proper type handling and conditional property inclusion in FHIR resource generation. Additionally, configuration accessors and package dependency adjustments were made.

Changes

File(s) Change Summary
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts New module for converting HL7v2 messages to FHIR bundles, uploading to S3, and notifying internal APIs; exports main async function and input type.
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts Added functions for generating FHIR bundle S3 file keys.
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts Modified buildHumanName to conditionally include given property only if non-empty.
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts Updated HumanNameDetails.given type to allow undefined; improved name extraction logic and stricter return conditions.
packages/core/src/external/aws/s3.ts Introduced StoreInS3Params type and refactored storeInS3WithRetries parameter typing.
packages/api/src/command/medical/patient/hl7-fhir-webhook.ts Added async stub function processHl7FhirBundleWebhook for processing FHIR bundle webhooks.
packages/api/src/routes/internal/medical/patient.ts Added POST /internal/patient/:id/notification endpoint for HL7 FHIR bundle webhook; integrates validation and command handler.
packages/api/src/routes/medical/schemas/hl7-notification.ts New module defining presignedUrlSchema and hl7NotificationSchema for validating webhook requests.
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts New script for batch processing HL7v2 messages from file, converting to FHIR, uploading to S3, and triggering webhooks; includes error collection.
packages/utils/src/hl7v2-notifications/convert-adt-to-fhir-local-example.ts Updated script to process multiple HL7 files in a folder, saving each converted FHIR bundle and errors with unique filenames.
packages/utils/package.json Removed @medplum/core from dependencies.
packages/core/src/util/config.ts Added static method to retrieve OUTGOING_HL7_NOTIFICATION_BUCKET_NAME environment variable.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HL7ToFhirConverter
    participant S3
    participant InternalAPI
    participant WebhookEndpoint

    Client->>HL7ToFhirConverter: Provide HL7v2 message, context, patient info
    HL7ToFhirConverter->>HL7ToFhirConverter: Parse & convert HL7 to FHIR bundle
    HL7ToFhirConverter->>S3: Upload FHIR bundle
    HL7ToFhirConverter->>S3: Upload combined FHIR bundle (with deduplication)
    HL7ToFhirConverter->>S3: Generate presigned URL for new bundle
    HL7ToFhirConverter->>InternalAPI: POST presigned URL, cxId, patientId, triggerEvent
    InternalAPI->>WebhookEndpoint: (via route) process presigned URL notification
    WebhookEndpoint->>WebhookEndpoint: Validate and process notification
Loading
sequenceDiagram
    participant ClientScript
    participant FileSystem
    participant HL7ToFhirConverter
    participant S3
    participant InternalAPI

    ClientScript->>FileSystem: Read HL7 files from folder
    loop For each HL7 message
        ClientScript->>HL7ToFhirConverter: Convert HL7 to FHIR and upload
        HL7ToFhirConverter->>S3: Upload FHIR bundle
        HL7ToFhirConverter->>InternalAPI: Notify via presigned URL
    end
    ClientScript->>FileSystem: Write errors to error folder (if any)
Loading

📜 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 9248e50 and 8b09b7e.

📒 Files selected for processing (3)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (1 hunks)
  • packages/core/src/util/config.ts (1 hunks)
  • packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/util/config.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts

🪧 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: 15

🧹 Nitpick comments (22)
packages/core/src/external/fhir/shared/datetime.ts (1)

4-14: Function looks good, with minor simplification opportunities.

This utility function follows functional programming principles and handles edge cases appropriately.

You could simplify the parameter types by removing the redundant | undefined since optional parameters already include undefined:

export function buildPeriod(
-  start?: string | undefined,
-  end?: string | undefined
+  start?: string,
+  end?: string
): Period | undefined {
  // ...
}
packages/shared/src/common/__tests__/date.test.ts (1)

50-80: Comprehensive test suite for the new date formatting function.

The tests cover various scenarios including compact dates with/without offsets, standard ISO format, and fallback behavior.

There's a small typo in the test description:

- it("correctly handless offset", () => {
+ it("correctly handles offset", () => {
packages/mllp-server/src/app.ts (1)

21-22: Remove unused constants.

These constants are no longer used after refactoring to use getHl7MessageIdentifierOrFail. Their presence could cause confusion for future developers.

-const IDENTIFIER_FIELD = 3;
-const IDENTIFIER_COMPONENT = 1;
packages/api/src/routes/internal/hl7/index.ts (1)

39-40: Add logging for webhook processing.

Adding more detailed logging will help with debugging and monitoring.

   await processHl7FhirBundleWebhook({ cxId, patientId, presignedUrl: validatedUrl });
+  console.log(`Successfully processed HL7 FHIR bundle webhook for patient ${patientId}, customer ${cxId}`);
   return res.sendStatus(httpStatus.OK);
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (3)

9-10: Define constants for all field indices.

Following the pattern for MSH_9_MESSAGE_TYPE, add constants for all field indices used in this module for better readability and maintenance.

 const MSH_9_MESSAGE_TYPE = 9;
+const MSH_7_MESSAGE_DATETIME = 7;
+
+// Component indices
+const COMPONENT_1 = 1;
+const COMPONENT_2 = 2;
+const COMPONENT_3 = 3;

16-33: Improve error messages with context and add comments for fallback logic.

The current implementation is solid, but could benefit from more descriptive error messages and explanation of the fallback logic for trigger event extraction.

 export function getHl7MessageIdentifierOrFail(hl7Message: Hl7Message): Hl7MessageIdentifier {
   const mshSegment = getSegmentByNameOrFail(hl7Message, "MSH");
-  const messageType = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, 1);
-  const triggerEvent = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, 2);
-  const messageStructure = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, 3);
+  const messageType = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, COMPONENT_1);
+  const triggerEvent = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, COMPONENT_2);
+  const messageStructure = getOptionalValueFromSegment(mshSegment, MSH_9_MESSAGE_TYPE, COMPONENT_3);
 
   if (!messageType) {
-    throw new MetriportError("Message type not found in MSH segment");
+    throw new MetriportError("Message type not found in MSH segment", undefined, {
+      segment: "MSH",
+      field: MSH_9_MESSAGE_TYPE,
+      component: COMPONENT_1
+    });
   }
 
+  // If triggerEvent is missing, try to derive it from messageStructure (e.g., "ADT_A01" -> "A01")
   const derivedTriggerEvent = triggerEvent ?? messageStructure?.split("_")[1];
 
   if (!derivedTriggerEvent) {
-    throw new MetriportError("Trigger event not found in MSH segment");
+    throw new MetriportError("Trigger event not found in MSH segment", undefined, {
+      segment: "MSH",
+      field: MSH_9_MESSAGE_TYPE,
+      messageType
+    });
   }
 
   return { messageType, triggerEvent: derivedTriggerEvent };
 }

35-37: Update getMessageDatetime to use constants.

For consistency, use the newly defined constants in this function as well.

 export function getMessageDatetime(msg: Hl7Message): string | undefined {
-  return getOptionalValueFromMessage(msg, "MSH", 7, 1);
+  return getOptionalValueFromMessage(msg, "MSH", MSH_7_MESSAGE_DATETIME, COMPONENT_1);
 }
packages/utils/src/hl7v2-notifications/convert-adt-to-fhir-local-example.ts (1)

49-51: Simplify timestamp formatting.

The timestamp formatting code is overly complex and hard to read. Using a library function would be more maintainable.

-    console.log(
-      new Date().toISOString().split(".")[0].replace(/-/g, "").replace("T", "").replace(/:/g, "")
-    );
+    // Format: YYYYMMDDHHmmss
+    const now = new Date();
+    const formattedTime = now.toISOString().replace(/[-:T.Z]/g, "").substring(0, 14);
+    console.log(formattedTime);
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/location.ts (1)

11-24: Improve error handling for location data extraction.

The function returns undefined if no facility name is found, but doesn't provide any information about why. Consider returning a more informative result.

 export function getLocationFromAdt(
   adt: Hl7Message
-): { location: Location; locationReference: EncounterLocation } | undefined {
+): { location: Location; locationReference: EncounterLocation; } | { error: string } | undefined {
   const name = getFacilityName(adt);
   // TODO 2883: Parse the address, if possible

-  if (!name) return undefined;
+  if (!name) {
+    return { error: "No facility name found in ADT message" };
+  }
   const location = buildLocation({ name });

   return {
     location,
     locationReference: buildLocationReference({ resource: location }),
   };
 }
packages/core/src/external/fhir/shared/references.ts (1)

107-114: Use consistent error handling for invalid parameters.

The function assumes the input is valid and doesn't handle the case where neither property is provided or both are provided. This is partly covered by the XOR type, but runtime validation would be more robust.

 export function buildEncounterParticipant(input: EncounterParticipantInput): EncounterParticipant {
+  if (!input || (typeof input !== 'object')) {
+    throw new Error('EncounterParticipant input must be a valid object');
+  }
+  
   if ("practitionerId" in input) {
     return { individual: { reference: `Practitioner/${input.practitionerId}` } };
   }

+  if (!("resource" in input) || !input.resource) {
+    throw new Error('EncounterParticipant input must contain either practitionerId or resource');
+  }
+
   return { individual: { reference: buildEntryReference(input.resource) } };
 }
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts (2)

12-17: Add optional validation or doc comments for parameters.
The structure of Hl7ToFhirParams looks good. Consider adding optional doc comments or type validations (e.g., ensuring timestampString is a valid date/time string) to enhance clarity and type safety.


67-73: Preserve existing extensions instead of overwriting them.
Currently, any previous extension array on entries in the bundle might be replaced. If an entry already has extensions, those extensions would be lost.

Apply this diff to append the new extension instead of overwriting:

-function addHl7SourceExtension(
  bundle: BundleWithEntry<Resource>,
  sourcePath: string
): Bundle<Resource> {
  const ext = buildDocIdFhirExtension(sourcePath);
  return { ...bundle, entry: bundle.entry.map(e => ({ ...e, extension: [ext] })) };
}
+function addHl7SourceExtension(
+  bundle: BundleWithEntry<Resource>,
+  sourcePath: string
+): Bundle<Resource> {
+  const ext = buildDocIdFhirExtension(sourcePath);
+  return { 
+    ...bundle, 
+    entry: bundle.entry.map(e => ({
+      ...e,
+      extension: e.extension ? [...e.extension, ext] : [ext],
+    })),
+  };
+}
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/encounter.ts (1)

47-65: Extend coverage for additional ADT trigger events.
getPatientStatus only covers A01 (in-progress) and A03 (finished). The TODO comment suggests supporting more events. Expanding this mapping or inferring from the ADT message directly can reduce reliance on defaults.

Would you like suggestions on additional trigger event mappings or a reminder to open an issue for it?

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (3)

22-24: Consider centralizing event type references.

Currently, the createMessageTypes and updateMessageTypes arrays are defined at lines 23–24. Since more event types will be added, consider centralizing these into a configuration or a constant file to facilitate easy maintenance.

Would you like me to open a follow-up ticket or propose a shared config approach?


133-138: Replace console.log with out().log for consistency.

At line 136, there's a console.log statement. To align with logging guidelines, replace it with out().log(...).

-    console.log("Need to retrieve and update existing entries");
+    out().log("Need to retrieve and update existing entries");

144-150: Validate special characters in document name.

buildDocumentNameForHl7v2Roster concatenates string components (lines 148–149). If hieName or elements in subscriptions contain unexpected or special characters, it might cause S3 path issues. Consider sanitizing these fields or encoding them if needed.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/condition.ts (2)

45-62: Optional system inference improvement.

buildConditionCoding (lines 45–62) calls inferConditionSystem, which always returns undefined. If you plan to support ICD-10, SNOMED, etc., consider implementing logic within inferConditionSystem.


76-81: Implement planned system detection.

inferConditionSystem is left unimplemented (line 79). Consider mapping known code prefixes to recognized systems (ICD-10, LOINC, SNOMED) if feasible.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts (1)

16-32: Support multiple practitioner types.

Right now, getParticipantsFromAdt focuses on the attending doctor only. If your HL7 feed can include additional practitioners (consulting, referring, etc.), consider expanding your logic to collect them.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (1)

20-20: Consider adding a descriptive comment for the crypto instance.

The Base64Scrambler instance is initialized but lacks context on why it's needed and how it fits into the overall flow.

-const crypto = new Base64Scrambler(Config.getHl7Base64ScramblerSeed());
+// Initialize scrambler with a seed for consistent UUID obfuscation across application restarts
+const crypto = new Base64Scrambler(Config.getHl7Base64ScramblerSeed());
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (2)

16-17: Consider using an enum for ConditionOffset.

Using a type for the limited set of condition offsets (0 or 1) is good, but an enum would provide more clarity and type safety.

-type ConditionOffset = 0 | 1;
+enum ConditionOffset {
+  Primary = 0,
+  Secondary = 1,
+}

44-60: Consider refactoring to reduce nested conditionals.

The function has multiple fallback options which could be simplified using a chain of operations or a more declarative approach.

-export function getFacilityName(adt: Hl7Message): string | undefined {
-  const evn = getSegmentByNameOrFail(adt, "EVN");
-  const eventFacilityNamespace = getOptionalValueFromSegment(evn, 7, 1);
-  const eventFacilityUniversalId = getOptionalValueFromSegment(evn, 7, 2);
-  if (eventFacilityNamespace || eventFacilityUniversalId) {
-    return [eventFacilityNamespace, eventFacilityUniversalId].filter(Boolean).join(" - ");
-  }
-
-  const pv1Segment = adt.getSegment("PV1");
-  if (!pv1Segment) return undefined;
-
-  const servicingFacilityName = getOptionalValueFromSegment(pv1Segment, 39, 1);
-  if (servicingFacilityName) return servicingFacilityName;
-
-  const assignedPatientLocationFacility = getOptionalValueFromSegment(pv1Segment, 3, 4);
-  return assignedPatientLocationFacility ?? undefined;
+export function getFacilityName(adt: Hl7Message): string | undefined {
+  // Try to get facility from EVN segment first
+  try {
+    const evn = getSegmentByNameOrFail(adt, "EVN");
+    const eventFacilityNamespace = getOptionalValueFromSegment(evn, 7, 1);
+    const eventFacilityUniversalId = getOptionalValueFromSegment(evn, 7, 2);
+    if (eventFacilityNamespace || eventFacilityUniversalId) {
+      return [eventFacilityNamespace, eventFacilityUniversalId].filter(Boolean).join(" - ");
+    }
+  } catch (error) {
+    // EVN segment not found, continue to PV1 fallbacks
+  }
+
+  // Fall back to PV1 segment
+  const pv1Segment = adt.getSegment("PV1");
+  if (!pv1Segment) return undefined;
+
+  // Try servicingFacilityName first, then assigned location
+  return (
+    getOptionalValueFromSegment(pv1Segment, 39, 1) || 
+    getOptionalValueFromSegment(pv1Segment, 3, 4)
+  );
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8dce641 and b63b1ab.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • packages/api/src/command/medical/patient/hl7-fhir-webhook.ts (1 hunks)
  • packages/api/src/routes/internal/hl7/index.ts (1 hunks)
  • packages/api/src/routes/internal/hl7/schemas.ts (1 hunks)
  • packages/api/src/routes/internal/index.ts (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/condition.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/encounter.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/location.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/mappings.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (1 hunks)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (1 hunks)
  • packages/core/src/external/fhir/shared/bundle.ts (2 hunks)
  • packages/core/src/external/fhir/shared/datetime.ts (1 hunks)
  • packages/core/src/external/fhir/shared/references.ts (2 hunks)
  • packages/mllp-server/src/app.ts (3 hunks)
  • packages/shared/src/common/__tests__/date.test.ts (2 hunks)
  • packages/shared/src/common/date.ts (1 hunks)
  • packages/utils/package.json (2 hunks)
  • packages/utils/src/hl7v2-notifications/convert-adt-to-fhir-local-example.ts (1 hunks)
  • packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.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/index.ts
  • packages/shared/src/common/__tests__/date.test.ts
  • packages/api/src/command/medical/patient/hl7-fhir-webhook.ts
  • packages/utils/src/hl7v2-notifications/convert-adt-to-fhir-local-example.ts
  • packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts
  • packages/api/src/routes/internal/hl7/index.ts
  • packages/api/src/routes/internal/hl7/schemas.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts
  • packages/shared/src/common/date.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/encounter.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/mappings.ts
  • packages/mllp-server/src/app.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts
  • packages/core/src/external/fhir/shared/datetime.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts
  • packages/core/src/external/fhir/shared/references.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts
  • packages/core/src/external/fhir/shared/bundle.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/location.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/condition.ts
🧬 Code Graph Analysis (12)
packages/shared/src/common/__tests__/date.test.ts (1)
packages/shared/src/common/date.ts (1)
  • buildDayjsFromCompactDate (87-97)
packages/api/src/command/medical/patient/hl7-fhir-webhook.ts (1)
packages/core/src/util/log.ts (2)
  • out (29-34)
  • log (9-22)
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts (4)
packages/api/src/shared/config.ts (1)
  • getEnvVarOrFail (14-14)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1)
  • getMessageDatetime (35-37)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (1)
  • getPatientIdsOrFail (123-127)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (1)
  • convertHl7MessageToFhirAndUpload (43-142)
packages/api/src/routes/internal/hl7/index.ts (4)
packages/api/src/routes/helpers/request-logger.ts (1)
  • requestLogger (10-63)
packages/api/src/routes/util.ts (1)
  • asyncHandler (10-28)
packages/api/src/routes/internal/hl7/schemas.ts (1)
  • hl7WebhookParamsSchema (25-29)
packages/api/src/command/medical/patient/hl7-fhir-webhook.ts (1)
  • processHl7FhirBundleWebhook (3-13)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (2)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (3)
  • getSegmentByNameOrFail (89-102)
  • getOptionalValueFromSegment (104-110)
  • getOptionalValueFromMessage (77-87)
packages/shared/src/index.ts (1)
  • MetriportError (37-37)
packages/mllp-server/src/app.ts (1)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1)
  • getHl7MessageIdentifierOrFail (16-33)
packages/core/src/external/fhir/shared/datetime.ts (1)
packages/shared/src/common/date.ts (1)
  • buildDayjsFromCompactDate (87-97)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts (2)
packages/core/src/external/fhir/shared/references.ts (1)
  • buildEncounterParticipant (108-114)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (1)
  • getAttendingDoctorNameDetails (62-76)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (3)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (6)
  • getOptionalValueFromSegment (104-110)
  • getOptionalValueFromMessage (77-87)
  • getSegmentByNameOrFail (89-102)
  • getOptionalValueFromField (112-117)
  • mapHl7SystemNameToSystemUrl (123-140)
  • unpackPidFieldOrFail (40-51)
packages/core/src/external/fhir/shared/datetime.ts (1)
  • buildPeriod (4-14)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/condition.ts (1)
  • buildConditionCoding (45-62)
packages/core/src/external/fhir/shared/bundle.ts (1)
packages/api/src/routes/medical/schemas/fhir.ts (2)
  • Bundle (23-23)
  • BundleEntry (22-22)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (7)
packages/core/src/util/base64-scrambler.ts (1)
  • Base64Scrambler (7-87)
packages/core/src/util/pack-uuid.ts (2)
  • unpackUuid (19-39)
  • packUuid (8-14)
packages/shared/src/index.ts (1)
  • MetriportError (37-37)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (1)
  • getPatientIdsOrFail (123-127)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1)
  • getMessageDatetime (35-37)
packages/core/src/util/log.ts (2)
  • out (29-34)
  • log (9-22)
packages/core/src/util/mime.ts (1)
  • JSON_FILE_EXTENSION (19-19)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts (7)
packages/core/src/util/log.ts (2)
  • out (29-34)
  • log (9-22)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1)
  • getHl7MessageIdentifierOrFail (16-33)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (1)
  • buildHl7MessageFileKey (142-150)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/encounter.ts (1)
  • mapEncounterAndRelatedResources (11-45)
packages/core/src/external/fhir/shared/bundle.ts (2)
  • buildBundleFromResources (161-174)
  • BundleWithEntry (54-56)
packages/shared/src/common/date.ts (1)
  • elapsedTimeFromNow (60-65)
packages/shared/src/index.ts (1)
  • MetriportError (37-37)
🪛 GitHub Actions: PR Check - Core
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts

[error] 6-6: TypeScript error TS2307: Cannot find module '@***/core/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/pid' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - Utils
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts

[error] 6-6: TypeScript error TS2307: Cannot find module '@***/core/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/pid' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - API
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts

[error] 6-6: TypeScript error TS2307: Cannot find module '@***/core/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/pid' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - MLLP Server
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts

[error] 6-6: TypeScript error TS2307: Cannot find module '@***/core/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/pid' or its corresponding type declarations.

🪛 GitHub Actions: PR Check - Shared
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts

[error] 6-6: TypeScript error TS2307: Cannot find module '@***/core/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/pid' or its corresponding type declarations.

🔇 Additional comments (39)
packages/shared/src/common/date.ts (2)

71-85: Well-implemented date parsing utility!

The tryParseCompactDate function effectively parses HL7's compact date format using regex pattern matching. The implementation is clean and follows good functional programming practices.


87-97: Good integration with existing date handling system.

The buildDayjsFromCompactDate function maintains the same signature as the existing buildDayjs function, making it a drop-in enhancement. It correctly attempts to parse the compact date format first before falling back to the standard parser, providing backward compatibility.

packages/api/src/routes/internal/hl7/schemas.ts (2)

7-20: Good validation for S3 presigned URLs.

The schema properly validates that URLs are from S3 endpoints while providing appropriate error messages. The error handling in the refinement is a good practice.


25-29:

❓ Verification inconclusive

Verify webhook parameters are sufficient.

The schema correctly validates UUIDs for patient and connection identifiers along with the presigned URL. However, consider if additional parameters might be needed for future extensibility, such as message type or source information.


🏁 Script executed:

#!/bin/bash
# Search for usages of this schema to verify if these parameters are sufficient
fd hl7WebhookParamsSchema --type file --exec grep -A 10 -B 10 "hl7WebhookParamsSchema" {} \;

Length of output: 95


Below is a new shell script to re-run the search (using rg for a broader scan) so that we can gain a clearer picture of where and how hl7WebhookParamsSchema is used. Once you run it, please verify if the schema is indeed only used as-is—or if there are any contexts suggesting additional parameters might be needed.

Based on the output of this search, you can confirm whether the current fields (i.e., validating UUIDs for patient and connection IDs and the presigned URL) fully meet the requirements. If the search still returns no usage context, please perform a manual verification of how this schema is utilized across endpoints to decide if future extensibility (e.g., including message type or source information) is needed.


🏁 Script executed:

#!/bin/bash
# Use ripgrep to search the entire repository for 'hl7WebhookParamsSchema'
rg -i "hl7WebhookParamsSchema" .

Length of output: 475


Action: Assess HL7 Schema Parameter Usage & Consider Future Extensibility

The current schema in packages/api/src/routes/internal/hl7/schemas.ts correctly validates patientId and cxId as UUIDs and the presignedUrl using presignedUrlSchema. However, the usage seen in packages/api/src/routes/internal/hl7/index.ts only destructures the presignedUrl. Please verify that the inputs for patientId and cxId are indeed required for downstream processing. Additionally, consider whether future scenarios might benefit from extra parameters (e.g., message type or source information) or if it would be clearer to remove the unused fields for now.

packages/core/src/external/fhir/shared/bundle.ts (2)

54-56: Good type extension for bundle with entries.

This type alias ensures that bundles created with this type will always have an entry array, which improves type safety when working with FHIR bundles.


161-174: Concise bundle creation utility.

The buildBundleFromResources function follows good functional programming principles by creating a new bundle without modifying inputs. It leverages existing buildBundleEntry function for consistency and maintains the pattern of other bundle creation utilities in this file.

packages/core/package.json (1)

151-151:

❓ Verification inconclusive

Verify compatibility with existing Medplum dependencies.

Adding @medplum/core as a dev dependency is appropriate for the HL7-to-FHIR conversion functionality. However, ensure this version is compatible with the existing @medplum/fhirtypes package (v2.2.10) as there might be version mismatches between these related packages.


🌐 Web query:

Are @medplum/core v3.2.33 and @medplum/fhirtypes v2.2.10 compatible versions?

💡 Result:

Based on the versions provided—@medplum/core v3.2.33 and @medplum/fhirtypes v2.2.10—compatibility between these two libraries cannot be definitively confirmed with the available information. There are no explicit compatibility matrices or documentation found in the search results that directly address whether these specific versions work seamlessly together.

However, Medplum, as a platform, emphasizes backwards compatibility and type safety in their libraries, particularly those supporting FHIR standards. Additionally:

  1. Semantic Versioning: Both libraries use semantic versioning. The @medplum/core library is at version 3.x, and @medplum/fhirtypes is at version 2.x. Given their minor patch updates (last digits), it is likely the updates remain backward-compatible. This suggests a low likelihood of breaking changes unless explicitly stated.

  2. Documentation Guidance: Medplum typically provides type definitions and developer-friendly tooling in @medplum/fhirtypes to interface with FHIR resources. The @medplum/core library is designed to integrate with these type definitions as part of the Medplum ecosystem.

To verify compatibility, refer to Medplum’s version policy or test your application with these versions to ensure that they function together as expected.

Citations:


Action: Confirm Medplum Package Compatibility for HL7-to-FHIR Conversion

The dependency on @medplum/core v3.2.33 appears well-suited for the HL7-to-FHIR conversion functionality. However, compatibility with the installed @medplum/fhirtypes v2.2.10 could not be definitively confirmed based on public information. Medplum’s commitment to semantic versioning and backwards compatibility suggests that issues are unlikely, but it's important to:

  • Verify integration by consulting Medplum’s version policy and relevant documentation.
  • Perform local integration tests to ensure there are no unexpected issues between these versions.
packages/api/src/routes/internal/index.ts (1)

45-45: LGTM: Clean integration of HL7 routes.

The import and route registration follow the established patterns in the codebase.

Also applies to: 69-69

packages/core/src/external/fhir/shared/datetime.ts (2)

8-8: Good early return for edge case.

Early returning undefined when both parameters are missing is a clean approach.


10-13: Clean conditional property construction.

Using spread operators with conditional objects is a nice functional approach to conditionally include properties.

packages/shared/src/common/__tests__/date.test.ts (1)

75-79: Verify expected behavior for invalid date formats.

This test suggests that invalid compact date formats like "20240226123000" (missing timezone) are still successfully parsed. Is this the intended behavior? If so, the test is correct, but it's worth confirming this fallback behavior aligns with your expectations.

packages/mllp-server/src/app.ts (3)

5-6: New import for HL7 message identifier extraction looks good.

The addition of getHl7MessageIdentifierOrFail properly centralizes the HL7 message type and trigger event extraction logic.


46-52: Good centralization of HL7 message identifier extraction.

Using the getHl7MessageIdentifierOrFail function improves code maintainability by centralizing extraction logic and error handling. The Sentry context setting maintains the same behavior while leveraging the new utility.


65-66: Consistent usage of extracted message identifiers.

The S3 key building correctly uses the values from the extracted message identifier, maintaining consistency with the Sentry context setting.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts (1)

22-47: Solid core logic with appropriate error handling.
The convertHl7v2MessageToFhir function effectively handles ADT message conversions and logs relevant data. Error handling for unsupported message types is cleanly implemented.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/mappings.ts (2)

1-6: Type guard and constants look appropriate.
The type guard isAdtPatientClass accurately checks membership in the adtPatientClass tuple. This provides a clean and type-safe approach.


24-34: Mapping logic is well-structured.
The adtToFhirEncounterClassMap covers essential ADT codes with a fallback to DEFAULT_ENCOUNTER_CLASS. The code and display values appear correct.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/encounter.ts (2)

11-45: Effective creation of Encounter and related resources.
mapEncounterAndRelatedResources neatly composes an Encounter resource along with relevant Condition, Practitioner, and Location resources. The optional chaining for each resource is clean and avoids unnecessary code repetition.


67-85: Mapping HL7 patient class code to FHIR is appropriate.
getEncounterClass properly uses the fallback class if the HL7 patient class is missing or invalid. The code is succinct and follows best practices for returning typed Coding.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (3)

58-63: Validate HL7 parsing logic.

The logic for parsing HL7 messages here looks straightforward. However, ensure that convertHl7v2MessageToFhir handles all ADT segments properly, including unexpected or missing segments. You might consider adding negative tests or verifying partial ADT messages.


65-72: Confirm downstream success for patient retrieval.

You're correctly using executeWithNetworkRetries and structured error handling in the request to internal patient data. Confirm that the toFhirPatient function successfully maps all essential fields for your FHIR Bundle, especially for edge cases (e.g., minimal or inconsistent data).


75-131: Good use of retries and structured logging.

The block for handling “create” events is solid. The usage of storeInS3WithRetries and a nested executeWithNetworkRetries for calling the internal endpoint ensures robust error handling. This is a good pattern to follow for other ADT trigger events.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/condition.ts (2)

24-43: Check for partial or missing Condition codes.

getAdmitReason gracefully returns undefined if no codings are found. Confirm that downstream logic adequately handles this scenario. If certain ADT messages never contain a PV2 segment, you might need fallback logic to avoid missing resources.


64-74: Descriptive resource composition.

buildCondition is a clear pattern for generating minimal FHIR Condition resources. Great job leveraging a stable UUID for the resource id based on the coding object. This ensures deterministic references across the pipeline.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/practitioner.ts (3)

34-47: Mapping HL7 to FHIR names.

getAttendingDoctorDetailsFromAdt extracts and maps HL7 fields to FHIR HumanName (line 42). The approach is concise, but keep an eye on potential data loss if HL7 includes more specific name segments (e.g., multiple prefixes or name types).


49-61: Concise name building.

buildHumanName is well-structured; returning arrays only when needed aligns with FHIR’s approach to supporting multiple name components.


63-72: Deterministic ID creation.

buildPractitioner uses a stable UUID derived from the name property when no explicit id is provided. This helps ensure consistent referencing for repeated ADT references with the same name data.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (9)

1-11: Import organization looks good.

Clean organization of imports with clear separation between external dependencies and internal utilities.


32-38: Good use of UUID compression with scrambling for security.

The UUID compression/decompression functions provide a secure way to shorten UUIDs while maintaining uniqueness.


40-51: LGTM: Robust error handling in PID field unpacking.

The function properly validates PID format and throws descriptive errors when invalid.


62-72: Error handling includes helpful context.

Good practice adding patient IDs and datetime to error context for easier debugging and traceability.


104-117: Functions follow functional programming principles.

These utility functions are small, deterministic, and have clear input/output relationships, following the recommended functional programming style.


123-140: Good warning logging for unmapped system names.

The function properly handles missing system names and logs warnings when mappings fail, which helps with monitoring and debugging.


142-154: Well-structured file key generation for HL7 and FHIR artifacts.

The functions create consistent file paths that maintain the relationship between original HL7 messages and their FHIR bundle counterparts.


22-30:

❓ Verification inconclusive

Consider implementing validation for coding system mappings.

The TODO comment indicates uncertainty about the completeness of the HL7 coding system mappings. This could lead to unexpected behavior if unmapped systems are encountered.


🏁 Script executed:

#!/bin/bash
# Check if there are any unmapped coding systems in the codebase

echo "Searching for potential HL7 coding systems that might need to be added to the mapping..."
rg -i "(?:coding|system)\s*[:=].*(?!SCT|L|I10|ICD-10|I9|ICD-9)[A-Z0-9-]+" --type ts --type js

Length of output: 577


Action Required: Validate and Enforce HL7 Coding System Mappings

It appears that the current mapping in packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts is solely a static record without any active validation. To prevent potential issues when encountering unmapped HL7 coding systems, please consider the following actions:

  • Implement Runtime/Validation Checks: Add a function or testing mechanism that verifies each HL7 coding system used in the codebase is present in the mapping. This could alert you at runtime or during tests if an unmapped system is encountered.
  • Improve Regex-Based Searches: The initial script used to search for unmapped systems resulted in an error due to unsupported look-around syntax. When re-verifying, please ensure to run the search using PCRE2 by adding the --pcre2 flag (e.g., use rg --pcre2 ...) or modify the regex to eliminate look-around constructs.
  • Manual Verification: Given the regex issue encountered, a manual review of HL7 system appearances in the repository may also be worthwhile to ensure the mapping remains comprehensive.

Please address these points to strengthen the robustness of our HL7-to-FHIR conversion logic.


53-75:

✅ Verification successful

Consider handling potential circular dependency.

The function relies on getPatientIdsOrFail which could create a circular dependency if that function (imported from ./adt/utils.ts) also depends on functions from this module.


🏁 Script executed:

#!/bin/bash
# Check for potential circular dependencies

echo "Checking for imports of shared.ts in adt/utils.ts..."
rg -A 10 "import.*from\s+['\"].*shared['\"]" packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts

echo "Checking which functions from shared.ts are used in utils.ts..."
rg "getRequiredValueFromMessage|getOptionalValueFromMessage|getSegmentByNameOrFail" packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts

Length of output: 1040


No Circular Dependency Issue Identified

After verifying the dependency flow:

  • The shared.ts module calls getPatientIdsOrFail from adt/utils.ts.
  • Our search shows that adt/utils.ts does not import or reference shared.ts (despite using similarly named helper functions), so there isn’t a circular dependency occurring.

No modifications are required at this time, but please remain mindful of future refactoring that might inadvertently bridge these modules.

packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/adt/utils.ts (3)

27-38: LGTM: Good use of optional chaining and early returns.

The function properly handles potentially missing segments and returns undefined when appropriate.


78-95: LGTM: Good use of offset for extracting multiple conditions.

The function provides a flexible way to extract different conditions using an offset mechanism.


111-121: Deterministic encounter ID creation with good fallbacks.

The function correctly creates deterministic IDs for encounters when possible, with a UUIDv7 fallback when needed.

@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
@metriport metriport deleted a comment from coderabbitai bot Apr 16, 2025
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
@RamilGaripov
Copy link
Contributor Author

deleted a bunch of comments cuz they were created for an unrelated PR that this branch was created off of

…only. also added api route stub

refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
@RamilGaripov RamilGaripov changed the base branch from develop to 2330-flexible-columns April 16, 2025 00:38
@RamilGaripov RamilGaripov changed the base branch from 2330-flexible-columns to develop April 16, 2025 00:38
refs. metriport/metriport-internal#2887

Signed-off-by: Ramil Garipov <ramil@metriport.com>
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: 3

🧹 Nitpick comments (1)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (1)

135-137: Consider throwing an error for unexpected message types.

Currently, the function just logs a message for unexpected message types and returns. Consider either throwing an error or adding more detailed logging about what to do with these message types.

-  log("Unexpected message type: ", msgIdentifier.triggerEvent);
-  return;
+  const errorMessage = `Unexpected message type: ${msgIdentifier.triggerEvent}`;
+  log(errorMessage);
+  // Either throw an error or return a specific status
+  return;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f8a4093 and 6ea5fec.

📒 Files selected for processing (1)
  • packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.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/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts
🧬 Code Graph Analysis (1)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (7)
packages/core/src/external/aws/s3.ts (2)
  • S3Utils (99-471)
  • storeInS3WithRetries (526-581)
packages/core/src/util/log.ts (2)
  • out (29-34)
  • log (9-22)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/index.ts (1)
  • convertHl7v2MessageToFhir (22-65)
packages/core/src/external/fhir/shared/bundle.ts (2)
  • buildBundleEntry (184-190)
  • buildBundle (151-159)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/msh.ts (1)
  • getHl7MessageIdentifierOrFail (16-33)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts (1)
  • buildHl7MessageFhirBundleFileKey (152-154)
packages/core/src/util/mime.ts (1)
  • JSON_APP_MIME_TYPE (17-17)
🔇 Additional comments (5)
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (5)

41-48: Function implements the HL7v2 to FHIR conversion logic as described in the PR objectives.

The function signature matches the requirements and includes all necessary parameters for the conversion process.


73-82: LGTM! Good conditional handling based on message type.

The code correctly identifies create messages by checking if the trigger event is in the createMessageTypes array. The file key generation looks correct as well.


83-100: LGTM! Good use of existing utilities with proper error configuration.

Using storeInS3WithRetries with comprehensive error configuration is a good practice. The error message and context are well defined.


108-126: LGTM! Good error handling for the API request.

The code properly uses executeWithNetworkRetries for the API request and includes appropriate error handling with logging.


20-22:

❓ Verification inconclusive

TODO comment needs to be addressed before merging.

The TODO comment indicates that additional event types need to be added to both arrays. Consider either implementing this before merging or creating a tracking issue to ensure this doesn't get forgotten.


🏁 Script executed:

#!/bin/bash
# Check if there are other HL7 event types that should be included in these arrays
echo "Searching for HL7 message types and trigger events in the codebase..."
rg -A 3 "getHl7MessageIdentifierOrFail" | grep "triggerEvent"

Length of output: 511


Action Required: Address the Pending TODO for HL7 Event Types

Before merging, please ensure that the pending TODO is resolved—either by extending the arrays with the complete set of HL7 event types needed by the logic or by creating a tracking issue to revisit this enhancement later. Our search confirms that the code checks msgIdentifier.triggerEvent against the createMessageTypes array in the converter, suggesting there may be more event types that warrant handling.

  • Location: packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-converter.ts (lines 20-22)
  • Recommendation: Update the createMessageTypes and updateMessageTypes arrays with any missing HL7 event types or create an issue to track this pending work.

@RamilGaripov RamilGaripov marked this pull request as draft April 16, 2025 21:47
Part of ENG-89

Signed-off-by: Ramil Garipov <ramil@metriport.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts (2)

89-90: Consider making the script more modular.

Directly invoking the function at the bottom of the file makes it difficult to import and use this module programmatically in other contexts.

-invokeLambdaLogic();
+// Only run the function if this file is being executed directly
+if (require.main === module) {
+  invokeLambdaLogic()
+    .then(result => {
+      if (result.errors) {
+        process.exit(1);
+      }
+      process.exit(0);
+    })
+    .catch(err => {
+      console.error("Fatal error:", errorToString(err));
+      process.exit(1);
+    });
+}
+
+// Export the function for use in other modules
+export { invokeLambdaLogic };

58-81: Consider processing messages in sequence rather than in parallel.

The current implementation processes all messages in parallel using forEach, which could lead to race conditions or resource exhaustion if there are many messages. Consider processing them sequentially with a for...of loop or using a batching approach.

-  chunks.forEach(message => {
+  // Process messages sequentially to avoid overwhelming resources
+  for (const message of chunks) {
     const hl7Message = Hl7Message.parse(message);
     const timestamp = getOrCreateMessageDatetime(hl7Message);
     const messageId = getMessageUniqueIdentifier(hl7Message);

     try {
       const { cxId, patientId } = getCxIdAndPatientIdOrFail(hl7Message);
-      convertHl7MessageToFhirAndUpload({
+      await convertHl7MessageToFhirAndUpload({
         cxId,
         patientId,
         message,
         messageId,
         messageReceivedTimestamp: timestamp,
         apiUrl,
         bucketName,
       });
     } catch (err) {
       errors.push({
         err,
         msg: errorToString(err),
         timestamp,
       });
     }
-  });
+  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d50e64 and 6ffd3c0.

📒 Files selected for processing (1)
  • packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.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/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts
🔇 Additional comments (4)
packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts (4)

1-13: Imports look good, but consider reorganizing per guidelines.

The imports are structured appropriately with dotenv configuration at the top, followed by external and internal dependencies. This follows good practices for script initialization.


14-46: Documentation is thorough and clear.

The JSDoc provides excellent context about the purpose, inputs, process, outputs, required environment variables, and usage instructions for this utility script. This level of documentation is valuable for maintainability.


47-49: Environment variables are correctly validated.

Using getEnvVarOrFail ensures the script will fail fast with a clear message if required configuration is missing.


54-56:

✅ Verification successful

Consider more robust HL7 message parsing.

The current splitting logic assumes messages always start with "MSH|" at the beginning of a line. This might miss messages if they don't strictly follow this format or if the file has different formatting.


🏁 Script executed:

#!/bin/bash
# Check if there are any common HL7 message formats that might not be captured by the current regex

# Look for potential message segments that might indicate the start of an HL7 message
rg -i "^[A-Z]{3}\|" ${filePath}/${fileName} || echo "No alternative segment headers found"

# Check if all MSH segments are at the beginning of lines
rg -i "MSH\|" ${filePath}/${fileName} | rg -v "^MSH\|" || echo "All MSH segments are at the beginning of lines"

Length of output: 403


🏁 Script executed:

#!/bin/bash
set -e

# Files to verify
files=(
  "packages/utils/src/hl7v2-notifications/hl7-to-fhir-converter-logic.ts"
  "packages/utils/src/hl7v2-notifications/convert-adt-to-fhir-local-example.ts"
)

for file in "${files[@]}"; do
  echo "=== Checking $file ==="

  # 1. Look for any three-letter segment headers (e.g., PID|, EVN|) that might indicate alternative message starts
  echo "- Searching for any three-letter segment header at line start:"
  rg -n "^[A-Z]{3}\|" "$file" || echo "  → No alternative segment headers found"

  # 2. Ensure all MSH| occurrences are anchored at the start of a line
  echo "- Verifying that all MSH| entries begin at column 1:"
  rg -n "MSH\|" "$file" | rg -n -v "^MSH\|" || echo "  → All MSH| entries are anchored at line start"

  echo
done

Length of output: 1796


Splitting logic is sufficient for current HL7 message patterns

We’ve verified across both hl7-to-fhir-converter-logic.ts and convert-adt-to-fhir-local-example.ts that:

  • No other three‑letter segment headers (e.g. “PID|”, “EVN|”) appear at the start of lines.
  • Every MSH| occurrence is already anchored to the beginning of a line.

Given this, the existing hl7Text.split(/(?=^MSH\|)/m) approach correctly captures each message in our current files. No changes are required here.

Copy link
Contributor

@lucasdellabella lucasdellabella left a comment

Choose a reason for hiding this comment

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

Looks great, other than removing a lot of the try catch stuff to let errors bubble up and get caught by the lambda handler, think we can call it a wrap.

Comment on lines 81 to 83
static getOutgoingHl7NotificationBucketName(): string {
return getEnvVarOrFail("OUTGOING_HL7_NOTIFICATION_BUCKET_NAME");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

when you pull in upstream changes you'll want to swap this out for the other, similarly but differently named getHl7OutgoingMessageBucketName

Copy link
Contributor Author

@RamilGaripov RamilGaripov Apr 24, 2025

Choose a reason for hiding this comment

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

Ya, I was expecting there to be a mashup here haha
Updated!

Part of ENG-89

Signed-off-by: Ramil Garipov <ramil@metriport.com>
Part of ENG-89

Signed-off-by: Ramil Garipov <ramil@metriport.com>
@RamilGaripov RamilGaripov added this pull request to the merge queue Apr 25, 2025
Merged via the queue into develop with commit 3252425 Apr 25, 2025
17 checks passed
@RamilGaripov RamilGaripov deleted the 2887-hl7-to-fhir-lambda-logic branch April 25, 2025 02:40
@coderabbitai coderabbitai bot mentioned this pull request Apr 25, 2025
6 tasks
@coderabbitai coderabbitai bot mentioned this pull request May 28, 2025
1 task
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