Skip to content

Conversation

@landonshumway-ia
Copy link
Collaborator

@landonshumway-ia landonshumway-ia commented Oct 7, 2025

Several states have requested the ability to set multiple NPDB categories when placing an encumbrance on a license or privilege. This adds support for that in a backwards incompatible manner by adding a new field to the adverse action data type and using that field creating these encumbrance records in the system.

Closes #1090

Summary by CodeRabbit

  • New Features
    • Support for multiple clinical privilege action categories (clinicalPrivilegeActionCategories); singular category deprecated and auto-migrated.
  • Bug Fixes
    • License encumbrance now validates adverse-action existence and fails fast with clear error logging when missing.
  • Refactor
    • Provider-management endpoints wired to centralized handlers; encumbrance event payloads no longer include deprecated adverseActionCategory.
  • Tests
    • Unit, integration and smoke tests updated for migration, validation and feature-flag scenarios.
  • Chores
    • Feature flag added for multi-category rollout; API_BASE_URL env propagated and alarm descriptions updated.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Feature-flagged migration from a single clinicalPrivilegeActionCategory to plural clinicalPrivilegeActionCategories across schemas, records, handlers, tests, and events; removed adverse_action_category from several event/public payloads; added ProviderManagement lambdas to ApiLambdaStack and introduced an encumbrance feature flag and environment propagation.

Changes

