Skip to content

[Social Work] Support single and multi-state license types#1597

Merged
isabeleliassen merged 48 commits into
csg-org:mainfrom
InspiringApps:feat/sw-license-scope
Jun 9, 2026
Merged

[Social Work] Support single and multi-state license types#1597
isabeleliassen merged 48 commits into
csg-org:mainfrom
InspiringApps:feat/sw-license-scope

Conversation

@landonshumway-ia

@landonshumway-ia landonshumway-ia commented May 28, 2026

Copy link
Copy Markdown
Collaborator

The Social Work compact needs to support both single and multi-state license types. To support this, we are adding a new license field specific to their API, licenseScope, which will have a value of either single-state or multi-state.

This change includes updating all of the relevant schemas and tests to account for this new field, and inserts the relevant license types specific for Social Work throughout the codebase.

As part of this, we needed to update the sort keys for our license related DynamoDB records to include the license scope, in order to distinguish the single state versus multi state records when storing these records in the provider DynamoDB table. For adverse actions/investigations, this means the frontend client will now need to specify the license scope as part of the request body when setting/lifting encumbrances and investigations (privilege related records will always have a 'licenseScope' of single-state, since a privilege record in the system is scoped to the state it is associated with).

In addition, the ingest flow now checks for additional conditions to trigger home state changes for providers. A home state change is not triggered until both the single and multi-state licenses have been uploaded for a jurisdiction, and the multi-state license is more recent than any other jurisdiction's multi-state license.

This logic also holds for generating privileges. We only generate privileges for multi-state licenses if the multi-state license is marked as compact eligible and there is an associated single-state license for the same jurisdiction/license type that is also compact eligible. Likewise, if the single-state license is marked as compact ineligible, than the associated multi-state license will also show as compact ineligible in the api response.

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • For API configuration changes: CDK tests added/updated in backend/compact-connect/tests/unit/test_api.py
  • For API endpoint changes: OpenAPI spec updated to show latest endpoint configuration run compact-connect/bin/download_oas30.py
  • Code review

Closes #1585

Summary by CodeRabbit

  • New Features

    • Introduced required licenseScope (single-state | multi-state) across APIs, events, storage and search; provider/license identities and public search now include scope and privilege rules respect paired single/multi-state logic. Removed prior "mostRecentLicenseForType" constraint from public searches.
  • Documentation

    • CSV/JSON examples, onboarding, API schemas, design docs and Postman updated for licenseScope and Social Work license types.
  • Bug Fixes

    • Bulk upload/ingest always enforce duplicate SSN + license type + licenseScope uniqueness.
  • Tests

    • Fixtures, unit/integration tests and snapshots updated for scope and new license types.

We need to be able to distinguish DynamoDB records associated with
licenses  by their license scope when querying for records, since
a practitioner can have both a single and multi state license with
the same jurisdiction/license type.

This largely impacts investigation and adverse action records, since
they are associated with license/privilege records. This commit adds
the licenseScope field to those record types to make them more readily
distinguishable in terms of which license they are associated with.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds required licenseScope (single-state|multi-state) and provider_pk/license_sk_suffix helpers; threads licenseScope into schemas, DynamoDB PK/SK generation, API models, handlers, event payloads, OpenSearch mapping/document IDs, mock CSV generator, docs, and extensive tests/fixtures for Social Work license types.

Changes

Scope-aware Social Work licensing

