Skip to content

Conversation

keshavsaharia
Copy link
Contributor

@keshavsaharia keshavsaharia commented Aug 12, 2025

Issues:

Dependencies

  • Upstream: None
  • Downstream: None

Description

Adds a script in utils that collects all patient ID + transmission IDs for a particular customer and facility, to produce a standardized CSV for accurately converting all Surescripts responses of a particular customer into the pharmacy conversion bucket. Also has been drastically sped up by adding executeAsynchronously with 10 parallel workers.

Also handles a support case from SS where we were informed that N is not a valid gender code, despite the specification saying that it is. This update removes N and maps all non-M/F genders to U (unknown).

Testing

  • Local
    • Converted 22k bundles
  • Staging
    • Run conversion on existing SS transmissions in staging environment
    • Run sending of facility+patient request script from EC2
  • Production
    • Run reconversion on all patient bundles to ensure pharmacy FHIR mapping is completely up to date

Release Plan

After releasing this, we can run reconversion on the CX IDs identified in the ticket ENG-727. That is, we would first generate a CSV with all patient + transmissions, then run the FHIR converter on all response files that were found.

  • Merge this
  • Test reconversion scripts with staging on develop branch, to target existing CXs from SS staging environment
  • Release this
  • Run reconversion on CXs in order of size, and monitor results with preview dashboard

Summary by CodeRabbit

  • New Features

    • CLI to generate a roster CSV of patient and transmission IDs.
    • Replica utilities to list response files and retrieve raw response blobs.
    • MedicationRequests now include identifiers, reason codes, intent "order", and optional pharmacy performer.
    • Payment codes accept single- and double-digit forms with normalized name lookup.
    • New HL7 code system constants and richer Condition fields (clinical/verification status).
  • Performance

    • Parallelized Surescripts conversion and recreation flows (up to 10 concurrent workers).
  • Documentation

    • Expanded CLI and SFTP usage guidance.
  • Refactor

    • Surescripts gender option simplified (removed "N").

Copy link

linear bot commented Aug 12, 2025

Copy link

coderabbitai bot commented Aug 12, 2025

Walkthrough

Visibility changes on S3Replica fields; expansion and normalization of Surescripts payment codes; refactors and enriches Surescripts FHIR builders (MedicationRequest, Condition) and constants; adds Surescripts replica listing/reading APIs; adds a CSV-generator CLI and parallelized conversion flow; tightens gender enums and updates related schemas and mappings.

Changes

