-
Notifications
You must be signed in to change notification settings - Fork 72
ENG-245 Check DOB length + Update cloud factory pattern #4034
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
base: develop
Are you sure you want to change the base?
Conversation
Ref eng-36 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
WalkthroughThis change refactors the patient import workflow by moving core logic from class-based handlers to command-style functions, updates type signatures for better CSV data handling, improves date-of-birth validation, introduces new error handling and reporting structures, and consolidates AWS infrastructure setup using new constructs. Additional comprehensive tests and schema updates are included. Changes
Sequence Diagram(s)Patient Import Parse and Create (New Command Flow)sequenceDiagram
participant LambdaHandler
participant ParseCommand
participant S3
participant CreateCommand
participant API
LambdaHandler->>ParseCommand: processJobParse({cxId, jobId, ...})
ParseCommand->>S3: Download CSV
ParseCommand->>ParseCommand: Partition patients (success/fail)
ParseCommand->>API: updateJobAtApi (status: processing)
alt Not dry run
loop For each chunk of patients
ParseCommand->>CreateCommand: processPatientCreate({cxId, jobId, ...})
CreateCommand->>API: updatePatientRecord (processing)
CreateCommand->>API: createPatient
CreateCommand->>API: updatePatientRecord (with patientId)
CreateCommand->>API: createPatientMapping
CreateCommand->>API: processPatientQuery (if next step)
end
end
ParseCommand->>API: updateJobAtApi (with counts)
Patient Import Result (New Command Flow)sequenceDiagram
participant LambdaHandler
participant ResultCommand
participant S3
participant API
LambdaHandler->>ResultCommand: processPatientResult({cxId, jobId, ...})
ResultCommand->>S3: List patient record keys
loop For each record
ResultCommand->>S3: Load patient record JSON
ResultCommand->>ResultCommand: Map to result entry
end
ResultCommand->>S3: Store results CSV
ResultCommand->>API: updateJobAtApi (status: complete or failed)
Possibly related PRs
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error code ERR_SSL_WRONG_VERSION_NUMBER ✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Ref eng-36 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-438 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.com>
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.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.
indicate individual fields that are missing instead of generic message
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.
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.
Main logic moved to packages/core/src/command/patient-import/steps/create/patient-import-create-command.ts
. Left only the logic to call it and swallow errors.
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.
Cleaner factory, the implementation knows about its defaults
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.
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.
Main logic moved to packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts
. Left only the logic to call it and swallow errors.
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.
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.
Main logic moved to packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts
. Left only the logic to call it and swallow errors.
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.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.
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.
Main logic moved to packages/core/src/command/patient-import/steps/result/patient-import-result-command.ts
. Left only the logic to call it and swallow errors.
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.
Most of the code removed went to packages/infra/lib/shared/consolidated-search-construct.ts
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.
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: 5
🔭 Outside diff range comments (1)
packages/shared/src/domain/patient/patient-import/schemas.ts (1)
29-34
: 🛠️ Refactor suggestionSchema now allows empty objects – refine to require at least one prop
Since
status
is now optional the following passes validation but is useless:updateJobSchema.parse({})Guard it at the schema level:
export const updateJobSchema = z .object({ status: z.enum(patientImportJobStatus).optional(), total: z.number().optional(), failed: z.number().optional(), forceStatusUpdate: z.boolean().optional(), -}); +}) +.refine( + d => d.status !== undefined || d.total !== undefined || d.failed !== undefined, + { message: "At least one of status, total or failed must be provided" }, +);Pushing the constraint into zod removes the need for scattered runtime checks.
♻️ Duplicate comments (3)
packages/core/src/command/patient-import/steps/create/patient-import-create-cloud.ts (1)
8-11
: Same DI concern as in patient-import-query-cloudInline creation of
SQSClient
limits testability; consider accepting it as a constructor argument with a sensible default.packages/core/src/command/patient-import/steps/query/patient-import-query-local.ts (1)
43-45
: Same silent-failure pattern as other Local wrappers – see earlier comment; considerations there apply here as well.packages/core/src/command/patient-import/steps/create/patient-import-create-local.ts (1)
41-43
: Same silent-failure pattern as other Local wrappers – see earlier comment; considerations there apply here as well.
🧹 Nitpick comments (28)
packages/core/src/command/patient-import/csv/store-results.ts (1)
24-26
:reasonForDev
is never consumed – dead field or missing plumbing?
ResultEntry
now carries bothreasonForCx
andreasonForDev
, but only the customer-facing reason is ever read in this module. If developer reasons are needed elsewhere, wire them through; otherwise drop the unused property to avoid type-rot and larger payloads.packages/lambdas/src/patient-import-parse.ts (1)
30-34
: Explicitly passnext
/result
when diverging from defaults
processJobParse
allows overridingnext
andresult
. If the lambda ever needs custom wiring (e.g., for A/B or test doubles) the current implicit defaults hide this option. Consider passing them explicitly or documenting why defaults are always sufficient.packages/core/src/command/patient-import/steps/query/patient-import-query-factory.ts (1)
7-11
: Constructor now parameter-less – re-check testability & env flexibilityThe factory no longer injects
patientImportBucket
/queueUrl
.
Great for brevity, but:
- Hard-wiring configs inside the classes makes unit testing harder (no DI).
- Alternate environments (staging with different buckets) now require code changes instead of config.
If the classes read from
Config
internally, consider still accepting overrides via constructor for flexibility.packages/lambdas/src/patient-import-result.ts (1)
29-34
: Variable name doesn’t reflect the new command
processJobResultCmd
still carries the old “job-result” wording, which can be misleading now that we’re invokingprocessPatientResult
. Renaming improves clarity and greppability.- const processJobResultCmd: ProcessPatientResultCommandRequest = { + const processPatientResultCmd: ProcessPatientResultCommandRequest = { cxId, jobId, patientImportBucket, }; - await processPatientResult(processJobResultCmd); + await processPatientResult(processPatientResultCmd);packages/lambdas/src/patient-import-query.ts (2)
26-29
: Specify radix inparseInt
to avoid accidental octal parsing
parseInt(waitTimeInMillisRaw)
will treat leading zeros as octal on some runtimes. Make the intent explicit:-const waitTimeInMillis = parseInt(waitTimeInMillisRaw); +const waitTimeInMillis = parseInt(waitTimeInMillisRaw, 10);
38-50
: Replaceconsole.log
with the standard loggerCoding-guidelines forbid
console.log
outside utils/infra/shared packages. UseprefixedLog
(already available in this scope) orout().log
to remain consistent and keep structured logging.- console.log(`Running with unparsed body: ${message.body}`); + prefixedLog(lambdaName)(`Running with unparsed body: ${message.body}`); @@ - console.log(`Done local duration: ${finishedAt - startedAt}ms`); + prefixedLog(lambdaName)(`Done local duration: ${finishedAt - startedAt}ms`);packages/lambdas/src/patient-import-create.ts (1)
38-39
: Replaceconsole.log
with the project’s structured loggerCoding guidelines restrict
console.*
usage outside utils/infra/shared.
You already haveprefixedLog
– use it (orout().log
) instead to keep log
formatting consistent and avoid missing correlation IDs in production logs.Also applies to: 74-75
packages/shared/src/common/date.ts (1)
28-28
: Redundant condition
!date
is checked twice in the same clause; remove the duplication to keep the guard concise.packages/shared/src/common/__tests__/date.test.ts (2)
173-179
: Duplicate test descriptionThere are two
it("returns true when date is valid ISO datetime", …)
blocks. Rename one of
them to avoid confusion when reading test output.
128-128
: Remove stray console.log
console.log(result);
will pollute CI logs and is not needed for assertions.packages/core/src/command/patient-import/csv/__tests__/convert-patient.test.ts (1)
165-169
: Spy does not stub return value
jest.spyOn(normalizeDobFile, "normalizeDobSafe")
only records calls; if
normalizeDobSafe
throws the same failure will propagate into the test.
Be explicit (mockReturnValueOnce(...)
) where the function’s behaviour matters to keep
tests deterministic.packages/core/src/command/patient-import/steps/parse/patient-import-parse-cloud.ts (1)
7-10
: Constructor default creates a new client per instanceInlining
makeLambdaClient
in the parameter default means every new
PatientImportParseCloud()
instantiation spins up an AWS SDK client.
If these objects are short-lived, this may create unnecessary sockets.
Consider injecting a shared client from the caller or making the default a
module-level singleton.packages/core/src/command/patient-import/steps/result/patient-import-result-cloud.ts (1)
7-10
: Same client-instantiation concern as parse stepSee previous comment – apply the same singleton / DI pattern here for consistency.
packages/core/src/command/patient-import/steps/query/patient-import-query-cloud.ts (1)
7-10
: Prefer dependency injection over inline instantiationCreating a fresh
SQSClient
inside the constructor ties the class to AWS at instantiation time and makes unit-testing harder. Passing an already-built client (with the same default) keeps the current DX while allowing callers to inject stubs/mocks when needed.packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts (2)
52-53
: Guard against negative wait times
if (waitTimeAtTheEndInMillis > 0) …
silently ignores negatives; clamping would be clearer:-if (waitTimeAtTheEndInMillis > 0) await sleep(waitTimeAtTheEndInMillis); +await sleep(Math.max(0, waitTimeAtTheEndInMillis));
13-16
: Avoid extending DayJS plugin on every import
dayjs.extend(duration);
executes each time this module is required. Moving the extension to a central bootstrap file prevents accidental multiple calls and keeps plugin setup in one place.packages/api/src/routes/internal/medical/patient-import.ts (1)
302-304
: Make the validation message explicitIncluding the hard limit helps the caller immediately understand why the request was rejected:
-throw new BadRequestError(`Debug mode is not supported for jobs with many patients`); +throw new BadRequestError( + `Debug mode is limited to ${maxPatientsOnDebugGetBulk} patients or fewer` +);packages/core/src/command/patient-import/steps/create/patient-import-create-command.ts (1)
59-65
: Update only the fields that changed to avoid stale writesRe-passing the entire
patientRecord
object can unintentionally overwrite fields changed elsewhere after the first write. Prefer a minimal patch:- await Promise.all([ - updatePatientRecord({ - ...patientRecord, - patientId, - bucketName: patientImportBucket, - }), + await Promise.all([ + updatePatientRecord({ + cxId, + jobId, + rowNumber, + patientId, + bucketName: patientImportBucket, + }),This keeps the write explicit and decoupled from the record’s full interface.
packages/core/src/command/patient-import/steps/result/patient-import-result-command.ts (4)
13-13
: Remove unuseddayjs.duration
plugin import
dayjs.extend(duration);
is never referenced afterwards, so the plugin isn’t needed here and can be deleted to keep dependencies lean.
66-71
: Shared array mutation inside concurrent loop can cause unpredictable ordering
records.push(patientRecord)
from multiple async tasks means the final array order depends on fetch-completion order, not the original CSV order.
If consumer code relies on row sequence, preserve determinism by either:- records.push(patientRecord); + records[patientRecord.rowNumber - 1] = patientRecord;or sort afterwards.
60-75
: Consider streaming to avoid loading all patient records into memoryFor large imports every record is held twice (raw string + parsed object) before being written, which can exhaust Lambda memory.
Mapping each record directly to aResultEntry
and streamingstoreResults
chunk-by-chunk would keep memory constant.
90-93
: Wrap JSON parse errors with contextIf a single record file is malformed, the entire job fails without indicating which key was bad. Catch here, add the key to the error message, and rethrow.
packages/core/src/command/patient-import/steps/result/patient-import-result-local.ts (1)
26-28
: Swallowing the command error hides failures from callersWhile mimicking async cloud behaviour, any local invoker now has no way to know the job failed. Consider returning a boolean or logging the command’s updated status so upstream tests can assert success/failure.
packages/core/src/command/patient-import/csv/convert-patient.ts (2)
45-47
: Redundant “missing first/last name” checks
normalizeName
already throws for empty values, so the extraif (!firstName)
/if (!lastName)
blocks never fire. Remove them to simplify control flow.Also applies to: 53-55
76-81
: Duplicate address presence validationYou push an
"Missing address"
error at lines 78-81 and immediately repeat a generic check later (100-103). Drop one to avoid confusing, duplicated error messages.Also applies to: 100-103
packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts (2)
53-53
: Drop the useless ternary inpartition
p => p.status !== "failed"
is already a boolean – the? true : false
adds noise without value.🧰 Tools
🪛 Biome (1.9.4)
[error] 53-53: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with(lint/complexity/noUselessTernary)
71-74
:Promise.allSettled
+ internaltry/catch
is redundantBecause every mapped promise already catches its own error and never rejects, the outer
Promise.allSettled
provides no extra value.
A plainPromise.all
(or evenfor … of
with awaits) will be cheaper and a bit clearer.packages/infra/lib/lambdas-nested-stack.ts (1)
676-710
: Consider renaming thestack
parameter toscope
for clarity.The parameter is used as the scope for the CDK construct, which is the standard pattern. Renaming it to
scope
would better align with CDK conventions and improve code readability.- private setupConsolidatedSearchConstruct(ownProps: { - stack: Stack; + private setupConsolidatedSearchConstruct(ownProps: { + scope: Construct; vpc: ec2.IVpc; config: EnvConfig; lambdaLayers: LambdaLayers; secrets: Secrets; medicalDocumentsBucket: s3.Bucket; featureFlagsTable: dynamodb.Table; openSearch: OpenSearchConfigForLambdas; }): ConsolidatedSearchConstruct { const { - stack, + scope, vpc, config, lambdaLayers, secrets, medicalDocumentsBucket, featureFlagsTable, openSearch, } = ownProps; const consolidatedSearchConstruct = new ConsolidatedSearchConstruct( - stack, + scope, "ConsolidatedSearchConstruct", { config, vpc, lambdaLayers, secrets, medicalDocumentsBucket, featureFlagsTable, openSearch, } ); return consolidatedSearchConstruct; }Also update the call site on line 176:
- stack: this, + scope: this,And remove the
Stack
import on line 1 since it's no longer needed (you can importConstruct
from 'constructs' if needed for the type).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
packages/api/src/command/medical/patient/shared.ts
(2 hunks)packages/api/src/routes/internal/medical/patient-import.ts
(6 hunks)packages/core/src/command/patient-import/api/update-job-status.ts
(1 hunks)packages/core/src/command/patient-import/csv/__tests__/convert-patient.test.ts
(1 hunks)packages/core/src/command/patient-import/csv/address.ts
(2 hunks)packages/core/src/command/patient-import/csv/contact.ts
(2 hunks)packages/core/src/command/patient-import/csv/convert-patient.ts
(5 hunks)packages/core/src/command/patient-import/csv/store-results.ts
(2 hunks)packages/core/src/command/patient-import/patient-or-record-failed.ts
(1 hunks)packages/core/src/command/patient-import/steps/create/patient-import-create-cloud.ts
(2 hunks)packages/core/src/command/patient-import/steps/create/patient-import-create-command.ts
(1 hunks)packages/core/src/command/patient-import/steps/create/patient-import-create-factory.ts
(1 hunks)packages/core/src/command/patient-import/steps/create/patient-import-create-local.ts
(2 hunks)packages/core/src/command/patient-import/steps/parse/patient-import-parse-cloud.ts
(1 hunks)packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts
(1 hunks)packages/core/src/command/patient-import/steps/parse/patient-import-parse-factory.ts
(1 hunks)packages/core/src/command/patient-import/steps/parse/patient-import-parse-local.ts
(2 hunks)packages/core/src/command/patient-import/steps/query/patient-import-query-cloud.ts
(2 hunks)packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts
(1 hunks)packages/core/src/command/patient-import/steps/query/patient-import-query-factory.ts
(1 hunks)packages/core/src/command/patient-import/steps/query/patient-import-query-local.ts
(2 hunks)packages/core/src/command/patient-import/steps/result/patient-import-result-cloud.ts
(2 hunks)packages/core/src/command/patient-import/steps/result/patient-import-result-command.ts
(1 hunks)packages/core/src/command/patient-import/steps/result/patient-import-result-factory.ts
(1 hunks)packages/core/src/command/patient-import/steps/result/patient-import-result-local.ts
(2 hunks)packages/infra/lib/lambdas-nested-stack.ts
(4 hunks)packages/infra/lib/shared/consolidated-search-construct.ts
(1 hunks)packages/lambdas/src/patient-import-create.ts
(2 hunks)packages/lambdas/src/patient-import-parse.ts
(2 hunks)packages/lambdas/src/patient-import-query.ts
(2 hunks)packages/lambdas/src/patient-import-result.ts
(2 hunks)packages/shared/src/common/__tests__/date.test.ts
(3 hunks)packages/shared/src/common/date.ts
(1 hunks)packages/shared/src/domain/dob.ts
(0 hunks)packages/shared/src/domain/patient/patient-import/schemas.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- packages/shared/src/domain/dob.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/lambdas/src/patient-import-query.ts
packages/lambdas/src/patient-import-parse.ts
packages/core/src/command/patient-import/csv/contact.ts
packages/lambdas/src/patient-import-result.ts
packages/shared/src/domain/patient/patient-import/schemas.ts
packages/core/src/command/patient-import/csv/store-results.ts
packages/core/src/command/patient-import/steps/parse/patient-import-parse-factory.ts
packages/api/src/command/medical/patient/shared.ts
packages/core/src/command/patient-import/steps/query/patient-import-query-factory.ts
packages/core/src/command/patient-import/steps/result/patient-import-result-factory.ts
packages/core/src/command/patient-import/steps/create/patient-import-create-factory.ts
packages/core/src/command/patient-import/csv/__tests__/convert-patient.test.ts
packages/core/src/command/patient-import/api/update-job-status.ts
packages/shared/src/common/__tests__/date.test.ts
packages/core/src/command/patient-import/steps/parse/patient-import-parse-cloud.ts
packages/lambdas/src/patient-import-create.ts
packages/core/src/command/patient-import/steps/result/patient-import-result-local.ts
packages/core/src/command/patient-import/steps/result/patient-import-result-cloud.ts
packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts
packages/core/src/command/patient-import/csv/address.ts
packages/core/src/command/patient-import/steps/create/patient-import-create-cloud.ts
packages/core/src/command/patient-import/patient-or-record-failed.ts
packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts
packages/core/src/command/patient-import/steps/create/patient-import-create-command.ts
packages/core/src/command/patient-import/steps/query/patient-import-query-local.ts
packages/core/src/command/patient-import/csv/convert-patient.ts
packages/core/src/command/patient-import/steps/parse/patient-import-parse-local.ts
packages/core/src/command/patient-import/steps/query/patient-import-query-cloud.ts
packages/shared/src/common/date.ts
packages/api/src/routes/internal/medical/patient-import.ts
packages/infra/lib/shared/consolidated-search-construct.ts
packages/core/src/command/patient-import/steps/create/patient-import-create-local.ts
packages/infra/lib/lambdas-nested-stack.ts
packages/core/src/command/patient-import/steps/result/patient-import-result-command.ts
🧠 Learnings (2)
packages/api/src/command/medical/patient/shared.ts (1)
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/api/src/routes/internal/medical/patient-consolidated.ts:141-174
Timestamp: 2025-05-20T21:26:26.804Z
Learning: The functionality introduced in packages/api/src/routes/internal/medical/patient-consolidated.ts is planned to be refactored in downstream PR #3857, including improvements to error handling and validation.
packages/api/src/routes/internal/medical/patient-import.ts (1)
Learnt from: leite08
PR: metriport/metriport#3814
File: packages/api/src/routes/internal/medical/patient-consolidated.ts:141-174
Timestamp: 2025-05-20T21:26:26.804Z
Learning: The functionality introduced in packages/api/src/routes/internal/medical/patient-consolidated.ts is planned to be refactored in downstream PR #3857, including improvements to error handling and validation.
🧬 Code Graph Analysis (14)
packages/lambdas/src/patient-import-query.ts (1)
packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts (1)
ProcessPatientQueryCommandRequest
(17-20)
packages/lambdas/src/patient-import-parse.ts (2)
packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts (2)
ProcessJobParseCommandRequest
(23-27)processJobParse
(29-131)packages/core/src/command/patient-import/steps/parse/patient-import-parse-local.ts (1)
processJobParse
(10-30)
packages/lambdas/src/patient-import-result.ts (1)
packages/core/src/command/patient-import/steps/result/patient-import-result-command.ts (2)
ProcessPatientResultCommandRequest
(17-19)processPatientResult
(21-49)
packages/api/src/command/medical/patient/shared.ts (2)
packages/api/src/command/medical/patient/get-patient.ts (1)
PatientMatchCmd
(27-27)packages/shared/src/common/date.ts (1)
validateDateOfBirth
(21-40)
packages/core/src/command/patient-import/steps/result/patient-import-result-factory.ts (2)
packages/core/src/command/patient-import/steps/result/patient-import-result-local.ts (1)
PatientImportResultLocal
(7-30)packages/core/src/command/patient-import/steps/result/patient-import-result-cloud.ts (1)
PatientImportResultHandlerCloud
(6-29)
packages/core/src/command/patient-import/steps/create/patient-import-create-factory.ts (2)
packages/core/src/command/patient-import/steps/create/patient-import-create-local.ts (1)
PatientImportCreateLocal
(7-45)packages/core/src/command/patient-import/steps/create/patient-import-create-cloud.ts (1)
PatientImportCreateCloud
(7-28)
packages/core/src/command/patient-import/csv/__tests__/convert-patient.test.ts (1)
packages/core/src/command/patient-import/csv/convert-patient.ts (1)
mapCsvPatientToMetriportPatient
(38-117)
packages/shared/src/common/__tests__/date.test.ts (1)
packages/shared/src/common/date.ts (1)
validateDateOfBirth
(21-40)
packages/lambdas/src/patient-import-create.ts (1)
packages/core/src/command/patient-import/steps/create/patient-import-create-command.ts (1)
ProcessPatientCreateCommandRequest
(13-17)
packages/core/src/command/patient-import/steps/result/patient-import-result-cloud.ts (1)
packages/core/src/external/aws/lambda.ts (1)
makeLambdaClient
(13-19)
packages/core/src/command/patient-import/steps/query/patient-import-query-command.ts (3)
packages/core/src/command/patient-import/steps/query/patient-import-query-local.ts (1)
processPatientQuery
(13-46)packages/shared/src/index.ts (1)
sleep
(13-13)packages/core/src/command/patient-import/patient-or-record-failed.ts (1)
setPatientOrRecordFailed
(4-31)
packages/core/src/command/patient-import/patient-or-record-failed.ts (1)
packages/core/src/command/patient-import/api/update-job-status.ts (1)
updateJobAtApi
(21-56)
packages/api/src/routes/internal/medical/patient-import.ts (1)
packages/shared/src/index.ts (1)
BadRequestError
(40-40)
packages/infra/lib/shared/consolidated-search-construct.ts (3)
packages/infra/config/env-config.ts (1)
EnvConfig
(307-307)packages/infra/lib/shared/secrets.ts (1)
Secrets
(6-6)packages/infra/lib/lambdas-nested-stack-settings.ts (2)
getConsolidatedSearchConnectorSettings
(33-42)getConsolidatedIngestionConnectorSettings
(7-31)
🪛 Biome (1.9.4)
packages/core/src/command/patient-import/steps/parse/patient-import-parse-command.ts
[error] 53-53: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with
(lint/complexity/noUselessTernary)
🔇 Additional comments (16)
packages/core/src/command/patient-import/csv/store-results.ts (1)
88-92
: CSV omits developer-facing reason – confirm this is intended
entryToCsv()
writesreasonForCx
but discardsreasonForDev
.
If the dev reason is useful for internal debugging, consider:- const reason = e.reasonForCx ? escapeCsvValueIfNeeded(e.reasonForCx) : ""; - return `${e.rowCsv},${patientId},${status},${reason}`; + const reasonCx = e.reasonForCx ? escapeCsvValueIfNeeded(e.reasonForCx) : ""; + const reasonDev = e.reasonForDev ? escapeCsvValueIfNeeded(e.reasonForDev) : ""; + return `${e.rowCsv},${patientId},${status},${reasonCx},${reasonDev}`;(Adjust header accordingly.) Otherwise remove
reasonForDev
altogether.packages/lambdas/src/patient-import-parse.ts (1)
2-5
: Good move to command-style import – cleaner bundlingShifting from class instantiation to a plain command import simplifies the handler and aligns with the new architecture.
packages/core/src/command/patient-import/steps/create/patient-import-create-factory.ts (1)
8-10
: Factory simplification looks goodConstructors now rely on their own defaults, reducing parameter threading from the factory. Nice cleanup.
packages/core/src/command/patient-import/steps/parse/patient-import-parse-factory.ts (1)
8-10
: Consistent with the create-factory changeDropping explicit config parameters keeps factories lightweight and hides config look-ups inside the respective classes. Looks good.
packages/core/src/command/patient-import/patient-or-record-failed.ts (1)
19-22
:failed
counter likely overwritten instead of incremented
updateJobAtApi({ cxId, jobId, failed: 1 })
passes a literal1
, which most APIs interpret as an absolute value, not a delta.
If the job already hasfailed > 0
, this call will reset the counter back to 1 and under-report subsequent failures.- updateJobAtApi({ cxId, jobId, failed: 1 }), + // Consider fetching the current value or using an increment-by endpoint + updateJobAtApi({ cxId, jobId, failed: currentFailed + 1 }), // pseudo-codeVerify the API semantics or introduce an incrementing helper to avoid silent data corruption.
packages/core/src/command/patient-import/csv/contact.ts (1)
22-23
: Good call making CSV field types optionalAllowing
undefined
better reflects sparse CSV input and removes the need for
superfluous casts down-stream.Also applies to: 44-45
packages/core/src/command/patient-import/csv/address.ts (1)
26-27
: Type annotation update looks correctThe switch to
string | undefined
aligns this parser with the new contact
typing and avoids false positives when fields are blank.Also applies to: 54-55
packages/core/src/command/patient-import/steps/result/patient-import-result-factory.ts (1)
8-11
: Constructor simplification is fineRemoving the explicit bucket argument reduces coupling;
PatientImportResultLocal
already defaults toConfig.getPatientImportBucket()
. No concerns.packages/shared/src/common/__tests__/date.test.ts (1)
131-148
: Mocks hide the real bugAll “true / false” assertions from here down pass replacement mocks for
validateDateIsAfter1900
andvalidateIsPastOrPresent
, so they never exercise the real
logic that just broke (see previous comment). Add at least one happy-path test that does
not pass the overrides to ensure the default implementation still works.packages/core/src/command/patient-import/csv/__tests__/convert-patient.test.ts (1)
1-28
: faker state abbreviation can be lowercase
faker.location.state({ abbreviated: true })
occasionally returns e.g."nm"
.
mapCsvPatientToMetriportPatient
expects a validUSState
enum (uppercase). Consider
.toUpperCase()
when generating the fixture or assert that mapping handles lower-case
input.packages/core/src/command/patient-import/steps/parse/patient-import-parse-local.ts (1)
23-29
: Confirm intentional error swallowingSwallowing every error here means local callers will always see a resolved promise even if the command failed.
If that’s purely to mimic the async-queue behaviour in cloud, please add a brief comment at the call-site or unit test so future readers don’t assume this is a bug.packages/infra/lib/shared/consolidated-search-construct.ts (4)
39-46
: Good modularization of infrastructure components.The construct properly encapsulates related AWS resources for consolidated search functionality, following CDK best practices for reusability and maintainability.
83-148
: Well-structured Lambda configuration with proper permissions.The method correctly sets up the consolidated search Lambda with all necessary environment variables and grants appropriate read/write permissions to required resources.
150-167
: Clean queue setup implementation.The queue creation properly uses centralized settings and follows the established pattern.
169-231
: Proper Lambda setup with queue integration.The ingestion Lambda is correctly configured with the queue as an event source and appropriate permissions for all required resources.
packages/infra/lib/lambdas-nested-stack.ts (1)
175-187
: Clean refactoring to use the new construct.The consolidation of three separate setup methods into a single construct instantiation improves code organization and maintainability.
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.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 (1)
packages/shared/src/common/__tests__/title-case.test.ts (1)
22-26
: Nice addition – covers the new edge case
Good to see the apostrophe scenario exercised. Consider adding a variant with leading/trailing whitespace (e.g." o'neal "
), but that’s optional.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/shared/src/common/__tests__/title-case.test.ts
(1 hunks)packages/shared/src/common/title-case.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/shared/src/common/__tests__/title-case.test.ts
packages/shared/src/common/title-case.ts
🧬 Code Graph Analysis (1)
packages/shared/src/common/__tests__/title-case.test.ts (1)
packages/shared/src/common/title-case.ts (1)
toTitleCase
(1-13)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/shared/src/common/title-case.ts (1)
7-10
: Solid apostrophe handling – implementation looks correct
The new split-/rejoin logic correctly capitalises each segment in"D'Angelo"
without affecting the punctuation or surrounding words. No functional concerns on these lines.
Ref eng-245 Signed-off-by: Rafael Leite <2132564+leite08@users.noreply.github.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 (1)
packages/shared/src/domain/patient/patient-import/create.ts (1)
33-36
: Changing these defaults is a silent breaking change – audit call-sites & update docs/tests.
rerunPdOnNewDemographics
andtriggerConsolidated
now default totrue
. Any existing caller that relied on the previousfalse
default will suddenly trigger extra processing (PD re-runs, consolidated search) unless they explicitly passfalse
.Please:
- Confirm that every caller really wants this new behaviour; otherwise make them pass
true
explicitly and keep the defaultfalse
.- Update unit/integration tests that assert default flag values.
- Mention the new default in public docs / changelog to avoid surprises in downstream services.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/shared/src/domain/patient/patient-import/create.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: - Use the Onion Pattern to organize a package's code in layers - Try to use immutable code and avoid sharing state across different functions, objects, and systems - Try...
**/*.ts
: - Use the Onion Pattern to organize a package's code in layers
- Try to use immutable code and avoid sharing state across different functions, objects, and systems
- Try to build code that's idempotent whenever possible
- Prefer functional programming style functions: small, deterministic, 1 input, 1 output
- Minimize coupling / dependencies
- Avoid modifying objects received as parameter
- Only add comments to code to explain why something was done, not how it works
- Naming
- classes, enums:
PascalCase
- constants, variables, functions:
camelCase
- file names:
kebab-case
- table and column names:
snake_case
- Use meaningful names, so whoever is reading the code understands what it means
- Don’t use negative names, like
notEnabled
, 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
- Use truthy syntax instead of
in
- i.e.,if (data.link)
notif ('link' in data)
- Error handling
- Pass the original error as the new one’s
cause
so the stack trace is persisted- Error messages should have a static message - add dynamic data to MetriportError's
additionalInfo
prop- Avoid sending multiple events to Sentry for a single error
- Global constants and variables
- Move literals to constants declared after imports when possible (avoid magic numbers)
- Avoid shared, global objects
- Avoid using
console.log
andconsole.error
in packages other than utils, infra and shared,
and try to useout().log
instead- Avoid multi-line logs
- don't send objects as a second parameter to
console.log()
orout().log()
- don't create multi-line strings when using
JSON.stringify()
- Use
eslint
to enforce code style- Use
prettier
to format code- max column length is 100 chars
- multi-line comments use
/** */
- scripts: top-level comments go after the import
packages/shared/src/domain/patient/patient-import/create.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: check-pr / lint-build-test
- GitHub Check: Analyze (javascript)
@@ -296,8 +299,11 @@ router.get( | |||
|
|||
const patientImport = await getPatientImportJobOrFail({ cxId, jobId }); | |||
|
|||
if (patientImport.status === "processing" && detail === "debug") { | |||
const details: Record<string, string | number | null>[] = []; | |||
if (detail === "debug" && patientImport.total > maxPatientsOnDebugGetBulk) { |
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.
maybe we should do pagination? if the patient Ids are ordered could be easy-ish?
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.
just use lastPatientId token or something like that
import { updateJobAtApi } from "./api/update-job-status"; | ||
import { updatePatientRecord } from "./record/create-or-update-patient-record"; | ||
|
||
export async function setPatientOrRecordFailed({ |
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.
setPatientOrRecordFailed
what's the OR for? Seems like an AND based on the code?
* @returns true if the period is valid. | ||
* @throws BadRequestError if the period is invalid. | ||
*/ | ||
function validatePeriod(period: Period): true { |
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.
don't want to move this to core too?
): PatientPayload | ParsingError[] { | ||
const errors: ParsingError[] = []; | ||
|
||
let firstName: string | undefined = undefined; | ||
try { | ||
firstName = normalizeName(csvPatient.firstname ?? csvPatient.firstName, firstNameFieldName); | ||
if (!firstName) throw new BadRequestError(`Missing first name`); |
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.
what's this checking? empty string? I'd do .length < 1
for empty string to make it clear if that's what's happening but not a big deal. I assume normalizeName
trims.
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.
normalizeNameSafe
could be something that checks empty string and returns undefined? not needed here, but might be nice
} catch (error) { | ||
errors.push({ field: firstNameFieldName, error: errorToString(error) }); | ||
} | ||
|
||
let lastName: string | undefined = undefined; | ||
try { | ||
lastName = normalizeName(csvPatient.lastname ?? csvPatient.lastName, lastNameFieldName); | ||
if (!lastName) throw new BadRequestError(`Missing last name`); |
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.
ditto
const patientImportParser = new PatientImportParseLocal(patientImportBucket); | ||
|
||
await patientImportParser.processJobParse(params); | ||
const cmdParams: ProcessJobParseCommandRequest = { |
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.
processJobParseCmd? other 3 match that style
Dependencies
Description
Updates to the codebase pre-coverage assessment on lambdas.
Testing
Release Plan
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Tests