Layer / File(s) Summary
Contracts and key helpers
.../schema/common.py, .../schema/fields.py, .../schema/*
Adds LicenseScopeEnum and LicenseScopeField, plus provider_pk and license_sk_suffix; updates license/adverse-action/investigation/license record schemas to include licenseScope and validate scope-dependent rules.
Record PK/SK and model wiring
data_model/*/record.py, .../data_client.py, provider_record_util.py
Switches provider PK construction to provider_pk(...), reformats SKs via license_sk_suffix(...), and threads license_scope into lookups/filters and DataClient methods (create/close/lift flows).
API models and docs
stacks/*/api_model.py, docs/*
Adds required licenseScope to encumbrance/investigation/license request/response schemas and updates API Gateway models and documentation/examples accordingly.
Handlers and event flow
provider-data-v1/handlers/*, event_bus_client.py, data-events/handlers/*
Parses/validates license-specific request bodies, persists licenseScope on generated records, passes license_scope through to DataClient and EventBus publish calls, and extracts licenseScope from events for processing/logging.
OpenSearch and document IDs
search/opensearch_client.py, search/utils.py, search/tests/*
Adds licenseScope mapping (keyword), includes scope in per-license documentId composition, and updates index/doc expectations in tests.
Mock generator, config, and docs
bin/generate_mock_license_csv_upload_file.py, cdk.json, cc_common/config.py, docs/*
Mock CSV now emits licenseScope; config normalized; README and onboarding docs updated with licenseScope.
Tests and fixtures
*/tests/*, tests/resources/*, tests/smoke/*
Extensive test/fixture updates to use Social Work license types (lcsw/lmsw/lbsw) and include licenseScope across unit, integration, function, and smoke tests and JSON snapshots.

Sequence Diagram

sequenceDiagram
  participant Client
  participant API_Gateway
  participant Handler
  participant DataClient
  participant DynamoDB
  participant EventBus
  Client->>API_Gateway: POST/PATCH (licenseType, licenseScope, ...)
  API_Gateway->>Handler: validated payload (includes licenseScope)
  Handler->>DataClient: create/close/encumber(..., license_scope)
  DataClient->>DynamoDB: PK via provider_pk(...)/ SK via license_sk_suffix(...)
  Handler->>EventBus: publish event (detail includes licenseScope)
  EventBus->>Listener: delivers event (listeners extract licenseScope)
Loading

Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Suggested reviewers

  • jlkravitz

"A rabbit inspects every scope in sight,
From single-state burrows to multi-state flight.
With keys neatly forged and records in line,
Events hop along and OpenSearch shines.
Thump-thump — tests green, the licenses align. 🐇✨"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py (1)

306-332: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require license_scope for license investigations before publishing events.

At Line 331 and Line 389, licenseScope is optional even when investigation_against is license. That can emit ambiguous events and lose single-state vs multi-state disambiguation.

Proposed fix
     def publish_investigation_event(
         self,
@@
         license_scope: str | None = None,
         event_batch_writer: EventBatchWriter | None = None,
     ):
+        if investigation_against == InvestigationAgainstEnum.LICENSE and license_scope is None:
+            raise ValidationError({'licenseScope': ['Missing data for required field.']})
+
         event_detail = {
@@
         if license_scope is not None:
             event_detail['licenseScope'] = license_scope
@@
     def publish_investigation_closed_event(
         self,
@@
         license_scope: str | None = None,
         event_batch_writer: EventBatchWriter | None = None,
     ):
+        if investigation_against == InvestigationAgainstEnum.LICENSE and license_scope is None:
+            raise ValidationError({'licenseScope': ['Missing data for required field.']})
+
         event_detail = {

Also applies to: 358-390

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py`
around lines 306 - 332, When publishing investigation events, require
license_scope when investigation_against indicates a license: in the function
that builds event_detail (the method taking parameters license_scope and
investigation_against and using investigation_against.value and event_detail),
add an early validation that if investigation_against ==
InvestigationAgainst.LICENSE (or the enum/member used in this codebase) and
license_scope is None, raise a ValueError (or a DomainValidationError used in
this module) rather than omitting licenseScope; then set
event_detail['licenseScope'] = license_scope only after that check. Repeat the
same validation for the other code path that constructs event_detail (the second
occurrence that currently treats licenseScope as optional) so both publishing
branches enforce license_scope for license investigations.
backend/social-work-app/lambdas/python/search/opensearch_client.py (1)

246-254: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit licenseScope mapping to adverse_action_properties (it’s already present for investigation_properties and licenses).

Nested adverseActions include licenseScope (schema field is required), so without an explicit keyword mapping OpenSearch may infer it inconsistently via dynamic mapping. [backend/social-work-app/lambdas/python/search/opensearch_client.py]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/lambdas/python/search/opensearch_client.py` around
lines 246 - 254, The adverse_action_properties mapping is missing an explicit
licenseScope entry, causing OpenSearch to dynamically infer its type; update the
adverse_action_properties dict (the variable named adverse_action_properties in
opensearch_client.py) to include "licenseScope": {"type": "keyword"}—mirror the
existing licenseScope mapping used in investigation_properties/licenses so the
nested adverseActions field is consistently mapped as a keyword.
🧹 Nitpick comments (6)
backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py (1)

37-38: ⚡ Quick win

Add an explicit nested-license assertion for licenseScope.

Since licenseScope is now part of the fixture contract, asserting it in at least one test here would improve regression detection for provider document shaping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py`
around lines 37 - 38, The test in test_provider.py currently asserts the nested
'licenseType' but does not assert 'licenseScope'; add an explicit assertion
verifying the shaped provider document contains the nested licenseScope value
(e.g., assert provider_doc['license']['licenseScope'] == 'single-state' or
adjust to your actual shaped structure such as
provider_doc['licenses'][0]['licenseScope']) next to the existing licenseType
assertion so the fixture contract for 'licenseScope' is enforced.
backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py (1)

622-623: ⚡ Quick win

Add rollback coverage for multi-state update-key suffixes.

These assertions now lock the single-state key format; add a multi-state rollback case to ensure both scope-specific key variants are handled and reported correctly.

Also applies to: 927-928, 940-941

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py`
around lines 622 - 623, The test currently asserts rollback keys only for the
'single-state' suffix (e.g., '.../license/oh/lcsw/single-state/...') so add
equivalent assertions covering the 'multi-state' suffix to ensure both scope
variants are validated; update the expected-keys lists or assertions in
test_rollback_license_upload.py (the assertions near the existing 'single-state'
entries around the three spots mentioned) to include corresponding
'.../license/oh/lcsw/multi-state/...' entries and mirror the same checks
(counts/messages) used for single-state so multi-state update-key suffixes are
exercised and reported.
backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py (1)

32-33: ⚡ Quick win

Add a multi-state license investigation path test.

The generator and assertions now pin single-state; please add a corresponding multi-state case (open/closed) so both supported scopes are covered.

Also applies to: 50-51

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py`
around lines 32 - 33, The test currently only covers a 'single-state'
licenseScope in test_investigation_events.py (the diff pins 'licenseScope':
'single-state' and uses DEFAULT_DATE_OF_UPDATE_TIMESTAMP); add a new test case
that mirrors the existing single-state case but sets 'licenseScope':
'multi-state' and asserts both open and closed outcomes as appropriate (create
one event for an open multi-state investigation and one for a closed multi-state
investigation), updating expected generator outputs and assertions to cover both
scopes so the generator is tested for multi-state behavior as well.
backend/social-work-app/lambdas/python/data-events/tests/function/test_data_events.py (1)

70-71: ⚡ Quick win

Add a multi-state sanitization test case.

This assertion only protects the single-state path. Please add a companion case for licenseScope='multi-state' so both supported scopes are regression-covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_data_events.py`
around lines 70 - 71, Add a companion test in test_data_events.py that covers
licenseScope='multi-state' alongside the existing 'single-state' case: locate
the test that builds the input dict with keys 'licenseType' and 'licenseScope'
and add another assertion/input case where 'licenseScope' is 'multi-state' and
assert the expected sanitized/output behavior (mirroring the pattern used for
the 'single-state' assertion) so both supported scopes are regression-tested.
backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py (1)

37-37: ⚡ Quick win

Cover multi-state encumbrance/lifting flows in this suite.

These updates validate the new scope-aware contract for single-state, but the new feature supports both scopes. Add at least one license encumbrance + one lifting test with licenseScope='multi-state' to lock behavior.

Also applies to: 426-427, 501-502, 772-773, 844-845, 928-929

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py`
at line 37, Add tests to exercise the multi-state flow by duplicating the
existing license-encumbrance and lifting test cases that set "licenseScope":
"single-state" and changing the payload key "licenseScope" to "multi-state";
update the test functions in test_encumbrance_events.py that create encumbrance
events and lifting events (the cases around the existing "licenseScope" usages)
to include at least one encumbrance test and one lifting test using
"multi-state" so the new scope-aware contract is covered for both flows.
backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py (1)

362-377: ⚡ Quick win

Add a duplicate-SSN test that varies licenseScope (same licenseType).

Coverage currently proves duplicates are allowed when licenseType differs, but not when only licenseScope differs. Given scope is now a first-class discriminator, add this case to lock intended behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py`
around lines 362 - 377, Add a new test (e.g.,
test_bulk_upload_allows_repeated_ssns_for_different_license_scopes) that mirrors
test_bulk_upload_allows_repeated_ssns_for_different_license_types but keeps
licenseType identical and varies licenseScope to ensure duplicates are allowed
when only scope differs; build CSV content with the same ssn and licenseType but
different licenseScope, call handlers.bulk_upload.parse_bulk_upload_file and
assert it returns/accepts both records (no duplicate-error or rejection),
referencing the existing test function and parse_bulk_upload_file to locate
where to add the case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py`:
- Line 21: The event detail schemas currently declare licenseScope =
LicenseScopeField(required=False, allow_none=False) (see the licenseScope field
declarations in this module) which permits omitting scope; change those
declarations to require the field by setting required=True (keep
allow_none=False) so licenseScope is mandatory for encumbrance/investigation
events — update both occurrences of licenseScope in this file to
LicenseScopeField(required=True, allow_none=False).

In
`@backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py`:
- Line 61: The invalid-type test is using a non-canonical attribute name
(adverse_action_data.license_abbreviation) which masks the real validation;
change that assignment to use the canonical field
adverse_action_data.licenseTypeAbbreviation so the test exercises the intended
type/abbreviation pairing logic (update the test that sets license_abbreviation
to instead set licenseTypeAbbreviation and keep the invalid type value
unchanged).

In
`@backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py`:
- Around line 359-360: The retention tests currently include licenseScope in the
test data but never assert it; update the response-field retention tests to
explicitly assert the returned/license-preserved value for "licenseScope"
(alongside the existing "licenseType" checks). Locate the tests in
test_license.py that verify field retention (the blocks using the test data
containing 'licenseType' and 'licenseScope' around the shown diff and the
similar lines at 451-452) and add an assertion that the response object's
licenseScope equals the expected value (e.g., assert response['licenseScope'] ==
'single-state' or the test framework’s equivalent).

In
`@backend/social-work-app/lambdas/python/provider-data-v1/handlers/investigation.py`:
- Around line 62-67: The code currently calls json.loads(event['body']) outside
the ValidationError try/except so a malformed body raises json.JSONDecodeError
and escapes the intended CCInvalidRequestException handling; update
_load_license_investigation_patch_body to wrap json.loads(...) and the
subsequent schema.load(...) in a try that catches both json.JSONDecodeError and
ValidationError and raises CCInvalidRequestException with a clear message
(include e.msg or e.messages as appropriate), and apply the same pattern in
handle_license_investigation where event['body'] is parsed so all JSON decode
errors are converted to CCInvalidRequestException instead of bubbling up.

In
`@backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py`:
- Line 433: The OpenSearch failure fixture uses an outdated document_id format;
update the document_id assignment in test_provider_update_ingest.py (the
document_id variable) to include the scope marker "`#single-state`" between the
provider and jurisdiction segments so the ID matches current document IDs (i.e.,
change the string from f'{MOCK_SOCW_PROVIDER_ID}`#oh`#licensed clinical social
worker' to include '`#single-state`' resulting in
f'{MOCK_SOCW_PROVIDER_ID}`#single-state`#oh#licensed clinical social worker').

In
`@backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py`:
- Line 631: Update the pagination test fixtures so the document IDs include the
licenseScope marker `#single-state`; specifically modify the cursor payload
strings (e.g., the last_key_payload variable and the other two fixtures
referenced in this test file) to insert `#single-state` into the ID shape (for
example change 'uuid-123#oh#licensed clinical social worker' to
'uuid-123#single-state#oh#licensed clinical social worker') so the search_after
cursors mirror the new ID format.
- Around line 790-792: The test data has mismatched values for the keys
'licenseTypeAbbreviation' and 'licenseType' (currently 'lcsw' vs 'licensed
master social worker'); update the 'licenseTypeAbbreviation' value to the
correct abbreviation 'lmsw' so it matches the 'licenseType' entry used for
type-based filtering, locating the object containing the keys
'licenseTypeAbbreviation' and 'licenseType' in the
test_public_search_providers.py tests.

In
`@backend/social-work-app/lambdas/python/search/tests/function/test_search_providers.py`:
- Line 80: The test fixture builds an outdated document_id; update the
document_id variable in test_search_providers.py (where document_id =
f'{provider_id}`#oh`#licensed clinical social worker') to include the scope suffix
so it becomes scope-aware (append "`#single-state`"), ensuring the fixture matches
current indexing identity used by the search providers tests.

---

Outside diff comments:
In `@backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py`:
- Around line 306-332: When publishing investigation events, require
license_scope when investigation_against indicates a license: in the function
that builds event_detail (the method taking parameters license_scope and
investigation_against and using investigation_against.value and event_detail),
add an early validation that if investigation_against ==
InvestigationAgainst.LICENSE (or the enum/member used in this codebase) and
license_scope is None, raise a ValueError (or a DomainValidationError used in
this module) rather than omitting licenseScope; then set
event_detail['licenseScope'] = license_scope only after that check. Repeat the
same validation for the other code path that constructs event_detail (the second
occurrence that currently treats licenseScope as optional) so both publishing
branches enforce license_scope for license investigations.

In `@backend/social-work-app/lambdas/python/search/opensearch_client.py`:
- Around line 246-254: The adverse_action_properties mapping is missing an
explicit licenseScope entry, causing OpenSearch to dynamically infer its type;
update the adverse_action_properties dict (the variable named
adverse_action_properties in opensearch_client.py) to include "licenseScope":
{"type": "keyword"}—mirror the existing licenseScope mapping used in
investigation_properties/licenses so the nested adverseActions field is
consistently mapped as a keyword.

---

Nitpick comments:
In
`@backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py`:
- Around line 37-38: The test in test_provider.py currently asserts the nested
'licenseType' but does not assert 'licenseScope'; add an explicit assertion
verifying the shaped provider document contains the nested licenseScope value
(e.g., assert provider_doc['license']['licenseScope'] == 'single-state' or
adjust to your actual shaped structure such as
provider_doc['licenses'][0]['licenseScope']) next to the existing licenseType
assertion so the fixture contract for 'licenseScope' is enforced.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_data_events.py`:
- Around line 70-71: Add a companion test in test_data_events.py that covers
licenseScope='multi-state' alongside the existing 'single-state' case: locate
the test that builds the input dict with keys 'licenseType' and 'licenseScope'
and add another assertion/input case where 'licenseScope' is 'multi-state' and
assert the expected sanitized/output behavior (mirroring the pattern used for
the 'single-state' assertion) so both supported scopes are regression-tested.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py`:
- Line 37: Add tests to exercise the multi-state flow by duplicating the
existing license-encumbrance and lifting test cases that set "licenseScope":
"single-state" and changing the payload key "licenseScope" to "multi-state";
update the test functions in test_encumbrance_events.py that create encumbrance
events and lifting events (the cases around the existing "licenseScope" usages)
to include at least one encumbrance test and one lifting test using
"multi-state" so the new scope-aware contract is covered for both flows.

In
`@backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py`:
- Around line 32-33: The test currently only covers a 'single-state'
licenseScope in test_investigation_events.py (the diff pins 'licenseScope':
'single-state' and uses DEFAULT_DATE_OF_UPDATE_TIMESTAMP); add a new test case
that mirrors the existing single-state case but sets 'licenseScope':
'multi-state' and asserts both open and closed outcomes as appropriate (create
one event for an open multi-state investigation and one for a closed multi-state
investigation), updating expected generator outputs and assertions to cover both
scopes so the generator is tested for multi-state behavior as well.

In
`@backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py`:
- Around line 622-623: The test currently asserts rollback keys only for the
'single-state' suffix (e.g., '.../license/oh/lcsw/single-state/...') so add
equivalent assertions covering the 'multi-state' suffix to ensure both scope
variants are validated; update the expected-keys lists or assertions in
test_rollback_license_upload.py (the assertions near the existing 'single-state'
entries around the three spots mentioned) to include corresponding
'.../license/oh/lcsw/multi-state/...' entries and mirror the same checks
(counts/messages) used for single-state so multi-state update-key suffixes are
exercised and reported.

In
`@backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py`:
- Around line 362-377: Add a new test (e.g.,
test_bulk_upload_allows_repeated_ssns_for_different_license_scopes) that mirrors
test_bulk_upload_allows_repeated_ssns_for_different_license_types but keeps
licenseType identical and varies licenseScope to ensure duplicates are allowed
when only scope differs; build CSV content with the same ssn and licenseType but
different licenseScope, call handlers.bulk_upload.parse_bulk_upload_file and
assert it returns/accepts both records (no duplicate-error or rejection),
referencing the existing test function and parse_bulk_upload_file to locate
where to add the case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3d28668-cb81-4b26-a8e3-bad122f895c5

📥 Commits

Reviewing files that changed from the base of the PR and between 94174b5 and 5e86152.

⛔ Files ignored due to path filters (3)
  • backend/social-work-app/lambdas/python/common/tests/resources/licenses-invalid-records.csv is excluded by !**/*.csv
  • backend/social-work-app/lambdas/python/common/tests/resources/licenses.csv is excluded by !**/*.csv
  • backend/social-work-app/tests/smoke/cosm-al-mock-licenses.csv is excluded by !**/*.csv
