Skip to content

Conversation

lucasdellabella
Copy link
Contributor

@lucasdellabella lucasdellabella commented Jun 18, 2025

Dependencies

Description

This PR adds a migration and some endpoints to allow for tracking encounter rows that would be displayed in the TCM viewer

Testing

Wrote some unit tests to check happy path and common failure cases.
curl-ed

Release Plan

  • Merge this
  • Merge downstream

Summary by CodeRabbit

  • New Features
    • Introduced support for managing Traditional Chinese Medicine (TCM) encounters, including creation, updating, and listing of TCM encounter records.
    • Added new API endpoints for TCM encounter creation, update, and retrieval with filtering and pagination options.
    • Implemented validation schemas for TCM encounter data to ensure data integrity.
  • Database
    • Added a new database table for TCM encounters with relevant fields and indexing for efficient queries.
  • Tests
    • Added comprehensive unit and integration tests for TCM encounter creation, updating, and listing functionalities.
  • Chores
    • Established new model associations to link TCM encounters with patients and organizations.

metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
Copy link

linear bot commented Jun 18, 2025

Copy link

coderabbitai bot commented Jun 18, 2025

Walkthrough

Traditional Chinese Medicine (TCM) encounter management functionality has been introduced. This includes new domain and Sequelize model definitions, migration scripts for the database, command functions for creating, updating, and querying encounters, validation schemas, API routes for creating, updating, and listing encounters, and associated test utilities and suites. Model associations and routing logic have been updated accordingly.

Changes

File(s) Change Summary
.../src/domain/medical/tcm-encounter.ts Added TCM encounter domain model and event type definitions.
.../src/domain/medical/tests/tcm-encounter.ts Added test utility for generating TCM encounter domain objects.
.../src/models/medical/tcm-encounter.ts Introduced Sequelize model for TCM encounters, setup, associations, and creation input type.
.../src/models/medical/patient.ts
.../organization.ts
Added associations linking Patient and Organization models to TCM encounters.
.../src/models/medical/tests/tcm-encounter.ts Added test utility for generating mocked TCM encounter model instances.
.../src/models/db.ts Registered TCM encounter model and set up associations in DB initialization.
.../src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts Added migration to create the tcm_encounter table, columns, and index.
.../src/command/medical/tcm-encounter/create-tcm-encounter.ts Added command for creating TCM encounters with type definitions.
.../src/command/medical/tcm-encounter/update-tcm-encounter.ts Added command for updating TCM encounters with type definitions.
.../src/command/medical/tcm-encounter/get-tcm-encounters.ts Added commands for querying paginated TCM encounters with patient details and counts.
.../src/routes/medical/schemas/tcm-encounter.ts Added Zod schemas and TypeScript types for TCM encounter validation and typing.
.../src/routes/medical/tcm-encounter.ts Added router for updating and listing TCM encounters (PUT, GET endpoints).
.../src/routes/medical/dash.ts Added router aggregating medical and TCM encounter routes.
.../src/routes/medical/index.ts Reordered import statements (no functional change).
.../src/routes/index.ts Switched medical dash route to use new aggregated router.
.../src/routes/internal/medical/tcm-encounter.ts Added internal router for TCM encounter creation (POST endpoint).
.../src/routes/internal/index.ts Registered new internal TCM encounter route.
.../src/routes/internal/tests/tcm-encounter.test.ts Added tests for TCM encounter creation command and internal route.
.../src/routes/medical/tests/tcm-encounter.test.ts Added tests for TCM encounter update and list commands and endpoints.

Sequence Diagram(s)

TCM Encounter Creation (Internal POST Endpoint)

sequenceDiagram
    participant Client
    participant InternalRouter
    participant Validation
    participant Command
    participant TcmEncounterModel

    Client->>InternalRouter: POST /internal/tcm/encounter
    InternalRouter->>Validation: Validate request body
    Validation-->>InternalRouter: Validated data
    InternalRouter->>Command: createTcmEncounter(data)
    Command->>TcmEncounterModel: create(data)
    TcmEncounterModel-->>Command: Created encounter
    Command-->>InternalRouter: Created encounter
    InternalRouter-->>Client: 201 Created + encounter JSON
Loading

TCM Encounter Update (PUT Endpoint)

sequenceDiagram
    participant Client
    participant Router
    participant Validation
    participant Command
    participant TcmEncounterModel

    Client->>Router: PUT /tcm/encounter/:id
    Router->>Validation: Validate request body
    Validation-->>Router: Validated data
    Router->>Command: updateTcmEncounter({id, cxId, data})
    Command->>TcmEncounterModel: findByPk(id)
    TcmEncounterModel-->>Command: Encounter or null
    alt Encounter found
        Command->>TcmEncounterModel: update(data)
        TcmEncounterModel-->>Command: Updated
        Command->>TcmEncounterModel: findByPk(id)
        TcmEncounterModel-->>Command: Updated encounter
        Command-->>Router: Updated encounter
        Router-->>Client: 200 OK + encounter JSON
    else Not found
        Command-->>Router: NotFoundError
        Router-->>Client: Error response
    end
Loading

TCM Encounter Listing (GET Endpoint)

sequenceDiagram
    participant Client
    participant Router
    participant Validation
    participant ListCommand
    participant TcmEncounterModel

    Client->>Router: GET /tcm/encounter?after=...&limit=...
    Router->>Validation: Validate query params
    Validation-->>Router: Validated params
    Router->>ListCommand: getTcmEncounters({cxId, after, limit, offset})
    ListCommand->>TcmEncounterModel: findAll (with filters, pagination)
    TcmEncounterModel-->>ListCommand: Encounter list
    ListCommand-->>Router: Encounter list
    Router-->>Client: 200 OK + paginated results
Loading

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error code ERR_SSL_WRONG_VERSION_NUMBER
npm error errno ERR_SSL_WRONG_VERSION_NUMBER
npm error request to https://10.0.0.28:4873/punycode/-/punycode-2.3.1.tgz failed, reason: C00C1E1F3D7F0000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:354:
npm error
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-19T02_05_59_938Z-debug-0.log


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a85174c and 1eee568.

📒 Files selected for processing (1)
  • packages/api/src/routes/medical/__tests__/tcm-encounter.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/routes/medical/tests/tcm-encounter.test.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@lucasdellabella lucasdellabella marked this pull request as ready for review June 18, 2025 02:50
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (13)
packages/api/src/models/db.ts (1)

44-45: Nit: keep import list sorted for readability.

The new import breaks the existing alpha sort in the medical-model block (DocRefMapping → MAPIAccess → Patient …). Consider moving it to keep the list ordered.

packages/api/src/routes/index.ts (1)

40-43: New dash router means feature only available via dashboard path.

