CHI-3719: Create script for importing PRN iCarol contacts to HRM - #1100
CHI-3719: Create script for importing PRN iCarol contacts to HRM#1100stephenhand wants to merge 9 commits into
Conversation
|
@copilot Using the mapping spreadsheet and example CSV attached, complete the TODO items in the comments that were added in this PR |
Done in d3596de. Both TODOs are now implemented in
One deviation to flag: the TODO referenced |
stephenhand
left a comment
There was a problem hiding this comment.
@copilot - write a set of unit tests for the mapping / importing
| if (protocol !== 's3:') { | ||
| throw new Error(`location must be an S3 URI (s3://bucket/key), got: ${location}`); | ||
| } | ||
| return { bucket: hostname, key: decodeURIComponent(pathname.replace(/^\//, '')) }; |
There was a problem hiding this comment.
@copilot Can we use a more explicit method to exclude a leading slash, the 'replace' method behaviour can be confusing
There was a problem hiding this comment.
Done in f6de203. parseS3Uri now strips the leading slash explicitly: const key = pathname.startsWith('/') ? pathname.slice(1) : pathname; instead of the regex replace.
Added in f6de203. I extracted the pure mapping/parsing logic into |
stephenhand
left a comment
There was a problem hiding this comment.
@copilot Here is a link to the Aselo form definitions that map display names used in the mapping spreadsheet to field names: https://github.com/techmatters/flex-plugins/tree/master/lambdas/packages/hrm-form-definitions/form-definitions/usnc/v1
Correct any target field names using this information~
Address the other 2 comments in this review
| ); | ||
|
|
||
| const rawJson: ContactRawJson = { | ||
| callType: record['Call Information - Call Type'] || '', |
There was a problem hiding this comment.
As described in the spreadsheet, if this field is empty you need to fall back to inferring from the boolean fields described in row 18
If the value here is crisis or non crisis, substitute for 'Child calling about self' and record crisis / non crisis as a boolean 'isCrisis' field under caseInformation
There was a problem hiding this comment.
Done in 3e38310 (mapCallType). When "Call Information - Call Type" is Crisis/Non-Crisis, the callType is set to Child calling about self and caseInformation.isCrisis records the crisis/non-crisis boolean. When that field is empty, the callType is inferred from the row-18 boolean flags (WasRealCall→Child calling about self, WasSilentCall→Silent, WasHangup→Hang up, WasWrongNumber→Wrong Number, WasPrankCall→Prank Call, WasSexCall→Sexual Gratifier, using the PRN CallTypeButtons labels).
| ), | ||
| }; | ||
|
|
||
| const callLengthMinutes = Number.parseInt(record.CallLength, 10); |
There was a problem hiding this comment.
Calculate this from CallDateAndTimeStart and CallDateAndTimeEnd rather than using CallLength
There was a problem hiding this comment.
Done in 3e38310. conversationDuration is now computed by calculateConversationDuration from CallDateAndTimeStart/CallDateAndTimeEnd (seconds), falling back to 0 when either timestamp is missing or unparseable. CallLength is no longer used.
Done in 3e38310. Corrected the target field names against the usnc/v1 form definitions:
The other two review comments are addressed in the same commit (see thread replies). |
|
@copilot - we need to look up Twilio worker SIDs from PhoneWorkerName to populate the twilioWorkerId and createdBy fields, and the createdOnBehalfOf field in the contactlessTask rawJson object https://github.com/techmatters/flex-plugins/blob/master/lambdas/account-scoped/src/worker/populateCounselors.ts - use this code as a guide to looking up twilio workers on the specified account, creating an in memory mapping and using that mapping to go from PhoneWorkerName (FullName) -> SID https://github.com/techmatters/flex-plugins/blob/47234b5d29dd26fed9fe13f90673259fc7f6f501/lambdas/packages/twilio-configuration/src/twilioConfiguration.ts#L48 - look up the auth token in our SSM store like this |
…e imported contacts
…Sid for missing-worker warning
Done. iCarol contacts are now attributed to the counsellor named in
|
|
@copilot - If a worker cannot be looked up from the PhoneWorkerName, fall back to a default passed in as a CLI parameter rather than omitting it, the worker sid for these fields should be considered required. Log a warning for all records where the lookup fails, including the CallReportNum and the PhoneWorkerName the lookup was attempted on |
- Add --fallback-worker-sid (-f) CLI parameter (required) - Use fallback worker SID when PhoneWorkerName lookup fails - Log warnings with CallReportNum and PhoneWorkerName when lookup fails - Update mapContact to require worker SID parameter instead of optional lookup map - Update all tests to pass default worker SID
Done. Added a required |
|
@mckinneybock - in case this work comes your way, this is a copilot assisted, untested first stab at an importing PRN contacts, if it's of any value to you |
Description
Adds an admin CLI command (
import icarol contacts) that imports PRN iCarol "CallReports" CSV exports into HRM as contacts.@tech-matters/s3-client) and parses it into typedICarolContactRecordrows usingcsv-parse. iCarol exports prefix the file with a title row and a blank row, so parsing starts at line 3.rawJson.childInformation, "Contact > Summary" →rawJson.caseInformation, and the "Eight Dimensions of Wellness" →rawJson.categories(each dimension mapped to its category and an "Unspecified/Other" subcategory). Yes/No values are coerced to booleans and blank columns are omitted. Target field names are taken from the Aselousnc/v1form definitions (e.g.friendlyName,referral988,wasTheCallerSatisfiedWithTheSupportProvided,doWeHaveTheirPermissionToCallBack,referrals); the unsupportedcitycolumn is dropped.Crisis/Non-Crisismap to the data callTypeChild calling about selfwith a booleancaseInformation.isCrisis, and when that field is empty the callType is inferred from the iCarol boolean flag columns (WasRealCall,WasSilentCall,WasHangup,WasWrongNumber,WasPrankCall,WasSexCall) using the PRN CallTypeButtons labels.conversationDuration(seconds) from theCallDateAndTimeStart/CallDateAndTimeEndtimestamps, defaulting to 0 when either is missing or unparseable.buildWorkerSidMap(modelled on the FlexpopulateCounselorslambda) lists the account's TaskRouter workers and builds an in-memoryfull_name→ worker SID map, reading the Twilio auth token and workspace SID from SSM (/${environment}/twilio/${accountSid}/auth_tokenand.../workspace_sid) and creating the client via@tech-matters/twilio-client'sgetClient. When aPhoneWorkerNameresolves to a worker SID, that SID populatestwilioWorkerId,createdBy, and thecontactlessTask.createdOnBehalfOffield inrawJson. Records whose worker name has no match fall back to a required default worker SID passed via the--fallback-worker-sidCLI parameter, with a warning logged that includes theCallReportNumandPhoneWorkerName.contactMapper.tsmodule (parseICarolBoolean,parseS3Uri,mapCategories,mapCallType,calculateConversationDuration,resolveWorkerSid,mapContact), keeping it isolated from the S3/HTTP/Twilio I/O incontacts.tsso it can be unit tested.parseS3Uristrips the leading slash from the S3 key with an explicitstartsWith/slicecheck rather than a regexreplace.@tech-matters/s3-clientdependency, movescsv-parseto runtime dependencies, and moves@tech-matters/twilio-clientto runtime dependencies.contactMapper.test.ts) covering the mapping/importing logic (includingresolveWorkerSidand worker attribution), plus atest:unitscript on the hrm-service package so they run undernpm run test:unit.Note: the original TODO referenced
rawJson.caseSummary, butContactRawJsonhas no such field — the Summary tab is stored undercaseInformation, so it is mapped there.Checklist
Other Related Issues
None
Verification steps
Run the admin CLI command against an environment, pointing at an iCarol CSV export in S3:
Confirm the contacts are created in HRM with the expected
childInformation,caseInformation,categories,callTypeandconversationDurationvalues, and that contacts whosePhoneWorkerNamematches a Twilio worker are attributed to that worker (twilioWorkerId,createdBy, andrawJson.contactlessTask.createdOnBehalfOfall set to the worker SID). Contacts whosePhoneWorkerNamedoes not match a worker should be attributed to the fallback worker SID, with warnings logged including theCallReportNumandPhoneWorkerName.The mapping logic can be exercised in isolation with the unit tests via
npm run test:unitinhrm-domain/hrm-service.AFTER YOU MERGE
You are responsible for ensuring the above steps are completed. If you move a ticket into QA without advising what version to test, the QA team will assume the latest tag has the changes. If it does not, the following confusion is on you! :-P