Cohort / File(s) Summary of changes
S3 replica visibility
packages/core/src/external/sftp/replica/s3.ts
S3Replica fields s3 and bucketName changed from privateprotected.
Payment code normalization
packages/core/src/external/surescripts/codes.ts
PAYMENT_CODES expanded to include single- and two-digit forms; PAYMENT_CODE_NAME relaxed to Partial<Record<...>>; added getPaymentCodeName(code) to normalize single-digit codes and provide fallback "Other".
FHIR — MedicationRequest, bundle wiring & imports
packages/core/src/external/surescripts/fhir/medication-request.ts, packages/core/src/external/surescripts/fhir/bundle-entry.ts, packages/core/src/external/surescripts/fhir/bundle.ts, packages/core/src/external/surescripts/fhir/medication.ts
getMedicationRequest gains optional pharmacy?: Organization, builds identifiers and reasonCode, includes performer, sets intent: "order", uses MEDICATION_REQUEST_CATEGORY_URL; bundle-entry forwards pharmacy; NDC/SNOMED imports moved to @metriport/shared/medical.
FHIR — Condition helpers & constants
packages/core/src/external/surescripts/fhir/condition.ts, packages/core/src/external/surescripts/fhir/constants.ts
getCondition refactored into helpers (getConditionCoding, getClinicalStatus, getVerificationStatus); added CONDITION_CLINICAL_STATUS_URL, CONDITION_VERIFICATION_STATUS_URL, HL7_CODE_SYSTEM_URL, MEDICATION_REQUEST_CATEGORY_URL; ICD_10_URL import moved to shared module.
Surescripts replica APIs
packages/core/src/external/surescripts/replica.ts
Added getRawResponseFileByKey(key): Promise<Buffer | undefined> and listResponseFiles(): Promise<Array<{ key:string; transmissionId:string; patientId:string }>> to list/read/decompress response files and extract ids by fixed-position parsing; replaced hard-coded prefix with INCOMING_NAME.
Utils — conversion concurrency & instrumentation
packages/utils/src/surescripts/convert-customer-response.ts
Converted sequential processing to executeAsynchronously(..., { numberOfParallelExecutions: 10 }) for conversion and recreation flows; added timing instrumentation and adjusted logging/error handling.
Utils — new CSV generator CLI & registration
packages/utils/src/surescripts/generate-csv.ts, packages/utils/src/surescripts/index.ts
New generate-csv CLI: collects facility patient IDs, lists response files, concurrently reads matching responses (up to 10 parallel), writes "-roster.csv" with "patient_id","transmission_id"; command registered.
Utils — docs & CLI comments
packages/utils/src/surescripts/sftp-action.ts, packages/utils/src/surescripts/send-facility-request.ts
Added/updated detailed documentation blocks and usage examples for SFTP actions and facility-request CLI; no runtime changes.
Surescripts gender types & schemas
packages/core/src/external/surescripts/types.ts, packages/core/src/external/surescripts/file/file-generator.ts, packages/core/src/external/surescripts/schema/request.ts, packages/core/src/external/surescripts/schema/response.ts, packages/core/src/external/surescripts/fhir/patient.ts
Removed "N" from SurescriptsGender (now `"M"

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as generate-csv
  participant Mapper as SurescriptsDataMapper
  participant Replica as SurescriptsReplica
  participant S3 as S3 Backend
  participant CSV as CSV Writer

  User->>CLI: run generate-csv --cx-name --cx-id --facility-id
  CLI->>Mapper: listPatientIds(facilityId)
  Mapper-->>CLI: patientIds[]
  CLI->>Replica: listResponseFiles()
  Replica->>S3: list(prefix=INCOMING_NAME)
  S3-->>Replica: keys[]
  Replica-->>CLI: [{key, transmissionId, patientId}][]

  loop matching patientIds (parallel up to 10)
    CLI->>Replica: getRawResponseFileByKey(key)
    Replica->>S3: read(key) + decompress
    S3-->>Replica: Buffer
    Replica-->>CLI: Buffer
    CLI->>CSV: collect(patient_id, transmission_id)
  end

  CSV-->>User: <cx-name>-roster.csv written
Loading
sequenceDiagram
  participant BundleEntry
  participant MedReq as getMedicationRequest
  participant Pharmacy as Organization

  BundleEntry->>MedReq: { detail, patient, prescriber, pharmacy }
  MedReq->>MedReq: build identifier, reasonCode, performer
  MedReq-->>BundleEntry: MedicationRequest (intent="order")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba64d2 and 8e527a6.

📒 Files selected for processing (1)
  • packages/core/src/external/surescripts/replica.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/external/surescripts/replica.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch eng-484-surescripts-reconversion

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

@@ -37,7 +37,23 @@ export const PLAN_CODE_NAME: Record<PlanCode, PlanCodeName> = {
"06": "Workers' Compensation",
};

export const PAYMENT_CODES = ["01", "02", "03", "04", "05", "06", "07", "99"] as const;
export const PAYMENT_CODES = [
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 was required to handle a schema change on Surescript's end - the specification clearly states that the values would be "01", "02", etc, and the initial data we received before this month always had the trailing zero. This is therefore a backwards-compatible change to account for both allowed enum types, and defines a function for converting to the payment code name with either format.

import { dangerouslyDeduplicateFhir } from "../../../fhir-deduplication/deduplicate-fhir";
import { hydrateFhir } from "../../fhir/hydration/hydrate-fhir";
import { crosswalkNdcToRxNorm } from "../../term-server";
import { NDC_URL } from "../../../util/constants";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

deprecated

const clinicalStatus = getClinicalStatus(detail);
const verificationStatus = getVerificationStatus(detail);
const code = getConditionCoding(detail);
if (!code) return undefined;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

has the same effect as !detail.diagnosisICD10Code

async getRawResponseFileByKey(key: string): Promise<Buffer | undefined> {
const fileContent = await this.readFile(key);
return decompressGzip(fileContent);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These two functions list all response files, and automatically parse the file names to ensure that the list only contains response files (not verification files or other file name structures that were copied into the replica in the same directory). The way to identify this file name is:

[10 character transmission ID]_[36 character UUID]_[other stuff]

Therefore, the first _ is at index start + 10, and the second is at start + 10 + 1 + 36 (accounting for the first underscore).

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

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

40-86: Action required: PaymentCode widened to include single-digit codes — standardize to canonical two-digit values

Core change introduced single-digit codes ("1".."7") while shared/consumers expect two-digit codes ("01".."07"), producing an inconsistent PaymentCode type and risking breakage in zod schemas, DB constraints, UI enums, and maps.

Files to fix (found by the search):

  • packages/core/src/external/surescripts/codes.ts — PAYMENT_CODES contains single-digit entries and getPaymentCodeName is typed to PaymentCode but also normalizes single-digit input.
  • packages/shared/src/interface/external/surescripts/payment-code.ts — PaymentCodes and mapping assume only two-digit codes and index mappings directly.

Recommended minimal fix (make canonical codes two-digit and normalize external inputs):

Replace PAYMENT_CODES in packages/core/src/external/surescripts/codes.ts with only two-digit entries and normalize incoming strings in the lookup function:

export const PAYMENT_CODES = [
  "01",
  "02",
  "03",
  "04",
  "05",
  "06",
  "07",
  "99",
] as const;

export function getPaymentCodeName(code: string): PaymentCodeName {
  const canonical = code.length === 1 && code.match(/^[1-7]$/) ? ("0" + code) : code;
  return PAYMENT_CODE_NAME[canonical as PaymentCode] ?? "Other";
}

Either apply the above (recommended) or intentionally widen all consumer types/mappings to accept single-digit forms and add normalization/tests. Run a follow-up search for any zod schemas, DB constraints, or UI enums that assume two-digit codes and update them accordingly.

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

21-24: Avoid console.log in core package; pass a proper/redacting logger or a noop

Per coding guidelines, console.log should not be used in core. It may also leak PII during hydration. Prefer a redacting logger (e.g., out().log if available in this package) or pass a noop logger.

Example (outside selected lines; illustrative only):

function noopLogger(): void {}
// ...
await hydrateFhir(bundle, noopLogger);

31-41: Prevent duplicate codings when re-hydrating or re-running the pipeline

If dangerouslyHydrateMedications executes multiple times on the same bundle, rxNormCode could be pushed repeatedly. Add a guard to avoid duplicates before pushing.

Example (outside selected lines; illustrative only):

import type { Coding } from "@medplum/fhirtypes";

function hasCoding(codings: Coding[] | undefined, candidate: Coding): boolean {
  return !!codings?.some(c => c.system === candidate.system && c.code === candidate.code);
}

// ...
if (!hasCoding(medication.code?.coding, rxNormCode)) {
  medication.code!.coding!.push(rxNormCode);
}
♻️ Duplicate comments (1)
packages/core/src/external/surescripts/replica.ts (1)

52-71: Replace magic numbers with named lengths (10/36) and derive indices for clarity and maintainability

The fixed positions are correct per our filename contract, but hard-coded 10 and 47 obscure intent and are fragile. Define lengths and compute indices to make the logic self-explanatory and safer against future changes.

Apply this diff within the method to eliminate magic numbers:

   async listResponseFiles(): Promise<
     Array<{ key: string; transmissionId: string; patientId: string }>
   > {
     const prefix = "from_surescripts/";
     const responseFiles = await this.listFileNames(prefix);
-    const startIndex = prefix.length;
-    return responseFiles
-      .filter(key => {
-        return key.charAt(startIndex + 10) === "_" && key.charAt(startIndex + 47) === "_";
-      })
-      .map(key => {
-        const transmissionId = key.substring(startIndex, startIndex + 10);
-        const patientId = key.substring(startIndex + 11, startIndex + 47);
-        return {
-          key,
-          transmissionId,
-          patientId,
-        };
-      });
+    const startIndex = prefix.length;
+    const TX_LEN = 10;
+    const UUID_LEN = 36;
+    const firstUnderscoreIdx = (base: number) => base + TX_LEN;
+    const secondUnderscoreIdx = (base: number) => firstUnderscoreIdx(base) + 1 + UUID_LEN;
+    return responseFiles
+      .filter(key => {
+        const u1 = firstUnderscoreIdx(startIndex);
+        const u2 = secondUnderscoreIdx(startIndex);
+        return key.charAt(u1) === "_" && key.charAt(u2) === "_";
+      })
+      .map(key => {
+        const u1 = firstUnderscoreIdx(startIndex);
+        const u2 = secondUnderscoreIdx(startIndex);
+        const transmissionId = key.substring(startIndex, u1);
+        const patientId = key.substring(u1 + 1, u2);
+        return { key, transmissionId, patientId };
+      });
   }

Note: Using "from_surescripts/" (no leading slash) is correct for the S3 replica. The earlier intentional leading slash convention applies to the SFTP client pathing, not S3.

🧹 Nitpick comments (13)
packages/utils/src/surescripts/convert-customer-response.ts (1)

70-91: Optional: Make concurrency configurable via CLI to tune throughput vs. load

Hard-coding 10 works for now, but environments differ. Consider a --concurrency option defaulting to 10, and pass it through to executeAsynchronously.

If you want, I can draft the option plumbing and parsing for you.

packages/utils/src/surescripts/generate-csv.ts (7)

18-18: Fix doc typo: “pneumonic” → “mnemonic”

Minor documentation nit.

Apply this diff:

- * cx-name: A pneumonic customer name, used in file naming for the generated CSV roster.
+ * cx-name: A mnemonic customer name, used in file naming for the generated CSV roster.

23-31: Wrap long usage lines to respect 100-char max column width

A few lines exceed the 100-char guideline within the JSDoc usage examples. Consider wrapping them for readability and guideline compliance.


67-85: Align with TS guideline (avoid arrow functions) and pass filtered input to executor

Per repo guidelines, prefer named functions over arrow functions in TS. Also pass matchingResponses (from earlier suggestion) to avoid scheduling unnecessary tasks.

Apply this diff:

-  await executeAsynchronously(
-    responses,
-    async response => {
-      if (patientIdSet.has(response.patientId)) {
-        identifiers.push({
-          transmissionId: response.transmissionId,
-          populationId: response.patientId,
-        });
-      }
-    },
-    {
-      numberOfParallelExecutions: 10,
-    }
-  );
+  async function processResponse(response: { key: string; transmissionId: string; patientId: string }) {
+    if (!patientIdSet.has(response.patientId)) return;
+    identifiers.push({
+      transmissionId: response.transmissionId,
+      populationId: response.patientId,
+    });
+  }
+
+  await executeAsynchronously(matchingResponses, processResponse, {
+    numberOfParallelExecutions: 10,
+  });

83-83: Extract magic number to a named constant

Move 10 into a named constant to make it configurable and self-documenting.

Apply this diff to replace the literal usage:

-      numberOfParallelExecutions: 10,
+      numberOfParallelExecutions: PARALLELISM,

Add this constant after the imports (see additional code block below).


76-79: Avoid logging patient identifiers (PII) in error paths

If you keep a “file not found” branch, avoid emitting patientId into logs. Prefer the S3 key, a truncated/hashed ID, or just counts.

Would you like me to update the log to something like: log(file not found for key ${response.key}) or log a hashed/truncated patientId?


76-79: Clarify naming mismatch: response.patientId vs. SurescriptsFileIdentifier.populationId

SurescriptsFileIdentifier uses populationId, but response objects from listResponseFiles expose patientId. The mapping is correct if patientId corresponds to populationId; otherwise, this may cause confusion.

Consider renaming the field in the listResponseFiles result to populationId or adding a short comment documenting the equivalence here for maintainers.


35-38: Instantiation at module scope is fine here; consider DI for testability (optional)

Keeping replica and dataMapper at module scope is fine for a CLI. If you plan to unit test main, consider injecting them via parameters or a small factory.

packages/core/src/external/surescripts/fhir/condition.ts (2)

42-54: Document the Z-code-to-active mapping rationale

Mapping ICD-10 Z-codes to clinicalStatus=active may be non-obvious. A brief “why” comment will help future readers.

 function getClinicalStatus(detail: ResponseDetail): Condition["clinicalStatus"] {
-  if (detail.diagnosisICD10Code?.startsWith("Z")) {
+  /** 
+   * Z-codes (factors influencing health status/contacts) are treated as "active" in our pipeline
+   * to surface ongoing health context. Update if business rules change.
+   */
+  if (detail.diagnosisICD10Code?.startsWith("Z")) {
     return {
       coding: [
         {
           system: CONDITION_CLINICAL_STATUS_SYSTEM,
           code: "active",
         },
       ],
     };
   }
   return undefined;
 }

56-68: Document verificationStatus mapping for Z-codes

Same suggestion for verificationStatus to clarify the business assumption.

 function getVerificationStatus(detail: ResponseDetail): Condition["verificationStatus"] {
-  if (detail.diagnosisICD10Code?.startsWith("Z")) {
+  /**
+   * Z-codes are considered "confirmed" to reflect a verified contextual condition/encounter reason.
+   * Revisit if upstream semantics evolve.
+   */
+  if (detail.diagnosisICD10Code?.startsWith("Z")) {
     return {
       coding: [
         {
           system: CONDITION_VERIFICATION_STATUS_SYSTEM,
           code: "confirmed",
         },
       ],
     };
   }
   return undefined;
 }
packages/core/src/external/surescripts/codes.ts (1)

70-75: Avoid recursion; normalize once for clarity

Simplify by normalizing to the 2-digit canonical form via padStart and indexing once.

-export function getPaymentCodeName(code: PaymentCode): PaymentCodeName {
-  if (code.length === 1 && code.match(/^[1-7]$/)) {
-    return getPaymentCodeName(("0" + code) as PaymentCode);
-  }
-  return PAYMENT_CODE_NAME[code] ?? "Other";
-}
+export function getPaymentCodeName(code: PaymentCode): PaymentCodeName {
+  const canonical = code.length === 1 && /^[1-7]$/.test(code) ? (("0" + code) as PaymentCode) : code;
+  return PAYMENT_CODE_NAME[canonical] ?? "Other";
+}
packages/core/src/external/surescripts/fhir/bundle.ts (2)

31-41: Sequential crosswalk calls can be slow; consider bounded concurrency

For bundles with many Medication resources, awaiting crosswalkNdcToRxNorm inside the loop serializes I/O. Consider parallelizing with a concurrency cap (e.g., 5–10), similar to the executeAsynchronously approach mentioned in the PR. This will reduce wall-clock time without overloading the term server.

Example pattern (outside selected lines; illustrative only):

// pseudo-code using a concurrency helper
await executeAsynchronously(
  bundle.entry!.filter(e => e.resource?.resourceType === "Medication"),
  async entry => { /* same crosswalk + push logic */ },
  { concurrency: 10 }
);

I can wire this up using your executeAsynchronously helper if you confirm its import path in core.


35-36: Minor: prefer named predicate over inline arrow for consistency

Guidelines suggest avoiding arrow functions; use a named predicate for readability and reuse.

Example (outside selected lines; illustrative only):

import type { Coding } from "@medplum/fhirtypes";

function isNdcCoding(c: Coding): boolean {
  return c.system === NDC_URL;
}

const ndcCode = medication.code?.coding?.find(isNdcCoding);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 96c6992 and 90da991.

📒 Files selected for processing (12)
  • packages/core/src/external/sftp/replica/s3.ts (1 hunks)
  • packages/core/src/external/surescripts/codes.ts (2 hunks)
  • packages/core/src/external/surescripts/fhir/bundle-entry.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/bundle.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/condition.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/constants.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/medication-request.ts (4 hunks)
  • packages/core/src/external/surescripts/replica.ts (1 hunks)
  • packages/utils/src/surescripts/convert-customer-response.ts (2 hunks)
  • packages/utils/src/surescripts/generate-csv.ts (1 hunks)
  • packages/utils/src/surescripts/index.ts (2 hunks)
  • packages/utils/src/surescripts/sftp-action.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Don’t use null inside the app, only on code interacting with external interfaces/services, like DB and HTTP; convert to undefined before sending inwards into the code
Use const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want to provide a default value
Avoid creating arrow functions
Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use /** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible

Files:

  • packages/core/src/external/sftp/replica/s3.ts
  • packages/core/src/external/surescripts/fhir/constants.ts
  • packages/utils/src/surescripts/index.ts
  • packages/utils/src/surescripts/generate-csv.ts
  • packages/core/src/external/surescripts/fhir/bundle-entry.ts
  • packages/utils/src/surescripts/sftp-action.ts
  • packages/core/src/external/surescripts/fhir/bundle.ts
  • packages/core/src/external/surescripts/replica.ts
  • packages/core/src/external/surescripts/fhir/condition.ts
  • packages/core/src/external/surescripts/codes.ts
  • packages/core/src/external/surescripts/fhir/medication-request.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/sftp/replica/s3.ts
  • packages/core/src/external/surescripts/fhir/constants.ts
  • packages/utils/src/surescripts/index.ts
  • packages/utils/src/surescripts/generate-csv.ts
  • packages/core/src/external/surescripts/fhir/bundle-entry.ts
  • packages/utils/src/surescripts/sftp-action.ts
  • packages/core/src/external/surescripts/fhir/bundle.ts
  • packages/core/src/external/surescripts/replica.ts
  • packages/core/src/external/surescripts/fhir/condition.ts
  • packages/core/src/external/surescripts/codes.ts
  • packages/core/src/external/surescripts/fhir/medication-request.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
**/*.ts

⚙️ CodeRabbit Configuration File

**/*.ts: - Use the Onion Pattern to organize a package's code in layers

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • U...

Files:

  • packages/core/src/external/sftp/replica/s3.ts
  • packages/core/src/external/surescripts/fhir/constants.ts
  • packages/utils/src/surescripts/index.ts
  • packages/utils/src/surescripts/generate-csv.ts
  • packages/core/src/external/surescripts/fhir/bundle-entry.ts
  • packages/utils/src/surescripts/sftp-action.ts
  • packages/core/src/external/surescripts/fhir/bundle.ts
  • packages/core/src/external/surescripts/replica.ts
  • packages/core/src/external/surescripts/fhir/condition.ts
  • packages/core/src/external/surescripts/codes.ts
  • packages/core/src/external/surescripts/fhir/medication-request.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
🧠 Learnings (16)
📓 Common learnings
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/core/src/external/surescripts/fhir/coverage.ts:0-0
Timestamp: 2025-06-18T18:34:10.489Z
Learning: Coverage resources in Surescripts FHIR conversion are currently excluded from bundles to prevent skewing data lift metrics. The team plans to examine available insurance data thoroughly before including properly structured Coverage resources with mandatory FHIR R4 elements like beneficiary and payor references.
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/core/src/external/surescripts/fhir/medication.ts:148-151
Timestamp: 2025-06-18T18:48:28.244Z
Learning: In Surescripts FHIR resource generation, avoid emitting empty strings for FHIR Coding.code fields. Use conditional inclusion patterns (e.g., spreading only when the code value is present and non-empty) to ensure FHIR compliance, as Coding.code MUST NOT be empty according to FHIR standards.
Learnt from: keshavsaharia
PR: metriport/metriport#4020
File: packages/core/src/external/fhir/adt-encounters.ts:245-253
Timestamp: 2025-08-04T17:46:17.853Z
Learning: User keshavsaharia prefers lodash chains over native JavaScript methods for readability when working with complex data transformations, even when it involves using underscore imports and might have less robust error handling.
📚 Learning: 2025-04-22T00:07:37.729Z
Learnt from: RamilGaripov
PR: metriport/metriport#3686
File: docs/medical-api/fhir/extensions/chronicity.mdx:12-21
Timestamp: 2025-04-22T00:07:37.729Z
Learning: In Metriport's FHIR Condition chronicity extension, they use "http://hl7.org/fhir/StructureDefinition/condition-related" as the extension URL and "https://public.metriport.com/fhir/StructureDefinition/condition-chronicity.json" as the valueCoding.system.

Applied to files:

  • packages/core/src/external/surescripts/fhir/constants.ts
  • packages/core/src/external/surescripts/fhir/condition.ts
📚 Learning: 2025-06-06T16:45:31.832Z
Learnt from: thomasyopes
PR: metriport/metriport#3970
File: packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts:17-17
Timestamp: 2025-06-06T16:45:31.832Z
Learning: The writeMedicationToChart function in packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts returns a response that is not currently used by any consumers, so changes to its return type are not breaking changes in practice.

Applied to files:

  • packages/core/src/external/surescripts/fhir/bundle-entry.ts
  • packages/core/src/external/surescripts/fhir/medication-request.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-06-25T18:42:07.231Z
Learnt from: keshavsaharia
PR: metriport/metriport#4075
File: packages/core/src/external/surescripts/fhir/medication-request.ts:76-83
Timestamp: 2025-06-25T18:42:07.231Z
Learning: In packages/core/src/external/surescripts/fhir/medication-request.ts, the getDispenseNote and getDosageInstruction functions are intentionally identical because the dashboard requires both the note and dosageInstruction fields of MedicationRequest to be populated with detail.directions for proper note display functionality.

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/core/src/external/surescripts/fhir/bundle.ts
📚 Learning: 2025-06-18T18:34:10.489Z
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/core/src/external/surescripts/fhir/coverage.ts:0-0
Timestamp: 2025-06-18T18:34:10.489Z
Learning: Coverage resources in Surescripts FHIR conversion are currently excluded from bundles to prevent skewing data lift metrics. The team plans to examine available insurance data thoroughly before including properly structured Coverage resources with mandatory FHIR R4 elements like beneficiary and payor references.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/core/src/external/surescripts/fhir/bundle.ts
📚 Learning: 2025-06-13T21:41:01.744Z
Learnt from: thomasyopes
PR: metriport/metriport#3885
File: packages/core/src/external/surescripts/client.ts:170-180
Timestamp: 2025-06-13T21:41:01.744Z
Learning: In packages/core/src/external/surescripts/client.ts, the leading slash in "/from_surescripts" used with listFileNamesWithPrefix for replica lookups is intentional, despite appearing inconsistent with other replica path references that don't use leading slashes.

Applied to files:

  • packages/core/src/external/surescripts/replica.ts
📚 Learning: 2025-05-28T19:23:20.179Z
Learnt from: keshavsaharia
PR: metriport/metriport#3885
File: packages/core/src/external/sftp/surescripts/client.ts:1-134
Timestamp: 2025-05-28T19:23:20.179Z
Learning: In packages/core/src/external/sftp/surescripts/client.ts, the standalone getPatientLoadFileName function intentionally omits the transmission ID from the filename, which differs from the class method getPatientLoadFileName. This difference in filename generation is expected behavior.

Applied to files:

  • packages/core/src/external/surescripts/replica.ts
📚 Learning: 2025-06-18T17:22:24.597Z
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/shared/src/interface/external/surescripts/plan-code.ts:1-3
Timestamp: 2025-06-18T17:22:24.597Z
Learning: In packages/shared/src/interface/external/surescripts/plan-code.ts, the getPlanCodeName function should return undefined for invalid plan codes rather than throwing errors, as per user preference for graceful error handling.

Applied to files:

  • packages/core/src/external/surescripts/codes.ts
🧬 Code Graph Analysis (7)
packages/core/src/external/sftp/replica/s3.ts (1)
packages/core/src/external/aws/s3.ts (1)
  • S3Utils (140-570)
packages/utils/src/surescripts/index.ts (1)
packages/core/src/command/hl7v2-subscriptions/hl7v2-roster-generator.ts (1)
  • generateCsv (167-170)
packages/utils/src/surescripts/generate-csv.ts (2)
packages/core/src/external/surescripts/types.ts (1)
  • SurescriptsFileIdentifier (27-30)
packages/utils/src/surescripts/shared.ts (1)
  • writeSurescriptsRunsFile (31-39)
packages/core/src/external/surescripts/replica.ts (1)
packages/core/src/external/sftp/compression.ts (1)
  • decompressGzip (12-19)
packages/core/src/external/surescripts/fhir/condition.ts (5)
packages/core/src/external/surescripts/schema/response.ts (1)
  • ResponseDetail (158-158)
packages/core/src/external/surescripts/fhir/patient.ts (1)
  • getPatientReference (23-31)
packages/shared/src/util/uuid-v7.ts (1)
  • uuidv7 (369-371)
packages/core/src/external/surescripts/fhir/shared.ts (1)
  • getSurescriptsDataSourceExtension (4-12)
packages/core/src/external/surescripts/fhir/constants.ts (2)
  • CONDITION_CLINICAL_STATUS_SYSTEM (22-23)
  • CONDITION_VERIFICATION_STATUS_SYSTEM (24-25)
packages/core/src/external/surescripts/fhir/medication-request.ts (4)
packages/core/src/external/surescripts/schema/response.ts (1)
  • ResponseDetail (158-158)
packages/core/src/external/surescripts/fhir/pharmacy.ts (1)
  • getPharmacyReference (28-32)
packages/core/src/external/surescripts/fhir/shared.ts (1)
  • getSurescriptsDataSourceExtension (4-12)
packages/shared/src/util/uuid-v7.ts (1)
  • uuidv7 (369-371)
packages/utils/src/surescripts/convert-customer-response.ts (4)
packages/lambdas/src/surescripts/convert-batch-response.ts (1)
  • handler (8-13)
packages/lambdas/src/surescripts/convert-patient-response.ts (1)
  • handler (8-13)
packages/core/src/external/surescripts/command/convert-patient-response/convert-patient-response-direct.ts (1)
  • SurescriptsConvertPatientResponseHandlerDirect (7-30)
packages/core/src/external/surescripts/data-mapper.ts (1)
  • SurescriptsDataMapper (19-109)
🔇 Additional comments (12)
packages/core/src/external/sftp/replica/s3.ts (1)

5-6: Visibility relaxation to protected is reasonable

Making s3 and bucketName protected improves extensibility for subclasses without changing runtime behavior. Readonly preserves invariants. LGTM.

packages/core/src/external/surescripts/replica.ts (1)

47-50: Convenience method aligns with existing decompression behavior

getRawResponseFileByKey mirrors getRawResponseFile’s decompress flow and keeps the API focused. Good addition.

packages/utils/src/surescripts/sftp-action.ts (1)

7-37: Helpful CLI docs—placement and content look good

  • Uses a proper /** */ block after imports, matching our scripts guideline.
  • Clear examples for connect/list/read/write/sync/exists and gzip flags.
    LGTM.
packages/utils/src/surescripts/index.ts (1)

22-22: Registering generate-csv command is correct and documented

  • Import + addCommand(generateCsv) wired properly.
  • Nice short registry doc block after imports.
    LGTM.

Also applies to: 24-27, 46-46

packages/core/src/external/surescripts/fhir/constants.ts (1)

22-25: LGTM: correct HL7 CodeSystem URLs for Condition statuses

The constants match the official HL7 URLs for Condition clinical and verification status and align with their intended usage downstream.

packages/core/src/external/surescripts/fhir/medication-request.ts (2)

68-70: Performer reference: ensure pharmacy has a stable ID

Using performer derived from pharmacy is correct. Ensure pharmacy.id is always present; otherwise the reference will be invalid (e.g., Organization/undefined). Consider mirroring getPatientReference’s guard.

Would you like me to add a defensive check to getPharmacyReference similar to getPatientReference? For example:

Outside this file (packages/core/src/external/surescripts/fhir/pharmacy.ts):

export function getPharmacyReference(pharmacy: Organization): Reference<Organization> {
  if (!pharmacy.id) {
    throw new BadRequestError("Pharmacy ID is not defined");
  }
  return { reference: `Organization/${pharmacy.id}` };
}

177-189: Reason code mapping looks good and follows FHIR coding hygiene

Guards against empty codes and uses the shared ICD_10_URL. This aligns with prior guidance to avoid emitting empty Coding.code values.

packages/core/src/external/surescripts/fhir/condition.ts (1)

30-40: LGTM: CodeableConcept construction respects empty-code avoidance

getConditionCoding correctly omits the code when diagnosisICD10Code is missing/empty and prevents invalid Coding.code emissions.

packages/core/src/external/surescripts/codes.ts (1)

40-56: LGTM: expanded enum accepts both single- and double-digit payment codes

The widened literal union covers both formats seen from Surescripts and maintains “99”.

packages/core/src/external/surescripts/fhir/bundle-entry.ts (1)

73-73: LGTM: passing pharmacy through to MedicationRequest

This wires the optional performer reference correctly.

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

2-2: Good consolidation: import NDC_URL from shared module looks correct

Using @metriport/shared/medical for code-system constants reduces duplication and drift. No runtime impact here.


38-41: Handle crosswalk failures per-entry to avoid failing the entire bundle

I checked the code: crosswalkNdcToRxNorm (packages/core/src/external/term-server/index.ts) will return undefined for missing mappings but calls executeWithNetworkRetries and therefore can throw on network errors. The call in packages/core/src/external/surescripts/fhir/bundle.ts (around lines 38–41) is currently unprotected and a thrown error would abort hydration.

  • Files to review:
    • packages/core/src/external/surescripts/fhir/bundle.ts — the await crosswalkNdcToRxNorm(ndcCode.code) call.
    • packages/core/src/external/term-server/index.ts — crosswalkNdcToRxNorm uses executeWithNetworkRetries (can throw).

Suggested per-entry handling (replace the current 3 lines with this pattern):

try {
  const rxNormCode = await crosswalkNdcToRxNorm(ndcCode.code);
  if (
    rxNormCode &&
    !medication.code?.coding?.some(c => c.system === rxNormCode.system && c.code === rxNormCode.code)
  ) {
    medication.code!.coding!.push(rxNormCode);
  }
} catch (err) {
  // Use project logger at debug level; continue processing other Medication entries.
  console.debug("rxNorm crosswalk failed", { ndc: ndcCode.code, cause: err });
}

Decision needed: should Surescripts medication crosswalk be best-effort (skip-and-continue, as above) or fail-fast (let network/crosswalk errors abort hydration)? Note: elsewhere (consolidated bundle creation) the team sometimes prefers strict fail-fast behavior — please confirm the desired policy for Surescripts hydration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/external/surescripts/file/file-generator.ts (1)

25-26: Avoid duplicating the SurescriptsGender alias locally—import the canonical type to prevent drift

Redefining the union here can diverge from the source in ../types over time. Prefer importing the canonical alias.

Apply this diff to remove the local alias:

-type SurescriptsGender = "M" | "F" | "U";

And update the import (outside this range) to bring the type from ../types:

// At the existing import from "../types"
import { SurescriptsPatientRequestData, SurescriptsBatchRequestData } from "../types";
// Change to:
import type { SurescriptsGender } from "../types";
import { SurescriptsPatientRequestData, SurescriptsBatchRequestData } from "../types";

Optional: I can sweep the codebase and replace any other local shadowing definitions in the Surescripts folder.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6c0d665 and 9becbd6.

📒 Files selected for processing (2)
  • packages/core/src/external/surescripts/file/file-generator.ts (1 hunks)
  • packages/core/src/external/surescripts/types.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Don’t use null inside the app, only on code interacting with external interfaces/services, like DB and HTTP; convert to undefined before sending inwards into the code
Use const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want to provide a default value
Avoid creating arrow functions
Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use /** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible

Files:

  • packages/core/src/external/surescripts/types.ts
  • packages/core/src/external/surescripts/file/file-generator.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/surescripts/types.ts
  • packages/core/src/external/surescripts/file/file-generator.ts
**/*.ts

⚙️ CodeRabbit Configuration File

**/*.ts: - Use the Onion Pattern to organize a package's code in layers

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • U...

Files:

  • packages/core/src/external/surescripts/types.ts
  • packages/core/src/external/surescripts/file/file-generator.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: keshavsaharia
PR: metriport/metriport#4370
File: packages/core/src/external/quest/id-generator.ts:5-8
Timestamp: 2025-08-15T00:20:50.840Z
Learning: User keshavsaharia highly values rigorous mathematical analysis and comprehensive technical solutions that include both problem identification and complete remediation paths, particularly appreciating when statistical claims are verified and corrected with proper calculations.
Learnt from: keshavsaharia
PR: metriport/metriport#4020
File: packages/core/src/external/fhir/adt-encounters.ts:245-253
Timestamp: 2025-08-04T17:46:17.853Z
Learning: User keshavsaharia prefers lodash chains over native JavaScript methods for readability when working with complex data transformations, even when it involves using underscore imports and might have less robust error handling.
📚 Learning: 2025-05-30T01:11:35.300Z
Learnt from: keshavsaharia
PR: metriport/metriport#3885
File: packages/utils/src/surescripts/generate-patient-load-file.ts:92-94
Timestamp: 2025-05-30T01:11:35.300Z
Learning: In V0 of the Surescripts implementation (packages/utils/src/surescripts/generate-patient-load-file.ts), there is always only 1 facility ID, so the current facility_ids parsing logic with substring operations is sufficient and additional validation is not necessary.

Applied to files:

  • packages/core/src/external/surescripts/types.ts
🧬 Code Graph Analysis (1)
packages/core/src/external/surescripts/file/file-generator.ts (2)
packages/core/src/external/surescripts/types.ts (1)
  • SurescriptsGender (6-6)
packages/core/src/external/quest/codes.ts (1)
  • makeGenderDemographics (10-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/core/src/external/surescripts/file/file-generator.ts (1)

30-31: O → U mapping is consistent with the narrowed type and spec intent

The change aligns with the updated union ("M" | "F" | "U") and mirrors our Quest mapping. Default remains "U", which is appropriate.

}

function getClinicalStatus(detail: ResponseDetail): Condition["clinicalStatus"] {
if (detail.diagnosisICD10Code?.startsWith("Z")) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Z codes capture factors that influence a patient's health, not actual health conditions that should be used as the clinical status.

https://www.notion.so/metriport/Z-Codes-23fb51bb904080129d55c46658c1f6a3

export const CONDITION_CLINICAL_STATUS_SYSTEM =
"http://terminology.hl7.org/CodeSystem/condition-clinical";
export const CONDITION_VERIFICATION_STATUS_SYSTEM =
"http://terminology.hl7.org/CodeSystem/condition-ver-status";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

metriport/metriport-internal#1040

Signed-off-by: Keshav Saharia <keshav@metriport.com>
metriport/metriport-internal#1040

Signed-off-by: Keshav Saharia <keshav@metriport.com>
@@ -22,12 +22,12 @@ import { buildResponseFileNamePrefix } from "./file-names";
// Latest Surescripts specification, but responses may be in 2.2 format
const surescriptsVersion = "3.0";

type SurescriptsGender = "M" | "F" | "N" | "U";
type SurescriptsGender = "M" | "F" | "U";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While the 3.0 specification indicates that "N" is a valid gender code, we received a message from their technical support telling us that it is not valid - therefore, the implementation is being changed to the SS-expected behavior of "any gender code that is not M or F is U".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/core/src/external/surescripts/schema/response.ts (2)

226-229: Option: add tolerant decoder to map legacy "N" → "U" during parsing

If there's any chance of encountering "N" in older response files, you can preserve strict types while tolerating legacy data by normalizing at decode time.

Apply this minimal diff:

   {
     field: 12,
     key: "patientGender",
-    fromSurescripts: fromSurescriptsEnum(["M", "F", "U"]),
+    fromSurescripts: s => fromSurescriptsEnum(["M", "F", "U"])(s === "N" ? "U" : s),
   },

87-87: Removed legacy “N” from patientGender enum – no occurrences found
Automated searches across code, tests, and sample fixtures did not surface any references to "N" for patientGender or genderAtBirth. The tightened schema at packages/core/src/external/surescripts/schema/response.ts (line 87) is safe within this codebase.

However, to guard against ingestion failures if downstream feeds still emit "N", consider an optional tolerant decode that maps "N""U" before zod validation. For example:

const rawGender = incomingData.patientGender as string;
const normalizedGender = rawGender === "N" ? "U" : rawGender;
const parsed = PatientResponseSchema.parse({
  ...incomingData,
  patientGender: normalizedGender,
});
packages/core/src/external/surescripts/schema/request.ts (1)

270-273: Consider a defensive normalization in the generator (optional)

If you want extra safety against stale "N" values slipping through, you can coerce them to "U" while keeping strict output validation. If you prefer to fail fast, ignore this suggestion.

   {
     field: 15,
     key: "genderAtBirth",
-    toSurescripts: toSurescriptsEnum("genderAtBirth", ["M", "F", "U"]),
+    toSurescripts: d =>
+      toSurescriptsEnum("genderAtBirth", ["M", "F", "U"])({
+        ...d,
+        genderAtBirth: d.genderAtBirth === "N" ? "U" : d.genderAtBirth,
+      }),
   },
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9becbd6 and 337d321.

📒 Files selected for processing (6)
  • packages/core/src/external/surescripts/fhir/condition.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/constants.ts (1 hunks)
  • packages/core/src/external/surescripts/fhir/medication-request.ts (6 hunks)
  • packages/core/src/external/surescripts/fhir/patient.ts (0 hunks)
  • packages/core/src/external/surescripts/schema/request.ts (2 hunks)
  • packages/core/src/external/surescripts/schema/response.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/core/src/external/surescripts/fhir/patient.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/external/surescripts/fhir/constants.ts
  • packages/core/src/external/surescripts/fhir/medication-request.ts
  • packages/core/src/external/surescripts/fhir/condition.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Don’t use null inside the app, only on code interacting with external interfaces/services, like DB and HTTP; convert to undefined before sending inwards into the code
Use const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want to provide a default value
Avoid creating arrow functions
Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use /** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible

Files:

  • packages/core/src/external/surescripts/schema/response.ts
  • packages/core/src/external/surescripts/schema/request.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/surescripts/schema/response.ts
  • packages/core/src/external/surescripts/schema/request.ts
**/*.ts

⚙️ CodeRabbit Configuration File

**/*.ts: - Use the Onion Pattern to organize a package's code in layers

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • U...

Files:

  • packages/core/src/external/surescripts/schema/response.ts
  • packages/core/src/external/surescripts/schema/request.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: keshavsaharia
PR: metriport/metriport#4045
File: packages/core/src/external/surescripts/fhir/coverage.ts:0-0
Timestamp: 2025-06-18T18:34:10.489Z
Learning: Coverage resources in Surescripts FHIR conversion are currently excluded from bundles to prevent skewing data lift metrics. The team plans to examine available insurance data thoroughly before including properly structured Coverage resources with mandatory FHIR R4 elements like beneficiary and payor references.
Learnt from: keshavsaharia
PR: metriport/metriport#4370
File: packages/core/src/external/quest/id-generator.ts:5-8
Timestamp: 2025-08-15T00:20:50.840Z
Learning: User keshavsaharia highly values rigorous mathematical analysis and comprehensive technical solutions that include both problem identification and complete remediation paths, particularly appreciating when statistical claims are verified and corrected with proper calculations.
Learnt from: keshavsaharia
PR: metriport/metriport#4020
File: packages/core/src/external/fhir/adt-encounters.ts:245-253
Timestamp: 2025-08-04T17:46:17.853Z
Learning: User keshavsaharia prefers lodash chains over native JavaScript methods for readability when working with complex data transformations, even when it involves using underscore imports and might have less robust error handling.
📚 Learning: 2025-08-15T00:00:45.080Z
Learnt from: keshavsaharia
PR: metriport/metriport#4370
File: packages/shared/src/domain/patient.ts:5-5
Timestamp: 2025-08-15T00:00:45.080Z
Learning: The patientSchema in packages/shared/src/domain/patient.ts is used in a limited subsystem scope ("SS") and is not the same as other Patient schemas referenced throughout the codebase, making additive optional field changes like externalId safe and non-breaking.

Applied to files:

  • packages/core/src/external/surescripts/schema/response.ts
  • packages/core/src/external/surescripts/schema/request.ts
📚 Learning: 2025-06-06T16:45:31.832Z
Learnt from: thomasyopes
PR: metriport/metriport#3970
File: packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts:17-17
Timestamp: 2025-06-06T16:45:31.832Z
Learning: The writeMedicationToChart function in packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts returns a response that is not currently used by any consumers, so changes to its return type are not breaking changes in practice.

Applied to files:

  • packages/core/src/external/surescripts/schema/response.ts
📚 Learning: 2025-06-17T01:51:12.930Z
Learnt from: keshavsaharia
PR: metriport/metriport#4033
File: packages/core/src/external/surescripts/fhir/patient.ts:29-36
Timestamp: 2025-06-17T01:51:12.930Z
Learning: In Surescripts response files, the patientFirstName and patientLastName fields in ResponseDetail are always set to valid strings and are required according to the specification, so defensive checks for undefined/empty values are not needed when constructing FHIR Patient names.

Applied to files:

  • packages/core/src/external/surescripts/schema/response.ts
📚 Learning: 2025-05-30T03:42:53.380Z
Learnt from: keshavsaharia
PR: metriport/metriport#3885
File: packages/core/src/external/surescripts/schema/load.ts:65-71
Timestamp: 2025-05-30T03:42:53.380Z
Learning: In Surescripts schema files like `packages/core/src/external/surescripts/schema/load.ts`, the `key` property in field definitions is only used for direct one-to-one field mappings. When multiple fields are derived from the same source property but with different transformations (like extracting date vs. time portions), they should not have `key` properties as they represent transformations rather than direct mappings.

Applied to files:

  • packages/core/src/external/surescripts/schema/request.ts
📚 Learning: 2025-07-09T17:18:28.920Z
Learnt from: thomasyopes
PR: metriport/metriport#4164
File: packages/core/src/external/ehr/healthie/index.ts:1031-1076
Timestamp: 2025-07-09T17:18:28.920Z
Learning: In packages/core/src/external/ehr/healthie/index.ts, the `interpretation` field in `labObservationResult` is defined as a required enum in the Zod schema (`z.enum(["NORMAL", "ABNORMAL", "CRITICAL", "UNKNOWN"])`), making null-safety checks unnecessary when calling `toTitleCase(labObservationResult.interpretation)`.

Applied to files:

  • packages/core/src/external/surescripts/schema/request.ts
📚 Learning: 2025-07-09T17:18:28.920Z
Learnt from: thomasyopes
PR: metriport/metriport#4164
File: packages/core/src/external/ehr/healthie/index.ts:1031-1076
Timestamp: 2025-07-09T17:18:28.920Z
Learning: In packages/core/src/external/ehr/healthie/index.ts, the `interpretation` field in `labObservationResult` is defined as a required enum in the Zod schema (`z.enum(["high", "low", "normal"])`), making null-safety checks unnecessary when calling `toTitleCase(labObservationResult.interpretation)`.

Applied to files:

  • packages/core/src/external/surescripts/schema/request.ts
🧬 Code Graph Analysis (2)
packages/core/src/external/surescripts/schema/response.ts (1)
packages/core/src/external/surescripts/schema/shared.ts (1)
  • fromSurescriptsEnum (84-101)
packages/core/src/external/surescripts/schema/request.ts (1)
packages/core/src/external/surescripts/schema/shared.ts (1)
  • toSurescriptsEnum (103-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/core/src/external/surescripts/schema/request.ts (1)

148-149: No remaining “N” references for genderAtBirth detected
A repository-wide search (TS/JS sources, Zod enums, JSON fixtures) returned zero matches for "N". It’s safe to narrow the enum to ["M","F","U"].

@keshavsaharia keshavsaharia changed the title ENG-484: Surescripts improvements (documentation + utils scripts) ENG-484: Surescripts improvements Aug 18, 2025
@keshavsaharia keshavsaharia changed the title ENG-484: Surescripts improvements ENG-484: Surescripts updates and improvements Aug 18, 2025
async listResponseFiles(): Promise<
Array<{ key: string; transmissionId: string; patientId: string }>
> {
const prefix = "from_surescripts/";
Copy link
Contributor

Choose a reason for hiding this comment

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

this is getting used in multiple places in the file, should get pulled into a constant imo. Also this one has a slash but the others dont, is that necessary?

Copy link
Contributor Author

@keshavsaharia keshavsaharia Aug 18, 2025

Choose a reason for hiding this comment

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

addressed - there was actually already a constant that wasn't being used, so updated it in multiple places

Comment on lines +58 to +70
return responseFiles
.filter(key => {
return key.charAt(startIndex + 10) === "_" && key.charAt(startIndex + 47) === "_";
})
.map(key => {
const transmissionId = key.substring(startIndex, startIndex + 10);
const patientId = key.substring(startIndex + 11, startIndex + 47);
return {
key,
transmissionId,
patientId,
};
});
Copy link
Contributor

Choose a reason for hiding this comment

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

could we split on underscores instead of doing all this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't since there might be underscores in the transmission ID.

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.

🚢

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (2)
packages/core/src/external/surescripts/fhir/medication.ts (2)

119-129: Quantity.display is not a valid FHIR field; map to unit/system/code (UCUM) instead

Quantity does not have a display property. This produces non-conformant FHIR and may be rejected by consumers/validators. Also, set UCUM system/code consistently.

Apply:

   return {
     numerator: {
-      value: Number(detail.quantityDispensed),
-      unit: detail.quantityUnitOfMeasure,
-      ...(quantityUnitOfMeasureDisplay ? { display: quantityUnitOfMeasureDisplay } : undefined),
+      value: Number(detail.quantityDispensed),
+      unit: quantityUnitOfMeasureDisplay ?? detail.quantityUnitOfMeasure,
+      system: UNIT_OF_MEASURE_URL,
+      code: detail.quantityUnitOfMeasure,
     },
     denominator: {
       value: 1,
-      unit: "1",
+      unit: "1",
+      system: UNIT_OF_MEASURE_URL,
+      code: "1",
     },
   };

152-164: Invalid Quantity.display in strength and inconsistent code system for denominator

  • Quantity also lacks display here.
  • Denominator uses UNIT_OF_MEASURE_URL with a form code. Use the same code system you use for form (SNOMED_URL) and map the human-readable string to unit.

Apply:

       strength: {
         numerator: {
-          value: Number(detail.strengthValue),
-          system: UNIT_OF_MEASURE_URL,
-          code: detail.strengthUnitOfMeasure,
-          ...(strengthUnitOfMeasureDisplay ? { display: strengthUnitOfMeasureDisplay } : undefined),
+          value: Number(detail.strengthValue),
+          unit: strengthUnitOfMeasureDisplay ?? detail.strengthUnitOfMeasure,
+          system: UNIT_OF_MEASURE_URL,
+          code: detail.strengthUnitOfMeasure,
         },
         denominator: {
           value: 1,
-          unit: detail.strengthFormCode,
-          system: UNIT_OF_MEASURE_URL,
+          unit: strengthFormCodeDisplay ?? detail.strengthFormCode,
+          system: SNOMED_URL,
           code: detail.strengthFormCode,
-          ...(strengthFormCodeDisplay ? { display: strengthFormCodeDisplay } : undefined),
         },
🧹 Nitpick comments (7)
packages/core/src/external/surescripts/fhir/medication.ts (2)

132-136: Overly strict guard prevents lotNumber when lastFilledDate is absent

You require lastFilledDate to set lotNumber but don’t use it in the payload. This drops valid lotNumber data if lastFilledDate is missing.

Apply:

-function getMedicationBatch(detail: ResponseDetail): MedicationBatch | undefined {
-  if (!detail.lastFilledDate || !detail.prescriptionNumber) return undefined;
+function getMedicationBatch(detail: ResponseDetail): MedicationBatch | undefined {
+  if (!detail.prescriptionNumber) return undefined;

99-111: Dose form coding system review (optional)

You render form as SNOMED with display from NCPDP. If strengthFormCode originates from NCPDP’s orderable drug form set, consider using HL7 v3 orderableDrugForm code system or an NCPDP system URI for stronger semantics. If the code values are actually SNOMED, then this is already correct.

packages/utils/src/surescripts/convert-customer-response.ts (5)

11-26: Docstring clarity: s/reconversion/conversion; tighten option descriptions

Small wording tweaks improve accuracy and consistency with the command’s behavior.

Apply this diff to adjust the wording:

 /**
  * Converts all patient responses for a customer transmission to FHIR bundles.
  *
  * Usage:
  * npm run surescripts -- convert-customer-response \
  *  --cx-id "acme-uuid-1234-sadsjksl" \
  *  --facility-id "facility-uuid-7890-asdkjkds" \
  *  --csv-data "acme-roster.csv"
  *
- * cx-id: The CX ID to perform reconversion.
- * facility-id: The facility ID to perform reconversion.
- * csv-data: A CSV file with two columns: "patient_id" and "transmission_id". This file can be automatically generated
- * using the `generate-csv` command.
+ * cx-id: The customer ID to perform conversion.
+ * facility-id: The facility ID to perform conversion.
+ * csv-data: Path to a CSV with two columns: "patient_id" and "transmission_id".
+ *           This file can be generated using the `generate-csv` command.
  *
  * @see generate-csv.ts for more details on how to generate a CSV of all patient IDs for batch reconversion.
  */

70-70: Name should encode units: prefer conversionStartMs

Per our TS naming guideline for numeric values with implicit units, encode the unit in the variable name to avoid ambiguity.

Apply this diff:

-    const conversionStart = Date.now();
+    const conversionStartMs = Date.now();

And update its usage in the summary log accordingly:

-      `Converted ${convertedCount} patients in ${((Date.now() - conversionStart) / 1000).toFixed(
+      `Converted ${convertedCount} patients in ${((Date.now() - conversionStartMs) / 1000).toFixed(
         1
       )} seconds`

73-92: Count only successful conversions (when a bundle is produced)

convertPatientResponse returns undefined when no response content exists. To avoid overcounting, increment only on a truthy return.

Apply this diff:

     await executeAsynchronously(
       transmissions,
-      async ({ patientId, transmissionId }) => {
+      async ({ patientId, transmissionId }) => {
         console.log(`Converting patient ${patientId} with transmission ${transmissionId}`);
-        await handler.convertPatientResponse({
+        const conversionBundle = await handler.convertPatientResponse({
           cxId,
           facilityId,
           transmissionId,
           populationId: patientId,
         });
-        convertedCount++;
-        if (convertedCount % 100 === 0) {
-          console.log(`Converted ${convertedCount} patients`);
-        }
+        if (conversionBundle) {
+          convertedCount++;
+          if (convertedCount % 100 === 0) {
+            console.log(`Converted ${convertedCount} patients`);
+          }
+        }
       },
       {
         numberOfParallelExecutions: 10,
         keepExecutingOnError: true,
       }
     );

Optional: extract the parallelism to a single constant to reuse across both flows.

// after imports
const PARALLELISM = 10;

Then replace the two occurrences of numberOfParallelExecutions: 10 with PARALLELISM.


93-97: Improve summary: show success vs attempted

Helps quickly gauge success rate without scanning logs.

-    console.log(
-      `Converted ${convertedCount} patients in ${((Date.now() - conversionStartMs) / 1000).toFixed(
+    console.log(
+      `Converted ${convertedCount}/${transmissions.length} patients in ${((Date.now() - conversionStartMs) / 1000).toFixed(
         1
       )} seconds`
     );

103-120: Redundant keepExecutingOnError with local try/catch; streamline and improve error logging

You already handle per-item failures with try/catch inside the worker, so keepExecutingOnError: true here is redundant. Also, log the error message robustly to avoid [object Object].

Apply this diff:

       await executeAsynchronously(
         transmissions,
         async ({ patientId }) => {
           try {
             const result = await dataMapper.recreateConsolidatedBundle(cxId, patientId);
             console.log(
               `Recreated consolidated bundle for ${patientId} with request ID ${result.requestId}`
             );
             recreatedCount++;
           } catch (error) {
-            console.error(`Error recreating consolidated bundle for ${patientId}: ${error}`);
+            const message = error instanceof Error ? error.message : String(error);
+            console.error(`Error recreating consolidated bundle for ${patientId}: ${message}`);
           }
         },
         {
-          numberOfParallelExecutions: 10,
-          keepExecutingOnError: true,
+          numberOfParallelExecutions: 10,
         }
       );

Optional: mirror the conversion timing by timing the recreation phase and adding a duration to the final log (helps when running large batches).

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 337d321 and 3ba64d2.

📒 Files selected for processing (3)
  • packages/core/src/external/surescripts/fhir/medication.ts (1 hunks)
  • packages/utils/src/surescripts/convert-customer-response.ts (2 hunks)
  • packages/utils/src/surescripts/generate-csv.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/utils/src/surescripts/generate-csv.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Don’t use null inside the app, only on code interacting with external interfaces/services, like DB and HTTP; convert to undefined before sending inwards into the code
Use const whenever possible
Use async/await instead of .then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, like notEnabled, prefer isDisabled
If possible, use decomposing objects for function parameters
Prefer Nullish Coalesce (??) than the OR operator (||) when you want to provide a default value
Avoid creating arrow functions
Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
While handling errors, keep the stack trace around: if you create a new Error (e.g., MetriportError), make sure to pass the original error as the new one’s cause so the stack trace is available upstream.
max column length is 100 chars
multi-line comments use /** */
top-level comments go after the import (save pre-import to basic file header, like license)
move literals to constants declared after imports when possible

Files:

  • packages/core/src/external/surescripts/fhir/medication.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursorrules)

Use types whenever possible

Files:

  • packages/core/src/external/surescripts/fhir/medication.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
**/*.ts

⚙️ CodeRabbit Configuration File

**/*.ts: - Use the Onion Pattern to organize a package's code in layers

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • U...

Files:

  • packages/core/src/external/surescripts/fhir/medication.ts
  • packages/utils/src/surescripts/convert-customer-response.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: keshavsaharia
PR: metriport/metriport#4370
File: packages/core/src/external/quest/id-generator.ts:5-8
Timestamp: 2025-08-15T00:20:50.840Z
Learning: User keshavsaharia highly values rigorous mathematical analysis and comprehensive technical solutions that include both problem identification and complete remediation paths, particularly appreciating when statistical claims are verified and corrected with proper calculations.
Learnt from: keshavsaharia
PR: metriport/metriport#4020
File: packages/core/src/external/fhir/adt-encounters.ts:245-253
Timestamp: 2025-08-04T17:46:17.853Z
Learning: User keshavsaharia prefers lodash chains over native JavaScript methods for readability when working with complex data transformations, even when it involves using underscore imports and might have less robust error handling.
📚 Learning: 2025-06-06T16:45:31.832Z
Learnt from: thomasyopes
PR: metriport/metriport#3970
File: packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts:17-17
Timestamp: 2025-06-06T16:45:31.832Z
Learning: The writeMedicationToChart function in packages/api/src/external/ehr/athenahealth/command/write-back/medication.ts returns a response that is not currently used by any consumers, so changes to its return type are not breaking changes in practice.

Applied to files:

  • packages/core/src/external/surescripts/fhir/medication.ts
📚 Learning: 2025-06-25T18:42:07.231Z
Learnt from: keshavsaharia
PR: metriport/metriport#4075
File: packages/core/src/external/surescripts/fhir/medication-request.ts:76-83
Timestamp: 2025-06-25T18:42:07.231Z
Learning: In packages/core/src/external/surescripts/fhir/medication-request.ts, the getDispenseNote and getDosageInstruction functions are intentionally identical because the dashboard requires both the note and dosageInstruction fields of MedicationRequest to be populated with detail.directions for proper note display functionality.

Applied to files:

  • packages/core/src/external/surescripts/fhir/medication.ts
📚 Learning: 2025-08-18T17:40:14.461Z
Learnt from: keshavsaharia
PR: metriport/metriport#4359
File: packages/utils/src/surescripts/convert-customer-response.ts:70-91
Timestamp: 2025-08-18T17:40:14.461Z
Learning: The executeAsynchronously function from metriport/core/util/concurrency does not stop execution when individual items fail - it continues processing remaining items even if some throw errors, making individual try/catch blocks unnecessary for preventing execution abortion.

Applied to files:

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

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-05-27T16:10:48.223Z
Learnt from: lucasdellabella
PR: metriport/metriport#3866
File: packages/core/src/command/hl7v2-subscriptions/hl7v2-roster-generator.ts:142-151
Timestamp: 2025-05-27T16:10:48.223Z
Learning: In packages/core/src/command/hl7v2-subscriptions/hl7v2-roster-generator.ts, the user prefers to let axios errors bubble up naturally rather than adding try-catch blocks that re-throw with MetriportError wrappers, especially when the calling code already has retry mechanisms in place via simpleExecuteWithRetries.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-08-13T21:37:33.680Z
Learnt from: thomasyopes
PR: metriport/metriport#4346
File: packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts:25-29
Timestamp: 2025-08-13T21:37:33.680Z
Learning: In Athena EHR integration (packages/core/src/external/ehr/athenahealth/command/get-bundle-by-resource-type.ts), thomasyopes prefers hard-failing when secondary mappings fetch fails rather than graceful degradation, because these mappings control critical behaviors like contributionEncounterAppointmentTypesBlacklist and contributionEncounterSummariesEnabled. Running with default values when mappings are unavailable could lead to incorrect processing.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-07-17T00:19:09.217Z
Learnt from: lucasdellabella
PR: metriport/metriport#4181
File: packages/api/src/command/medical/patient/batch-utils.ts:63-78
Timestamp: 2025-07-17T00:19:09.217Z
Learning: In batch processing utilities like packages/api/src/command/medical/patient/batch-utils.ts, user lucasdellabella prefers to keep the utility focused on its core responsibility (batching) and delegate retry logic to the calling code, rather than implementing retry mechanisms within the utility itself.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-05-20T21:26:26.804Z
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/api/src/routes/internal/medical/patient-consolidated.ts:141-174
Timestamp: 2025-05-20T21:26:26.804Z
Learning: The functionality introduced in packages/api/src/routes/internal/medical/patient-consolidated.ts is planned to be refactored in downstream PR #3857, including improvements to error handling and validation.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-06-19T22:44:49.393Z
Learnt from: thomasyopes
PR: metriport/metriport#4061
File: packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/run-job.ts:34-47
Timestamp: 2025-06-19T22:44:49.393Z
Learning: In packages/api/src/external/ehr/shared/job/bundle/create-resource-diff-bundles/run-job.ts, the team prefers to keep refreshEhrBundles operations grouped using fire-and-forget pattern rather than awaiting all operations with Promise.allSettled(). This allows individual refresh operations to run independently without blocking the job runner.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-05-31T21:29:39.196Z
Learnt from: leite08
PR: metriport/metriport#3940
File: packages/core/src/command/consolidated/search/fhir-resource/search-consolidated.ts:60-73
Timestamp: 2025-05-31T21:29:39.196Z
Learning: In search-consolidated.ts, the user prefers to let errors bubble up from concurrent search operations (Promise.all) rather than catching them explicitly with try-catch blocks. Errors from searchFhirResources and searchDocuments should be allowed to propagate up the call stack.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
📚 Learning: 2025-06-25T01:56:08.732Z
Learnt from: thomasyopes
PR: metriport/metriport#4090
File: packages/core/src/command/conversion-fhir/conversion-fhir-direct.ts:18-25
Timestamp: 2025-06-25T01:56:08.732Z
Learning: In packages/core/src/command/conversion-fhir/conversion-fhir-direct.ts, the ConversionFhirDirect class is designed for local testing purposes and does not require comprehensive error handling for HTTP requests, as confirmed by the user thomasyopes.

Applied to files:

  • packages/utils/src/surescripts/convert-customer-response.ts
🧬 Code Graph Analysis (1)
packages/utils/src/surescripts/convert-customer-response.ts (3)
packages/lambdas/src/surescripts/convert-patient-response.ts (1)
  • handler (8-13)
packages/core/src/external/surescripts/command/convert-patient-response/convert-patient-response-direct.ts (1)
  • SurescriptsConvertPatientResponseHandlerDirect (7-30)
packages/core/src/external/surescripts/data-mapper.ts (1)
  • SurescriptsDataMapper (19-124)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/core/src/external/surescripts/fhir/medication.ts (1)

79-86: Verify and map Surescripts productCode’s system using productCodeQualifier

The current implementation in getMedicationProductCode always uses NDC_URL, but the response schema exposes productCodeQualifier (z.string().optional()) which likely indicates the true code system. Confirm with Surescripts documentation whether productCode is always an NDC or if it can be another code type. If it can vary, update getMedicationProductCode to:

  • Inspect detail.productCodeQualifier and map it to the correct FHIR system URI (e.g. NDC_URL when qualifier === "NDC", SNOMED, HCPCS, etc., otherwise a dedicated URI).
  • Fall back to a sensible default or omit the Coding if the qualifier is unrecognized.

Locations to address:

  • packages/core/src/external/surescripts/fhir/medication.ts:79–86 (getMedicationProductCode)
  • packages/core/src/external/surescripts/schema/response.ts:93–94 (productCode & productCodeQualifier)
packages/utils/src/surescripts/convert-customer-response.ts (1)

9-9: LGTM: correct import of concurrency helper

Importing executeAsynchronously from @metriport/core/util/concurrency aligns this script with the shared concurrency utility. No issues here.

Comment on lines +13 to +16
import { ResponseDetail } from "../schema/response";
import { getSurescriptsDataSourceExtension } from "./shared";
import { DEA_SCHEDULE_URL, UNIT_OF_MEASURE_URL } from "./constants";
import { NDC_URL, SNOMED_URL } from "@metriport/shared/medical";
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Imports centralization to shared medical constants looks good

Switching NDC_URL and SNOMED_URL to @metriport/shared/medical and keeping DEA_SCHEDULE_URL/UNIT_OF_MEASURE_URL local aligns with a single source of truth for code system URIs. Nice cleanup.

Run this to ensure there are no lingering imports of NDC_URL or SNOMED_URL from older locations:


🏁 Script executed:

#!/bin/bash
# Verify all imports of NDC_URL and SNOMED_URL come from @metriport/shared/medical
rg -n -C2 -P 'import\s+{[^}]*\b(NDC_URL|SNOMED_URL)\b[^}]*}\s+from\s+[\"\']([^\"\']+)[\"\']' \
  | sed -n 's/.*from \(.*\)$/\1/p' | sort | uniq -c

Length of output: 277


🏁 Script executed:

#!/bin/bash
# List all import statements containing NDC_URL or SNOMED_URL under packages/core
rg -n --type ts 'import.*\b(NDC_URL|SNOMED_URL)\b' -C1 packages/core

Length of output: 1904


Update remaining SNOMED_URL imports to use the shared constants

While the new imports in medication.ts and bundle.ts are correct, there are still references to SNOMED_URL (and potentially other code-system URLs) coming from the old util/constants. These need to be updated to import from @metriport/shared/medical to complete the centralization:

• packages/core/src/external/fhir/normalization/tests/normalize-coding.test.ts

// Change:
import { , SNOMED_URL } from "../../../../util/constants";
// To:
import { SNOMED_URL } from "@metriport/shared/medical";

• packages/core/src/external/fhir/normalization/tests/coding-regex.test.ts

// Change:
import { , SNOMED_URL } from "../../../../util/constants";
// To:
import { SNOMED_URL } from "@metriport/shared/medical";

• packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts

// Change:
import { , SNOMED_URL } from "../../../util/constants";
// To:
import { SNOMED_URL } from "@metriport/shared/medical";

After updating these, rerun your import-search script to confirm there are no remaining imports of NDC_URL or SNOMED_URL from local constants.

🤖 Prompt for AI Agents
In packages/core/src/external/surescripts/fhir/medication.ts around lines 13 to
16, update remaining imports of SNOMED_URL (and other code-system URLs like
NDC_URL if present) that currently come from local util/constants to instead
import from "@metriport/shared/medical"; also make the same changes in the three
test and shared files listed
(packages/core/src/external/fhir/normalization/__tests__/normalize-coding.test.ts,
packages/core/src/external/fhir/normalization/__tests__/coding-regex.test.ts,
packages/core/src/command/hl7v2-subscriptions/hl7v2-to-fhir-conversion/shared.ts)
replacing imports from their relative util/constants with imports from
"@metriport/shared/medical", then run your import-search script to verify no
remaining local imports of NDC_URL or SNOMED_URL.

@keshavsaharia keshavsaharia added this pull request to the merge queue Aug 18, 2025
Merged via the queue into develop with commit c558938 Aug 18, 2025
16 checks passed
@keshavsaharia keshavsaharia deleted the eng-484-surescripts-reconversion branch August 18, 2025 21:22
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