medicalDash supersedes medical only on the /dash-oss prefix, leaving the original /medical/v1 path without TCM encounter endpoints. Is that intentional, or should the base medical router also include them?

packages/api/src/routes/medical/dash.ts (1)

7-9: Potential double-mount if medical later imports the same route.

Right now medical does not include tcmEncounter, so this is fine.
If somebody later adds it there, it will be mounted twice under different prefixes.
Add a comment explaining why it lives separately to prevent future duplication.

packages/api/src/routes/internal/__tests__/tcm-encounter.test.ts (1)

40-43: jest.restoreAllMocks() is redundant here

You aren’t using jest.spyOn anywhere in this suite, only top-level jest.mock.
restoreAllMocks() buys nothing and adds minor cognitive overhead. Consider dropping it and keeping just clearAllMocks().

-    jest.clearAllMocks();
-    jest.restoreAllMocks();
+    jest.clearAllMocks();
packages/api/src/domain/medical/__tests__/tcm-encounter.ts (1)

10-12: Field name class collides with reserved keyword – prefer a safer alias

While valid, using class as a property forces awkward escapes (encounter["class"]) in many linters/TS configs and hurts readability.

If the DB & API contract permits, rename to something like encounterClass / visitClass. Otherwise, wrap it in a /** eslint-disable-line @typescript-eslint/no-unsafe-member-access */ comment wherever accessed to avoid future friction.

packages/api/src/routes/internal/medical/tcm-encounter.ts (1)

20-24: Return a Location header for the newly-created resource

REST best-practice: after 201 Created, include the canonical URL of the new resource.
Easy win for client discoverability and HTTP tooling support.

-  return res.status(httpStatus.CREATED).json(encounter);
+  return res
+    .status(httpStatus.CREATED)
+    .location(`/internal/tcm/encounter/${encounter.id}`)
+    .json(encounter);
packages/api/src/domain/medical/tcm-encounter.ts (1)

5-14: Reconsider class field name and date representation

  1. class collides with the TypeScript reserved keyword. Access is allowed but regularly bites consumers doing object-deconstruction ({ class } = x is invalid). Consider encounterClass for clarity.
  2. admitTime / dischargeTime are typed as Date, yet project guidelines prescribe dayjs with buildDayjs(). Persisting JS Date objects and dayjs interchangeably quickly becomes messy. Define them as Dayjs or plain string (ISO) and convert at the boundary.

Refactoring here also propagates to the Sequelize model & Zod schemas.

packages/api/src/models/medical/tcm-encounter.ts (1)

32-33: Persist enum as constant – avoid magic strings in multiple places

The literal list "Admitted" | "Transferred" | "Discharged" is hard-coded here and in the domain file. Extract a single TcmEncounterLatestEvent enum/const and reuse to minimise drift.

export const TCM_ENCOUNTER_EVENTS = ["Admitted", "Transferred", "Discharged"] as const;
export type TcmEncounterLatestEvent = typeof TCM_ENCOUNTER_EVENTS[number];

latestEvent: { type: DataTypes.ENUM(...TCM_ENCOUNTER_EVENTS) }
packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts (1)

21-33: after filter overrides default but keeps stale value in memory object

You mutate where.admitTime by spreading the existing object and then adding a new [Op.gt]. Since the key is identical, the first entry is silently overwritten – ok, but unnecessary.

-    where.admitTime = {
-      ...(where.admitTime as Record<string, unknown>),
-      [Op.gt]: new Date(after),
-    };
+    where.admitTime = { [Op.gt]: new Date(after) };

Also, convert with buildDayjs(after).toDate() to respect the project’s date-handling rule set.

packages/api/src/command/medical/tcm-encounter/update-tcm-encounter.ts (1)

38-44: Use optimistic-locking (eTag) and update timestamp

After update(), updatedEncounter is fetched again but the code does not bump updatedAt nor rotate eTag, defeating cache invalidation / concurrency control. Consider using a model hook or include:

await encounter.update({ ...data, updatedAt: buildDayjs(), eTag: uuidv4() });
packages/api/src/routes/medical/__tests__/tcm-encounter.test.ts (1)

80-104: Hard-coded Symbol.for("gt") risks false positives

expectedQuery.where.admitTime uses Symbol.for("gt") directly.
If the implementation later switches to Sequelize.Op.gt (a different symbol instance in some dialects/tests) the expectation will silently pass even though the call shape changed.

Prefer asserting on business semantics instead of raw symbols, e.g.:

expect(TcmEncounterModel.findAndCountAll).toHaveBeenCalledWith(
  expect.objectContaining({
    where: expect.objectContaining({
      admitTime: expect.any(Object),
    }),
  }),
);

Or inject Op from Sequelize into the test.

packages/api/src/routes/medical/schemas/tcm-encounter.ts (2)

47-55: Duplicate cxId definition in response schema

cxId is already present in tcmEncounterCreateSchema; redefining it in .extend() is redundant and may cause maintenance churn if the type ever changes.