Cohort / File(s) Summary
Data client & event publishing
backend/.../data_model/data_client.py, backend/.../event_bus_client.py
Feature-flagged selection of encumbrance details (plural vs singular); added adverse-action existence check on license path; removed adverse_action_category from license event payloads and signatures; lazy feature-flag import.
Provider utilities & records
backend/.../data_model/provider_record_util.py
Added get_adverse_action_by_id on ProviderRecordUtility/ProviderUserRecords; feature-flagged privilege-history note construction to read plural or singular category fields.
Adverse action schema & record
backend/.../schema/adverse_action/__init__.py, .../api.py, .../record.py
Introduced clinicalPrivilegeActionCategories (List[str]) and deprecated singular clinicalPrivilegeActionCategory; added validators enforcing mutual exclusivity and pre_load/pre_dump migration hooks.
Privilege record schema
backend/.../schema/privilege/record.py
Added clinicalPrivilegeActionCategories to EncumbranceDetailsSchema; retained deprecated singular accessor and TODOs.
Encumbrance handlers
backend/.../data-events/handlers/encumbrance_events.py, backend/.../provider-data-v1/handlers/encumbrance.py
Removed extraction/logging/passing of deprecated adverse_action_category; feature-flagged adverse-action creation/population to support both singular (deprecated) and plural forms.
Tests (unit/functional/smoke)
backend/.../tests/unit/*, backend/.../data-events/tests/*, backend/.../provider-data-v1/tests/*, backend/.../tests/smoke/*
Updated fixtures/snapshots and assertions to use plural field; added feature-flag mocks/patches and extra test wiring; inserted adverse-action records in flows; adapted test signatures; smoke helpers query ProviderUserRecords.
API models & snapshots
backend/.../stacks/api_stack/v1_api/api_model.py, backend/.../tests/resources/snapshots/*, backend/.../*_REQUEST_SCHEMA.json
OpenAPI/schema snapshots and request models include clinicalPrivilegeActionCategories (array), mark singular deprecated, and remove singular from required lists.
ApiLambdaStack / Provider-management refactor
backend/.../stacks/api_lambda_stack/__init__.py, .../provider_management.py, .../v1_api/provider_management.py, .../stacks/api_stack/v1_api/api.py
Added ProviderManagementLambdas to ApiLambdaStack; refactored API stack to reference pre-created provider-management handlers (constructor/signature and wiring changes); tests updated for separate lambda stack assertions.
Feature flags & env propagation
backend/.../stacks/feature_flag_stack/__init__.py, .../event_listener_stack/__init__.py
Added encumbrance-multi-category-flag (auto-enabled for TEST); conditional injection of API_BASE_URL into event-listener env when API_DOMAIN_NAME present.
Misc / generators / retention policy
backend/.../common_test/test_data_generator.py, backend/.../tests/smoke/*, backend/.../common_constructs/python_common_layer_versions.py
Default adverse-action generator now emits plural field (single-element list); smoke tests use ProviderUserRecords helper; simplified Python layer retention policy to RETAIN.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as API (Provider)
  participant Handler as Encumbrance Handler
  participant FF as FeatureFlagClient
  participant Schema as AdverseAction Schema
  participant DB as Provider DB
  participant Bus as Event Bus
  participant Worker as Data-Events
  participant DC as DataClient

  Client->>API: POST /encumbrances
  API->>Handler: invoke handler with payload
  Handler->>FF: is_feature_enabled("encumbrance-multi-category-flag")
  alt Flag ON
    Handler->>Schema: build adverseAction with clinicalPrivilegeActionCategories (list)
  else Flag OFF
    Handler->>Schema: build adverseAction with clinicalPrivilegeActionCategory (string)
  end
  Handler->>DB: persist adverseAction
  Handler->>Bus: publish encumbrance event (adverseActionId)  %% adverseActionCategory removed
  Bus->>Worker: deliver event
  Worker->>FF: is_feature_enabled("encumbrance-multi-category-flag")
  Worker->>DC: encumber_* (select encumbranceDetails: categories vs category)
  DC->>DB: update privilege/license encumbranceDetails
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • jlkravitz
  • jusdino

Poem

I twitch my whiskers, hop and sing,
One category grew into a ring.
Flags decide if many or one,
Lambdas hum and work gets done.
A rabbit cheers: "Tests — hop on!" 🐇

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request contains several identifiable out-of-scope changes unrelated to supporting multiple NPDB categories for encumbrances. In python_common_layer_versions.py, a conditional layer removal_policy was replaced with a constant RETAIN value, which is an infrastructure/deployment change unrelated to the feature. Test formatting changes in test_transaction_reporting.py (collapsing ReportWindow constructor to single line) are cosmetic and unrelated. An IAM policy safety improvement in test_purchases_api.py (switching from direct indexing to .get() with default) is a best practice but not necessary for this feature. While most of the PR is well-scoped to the core feature, these auxiliary changes suggest scope creep or mixed concerns in a single PR. Remove or defer the out-of-scope changes to separate PRs: (1) revert the layer version retention policy change in python_common_layer_versions.py unless it is critical for this feature; (2) remove cosmetic formatting changes from test_transaction_reporting.py; (3) move the IAM policy safety improvement in test_purchases_api.py to a dedicated improvements PR. Keep the PR focused on supporting multiple NPDB categories for encumbrances to maintain clear commit history and simplify review.
Description Check ⚠️ Warning The pull request description does not follow the required template structure defined in the repository. The template specifies three mandatory sections: Requirements List, Description List, and Testing List with specific testing items (yarn commands, CDK tests, OpenAPI spec updates, etc.). The provided description contains only a brief explanation of the feature and a "Closes" reference, entirely omitting the required sections. While the description is not vague and does communicate the general intent of the change, it fails to meet the template's structural requirements for a high-complexity PR affecting numerous files across data models, API schemas, handlers, tests, and CDK stacks.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "support list of npdb categories for encumbrances" clearly and concisely summarizes the main change in the changeset. The raw summary confirms that the primary objective is adding support for multiple clinical privilege action categories (NPDB categories) when placing encumbrances on licenses and privileges. The title is specific, avoids vague terminology, and accurately reflects the core feature being introduced.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@landonshumway-ia landonshumway-ia force-pushed the feat/encumbrance-multiple-npdb-categories branch 2 times, most recently from b2db442 to 6e22f88 Compare October 9, 2025 17:19
@landonshumway-ia landonshumway-ia marked this pull request as ready for review October 15, 2025 14:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)

78-79: Validator bug: use enum values, not enum instances

actionAgainst is a String; OneOf should validate against [e.value], not [e].

Apply this diff:

-    actionAgainst = String(required=True, allow_none=False, validate=OneOf([e for e in AdverseActionAgainstEnum]))
+    actionAgainst = String(
+        required=True,
+        allow_none=False,
+        validate=OneOf([e.value for e in AdverseActionAgainstEnum]),
+    )
🧹 Nitpick comments (18)
backend/compact-connect/stacks/event_listener_stack/__init__.py (1)

38-41: Approve conditional API_BASE_URL propagation; add domain‐format validation

The conditional update is correct and safe, but there’s no existing validation of api_domain_name. To prevent malformed URLs if a protocol prefix is passed, strip any http:///https:// before constructing API_BASE_URL.

Consider:

 if persistent_stack.api_domain_name:
+    domain = persistent_stack.api_domain_name.removeprefix('https://').removeprefix('http://')
     self.common_env_vars.update({'API_BASE_URL': f'https://{domain}'})
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (1)

93-99: Consider validating non-empty list in setter.

The clinicalPrivilegeActionCategories setter accepts list[str] but doesn't validate that the list is non-empty. If an empty list is semantically invalid (which seems likely for NPDB categories), consider adding validation.

Apply this diff to add validation:

 @clinicalPrivilegeActionCategories.setter
 def clinicalPrivilegeActionCategories(self, value: list[str]) -> None:
+    if not value:
+        raise ValueError('clinicalPrivilegeActionCategories must be a non-empty list')
     self._data['clinicalPrivilegeActionCategories'] = value
backend/compact-connect/stacks/api_stack/v1_api/api_model.py (3)

417-436: Encumbrance categories: add validation (exclusivity, min_items, unique_items).

Currently both fields can be sent and arrays can be empty/duplicated. Recommend:

  • Enforce exactly one of single vs. list (one_of).
  • Require at least 1 category and unique values.

Apply:

         self.api._v1_post_privilege_encumbrance_request_model = self.api.add_model(
@@
             schema=JsonSchema(
                 type=JsonSchemaType.OBJECT,
                 additional_properties=False,
-                required=['encumbranceEffectiveDate', 'encumbranceType'],
+                required=['encumbranceEffectiveDate', 'encumbranceType'],
                 properties={
@@
-                    'clinicalPrivilegeActionCategories': JsonSchema(
-                        type=JsonSchemaType.ARRAY,
-                        description='The categories of clinical privilege action',
-                        items=JsonSchema(type=JsonSchemaType.STRING),
-                    ),
+                    'clinicalPrivilegeActionCategories': JsonSchema(
+                        type=JsonSchemaType.ARRAY,
+                        description='The categories of clinical privilege action',
+                        min_items=1,
+                        unique_items=True,
+                        items=JsonSchema(type=JsonSchemaType.STRING),
+                    ),
                 },
+                one_of=[
+                    JsonSchema(required=['clinicalPrivilegeActionCategory']),
+                    JsonSchema(required=['clinicalPrivilegeActionCategories']),
+                ],
             ),

454-473: Mirror validation on license encumbrance request.

Same constraints should apply here for consistency.

         self.api._v1_post_license_encumbrance_request_model = self.api.add_model(
@@
             schema=JsonSchema(
                 type=JsonSchemaType.OBJECT,
                 additional_properties=False,
-                required=['encumbranceEffectiveDate', 'encumbranceType'],
+                required=['encumbranceEffectiveDate', 'encumbranceType'],
                 properties={
@@
-                    'clinicalPrivilegeActionCategories': JsonSchema(
-                        type=JsonSchemaType.ARRAY,
-                        description='The categories of clinical privilege action',
-                        items=JsonSchema(type=JsonSchemaType.STRING),
-                    ),
+                    'clinicalPrivilegeActionCategories': JsonSchema(
+                        type=JsonSchemaType.ARRAY,
+                        description='The categories of clinical privilege action',
+                        min_items=1,
+                        unique_items=True,
+                        items=JsonSchema(type=JsonSchemaType.STRING),
+                    ),
                 },
+                one_of=[
+                    JsonSchema(required=['clinicalPrivilegeActionCategory']),
+                    JsonSchema(required=['clinicalPrivilegeActionCategories']),
+                ],
             ),

1217-1223: Response shape: mark single field as deprecated; keep list as canonical.

Minor polish: annotate the single field as deprecated in the response too.

-                                        'clinicalPrivilegeActionCategory': JsonSchema(type=JsonSchemaType.STRING),
+                                        'clinicalPrivilegeActionCategory': JsonSchema(
+                                            type=JsonSchemaType.STRING,
+                                            description='(Deprecated) Use clinicalPrivilegeActionCategories'
+                                        ),

Repeat for both adverseActions response blocks.

Also applies to: 1358-1364

backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1)

32-41: Schema accepts new plural field; consider validation refinements

  • Deprecation notice is clear.
  • Consider enforcing at least one category and uniqueness, and allowing either singular or plural during migration via anyOf.

Suggested additions:

     "clinicalPrivilegeActionCategories": {
       "description": "The categories of clinical privilege action",
       "items": {
         "type": "string"
       },
-      "type": "array"
+      "type": "array",
+      "minItems": 1,
+      "uniqueItems": true
     }
   },
+  "anyOf": [
+    { "required": ["clinicalPrivilegeActionCategories"] },
+    { "required": ["clinicalPrivilegeActionCategory"] }
+  ],
   "required": [
     "encumbranceEffectiveDate",
     "encumbranceType"
   ],

Also applies to: 45-45

backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)

380-387: Avoid repeated remote flag checks inside the loop

is_feature_enabled likely makes a network call; calling it per event is wasteful. Cache its result once before iterating.

Apply this diff:

-                from cc_common.feature_flag_client import is_feature_enabled
-
-                if is_feature_enabled('encumbrance-multi-category-flag'):
+                from cc_common.feature_flag_client import is_feature_enabled
+                # Cache the flag result outside the per-event path
+                flag_enabled = getattr(ProviderRecordUtility, '_encumbrance_multi_cat_flag', None)
+                if flag_enabled is None:
+                    flag_enabled = is_feature_enabled('encumbrance-multi-category-flag')
+                    # stash for the duration of this process; safe in Lambda container
+                    ProviderRecordUtility._encumbrance_multi_cat_flag = flag_enabled
+
+                if flag_enabled:
                     if 'clinicalPrivilegeActionCategory' in event['encumbranceDetails']:
                         event['note'] = event['encumbranceDetails'].get('clinicalPrivilegeActionCategory')
                     else:
                         # else we are using the new field, parse the list into a comma-separated string
                         event['note'] = ', '.join(event['encumbranceDetails']['clinicalPrivilegeActionCategories'])
                 else:
                     event['note'] = event['encumbranceDetails']['clinicalPrivilegeActionCategory']
backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (4)

106-122: Ensure unique privilege selection or assert expectation

You select the first NE privilege. If multiple NE privileges can exist, assert exactly one or select deterministically (e.g., by creationDate) to avoid flaky tests.


204-208: Harden error handling on non-200 responses

response.json() can raise if body isn’t JSON. Fall back to response.text to preserve the original error.

Apply this diff:

-        if response.status_code != 200:
-            operation = 'lift' if encumbrance_id else 'create'
-            raise SmokeTestFailureException(f'Failed to {operation} license encumbrance. Response: {response.json()}')
+        if response.status_code != 200:
+            operation = 'lift' if encumbrance_id else 'create'
+            try:
+                err = response.json()
+            except Exception:
+                err = response.text
+            raise SmokeTestFailureException(f'Failed to {operation} license encumbrance. Response: {err}')
-        if response.status_code != 200:
-            operation = 'lift' if encumbrance_id else 'create'
-            raise SmokeTestFailureException(f'Failed to {operation} privilege encumbrance. Response: {response.json()}')
+        if response.status_code != 200:
+            operation = 'lift' if encumbrance_id else 'create'
+            try:
+                err = response.json()
+            except Exception:
+                err = response.text
+            raise SmokeTestFailureException(f'Failed to {operation} privilege encumbrance. Response: {err}')

Also applies to: 234-238


283-289: Guarantee at least one polling attempt

If max_wait_time < check_interval, max_attempts becomes 0 and the loop never runs. Use ceil division with a floor of 1.

Apply this diff:

-        attempts = 0
-        max_attempts = max_wait_time // check_interval
+        attempts = 0
+        max_attempts = max(1, (max_wait_time + check_interval - 1) // check_interval)

Also applies to: 291-297, 301-307


512-546: Avoid duplicated state checks; reuse helper

You manually re-fetch and check license/provider state here. Consider calling validate_license_encumbered_state and validate_provider_encumbered_state for consistency and less duplication.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)

28-31: Enforce non-empty category list and mutual exclusivity

Allowing an empty list weakens prior semantics (singular was required). Add Length(min=1) to the list field to ensure at least one category when the plural is used; keep your exclusivity check.

Apply this diff:

-from marshmallow.validate import OneOf
+from marshmallow.validate import OneOf, Length
@@
-    clinicalPrivilegeActionCategories = List(ClinicalPrivilegeActionCategoryField(), required=False, allow_none=False)
+    clinicalPrivilegeActionCategories = List(
+        ClinicalPrivilegeActionCategoryField(),
+        required=False,
+        allow_none=False,
+        validate=Length(min=1),
+    )

Also applies to: 32-47

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (2)

49-60: Load-time migration is good; also drop deprecated field when both present

If both fields exist, drop the deprecated one to keep the in-memory object clean.

Apply this diff:

-    def migrate_clinical_privilege_action_category_on_load(self, in_data, **_kwargs):
+    def migrate_clinical_privilege_action_category_on_load(self, in_data, **_kwargs):
         """Migrate deprecated clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories list
         when loading from database."""
-        # If the deprecated field exists and the new field doesn't, migrate it
-        if 'clinicalPrivilegeActionCategory' in in_data and 'clinicalPrivilegeActionCategories' not in in_data:
-            in_data['clinicalPrivilegeActionCategories'] = [in_data['clinicalPrivilegeActionCategory']]
-            # Remove the deprecated field to avoid having both
-            del in_data['clinicalPrivilegeActionCategory']
+        if 'clinicalPrivilegeActionCategory' in in_data:
+            if 'clinicalPrivilegeActionCategories' not in in_data:
+                in_data['clinicalPrivilegeActionCategories'] = [in_data['clinicalPrivilegeActionCategory']]
+            # Remove the deprecated field to avoid having both
+            del in_data['clinicalPrivilegeActionCategory']
         return in_data

61-66: Pre-dump migration: also remove deprecated field to avoid writing both

You add the plural but leave the singular; consider removing the singular at dump time for DB cleanliness during the transition.

Apply this diff:

     def migrate_clinical_privilege_action_category(self, in_data, **_kwargs):
         """Migrate deprecated clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories list."""
-        # If the deprecated field exists and the new field doesn't, migrate it for backwards compatibility
-        if 'clinicalPrivilegeActionCategory' in in_data and 'clinicalPrivilegeActionCategories' not in in_data:
-            in_data['clinicalPrivilegeActionCategories'] = [in_data['clinicalPrivilegeActionCategory']]
+        # If the deprecated field exists and the new field doesn't, migrate it
+        if 'clinicalPrivilegeActionCategory' in in_data and 'clinicalPrivilegeActionCategories' not in in_data:
+            in_data['clinicalPrivilegeActionCategories'] = [in_data['clinicalPrivilegeActionCategory']]
+        # Remove deprecated field if present to avoid persisting both
+        if 'clinicalPrivilegeActionCategory' in in_data:
+            del in_data['clinicalPrivilegeActionCategory']
         return in_data

Also applies to: 76-83

backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (1)

60-66: Add provider_encumbrance_handler log group to API log_groups

You append log groups for other handlers, but not for provider_encumbrance_handler. Add it once to ensure logs are tracked with the API.

Apply this diff:

         # Reference lambda handlers from api_lambda_stack
         self.get_provider_handler = api_lambda_stack.provider_management_lambdas.get_provider_handler
         self.query_providers_handler = api_lambda_stack.provider_management_lambdas.query_providers_handler
         self.get_provider_ssn_handler = api_lambda_stack.provider_management_lambdas.get_provider_ssn_handler
         self.deactivate_privilege_handler = api_lambda_stack.provider_management_lambdas.deactivate_privilege_handler
         self.provider_encumbrance_handler = api_lambda_stack.provider_management_lambdas.provider_encumbrance_handler
+        self.api.log_groups.append(self.provider_encumbrance_handler.log_group)

Also applies to: 230-255, 275-317

backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (1)

114-120: Comment mismatch with assertions (minor).

The comment says “event was published for the affected privilege,” but two events are asserted (for 'ne' and 'ky'). Update the comment to reflect both events.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (2)

356-393: Comment text contradicts assertions (nit).

The comment says “deprecated field is not present” but the test asserts it IS present. Update the comment to reflect current migration behavior.

Apply this diff to fix the comment:

-        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
-        # Verify that the deprecated field is not present in the stored data
+        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
+        # Verify that the deprecated field is still present during migration

643-680: Same comment mismatch for license migration test (nit).

Adjust the comment to say the deprecated field is still present during migration.

-        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
-        # Verify that the deprecated field is not present in the stored data
+        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
+        # Verify that the deprecated field is still present during migration
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 397d423 and d304a9c.

📒 Files selected for processing (31)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py (0 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_provider_record_util.py (2 hunks)
  • backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (0 hunks)
  • backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (7 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (4 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py (3 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/__init__.py (3 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py (6 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (8 hunks)
  • backend/compact-connect/stacks/event_listener_stack/__init__.py (1 hunks)
  • backend/compact-connect/stacks/feature_flag_stack/__init__.py (1 hunks)
  • backend/compact-connect/tests/app/test_api/test_provider_management_api.py (23 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json (4 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ANOMALY_DETECTION_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ENDPOINT_DISABLED_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_READS_RATE_LIMITED_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json (4 hunks)
  • backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (16 hunks)
  • backend/compact-connect/tests/smoke/smoke_common.py (2 hunks)
💤 Files with no reviewable changes (2)
  • backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py
  • backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional).

Applied to files:

  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
📚 Learning: 2025-09-03T22:29:24.535Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py:0-0
Timestamp: 2025-09-03T22:29:24.535Z
Learning: The EncumbranceEventDetailSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py is used across multiple instances/contexts where adverseActionCategory and adverseActionId fields may be required in some cases and not present in others. This is an intentional design pattern for handling different event variants with a single schema.

Applied to files:

  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional). When working with encumbrance notes, use encumbranceDetails['clinicalPrivilegeActionCategory'], not encumbranceDetails['note'].

Applied to files:

  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
🧬 Code graph analysis (16)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (5)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (7)
  • PrivilegeUpdateData (91-162)
  • compact (28-29)
  • compact (108-109)
  • jurisdiction (32-33)
  • jurisdiction (112-113)
  • encumbranceDetails (151-155)
  • updatedValues (124-125)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (42-54)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • serialize_to_database_record (205-208)
  • from_database_record (124-139)
  • to_dict (169-180)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (9)
  • compact (27-28)
  • compact (31-32)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
  • AdverseActionData (14-147)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)
  • generate_default_privilege_update (329-356)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4)
backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py (1)
  • is_feature_enabled (46-112)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (8)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • adverseActionId (126-127)
  • adverseActionId (130-131)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)
  • get_adverse_action_by_id (525-535)
backend/compact-connect/lambdas/python/common/cc_common/exceptions.py (1)
  • CCInternalException (44-45)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (3)
backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py (1)
  • is_feature_enabled (46-112)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • ClinicalPrivilegeActionCategory (375-388)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)
backend/compact-connect/lambdas/python/common/cc_common/utils.py (1)
  • get (86-87)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • ClinicalPrivilegeActionCategory (375-388)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (2)
backend/compact-connect/common_constructs/ssm_parameter_utility.py (2)
  • SSMParameterUtility (8-43)
  • load_data_event_bus_from_ssm_parameter (26-43)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)
  • ProviderManagementLambdas (17-329)
backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (3)
backend/compact-connect/tests/smoke/smoke_common.py (2)
  • get_provider_user_records (251-264)
  • SmokeTestFailureException (13-17)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (7)
  • get_privilege_records (481-489)
  • get_specific_license_record (447-462)
  • get_specific_privilege_record (464-479)
  • get_provider_record (79-84)
  • get_provider_record (605-614)
  • get_adverse_action_records_for_license (507-523)
  • get_adverse_action_records_for_privilege (587-603)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • licenseTypeAbbreviation (156-167)
  • to_dict (169-180)
backend/compact-connect/tests/smoke/smoke_common.py (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)
  • ProviderUserRecords (403-791)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/tests/smoke/config.py (1)
  • provider_user_dynamodb_table (40-41)
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (2)
backend/compact-connect/tests/app/test_api/__init__.py (2)
  • TestApi (11-83)
  • generate_expected_integration_object_for_imported_lambda (48-83)
backend/compact-connect/tests/app/base.py (1)
  • get_resource_properties_by_logical_id (89-96)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (5)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (3)
  • put_default_adverse_action_record_in_provider_table (154-160)
  • put_default_provider_record_in_provider_table (409-429)
  • put_default_privilege_record_in_provider_table (310-320)
backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (1)
  • license_encumbrance_listener (186-243)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (3)
  • compact (28-29)
  • compact (108-109)
  • encumberedStatus (72-73)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • serialize_to_database_record (205-208)
  • PrivilegeEncumberedStatusEnum (317-321)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (6)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • adverseActionId (126-127)
  • adverseActionId (130-131)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (4)
backend/compact-connect/stacks/persistent_stack/event_bus.py (1)
  • EventBus (9-25)
backend/compact-connect/common_constructs/python_function.py (1)
  • PythonFunction (21-155)
backend/compact-connect/stacks/persistent_stack/__init__.py (1)
  • PersistentStack (38-510)
backend/compact-connect/lambdas/python/common/cc_common/config.py (6)
  • provider_table (88-89)
  • ssn_table (92-93)
  • ssn_index_name (179-180)
  • event_bus_name (84-85)
  • rate_limiting_table (282-283)
  • user_pool_id (191-195)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (2)
backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py (1)
  • is_feature_enabled (46-112)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (3)
  • AdverseActionData (14-147)
  • adverseActionId (126-127)
  • adverseActionId (130-131)
backend/compact-connect/stacks/feature_flag_stack/__init__.py (1)
backend/compact-connect/stacks/feature_flag_stack/feature_flag_resource.py (2)
  • FeatureFlagResource (23-72)
  • FeatureFlagEnvironmentName (15-20)
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (2)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)
  • ApiLambdaStack (15-60)
backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py (1)
  • _add_get_provider (74-106)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (39)
backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ENDPOINT_DISABLED_ALARM_SCHEMA.json (1)

2-2: LGTM!

The alarm description correctly reflects the migration to ApiLambdaStack/GetProviderSSNHandler from the previous APIStack/LicenseApi structure.

backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_READS_RATE_LIMITED_ALARM_SCHEMA.json (1)

2-2: LGTM!

Consistent with the stack refactoring that moves SSN handlers to ApiLambdaStack/GetProviderSSNHandler.

backend/compact-connect/stacks/api_stack/v1_api/api.py (1)

200-208: LGTM The ProviderManagement constructor includes the api_lambda_stack parameter.

backend/compact-connect/stacks/feature_flag_stack/__init__.py (1)

116-125: Approve feature flag addition: Verified ‘encumbrance-multi-category-flag’ is referenced in provider-data-v1 handlers and cc_common data model, matching rollout strategy.

backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1)

31-46: LGTM — existing schema enforces exactly one of clinicalPrivilegeActionCategory or clinicalPrivilegeActionCategories via validate_clinical_privilege_action_category_fields.

backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (1)

114-115: Accept dual presence of category fields in record serialization

Record schema’s pre_dump migration intentionally preserves both clinicalPrivilegeActionCategories and clinicalPrivilegeActionCategory for backward compatibility, so the test’s expectation is correct.

backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)

35-60: LGTM!

The integration of ProviderManagementLambdas is clean and follows the established pattern:

  • Data event bus is correctly loaded from SSM parameter to avoid cross-stack references
  • The lambda construct is properly initialized with all required dependencies (scope, persistent_stack, data_event_bus)
  • The approach is consistent with how FeatureFlagsLambdas and ProviderUsersLambdas are instantiated
backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json (1)

631-637: LGTM – Schema supports migration path.

The schema correctly adds the new clinicalPrivilegeActionCategories array field while keeping the deprecated clinicalPrivilegeActionCategory field. Neither is required, which allows for:

  1. Old clients to continue using the singular field
  2. New clients to use the plural field
  3. Gradual migration without breaking existing integrations

This aligns with the feature-flag driven migration strategy described in the PR.

Also applies to: 654-654

backend/compact-connect/tests/smoke/smoke_common.py (2)

33-33: LGTM!

Adding LICENSE_TYPES to the environment follows the established pattern for COMPACTS and JURISDICTIONS at lines 31-32.


251-264: LGTM – Well-structured helper function.

The get_provider_user_records function is a clean addition that:

  • Mirrors the pattern of get_all_provider_database_records (lines 238-248)
  • Returns a typed ProviderUserRecords object for better testability
  • Includes proper documentation
  • Uses consistent query patterns with KeyConditionExpression

This will enable more precise smoke test assertions using the typed helper methods from ProviderUserRecords.

backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json (1)

631-637: LGTM – Consistent schema migration.

This snapshot follows the same migration pattern as PROVIDER_USER_RESPONSE_SCHEMA.json:

  • Adds clinicalPrivilegeActionCategories array field (lines 631-637, 1443-1449)
  • Removes the singular field from required lists (lines 654, 1466)
  • Maintains the old field for backward compatibility

The consistency across schemas ensures a smooth migration path.

Also applies to: 654-654, 1443-1449, 1466-1466

backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

1430-1442: Feature-flagged encumbranceDetails handling looks good

Conditional population with plural/singular fields is correct and consistent with schema changes.


2676-2690: Plural/singular encumbranceDetails for license-driven privilege encumbrance

LGTM. Includes licenseJurisdiction and adverseActionId; aligns with EncumbranceDetails schema.

backend/compact-connect/lambdas/python/common/tests/unit/test_provider_record_util.py (2)

914-916: Flag mock for multi-category path

Mocking is_feature_enabled to True is appropriate to exercise the new path. Remember to remove with the flag.


995-999: Second flag-mocked test signature update

Looks good and consistent with the added decorator.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py (3)

83-86: Staff test data updated to plural categories

Good move to exercise multi-category support.


225-227: Flag mock for staff endpoint

Appropriate to align expectations with multi-category behavior. Remove once flag is retired.


260-261: Assertion updated to comma-separated categories

Matches the utility behavior (joining categories). Looks correct.

backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (5)

21-21: Import of provider_user_records util looks good

Switching tests to use ProviderUserRecords directly is a solid move for determinism and clarity.


340-346: Provider encumbered state validation via provider_user_records

Good direct validation against provider DynamoDB record.


351-360: Adverse action retrieval via ProviderUserRecords

Using typed accessors and returning to_dict() for assertions is clean and consistent.

Also applies to: 364-373


374-411: Adverse action vs request verification

Set-based comparison for categories is correct and order-agnostic. Consider normalizing case if upstream ever varies casing; otherwise this is solid.


503-506: Enum values validated
All encumbranceType and clinicalPrivilegeActionCategories strings in the smoke tests exactly match their respective enum definitions.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)

98-103: Response schema fields look aligned

Exposing clinicalPrivilegeActionCategories with deprecated singular alias is fine for transition. Consider adding a deprecation note in API docs.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1)

35-44: Record schema field changes look correct

Plural categories with deprecated singular alias is appropriate; validators and required flags match the migration strategy.

backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (1)

109-126: Lambda wiring via ApiLambdaStack looks consistent

Handlers are referenced cleanly and API methods use 29s timeouts with existing models/authorizers. Good refactor to centralize Lambda construction/wiring.

Also applies to: 133-151, 185-229

backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (4)

198-205: Decorator order and mock args alignment look correct.

Patching order matches the function signature (publish_event first, then flag). Good.


222-266: Good coverage for “all privileges already encumbered” with flag ON.

AA seeding + assertions for encumbranceDetails using clinicalPrivilegeActionCategories look correct.


251-261: Encumbrance details now use plural categories — LGTM.

Asserting clinicalPrivilegeActionCategories list is correct per the new model.


266-273: Flag-OFF variant preserved — LGTM.

Signature and decorator order are consistent with the patched mocks.

backend/compact-connect/tests/app/test_api/test_provider_management_api.py (4)

217-221: Handler wiring assertions against api_lambda_stack are correct.

Handler names align with PythonFunction(index='handlers/providers.py', handler=...).


234-238: Imported Lambda integration helper usage looks right. Please confirm API Lambda outputs exist.

Using ImportValue requires ApiLambdaStack to export Lambda ARNs. Ensure corresponding CfnOutputs exist for:

  • GetProviderHandler
  • QueryProvidersHandler
  • GetProviderSSNHandler
  • DeactivatePrivilegeHandler
  • ProviderEncumbranceHandler

Would you confirm ApiLambdaStack exports these? If helpful, I can scan the repo and pinpoint missing outputs.


399-475: SSN alarms relocation validated — LGTM.

Alarms asserted from api_lambda_stack; 4xx API alarm still in api_stack. Snapshots compare the properties minus actions as intended.


579-586: Encumbrance handler integration (imported lambda) verified — LGTM.

Request model capture + integration to provider_encumbrance_handler match the refactor.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1)

136-178: Privilege update record with plural categories under flag — LGTM.

Assertions for updatedValues, effective/create dates, and encumbranceDetails look correct.

backend/compact-connect/stacks/api_lambda_stack/provider_management.py (4)

52-81: GetProvider handler wiring and grants — LGTM.

Env, code entry, decrypt/bucket/table grants, and nag suppression look sound.


118-169: SSN handler permissions and alarms — LGTM with a note.

Inline policy for PutFunctionConcurrency and alarm wiring look correct. Note: you’re attaching to a shared role (api_query_role). Ensure it’s not reused by other Lambdas to avoid over‑permitting.


266-297: Deactivate privilege handler wiring — LGTM.

Grants and event bus permissions look correct.


299-329: Provider encumbrance handler wiring — LGTM.

Grants and nag suppression match expected needs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

2629-2632: Docstring types: add type for adverse_action_id and use datetime.date

Current docstring omits the type for adverse_action_id. Also prefer datetime.date for clarity.

-        :param adverse_action_id: The ID of the adverse action.
-        :param str license_type_abbreviation: The license type abbreviation.
-        :param date effective_date: effective date of the encumbrance on the license and therefore privilege.
+        :param str adverse_action_id: The ID of the adverse action.
+        :param str license_type_abbreviation: The license type abbreviation.
+        :param datetime.date effective_date: Effective date of the encumbrance on the license (and therefore privilege).
🧹 Nitpick comments (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

1430-1442: Feature-flagged multi-category support looks good

Branching on encumbrance-multi-category-flag and shaping encumbranceDetails for privilege is correct for the migration. Minor: consider hoisting the import to module scope to avoid repeating inline imports.

Based on learnings


2676-2690: Standardize adverseActionId type in encumbranceDetails

Here adverseActionId uses the string parameter while encumber_privilege uses the UUID from the fetched record. For consistency with schema and downstream consumers, prefer the UUID from the loaded adverse_action object.

-            encumbrance_details = {
-                'clinicalPrivilegeActionCategories': adverse_action.clinicalPrivilegeActionCategories,
-                'licenseJurisdiction': jurisdiction,
-                'adverseActionId': adverse_action_id,
-            }
+            encumbrance_details = {
+                'clinicalPrivilegeActionCategories': adverse_action.clinicalPrivilegeActionCategories,
+                'licenseJurisdiction': jurisdiction,
+                'adverseActionId': adverse_action.adverseActionId,
+            }
@@
-            encumbrance_details = {
-                'clinicalPrivilegeActionCategory': adverse_action.clinicalPrivilegeActionCategory,
-                'licenseJurisdiction': jurisdiction,
-                'adverseActionId': adverse_action_id,
-            }
+            encumbrance_details = {
+                'clinicalPrivilegeActionCategory': adverse_action.clinicalPrivilegeActionCategory,
+                'licenseJurisdiction': jurisdiction,
+                'adverseActionId': adverse_action.adverseActionId,
+            }

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d304a9c and 1dc3864.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional). When working with encumbrance notes, use encumbranceDetails['clinicalPrivilegeActionCategory'], not encumbranceDetails['note'].

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional).

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
📚 Learning: 2025-10-15T19:53:00.390Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py:2642-2654
Timestamp: 2025-10-15T19:53:00.390Z
Learning: In backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py and provider_record_util.py, the get_adverse_action_by_id method correctly handles UUID vs string comparison as written. Attempting to add explicit type conversion causes the mapping to fail. The current implementation should not be changed to add UUID/string conversion logic.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
🧬 Code graph analysis (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4)
backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py (1)
  • is_feature_enabled (46-112)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (8)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • adverseActionId (126-127)
  • adverseActionId (130-131)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)
  • get_adverse_action_by_id (525-535)
backend/compact-connect/lambdas/python/common/cc_common/exceptions.py (1)
  • CCInternalException (44-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

2642-2654: Adverse action lookup path is fine

Using ProviderUserRecords.get_adverse_action_by_id without UUID/string normalization and raising CCInternalException on miss aligns with current behavior.

Based on learnings

Copy link
Collaborator

@jusdino jusdino left a comment

Choose a reason for hiding this comment

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

Looks good! Just to confirm my understanding:

While the feature flag is inactive

  • existing API and DB behavior remain

While the feature flag is active

  • We accept either field
  • We require that one must be included to create an encumbrance
  • We internally translate everything to the new field
  • We return the new field

While the feature flag scaffolding remains

  • Both fields in schema are present and optional

Once the feature flag scaffolding is removed

  • the new field is required
  • we perform no more translation

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (3)

23-31: Consolidate feature‑flag mocking to a single mechanism

You patch is_feature_enabled at class level and also per‑test elsewhere. This is redundant and can lead to confusion. Keep the class‑level patch with a MagicMock and flip return_value in individual tests that need False.


204-211: Remove redundant is_feature_enabled patch on this test

This test already benefits from the class‑level patch. Dropping the per‑test patch simplifies the signature and avoids double-patching.

Apply this diff:

-    @patch('cc_common.feature_flag_client.is_feature_enabled', return_value=True)
     @patch('cc_common.event_bus_client.EventBusClient._publish_event')
-    def test_license_encumbrance_listener_handles_all_privileges_already_encumbered(
-        self,
-        mock_publish_event,
-        mock_flag,  # noqa: ARG002
-    ):
+    def test_license_encumbrance_listener_handles_all_privileges_already_encumbered(
+        self,
+        mock_publish_event,
+    ):

135-136: Duplicate comment line

Remove the repeated “Now verify the rest with comprehensive assertion”.

-        # Now verify the rest with comprehensive assertion
-        # Now verify the rest with comprehensive assertion
+        # Now verify the rest with comprehensive assertion
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (2)

385-389: Comment contradicts assertions — fix wording

The code asserts the deprecated field exists, but the comment says “not present”. Update the comment to avoid confusion.

-        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
-        # Verify that the deprecated field is not present in the stored data
+        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
+        # Verify that the deprecated field is still present in the stored data during migration

675-679: Comment contradicts assertions — fix wording (license variant)

Same issue here; adjust the comment to match the asserts.

-        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
-        # Verify that the deprecated field is not present in the stored data
+        # TODO - remove this assertion as part of https://github.com/csg-org/CompactConnect/issues/1136 # noqa: FIX002
+        # Verify that the deprecated field is still present in the stored data during migration
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc3864 and a86a6d2.

📒 Files selected for processing (4)
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (2 hunks)
  • backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (9 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (8 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional). When working with encumbrance notes, use encumbranceDetails['clinicalPrivilegeActionCategory'], not encumbranceDetails['note'].

Applied to files:

  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional).

Applied to files:

  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
🧬 Code graph analysis (3)
backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (4)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (3)
  • put_default_adverse_action_record_in_provider_table (154-160)
  • put_default_provider_record_in_provider_table (409-429)
  • put_default_privilege_record_in_provider_table (310-320)
backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (1)
  • license_encumbrance_listener (186-243)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • serialize_to_database_record (205-208)
  • PrivilegeEncumberedStatusEnum (317-321)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (7)
  • PrivilegeUpdateData (91-162)
  • compact (28-29)
  • compact (108-109)
  • jurisdiction (32-33)
  • jurisdiction (112-113)
  • encumbranceDetails (151-155)
  • updatedValues (124-125)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • serialize_to_database_record (205-208)
  • from_database_record (124-139)
  • to_dict (169-180)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (9)
  • compact (27-28)
  • compact (31-32)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
  • AdverseActionData (14-147)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)
  • generate_default_privilege_update (329-356)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (5)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)

142-147: Default AA uses plural categories — good migration step

Setting clinicalPrivilegeActionCategories with a single-element list matches the new schema and keeps defaults stable.

backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (1)

96-97: Assertions updated to plural field look correct

Getter and snapshot now validate clinicalPrivilegeActionCategories as a list; consistent with schema.

Also applies to: 114-115

backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (1)

120-166: Clarify intended publish semantics for already‑encumbered privileges

This test expects two privilege.encumbrance events even though one privilege was already encumbered. Please confirm this is intentional (audit signal even without state change) and update the handler/docstring if needed so “affected_privileges” semantics and logs (privileges_encumbered=...) remain accurate.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (2)

398-421: 400 on “both fields provided” — confirm alignment with migration policy

These tests enforce a 400 when both clinicalPrivilegeActionCategory and clinicalPrivilegeActionCategories are provided. Prior migration guidance was to keep both optional and not enforce mutual exclusivity until the deprecated field is removed. Confirm this stricter API validation is deliberate and documented for clients.

Based on learnings

Also applies to: 688-711


139-181: EncumbranceDetails plural field usage looks correct under flag-on

Expected privilege update record uses clinicalPrivilegeActionCategories with AA id; matches the migration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1)

360-685: Consider extracting common migration test logic.

The privilege migration tests (lines 360-420) and license migration tests (lines 649-710) follow nearly identical patterns:

  1. Create encumbrance with deprecated field
  2. Verify both old and new fields are present in database
  3. Verify error when both fields provided

While test duplication is acceptable, extracting a helper method could reduce maintenance burden:

def _verify_category_field_migration(self, record_type: str, test_record, query_key_prefix: str):
    """Helper to verify migration from singular to plural category field"""
    # Query database
    # Assert both fields present
    # Assert list contains migrated value

This would make the tests more concise and easier to update when the migration completes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a86a6d2 and cdfd125.

📒 Files selected for processing (2)
  • backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (9 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.
🧬 Code graph analysis (2)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (4)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (3)
  • put_default_adverse_action_record_in_provider_table (154-160)
  • put_default_provider_record_in_provider_table (409-429)
  • put_default_privilege_record_in_provider_table (310-320)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • PrivilegeEncumberedStatusEnum (317-321)
  • serialize_to_database_record (205-208)
backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (1)
  • license_encumbrance_listener (186-243)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (7)
  • PrivilegeUpdateData (91-162)
  • compact (28-29)
  • compact (108-109)
  • jurisdiction (32-33)
  • jurisdiction (112-113)
  • encumbranceDetails (151-155)
  • updatedValues (124-125)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (42-54)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (9)
  • compact (27-28)
  • compact (31-32)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
  • AdverseActionData (14-147)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)
  • generate_default_privilege_update (329-356)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (5)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (2)

89-93: LGTM! Proper test data setup.

Adding adverse action records before testing encumbrance events correctly simulates the expected state where license-level adverse actions exist and trigger privilege encumbrance flows.

Also applies to: 224-228, 291-297, 348-351, 591-594, 737-740, 791-794


256-263: LGTM! Proper verification of field migration.

The tests correctly verify the migration from clinicalPrivilegeActionCategory (singular) to clinicalPrivilegeActionCategories (plural) under different feature flag states:

  • Lines 256-263: Verify plural field when flag is enabled
  • Lines 326-333: Verify singular field when flag is disabled

This ensures backward compatibility during the migration period.

Also applies to: 326-333

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (3)

100-180: LGTM! Feature-flag-gated privilege encumbrance tests.

The tests properly verify encumbrance handling under different feature flag states:

  • Lines 100-137: With flag enabled, verifies plural clinicalPrivilegeActionCategories field
  • Lines 139-180: Verifies privilege update records with plural field
  • Lines 182-228: With flag disabled, verifies singular clinicalPrivilegeActionCategory field

The mock_flag parameter is correctly marked as unused since it's only needed for the decorator.


359-420: LGTM! Comprehensive migration and validation tests.

These tests properly verify:

  1. Lines 360-395: Deprecated clinicalPrivilegeActionCategory is automatically migrated to clinicalPrivilegeActionCategories list, with both fields present in stored data during migration
  2. Lines 397-420: Proper 400 error when both singular and plural fields are provided, preventing ambiguous input

This ensures a smooth migration path and proper error handling.

Based on learnings: The codebase intentionally keeps both fields as optional during migration to support backward compatibility, and mutual exclusivity validation is deferred until the deprecated field is removed in a follow-up PR.


476-477: LGTM! Consistent license encumbrance migration tests.

The license encumbrance tests mirror the privilege encumbrance migration pattern:

  • Lines 476-514: Verify plural field with flag enabled
  • Lines 649-685: Verify field migration
  • Lines 687-710: Verify mutual exclusivity validation

The TODO comments appropriately track that these singular field tests will be removed in issue #1136.

Also applies to: 505-506, 649-710

Copy link
Collaborator

@jusdino jusdino left a comment

Choose a reason for hiding this comment

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

Great!

@landonshumway-ia
Copy link
Collaborator Author

@jlkravitz This is ready for your review. Thanks!

jlkravitz
jlkravitz previously approved these changes Oct 20, 2025
Copy link
Collaborator

@jlkravitz jlkravitz left a comment

Choose a reason for hiding this comment

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

Looks good to me! @isabeleliassen This is good to merge.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)

107-123: Explicit cdk‑nag suppression accepted.

Hard‑coded logical ID in appliesTo is intentional in this repo to force review on underlying changes. No action.

backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (1)

269-341: Avoid mutating shared MagicMock state; patch the flag per-test.

Flip/reset of a class-scoped MagicMock is brittle and can leak across tests on failures/parallelism. Patch the flag on this test instead and drop manual reset.

-    @patch('cc_common.event_bus_client.EventBusClient._publish_event')
-    def test_license_encumbrance_listener_handles_all_privileges_already_encumbered_with_experiment_disabled(
-        self,
-        mock_publish_event,
-    ):
-        mock_feature_client.return_value = False
+    @patch('cc_common.feature_flag_client.is_feature_enabled', return_value=False)
+    @patch('cc_common.event_bus_client.EventBusClient._publish_event')
+    def test_license_encumbrance_listener_handles_all_privileges_already_encumbered_with_experiment_disabled(
+        self,
+        mock_publish_event,
+        mock_flag,  # noqa: ARG002
+    ):
@@
-        # reset the flag client for the other tests
-        mock_feature_client.return_value = True
🧹 Nitpick comments (20)
backend/compact-connect/stacks/api_stack/v1_api/api_model.py (4)

417-441: Consider adding validation constraints and precedence documentation.

The migration from singular to plural field is well-structured for backward compatibility. However, consider these enhancements:

  1. Array validation: The clinicalPrivilegeActionCategories array has no maxItems constraint or item-level validation. Should the items be constrained to specific enum values, non-empty strings, or have a reasonable max array length?

  2. Field precedence: When both clinicalPrivilegeActionCategory and clinicalPrivilegeActionCategories are provided in a request, which takes precedence? This should be documented in the description or handled with validation logic in the handler.

Based on learnings


454-478: Consider adding validation constraints and precedence documentation.

Same concerns as the privilege encumbrance model:

  1. Array validation: Add maxItems constraint and consider item-level validation for clinicalPrivilegeActionCategories.

  2. Field precedence: Document which field takes precedence when both are provided in a request.

Consistency between privilege and license encumbrance models is good.

Based on learnings


1217-1223: Response schema migration looks good.

The addition of clinicalPrivilegeActionCategories to the adverse action response schema is well-structured for the migration. Both fields remain optional, allowing clients to handle either format.

For consistency with request models, consider adding a maxItems constraint to the array, even in response schemas, to document expected bounds.

Based on learnings


1358-1364: Response schema migration looks good.

Consistent implementation with the license adverse actions schema. Both fields remain optional for backward compatibility during the migration period.

Same suggestion as above: consider adding a maxItems constraint for consistency with request models.

Based on learnings

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py (1)

260-260: Expected output format is correct, but consider edge case coverage.

The comma-separated format for multiple categories is appropriate. However, consider adding test cases for:

  • Single category in the list (to verify no trailing comma)
  • Empty list (if allowed)
  • Categories with special characters or commas in their names
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (2)

201-203: Good pivot to imported‑lambda pattern.

Using api_lambda_stack and its template is correct for imported Lambda integrations. Consider moving these repeated lookups into setUp to DRY tests.

Also applies to: 263-265, 341-343, 404-405, 481-483, 576-578, 637-639, 692-694, 760-762


218-221: Handler name assertions may be brittle.

Hard‑coding 'Handler' strings can drift with bundling changes. Prefer asserting by logical ID via get_logical_id(...) or by matching Integration Uri ImportValue instead of Handler literal.

Also applies to: 281-284, 358-361, 485-488, 580-583

backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)

177-220: Minor: comment vs period mismatch.

Comment says “within a day,” but MetricStat uses 3600s period. If intentional for anomaly sensitivity, update comment; otherwise consider aligning periods.

backend/compact-connect/tests/smoke/smoke_common.py (1)

251-265: Add pagination and ConsistentRead to avoid truncated provider snapshots.

Current Query returns a single page and may miss items; optionally fail fast if none.

-def get_provider_user_records(compact: str, provider_id: str) -> ProviderUserRecords:
+def get_provider_user_records(compact: str, provider_id: str, *, consistent_read: bool = True) -> ProviderUserRecords:
     """
     Get all provider records from DynamoDB and return as ProviderUserRecords utility class.

     :param compact: The compact identifier
     :param provider_id: The provider's ID
+    :param consistent_read: Use strongly consistent reads (default True)
     :return: ProviderUserRecords instance containing all records for this provider
     """
-    # Query the provider database for all records
-    query_result = config.provider_user_dynamodb_table.query(
-        KeyConditionExpression=Key('pk').eq(f'{compact}#PROVIDER#{provider_id}')
-    )
-
-    return ProviderUserRecords(query_result['Items'])
+    items = []
+    last_evaluated_key = None
+    while True:
+        params = {
+            'Select': 'ALL_ATTRIBUTES',
+            'KeyConditionExpression': Key('pk').eq(f'{compact}#PROVIDER#{provider_id}'),
+            'ConsistentRead': consistent_read,
+        }
+        if last_evaluated_key:
+            params['ExclusiveStartKey'] = last_evaluated_key
+        resp = config.provider_user_dynamodb_table.query(**params)
+        items.extend(resp.get('Items', []))
+        last_evaluated_key = resp.get('LastEvaluatedKey')
+        if not last_evaluated_key:
+            break
+    if not items:
+        raise SmokeTestFailureException('Provider not found')
+    return ProviderUserRecords(items)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (3)

126-133: Don’t mutate Mock call kwargs in-place.

Use a shallow copy before pop to avoid side effects on mock internals.

-        called_event_batch_writer_1 = call_args_1.pop('event_batch_writer')
-        called_event_batch_writer_2 = call_args_2.pop('event_batch_writer')
+        call_args_1 = dict(call_args_1)
+        call_args_2 = dict(call_args_2)
+        called_event_batch_writer_1 = call_args_1.pop('event_batch_writer')
+        called_event_batch_writer_2 = call_args_2.pop('event_batch_writer')

120-166: Tweak comment to match assertions.

Docstring says “affected privilege” (singular) but you assert two events. Update wording to avoid confusion.


89-93: Reduce repeated AA setup with a small helper.

AA-for-license inserts are duplicated across tests. Consider a local helper (or extend TestDataGenerator) to DRY these lines and keep intent clear.

Also applies to: 224-228, 349-351, 591-595, 737-740, 791-795

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1)

151-157: Avoid hard-coding 'slp' in SK prefix; use the record’s abbreviation.

Improves resilience if defaults change.

-            & Key('sk').begins_with(
-                f'{test_privilege_record.compact}#PROVIDER#privilege/{test_privilege_record.jurisdiction}/slp#UPDATE'
-            ),
+            & Key('sk').begins_with(
+                f'{test_privilege_record.compact}#PROVIDER#privilege/'
+                f'{test_privilege_record.jurisdiction}/{test_privilege_record.licenseTypeAbbreviation}#UPDATE'
+            ),
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)

82-91: Deprecation note and caller guidance for clinicalPrivilegeActionCategory

Good to keep the singular property during migration. Please document the deprecation and the Optional return to prevent surprises at call sites.

Apply this small docstring addition:

 @property
 def clinicalPrivilegeActionCategory(self) -> str | None:
-    return self._data.get('clinicalPrivilegeActionCategory')
+    """Deprecated: prefer clinicalPrivilegeActionCategories. Returns None if unset."""
+    return self._data.get('clinicalPrivilegeActionCategory')

Optionally verify downstream callers handle None; I can provide a grep script if helpful. Based on learnings.


93-100: DX: accept enums in plural setter

Setter only accepts list[str]. To improve ergonomics while keeping storage as strings, accept ClinicalPrivilegeActionCategory items too.

-@clinicalPrivilegeActionCategories.setter
-def clinicalPrivilegeActionCategories(self, value: list[str]) -> None:
-    self._data['clinicalPrivilegeActionCategories'] = value
+@clinicalPrivilegeActionCategories.setter
+def clinicalPrivilegeActionCategories(self, value: list[str] | list[ClinicalPrivilegeActionCategory]) -> None:
+    normalized = [
+        v.value if isinstance(v, ClinicalPrivilegeActionCategory) else v
+    for v in value]
+    self._data['clinicalPrivilegeActionCategories'] = normalized
backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (3)

242-260: Reduce flakiness: poll for license/provider encumbered state (eventual consistency)

Privilege validation already polls; license/provider checks are single-shot reads and may flake under eventual consistency.

  • Mirror the polling approach used in validate_privilege_encumbered_state for:
    • validate_license_encumbered_state
    • validate_provider_encumbered_state
  • Or allow a short retry loop (e.g., 3 attempts with 5s sleep).

Also applies to: 291-298, 337-346


374-411: Tests: support singular fallback for robustness

If a legacy record sneaks through with the singular field, this matcher will miss it.

Make the matcher tolerate both shapes:

-        for adverse_action in adverse_actions:
-            action_type = adverse_action.get('encumbranceType')
-            action_categories = set(adverse_action.get('clinicalPrivilegeActionCategories', []))
+        for adverse_action in adverse_actions:
+            action_type = adverse_action.get('encumbranceType')
+            plural = adverse_action.get('clinicalPrivilegeActionCategories') or []
+            # singular fallback for robustness during migration
+            if not plural and 'clinicalPrivilegeActionCategory' in adverse_action:
+                plural = [adverse_action['clinicalPrivilegeActionCategory']]
+            action_categories = set(plural)

512-546: Good assertions; consider asserting that categories persisted as requested

Nice state assertions. Optionally assert that at least one adverse action contains exactly the requested categories to fully exercise the write/read path.

I can add a helper usage here to assert category round-trip using verify_license_adverse_action_matches_request.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1)

49-60: Migration hooks: allow duplicates? consider validation

Migration looks good. If duplicates can be posted, they’ll persist. If that’s undesirable, add a schema-level check to reject duplicates.

Example:

 @validates_schema
 def validate_license_type(self, data, **_kwargs):
     ...
+
+    # Also ensure categories (if present) are unique
+@validates_schema
+def validate_categories_unique(self, data, **_kwargs):
+    cats = data.get('clinicalPrivilegeActionCategories')
+    if cats is not None and len(cats) != len(set(cats)):
+        raise ValidationError({'clinicalPrivilegeActionCategories': ['Duplicate entries are not allowed.']})

Also applies to: 76-83

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)

32-48: Mutual exclusivity validator: concise and clear

Exactly-one-of check is good. Consider adding an explicit check that, when plural is provided, it’s not empty (pairs well with Length(min=1)).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdfd125 and 40eca54.

📒 Files selected for processing (33)
  • backend/compact-connect/common_constructs/python_common_layer_versions.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py (0 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_provider_record_util.py (2 hunks)
  • backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (0 hunks)
  • backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (9 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (8 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py (3 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/__init__.py (2 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py (6 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (6 hunks)
  • backend/compact-connect/stacks/event_listener_stack/__init__.py (1 hunks)
  • backend/compact-connect/stacks/feature_flag_stack/__init__.py (1 hunks)
  • backend/compact-connect/tests/app/test_api/test_provider_management_api.py (23 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json (4 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ANOMALY_DETECTION_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ENDPOINT_DISABLED_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_READS_RATE_LIMITED_ALARM_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json (4 hunks)
  • backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (16 hunks)
  • backend/compact-connect/tests/smoke/smoke_common.py (2 hunks)
💤 Files with no reviewable changes (2)
  • backend/compact-connect/lambdas/python/common/cc_common/event_bus_client.py
  • backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ANOMALY_DETECTION_ALARM_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/tests/unit/test_provider_record_util.py
  • backend/compact-connect/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_ENDPOINT_DISABLED_ALARM_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py
  • backend/compact-connect/stacks/api_stack/v1_api/api.py
  • backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.
📚 Learning: 2025-10-15T19:56:58.007Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional). When working with encumbrance notes, use encumbranceDetails['clinicalPrivilegeActionCategory'], not encumbranceDetails['note'].

Applied to files:

  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional).

Applied to files:

  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
📚 Learning: 2025-10-15T19:53:00.390Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py:2642-2654
Timestamp: 2025-10-15T19:53:00.390Z
Learning: In backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py and provider_record_util.py, the get_adverse_action_by_id method correctly handles UUID vs string comparison as written. Attempting to add explicit type conversion causes the mapping to fail. The current implementation should not be changed to add UUID/string conversion logic.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
📚 Learning: 2025-09-03T22:29:24.535Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py:0-0
Timestamp: 2025-09-03T22:29:24.535Z
Learning: The EncumbranceEventDetailSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py is used across multiple instances/contexts where adverseActionCategory and adverseActionId fields may be required in some cases and not present in others. This is an intentional design pattern for handling different event variants with a single schema.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
📚 Learning: 2025-10-15T21:52:47.370Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/stacks/api_lambda_stack/provider_management.py:100-0
Timestamp: 2025-10-15T21:52:47.370Z
Learning: In the CompactConnect repository, the team prefers to keep cdk-nag suppressions explicit with hard-coded logical IDs (e.g., 'Resource::<ProviderTableEC5D0597.Arn>/index/*') rather than using generic suppressions. This intentional brittleness ensures that when underlying resources change, the suppressions break and force manual review of the IAM policies.

Applied to files:

  • backend/compact-connect/stacks/api_lambda_stack/provider_management.py
🧬 Code graph analysis (15)
backend/compact-connect/tests/smoke/smoke_common.py (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)
  • ProviderUserRecords (403-791)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/tests/smoke/config.py (1)
  • provider_user_dynamodb_table (40-41)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)
backend/compact-connect/lambdas/python/common/cc_common/utils.py (1)
  • get (86-87)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • ClinicalPrivilegeActionCategory (375-388)
backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/stacks/event_listener_stack/__init__.py (1)
backend/common-cdk/common_constructs/stack.py (2)
  • api_domain_name (110-113)
  • common_env_vars (81-89)
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (2)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)
  • ApiLambdaStack (33-222)
backend/compact-connect/stacks/state_api_stack/v1_api/provider_management.py (1)
  • _add_get_provider (74-106)
backend/compact-connect/stacks/feature_flag_stack/__init__.py (1)
backend/compact-connect/stacks/feature_flag_stack/feature_flag_resource.py (2)
  • FeatureFlagResource (23-72)
  • FeatureFlagEnvironmentName (15-20)
backend/compact-connect/tests/smoke/encumbrance_smoke_tests.py (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/tests/smoke/smoke_common.py (1)
  • get_provider_user_records (251-264)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (7)
  • get_privilege_records (481-489)
  • get_specific_license_record (447-462)
  • get_specific_privilege_record (464-479)
  • get_provider_record (79-84)
  • get_provider_record (605-614)
  • get_adverse_action_records_for_license (507-523)
  • get_adverse_action_records_for_privilege (587-603)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • licenseTypeAbbreviation (156-167)
  • to_dict (169-180)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (2)
backend/compact-connect/lambdas/python/common/cc_common/feature_flag_client.py (1)
  • is_feature_enabled (46-112)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (3)
  • AdverseActionData (14-147)
  • adverseActionId (126-127)
  • adverseActionId (130-131)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (2)
backend/compact-connect/tests/app/test_api/__init__.py (2)
  • TestApi (11-83)
  • generate_expected_integration_object_for_imported_lambda (48-83)
backend/compact-connect/tests/app/base.py (1)
  • get_resource_properties_by_logical_id (89-96)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)
  • ProviderManagementLambdas (18-336)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (4)
backend/compact-connect/common_constructs/python_function.py (1)
  • PythonFunction (19-138)
backend/compact-connect/stacks/persistent_stack/__init__.py (1)
  • PersistentStack (35-480)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)
  • ApiLambdaStack (33-222)
backend/compact-connect/lambdas/python/common/cc_common/config.py (6)
  • provider_table (88-89)
  • ssn_table (92-93)
  • ssn_index_name (179-180)
  • event_bus_name (84-85)
  • rate_limiting_table (282-283)
  • user_pool_id (191-195)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (7)
  • PrivilegeUpdateData (91-162)
  • compact (28-29)
  • compact (108-109)
  • jurisdiction (32-33)
  • jurisdiction (112-113)
  • encumbranceDetails (151-155)
  • updatedValues (124-125)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • serialize_to_database_record (205-208)
  • from_database_record (124-139)
  • to_dict (169-180)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (9)
  • compact (27-28)
  • compact (31-32)
  • jurisdiction (43-44)
  • jurisdiction (47-48)
  • AdverseActionData (14-147)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)
  • generate_default_privilege_update (329-356)
backend/compact-connect/lambdas/python/data-events/tests/function/test_encumbrance_events.py (6)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (3)
  • put_default_adverse_action_record_in_provider_table (154-160)
  • put_default_provider_record_in_provider_table (409-429)
  • put_default_privilege_record_in_provider_table (310-320)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • PrivilegeEncumberedStatusEnum (317-321)
  • serialize_to_database_record (205-208)
backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py (1)
  • license_encumbrance_listener (186-243)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)
  • get_provider_user_records (173-204)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (3)
  • compact (28-29)
  • compact (108-109)
  • encumberedStatus (72-73)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)
  • get_privilege_records (481-489)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (26)
backend/compact-connect/common_constructs/python_common_layer_versions.py (1)

53-53: Verify intentional removal of conditional retention policy.

The removal policy is now always RETAIN, whereas it previously varied based on environment (sandbox vs. non-sandbox). This means sandbox environments will now accumulate layer versions rather than destroying them, potentially accelerating storage consumption toward the 75 GB AWS limit mentioned in the comment above.

Please confirm this change was intentional and that the team has considered the storage implications for sandbox environments.

backend/compact-connect/tests/resources/snapshots/GET_PROVIDER_SSN_READS_RATE_LIMITED_ALARM_SCHEMA.json (1)

2-2: Snapshot updates are correct and intentionally reference different stacks by design.

The naming difference across the four alarm snapshots reflects intentional architectural design, not inconsistency:

  • GET_PROVIDER_SSN_4XX_ALARM_SCHEMA.json: References APIStack/LicenseApi because it monitors API Gateway 4xx errors (test comment explicitly states: "still in api_stack")
  • Other three alarms (anomaly detection, rate-limited, endpoint disabled): Reference ApiLambdaStack/GetProviderSSNHandler because they monitor Lambda execution metrics

All alarms monitor the same endpoint (/v1/compacts/{compact}/providers/{providerId}/ssn) but at different infrastructure layers. The reviewed file's snapshot change is correct—the rate-limited alarm should reference the new Lambda handler in ApiLambdaStack. Snapshot tests pass and validate this expected structure.

backend/compact-connect/stacks/event_listener_stack/__init__.py (1)

38-41: ****

The concern about mutating common_env_vars is unfounded. Line 80 shows @cached_property, not @property. Unlike @property (which returns a new dict on each access), @cached_property caches the result after the first computation and stores it as an instance attribute. Consequently, the .update() call on line 41 mutates the cached dict in-place, and all subsequent spreads of **self.common_env_vars (lines 63, 111, 158) access the same cached dict with API_BASE_URL correctly included.

Likely an incorrect or invalid review comment.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py (3)

83-83: Test data correctly exercises the new multi-category field.

The use of clinicalPrivilegeActionCategories with a list of two categories appropriately tests the new functionality for the staff endpoint.


28-28: Confirm the intentional divergence in field usage across endpoints.

The staff endpoint test uses the new clinicalPrivilegeActionCategories (plural) field, while the provider user and public endpoint tests continue using the singular clinicalPrivilegeActionCategory field. This appears to be an intentional phased rollout where the staff endpoint adopts the new multi-category support first, but please confirm this is the expected behavior.

Based on learnings

Also applies to: 53-53, 83-83


225-227: **** The test file already has adequate backward compatibility coverage. The five test methods at lines 107–182 (those without explicit feature flag mocks) implicitly test the disabled flag scenario by running against the unmocked is_feature_enabled client. The single test at line 227 with return_value=True explicitly tests the new enabled behavior. This is the correct test structure: legacy behavior is covered implicitly by default unmocked tests, while the new feature behavior is covered by the explicit mock. No additional test is needed.

Likely an incorrect or invalid review comment.

backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)

26-26: Import looks good.

ProviderManagementLambdas import aligns with usage below; no issues.

backend/compact-connect/tests/app/test_api/test_provider_management_api.py (2)

407-417: Alarm assertions look solid.

Correctly scopes SSN alarms to api_lambda_stack and 4XX API alarm to api_stack. Snapshot approach is appropriate.

Also applies to: 424-456, 458-465, 470-474


234-238: Method is properly exposed as a static class member—no AttributeError risk.

The verification confirms that generate_expected_integration_object_for_imported_lambda is defined as a @staticmethod on the TestApi class (line 48 in backend/compact-connect/tests/app/test_api/__init__.py), making it correctly accessible via TestApi.generate_expected_integration_object_for_imported_lambda() at all referenced locations. No action required.

backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (3)

15-15: Clean dependency inversion to ApiLambdaStack.

Constructor now consumes pre‑bound handlers; reduces duplication and tightens IAM in one place. Nice.

Also applies to: 34-35


60-66: Correct use of pre‑bound handlers.

Directly wiring api_lambda_stack.provider_management_lambdas.* keeps API wiring lean.


117-117: Integrations and timeouts are consistent.

29s timeout pattern matches rest of API; authorization wiring is unchanged.

Also applies to: 140-140, 215-215, 241-241, 260-260, 284-284, 303-303, 325-325

backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)

150-163: Concurrency policy scope is tight.

Allowing lambda:PutFunctionConcurrency only on self ARN is appropriate. No change needed.

backend/compact-connect/tests/smoke/smoke_common.py (2)

33-33: LGTM on env propagation.

Setting LICENSE_TYPES mirrors COMPACTS/JURISDICTIONS and unblocks helpers.


36-36: Import placement is fine for tests.

Late import with E402 suppression after sys.path/env setup is appropriate here.

backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (3)

100-101: Per-test feature flag patching: good.

Keeps tests isolated and avoids global state issues.


139-181: EncumbranceDetails list-field assertions look correct.

Validates plural category path with effective/create dates.


359-415: Tests correctly validate backward compatibility during migration phase; no changes needed.

Both tests properly cover the temporary dual-field window before clinicalPrivilegeActionCategory is removed. The widespread presence of the singular field across frontend, handlers, schemas, and API specs is intentional during this migration period. The validation enforcing mutual exclusivity (lines 399-418 snippet) correctly prevents mixed input and protects the transition. Removal of this field is tracked in issue #1136 with follow-up migration scripts.

backend/compact-connect/stacks/feature_flag_stack/__init__.py (1)

116-125: Flag resource wiring looks correct.

Name, provider reuse, and auto_enable for TEST align with the rollout plan.
Confirm environments that should auto-enable (TEST only vs also SANDBOX).

backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py (4)

96-96: Updated getter assertion to plural field is correct.

Matches AdverseActionData API.


114-115: Snapshot now expects clinicalPrivilegeActionCategories list.

This aligns with the new data shape.


14-32: Serde round-trip stays stable with migrated shape.

Good to exclude dynamic dateOfUpdate before equality.


1-9999: No action required—test_adverse_action.py correctly uses the plural field.

The test data generator populates clinicalPrivilegeActionCategories (plural), and all assertions in test_adverse_action.py reference the plural field. The schema's migration logic automatically converts any legacy singular field to the plural form during load, so there is no brittleness risk in this test file. The migration strategy is working as designed per the backwards compatibility approach outlined in learnings.

backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)

525-535: LGTM: UUID vs str comparison works as intended

Keeping the direct comparison is correct for this codebase; explicit casting breaks mapping per prior context. No change needed.

Based on learnings.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)

98-103: Response shape maintains backward-compat alias

Looks good to expose the plural while keeping the singular optional for migration. No action.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1)

37-44: Require at least one category; prevent empty lists

As written, an empty clinicalPrivilegeActionCategories list is allowed, which undermines the requirement that at least one NPDB category is set.

Add a Length(min=1) validator:

-from marshmallow import ValidationError, pre_dump, pre_load, validates_schema
+from marshmallow import ValidationError, pre_dump, pre_load, validates_schema
 from marshmallow.fields import UUID, Date, DateTime, List, String
-from marshmallow.validate import OneOf
+from marshmallow.validate import OneOf, Length
...
-    clinicalPrivilegeActionCategories = List(ClinicalPrivilegeActionCategoryField(), required=False, allow_none=False)
+    clinicalPrivilegeActionCategories = List(
+        ClinicalPrivilegeActionCategoryField(),
+        required=False,
+        allow_none=False,
+        validate=Length(min=1),
+    )
⛔ Skipped due to learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)

47-57: Critical issue resolved: log groups now correctly registered.

The previous critical issue flagging that handler objects were appended instead of their .log_group property has been fixed. All five handlers now correctly append .log_group to the api_lambda_stack.log_groups list, ensuring the list contains ILogGroup entries as expected.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40eca54 and 866e00f.

📒 Files selected for processing (5)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (3 hunks)
  • backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_reporting.py (1 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api.py (1 hunks)
  • backend/compact-connect/tests/app/test_api/test_purchases_api.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_reporting.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.
📚 Learning: 2025-10-15T21:52:47.370Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/stacks/api_lambda_stack/provider_management.py:100-0
Timestamp: 2025-10-15T21:52:47.370Z
Learning: In the CompactConnect repository, the team prefers to keep cdk-nag suppressions explicit with hard-coded logical IDs (e.g., 'Resource::<ProviderTableEC5D0597.Arn>/index/*') rather than using generic suppressions. This intentional brittleness ensures that when underlying resources change, the suppressions break and force manual review of the IAM policies.

Applied to files:

  • backend/compact-connect/stacks/api_lambda_stack/provider_management.py
📚 Learning: 2025-10-15T19:56:58.007Z
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#1135
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py:60-0
Timestamp: 2025-10-15T19:56:58.007Z
Learning: In the CompactConnect codebase, when migrating from a single field to a list field (e.g., clinicalPrivilegeActionCategory to clinicalPrivilegeActionCategories), both fields are intentionally kept as optional during the migration period to support backwards compatibility. Mutual exclusivity validation is not enforced during this phase, as the deprecated field will be removed in a follow-up PR with migration scripts.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
📚 Learning: 2025-09-03T22:29:24.535Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/data-events/handlers/encumbrance_events.py:0-0
Timestamp: 2025-09-03T22:29:24.535Z
Learning: The EncumbranceEventDetailSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/data_event/api.py is used across multiple instances/contexts where adverseActionCategory and adverseActionId fields may be required in some cases and not present in others. This is an intentional design pattern for handling different event variants with a single schema.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional).

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
📚 Learning: 2025-09-11T20:06:40.709Z
Learnt from: ChiefStief
PR: csg-org/CompactConnect#1036
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py:370-383
Timestamp: 2025-09-11T20:06:40.709Z
Learning: The EncumbranceDetailsSchema in backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py does not contain a 'note' field. It only has clinicalPrivilegeActionCategory (String, optional), adverseActionId (UUID, required), and licenseJurisdiction (Jurisdiction, optional). When working with encumbrance notes, use encumbranceDetails['clinicalPrivilegeActionCategory'], not encumbranceDetails['note'].

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
🧬 Code graph analysis (2)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (6)
backend/compact-connect/stacks/persistent_stack/event_bus.py (1)
  • EventBus (9-25)
backend/common-cdk/common_constructs/stack.py (2)
  • Stack (18-89)
  • common_env_vars (81-89)
backend/compact-connect/common_constructs/python_function.py (1)
  • PythonFunction (19-138)
backend/compact-connect/stacks/persistent_stack/__init__.py (1)
  • PersistentStack (35-480)
backend/compact-connect/stacks/api_lambda_stack/__init__.py (1)
  • ApiLambdaStack (33-222)
backend/compact-connect/lambdas/python/common/cc_common/config.py (6)
  • provider_table (88-89)
  • ssn_table (92-93)
  • ssn_index_name (179-180)
  • event_bus_name (84-85)
  • rate_limiting_table (282-283)
  • user_pool_id (191-195)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (4)
  • clinicalPrivilegeActionCategories (94-95)
  • clinicalPrivilegeActionCategories (98-99)
  • clinicalPrivilegeActionCategory (84-85)
  • clinicalPrivilegeActionCategory (88-91)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • ClinicalPrivilegeActionCategoryField (114-116)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (6)
backend/compact-connect/stacks/api_lambda_stack/provider_management.py (1)

18-336: Well-structured provider management infrastructure.

The ProviderManagementLambdas class provides a clean consolidation of provider management handlers with:

  • Comprehensive environment variable setup (lines 32-45)
  • Appropriate IAM grants for each handler's specific needs
  • Proper CDK-NAG suppressions with clear reasoning
  • Robust monitoring for the sensitive SSN endpoint (lines 177-269) including anomaly detection, rate limiting, and endpoint disable alarms
  • Correct integration with the event bus and persistent resources

The encumbrance handler (lines 306-336) correctly wires the new feature with appropriate table and event bus access.

backend/compact-connect/stacks/api_stack/v1_api/api.py (1)

159-167: Clean refactor: handler wiring delegated to api_lambda_stack.

The change to pass api_lambda_stack to ProviderManagement aligns with the new architecture where provider management handlers are pre-constructed in ProviderManagementLambdas and exposed through the api_lambda_stack. This simplifies the API wiring by removing the need to pass individual persistent resources and the event bus.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (4)

2-4: LGTM!

The new imports are correctly used for the list field definition and mutual exclusivity validation.


28-32: LGTM! Empty list validation is now enforced.

The validate=Length(min=1) on line 29 addresses the previous review concern about accepting empty category lists. The field-level validation will catch empty lists before schema-level validation runs.


34-49: LGTM! Validation logic is correct and clear.

The mutual exclusivity validation correctly enforces that exactly one of the two fields must be provided, with clear error messages guiding users toward the new plural field.


101-105: Asymmetry is intentional for migration handling—no action required

The difference between POST and response schema validation reflects a deliberate migration design:

  • POST schema validates that new requests use exactly one field (enforces data quality on write)
  • Response schema has no such validation (allows serializing legacy database records during migration period)

Since adverseActions data is populated from database records via to_dict(), the response schema must tolerate whatever state exists in the database—old singular fields, new plural fields, or transitional states. The POST validation ensures new adverse actions conform to the updated schema, while the response schema remains forgiving for backwards compatibility.

This design correctly handles the migration referenced in issue #1136.

@isabeleliassen isabeleliassen merged commit b7ba4f6 into csg-org:development Oct 21, 2025
5 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.

adverse action muliple select BE

4 participants