📒 Files selected for processing (90)
  • backend/social-work-app/bin/generate_mock_license_csv_upload_file.py
  • backend/social-work-app/cdk.json
  • backend/social-work-app/docs/README.md
  • backend/social-work-app/docs/it_staff_onboarding_instructions.md
  • backend/social-work-app/lambdas/nodejs/tests/sample-records.ts
  • backend/social-work-app/lambdas/python/common/cc_common/config.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/base_record.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/fields.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/investigation/__init__.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/investigation/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/investigation/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/common.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/ingest.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py
  • backend/social-work-app/lambdas/python/common/common_test/test_constants.py
  • backend/social-work-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/social-work-app/lambdas/python/common/tests/__init__.py
  • backend/social-work-app/lambdas/python/common/tests/function/test_data_client.py
  • backend/social-work-app/lambdas/python/common/tests/resources/api/license-post.json
  • backend/social-work-app/lambdas/python/common/tests/resources/api/provider-detail-response.json
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-update.json
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license.json
  • backend/social-work-app/lambdas/python/common/tests/resources/ingest/event-bridge-message.json
  • backend/social-work-app/lambdas/python/common/tests/resources/ingest/preprocessor-sqs-message.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_email_service_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_feature_flag_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/compact-configuration/tests/__init__.py
  • backend/social-work-app/lambdas/python/data-events/handlers/encumbrance_events.py
  • backend/social-work-app/lambdas/python/data-events/handlers/investigation_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/__init__.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_data_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_home_state_change_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/resources/events.json
  • backend/social-work-app/lambdas/python/disaster-recovery/tests/__init__.py
  • backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py
  • backend/social-work-app/lambdas/python/feature-flag/tests/function/test_statsig_client.py
  • backend/social-work-app/lambdas/python/migration/tests/__init__.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/__init__.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/__init__.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py
  • backend/social-work-app/lambdas/python/search/opensearch_client.py
  • backend/social-work-app/lambdas/python/search/tests/__init__.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_populate_provider_documents.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_search_providers.py
  • backend/social-work-app/lambdas/python/search/utils.py
  • backend/social-work-app/stacks/api_stack/v1_api/api_model.py
  • backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py
  • backend/social-work-app/stacks/state_api_stack/v1_api/api_model.py
  • backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_LIFTING_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py
  • backend/social-work-app/tests/smoke/license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/rollback_license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/smoke_common.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py`:
- Around line 329-330: When building the event payload in event_bus_client (the
code that sets 'licenseScope' and 'investigationAgainst'), don't silently
default license_scope to LicenseScopeEnum.SINGLE_STATE when investigation is
against a LICENSE; instead validate that license_scope is provided when
investigation_against == InvestigationAgainstEnum.LICENSE and raise/return an
error (fail fast). Keep the existing default behavior only when
investigation_against == InvestigationAgainstEnum.PRIVILEGE; update the logic
around the payload construction that references license_scope, LicenseScopeEnum,
and InvestigationAgainstEnum to enforce this check and prevent publishing
incorrect multi-state license events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7df98d4-3044-43c8-93dd-015702d39350

📥 Commits

Reviewing files that changed from the base of the PR and between 5e86152 and 4a7d692.

📒 Files selected for processing (16)
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_investigation_events.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py

Comment thread backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py Outdated
Now that we support both single and multi-state licenses, we
must specifically associate privileges with multi-state
license records, and only if there is a matching single-state
license record for that same license type/jurisdiction.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/social-work-app/docs/design/README.md`:
- Line 339: The wording is inconsistent about whether the pairing single-state
license must be "eligible" or "active"; update the sentence(s) so the rule is
explicit and consistent: require that the most-recent multi-state license must
have a paired single-state license in the same jurisdiction and license type
that is both active and eligible. Replace the two occurrences ("eligible
single-state" and "paired active single-state") with a single consistent phrase
like "an active and eligible single-state license in the same jurisdiction and
license type" and ensure the following sentence about privilege generation
references that same phrase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb4f3d7c-7f8e-48b8-92e0-24cfbeb3875e

📥 Commits

Reviewing files that changed from the base of the PR and between 4a7d692 and 9107ce3.

📒 Files selected for processing (14)
  • backend/social-work-app/docs/design/README.md
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/social-work-app/lambdas/python/common/tests/resources/api/provider-detail-response.json
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-multi-state.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_sanitize_provider_data.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_search_providers.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_search_providers.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py

Comment thread backend/social-work-app/docs/design/README.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py (1)

136-147: ⚠️ Potential issue | 🔴 Critical

Pass license_scope to all callers of license encumbrance and LICENSE investigation event publishers.

  • publish_license_encumbrance_event(...) / publish_license_encumbrance_lifting_event(...) now require license_scope: str (no default). backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/encumbrance.py and backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py call these without license_scope=, which will raise TypeError at runtime (call sites use keyword args, so it’s missing kwarg—not positional misalignment).
  • publish_investigation_event(...) / publish_investigation_closed_event(...) call _resolve_investigation_license_scope(...), which raises ValueError when investigation_against == InvestigationAgainstEnum.LICENSE and license_scope is not provided. backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/investigation.py (LICENSE calls at ~120/~155 and closed at ~211/~269) and backend/compact-connect/lambdas/python/provider-data-v1/handlers/investigation.py (LICENSE calls at ~120/~155 and closed at ~211/~269) omit license_scope=.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py`
around lines 136 - 147, Callers must pass the new required license_scope
parameter into the license-related publishers and into the investigation
publishers so _resolve_investigation_license_scope won't raise; update calls to
publish_license_encumbrance_event and publish_license_encumbrance_lifting_event
in the encumbrance handlers to include license_scope=<value>, and update calls
to publish_investigation_event and publish_investigation_closed_event in the
investigation handlers to include license_scope=<value> (or compute and pass the
same scope used for the investigation); ensure any code that relies on
_resolve_investigation_license_scope now supplies license_scope when
investigation_against == InvestigationAgainstEnum.LICENSE.
🧹 Nitpick comments (1)
backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py (1)

98-102: ⚡ Quick win

Verify license filtering logic handles both single-state and multi-state records correctly.

The test now expects exactly 2 license records and selects the single-state variant for comparison. However, the test only validates the single-state license against the expected fixture. Consider adding assertions for the multi-state license as well to ensure both ingestion paths work correctly.

🧪 Suggested enhancement to validate both licenses
 # One record for each of: provider and license (no privileges in Social Work model)
 license_records = [r for r in provider_user_records.provider_records if r['type'] == 'license']
 self.assertEqual(2, len(license_records))
 records = {item['type']: item for item in provider_user_records.provider_records if item['type'] != 'license'}
 records['license'] = next(r for r in license_records if r['licenseScope'] == 'single-state')
+
+# Also verify the multi-state license exists and has correct scope
+multi_state_license = next(r for r in license_records if r['licenseScope'] == 'multi-state')
+self.assertEqual('multi-state', multi_state_license['licenseScope'])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py`
around lines 98 - 102, The test currently finds two license_records and only
asserts the single-state license; update the test in
test_provider_transformations.py to also locate the multi-state license (e.g.,
use next(r for r in license_records if r['licenseScope'] == 'multi-state' or
r['licenseScope'] != 'single-state')) and add assertions that its fields match
the expected fixture for multi-state licenses (similar to how the single-state
license is validated), referencing provider_user_records.provider_records, the
license_records list, and the records dict to locate and validate both license
variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/social-work-app/lambdas/python/search/handlers/public_search.py`:
- Line 264: You removed the per-license-type scoping by setting nested_must:
list[dict] = [] in public_search.py which can return multiple license variants
per provider; either restore the original nested filter (add back a clause on
"licenses.mostRecentLicenseForType": True inside the nested_must used by
build_public_search_query / the provider licensing nested query) or implement
explicit provider-level deduping after search results (collapse by provider ID
and adjust total counts/pagination), and update the associated tests expecting
single-license-per-provider cardinality and any frontend pagination/count
expectations accordingly.

---

Outside diff comments:
In `@backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py`:
- Around line 136-147: Callers must pass the new required license_scope
parameter into the license-related publishers and into the investigation
publishers so _resolve_investigation_license_scope won't raise; update calls to
publish_license_encumbrance_event and publish_license_encumbrance_lifting_event
in the encumbrance handlers to include license_scope=<value>, and update calls
to publish_investigation_event and publish_investigation_closed_event in the
investigation handlers to include license_scope=<value> (or compute and pass the
same scope used for the investigation); ensure any code that relies on
_resolve_investigation_license_scope now supplies license_scope when
investigation_against == InvestigationAgainstEnum.LICENSE.

---

Nitpick comments:
In
`@backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py`:
- Around line 98-102: The test currently finds two license_records and only
asserts the single-state license; update the test in
test_provider_transformations.py to also locate the multi-state license (e.g.,
use next(r for r in license_records if r['licenseScope'] == 'multi-state' or
r['licenseScope'] != 'single-state')) and add assertions that its fields match
the expected fixture for multi-state licenses (similar to how the single-state
license is validated), referencing provider_user_records.provider_records, the
license_records list, and the records dict to locate and validate both license
variants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b88af71-832c-49a6-9cd2-5cdbe9ada4ae

📥 Commits

Reviewing files that changed from the base of the PR and between 9107ce3 and 7334611.

📒 Files selected for processing (22)
  • backend/cosmetology-app/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/docs/design/README.md
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/social-work-app/lambdas/python/common/tests/resources/api/license-post-multi-state.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_investigation_event_bus_client.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py
  • backend/social-work-app/lambdas/python/search/handlers/public_search.py
  • backend/social-work-app/lambdas/python/search/opensearch_client.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_populate_provider_documents.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
💤 Files with no reviewable changes (7)
  • backend/cosmetology-app/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_populate_provider_documents.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_provider_update_ingest.py
  • backend/social-work-app/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
✅ Files skipped from review due to trivial changes (2)
  • backend/social-work-app/lambdas/python/common/tests/resources/api/license-post-multi-state.json
  • backend/social-work-app/docs/design/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py (1)

141-153: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Propagate licenseScope into the update/deactivation path.

This match now correctly treats licenseScope as part of license identity, but the follow-on licenseUpdate record and deactivation event still identify the license only by jurisdiction + licenseType. If a provider has both single-state and multi-state versions of the same license in one jurisdiction, downstream consumers cannot tell which record changed.

Suggested direction
 def _process_license_update(*, existing_license: dict, new_license: dict, dynamo_transactions: list, data_events: list):
     ...
             license_deactivation_event = config.event_bus_client.generate_license_deactivation_event(
                 source='org.compactconnect.provider-data',
                 compact=existing_license['compact'],
                 jurisdiction=existing_license['jurisdiction'],
                 provider_id=existing_license['providerId'],
                 license_type=existing_license['licenseType'],
+                license_scope=existing_license['licenseScope'],
             )
     ...

 def _populate_update_record(*, existing_license: dict, updated_values: dict, removed_values: dict) -> dict:
     return license_update_schema.dump(
         {
             'type': ProviderRecordType.LICENSE_UPDATE,
             'updateType': update_type,
             'providerId': existing_license['providerId'],
             'compact': existing_license['compact'],
             'jurisdiction': existing_license['jurisdiction'],
             'licenseType': existing_license['licenseType'],
+            'licenseScope': existing_license['licenseScope'],
             ...
         }
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py`
around lines 141 - 153, The update/deactivation path is not including
licenseScope in the identity fields, so _process_license_update and subsequent
licenseUpdate/deactivation events cannot distinguish same
jurisdiction+licenseType with different licenseScope; update the code that
builds the existing_license dict (from existing_license_data.to_dict()) and the
payloads created inside _process_license_update and any deactivation event
generator to include the posted_license_record['licenseScope'] (or
existing_license['licenseScope']) as part of the identifying key, and ensure any
functions that emit a licenseUpdate/deactivation event (references:
_process_license_update, any deactivation event builder) use jurisdiction +
licenseType + licenseScope when constructing the record identity and event
payload so downstream consumers can disambiguate single-state vs multi-state
licenses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py`:
- Around line 141-153: The update/deactivation path is not including
licenseScope in the identity fields, so _process_license_update and subsequent
licenseUpdate/deactivation events cannot distinguish same
jurisdiction+licenseType with different licenseScope; update the code that
builds the existing_license dict (from existing_license_data.to_dict()) and the
payloads created inside _process_license_update and any deactivation event
generator to include the posted_license_record['licenseScope'] (or
existing_license['licenseScope']) as part of the identifying key, and ensure any
functions that emit a licenseUpdate/deactivation event (references:
_process_license_update, any deactivation event builder) use jurisdiction +
licenseType + licenseScope when constructing the record identity and event
payload so downstream consumers can disambiguate single-state vs multi-state
licenses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9c39bd6-06b7-441d-823f-807342d07227

📥 Commits

Reviewing files that changed from the base of the PR and between 7334611 and 67018ae.

📒 Files selected for processing (6)
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
✅ Files skipped from review due to trivial changes (1)
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py

We will not trigger a home state change if a multi-state license is
uploaded from a new jurisdiction, but there is no associated single
state license uploaded from that home state.

This means when generating the details for the provider to be returned
from the API, the home multi-state license will be the license which
was most recently issued AND has an associated single-state license on
record. Any multi-state license without an associated single-state license
will not be considered a home state license, regardless of how recently
it was issued.
Updating the ingest flow to only change the home jurisdiction if
both the single state and multi-state licenses have been uploaded for
a new jurisdiction.

@ChiefStief ChiefStief left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good to me. Well want to get those questions we spoke about earlier answered.

I believe we do actually have a bug with the rollback logic in that it can leave a provider with a different home state license than before the bad license data + rollback in this scenario and maybe a few others:

IN uploads single state expiring in 2022
OH uploads single state expiring in 2023

Home state is still IN.

OK uploads multi state and single state,
Ok gets rolled back.

Algorithm gets best license => OH 2023

So the home state would then be OH 2023 when it should be IN 2022.

The "best license" logic alone cant be applied here because its not how the home state is determined, date of upload is also relevant

@ChiefStief ChiefStief left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok looks good!

@landonshumway-ia

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is ready for your review. Thanks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py (1)

663-672: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep orphan detection scope-aware too.

This loop now rolls licenses back by (jurisdiction, licenseType, licenseScope), but _check_for_orphaned_update_records() still treats updates as unique only by (jurisdiction, licenseType). A missing multi-state top-level license can therefore be masked by an existing single-state record for the same jurisdiction/type, and rollback will proceed on inconsistent data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py`
around lines 663 - 672, The orphan-detection function
_check_for_orphaned_update_records currently deduplicates updates by
(jurisdiction, licenseType) but the rollback loop operates on (jurisdiction,
licenseType, licenseScope); update _check_for_orphaned_update_records to accept
license_scope (or derive it from passed records) and include licenseScope in the
uniqueness key/filter so it only treats updates as unique when jurisdiction,
licenseType AND licenseScope match (adjust callers such as the loop that uses
provider_records.get_update_records_for_license and any other callers to pass
the license_scope as needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/social-work-app/docs/devops/STAFF_USER_MFA_RECOVERY.md`:
- Line 49: Edit the sentence that currently reads "we use this field in the
index used to lookup the list of staff users" and replace "lookup" with the
two-word form "look up"; the change is in the STAFF_USER_MFA_RECOVERY document
and refers to the `famGiv` index field—update that sentence so it reads "...used
to look up the list of staff users" to correct the user-facing runbook text.

In
`@backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py`:
- Around line 708-711: When is_public_response is True the code currently calls
find_most_recent_licenses_for_each_license_type(LicenseScopeEnum.MULTI_STATE)
which drops license types that only have SINGLE_STATE records; change the logic
to build a per-license-type fallback: retrieve multi-state and single-state
recent maps (e.g., call find_most_recent_licenses_for_each_license_type twice
with LicenseScopeEnum.MULTI_STATE and LicenseScopeEnum.SINGLE_STATE or call once
for all and partition by scope), then for each license type select the
MULTI_STATE record if present otherwise the SINGLE_STATE record and assign that
to license_records; update the branch that sets license_records (referencing
is_public_response, find_most_recent_licenses_for_each_license_type,
LicenseScopeEnum.MULTI_STATE, LicenseScopeEnum.SINGLE_STATE) accordingly.
- Around line 540-554: The current loop filters multi-state licenses by whether
find_matching_single_state_license_for_multi_state_license(lic) returns non-None
before grouping, which lets an older paired multi-state replace a newer unpaired
one; instead, group self._license_records by lic.licenseType for records where
lic.licenseScope == LicenseScopeEnum.MULTI_STATE.value, sort each group with
_license_sort_key, pick the most_recent_multi_state for that type, then call
find_matching_single_state_license_for_multi_state_license(most_recent_multi_state)
and only append that most recent when the match exists (do not fall back to
older records); update the logic around by_type, sorted_multi_state,
most_recent_multi_state and the final append to enforce “newest-only” behavior.

In
`@backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py`:
- Around line 748-788: The provider-update selection must be constrained to the
same jurisdiction(s) as the upload being rolled back: modify the loop that
builds eligible_provider_updates_in_window (which iterates over
provider_records.get_all_provider_update_records and checks
LICENSE_UPLOAD_UPDATE_CATEGORIES) to also check that the provider_update affects
the same jurisdiction as the rollback target (e.g., compare
provider_update.jurisdiction / provider_update.target_jurisdiction /
provider_update.previous.homeJurisdiction — or whichever jurisdiction field
exists on ProviderUpdate — against the jurisdiction(s) of the licenses being
removed from provider_records.get_license_records()); only then add to
eligible_provider_updates_in_window and delete its DB record and consider it for
provider_revert_target (used with
ProviderData.create_new(earliest_provider_update.previous)).

---

Outside diff comments:
In
`@backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py`:
- Around line 663-672: The orphan-detection function
_check_for_orphaned_update_records currently deduplicates updates by
(jurisdiction, licenseType) but the rollback loop operates on (jurisdiction,
licenseType, licenseScope); update _check_for_orphaned_update_records to accept
license_scope (or derive it from passed records) and include licenseScope in the
uniqueness key/filter so it only treats updates as unique when jurisdiction,
licenseType AND licenseScope match (adjust callers such as the loop that uses
provider_records.get_update_records_for_license and any other callers to pass
the license_scope as needed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4589fb28-b444-4ce7-9034-924b1133a269

📥 Commits

Reviewing files that changed from the base of the PR and between 7334611 and bf2303f.

📒 Files selected for processing (45)
  • .github/workflows/check-for-external-state-api-spec-update.yml
  • .github/workflows/check-for-internal-api-spec-update.yml
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/cosmetology-app/docs/devops/STAFF_USER_MFA_RECOVERY.md
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/docs/api-specification/latest-oas30.json
  • backend/social-work-app/docs/devops/STAFF_USER_MFA_RECOVERY.md
  • backend/social-work-app/docs/internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/internal/postman/postman-collection.json
  • backend/social-work-app/docs/postman/postman-collection.json
  • backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/search-internal/postman/postman-collection.json
  • backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts
  • backend/social-work-app/lambdas/nodejs/lib/models/event-records.ts
  • backend/social-work-app/lambdas/nodejs/tests/lib/email/ingest-event-email-service.test.ts
  • backend/social-work-app/lambdas/nodejs/tests/sample-records.ts
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py
  • backend/social-work-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/social-work-app/lambdas/python/common/tests/function/test_data_client.py
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-update.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py
  • backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py
  • backend/social-work-app/tests/smoke/investigation_smoke_tests.py
  • backend/social-work-app/tests/smoke/license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/rollback_license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/smoke_common.py
💤 Files with no reviewable changes (1)
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
✅ Files skipped from review due to trivial changes (3)
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-update.json
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
🚧 Files skipped from review as they are similar to previous changes (11)
  • backend/social-work-app/tests/smoke/rollback_license_upload_smoke_tests.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/tests/function/test_data_client.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py

Comment thread backend/social-work-app/docs/devops/STAFF_USER_MFA_RECOVERY.md
Also added check to skip rollback if any license
updates occurred in other jurisdictions, as this
will require manual review for any home jurisdiction
changes.

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good stuff! Few comments/questions.

Comment thread backend/social-work-app/docs/README.md
Comment thread backend/social-work-app/docs/devops/STAFF_USER_MFA_RECOVERY.md Outdated
Comment thread backend/cosmetology-app/docs/devops/STAFF_USER_MFA_RECOVERY.md Outdated
Comment thread backend/social-work-app/docs/design/README.md Outdated
Comment thread backend/social-work-app/docs/design/README.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py (1)

52-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add licenseScope field to HomeJurisdictionChangeEventDetailSchema for audit consistency.

The event schema for home jurisdiction changes includes licenseType (line 54) but omits licenseScope. According to the PR objectives, home jurisdiction changes are triggered when a multi-state license is the most recent across jurisdictions, making license scope a key part of the triggering logic. For audit traceability and consistency with the other four event detail schemas in this file (which all include licenseScope), this event should capture which license scope caused the home jurisdiction change.

✨ Suggested addition
 class HomeJurisdictionChangeEventDetailSchema(DataEventDetailBaseSchema):
     providerId = UUID(required=True, allow_none=False)
     licenseType = String(required=True, allow_none=False)
+    licenseScope = LicenseScopeField(required=True, allow_none=False)
     formerHomeJurisdiction = Jurisdiction(required=True, allow_none=False)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py`
around lines 52 - 55, HomeJurisdictionChangeEventDetailSchema is missing the
licenseScope field used for audit consistency; update the class
(HomeJurisdictionChangeEventDetailSchema) to include licenseScope with the same
type and validation as the other event detail schemas (e.g. add licenseScope =
LicenseScope(required=True, allow_none=False)) alongside providerId, licenseType
and formerHomeJurisdiction, and ensure the LicenseScope symbol is imported if
not already.
♻️ Duplicate comments (1)
backend/social-work-app/docs/design/README.md (1)

333-333: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix spacing in "theSocial WorkCompact".

Should be "the Social Work Compact" with proper spacing.

📝 Proposed fix
-Privileges are authorizations that allow licensed providers to practice their profession in jurisdictions other than their home state. The Social Work Compact follows a multi-state licensure model, where privileges are automatically granted to a licensee as a result of having a multi-state license in their home state. The list of jurisdictions where privileges are granted is determined by which states have onboarded into the CompactConnect system for theSocial WorkCompact.
+Privileges are authorizations that allow licensed providers to practice their profession in jurisdictions other than their home state. The Social Work Compact follows a multi-state licensure model, where privileges are automatically granted to a licensee as a result of having a multi-state license in their home state. The list of jurisdictions where privileges are granted is determined by which states have onboarded into the CompactConnect system for the Social Work Compact.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/docs/design/README.md` at line 333, Replace the
typographical error "theSocial WorkCompact" in the README content with the
correct spaced phrase "the Social Work Compact"; locate the exact string
"theSocial WorkCompact" in the documentation text and update it to "the Social
Work Compact" so spacing is correct throughout the sentence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py`:
- Around line 911-914: The assertions currently check
provider_data_after_ky_single_state which was captured before the single-state
KY upload; update the two assertions to use the response object returned by the
step-5 ingestion call (the variable that holds the latest response from the KY
single-state upload, e.g., response_after_ky_single_state or whatever variable
name you used for the step-5 result) and assert its 'licenseJurisdiction' and
'givenName' fields instead of the earlier snapshot variable.

---

Outside diff comments:
In
`@backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py`:
- Around line 52-55: HomeJurisdictionChangeEventDetailSchema is missing the
licenseScope field used for audit consistency; update the class
(HomeJurisdictionChangeEventDetailSchema) to include licenseScope with the same
type and validation as the other event detail schemas (e.g. add licenseScope =
LicenseScope(required=True, allow_none=False)) alongside providerId, licenseType
and formerHomeJurisdiction, and ensure the LicenseScope symbol is imported if
not already.

---

Duplicate comments:
In `@backend/social-work-app/docs/design/README.md`:
- Line 333: Replace the typographical error "theSocial WorkCompact" in the
README content with the correct spaced phrase "the Social Work Compact"; locate
the exact string "theSocial WorkCompact" in the documentation text and update it
to "the Social Work Compact" so spacing is correct throughout the sentence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e538a51-08f8-461f-99ee-32e859f437dd

📥 Commits

Reviewing files that changed from the base of the PR and between 7334611 and 58d3dea.

⛔ Files ignored due to path filters (1)
  • backend/social-work-app/docs/design/practitioner-home-state-assignment.pdf is excluded by !**/*.pdf
📒 Files selected for processing (46)
  • .github/workflows/check-for-external-state-api-spec-update.yml
  • .github/workflows/check-for-internal-api-spec-update.yml
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/cosmetology-app/docs/devops/STAFF_USER_MFA_RECOVERY.md
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/docs/api-specification/latest-oas30.json
  • backend/social-work-app/docs/design/README.md
  • backend/social-work-app/docs/devops/STAFF_USER_MFA_RECOVERY.md
  • backend/social-work-app/docs/internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/internal/postman/postman-collection.json
  • backend/social-work-app/docs/postman/postman-collection.json
  • backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/search-internal/postman/postman-collection.json
  • backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts
  • backend/social-work-app/lambdas/nodejs/lib/models/event-records.ts
  • backend/social-work-app/lambdas/nodejs/tests/lib/email/ingest-event-email-service.test.ts
  • backend/social-work-app/lambdas/nodejs/tests/sample-records.ts
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/social-work-app/lambdas/python/common/cc_common/event_bus_client.py
  • backend/social-work-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/social-work-app/lambdas/python/common/tests/function/test_data_client.py
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-update.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py
  • backend/social-work-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py
  • backend/social-work-app/tests/smoke/investigation_smoke_tests.py
  • backend/social-work-app/tests/smoke/license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/rollback_license_upload_smoke_tests.py
  • backend/social-work-app/tests/smoke/smoke_common.py
💤 Files with no reviewable changes (1)
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
✅ Files skipped from review due to trivial changes (3)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/social-work-app/docs/postman/postman-collection.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • backend/social-work-app/lambdas/python/common/tests/resources/dynamo/license-update.json
  • backend/social-work-app/tests/smoke/rollback_license_upload_smoke_tests.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/social-work-app/lambdas/python/data-events/tests/function/test_encumbrance_events.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/tests/function/test_data_client.py

The previous implementation was not taking into consideration that
the best license is the most recent multi-state license that has an
associated single-state license. This updates the implementation and
adds test cases for each of the conditional branches.
These methods do not need to be referenced outside of the
ProviderUserRecords class, as they are primarily intended to be used
to generate the api response and opensearch documents. Keeping them
private will help encapsulate the logic for finding home state licenses.
@landonshumway-ia

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is now ready for another review. Thanks

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@isabeleliassen This is good to merge!

@isabeleliassen isabeleliassen merged commit 22cdad0 into csg-org:main Jun 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SW - License type design/implementation to support both single and multi-state licenses

4 participants