-export const tcmEncounterResponseSchema = tcmEncounterCreateSchema
-  .extend({
-    id: z.string().uuid(),
-    cxId: z.string().uuid(),   // duplicate
+export const tcmEncounterResponseSchema = tcmEncounterCreateSchema
+  .extend({
+    id: z.string().uuid(),

11-20: Consider delaying ISO-string → Date conversion

Transforming strings to Date at the API boundary mixes transport-layer and runtime representations.
Downstream layers that need the raw ISO string (e.g., logging, caching) must re-serialize.
You can keep the schema as z.string().datetime() and perform conversion deeper in the domain/DB layer where a Date is truly required.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 255246c and d8fb120.

📒 Files selected for processing (18)
  • packages/api/src/command/medical/tcm-encounter/create-tcm-encounter.ts (1 hunks)
  • packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts (1 hunks)
  • packages/api/src/command/medical/tcm-encounter/update-tcm-encounter.ts (1 hunks)
  • packages/api/src/domain/medical/__tests__/tcm-encounter.ts (1 hunks)
  • packages/api/src/domain/medical/tcm-encounter.ts (1 hunks)
  • packages/api/src/models/db.ts (2 hunks)
  • packages/api/src/models/medical/__tests__/tcm-encounter.ts (1 hunks)
  • packages/api/src/models/medical/tcm-encounter.ts (1 hunks)
  • packages/api/src/routes/index.ts (2 hunks)
  • packages/api/src/routes/internal/__tests__/tcm-encounter.test.ts (1 hunks)
  • packages/api/src/routes/internal/index.ts (2 hunks)
  • packages/api/src/routes/internal/medical/tcm-encounter.ts (1 hunks)
  • packages/api/src/routes/medical/__tests__/tcm-encounter.test.ts (1 hunks)
  • packages/api/src/routes/medical/dash.ts (1 hunks)
  • packages/api/src/routes/medical/index.ts (1 hunks)
  • packages/api/src/routes/medical/schemas/tcm-encounter.ts (1 hunks)
  • packages/api/src/routes/medical/tcm-encounter.ts (1 hunks)
  • packages/api/src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...

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

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
  • Error handling
    • Pass the original error as the new one’s cause so the stack trace is persisted
    • Error messages should have a static message - add dynamic data to MetriportError's additionalInfo prop
    • Avoid sending multiple events to Sentry for a single error
  • Global constants and variables
    • Move literals to constants declared after imports when possible (avoid magic numbers)
    • Avoid shared, global objects
  • Avoid using console.log and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().log()
    • don't create multi-line strings when using JSON.stringify()
  • Use eslint to enforce code style
  • Use prettier to format code
  • max column length is 100 chars
  • multi-line comments use /** */
  • scripts: top-level comments go after the import
  • packages/api/src/routes/medical/index.ts
  • packages/api/src/models/db.ts
  • packages/api/src/routes/internal/index.ts
  • packages/api/src/routes/index.ts
  • packages/api/src/routes/medical/dash.ts
  • packages/api/src/models/medical/__tests__/tcm-encounter.ts
  • packages/api/src/routes/internal/__tests__/tcm-encounter.test.ts
  • packages/api/src/domain/medical/__tests__/tcm-encounter.ts
  • packages/api/src/command/medical/tcm-encounter/create-tcm-encounter.ts
  • packages/api/src/routes/medical/__tests__/tcm-encounter.test.ts
  • packages/api/src/command/medical/tcm-encounter/update-tcm-encounter.ts
  • packages/api/src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts
  • packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts
  • packages/api/src/models/medical/tcm-encounter.ts
  • packages/api/src/domain/medical/tcm-encounter.ts
  • packages/api/src/routes/medical/schemas/tcm-encounter.ts
  • packages/api/src/routes/medical/tcm-encounter.ts
  • packages/api/src/routes/internal/medical/tcm-encounter.ts
🧠 Learnings (1)
packages/api/src/routes/medical/index.ts (1)
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.
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
packages/api/src/models/db.ts (1)

44-45: Add associate hook (if present) for TcmEncounterModel.

PatientSettingsModel.associate is wired up explicitly a few lines below (146-148).
If TcmEncounterModel defines its own static associate method (very common for Sequelize models that reference Facility, Patient, etc.), it also needs to be invoked after all models are registered, otherwise FK constraints or include queries will silently fail at runtime.

   PatientJobModel.setup,
   TcmEncounterModel.setup,
 ];

 // …

   PatientJobModel.setup(sequelize);
   TcmEncounterModel.setup(sequelize);
+  // wire up associations
+  if ('associate' in TcmEncounterModel) {
+    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+    // @ts-ignore – generic signature difference is acceptable here
+    TcmEncounterModel.associate(sequelize.models);
+  }

(Adjust call signature to match your convention.)
Failing to wire associations now will only surface once the first join is executed.

packages/api/src/routes/medical/index.ts (1)

2-9: Import shuffle only – no action required.

The change merely re-orders existing imports; runtime behaviour is unchanged.

packages/api/src/routes/internal/index.ts (2)

56-57: Confirm internal route is intentionally singular.

The public router mounts /tcm/encounter, and the internal router now mirrors that path.
Double-check naming conventions – other collections are plural (/patient, /facility). If we expect multiple rows, /tcm/encounters may be clearer.


73-74: Auth / RBAC check for new endpoint.

Most internal routes rely on API-GW protection only. If tcmEncounter exposes any destructive operations (e.g. DELETE) ensure it performs its own RBAC / cxId validation internally just like sibling routes.

packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts (1)

35-40: Pagination lacks cursor/offset – limit alone won’t page past first slice

limit = pagination.count + 1 is good for “hasNextPage” detection, but without offset, where admitTime < lastSeen or a proper cursor you always fetch the first window. Verify the API contract; if the UI expects infinite scroll you’ll need either:

where: { , admitTime: { [Op.lt]: cursorDate } }

or offset: pagination.skip.

Comment on lines +75 to 76
TcmEncounterModel.setup,
];
Copy link

@coderabbitai coderabbitai bot Jun 18, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Read-only replica missing the new model.

modelsReadOnly only contains PatientModelReadOnly.
If TCM encounters are queried on the read replica (dash pages often list them), add a view-only model analogue or include the full model in modelsReadOnly.

🤖 Prompt for AI Agents
In packages/api/src/models/db.ts around lines 75 to 76, the modelsReadOnly array
currently only includes PatientModelReadOnly and is missing the
TcmEncounterModel or its read-only equivalent. To fix this, add a view-only
model for TcmEncounter or include the full TcmEncounterModel in the
modelsReadOnly array to ensure read-only replica queries for TCM encounters work
correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@leite08 is this necessary? Can i just do this against the RW instance for now? I'm still surprised we don't just pass all models to two separate clients, and then use the appropriate client in each command around the app.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +5 to +9
const encounter = makeTcmEncounter(params) as unknown as TcmEncounterModel;
encounter.dataValues = encounter;
encounter.save = jest.fn();
encounter.update = jest.fn();
encounter.destroy = jest.fn();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mocked persistence methods should mirror Sequelize’s async contract

save, update, and destroy are async in Sequelize – production code may rely on awaiting them.
Returning plain jest.fn() (which resolves to undefined) risks await-chains or .then() calls exploding during tests.

-  encounter.save = jest.fn();
-  encounter.update = jest.fn();
-  encounter.destroy = jest.fn();
+  // Each method resolves to the same mocked instance to emulate Sequelize’s behaviour
+  encounter.save = jest.fn().mockResolvedValue(encounter);
+  encounter.update = jest.fn().mockResolvedValue(encounter);
+  encounter.destroy = jest.fn().mockResolvedValue(encounter);

This tiny tweak keeps the helper drop-in compatible with real models.

📝 Committable suggestion

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

Suggested change
const encounter = makeTcmEncounter(params) as unknown as TcmEncounterModel;
encounter.dataValues = encounter;
encounter.save = jest.fn();
encounter.update = jest.fn();
encounter.destroy = jest.fn();
const encounter = makeTcmEncounter(params) as unknown as TcmEncounterModel;
encounter.dataValues = encounter;
+ // Each method resolves to the same mocked instance to emulate Sequelize’s behaviour
encounter.save = jest.fn().mockResolvedValue(encounter);
encounter.update = jest.fn().mockResolvedValue(encounter);
encounter.destroy = jest.fn().mockResolvedValue(encounter);
🤖 Prompt for AI Agents
In packages/api/src/models/medical/__tests__/tcm-encounter.ts around lines 5 to
9, the mocked persistence methods save, update, and destroy are currently
synchronous jest.fn() mocks, but Sequelize's real methods are async and return
promises. To fix this, update these mocks to return resolved promises by using
jest.fn().mockResolvedValue(undefined) so that any await or then calls in the
production code work correctly during tests.

Comment on lines +54 to +58
clinicalInformation: {
type: DataTypes.JSONB,
field: "clinical_information",
allowNull: false,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing e_tag column – breaks domain/model contract

TcmEncounter domain object contains an eTag.
createTcmEncounter command (not shown) reportedly sets this field, yet the table schema omits it, leading to either silent data loss or runtime errors when Model.create() receives an unknown column.

         clinicalInformation: {
           type: DataTypes.JSONB,
           field: "clinical_information",
           allowNull: false,
         },
+        eTag: {
+          type: DataTypes.UUID,
+          field: "e_tag",
+          allowNull: false,
+        },

If the helper shared.createTable(..., { addVersion: true }) is already injecting an e_tag column, please ignore – otherwise, add it to keep persistence in sync.

📝 Committable suggestion

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

Suggested change
clinicalInformation: {
type: DataTypes.JSONB,
field: "clinical_information",
allowNull: false,
},
clinicalInformation: {
type: DataTypes.JSONB,
field: "clinical_information",
allowNull: false,
},
eTag: {
type: DataTypes.UUID,
field: "e_tag",
allowNull: false,
},
🤖 Prompt for AI Agents
In packages/api/src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts
around lines 54 to 58, the migration is missing the e_tag column required by the
TcmEncounter domain model. Verify if the shared.createTable helper with
addVersion: true option is adding the e_tag column automatically; if not,
explicitly add an e_tag column of appropriate type to the table schema to ensure
the database matches the domain model and prevent data loss or runtime errors
during Model.create().

Comment on lines 17 to 24
const now = new Date();
const encounter = await TcmEncounterModel.create({
...data,
id: uuidv4(),
createdAt: now,
updatedAt: now,
eTag: uuidv4(),
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Create timestamps & IDs via BaseModel + buildDayjs() for guideline compliance

– The coding-guidelines mandate creating dates with buildDayjs() rather than new Date().
BaseModel.attributes() already wires default id, createdAt, updatedAt, and probably eTag; supplying them manually risks divergence from the central logic (e.g., database default values, hooks, optimistic-lock versioning).

-  const now = new Date();
-  const encounter = await TcmEncounterModel.create({
-    ...data,
-    id: uuidv4(),
-    createdAt: now,
-    updatedAt: now,
-    eTag: uuidv4(),
-  });
+  const encounter = await TcmEncounterModel.create({
+    ...data,
+    // let BaseModel generate id / timestamps / eTag
+  });

Besides reducing duplication this keeps all timestamp handling consistent across models.

📝 Committable suggestion

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

Suggested change
const now = new Date();
const encounter = await TcmEncounterModel.create({
...data,
id: uuidv4(),
createdAt: now,
updatedAt: now,
eTag: uuidv4(),
});
const encounter = await TcmEncounterModel.create({
...data,
// let BaseModel generate id / timestamps / eTag
});
🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/create-tcm-encounter.ts around
lines 17 to 24, replace the manual creation of timestamps and IDs using new
Date() and uuidv4() with the centralized logic provided by BaseModel and
buildDayjs(). Remove the explicit setting of id, createdAt, updatedAt, and eTag
in the create call to avoid duplication and ensure consistency with the model's
default attributes and hooks. Use buildDayjs() for any date handling to comply
with coding guidelines.

Comment on lines 22 to 36
cxId: {
type: DataTypes.UUID,
},
patientId: {
type: DataTypes.UUID,
},
facilityName: {
type: DataTypes.STRING,
},
latestEvent: {
type: DataTypes.ENUM("Admitted", "Transferred", "Discharged"),
},
class: {
type: DataTypes.STRING,
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add allowNull: false (and constraints) to mandatory columns

cxId, patientId, facilityName, latestEvent, and class are semantically required, but the column definitions omit allowNull: false. This allows NULL rows, contradicting the domain contract and complicating validation at higher layers.

-        cxId: {
-          type: DataTypes.UUID,
-        },
+        cxId: {
+          type: DataTypes.UUID,
+          allowNull: false,
+        },
 # …repeat for the other mandatory columns…

Also consider validate: { notEmpty: true } for strings to prevent empty-string edge cases.

📝 Committable suggestion

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

Suggested change
cxId: {
type: DataTypes.UUID,
},
patientId: {
type: DataTypes.UUID,
},
facilityName: {
type: DataTypes.STRING,
},
latestEvent: {
type: DataTypes.ENUM("Admitted", "Transferred", "Discharged"),
},
class: {
type: DataTypes.STRING,
},
cxId: {
type: DataTypes.UUID,
allowNull: false,
},
patientId: {
type: DataTypes.UUID,
allowNull: false,
},
facilityName: {
type: DataTypes.STRING,
allowNull: false,
},
latestEvent: {
type: DataTypes.ENUM("Admitted", "Transferred", "Discharged"),
allowNull: false,
},
class: {
type: DataTypes.STRING,
allowNull: false,
},
🤖 Prompt for AI Agents
In packages/api/src/models/medical/tcm-encounter.ts between lines 22 and 36, the
columns cxId, patientId, facilityName, latestEvent, and class are missing
allowNull: false, which is necessary to enforce that these fields cannot be null
in the database. Update each of these column definitions to include allowNull:
false. Additionally, for string fields like facilityName and class, add
validate: { notEmpty: true } to prevent empty strings from being saved. This
will ensure the database schema enforces the mandatory nature of these fields
and improves data integrity.

Comment on lines 22 to 29
const encounter = await TcmEncounterModel.findByPk(id);
if (!encounter) {
return {
encounter: null,
status: 404,
message: `TCM encounter with ID ${id} not found`,
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential PII leakage – record fetched without cxId constraint

findByPk(id) may return an encounter belonging to a different customer. Even though the subsequent update is guarded by cxId, the initial read (and the 404 decision) exposes existence/metadata across tenants.

-  const encounter = await TcmEncounterModel.findByPk(id);
+  const encounter = await TcmEncounterModel.findOne({ where: { id, cxId } });

Return 404 when not found to maintain tenant isolation.

📝 Committable suggestion

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

Suggested change
const encounter = await TcmEncounterModel.findByPk(id);
if (!encounter) {
return {
encounter: null,
status: 404,
message: `TCM encounter with ID ${id} not found`,
};
}
// Ensure we only fetch an encounter belonging to the current customer
const encounter = await TcmEncounterModel.findOne({
where: { id, cxId },
});
if (!encounter) {
return {
encounter: null,
status: 404,
message: `TCM encounter with ID ${id} not found`,
};
}
🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/update-tcm-encounter.ts around
lines 22 to 29, the code fetches the encounter by primary key without filtering
by cxId, which risks exposing data across tenants. Modify the query to include a
condition that matches both the id and the cxId to ensure tenant isolation. This
way, the 404 response will correctly reflect whether the encounter exists for
the specific customer, preventing potential PII leakage.

Comment on lines +31 to +36
await TcmEncounterModel.update(data, {
where: {
id,
cxId,
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Allowing cxId updates breaks data integrity

TcmEncounterUpdateData is Partial<Omit<TcmEncounter,"id">> – this lets callers modify cxId, which would orphan the row from its owner. Exclude immutable/ownership fields:

-type TcmEncounterUpdateData = Partial<Omit<TcmEncounter, "id">>;
+type TcmEncounterUpdateData = Partial<
+  Omit<TcmEncounter, "id" | "cxId" | "createdAt" | "eTag">
+>;
📝 Committable suggestion

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

Suggested change
await TcmEncounterModel.update(data, {
where: {
id,
cxId,
},
});
- type TcmEncounterUpdateData = Partial<Omit<TcmEncounter, "id">>;
+ type TcmEncounterUpdateData = Partial<
+ Omit<TcmEncounter, "id" | "cxId" | "createdAt" | "eTag">
+ >;
🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/update-tcm-encounter.ts around
lines 31 to 36, the update operation allows modifying the cxId field, which
should be immutable to maintain data integrity. To fix this, exclude cxId from
the update data before calling TcmEncounterModel.update, ensuring that cxId
remains unchanged and the row stays correctly linked to its owner.

Comment on lines 57 to 76
const result = await paginated({
request: req,
additionalQueryParams: undefined,
getItems: async pagination => {
const { items } = await listTcmEncounters({
cxId,
after: query.after,
pagination,
});
return items;
},
getTotalCount: async () => {
const { totalCount } = await listTcmEncounters({
cxId,
after: query.after,
pagination: { count: 1, fromItem: undefined, toItem: undefined },
});
return totalCount;
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inline arrow functions violate house style & duplicate DB work

  1. Coding-guidelines advise avoiding arrow functions; define small named helpers instead.
  2. listTcmEncounters is invoked twice, doubling latency. Because the command already returns { items, totalCount }, call it once and derive both pieces.
-  const result = await paginated({
-    request: req,
-    additionalQueryParams: undefined,
-    getItems: async pagination => {
-      const { items } = await listTcmEncounters({ cxId, after: query.after, pagination });
-      return items;
-    },
-    getTotalCount: async () => {
-      const { totalCount } = await listTcmEncounters({
-        cxId,
-        after: query.after,
-        pagination: { count: 1, fromItem: undefined, toItem: undefined },
-      });
-      return totalCount;
-    },
-  });
+  const result = await paginated({
+    request: req,
+    getItemsAndTotal: async pagination =>
+      listTcmEncounters({ cxId, after: query.after, pagination }),
+  });

This both respects the style guide and halves the query load.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/api/src/routes/medical/tcm-encounter.ts around lines 57 to 76, the
code uses inline arrow functions for getItems and getTotalCount, which violates
the house style that prefers named helper functions. Additionally,
listTcmEncounters is called twice, causing redundant database queries and
increased latency. To fix this, define a single named async helper function that
calls listTcmEncounters once, retrieves both items and totalCount, and then use
this helper to provide the results needed for pagination, eliminating duplicate
calls and adhering to the style guide.

patientId: string;
facilityName: string;
latestEvent: TcmEncounterLatestEvent;
class: string;
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a TSDoc for this prop, it's not clear what it means.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,14 @@
import { BaseDomain } from "@metriport/core/domain/base-domain";

export type TcmEncounterLatestEvent = "Admitted" | "Transferred" | "Discharged";
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this represent TcmEncounterEvent, that can be used on the latest column but also to represent other non-lastest events?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep good point, renamed

latestEvent: TcmEncounterLatestEvent;
class: string;
admitTime?: Date | undefined;
dischargeTime?: Date | null | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

No null inside the code, please, only at the "edges"/interfaces when needed to by said interface (e.g., HTTP or DB), and convert it to undefined before it make it's way into our code - ref

@coderabbitai add a learning about this

Copy link

Choose a reason for hiding this comment

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

@leite08, understood! I'll make note of this guideline about avoiding null in internal code and only using it at interface boundaries.


✏️ Learnings added
Learnt from: leite08
PR: metriport/metriport#4044
File: packages/api/src/command/medical/tcm-encounter/create-tcm-encounter.ts:12-12
Timestamp: 2025-06-18T04:50:32.441Z
Learning: Do not use `null` inside the application code. Only use `null` at "edges"/interfaces when required by external interfaces (e.g., HTTP or DB). Convert `null` to `undefined` before it makes its way into the internal codebase. This applies to TypeScript type definitions and function parameters - prefer `undefined` over `null` for optional values in internal code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

id: uuidv4(),
createdAt: now,
updatedAt: now,
eTag: uuidv4(),
Copy link
Member

Choose a reason for hiding this comment

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

We don't set this upon creation - this is automatically calculated based on the version. See BaseModel.attributes()

Comment on lines 65 to 69
export type TcmEncounterCreate = z.infer<typeof tcmEncounterCreateSchema>;
export type TcmEncounterCreateInput = z.input<typeof tcmEncounterCreateSchema>;
export type TcmEncounterUpdate = z.infer<typeof tcmEncounterUpdateSchema>;
export type TcmEncounterResponse = z.infer<typeof tcmEncounterResponseSchema>;
export type TcmEncounterListQuery = z.infer<typeof tcmEncounterListQuerySchema>;
Copy link
Member

Choose a reason for hiding this comment

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

I suggest we keep the type close to the schema, like we've been doing everywhere else

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 13 to 14
router.put("/:id", requestLogger, asyncHandler(handleUpdateTcmEncounter));
router.get("/", requestLogger, asyncHandler(handleListTcmEncounters));
Copy link
Member

Choose a reason for hiding this comment

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

ditto single route definition block, unless needed to reuse it like a deprecated route like POST /patient/:id/consolidated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can do

Comment on lines 38 to 43
} catch (error) {
if (error instanceof NotFoundError) {
return res.status(httpStatus.NOT_FOUND).json({ message: error.message });
}
throw error;
}
Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure we don't need this and the default handler already takes care of this - can you check please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 64 to 79
getItems: async pagination => {
const { items } = await listTcmEncounters({
cxId,
after: query.after,
pagination,
});
return items;
},
getTotalCount: async () => {
const { totalCount } = await listTcmEncounters({
cxId,
after: query.after,
pagination: { count: 1, fromItem: undefined, toItem: undefined },
});
return totalCount;
},
Copy link
Member

Choose a reason for hiding this comment

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

Why are we listing the entries twice, and in the list we issue two queries to the DB (one for entries and one for count)? With this we're issuing 4 queries to the DB, aren't we?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

},
admitTime: {
type: DataTypes.DATE,
allowNull: true,
Copy link
Member

Choose a reason for hiding this comment

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

these (allowNull) we can leave to the migration only. We only need types here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if we get the nullability right on the model as well we end up getting better type inference, right? You're right though, columns are nullable by default. I updated some columns to have allowNull:false instead

metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts (3)

7-7: Address the context for the fixed date as previously requested.

This fixed date still lacks explanation. Please provide context for why encounters are filtered from 2020-01-01 onwards.


11-11: Improve type safety for the after parameter.

The after parameter should be typed as Date instead of string for better type safety, as previously suggested.

export type ListTcmEncountersCmd = {
  cxId: string;
-  after?: string;
+  after?: Date;
  pagination: Pagination;
};

27-39: Simplify the where clause logic and use buildDayjs().

The current where clause logic is unnecessarily complex. Please implement the simpler approach suggested in previous reviews and use buildDayjs() for consistent timezone handling.

+import { buildDayjs } from "../../../shared/date";

-const DEFAULT_FILTER_DATE = new Date("2020-01-01T00:00:00.000Z");
+const DEFAULT_FILTER_DATE = buildDayjs("2020-01-01T00:00:00.000Z").toDate();

-  const where: Record<string, unknown> = {
-    cxId,
-    admitTime: {
-      [Op.gt]: DEFAULT_FILTER_DATE,
-    },
-  };
-
-  if (after) {
-    where.admitTime = {
-      ...(where.admitTime as Record<string, unknown>),
-      [Op.gt]: new Date(after),
-    };
-  }
+  const admitTimeFilter = after ?? DEFAULT_FILTER_DATE;
+  const where: WhereOptions<TcmEncounterModel> = {
+    cxId,
+    admitTime: {
+      [Op.gt]: admitTimeFilter,
+    },
+  };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f6c8f2c and 572fce0.

📒 Files selected for processing (7)
  • packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts (1 hunks)
  • packages/api/src/models/db.ts (3 hunks)
  • packages/api/src/models/medical/organization.ts (2 hunks)
  • packages/api/src/models/medical/patient.ts (2 hunks)
  • packages/api/src/models/medical/tcm-encounter.ts (1 hunks)
  • packages/api/src/routes/medical/schemas/tcm-encounter.ts (1 hunks)
  • packages/api/src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/api/src/sequelize/migrations/2025-06-17_00_create-tcm-encounter.ts
  • packages/api/src/models/db.ts
  • packages/api/src/routes/medical/schemas/tcm-encounter.ts
  • packages/api/src/models/medical/tcm-encounter.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...

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

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
  • Error handling
    • Pass the original error as the new one’s cause so the stack trace is persisted
    • Error messages should have a static message - add dynamic data to MetriportError's additionalInfo prop
    • Avoid sending multiple events to Sentry for a single error
  • Global constants and variables
    • Move literals to constants declared after imports when possible (avoid magic numbers)
    • Avoid shared, global objects
  • Avoid using console.log and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().log()
    • don't create multi-line strings when using JSON.stringify()
  • Use eslint to enforce code style
  • Use prettier to format code
  • max column length is 100 chars
  • multi-line comments use /** */
  • scripts: top-level comments go after the import
  • packages/api/src/models/medical/patient.ts
  • packages/api/src/models/medical/organization.ts
  • packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts
🪛 Biome (1.9.4)
packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts

[error] 59-59: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/api/src/models/medical/patient.ts (1)

5-5: LGTM! Association follows Sequelize best practices.

The import and association setup correctly establishes the one-to-many relationship between patients and TCM encounters using standard Sequelize patterns.

Also applies to: 20-25

packages/api/src/models/medical/organization.ts (1)

8-8: LGTM! Association correctly uses cxId for organization linkage.

The association properly establishes the one-to-many relationship between organizations and TCM encounters using cxId, which is consistent with the organization model's design.

Also applies to: 71-76

Comment on lines 54 to 68
const items = rows.map(row => {
const patient = (row.get("PatientModel") as PatientModel).dataValues.data;

/** Hack to get around Sequelize type inference not seeing associations */
const encounterData = { ...row.dataValues, PatientModel: undefined };
delete encounterData.PatientModel;

return {
...encounterData,
patientName: patient.firstName + " " + patient.lastName,
patientDateOfBirth: patient.dob,
patientPhoneNumbers: patient.contact?.map(contact => contact.phone),
patientStates: patient.address.map(address => address.state),
};
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Improve data access safety and avoid delete operation.

The current mapping has several issues:

  1. Use of delete operator (flagged by static analysis)
  2. Potential null safety issues with patient data access
  3. Complex data extraction logic
  const items = rows.map(row => {
-    const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
+    const patientModel = row.get("PatientModel") as PatientModel;
+    const patient = patientModel?.dataValues?.data;
+    
+    if (!patient) {
+      throw new Error("Patient data not found for TCM encounter");
+    }

-    /** Hack to get around Sequelize type inference not seeing associations */
-    const encounterData = { ...row.dataValues, PatientModel: undefined };
-    delete encounterData.PatientModel;
+    const { PatientModel: _, ...encounterData } = row.dataValues;

    return {
      ...encounterData,
      patientName: patient.firstName + " " + patient.lastName,
      patientDateOfBirth: patient.dob,
-      patientPhoneNumbers: patient.contact?.map(contact => contact.phone),
-      patientStates: patient.address.map(address => address.state),
+      patientPhoneNumbers: patient.contact?.map(contact => contact.phone) ?? [],
+      patientStates: patient.address?.map(address => address.state) ?? [],
    };
  });
📝 Committable suggestion

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

Suggested change
const items = rows.map(row => {
const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
/** Hack to get around Sequelize type inference not seeing associations */
const encounterData = { ...row.dataValues, PatientModel: undefined };
delete encounterData.PatientModel;
return {
...encounterData,
patientName: patient.firstName + " " + patient.lastName,
patientDateOfBirth: patient.dob,
patientPhoneNumbers: patient.contact?.map(contact => contact.phone),
patientStates: patient.address.map(address => address.state),
};
});
const items = rows.map(row => {
- const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
+ const patientModel = row.get("PatientModel") as PatientModel;
+ const patient = patientModel?.dataValues?.data;
+
+ if (!patient) {
+ throw new Error("Patient data not found for TCM encounter");
+ }
- /** Hack to get around Sequelize type inference not seeing associations */
- const encounterData = { ...row.dataValues, PatientModel: undefined };
- delete encounterData.PatientModel;
+ const { PatientModel: _, ...encounterData } = row.dataValues;
return {
...encounterData,
patientName: patient.firstName + " " + patient.lastName,
patientDateOfBirth: patient.dob,
- patientPhoneNumbers: patient.contact?.map(contact => contact.phone),
- patientStates: patient.address.map(address => address.state),
+ patientPhoneNumbers: patient.contact?.map(contact => contact.phone) ?? [],
+ patientStates: patient.address?.map(address => address.state) ?? [],
};
});
🧰 Tools
🪛 Biome (1.9.4)

[error] 59-59: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/list-tcm-encounters.ts between
lines 54 and 68, avoid using the delete operator on encounterData by creating a
new object that excludes PatientModel directly. Add null checks for patient and
its nested properties like contact and address to prevent runtime errors.
Simplify data extraction by safely accessing patient fields and mapping over
arrays only if they exist, ensuring robust and clean data handling without
mutating original objects.

metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
@lucasdellabella lucasdellabella force-pushed the eng-479-backend-tcm-viewer-changes branch from d13cad8 to 5aa6bf8 Compare June 19, 2025 01:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts (1)

31-36: Extract shared where clause logic to reduce duplication.

The where clause building logic is duplicated between getTcmEncounters and getTcmEncountersCount functions.

Extract a helper function:

+function buildTcmEncounterWhereClause(cxId: string, after?: string): WhereOptions<TcmEncounterModel> {
+  return {
+    cxId,
+    admitTime: {
+      [Op.gt]: after ? buildDayjs(after).toDate() : DEFAULT_FILTER_DATE,
+    },
+  };
+}

 export async function getTcmEncounters({ cxId, after, pagination }: GetTcmEncountersCmd): Promise<{
   items: TcmEncounterForDisplay[];
 }> {
-  const where: WhereOptions<TcmEncounterModel> = {
-    cxId,
-    admitTime: {
-      [Op.gt]: after ? buildDayjs(after).toDate() : DEFAULT_FILTER_DATE,
-    },
-  };
+  const where = buildTcmEncounterWhereClause(cxId, after);

Also applies to: 75-80

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d13cad8 and 5aa6bf8.

📒 Files selected for processing (3)
  • packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts (1 hunks)
  • packages/api/src/routes/medical/__tests__/tcm-encounter.test.ts (1 hunks)
  • packages/api/src/routes/medical/tcm-encounter.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/api/src/routes/medical/tests/tcm-encounter.test.ts
  • packages/api/src/routes/medical/tcm-encounter.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...

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

  • Try to use immutable code and avoid sharing state across different functions, objects, and systems
  • Try to build code that's idempotent whenever possible
  • Prefer functional programming style functions: small, deterministic, 1 input, 1 output
  • Minimize coupling / dependencies
  • Avoid modifying objects received as parameter
  • Only add comments to code to explain why something was done, not how it works
  • Naming
    • classes, enums: PascalCase
    • constants, variables, functions: camelCase
    • file names: kebab-case
    • table and column names: snake_case
    • Use meaningful names, so whoever is reading the code understands what it means
    • Don’t use negative names, like notEnabled, prefer isDisabled
    • For numeric values, if the type doesn’t convey the unit, add the unit to the name
  • Typescript
    • Use types
    • Prefer const instead of let
    • Avoid any and casting from any to other types
    • Type predicates: only applicable to narrow down the type, not to force a complete type conversion
    • Prefer deconstructing parameters for functions instead of multiple parameters that might be of
      the same type
    • Don’t use null inside the app, only on code interacting with external interfaces/services,
      like DB and HTTP; convert to undefined before sending inwards into the code
    • Use async/await instead of .then()
    • Use the strict equality operator ===, don’t use abstract equality operator ==
    • When calling a Promise-returning function asynchronously (i.e., not awaiting), use .catch() to
      handle errors (see processAsyncError and emptyFunction depending on the case)
    • Date and Time
      • Always use buildDayjs() to create dayjs instances
      • Prefer dayjs.duration(...) to create duration consts and keep them as duration
  • Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
  • Avoid creating arrow functions
  • Use truthy syntax instead of in - i.e., if (data.link) not if ('link' in data)
  • Error handling
    • Pass the original error as the new one’s cause so the stack trace is persisted
    • Error messages should have a static message - add dynamic data to MetriportError's additionalInfo prop
    • Avoid sending multiple events to Sentry for a single error
  • Global constants and variables
    • Move literals to constants declared after imports when possible (avoid magic numbers)
    • Avoid shared, global objects
  • Avoid using console.log and console.error in packages other than utils, infra and shared,
    and try to use out().log instead
  • Avoid multi-line logs
    • don't send objects as a second parameter to console.log() or out().log()
    • don't create multi-line strings when using JSON.stringify()
  • Use eslint to enforce code style
  • Use prettier to format code
  • max column length is 100 chars
  • multi-line comments use /** */
  • scripts: top-level comments go after the import
  • packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts
🪛 Biome (1.9.4)
packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts

[error] 56-56: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-pr / lint-build-test
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts (2)

1-11: LGTM! Clean imports and well-documented constant.

The imports are appropriate and the DEFAULT_FILTER_DATE constant with its explanatory comment clearly indicates the database optimization intent.


13-26: LGTM! Well-structured type definitions.

The command types and display type extension are clean and follow TypeScript best practices.

Comment on lines 28 to 70
export async function getTcmEncounters({ cxId, after, pagination }: GetTcmEncountersCmd): Promise<{
items: TcmEncounterForDisplay[];
}> {
const where: WhereOptions<TcmEncounterModel> = {
cxId,
admitTime: {
[Op.gt]: after ? buildDayjs(after).toDate() : DEFAULT_FILTER_DATE,
},
};

const rows = await TcmEncounterModel.findAll({
where,
include: [
{
model: PatientModel,
as: "PatientModel",
attributes: ["id", "cxId", "data"],
},
],
limit: pagination.count + 1, // Get one extra to determine if there's a next page
order: [["admitTime", "DESC"]],
});

const items = rows.map((row: TcmEncounterModel) => {
const patient = (row.get("PatientModel") as PatientModel).dataValues.data;

/** Hack to get around Sequelize type inference not seeing associations */
const encounterData = { ...row.dataValues, PatientModel: undefined };
delete encounterData.PatientModel;

return {
...encounterData,
patientName: patient.firstName + " " + patient.lastName,
patientDateOfBirth: patient.dob,
patientPhoneNumbers: patient.contact?.map(contact => contact.phone) ?? [],
patientStates: patient.address?.map(address => address.state) ?? [],
};
});

return {
items,
};
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for database operations.

Both functions lack error handling for database operations, which could lead to unhandled promise rejections.

Consider adding try-catch blocks or let the errors bubble up with proper context:

 export async function getTcmEncounters({ cxId, after, pagination }: GetTcmEncountersCmd): Promise<{
   items: TcmEncounterForDisplay[];
 }> {
+  try {
     const where = buildTcmEncounterWhereClause(cxId, after);
     
     const rows = await TcmEncounterModel.findAll({
       // ... existing code
     });
     
     // ... rest of function
+  } catch (error) {
+    throw new Error(`Failed to get TCM encounters for cxId ${cxId}`, { cause: error });
+  }
 }

Also applies to: 72-89

🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts around
lines 28 to 70, the database query and data processing lack error handling,
risking unhandled promise rejections. Wrap the entire function body in a
try-catch block to catch any errors during the database operations. In the catch
block, either log the error with relevant context or rethrow it with additional
information to ensure proper error propagation and easier debugging. Apply the
same pattern to the similar code in lines 72 to 89.

});

const items = rows.map((row: TcmEncounterModel) => {
const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fragile data access pattern - add null safety.

The nested property access (row.get("PatientModel") as PatientModel).dataValues.data is fragile and could fail if the association isn't loaded or the structure changes.

Add proper null checking:

-    const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
+    const patientModel = row.get("PatientModel") as PatientModel | null;
+    if (!patientModel?.dataValues?.data) {
+      throw new Error(`Patient data not found for TCM encounter ${row.id}`);
+    }
+    const patient = patientModel.dataValues.data;
📝 Committable suggestion

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

Suggested change
const patient = (row.get("PatientModel") as PatientModel).dataValues.data;
const patientModel = row.get("PatientModel") as PatientModel | null;
if (!patientModel?.dataValues?.data) {
throw new Error(`Patient data not found for TCM encounter ${row.id}`);
}
const patient = patientModel.dataValues.data;
🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts at line
52, the code directly accesses nested properties without checking if they exist,
which can cause runtime errors if the association is missing or the structure
changes. Modify the code to safely check that row.get("PatientModel") returns a
valid object and that dataValues and data are defined before accessing them,
using conditional checks or optional chaining to prevent exceptions.

Comment on lines +54 to +56
/** Hack to get around Sequelize type inference not seeing associations */
const encounterData = { ...row.dataValues, PatientModel: undefined };
delete encounterData.PatientModel;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace delete operator for better performance.

The static analysis tool correctly identifies that the delete operator can impact performance. The code comment acknowledges this is a hack.

Apply the suggested fix from the static analysis tool:

-    /** Hack to get around Sequelize type inference not seeing associations */
-    const encounterData = { ...row.dataValues, PatientModel: undefined };
-    delete encounterData.PatientModel;
+    /** Extract encounter data without associations */
+    const encounterData = { ...row.dataValues };
+    encounterData.PatientModel = undefined;

Better yet, consider destructuring to avoid the mutation entirely:

-    /** Hack to get around Sequelize type inference not seeing associations */
-    const encounterData = { ...row.dataValues, PatientModel: undefined };
-    delete encounterData.PatientModel;
+    /** Extract encounter data without associations */
+    const { PatientModel: _, ...encounterData } = row.dataValues;
🧰 Tools
🪛 Biome (1.9.4)

[error] 56-56: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts around
lines 54 to 56, avoid using the delete operator on encounterData.PatientModel
for better performance. Instead of creating encounterData by spreading
row.dataValues and then deleting PatientModel, use destructuring to exclude
PatientModel from the copied object in one step, preventing mutation and
improving efficiency.

attributes: ["id", "cxId", "data"],
},
],
limit: pagination.count + 1, // Get one extra to determine if there's a next page
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Pagination logic incomplete - extra record not handled.

The code fetches pagination.count + 1 records to detect if there's a next page, but the function doesn't remove the extra record or provide pagination metadata to indicate whether more pages exist.

Consider this approach to properly handle pagination:

  const rows = await TcmEncounterModel.findAll({
    where,
    include: [
      {
        model: PatientModel,
        as: "PatientModel",
        attributes: ["id", "cxId", "data"],
      },
    ],
    limit: pagination.count + 1, // Get one extra to determine if there's a next page
    order: [["admitTime", "DESC"]],
  });

+  const hasNextPage = rows.length > pagination.count;
+  const items = rows.slice(0, pagination.count).map((row: TcmEncounterModel) => {
-  const items = rows.map((row: TcmEncounterModel) => {

And update the return type to include pagination info:

-export async function getTcmEncounters({ cxId, after, pagination }: GetTcmEncountersCmd): Promise<{
-  items: TcmEncounterForDisplay[];
-}> {
+export async function getTcmEncounters({ cxId, after, pagination }: GetTcmEncountersCmd): Promise<{
+  items: TcmEncounterForDisplay[];
+  hasNextPage: boolean;
+}> {
📝 Committable suggestion

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

Suggested change
limit: pagination.count + 1, // Get one extra to determine if there's a next page
export async function getTcmEncounters({
cxId,
after,
pagination,
}: GetTcmEncountersCmd): Promise<{
items: TcmEncounterForDisplay[];
hasNextPage: boolean;
}> {
const rows = await TcmEncounterModel.findAll({
where,
include: [
{
model: PatientModel,
as: "PatientModel",
attributes: ["id", "cxId", "data"],
},
],
limit: pagination.count + 1, // Get one extra to determine if there's a next page
order: [["admitTime", "DESC"]],
});
const hasNextPage = rows.length > pagination.count;
const items = rows
.slice(0, pagination.count)
.map((row: TcmEncounterModel) => {
// existing mapping logic to TcmEncounterForDisplay
});
return { items, hasNextPage };
}
🤖 Prompt for AI Agents
In packages/api/src/command/medical/tcm-encounter/get-tcm-encounters.ts at line
47, the code fetches one extra record to check for a next page but does not
remove this extra record from the results or return pagination metadata. To fix
this, adjust the returned data to exclude the extra record if it exists, and add
a flag or metadata indicating whether there is a next page. Also, update the
function's return type to include this pagination information.

metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
metriport/metriport-internal#1040

Ref: ENG-479
Signed-off-by: Lucas Della Bella <dellabella.lucas@gmail.com>
Copy link
Member

@Goncharo Goncharo left a comment

Choose a reason for hiding this comment

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

Approving to get this out as discussed - please address minor outstanding comments from @leite08 on a follow up PR

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.

3 participants