-
Notifications
You must be signed in to change notification settings - Fork 72
feat(core): build Athena auto write-back #4386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Warning Rate limit exceeded@thomasyopes has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 24 minutes and 21 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (5)
WalkthroughAdds Athena write-back handlers (condition, lab, grouped-vitals, medication-statement) and dispatch wiring. Introduces secondary mappings for patient mappings (schemas, API, DB column, routes, sync call-sites). Implements Athena patient department-id retrieval/validation and update flow. Refactors grouped-vitals (union typing, grouping utilities). Extends write-back bundle logic with medication paths and new skip filters. Updates Athena API to AthenaOne patients. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Caller
participant WB as writeBackMedicationStatement
participant Dept as getAndCheckAthenaPatientDepartmentId
participant GetDept as getPatientDepartmentId
participant API as Core API (secondary mappings)
participant AthCli as AthenaHealth Client
participant Srv as API Server (update dept)
Caller->>WB: params { cxId, practiceId, ehrPatientId, medication, statements, tokenInfo? }
WB->>WB: createMedicationWithRefs()
WB->>Dept: { cxId, practiceId, patientId, tokenInfo? }
par Resolve dept
Dept->>GetDept: { cxId, athenaPatientId, athenaPracticeId, tokenInfo? }
alt dept in secondary mappings
GetDept->>API: GET /internal/ehr/athenahealth/patient/{id}/secondary-mappings
API-->>GetDept: { departmentId }
GetDept-->>Dept: departmentId
else not stored
GetDept->>AthCli: getDepartmentIdForPatient()
AthCli-->>GetDept: departmentId
GetDept->>API: GET secondary mappings
GetDept->>Srv: POST /internal/ehr/athenahealth/patient/secondary-mappings/department-id
Srv-->>GetDept: 200
GetDept-->>Dept: departmentId
end
and Validate dept
Dept->>API: GET secondary mappings (enabled depts)
API-->>Dept: { enabledDepartmentIds? }
Dept-->>WB: departmentId (or throws if not enabled)
end
WB->>AthCli: createMedicationWithStatements({ cxId, ehrPatientId, departmentId, medicationWithRefs })
AthCli-->>WB: 200
WB-->>Caller: void
sequenceDiagram
autonumber
actor Job as Write-back Job
participant Diff as shouldWriteBackResource
participant Utils as Filters/Grouping
participant Map as Dispatcher
Job->>Diff: { ehr, resource, filters }
alt Condition
Diff->>Utils: skipConditionStringFilters()
else Lab
Diff->>Utils: skipLabDateAbsolute()
else Lab Panel
Diff->>Utils: skipLabPanelDateAbsolute()
else MedicationStatement
Diff->>Utils: skipMedicationRxnormCode()/skipMedicationStatementDateAbsolute()
end
Diff-->>Job: boolean
Job->>Utils: getEhrGroupedVitals({ ehr, vitals })
Utils-->>Job: EhrGroupedVitals[]
Job->>Map: writeBackResource dispatch
Map-->>Job: handler
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
✨ Finishing Touches
🧪 Generate unit tests
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. Comment |
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
Outdated
Show resolved
Hide resolved
9b473be
to
0703c6f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (11)
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (1)
15-21
: Minor: prefer safe destructuring for departmentIdUsing array destructuring is a bit more idiomatic and guards against undefined arrays.
Apply this diff:
- const athenaDepartmentId = secondaryMappings.departmentIds[0]; + const [athenaDepartmentId] = secondaryMappings.departmentIds ?? [];packages/core/src/external/ehr/shared.ts (2)
1009-1021
: getValidCode may over-filter; consider allowing entries without displayFiltering out codings without a display can yield no titles even when valid codes exist. Suggest allowing codings with a code but missing display, and let the caller derive a fallback label.
Apply this diff:
-export function getValidCode(coding: Coding[] | undefined): Coding[] { +export function getValidCode(coding: Coding[] | undefined): Coding[] { if (!coding) return []; return coding.filter(coding => { return ( coding.code && coding.code.toLowerCase().trim() !== UNK_CODE.toLowerCase() && - coding.display && - coding.display.toLowerCase().trim() !== UNKNOWN_DISPLAY + (coding.display ? coding.display.toLowerCase().trim() !== UNKNOWN_DISPLAY : true) ); }); }
1023-1041
: Title normalization: broaden BP aliases and avoid mutating for non-S/D componentsCurrent logic rewrites any title containing “blood pressure” to “Blood Pressure” and sets
grouping = title
. That’s fine for S/D components, but generic BP measurements (e.g., MAP) will later be dropped by the pairing routine. Keep grouping only for S/D, and let other BP-related observations preserve their title.Apply this diff:
- if ( - title.toLowerCase().includes("blood pressure") || - title.toLowerCase().includes("bp sys") || - title.toLowerCase().includes("bp dias") - ) { - observationPoint.grouping = title; - updatedTitle = "Blood Pressure"; - } + const lower = title.toLowerCase(); + const isSystolic = lower.includes("systolic") || lower.includes("bp sys"); + const isDiastolic = lower.includes("diastolic") || lower.includes("bp dias"); + const isBloodPressure = lower.includes("blood pressure") || lower.startsWith("bp "); + if (isBloodPressure && (isSystolic || isDiastolic)) { + observationPoint.grouping = title; + updatedTitle = "Blood Pressure"; + }packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (4)
70-72
: Type guard: avoid 'in' per local guideline; use property presence checkSlight style tweak per guideline “Use truthy syntax instead of in”.
Apply this diff:
-export function isGroupedVitalsByCode(val: unknown): val is GroupedVitals { - return !!val && typeof val === "object" && "mostRecentObservation" in val; -} +export function isGroupedVitalsByCode(val: unknown): val is GroupedVitals { + const obj = val as Partial<GroupedVitals> | undefined; + return !!obj && typeof obj === "object" && obj.mostRecentObservation !== undefined; +}
92-96
: Ensure date-grouped output is sorted by dateDownstream consumers typically expect chronological order.
Apply this diff:
- return Object.entries(groupedVitals).map(([chartDate, observations]) => [ - buildDayjs(chartDate).toDate(), - observations, - ]); + return Object.entries(groupedVitals) + .sort((a, b) => buildDayjs(a[0]).valueOf() - buildDayjs(b[0]).valueOf()) + .map(([chartDate, observations]) => [buildDayjs(chartDate).toDate(), observations]);
105-143
: Use forEach instead of map for side effects
.map
’s return value is unused;.forEach
communicates intent and avoids creating an intermediate array.Apply this diff:
- observations.map(v => { + observations.forEach(v => { let title: string; const codings = getValidCode(v.code?.coding); const displays = codings.map(coding => coding.display); if (displays.length) { title = Array.from(new Set(displays)).join(", "); } else if (v.code?.text) { title = v.code.text; } else { results.push({ title: "-", mostRecentObservation: v }); return; } @@ - const groupedObservation = observationMap.get(title); - if (groupedObservation) { - groupedObservation.push(observationPoint); - observationMap.set(title, groupedObservation); - } else { - observationMap.set(title, [observationPoint]); - } - }); + const groupedObservation = observationMap.get(title); + if (groupedObservation) groupedObservation.push(observationPoint); + else observationMap.set(title, [observationPoint]); + });
148-164
: Nit: prefer forEach over map when pushing to an arrayThe result of
.map
is not used; use.forEach
for clarity.Apply this diff:
- Array.from(observationMap.entries()).map(([title, values]) => { + Array.from(observationMap.entries()).forEach(([title, values]) => { const sortedPoints = values.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); const mostRecent = sortedPoints[sortedPoints.length - 1]; if (!mostRecent) return; mainResults.push({ title, mostRecentObservation: mostRecent.rawVital, sortedPoints: sortedPoints.map(p => ({ value: p.observation, date: p.date, ...(p.unit && { unit: p.unit }), ...(p.bp && { bp: p.bp }), })), }); - }); + });packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (2)
10-16
: Department selection: picking the first ID is brittle when multiple are configuredIf multiple departmentIds are present, always choosing index 0 may write to an unintended department. Consider allowing an explicit preferred department (if/when our mappings support it) or at least logging which one was chosen to aid traceability.
Example minimal improvement (retain behavior, add clarity):
- const athenaDepartmentId = secondaryMappings.departmentIds[0]; + const [athenaDepartmentId] = secondaryMappings.departmentIds ?? [];Optionally, centralize resolution into a helper (see grouped-vitals comment) so all Athena write-backs share the same logic.
27-33
: Optional: Pre-validate SNOMED code before calling Athena createProblemAlthough our
ehrWriteBackConditionPrimaryCodeMap
correctly maps Athena toSNOMED_CODE
, the Athena‐specificwriteBackCondition
command does not verify that the provided FHIRCondition
actually contains a SNOMED coding. If it’s missing, Athena will return a generic 4xx—adding an explicit check will surface a clearer, early error.• Location:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (around lines 27–33)• Suggested tweak:
import { BadRequestError } from "@metriport/shared"; +import { getConditionSnomedCode } from "../../../shared"; export async function writeBackCondition(params: WriteBackConditionClientRequest): Promise<void> { const { cxId, practiceId, ehrPatientId, tokenId, condition } = params; + + // Optional: ensure the Condition has a SNOMED coding + const snomedCode = getConditionSnomedCode(condition); + if (!snomedCode) { + throw new BadRequestError( + "Missing SNOMED code for condition", + undefined, + { ehr: EhrSources.athena, practiceId } + ); + } const secondaryMappings = await getSecondaryMappings({ … }); // … await client.createProblem({ cxId, patientId: ehrPatientId, departmentId: athenaDepartmentId, condition, }); }This check is purely optional but will fail fast with a descriptive error if a SNOMED code is absent.
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
639-642
: getEhrGroupedVitals usage verified—please add guardrail testI’ve confirmed that in packages/core/src/external/ehr/job/bundle/write-back-bundles:
- There are no remaining references to the old
GroupedVitalsByDate
shape orisGroupedVitalsByDate
.- All grouped‐vitals write-back paths now call
getEhrGroupedVitals
.The switch is safe and EHR-aware. Please add or adjust a unit/integration test to cover both Athena (code-based) and Elation (date-based) grouping paths.
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (1)
23-34
: Duplicate department resolution logic — extract a shared helperThis logic mirrors the pattern in the Athena condition write-back (and likely labs). Centralize in a single resolver to ensure consistent selection, logging, and future preferences (e.g., preferred department).
Apply this diff locally to use a helper:
- const secondaryMappings = await getSecondaryMappings({ - ehr: EhrSources.athena, - practiceId, - schema: athenaSecondaryMappingsSchema, - }); - const athenaDepartmentId = secondaryMappings.departmentIds[0]; + const secondaryMappings = await getSecondaryMappings({ + ehr: EhrSources.athena, + practiceId, + schema: athenaSecondaryMappingsSchema, + }); + const [athenaDepartmentId] = secondaryMappings.departmentIds ?? []; if (!athenaDepartmentId) { throw new BadRequestError("Department ID not found", undefined, { ehr: EhrSources.athena, practiceId, }); }And consider adding a shared resolver in
packages/core/src/external/ehr/athenahealth/shared.ts
:// in athenahealth/shared.ts export async function resolveAthenaDepartmentId(practiceId: string): Promise<string> { const mappings = await getSecondaryMappings({ ehr: EhrSources.athena, practiceId, schema: athenaSecondaryMappingsSchema, }); const [id] = mappings.departmentIds ?? []; if (!id) { throw new BadRequestError("Department ID not found", undefined, { ehr: EhrSources.athena, practiceId, }); } return id; }Then replace local logic with
const athenaDepartmentId = await resolveAthenaDepartmentId(practiceId);
If you want, I can prep a follow-up PR to introduce this helper and update all Athena write-backs for consistency.
📜 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.
📒 Files selected for processing (10)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
(1 hunks)packages/core/src/external/ehr/command/write-back/condition.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
(3 hunks)packages/core/src/external/ehr/command/write-back/lab.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/shared.ts
(3 hunks)packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
(2 hunks)packages/core/src/external/ehr/shared.ts
(3 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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
🧠 Learnings (7)
📚 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/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-06-11T21:39:26.805Z
Learnt from: thomasyopes
PR: metriport/metriport#4000
File: packages/core/src/external/ehr/athenahealth/index.ts:504-507
Timestamp: 2025-06-11T21:39:26.805Z
Learning: In AthenaHealth write-back (`packages/core/src/external/ehr/athenahealth/index.ts`), only the condition statuses “relapse” and “recurrence” are currently mapped to AthenaHealth problem statuses (“CHRONIC”); other FHIR clinicalStatus values (e.g., “active”, “resolved”, “inactive”, “remission”) are not yet supported.
Applied to files:
packages/core/src/external/ehr/command/write-back/lab.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.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/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
packages/core/src/external/ehr/command/write-back/shared.ts
📚 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/ehr/shared.ts
📚 Learning: 2025-03-19T13:44:54.126Z
Learnt from: leite08
PR: metriport/metriport#3463
File: packages/api/src/command/medical/patient/settings/create-patient-settings.ts:174-175
Timestamp: 2025-03-19T13:44:54.126Z
Learning: In the Metriport codebase, Lodash is used extensively and preferred for array and object manipulations. When working with batches of data, use Lodash's `_.chunk()` instead of manual array slicing with `array.slice(i, i + batchSize)`. The codebase also commonly uses other Lodash functions like `cloneDeep`, `uniq`, `uniqBy`, `groupBy`, and `orderBy`. Prefer Lodash functions over native JavaScript implementations for better readability and consistency across the codebase.
Applied to files:
packages/core/src/external/ehr/shared.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/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
🧬 Code graph analysis (8)
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (5)
packages/core/src/external/ehr/command/write-back/lab.ts (2)
writeBackLab
(18-21)WriteBackLabClientRequest
(16-16)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/shared/src/index.ts (1)
BadRequestError
(42-42)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(8-40)
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (4)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (2)
WriteBackGroupedVitalsClientRequest
(29-29)isGroupedVitalsByCode
(70-72)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(8-40)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (4)
packages/core/src/external/ehr/command/write-back/condition.ts (2)
writeBackCondition
(19-25)WriteBackConditionClientRequest
(17-17)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(8-40)
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts (2)
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (1)
writeBackGroupedVitals
(11-46)packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
writeBackGroupedVitals
(31-37)WriteBackGroupedVitalsClientRequest
(29-29)isGroupedVitalsByDate
(66-68)
packages/core/src/external/ehr/shared.ts (2)
packages/commonwell-sdk/src/models/document.ts (1)
Coding
(21-21)packages/core/src/fhir-deduplication/shared.ts (2)
UNK_CODE
(26-26)UNKNOWN_DISPLAY
(27-27)
packages/core/src/external/ehr/command/write-back/shared.ts (1)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
EhrGroupedVitals
(18-18)
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
getEhrGroupedVitals
(169-185)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
packages/core/src/external/ehr/shared.ts (8)
GroupedVitals
(365-369)GroupedVitalsByDate
(371-371)getObservationObservedDate
(666-672)formatDate
(99-105)GroupedObservation
(373-380)getValidCode
(1010-1021)handleTitleSpecialCases
(1023-1042)handleBloodPressureMapping
(1044-1094)packages/shared/src/interface/external/ehr/source.ts (1)
EhrSource
(11-11)packages/shared/src/common/date.ts (1)
buildDayjs
(100-102)
⏰ 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). (2)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
🔇 Additional comments (16)
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (2)
8-33
: Athena lab write-back wiring looks goodFlow is straightforward: fetch secondary mappings, validate department, instantiate client, submit lab result. Error contexts include ehr and practiceId. No functional issues spotted.
27-32
: createLabResultDocument payload shape is correct
I verified that in packages/core/src/external/ehr/athenahealth/index.ts the client defines:async createLabResultDocument({ cxId, patientId, departmentId, observation, }: { cxId: string; patientId: string; departmentId: string; observation: Observation; }): Promise<CreatedLabResultSuccess> { … }– which exactly matches the four fields you’re passing in the write-back call. No mismatches in field names or types were found.
packages/core/src/external/ehr/shared.ts (1)
368-380
: Type additions for vitals grouping look appropriateAdding
title?: string
,GroupedVitalsByDate
, andGroupedObservation
provides the necessary structure for code- and date-based paths.packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
169-185
: LGTM: dispatching grouping strategy by EHR sourceClear separation: Athena -> by code; Elation -> by date; appropriate BadRequest on unsupported EHRs.
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts (2)
16-21
: Good runtime validation for union typeValidating
groupedVitals
withisGroupedVitalsByDate
keeps the Elation path safe from wrong payloads.
23-48
: Wiring and error contexts look solidSecondary mappings validation and client invocation are consistent with existing patterns. No issues spotted.
packages/core/src/external/ehr/command/write-back/lab.ts (1)
27-33
: Athena handler successfully added to dispatch mapThis enables Athena lab write-back via the shared entry point. All good.
packages/core/src/external/ehr/command/write-back/condition.ts (2)
5-5
: Athena handler wiring import looks goodAlias import pattern is consistent with other write-back handlers and keeps the dispatch table clean.
31-37
: Athena write-back handler correctly exported and registeredThe
writeBackConditionAthena
function is properly exported from
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
and the dispatcher map in
packages/core/src/external/ehr/command/write-back/condition.ts
includes the[EhrSources.athena]: writeBackConditionAthena
entry. No otherehrWriteBackConditionMap
definitions were found, and there are no stale or missing mappings. All checks out—approving this change.packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (1)
22-26
: Client creation pattern is consistentOptional token propagation via spread is clean and aligned with our conventions. No action needed.
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
33-35
: Importing getEhrGroupedVitals is the right abstractionCentralizing grouping per EHR in one place reduces drift with future EHRs and keeps this job focused on orchestration.
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (2)
16-21
: Good runtime validation for grouped-vitals shapeType guard ensures Athena receives code-grouped vitals only. Error metadata is helpful for triage.
35-45
: Client call parameters are correct and consistent with our createVitals contractPassing
cxId
,patientId
,departmentId
, and the grouped vitals payload matches the established pattern.packages/core/src/external/ehr/command/write-back/shared.ts (3)
5-5
: Importing EhrGroupedVitals keeps the public API flexibleMoving to a union type prepares us for multiple grouping strategies without leaking EHR specifics here.
17-17
: Type broadening for primaryResourceOrResources is appropriateAllowing
EhrGroupedVitals
here removes the need for local shape checks. Validation remains in EHR-specific handlers.
42-45
: No lingering raw tuple usage in grouped-vitals dispatch—resolvedAll call sites have been updated to forward the
EhrGroupedVitals
type. I verified across the repository that:
- There are no remaining
writeBackResource({…, writeBackResource: "grouped-vitals" })
calls.writeBackGroupedVitals
is only defined and invoked in theelation
andathenahealth
modules.- No raw
[Date, Observation[]]
tuples remain anywhere inpackages/core/src
.No further changes are required.
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
Outdated
Show resolved
Hide resolved
0703c6f
to
55293b7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
packages/core/src/external/ehr/shared.ts (1)
1087-1136
: BP pairing loses data and is timestamp-fragile; normalize keys and preserve singletonsPairs are done by raw date string; near-equal timestamps won’t match. Non S/D BP entries are dropped. Reuse the normalized-key strategy and keep unmatched entries.
Apply:
export function handleBloodPressureMapping(obsMap: Map<string, GroupedObservation[]>) { const bloodPressure = obsMap.get("Blood Pressure"); if (!bloodPressure) return; - const groupedBloodPressure: GroupedObservation[] = []; - - const systolicMap = new Map<string, number>(); - const diastolicMap = new Map<string, number>(); - - bloodPressure.forEach(bp => { - if (bp.grouping?.toLowerCase().includes("systolic")) { - systolicMap.set(bp.date, bp.observation); - } else if (bp.grouping?.toLowerCase().includes("diastolic")) { - diastolicMap.set(bp.date, bp.observation); - } - }); - - bloodPressure.forEach(bp => { - if (bp.grouping?.toLowerCase().includes("systolic")) { - const diastolicValue = diastolicMap.get(bp.date); - if (diastolicValue !== undefined) { - groupedBloodPressure.push({ - ...bp, - bp: { - systolic: bp.observation, - diastolic: diastolicValue, - }, - }); - } - } else if (bp.grouping?.toLowerCase().includes("diastolic")) { - const systolicValue = systolicMap.get(bp.date); - if (systolicValue !== undefined) { - if ( - !groupedBloodPressure.some( - gbp => gbp.date === bp.date && gbp.bp?.diastolic === bp.observation - ) - ) { - groupedBloodPressure.push({ - ...bp, - bp: { - systolic: systolicValue, - diastolic: bp.observation, - }, - }); - } - } - } - }); - - obsMap.set("Blood Pressure", groupedBloodPressure); + const groupedBloodPressure: GroupedObservation[] = []; + const systolicMap = new Map<string, number>(); + const diastolicMap = new Map<string, number>(); + + const toKey = (d: string) => buildDayjs(d).toISOString(); + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const key = toKey(bp.date); + if (lower.includes("systolic")) { + systolicMap.set(key, bp.observation); + } else if (lower.includes("diastolic")) { + diastolicMap.set(key, bp.observation); + } + }); + + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const key = toKey(bp.date); + if (lower.includes("systolic")) { + const diastolicValue = diastolicMap.get(key); + if (diastolicValue !== undefined) { + const candidate = { ...bp, bp: { systolic: bp.observation, diastolic: diastolicValue } }; + const dup = groupedBloodPressure.some( + gbp => + toKey(gbp.date) === key && + gbp.bp?.systolic === candidate.bp!.systolic && + gbp.bp?.diastolic === candidate.bp!.diastolic + ); + if (!dup) groupedBloodPressure.push(candidate); + } + } else if (lower.includes("diastolic")) { + const systolicValue = systolicMap.get(key); + if (systolicValue !== undefined) { + const candidate = { ...bp, bp: { systolic: systolicValue, diastolic: bp.observation } }; + const dup = groupedBloodPressure.some( + gbp => + toKey(gbp.date) === key && + gbp.bp?.systolic === candidate.bp!.systolic && + gbp.bp?.diastolic === candidate.bp!.diastolic + ); + if (!dup) groupedBloodPressure.push(candidate); + } + } + }); + + // Preserve non S/D BP observations and unpaired singletons + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const key = toKey(bp.date); + const isSD = lower.includes("systolic") || lower.includes("diastolic"); + const hasPair = groupedBloodPressure.some(gbp => toKey(gbp.date) === key); + if (!isSD || !hasPair) groupedBloodPressure.push(bp); + }); + + obsMap.set("Blood Pressure", groupedBloodPressure); }packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
98-167
: Bug: numeric parsing drops decimals and treats 0 as missing
parseInt
truncates decimals (e.g., 98.6 → 98).if (!date || !observationValue)
discards valid zero values.Apply:
- const observationValue = v.valueQuantity?.value ?? v.valueString; + const observationValue = v.valueQuantity?.value ?? v.valueString; const date = v.effectiveDateTime ?? v.effectivePeriod?.start; - if (!date || !observationValue) { + if (!date || observationValue == null) { results.push({ title, mostRecentObservation: v }); return; } const observationUnit = v.valueQuantity?.unit?.replace(/[{()}]/g, ""); - const observationPoint: GroupedObservation = { - rawVital: v, - date, - observation: - typeof observationValue === "number" ? observationValue : parseInt(observationValue), - ...(observationUnit && { unit: observationUnit }), - }; + const parsedValue = + typeof observationValue === "number" + ? observationValue + : parseFloat(String(observationValue)); + if (!Number.isFinite(parsedValue)) { + results.push({ title, mostRecentObservation: v }); + return; + } + const observationPoint: GroupedObservation = { + rawVital: v, + date, + observation: parsedValue, + ...(observationUnit && { unit: observationUnit }), + };
🧹 Nitpick comments (6)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (2)
15-21
: Don’t pick the first department arbitrarily.Selecting departmentIds[0] is non-deterministic across envs. Prefer an explicit selection strategy (e.g., configured default write-back department, per-resource override, or stable sort by ID) and log which department is used.
I can wire a selectDepartmentId(secondaryMappings) helper with clear precedence rules.
27-33
: Add observability around the write-back.Log at debug level before/after createProblem with cxId, practiceId, patientId, and departmentId (no PHI). This eases triage without noisy multi-line logs.
packages/core/src/external/ehr/shared.ts (2)
413-422
: Clarify GroupedObservation.date semanticsDefine whether
date
must be ISO 8601 (full datetime) or date-only. A branded type alias (e.g.,type IsoDateString = string & { __isoDate: true }
) or a short doc comment reduces accidental mixing.
1053-1065
: getValidCode is fine; consider broader acceptance in futureCurrent filter requires both
code
anddisplay
(not UNK/unknown). If some EHRs omitdisplay
but have validcode
, you might want to keep those and fall back tocode
. Not blocking.packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (2)
74-96
: Group-by-date: consider stable output orderingOptional: sort the returned array by date for deterministic behavior (useful for tests/logs).
- return Object.entries(groupedVitals).map(([chartDate, observations]) => [ - buildDayjs(chartDate).toDate(), - observations, - ]); + return Object.entries(groupedVitals) + .sort(([a], [b]) => buildDayjs(a).diff(buildDayjs(b))) + .map(([chartDate, observations]) => [buildDayjs(chartDate).toDate(), observations]);
169-185
: Dispatch by EHR — good; add future-proofing noteThrowing on unknown EHR is fine. If more EHRs are added, extend
writeBackGroupedVitalsEhrs
consistently.
📜 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.
📒 Files selected for processing (10)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
(1 hunks)packages/core/src/external/ehr/command/write-back/condition.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
(3 hunks)packages/core/src/external/ehr/command/write-back/lab.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/shared.ts
(3 hunks)packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
(2 hunks)packages/core/src/external/ehr/shared.ts
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/core/src/external/ehr/command/write-back/lab.ts
- packages/core/src/external/ehr/command/write-back/condition.ts
- packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
- packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
- packages/core/src/external/ehr/command/write-back/shared.ts
- packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
- packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
🧠 Learnings (8)
📚 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/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-06-11T21:39:26.805Z
Learnt from: thomasyopes
PR: metriport/metriport#4000
File: packages/core/src/external/ehr/athenahealth/index.ts:504-507
Timestamp: 2025-06-11T21:39:26.805Z
Learning: In AthenaHealth write-back (`packages/core/src/external/ehr/athenahealth/index.ts`), only the condition statuses “relapse” and “recurrence” are currently mapped to AthenaHealth problem statuses (“CHRONIC”); other FHIR clinicalStatus values (e.g., “active”, “resolved”, “inactive”, “remission”) are not yet supported.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.
Applied to files:
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
📚 Learning: 2025-08-25T17:29:25.387Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts:143-151
Timestamp: 2025-08-25T17:29:25.387Z
Learning: In packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts, the filterObservations function with vital.latestOnly=true is designed to keep only the most recent observation per LOINC code across all dates, not per date+LOINC pair. This behavior intentionally collapses multiple days into a single latest reading per code.
Applied to files:
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.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/ehr/command/write-back/grouped-vitals.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/ehr/command/write-back/grouped-vitals.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/core/src/external/ehr/shared.ts
📚 Learning: 2025-03-19T13:44:54.126Z
Learnt from: leite08
PR: metriport/metriport#3463
File: packages/api/src/command/medical/patient/settings/create-patient-settings.ts:174-175
Timestamp: 2025-03-19T13:44:54.126Z
Learning: In the Metriport codebase, Lodash is used extensively and preferred for array and object manipulations. When working with batches of data, use Lodash's `_.chunk()` instead of manual array slicing with `array.slice(i, i + batchSize)`. The codebase also commonly uses other Lodash functions like `cloneDeep`, `uniq`, `uniqBy`, `groupBy`, and `orderBy`. Prefer Lodash functions over native JavaScript implementations for better readability and consistency across the codebase.
Applied to files:
packages/core/src/external/ehr/shared.ts
🧬 Code graph analysis (3)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (4)
packages/core/src/external/ehr/command/write-back/condition.ts (2)
writeBackCondition
(19-25)WriteBackConditionClientRequest
(17-17)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(7-36)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
packages/core/src/external/ehr/shared.ts (8)
GroupedVitals
(407-411)GroupedVitalsByDate
(413-413)getObservationObservedDate
(708-714)formatDate
(105-111)GroupedObservation
(415-422)getValidCode
(1053-1064)handleTitleSpecialCases
(1066-1085)handleBloodPressureMapping
(1087-1137)packages/shared/src/interface/external/ehr/source.ts (1)
EhrSource
(11-11)packages/shared/src/common/date.ts (1)
buildDayjs
(100-102)
packages/core/src/external/ehr/shared.ts (2)
packages/commonwell-sdk/src/models/document.ts (1)
Coding
(21-21)packages/core/src/fhir-deduplication/shared.ts (2)
UNK_CODE
(26-26)UNKNOWN_DISPLAY
(27-27)
⏰ 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). (2)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (2)
10-14
: Propagate secondary-mapping fetch context on failure paths. getSecondaryMappings already wraps errors; you’re fine here. If you add more logic later, preserve the original error via cause to meet guidelines.
27-32
: 俄packages/core/src/external/ehr/shared.ts (2)
52-56
: LGTM on new importsImporting UNK_CODE/UNKNOWN_DISPLAY and fetchCodingCodeOrDisplayOrSystem here centralizes code validation well.
410-412
: Add title on GroupedVitals — goodOptional title improves UX for grouped vitals.
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (4)
5-6
: Athena handler wiring and EHR allowlist — goodHandler map and
writeBackGroupedVitalsEhrs
correctly include Athena and Elation.Also applies to: 43-49, 61-61
66-73
: Type guards look correct
isGroupedVitalsByDate
andisGroupedVitalsByCode
are sufficient for runtime branching.
145-147
: BP mapping side-effects are applied — ensure function robustnessThis relies on
handleBloodPressureMapping
correctness. See comments in shared.ts to normalize timestamps and preserve singletons.
18-27
: Handlers enforce each variant before use—union type safe. Both Athena and Elation implementations guard with isGroupedVitalsByCode or isGroupedVitalsByDate before proceeding.
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
Outdated
Show resolved
Hide resolved
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (1)
9-10
: Token auth fix acknowledged; looks correct now.Destructuring
tokenInfo
and passing it tocreateAthenaHealthClient
addresses the earliertokenId
issue. ✅Also applies to: 22-26
packages/core/src/external/ehr/shared.ts (1)
1079-1129
: BP pairing is fragile and drops data; normalize timestamps and preserve singletons.Raw string date keys can miss S/D pairing when timestamps differ slightly; non S/D BP entries are dropped.
Apply:
export function handleBloodPressureMapping(obsMap: Map<string, GroupedObservation[]>) { const bloodPressure = obsMap.get("Blood Pressure"); if (!bloodPressure) return; const groupedBloodPressure: GroupedObservation[] = []; - const systolicMap = new Map<string, number>(); - const diastolicMap = new Map<string, number>(); + const systolicMap = new Map<string, number>(); + const diastolicMap = new Map<string, number>(); - bloodPressure.forEach(bp => { - if (bp.grouping?.toLowerCase().includes("systolic")) { - systolicMap.set(bp.date, bp.observation); - } else if (bp.grouping?.toLowerCase().includes("diastolic")) { - diastolicMap.set(bp.date, bp.observation); - } - }); + const toKey = (d: string) => buildDayjs(d).toISOString(); + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const key = toKey(bp.date); + if (lower.includes("systolic")) { + systolicMap.set(key, bp.observation); + } else if (lower.includes("diastolic")) { + diastolicMap.set(key, bp.observation); + } + }); - bloodPressure.forEach(bp => { - if (bp.grouping?.toLowerCase().includes("systolic")) { - const diastolicValue = diastolicMap.get(bp.date); - if (diastolicValue !== undefined) { - groupedBloodPressure.push({ - ...bp, - bp: { - systolic: bp.observation, - diastolic: diastolicValue, - }, - }); - } - } else if (bp.grouping?.toLowerCase().includes("diastolic")) { - const systolicValue = systolicMap.get(bp.date); - if (systolicValue !== undefined) { - if ( - !groupedBloodPressure.some( - gbp => gbp.date === bp.date && gbp.bp?.diastolic === bp.observation - ) - ) { - groupedBloodPressure.push({ - ...bp, - bp: { - systolic: systolicValue, - diastolic: bp.observation, - }, - }); - } - } - } - }); + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const key = toKey(bp.date); + if (lower.includes("systolic")) { + const diastolicValue = diastolicMap.get(key); + if (diastolicValue !== undefined) { + const candidate = { ...bp, bp: { systolic: bp.observation, diastolic: diastolicValue } }; + const dup = groupedBloodPressure.some( + gbp => + toKey(gbp.date) === key && + gbp.bp?.systolic === candidate.bp!.systolic && + gbp.bp?.diastolic === candidate.bp!.diastolic + ); + if (!dup) groupedBloodPressure.push(candidate); + } + } else if (lower.includes("diastolic")) { + const systolicValue = systolicMap.get(key); + if (systolicValue !== undefined) { + const candidate = { ...bp, bp: { systolic: systolicValue, diastolic: bp.observation } }; + const dup = groupedBloodPressure.some( + gbp => + toKey(gbp.date) === key && + gbp.bp?.systolic === candidate.bp!.systolic && + gbp.bp?.diastolic === candidate.bp!.diastolic + ); + if (!dup) groupedBloodPressure.push(candidate); + } + } + }); - obsMap.set("Blood Pressure", groupedBloodPressure); + // Preserve non S/D BP observations and any singletons that couldn't be paired + bloodPressure.forEach(bp => { + const lower = bp.grouping?.toLowerCase() ?? ""; + const isSD = lower.includes("systolic") || lower.includes("diastolic"); + const hasPair = groupedBloodPressure.some(gbp => toKey(gbp.date) === toKey(bp.date)); + if (!isSD || !hasPair) groupedBloodPressure.push(bp); + }); + + obsMap.set("Blood Pressure", groupedBloodPressure); }
🧹 Nitpick comments (3)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (1)
17-20
: Add write-back context to error metadata.Include
writeBackResource: "condition"
inBadRequestError
additionalInfo to improve observability.Apply:
if (!athenaDepartmentId) { - throw new BadRequestError("Department ID not found", undefined, { - ehr: EhrSources.athena, - practiceId, - }); + throw new BadRequestError("Department ID not found", undefined, { + ehr: EhrSources.athena, + practiceId, + writeBackResource: "condition", + }); }packages/core/src/external/ehr/shared.ts (1)
1066-1077
: Broaden BMI match; keep case handling consistent.Match “body mass index” too and avoid mixing
lower
with a case-insensitive regex target.Apply:
export function handleTitleSpecialCases( title: string, observationPoint: GroupedObservation ): string { - const lower = title.toLowerCase(); - if (/(blood pressure|bp sys|bp dias|systolic|diastolic)/i.test(title)) { + const lower = title.toLowerCase(); + if (/(blood pressure|bp sys|bp dias|systolic|diastolic)/i.test(lower)) { observationPoint.grouping = title; return "Blood Pressure"; } - if (lower.includes("bmi")) return "Body Mass Index (BMI)"; + if (/(bmi|body\s*mass\s*index)/i.test(lower)) return "Body Mass Index (BMI)"; return title; }packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (1)
35-45
: Add write-back context to error metadata (consistency).If department is missing, include
writeBackResource: "grouped-vitals"
for easier triage; otherwise the flow looks good.Apply:
if (!athenaDepartmentId) { - throw new BadRequestError("Department ID not found", undefined, { - ehr: EhrSources.athena, - practiceId, - }); + throw new BadRequestError("Department ID not found", undefined, { + ehr: EhrSources.athena, + practiceId, + writeBackResource: "grouped-vitals", + }); }
📜 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.
📒 Files selected for processing (4)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
(1 hunks)packages/core/src/external/ehr/shared.ts
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/core/src/external/ehr/athenahealth/command/write-back/lab.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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/shared.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
🧠 Learnings (11)
📚 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/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
packages/core/src/external/ehr/shared.ts
📚 Learning: 2025-06-11T21:39:26.805Z
Learnt from: thomasyopes
PR: metriport/metriport#4000
File: packages/core/src/external/ehr/athenahealth/index.ts:504-507
Timestamp: 2025-06-11T21:39:26.805Z
Learning: In AthenaHealth write-back (`packages/core/src/external/ehr/athenahealth/index.ts`), only the condition statuses “relapse” and “recurrence” are currently mapped to AthenaHealth problem statuses (“CHRONIC”); other FHIR clinicalStatus values (e.g., “active”, “resolved”, “inactive”, “remission”) are not yet supported.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.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/core/src/external/ehr/athenahealth/command/write-back/condition.ts
packages/core/src/external/ehr/shared.ts
📚 Learning: 2025-08-27T23:13:51.351Z
Learnt from: thomasyopes
PR: metriport/metriport#4478
File: packages/core/src/external/ehr/command/write-back/grouped-vitals.ts:10-10
Timestamp: 2025-08-27T23:13:51.351Z
Learning: In `packages/lambdas/src/shared/ehr.ts`, the `tokenId` field in `ehrCreateResourceDiffBundlesSchema` is intentionally kept as legacy support with a TODO comment referencing ENG-937, allowing existing queue messages to be processed during the tokenId → tokenInfo migration. This is not a missing update but intentional technical debt to be cleaned up once all queue messages have been processed.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
📚 Learning: 2025-06-01T13:48:26.600Z
Learnt from: leite08
PR: metriport/metriport#3942
File: packages/core/src/external/fhir/__tests__/location.ts:6-9
Timestamp: 2025-06-01T13:48:26.600Z
Learning: In TypeScript test code, don't flag deconstructed object parameters as needing simplification or refactoring. Test helper functions with nested optional object parameters (like `param?: { patient?: PatientWithId; location?: Partial<Location>; }`) are acceptable and should not be suggested for flattening.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
📚 Learning: 2025-07-16T00:54:56.156Z
Learnt from: leite08
PR: metriport/metriport#4193
File: packages/carequality-sdk/src/client/carequality-fhir.ts:198-208
Timestamp: 2025-07-16T00:54:56.156Z
Learning: User leite08 accepted the risk of parameter mutation in packages/carequality-sdk/src/client/carequality-fhir.ts when modifying org.active = false directly on input parameters, choosing to proceed despite the violation of the "Avoid modifying objects received as parameter" coding guideline.
Applied to files:
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
📚 Learning: 2025-08-25T17:29:25.387Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts:143-151
Timestamp: 2025-08-25T17:29:25.387Z
Learning: In packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts, the filterObservations function with vital.latestOnly=true is designed to keep only the most recent observation per LOINC code across all dates, not per date+LOINC pair. This behavior intentionally collapses multiple days into a single latest reading per code.
Applied to files:
packages/core/src/external/ehr/shared.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/ehr/shared.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/ehr/shared.ts
📚 Learning: 2025-03-19T13:44:54.126Z
Learnt from: leite08
PR: metriport/metriport#3463
File: packages/api/src/command/medical/patient/settings/create-patient-settings.ts:174-175
Timestamp: 2025-03-19T13:44:54.126Z
Learning: In the Metriport codebase, Lodash is used extensively and preferred for array and object manipulations. When working with batches of data, use Lodash's `_.chunk()` instead of manual array slicing with `array.slice(i, i + batchSize)`. The codebase also commonly uses other Lodash functions like `cloneDeep`, `uniq`, `uniqBy`, `groupBy`, and `orderBy`. Prefer Lodash functions over native JavaScript implementations for better readability and consistency across the codebase.
Applied to files:
packages/core/src/external/ehr/shared.ts
🧬 Code graph analysis (3)
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (5)
packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts (1)
writeBackGroupedVitals
(11-48)packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
writeBackGroupedVitals
(31-37)WriteBackGroupedVitalsClientRequest
(29-29)isGroupedVitalsByCode
(70-72)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(7-36)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (4)
packages/core/src/external/ehr/command/write-back/condition.ts (2)
writeBackCondition
(19-25)WriteBackConditionClientRequest
(17-17)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(7-36)
packages/core/src/external/ehr/shared.ts (2)
packages/commonwell-sdk/src/models/document.ts (1)
Coding
(21-21)packages/core/src/fhir-deduplication/shared.ts (2)
UNK_CODE
(26-26)UNKNOWN_DISPLAY
(27-27)
⏰ 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). (2)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
🔇 Additional comments (7)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts (2)
15-21
: Department selection: confirm “first ID” is intended.Using
secondaryMappings.departmentIds[0]
may pick a non-default department. Verify mapping config guarantees index 0 is the intended write-back target, or select by an explicit default key.
27-32
: Athena condition status support: validate/normalize before send.Per prior learnings, only “relapse”/“recurrence” map reliably to Athena problem status. Ensure
condition
’s status is acceptable or normalized beforecreateProblem
, otherwise Athena may reject or misclassify.packages/core/src/external/ehr/shared.ts (3)
52-56
: Import updates look correct.Import path to
../../fhir-deduplication/shared
is accurate from this module; usingUNK_CODE
/UNKNOWN_DISPLAY
aligns with the new filtering util.
407-422
: Grouped types addition LGTM.
title?
onGroupedVitals
,GroupedVitalsByDate
, andGroupedObservation
shape are coherent with the grouped-vitals flow.
1053-1064
: Filter unknown codes/display: edge-safe and fine.Logic correctly guards missing fields and trims case-insensitively. No further changes needed.
packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts (2)
14-21
: Type guard on groupedVitals is appropriate.Using
isGroupedVitalsByCode
ensures Athena path receives code-grouped payloads.
28-34
: Department selection: confirm first-id assumption.Same concern as conditions path—ensure
[0]
is the intended default for vitals write-back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/external/ehr/elation/index.ts (1)
1178-1182
: Use strict equality per TS guidelines.Follow repo standards: prefer
===/!==
over==/!=
.- if (!resultStatus || resultStatus.trim().toLowerCase() != "final") { + if (!resultStatus || resultStatus.trim().toLowerCase() !== "final") {
🧹 Nitpick comments (2)
packages/core/src/external/ehr/elation/index.ts (2)
624-629
: Tighten flatMap result type.Return an array consistently to avoid union typing and improve readability.
- const vitals = observations.flatMap(observation => { - const formattedVital = this.formatGroupedVital(observation); - if (!formattedVital) return []; - return formattedVital; - }); + const vitals = observations.flatMap(observation => { + const formattedVital = this.formatGroupedVital(observation); + return formattedVital ? [formattedVital] : []; + });
533-534
: Typo in additionalInfo key: diagnostricReportId → diagnosticReportId.Fix the log/metadata field name.
- diagnostricReportId: diagnosticReport.id, + diagnosticReportId: diagnosticReport.id,
📜 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.
📒 Files selected for processing (1)
packages/core/src/external/ehr/elation/index.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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/ehr/elation/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/core/src/external/ehr/elation/index.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/core/src/external/ehr/elation/index.ts
🧠 Learnings (1)
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.
Applied to files:
packages/core/src/external/ehr/elation/index.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). (2)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/core/src/external/ehr/elation/index.ts (2)
81-81
: LGTM: import moved to shared aggregator.Importing
GroupedVitalsByDate
from../shared
keeps the type centralized and aligns with the recent shared abstractions.
81-81
: GroupedVitalsByDate export and imports verified
GroupedVitalsByDate
is exported from packages/core/src/external/ehr/shared.ts (line 413) and all usages (e.g. in elation/index.ts) import it from../shared
; no stale imports remain.
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
Outdated
Show resolved
Hide resolved
metriport/metriport-internal#1040 Ref: Signed-off-by: Alexey Sergeev <alexey@metriport.com>
metriport/metriport-internal#1040 Ref: Signed-off-by: Alexey Sergeev <alexey@metriport.com>
metriport/metriport-internal#1040 Ref: Signed-off-by: Alexey Sergeev <alexey@metriport.com>
0a0abfe
to
f84b52e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
118-133
: Bug: drops decimals and treats 0 as missing; also misses end-date fallbackReplace parseInt with parseFloat and use explicit null checks; use getObservationObservedDate for full fallback.
Apply:
- const observationValue = v.valueQuantity?.value ?? v.valueString; - const date = v.effectiveDateTime ?? v.effectivePeriod?.start; - if (!date || !observationValue) { + const observationValue = v.valueQuantity?.value ?? v.valueString; + const date = getObservationObservedDate(v); + if (!date || observationValue == null) { results.push({ title, mostRecentObservation: v }); return; } - const observationUnit = v.valueQuantity?.unit?.replace(/[{()}]/g, ""); - const observationPoint: GroupedObservation = { - rawVital: v, - date, - observation: - typeof observationValue === "number" ? observationValue : parseInt(observationValue), - ...(observationUnit && { unit: observationUnit }), - }; + const observationUnit = v.valueQuantity?.unit?.replace(/[{()}]/g, "")?.trim(); + const parsedValue = + typeof observationValue === "number" + ? observationValue + : parseFloat(String(observationValue)); + if (!Number.isFinite(parsedValue)) { + results.push({ title, mostRecentObservation: v }); + return; + } + const observationPoint: GroupedObservation = { + rawVital: v, + date, + observation: parsedValue, + ...(observationUnit && { unit: observationUnit }), + };
🧹 Nitpick comments (7)
packages/core/src/external/ehr/command/write-back/condition.ts (1)
5-6
: Wrap long imports to respect the 100-char max line length.
packages/core/src/external/ehr/command/write-back/condition.ts:5–6-import { writeBackCondition as writeBackConditionAthena } from "../../athenahealth/command/write-back/condition"; +import { + writeBackCondition as writeBackConditionAthena, +} from "../../athenahealth/command/write-back/condition"; -import { writeBackCondition as writeBackConditionElation } from "../../elation/command/write-back/condition"; +import { + writeBackCondition as writeBackConditionElation, +} from "../../elation/command/write-back/condition";packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (4)
105-144
: Prefer forEach/for..of over map for side-effectsmap’s return is unused here; forEach or a simple for..of is clearer.
Apply:
- observations.map(v => { + observations.forEach(v => { // ... - }); + });
125-132
: Tiny robustness: trim unit and keep only printable ASCIISome EHRs include stray whitespace or braces in units. You’re already stripping braces; trimming helps too (included in the earlier diff). Optionally, normalize multiple spaces.
98-143
: Potential conflation: grouping key is display title, not LOINCDifferent LOINC codes can share identical display text, merging unrelated series under one title. Consider grouping by LOINC when present and using the title just for labeling.
Would you like me to draft a minimal change that uses a grouping key of
${title}::${loincCode}
when a LOINC is available while keepingtitle
unchanged in the returned payload?Also applies to: 147-165
35-37
: Minor: avoidreturn await
Not harmful, but
return handler({ ...params })
is equivalent and avoids an extra tick.- return await handler({ ...params }); + return handler({ ...params });packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (2)
15-15
: Avoid implicit “first department” selectionIf multiple departmentIds are configured, defaulting to index 0 may attach to the wrong department. Prefer an explicit default or document the convention inline for maintainers.
Minimal clarification:
- const athenaDepartmentId = secondaryMappings.departmentIds[0]; + const { departmentIds } = secondaryMappings; + // NOTE: if multiple departmentIds are configured, we default to the first by convention. + const athenaDepartmentId = departmentIds[0];
16-21
: Enrich error context for triageInclude configured departmentIds in the error’s additionalInfo to speed up support/debugging.
- throw new BadRequestError("Department ID not found", undefined, { - ehr: EhrSources.athena, - practiceId, - }); + throw new BadRequestError("Department ID not found", undefined, { + ehr: EhrSources.athena, + practiceId, + departmentIds: secondaryMappings.departmentIds, + });
📜 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.
📒 Files selected for processing (11)
packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
(1 hunks)packages/core/src/external/ehr/command/write-back/condition.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
(3 hunks)packages/core/src/external/ehr/command/write-back/lab.ts
(2 hunks)packages/core/src/external/ehr/command/write-back/shared.ts
(3 hunks)packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
(1 hunks)packages/core/src/external/ehr/elation/index.ts
(1 hunks)packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
(2 hunks)packages/core/src/external/ehr/shared.ts
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/core/src/external/ehr/elation/index.ts
- packages/core/src/external/ehr/athenahealth/command/write-back/grouped-vitals.ts
- packages/core/src/external/ehr/elation/command/write-back/grouped-vitals.ts
- packages/core/src/external/ehr/shared.ts
- packages/core/src/external/ehr/athenahealth/command/write-back/condition.ts
- packages/core/src/external/ehr/command/write-back/lab.ts
- packages/core/src/external/ehr/command/write-back/shared.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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
🧠 Learnings (7)
📚 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/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-06-11T21:39:26.805Z
Learnt from: thomasyopes
PR: metriport/metriport#4000
File: packages/core/src/external/ehr/athenahealth/index.ts:504-507
Timestamp: 2025-06-11T21:39:26.805Z
Learning: In AthenaHealth write-back (`packages/core/src/external/ehr/athenahealth/index.ts`), only the condition statuses “relapse” and “recurrence” are currently mapped to AthenaHealth problem statuses (“CHRONIC”); other FHIR clinicalStatus values (e.g., “active”, “resolved”, “inactive”, “remission”) are not yet supported.
Applied to files:
packages/core/src/external/ehr/command/write-back/condition.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-08-25T17:29:25.387Z
Learnt from: thomasyopes
PR: metriport/metriport#4451
File: packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts:143-151
Timestamp: 2025-08-25T17:29:25.387Z
Learning: In packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts, the filterObservations function with vital.latestOnly=true is designed to keep only the most recent observation per LOINC code across all dates, not per date+LOINC pair. This behavior intentionally collapses multiple days into a single latest reading per code.
Applied to files:
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts
📚 Learning: 2025-08-12T17:28:06.150Z
Learnt from: thomasyopes
PR: metriport/metriport#4358
File: packages/core/src/external/ehr/command/write-back/shared.ts:21-21
Timestamp: 2025-08-12T17:28:06.150Z
Learning: In packages/core/src/external/ehr/command/write-back/shared.ts, the WriteBackResourceRequest.primaryResourceOrResources field deliberately uses the generic tuple [Date, Resource[]] instead of the more specific GroupedVitalsByDate type ([Date, Observation[]]) to keep the shared interface flexible for potentially grouping different resource types by date in the future.
Applied to files:
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
packages/core/src/external/ehr/command/write-back/grouped-vitals.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/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.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/ehr/command/write-back/grouped-vitals.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/ehr/command/write-back/grouped-vitals.ts
🧬 Code graph analysis (3)
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (5)
packages/core/src/external/ehr/command/write-back/lab.ts (2)
writeBackLab
(18-21)WriteBackLabClientRequest
(16-16)packages/core/src/external/ehr/api/get-secondary-mappings.ts (1)
getSecondaryMappings
(17-41)packages/shared/src/interface/external/ehr/athenahealth/cx-mapping.ts (1)
athenaSecondaryMappingsSchema
(4-13)packages/core/src/external/ehr/athenahealth/shared.ts (1)
createAthenaHealthClient
(7-36)packages/api/src/external/ehr/athenahealth/command/write-back/lab.ts (1)
writeLabToChart
(5-26)
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
getEhrGroupedVitals
(169-185)
packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
packages/core/src/external/ehr/shared.ts (8)
GroupedVitals
(407-411)GroupedVitalsByDate
(413-413)getObservationObservedDate
(708-714)formatDate
(105-111)GroupedObservation
(415-422)getValidCode
(1053-1064)handleTitleSpecialCases
(1066-1077)handleBloodPressureMapping
(1079-1129)packages/shared/src/interface/external/ehr/source.ts (1)
EhrSource
(11-11)packages/shared/src/common/date.ts (1)
buildDayjs
(100-102)
⏰ 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). (2)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
packages/core/src/external/ehr/command/write-back/condition.ts (1)
33-35
: Athena handler wiring — looks good; verify status mapping coverage.Dispatch registration is correct and consistent with the primary-code map. Please verify the Athena handler behavior for Condition.clinicalStatus beyond “relapse/recurrence” (per prior learning, others weren’t mapped). Add a test to ensure unsupported statuses are either mapped/normalized or rejected with a clear error.
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (2)
36-38
: Good import: centralized, EHR-aware groupingSwitching to getEhrGroupedVitals consolidates grouping logic and avoids local duplication.
654-657
: PrimaryResourceOrResources already supports EhrGroupedVitals The WriteBackResourceRequest.primaryResourceOrResources union includes EhrGroupedVitals, so dispatch is safe.packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (3)
45-47
: Athena write-back handler wiredAdding Athena to ehrWriteBackGroupedVitalsMap looks correct.
61-64
: Exported EHR list aligns with mapwriteBackGroupedVitalsEhrs matches the handlers (Athena, Elation).
74-96
: Date-grouping uses shared helpers and UTC—LGTMUsing formatDate + buildDayjs ensures consistent UTC dates.
packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts (2)
8-14
: LGTM: clear Athena lab write-back flowStraightforward mapping -> validate -> client -> submit, aligns with the write-back contract and style guidelines (async/await, types, no nulls).
27-33
: Retry exists in client — confirm axios timeouts & add idempotency
- The Athena client already uses the shared makeRequest which wraps axios.request with executeWithRetries (retries applied). (packages/core/src/external/ehr/shared.ts → makeRequest; packages/core/src/external/ehr/athenahealth/index.ts → createLabResultDocument / private makeRequest)
- Confirm the axios timeout configured for the Athena client (where the axios instance is created); if no sensible response/connection timeout is set, add one (must be < surrounding lambda timeout) or explicitly set retry-on-timeout policy. (packages/core/src/external/ehr/athenahealth/index.ts)
- Add idempotency/dedup for lab write-backs (e.g., stable dedupe key = hash(cxId + patientId + observation.id) persisted or sent as an idempotency token) to avoid duplicate lab results when jobs/webhooks are retried. (call site: packages/core/src/external/ehr/athenahealth/command/write-back/lab.ts)
metriport/metriport-internal#1040 Ref: Signed-off-by: Alexey Sergeev <alexey@metriport.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts (4)
4-6
: Prefer a more explicit constant name than “column”.Improves grep-ability and avoids ambiguity when multiple columns are involved.
-const column = "secondary_mappings"; +const secondaryMappingsColumn = "secondary_mappings"; @@ - tableName, - column, + tableName, + secondaryMappingsColumn, @@ - await queryInterface.removeColumn(tableName, column, { transaction }); + await queryInterface.removeColumn(tableName, secondaryMappingsColumn, { transaction });Also applies to: 11-12, 20-20
7-16
: Up migration is safe and transactional. Consider adding a DB-level column comment.Nullable JSONB with no default avoids table rewrite; good for production. Optional: add a COMMENT for future maintainers.
await queryInterface.addColumn( tableName, - column, + secondaryMappingsColumn, { type: DataTypes.JSONB, allowNull: true }, { transaction } ); + await queryInterface.sequelize.query( + "COMMENT ON COLUMN patient_mapping.secondary_mappings IS " + + "'Secondary EHR-specific mappings used by write-back flows (e.g., Athena).'", + { transaction } + );
18-22
: Optional guard in down() to be resilient if partially applied.Not required, but avoids failures if the column was already removed in a partial rollback.
-export const down: Migration = ({ context: queryInterface }) => { - return queryInterface.sequelize.transaction(async transaction => { - await queryInterface.removeColumn(tableName, column, { transaction }); - }); -}; +export const down: Migration = ({ context: queryInterface }) => { + return queryInterface.sequelize.transaction(async transaction => { + const table = await queryInterface.describeTable(tableName); + if (table.secondary_mappings) { + await queryInterface.removeColumn(tableName, secondaryMappingsColumn, { transaction }); + } + }); +};
4-16
: Future-readiness: index only if you’ll query inside JSON.If lookups will filter by specific JSON paths (e.g., departmentId inside secondary_mappings), consider a GIN index or a functional index later. Skip for now if reads are by PK and the JSON is only fetched.
📜 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.
📒 Files selected for processing (1)
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.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
Useconst
whenever possible
Useasync/await
instead of.then()
Naming: classes, enums: PascalCase
Naming: constants, variables, functions: camelCase
Naming: file names: kebab-case
Naming: Don’t use negative names, likenotEnabled
, preferisDisabled
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 ofin
- i.e.,if (data.link)
notif ('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/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use types whenever possible
Files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.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
, preferisDisabled
- For numeric values, if the type doesn’t convey the unit, add the unit to the name
- Typescript
- Use types
- Prefer
const
instead oflet
- Avoid
any
and casting fromany
to other types- Type predicates: only applicable to narrow down the type, not to force a complete type conversion
- Prefer deconstructing parameters for functions instead of multiple parameters that might be of
the same type- Don’t use
null
inside the app, only on code interacting with external interfaces/services,
like DB and HTTP; convert toundefined
before sending inwards into the code- Use
async/await
instead of.then()
- Use the strict equality operator
===
, don’t use abstract equality operator==
- When calling a Promise-returning function asynchronously (i.e., not awaiting), use
.catch()
to
handle errors (seeprocessAsyncError
andemptyFunction
depending on the case)- Date and Time
- Always use
buildDayjs()
to createdayjs
instances- Prefer
dayjs.duration(...)
to create duration consts and keep them asduration
- Prefer Nullish Coalesce (??) than the OR operator (||) to provide a default value
- Avoid creating arrow functions
- U...
Files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
🧠 Learnings (6)
📚 Learning: 2025-04-26T22:17:45.248Z
Learnt from: leite08
PR: metriport/metriport#3412
File: packages/api/src/models/medical/patient-import-mapping.ts:16-41
Timestamp: 2025-04-26T22:17:45.248Z
Learning: The patient_import_mapping table already has a unique constraint on the fields "job_id", "row_number", and "data_pipeline_request_id" defined in the migration file 2025-04-24_02_create-patient_import_mapping-table.ts, which ensures data integrity.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
📚 Learning: 2025-06-18T08:03:50.674Z
Learnt from: RamilGaripov
PR: metriport/metriport#3962
File: packages/api/src/models/medical/patient-cohort.ts:15-21
Timestamp: 2025-06-18T08:03:50.674Z
Learning: In the Metriport codebase, Sequelize models typically don't define `allowNull: false` or `references` metadata for foreign key columns. These constraints are handled exclusively in migration files, keeping models simpler and avoiding duplication. This is a consistent pattern across recent models like CohortModel, CoverageEnhancementModel, DocRefMappingModel, etc. The migration files properly define all FK constraints, NOT NULL requirements, and cascade behaviors.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
📚 Learning: 2025-06-18T21:05:22.256Z
Learnt from: RamilGaripov
PR: metriport/metriport#3976
File: packages/api/src/routes/medical/patient.ts:541-543
Timestamp: 2025-06-18T21:05:22.256Z
Learning: In packages/api/src/routes/medical/patient.ts, inline schema definitions like cohortIdSchema are acceptable and don't need to be moved to separate schema files when the user prefers to keep them inline.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
📚 Learning: 2025-04-28T22:58:19.669Z
Learnt from: leite08
PR: metriport/metriport#3749
File: packages/api/src/models/medical/patient-import-mapping.ts:16-41
Timestamp: 2025-04-28T22:58:19.669Z
Learning: For Sequelize models in this codebase, field constraints (such as `allowNull: false`) and field mappings (such as `field: "column_name"`) are defined in the migrations, not in the model definitions themselves.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
📚 Learning: 2025-04-07T17:32:22.935Z
Learnt from: leite08
PR: metriport/metriport#3624
File: packages/api/src/command/medical/patient/get-patient.ts:90-97
Timestamp: 2025-04-07T17:32:22.935Z
Learning: When dealing with raw SQL queries in Sequelize that return complex data structures (especially with joins and JSON aggregation), using `any` type assertions may be necessary since TypeScript cannot infer the types. Using constants for column aliases helps minimize maintenance issues in these cases.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts
📚 Learning: 2025-06-18T08:03:50.674Z
Learnt from: RamilGaripov
PR: metriport/metriport#3962
File: packages/api/src/models/medical/patient-cohort.ts:15-21
Timestamp: 2025-06-18T08:03:50.674Z
Learning: In the Metriport codebase, Sequelize models typically don't define `allowNull: false` or `references` metadata for foreign key columns. These constraints are handled exclusively in migration files, keeping models simpler and avoiding duplication. This is a consistent pattern across recent models like CohortModel, CoverageEnhancementModel, DocRefMappingModel, etc.
Applied to files:
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.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)
🔇 Additional comments (1)
packages/api/src/sequelize/migrations/2025-08-29_00_add-secondary-mappings-to-patient-mapping.ts (1)
1-2
: Imports and typing look correct.Consistent with prior migrations; follows the pattern noted in the learnings (constraints/metadata in migrations).
metriport/metriport-internal#1040 Ref: Signed-off-by: Alexey Sergeev <alexey@metriport.com>
...rc/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
Outdated
Show resolved
Hide resolved
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
...rc/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
Show resolved
Hide resolved
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
packages/core/src/external/ehr/athenahealth/index.ts (1)
1681-1693
: By-ID short-circuit now forwards resourceId — good fix.Prevents cache-key collisions for by-id lookups. Consider Set-based predicate here too for parity.
- if (resourceType === "MedicationStatement") { + if (writeBackOnlyResources.has(resourceType as ResourceType)) { return await fetchEhrBundleUsingCache({ ehr: EhrSources.athena, cxId, metriportPatientId, ehrPatientId: athenaPatientId, resourceType, resourceId, fetchResourcesFromEhr: () => Promise.resolve([]), useCachedBundle, }); }packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (2)
149-151
: Keep sort logic as-is for parity with internal repo.Per prior guidance, do not change this comparator.
118-133
: Vitals parsing bug: decimals dropped; zero treated as missing.parseInt truncates; falsy check excludes 0. This corrupts values and skips valid observations.
Apply:
- const observationValue = v.valueQuantity?.value ?? v.valueString; + const observationValue = v.valueQuantity?.value ?? v.valueString; const date = v.effectiveDateTime ?? v.effectivePeriod?.start; - if (!date || !observationValue) { + if (!date || observationValue == null) { results.push({ title, mostRecentObservation: v }); return; } - - const observationPoint: GroupedObservation = { - rawVital: v, - date, - observation: - typeof observationValue === "number" ? observationValue : parseInt(observationValue), - // TODO: Make sure all data points have the same unit - unit: v.valueQuantity?.unit?.replace(/[{()}]/g, ""), - }; + const parsedValue = + typeof observationValue === "number" + ? observationValue + : parseFloat(String(observationValue)); + if (!Number.isFinite(parsedValue)) { + results.push({ title, mostRecentObservation: v }); + return; + } + const observationPoint: GroupedObservation = { + rawVital: v, + date, + observation: parsedValue, + // TODO: Make sure all data points have the same unit + unit: v.valueQuantity?.unit?.replace(/[{()}]/g, ""), + };
🧹 Nitpick comments (11)
packages/core/src/external/ehr/athenahealth/index.ts (4)
232-239
: Centralize write-back–only resources to avoid stringly-typed checks.Define a Set and reuse it across scopes and short-circuits, instead of hardcoding "MedicationStatement" in multiple places.
Apply the Set definition near resource lists:
export const supportedAthenaHealthResources: ResourceType[] = [ "AllergyIntolerance", "Condition", "DiagnosticReport", "Immunization", "MedicationRequest", - "MedicationStatement", // NOT REALLY SUPPORTED + "MedicationStatement", // write-back only (no read support) "Observation", "Procedure", "Encounter", ]; + +// Resources we allow for write-back flows but do not fetch from Athena +const writeBackOnlyResources = new Set<ResourceType>(["MedicationStatement"]);
254-258
: Derive OAuth scopes from the Set (avoid drift).Use the writeBackOnlyResources Set so the filtering logic stays consistent with future additions.
-export const scopes = [ - ...supportedAthenaHealthResources, - ...supportedAthenaHealthReferenceResources, -].filter(resource => resource !== "MedicationStatement"); +export const scopes = [ + ...supportedAthenaHealthResources, + ...supportedAthenaHealthReferenceResources, +].filter(resource => !writeBackOnlyResources.has(resource));
436-476
: Include query params in additionalInfo for better observability.Helps debug issues (e.g., department context).
- const additionalInfo = { cxId, practiceId: this.practiceId, patientId }; + const additionalInfo = { + cxId, + practiceId: this.practiceId, + patientId, + queryParams: queryParams.toString(), + };
1558-1569
: Short-circuit for write-back–only types is correct; reuse the Set.This returns a cached empty bundle as intended; switch to Set-based check for consistency.
- if (resourceType === "MedicationStatement") { + if (writeBackOnlyResources.has(resourceType as ResourceType)) { return await fetchEhrBundleUsingCache({ ehr: EhrSources.athena, cxId, metriportPatientId, ehrPatientId: athenaPatientId, resourceType, fetchResourcesFromEhr: () => Promise.resolve([]), useCachedBundle, }); }packages/core/src/external/ehr/command/write-back/shared.ts (2)
95-117
: Lab-panel: empty secondary array passes validation. Confirm intent.Array.every returns true for [], so an empty secondaries list will pass and write back with 0 observations.
If you want to require at least one Observation:
- if ( - !params.secondaryResourceOrResources || - !params.secondaryResourceOrResources.every(isObservation) - ) { + if ( + !Array.isArray(params.secondaryResourceOrResources) || + params.secondaryResourceOrResources.length < 1 || + !params.secondaryResourceOrResources.every(isObservation) + ) {
123-162
: Medication-statement: unify error text and single guard for “no medications”.Minor DX: duplicate error and a follow-up empty check.
Apply:
- if ( - !params.secondaryResourceOrResources || - !params.secondaryResourceOrResources.every(isMedication) - ) { - throw new BadRequestError( - "MedicationStatement write back requires secondary resources", - undefined, - { - ehr: params.ehr, - writeBackResource: params.writeBackResource, - } - ); - } - const medication = params.secondaryResourceOrResources[0]; - if (!medication) { + if ( + !Array.isArray(params.secondaryResourceOrResources) || + params.secondaryResourceOrResources.length < 1 || + !params.secondaryResourceOrResources.every(isMedication) + ) { throw new BadRequestError( - "MedicationStatement write back requires secondary resources", + "MedicationStatement write back requires at least one Medication in secondary resources", undefined, { ehr: params.ehr, writeBackResource: params.writeBackResource, } ); } + const medication = params.secondaryResourceOrResources[0];packages/core/src/external/ehr/command/write-back/grouped-vitals.ts (1)
105-116
: Iteration nit: use forEach instead of map when not using return value.Slight readability win, no behavior change.
- observations.map(v => { + observations.forEach(v => { ... - }); + });Also applies to: 143-144
packages/core/src/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts (1)
304-330
: Hydration is O(M×S); index statements by medication for O(M+S).Improves scalability without changing behavior.
Apply:
-async function hydrateMedications({ - medications, - statements, -}: { - medications: Medication[]; - statements: MedicationStatement[]; -}): Promise<{ medication: Medication; statements: MedicationStatement[] }[]> { - const hydratedMetriportOnlyResources: { - medication: Medication; - statements: MedicationStatement[]; - }[] = []; - for (const medication of medications) { - if (!medication.id) { - hydratedMetriportOnlyResources.push({ medication, statements: [] }); - continue; - } - const hydratedStatements: MedicationStatement[] = []; - for (const statement of statements) { - if (!statement.medicationReference || !statement.medicationReference.reference) continue; - const ref = statement.medicationReference.reference; - if (ref !== `Medication/${medication.id}`) continue; - hydratedStatements.push(statement); - } - hydratedMetriportOnlyResources.push({ medication, statements: hydratedStatements }); - } - return hydratedMetriportOnlyResources; -} +async function hydrateMedications({ + medications, + statements, +}: { + medications: Medication[]; + statements: MedicationStatement[]; +}): Promise<{ medication: Medication; statements: MedicationStatement[] }[]> { + const byMedicationId = new Map<string, MedicationStatement[]>(); + for (const s of statements) { + const ref = s.medicationReference?.reference; // "Medication/{id}" + if (!ref?.startsWith("Medication/")) continue; + const id = ref.slice("Medication/".length); + if (!id) continue; + const arr = byMedicationId.get(id) ?? []; + arr.push(s); + byMedicationId.set(id, arr); + } + return medications.map(m => ({ + medication: m, + statements: m.id ? byMedicationId.get(m.id) ?? [] : [], + })); +}packages/api/src/external/ehr/athenahealth/command/update-patient-mapping-department-id.ts (3)
5-10
: Param type is clear; consider immutability.
Optional: mark fields as readonly to signal intent and help TS catch accidental mutation.-export type UpdateAthenaPatientMappingDepartmentIdParams = { - cxId: string; - athenaPatientId: string; - athenaDepartmentId: string; -}; +export type UpdateAthenaPatientMappingDepartmentIdParams = { + readonly cxId: string; + readonly athenaPatientId: string; + readonly athenaDepartmentId: string; +};
11-15
: Doc should clarify persistence vs. context-bound department usage.
Per retrieved learnings, departmentId is context-sensitive in Athena (patient can be opened from multiple departments). Please state in the docstring whether persisting departmentId in secondaryMappings is the intended long-term model, or if this is a transitional mechanism for write-back only.
21-26
: Tighten typing by specializing the generic to Athena’s mappings.
Without binding T, parsedSecondaryMappings may be inferred as a broad union, weakening type-safety around departmentId. Import the Athena-specific type and specialize the call.-import { EhrSources } from "@metriport/shared/interface/external/ehr/source"; +import { EhrSources } from "@metriport/shared/interface/external/ehr/source"; +import { AthenaPatientMappingSecondaryMappings } from "../shared"; @@ - const { parsedSecondaryMappings, patientMapping } = - await getPatientMappingAndParsedSecondaryMappings({ + const { parsedSecondaryMappings, patientMapping } = + await getPatientMappingAndParsedSecondaryMappings<AthenaPatientMappingSecondaryMappings>({ ehr: EhrSources.athena, cxId, ehrPatientId: athenaPatientId, });
packages/api/src/external/ehr/athenahealth/command/update-patient-mapping-department-id.ts
Show resolved
Hide resolved
...rc/external/ehr/job/bundle/write-back-bundles/ehr-write-back-resource-diff-bundles-direct.ts
Outdated
Show resolved
Hide resolved
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you could just fix that one tiny typo (add an "e" to Statment
), everything else looks good to go! 🚀
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
…-athena-auto-writeback Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
Ref: ENG-585 Ref: #1040 Signed-off-by: Thomas Yopes <thomasyopes@Mac.attlocal.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving again to note that I have reviewed all the changes up to this point.
Issues:
Dependencies
Description
Testing
Release Plan
update patient_mapping set secondary_mappings = '{}'::jsonb;
Summary by CodeRabbit
New Features
Chores
Refactor
Tests