Skip to content

Feat/add endpoints for uploading adverse actions for licenses and privileges #769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

landonshumway-ia
Copy link
Collaborator

@landonshumway-ia landonshumway-ia commented Apr 28, 2025

State Admins need the ability to upload adverse actions against a practitioner to denote they are not able to practice with a specific license or privilege in a state.

As part of this change, we've started hitting pain points with using static json files for checking our expected responses from the API. This adds a TestDataGenerator class which can be used to create any number of variations in our tests to test specific use cases. We have also added data classes for abstracting the database layer from our application logic, which should greatly simplify how we are reading/writing data to our tables. This change is not a complete refactor, rather just a starting point for us to work with as we continue to develop the project.

Requirements List

  • This adds a new field to the provider detailed response body, which should be a backwards compatible change

Description List

  • Added endpoints for uploading adverse actions against licenses and privileges
  • Added adverse action schema with needed fields according to the data model design
  • Returning list of adverse actions for any privilege/license that has them in the provider detailed response
  • Added data classes for each record schema type for abstracting database layer from application logic.
  • Added TestDataGenerator util class to make it easier to generate tests with variable data.

Testing List

  • Added test cases for adverse action related changes
  • For API configuration changes: CDK tests added/updated in backend/compact-connect/tests/unit/test_api.py

Closes #672

Summary by CodeRabbit

  • New Features
    • Added API endpoints to encumber provider privileges and licenses with adverse actions, enabling administrative control to restrict privileges.
    • Introduced adverse action data models and validation schemas for detailed recording of encumbrances.
    • Included adverse actions in provider, license, and privilege response data.
    • Added a new authorization decorator enforcing state-level permission scopes.
    • Added utility to retrieve license type information by abbreviation within compacts.
  • Bug Fixes
    • Made deactivation note mandatory when deactivating privileges.
  • Refactor
    • Renamed and restructured provider management API classes and resources for clearer privilege and license operations.
    • Consolidated and updated data models for licenses, privileges, adverse actions, military affiliations, and related entities.
    • Centralized timezone handling for expiration dates.
    • Updated schema imports and validation logic for consistency.
  • Tests
    • Added thorough unit and integration tests for encumbrance endpoints, adverse action schemas, and provider responses.
    • Enhanced test data generators and standardized test constants for consistent testing.
  • Documentation
    • Improved API schema definitions and comments to specify required fields and clarify request/response formats.
    • Updated OpenAPI specification with new encumbrance endpoints and request schemas.

Copy link
Contributor

coderabbitai bot commented Apr 28, 2025

Walkthrough

This change introduces a comprehensive adverse action (encumbrance) feature for provider licenses and privileges within the Compact Connect system. It adds new API endpoints for state administrators to upload adverse actions, implements the backend logic to persist these encumbrances, and updates the data models and schemas to support adverse action records. The update includes new data classes, Marshmallow schemas, Lambda handler logic, CloudFormation resource definitions, and extensive unit and integration tests. Test utilities and constants are added to facilitate consistent test data generation, and existing provider and privilege/license models are refactored to support adverse actions and their associations.

Changes

File(s) / Path(s) Change Summary
.../cc_common/data_model/schema/adverse_action/, .../cc_common/data_model/schema/common.py, .../cc_common/data_model/schema/fields.py, .../cc_common/data_model/schema/license/common.py, .../cc_common/data_model/schema/license/__init__.py, .../cc_common/data_model/schema/license/api.py, .../cc_common/data_model/schema/privilege/__init__.py, .../cc_common/data_model/schema/privilege/api.py, .../cc_common/data_model/schema/privilege/record.py, .../cc_common/data_model/schema/provider/__init__.py, .../cc_common/data_model/schema/provider/record.py, .../cc_common/data_model/schema/home_jurisdiction/__init__.py, .../cc_common/data_model/schema/military_affiliation/__init__.py, .../cc_common/data_model/schema/military_affiliation/common.py, .../cc_common/data_model/schema/military_affiliation/record.py Introduced new data classes and schemas for adverse actions, refactored license/privilege/provider/military affiliation models to use data classes, and added/updated fields and enums related to adverse actions and military affiliations.
.../cc_common/data_model/data_client.py, .../cc_common/data_model/provider_record_util.py Added logic for encumbering privileges and licenses, including DynamoDB transaction helpers and adverse action record handling; extended provider record assembly to include adverse actions.
.../cc_common/config.py Added accessor for license type abbreviations and centralized expiration timezone logic.
.../cc_common/data_model/schema/__init__.py Registered new schema types for adverse actions and related records.
.../cc_common/data_model/schema/license/ingest.py, .../cc_common/data_model/schema/license/record.py, .../cc_common/data_model/schema/military_affiliation/api.py, .../cc_common/data_model/schema/provider/api.py Updated import paths and added clarifying comments; no functional logic changes.
.../cc_common/data_model/schema/base_record.py Standardized unused argument naming in schema methods for clarity; updated license and compact eligibility calculations to consider encumbrance status.
.../cc_common/common_test/test_constants.py, .../cc_common/common_test/test_data_generator.py Introduced shared test constants and a utility class for generating and storing default test data objects and API responses.
.../cc_common/tests/unit/test_data_model/test_schema/test_adverse_action.py Added unit tests for adverse action data model and schema validation.
.../provider-data-v1/handlers/encumbrance.py Added Lambda handler for privilege/license encumbrance endpoints, including request validation and adverse action persistence.
.../provider-data-v1/handlers/privileges.py Made deactivationNote a required field for privilege deactivation requests.
.../provider-data-v1/handlers/provider_s3_events.py, .../provider-data-v1/handlers/provider_users.py Updated import paths for military affiliation constants.
.../provider-data-v1/tests/function/__init__.py, .../provider-data-v1/tests/function/test_data_model/test_provider_transformations.py Updated test setup and imports for new test data generator and constants.
.../provider-data-v1/tests/function/test_handlers/test_encumbrance.py Added integration tests for privilege and license encumbrance endpoints, including database state verification.
.../provider-data-v1/tests/function/test_handlers/test_provider_users.py Refactored to use new test data generator and added tests for adverse action presence in responses.
.../tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json Made deactivationNote a required property in the privilege deactivation request schema.
.../tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json, .../tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json Added new JSON schema snapshots for privilege and license encumbrance request validation.
.../tests/resources/api/adverse-action-post.json, .../tests/resources/api/provider-detail-response.json, .../tests/resources/dynamo/provider.json Added/updated test resource files for adverse action and provider detail scenarios.
.../stacks/api_stack/v1_api/api.py, .../stacks/api_stack/v1_api/api_model.py, .../stacks/api_stack/v1_api/provider_management.py Renamed API classes/resources, added privilege/license encumbrance endpoints and request models, and set up Lambda integration and permissions.
.../tests/app/test_api/test_provider_management_api.py Renamed test class and added tests for new encumbrance endpoints and request models.

Sequence Diagram(s)

sequenceDiagram
    participant AdminUser
    participant API Gateway
    participant EncumbranceLambda
    participant DataClient
    participant DynamoDB

    AdminUser->>API Gateway: POST /providers/{id}/privileges/.../encumbrance (JSON body)
    API Gateway->>EncumbranceLambda: Invoke with path/body/context
    EncumbranceLambda->>DataClient: encumber_privilege(adverse_action)
    DataClient->>DynamoDB: Transactional write (adverse action, privilege update, privilege status)
    DynamoDB-->>DataClient: Success/Failure
    DataClient-->>EncumbranceLambda: Result
    EncumbranceLambda-->>API Gateway: 200 OK (Success message)
    API Gateway-->>AdminUser: Response

    Note over EncumbranceLambda,DataClient: Similar flow for license encumbrance via encumber_license
Loading

Assessment against linked issues

Objective Addressed Explanation
Add two API endpoints for privilege and license adverse action encumbrance (POST) ([#672])
Endpoints create adverse action object in DB with specified schema ([#672])
Update license or privilege with update record to show encumbrance in history ([#672])
Automated tests for encumbrance endpoints and data model ([#672])

Possibly related PRs

  • Feat/split license statuses #681: Introduces license status refactoring and schema updates related to license and privilege status handling, which complements the encumbrance feature additions in this PR.

Suggested reviewers

  • jusdino
  • jlkravitz
  • isabeleliassen

Poem

A bunny hopped through fields of code,
Leaving trails where adverse actions flowed.
Licenses and privileges, now encumbered with care,
Admins can mark them, the system aware.
With schemas and tests, the garden is neat—
Every endpoint and record, a carrot-shaped treat!
🥕🐇


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a123c8 and dd7c908.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@landonshumway-ia landonshumway-ia marked this pull request as ready for review April 28, 2025 21:26
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: 15

🧹 Nitpick comments (28)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)

38-56: Constructor does a costly dump→load round-trip and partially validates DB keys

__init__ first serializes the supplied data with record_schema.dump() only to immediately de-serialize it again with load().
This double pass:

  1. Performs duplicate (de)serialization work on every new object creation,
  2. Silently discards unknown fields during the dump() step (Marshmallow’s default is to drop them), possibly masking bad input before schema validation,
  3. Adds no extra validation beyond what a single load() already provides.

You can achieve the same effect—and catch unknown fields—by doing one load() call, then invoking a schema method (e.g. generate_gsis()) if you truly need post-processing.

Example diff:

-            serialized_object = self._record_schema.dump(data)
-            self._data = self._record_schema.load(serialized_object)
+            # A single load both validates and normalises. If additional post-
+            # processing (e.g. GSI generation) is required, call it explicitly.
+            self._data = self._record_schema.load(data)
+            self._data = self._record_schema.post_dump(self._data, many=False)

58-70: Expose safer fallback for missing mandatory fields

type and dateOfUpdate access self._data[...] directly, raising KeyError if the underlying dict is incomplete (e.g. object instantiated empty then not populated).
Consider returning None or a custom exception with a clearer message.


195-205: Long descriptive strings in ClinicalPrivilegeActionCategory

Storing full NPDB descriptions as enum values increases storage size in DynamoDB keys/indices. If these appear in primary or GSI keys, consider keeping concise machine-friendly values (e.g. FRAUD) and mapping to human-readable labels at the API layer.

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

1-24: Define privilege encumbrance request schema
The new schema correctly specifies the three required fields and disallows additional properties, aligning with the encumbrance handler’s expectations.

Optionally, add a "format": "date" or a regex pattern to encumbranceEffectiveDate to enforce ISO-8601 date compliance:

 "encumbranceEffectiveDate": {
   "description": "The effective date of the encumbrance",
   "type": "string",
+  "format": "date"
 }
backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1)

1-24: Schema definition is well-structured for license encumbrance requests.

The schema properly defines the required fields for license encumbrance (adverse action) requests with appropriate descriptions and type constraints. This aligns with the PR objective of adding endpoints for uploading adverse actions against licenses.

However, consider adding pattern validation for the encumbranceEffectiveDate string to ensure it matches the expected date format.

 "encumbranceEffectiveDate": {
   "description": "The effective date of the encumbrance",
-  "type": "string"
+  "type": "string",
+  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
 },
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (1)

1-32: Consider using direct dictionary access instead of .get() for property getters.

The implementation of HomeJurisdictionSelectionData follows the pattern of other data classes, but the property getters use self._data.get('key') which will return None if the key doesn't exist, potentially hiding errors. If these properties are expected to always exist, consider using direct dictionary access self._data['key'] to raise a KeyError if the key is missing, making debugging easier.

Also, consider expanding the class docstring to be more specific about what a "Home Jurisdiction Selection" represents in the context of the application.

class HomeJurisdictionSelectionData(CCDataClass):
    """
    Class representing a Home Jurisdiction Selection with properties and setters.
+   
+   A Home Jurisdiction Selection represents the jurisdiction a provider has 
+   designated as their primary location for compact purposes.
    """

    def __init__(self, data: dict[str, Any] = None):
        super().__init__(ProviderHomeJurisdictionSelectionRecordSchema(), data)

    @property
    def provider_id(self) -> UUID:
-       return self._data.get('providerId')
+       return self._data['providerId']

    @property
    def compact(self) -> str:
-       return self._data.get('compact')
+       return self._data['compact']

    @property
    def jurisdiction(self) -> str:
-       return self._data.get('jurisdiction')
+       return self._data['jurisdiction']

    @property
    def date_of_selection(self) -> datetime:
-       return self._data.get('dateOfSelection')
+       return self._data['dateOfSelection']
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1)

113-114: Ensure type validation in administrator_set_status setter

The setter method accepts a parameter of type ActiveInactiveStatus but doesn't validate that the input is of this type, potentially allowing strings or other values to be set directly.

Consider adding type validation to ensure the parameter is actually an ActiveInactiveStatus enum value:

@administrator_set_status.setter
def administrator_set_status(self, value: ActiveInactiveStatus) -> None:
+    if not isinstance(value, ActiveInactiveStatus):
+        value = ActiveInactiveStatus(value)
    self._data['administratorSetStatus'] = value
backend/compact-connect/stacks/api_stack/v1_api/api_model.py (1)

411-440: Extract common schema to reduce duplication

The post_license_encumbrance_request_model and post_privilege_encumbrance_request_model have identical structure and fields, indicating an opportunity for code reuse.

Create a shared schema definition:

@property
def _encumbrance_request_schema(self):
    return JsonSchema(
        type=JsonSchemaType.OBJECT,
        additional_properties=False,
        required=['encumbranceEffectiveDate', 'clinicalPrivilegeActionCategory', 'blocksFuturePrivileges'],
        properties={
            'encumbranceEffectiveDate': JsonSchema(
                type=JsonSchemaType.STRING,
                description='The effective date of the encumbrance',
                format='date',
                pattern=cc_api.YMD_FORMAT,
            ),
            'clinicalPrivilegeActionCategory': JsonSchema(
                type=JsonSchemaType.STRING,
                description='The category of clinical privilege action',
                enum=['SUSPENSION', 'REVOCATION', 'LIMITATION', 'OTHER'],
            ),
            'blocksFuturePrivileges': JsonSchema(
                type=JsonSchemaType.BOOLEAN,
                description='Whether this encumbrance blocks future privileges',
            ),
        },
    )

Then use it in both properties:

@property
def post_privilege_encumbrance_request_model(self) -> Model:
    """Return the post privilege encumbrance request model, which should only be created once per API"""
    if hasattr(self.api, '_v1_post_privilege_encumbrance_request_model'):
        return self.api._v1_post_privilege_encumbrance_request_model
    self.api._v1_post_privilege_encumbrance_request_model = self.api.add_model(
        'V1PostPrivilegeEncumbranceRequestModel',
        description='Post privilege encumbrance request model',
        schema=self._encumbrance_request_schema,
    )
    return self.api._v1_post_privilege_encumbrance_request_model
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1)

53-55: Inconsistency with class documentation

While the class documentation (lines 13-15) states that the class does not include setters because license records are only upserted during ingestion, a setter is provided for given_name.

Either:

  1. Update the documentation to clarify that some setters exist for specific fields:
"""
Class representing a License with getters and setters for all properties.

Unlike several other CCDataClass subclasses, this one does not include setters for most properties. This is because
license records are only upserted during ingestion, so we can pass the entire record
from the ingestion process into the constructor.
"""
  1. Or remove the setter for consistency:
-@given_name.setter
-def given_name(self, value: str) -> None:
-    self._data['givenName'] = value
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (1)

193-198: Add defensive error handling for adverse action records

The adverse action handling assumes that records have both "jurisdiction" and "licenseType" fields, but doesn't validate their presence, which could lead to KeyError exceptions.

Add defensive error handling:

case 'adverseAction':
    logger.debug('Identified adverse action record')
+   try:
        if record['actionAgainst'] == 'privilege':
            privileges[f'{record["jurisdiction"]}-{record["licenseType"]}']['adverseActions'].append(record)
        elif record['actionAgainst'] == 'license':
            licenses[f'{record["jurisdiction"]}-{record["licenseType"]}']['adverseActions'].append(record)
+   except KeyError as e:
+       logger.warning(f"Adverse action record missing required fields: {e}")
+       # Decide whether to raise an exception or skip the record
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)

75-85: Simplify & short-circuit licence-type look-up

The current loop keeps iterating even after finding a match. A dict comprehension or next() is clearer and O(n)→early-exit.

-    adverse_action_license_type = None
-    adverse_action_license_type_abbreviation = None
-
-    for license_type_name, license_type_abbreviation in compact_license_types.items():
-        if license_type_abbreviation_from_path_parameter == license_type_abbreviation.lower():
-            adverse_action_license_type = license_type_name
-            adverse_action_license_type_abbreviation = license_type_abbreviation
+    match = next(
+        (
+            (name, abbr)
+            for name, abbr in compact_license_types.items()
+            if abbr.lower() == license_type_abbreviation_from_path_parameter
+        ),
+        None,
+    )
+    if match:
+        adverse_action_license_type, adverse_action_license_type_abbreviation = match
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1)

14-19: Export constants in an __all__ to guide static analyzers

Adding __all__ makes public API intent explicit and prevents * import leaking.

 MILITARY_AFFILIATION_RECORD_TYPE = 'militaryAffiliation'

+# Public symbols re-exported by this module
+__all__ = [
+    'MilitaryAffiliationStatus',
+    'MilitaryAffiliationType',
+    'SUPPORTED_MILITARY_AFFILIATION_FILE_EXTENSIONS',
+    'MILITARY_AFFILIATIONS_DOCUMENT_TYPE_KEY_NAME',
+    'MILITARY_AFFILIATION_RECORD_TYPE',
+]
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py (1)

346-358: Typo in test name – retursreturns

Accurate test names aid discoverability.

-    def test_patch_provider_military_affiliation_returs_400_if_invalid_body(self):
+    def test_patch_provider_military_affiliation_returns_400_if_invalid_body(self):
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (2)

59-65: Getter / setter symmetry for action_against

The setter expects an AdverseActionAgainstEnum but the getter returns a raw str.
Call-sites therefore have to remember to convert back to the Enum if they want to compare values, which is error-prone and undermines the type-safety you were aiming for.

Consider returning the Enum instead:

-    def action_against(self) -> str:
-        return self._data.get('actionAgainst')
+    def action_against(self) -> AdverseActionAgainstEnum | None:
+        value = self._data.get('actionAgainst')
+        return AdverseActionAgainstEnum(value) if value is not None else None

That way the API remains symmetrical and consumers consistently work with the Enum.


121-126: Setter type hint should allow None

lifting_user is nullable (getter annotation already reflects this) but the setter’s parameter type is UUID, preventing callers from clearing the field without a cast.

-    def lifting_user(self, value: UUID) -> None:
+    def lifting_user(self, value: UUID | None) -> None:

This keeps the public contract consistent.

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

85-88: adverseActions list may be omitted – consider default empty list

The field is marked required=False / allow_none=False.
If the key is omitted entirely, Marshmallow accepts the object; if the key is present but null, validation fails.
Many clients find it more convenient when collections are ALWAYS present (as []) to avoid extra null-checks.

You can achieve that by adding missing=list:

-    adverseActions = List(Nested(AdverseActionGeneralResponseSchema, required=False, allow_none=False))
+    adverseActions = List(
+        Nested(AdverseActionGeneralResponseSchema, allow_none=False),
+        required=False,
+        allow_none=False,
+        missing=list,
+    )

Same comment applies to the public schema below.


153-155: Duplication between public & staff schemas

Both PrivilegeGeneralResponseSchema and PrivilegePublicResponseSchema now declare an identical adverseActions field.
To avoid drift, you could pull this into a small mix-in or base class shared by both privilege schemas (similar to how history is handled).

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

475-490: Missing negative-path assertion for Lambda handler name

You assert the handler path for the privilege encumbrance Lambda but do not check the same for the license encumbrance flow added later in the test file.
A simple copy-paste of the block below (adapted to the license resource) would guard against future regressions where the handler logical-ID changes silently.


491-563: Highly repetitive resource-path discovery – extract helper

The four nested find_resources calls (privilegesjurisdiction → …) are repeated almost verbatim in several tests (deactivate privilege, privilege encumbrance, license encumbrance).
Boiler-plate makes the test brittle and obscures intent.

Consider introducing a helper that walks the template given a list of PathParts and returns the logical-ID of the last resource, e.g.:

def get_resource_id(template: Template, parent_id: str, parts: list[str]) -> str:
    current_parent = parent_id
    for part in parts:
        resources = template.find_resources(
            CfnResource.CFN_RESOURCE_TYPE_NAME,
            props={'Properties': {'ParentId': {'Ref': current_parent}, 'PathPart': part}},
        )
        assert len(resources) == 1
        current_parent = next(iter(resources))
    return current_parent

Your tests then shrink to:

encumbrance_resource_id = get_resource_id(
    api_stack_template,
    api_stack.get_logical_id(provider_resource),
    ['privileges', 'jurisdiction', '{jurisdiction}', 'licenseType', '{licenseType}', 'encumbrance'],
)

This keeps each test focused on the behaviour being validated, not on the plumbing required to navigate the template.

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

78-90: Test could silently pass with a non-UUID value

assertIsInstance(dumped_data['adverseActionId'], str) only guarantees a string, not a UUID.
Using uuid.UUID() for parsing enforces the type and format:

-        self.assertIsInstance(dumped_data['adverseActionId'], str)
+        from uuid import UUID
+        # Will raise ValueError if not a valid UUID string
+        UUID(dumped_data['adverseActionId'])
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (2)

573-587: Docstring inaccurate – refers to deactivation instead of encumbrance

The helper builds the Lambda powering the encumbrance endpoints, but the docstring still says “deactivating a provider's privilege”, which is misleading for future maintainers.

-        """Create and configure the Lambda handler for deactivating a provider's privilege."""
+        """Create and configure the Lambda handler for encumbering a provider's license or privilege."""

608-656: Consider dedicated handlers or explicit request context

Both privilege- and license-level encumbrance routes invoke the same Lambda (ProviderEncumbranceHandler).
Ensure the handler reliably distinguishes the target (privilege vs. license) using pathParameters; otherwise an incorrect branch could be updated.

If the logic diverges substantially, two thin handlers delegating to shared code may simplify IAM scoping and CloudWatch metrics.

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

44-52: Enum instance vs. value – explicit string conversion improves readability

ClinicalPrivilegeActionCategory.UNSAFE_PRACTICE is a str-Enum, so json.dumps works, but the implicit coercion is opaque.
Passing .value (or casting to str) makes intent crystal-clear and avoids surprises if the enum base type changes.

-        'clinicalPrivilegeActionCategory': ClinicalPrivilegeActionCategory.UNSAFE_PRACTICE,
+        'clinicalPrivilegeActionCategory': ClinicalPrivilegeActionCategory.UNSAFE_PRACTICE.value,

59-76: Hidden duplication – consider parameterising helper

_when_testing_valid_privilege_encumbrance & _when_testing_valid_license_encumbrance only differ in the resource string &
event-specific record generator. Extract a generic helper (or use pytest.mark.parametrize) to cut duplication and future drift.

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

924-977: Duplicate helpers – consolidate _generate_put_adverse_action_item & _generate_put_item_transaction

Both helpers wrap a Put operation with identical structure; the only difference is that one expects an
AdverseActionData instance while the other accepts a plain dict. Keeping both increases maintenance cost
and risks divergence.

Idea: drop _generate_put_adverse_action_item and always call the generic _generate_put_item_transaction,
doing the serialize_to_database_record() call at the call-site when needed.


987-1078: Encumber privilege – conditional update is non-idempotent

If two concurrent encumbrance requests arrive for the same privilege, both will attempt
to write the same privilegeUpdate & adverseAction records (identical PK/SK collisions) and, when the privilege is already inactive, the second transaction still inserts a second duplicate update record with an empty updatedValues dict.

Consider:

  1. Adding an ConditionExpression to the Put on the update & adverse-action items (attribute_not_exists(pk)) to guarantee
    at-most-once semantics.
  2. Using a deterministic UUID or hash for adverseActionId to make retries idempotent.

1079-1169: Docstring & log messages mention “privilege”, but method operates on licenses

Minor, yet confusing: encumber_license copies the docstring from encumber_privilege, repeatedly
saying “privilege”. Update the wording to “license” to avoid cognitive overhead for future maintainers.

backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)

228-232: Consider consistent naming for abbreviation methods.

The method name uses "abbr" while working with "abbreviations" in the config. Consider using the full word for consistency.

-def get_license_type_abbr_for_license_type(compact: str, license_type: str) -> str:
+def get_license_type_abbreviation_for_license_type(compact: str, license_type: str) -> str:
    from cc_common.config import config

    return config.license_type_abbreviations[compact][license_type]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80f8fb9 and 94ce899.

📒 Files selected for processing (47)
  • backend/compact-connect/lambdas/python/common/cc_common/config.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (6 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/__init__.py (1 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 (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (5 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/common.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/ingest.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py (5 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/data_model/schema/provider/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_constants.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.json (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json (2 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json (1 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/provider-data-v1/handlers/encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py (2 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py (7 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api.py (2 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py (2 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (5 hunks)
  • backend/compact-connect/tests/app/test_api/test_provider_management_api.py (17 hunks)
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (15)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/ingest.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/common.py (1)
  • LicenseCommonSchema (15-44)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (2)
  • MilitaryAffiliationStatus (4-7)
  • MilitaryAffiliationType (10-12)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1)
  • MilitaryAffiliationType (10-12)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (4)
  • ActiveInactiveStatus (180-182)
  • ClinicalPrivilegeActionCategory (195-205)
  • CompactEligibilityStatus (185-187)
  • UpdateCategory (171-177)
backend/compact-connect/stacks/api_stack/v1_api/api.py (1)
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (1)
  • ProviderManagement (34-656)
backend/compact-connect/lambdas/python/common/cc_common/config.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (2)
  • compact (26-27)
  • compact (148-149)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (4)
  • compact (29-30)
  • compact (33-34)
  • compact (149-150)
  • compact (153-154)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (1)
  • AdverseActionGeneralResponseSchema (51-59)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (1)
  • ForgivingSchema (23-27)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (4)
  • NationalProviderIdentifier (30-32)
  • ITUTE164PhoneNumber (60-66)
  • ActiveInactive (45-47)
  • CompactEligibility (50-52)
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (4)
backend/compact-connect/tests/app/base.py (2)
  • get_resource_properties_by_logical_id (65-72)
  • compare_snapshot (393-420)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (28-36)
backend/compact-connect/stacks/api_stack/cc_api.py (1)
  • staff_users_authorizer (247-253)
backend/compact-connect/stacks/api_stack/v1_api/api_model.py (1)
  • message_response_model (21-39)
backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py (1)
  • LicenseUpdatePreviousResponseSchema (177-206)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1)
  • MilitaryAffiliationStatus (4-7)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py (1)
  • PrivilegeUpdatePreviousGeneralResponseSchema (28-45)
backend/compact-connect/lambdas/python/common/cc_common/exceptions.py (1)
  • CCInvalidRequestException (7-8)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py (2)
  • AdverseActionGeneralResponseSchema (51-59)
  • AdverseActionPublicResponseSchema (24-48)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2)
  • ActiveInactive (45-47)
  • Jurisdiction (40-42)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • CCEnum (95-105)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (4)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py (1)
  • TstFunction (22-394)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • ClinicalPrivilegeActionCategory (195-205)
  • serialize_to_database_record (89-92)
  • to_dict (76-87)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (8)
  • put_default_privilege_record_in_provider_table (220-226)
  • get_license_type_abbr_for_license_type (229-232)
  • generate_default_adverse_action (48-68)
  • generate_default_privilege_update (235-260)
  • generate_default_privilege (189-211)
  • put_default_license_record_in_provider_table (154-160)
  • generate_default_license_update (163-186)
  • generate_default_license (117-151)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (28-36)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py (1)
  • PostMilitaryAffiliationResponseSchema (11-23)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2)
  • ActiveInactive (45-47)
  • Jurisdiction (40-42)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/jurisdiction/__init__.py (1)
  • Jurisdiction (44-90)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (2)
  • attestations (93-94)
  • attestations (97-98)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • CCDataClass (15-92)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/record.py (1)
  • ProviderHomeJurisdictionSelectionRecordSchema (10-25)
🔇 Additional comments (52)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)

162-169: Confirm downstream handling for new AdverseActionAgainstEnum

Adding a brand-new enum is fine, but any .from_str() or literal comparisons in existing code paths will now receive unexpected values (privilege / license). Please verify usage sites to avoid KeyError/ValueError.


171-178: Enum re-ordering changes string values order only — good, but double-check DB migrations

Because UpdateCategory is a StrEnum, only the names / values matter (not the declaration order). Still, if you persist the .name of an enum anywhere, inserting new members may break deserialization of historical records. Make sure historical data is covered by tests/migrations.

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

119-119: Import path update looks correct

The test now imports MilitaryAffiliationType from the new ...military_affiliation.common module, matching the refactor. No further action required.

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

8-8: Updated import path aligns with new module structure

Switching to military_affiliation.common keeps the API schema in sync with the enum relocation. Looks good.

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

15-15: Import path update is correct and aligns with refactor
The LicenseCommonSchema import now points to the consolidated license/common.py module, matching the new schema layout. No changes needed beyond this alignment.

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

24-24: Consistent import path adjustment for LicenseCommonSchema
The import of LicenseCommonSchema from the license/common.py submodule mirrors the change in the ingest schema and maintains modularity. This aligns with the broader refactoring.

backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py (1)

3-3: Align military affiliation constant import with new module structure
The MILITARY_AFFILIATIONS_DOCUMENT_TYPE_KEY_NAME constant is now imported from the military_affiliation/common.py submodule, consistent with the refactoring in related files.

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

8-12: Update military affiliation enums import to common submodule
The MILITARY_AFFILIATION_RECORD_TYPE, MilitaryAffiliationStatus, and MilitaryAffiliationType are now sourced from military_affiliation/common.py, matching the new centralized definitions. This import change is correct.

backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json (2)

61-61: Include adverseActions in license objects
Ensures that the license schema in test fixtures aligns with the new LicenseGeneralResponseSchema which now exposes an empty adverseActions list when there are no adverse actions.

Please verify in provider_record_util.py and the corresponding Marshmallow schema that adverseActions is always present (defaulting to []) for license entries even when no actions exist.


118-118: Include adverseActions in privilege objects
Adds an empty adverseActions array to privilege fixtures to match the updated PrivilegeGeneralResponseSchema.

Confirm that the privilege encumbrance logic populates this field consistently and that the API response schema reflects it.

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

10-12: Enforce deactivationNote as a required property
Adding "required": ["deactivationNote"] ensures that any privilege deactivation request must include a descriptive note.

Verify that the handler in privileges.py correctly rejects requests without deactivationNote and returns a clear validation error.

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

116-116: Clarify omission of licenses in public schema
The added comment makes it explicit that ProviderPublicResponseSchema intentionally omits license data, helping future maintainers understand the exposure differences between public and staff endpoints.

backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.json (1)

1-5: Add sample adverse action POST payload
Provides a concrete example for testing the new encumbrance endpoint with the required fields: encumbranceEffectiveDate, clinicalPrivilegeActionCategory, and blocksFuturePrivileges.

Consider adding additional fixtures for edge cases (e.g., invalid dates or missing required fields) to strengthen schema validation tests.

backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py (1)

6-11: Import organization looks good.

The imports have been reorganized to align with the broader refactoring of the military affiliation data model, where schemas are imported from the .api submodule and constants/enums from the .common submodule.

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

3-12: Schema imports look good and align with the new adverse action functionality.

The new imports properly register all the necessary schema record types that support the adverse action functionality and other data model changes introduced in this PR.

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

4-7: Import cleanup looks good.

The removal of the pre_load import aligns with the refactoring of schema processing mentioned in the PR objectives. This simplifies the schema by removing redundant datetime conversion.


66-103:

✅ Verification successful

Verify impact of removing pre_load_initialization method.

The file appears to have removed a @pre_load method that previously converted the providerDateOfUpdate field to a datetime object. Ensure that this conversion is now handled elsewhere in the data model abstraction, especially since line 64 still defines providerDateOfUpdate as a DateTime field.


🏁 Script executed:

#!/bin/bash
# Search for any other handling of providerDateOfUpdate conversion
rg "providerDateOfUpdate" --type py -A 5 -B 5

Length of output: 25998


No missing conversion for providerDateOfUpdate

The Marshmallow DateTime field on providerDateOfUpdate automatically deserializes ISO-formatted strings into datetime objects on load and serializes them back to strings on dump, so the former @pre_load hook is no longer needed. The current @pre_dump and @post_load handlers correctly set and then remove the dynamic field.

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

69-71: LGTM! Your implementation matches the pattern used by other custom fields.

The new ClinicalPrivilegeActionCategoryField class correctly validates string values against the ClinicalPrivilegeActionCategory enum and follows the same implementation pattern as other similar field classes like ActiveInactive and UpdateType.

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

142-145: LGTM! Field reordering doesn't impact functionality.

The reordering of fields in PrivilegeUpdatePreviousRecordSchema maintains consistent field types and validation logic while improving alignment with related schemas in the codebase.

Also applies to: 150-150

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

28-30: LGTM! Good testing improvement.

Setting self.maxDiff = None ensures that test failures will show complete diffs regardless of object size, which is very helpful for debugging complex test failures.


33-37: LGTM! Good organization of environment-dependent imports.

Moving environment-dependent imports inside the setup method ensures they're only imported after the TstLambdas initialization has configured the required environment variables. The comment clearly explains the rationale, and the TestDataGenerator is properly assigned for use in tests.

Also applies to: 40-40

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

149-150: Good addition of helper method for accessing license type abbreviations by compact.

This method provides a clean interface to access the license type abbreviations dictionary for a specific compact, consistent with the existing license_types_for_compact method.


210-213: Good refactoring of timezone handling.

Extracting the timezone into a separate property improves maintainability by centralizing the UTC-4 timezone definition. This makes it easier to modify the timezone in the future if needed and follows the DRY principle by avoiding duplication.

Also applies to: 222-222

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

9-9: Clean refactoring from QueryProviders to ProviderManagement.

This change appropriately replaces the QueryProviders implementation with the more comprehensive ProviderManagement class while maintaining the same interface and constructor arguments. This is well-aligned with the PR's goal of adding adverse action functionality to providers, licenses, and privileges.

Also applies to: 139-140

backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py (1)

45-45: Consistent with updated requirement for deactivationNote.

The usage of deactivation_note from the required field is correctly propagated here.

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

1-45: Well-structured schema with clear field definitions and validation rules.

The LicenseCommonSchema provides a solid foundation for license data validation with:

  • Clear separation between required and optional fields
  • Appropriate validators for field lengths
  • Type-specific fields where needed (e.g., Date, Email, ITUTE164PhoneNumber)
  • Good documentation explaining the purpose and serialization direction

This schema will help ensure consistent data quality across the application.

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

9-125: Well-structured data class implementation for provider records.

The ProviderData class follows good practices for data encapsulation:

  • Inherits from CCDataClass for consistent behavior with other data models
  • Provides typed, read-only property accessors for all fields
  • Handles optional fields appropriately with .get() and default values
  • Includes descriptive docstrings explaining the purpose and data source

This implementation will improve code maintainability by providing a structured interface to provider data rather than direct dictionary access.

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

11-11: LGTM: Import added for adverse action integration.

The import of AdverseActionGeneralResponseSchema is needed for the new adverse actions feature.


174-174: LGTM: Added adverse actions to license response schema.

This change appropriately extends the license schema to include associated adverse actions, supporting the new encumbrance feature.


177-206: LGTM: New schema for detailed license history snapshots.

The LicenseUpdatePreviousResponseSchema properly implements a detailed snapshot for license history, including:

  • All fields have appropriate validation rules
  • Sensitive fields like SSN and DOB are included for users with 'readPrivate' permission
  • Clear docstrings explaining purpose and serialization direction

This aligns with the PR's data model enhancements for tracking license changes.

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

11-46: LGTM: Well-implemented data class for military affiliations.

The MilitaryAffiliationData class follows the same excellent pattern as other data classes:

  • Inherits from CCDataClass for consistent behavior
  • Provides typed property accessors for all fields
  • Uses .get() with appropriate defaults for optional fields and collections
  • Clean and consistent implementation with good type annotations

This refactoring improves code structure by moving from direct dictionary access to a more object-oriented approach.

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

9-22: LGTM: Well-designed POST request schema for adverse actions.

The AdverseActionPostRequestSchema properly defines the required fields for creating adverse actions:

  • Required date, category, and boolean flag fields with appropriate validations
  • Clear docstrings explaining purpose and serialization direction
  • Extends ForgivingSchema to handle unknown fields appropriately

This schema validates the inputs needed for the new encumbrance endpoints.


24-49: LGTM: Comprehensive public response schema for adverse actions.

The AdverseActionPublicResponseSchema includes all necessary fields for public API responses:

  • Core identifier fields (type, compact, providerId, jurisdiction)
  • License type information
  • Flag for blocking future privileges
  • Creation and update timestamps
  • Optional lift date for when actions are removed

The schema properly distinguishes between required creation fields and optional fields that are populated when an action is lifted.


51-59: LGTM: Extended schema for users with additional permissions.

The AdverseActionGeneralResponseSchema appropriately extends the public schema by adding the clinicalPrivilegeActionCategory field, which appears to require elevated permissions. This properly implements the permission-based schema hierarchy.

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

12-119: Privilege class implementation follows good OOP principles

The PrivilegeData class shows good encapsulation by implementing typed getters and setters for all properties, properly wrapping the schema validation provided by the Marshmallow PrivilegeRecordSchema.


124-197: PrivilegeUpdateData implementation is consistent and well-structured

The PrivilegeUpdateData class appropriately models privilege update operations with clearly defined properties, proper typing, and good documentation.

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

365-367: Good - Requiring deactivation note ensures proper documentation

Making the deactivationNote required ensures administrators provide reasons for deactivating privileges, improving audit trails and accountability.

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

9-16: Clear documentation about the lack of setters

The class documentation clearly explains why setters are generally not provided, which is helpful for understanding the design decision.


130-169: LicenseUpdateData class is well-structured

The LicenseUpdateData class appropriately models license update operations with clearly defined properties, proper typing, and good documentation.

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

32-33: Good initialization of schema instances at class level

Creating the schema instances at class level avoids repetitive instantiation and is a good optimization for frequently used schemas.


171-176: Proper initialization of adverseActions lists

Initializing empty lists for adverseActions alongside the existing history lists ensures consistent data structure.


231-239: Direct enum comparison is cleaner than string comparison

Comparing directly against enum members rather than their string values is safer and more maintainable.

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

90-118: Use assertCountEqual for collection comparison to avoid order sensitivity

provider_data['licenses'][0]['adverseActions'] ordering is not guaranteed by DynamoDB reads. A deterministic failure may occur.

-        self.assertEqual(
-            [self.test_data_generator.convert_data_to_api_response_formatted_dict(test_adverse_action)],
-            license_adverse_actions,
-        )
+        self.assertCountEqual(
+            [self.test_data_generator.convert_data_to_api_response_formatted_dict(test_adverse_action)],
+            license_adverse_actions,
+        )
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1)

44-53: generate_pk_sk mutates case inconsistently

Only the value used in sk is lower-cased, leaving licenseTypeAbbreviation in the persisted item untouched.
If callers supply "SLP" the stored abbreviation will be mixed-case between the attribute value and the key, making ad-hoc debugging harder.

Either normalise the attribute too or enforce lower-case earlier:

-        license_type_abbr = in_data['licenseTypeAbbreviation'].lower()
+        in_data['licenseTypeAbbreviation'] = in_data['licenseTypeAbbreviation'].lower()
+        license_type_abbr = in_data['licenseTypeAbbreviation']
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (1)

49-64: 👍 Renaming unused kwargs improves clarity

Replacing **kwargs with **_kwargs makes the intent explicit and removes the need for Ruff suppressions.
No functional impact observed.

backend/compact-connect/lambdas/python/common/common_test/test_constants.py (1)

1-96: Constants file looks good – single authoritative source improves test clarity

Centralising default values eliminates magic literals scattered across tests and generators.
Nice addition!

backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (6)

1-15: Import organization looks good.

The imports are well-organized with standard library imports first, followed by package-specific imports. The wildcard import of test constants is appropriately marked with a noqa comment to suppress linter warnings.


18-22: Class documentation is clear and helpful.

The class docstring clearly explains the purpose of this utility class for generating test data with customizable options.


311-314: Well-documented private method.

The method correctly documents that it's accessing private data that shouldn't be modified in runtime code. The noqa comment for the linter warning is appropriate.


316-398: Well-structured test data generation method.

This comprehensive method effectively generates default provider detail responses with realistic test data. It properly handles both cases where provider records are provided or need to be generated with default values, and ensures consistent date formatting for testing.


47-68: Well-implemented adverse action data generator.

The new generate_default_adverse_action method properly implements the data generation for the new adverse action feature, with appropriate fields and type conversions.


24-399: Overall well-designed test utility class.

The TestDataGenerator class is well-designed with consistent patterns for generating and storing test data. It provides methods for all necessary data types including the new adverse action functionality, and allows for flexible customization through value overrides. This approach will significantly improve test maintainability compared to using static JSON files.

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: 5

🔭 Outside diff range comments (1)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)

350-432: ⚠️ Potential issue

Missing adverse actions in default provider detail response.

While the class provides methods to generate adverse action data, the generate_default_provider_detail_response method doesn't include adverse actions in the default items list (lines 414-422). According to the PR objectives, provider responses should include adverse actions.

Apply this diff to include adverse actions in the default response:

default_home_jurisdiction = TestDataGenerator.generate_default_home_jurisdiction_selection()
TestDataGenerator._override_date_of_update_for_record(
    default_home_jurisdiction, datetime.fromisoformat(DEFAULT_HOME_UPDATE_DATE)
)

+ default_adverse_action = TestDataGenerator.generate_default_adverse_action()
+ TestDataGenerator._override_date_of_update_for_record(
+     default_adverse_action, datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)
+ )

items = [
    TestDataGenerator.generate_default_provider().to_dict(),
    default_license_record.to_dict(),
    default_license_update_record.to_dict(),
    default_privilege_record.to_dict(),
    default_privilege_update_record.to_dict(),
    default_military_affiliation.to_dict(),
    default_home_jurisdiction.to_dict(),
+   default_adverse_action.to_dict(),
]
♻️ Duplicate comments (1)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1)

78-109: Adverse action implementation looks good, previously fixed return statement.

The implementation correctly generates and stores default adverse action records with appropriate customization options. The previously identified missing return statement has been properly addressed.

🧹 Nitpick comments (9)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1)

29-35: Return copies of mutable collections to keep the instance truly read-only

document_keys / file_names expose the internal list; callers can mutate the list and inadvertently modify _data.
Return a shallow copy (or a tuple) instead.

-        return self._data.get('documentKeys', [])
+        return list(self._data.get('documentKeys', []))

Apply the same pattern for file_names.

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

123-124: Expose an immutable copy of privilege_jurisdictions

The method currently returns the actual set stored in _data, so callers can mutate the underlying record.

-        return self._data.get('privilegeJurisdictions', set())
+        return set(self._data.get('privilegeJurisdictions', set()))
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (1)

475-734: High duplication – extract helper utilities to keep tests maintainable

The two new encumbrance endpoint tests copy ~150 lines of almost identical nested-resource traversal code.
Abstracting the traversal & assertions into helper methods (or parametrising the test) would:

  1. Make intent clearer (what varies vs. what is boilerplate)
  2. Reduce the chance of future copy-paste mistakes
  3. Shorten individual test methods, improving readability
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (1)

608-656: Duplicate endpoint-registration code – favour a shared helper

_add_encumber_privilege and _add_encumber_license differ only in:

  • the parent resource variable
  • the API model used

Extracting a private method _add_encumber_endpoint(parent_resource, request_model) would reduce ~40 duplicated lines and make future updates safer.

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

970-977: Unused helper method.

This helper method for generating an adverse action item is defined but never used in the implementation. The encumber methods use _generate_put_item_transaction instead.

Consider removing this unused method or refactoring the code to use it instead of the generic _generate_put_item_transaction for adverse actions.

backend/compact-connect/docs/api-specification/latest-oas30.json (4)

414-504: Introduce license encumbrance endpoint.

The new POST /providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance endpoint is well-defined with all necessary path parameters, request schema reference (SandboLicen62Uj6JPFczem), and Cognito admin authorizer scopes. Consider adding standard error responses (400, 401, 403) under responses to document validation and auth failures.


596-686: Introduce privilege encumbrance endpoint.

The new POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance endpoint mirrors the license encumbrance endpoint. Ensure both endpoints share consistent path formatting and error response definitions. Adding 400, 401, and 403 responses would improve spec completeness.


4254-4276: Define license encumbrance request schema.

The SandboLicen62Uj6JPFczem schema correctly lists the required fields for a license encumbrance. To enforce date validation, consider adding "format": "date" or a pattern for encumbranceEffectiveDate.


5280-5302: Define privilege encumbrance request schema.

The SandboLicenFzKiEdNIK5oF schema is symmetric to the license version. Similarly, adding "format": "date" (or a regex pattern) to encumbranceEffectiveDate will improve consistency and validation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94ce899 and 0eb4c1c.

📒 Files selected for processing (49)
  • backend/compact-connect/docs/api-specification/latest-oas30.json (6 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/config.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (6 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/__init__.py (1 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 (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (5 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/common.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/ingest.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py (5 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/data_model/schema/provider/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_constants.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.json (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json (2 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json (1 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/provider-data-v1/handlers/encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py (2 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py (7 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api.py (2 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py (3 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (5 hunks)
  • backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py (1 hunks)
  • backend/compact-connect/tests/app/test_api/test_provider_management_api.py (17 hunks)
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json (1 hunks)
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/ingest.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/stacks/persistent_stack/compact_configuration_upload.py
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/init.py
🚧 Files skipped from review as they are similar to previous changes (35)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/compact-connect/lambdas/python/common/tests/resources/api/provider-detail-response.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py
  • backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.json
  • backend/compact-connect/lambdas/python/common/tests/resources/dynamo/provider.json
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/api.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/init.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py
  • backend/compact-connect/lambdas/python/common/cc_common/config.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/license/api.py
  • backend/compact-connect/stacks/api_stack/v1_api/api.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/common.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/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/api.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.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/schema/common.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/common.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py
  • backend/compact-connect/lambdas/python/common/common_test/test_constants.py
🧰 Additional context used
🧬 Code Graph Analysis (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • CCDataClass (15-92)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py (1)
  • ProviderRecordSchema (21-102)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
  • CCDataClass (15-92)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/record.py (1)
  • MilitaryAffiliationRecordSchema (16-41)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (2)
  • provider_id (21-22)
  • compact (25-26)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (6)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (11)
  • AdverseActionData (9-126)
  • compact (19-20)
  • compact (23-24)
  • provider_id (27-28)
  • provider_id (31-32)
  • jurisdiction (35-36)
  • jurisdiction (39-40)
  • license_type_abbreviation (43-44)
  • license_type_abbreviation (47-48)
  • license_type (51-52)
  • license_type (55-56)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (2)
  • ActiveInactiveStatus (180-182)
  • CompactEligibilityStatus (185-187)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (10)
  • LicenseData (9-127)
  • compact (26-27)
  • compact (148-149)
  • provider_id (22-23)
  • provider_id (144-145)
  • jurisdiction (30-31)
  • jurisdiction (152-153)
  • license_type (34-35)
  • license_type (156-157)
  • compact_eligibility (126-127)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (19)
  • PrivilegeData (12-121)
  • compact (29-30)
  • compact (33-34)
  • compact (149-150)
  • compact (153-154)
  • provider_id (21-22)
  • provider_id (25-26)
  • provider_id (141-142)
  • provider_id (145-146)
  • jurisdiction (37-38)
  • jurisdiction (41-42)
  • jurisdiction (157-158)
  • jurisdiction (161-162)
  • administrator_set_status (109-110)
  • administrator_set_status (113-114)
  • license_type (53-54)
  • license_type (57-58)
  • license_type (165-166)
  • license_type (169-170)
backend/compact-connect/lambdas/python/common/cc_common/config.py (3)
  • provider_table (86-87)
  • current_standard_datetime (247-251)
  • dynamodb_client (36-37)
backend/compact-connect/lambdas/python/common/cc_common/exceptions.py (1)
  • CCNotFoundException (19-20)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (13)
backend/compact-connect/tests/app/test_api/test_provider_management_api.py (1)

553-566: Potential mismatch between looked-up and hard-coded logical IDs

You locate the resource via find_resources(...) (👍) but then ignore that value and instead use
api_stack.get_logical_id(api_stack.api.v1_api.provider_management.encumbrance_privilege_resource …)
to build encumbrance_resource_logical_id.
If the CFN logical ID ever changes (e.g., due to refactoring), the assertion that len(encumbrance_resource_logical_ids) == 1
will still pass, but the subsequent has_resource_properties could reference the wrong ID and silently miss the bug.

Consider re-using the ID returned from find_resources:

-encumbrance_resource_logical_id = api_stack.get_logical_id(
-    api_stack.api.v1_api.provider_management.encumbrance_privilege_resource.node.default_child
-)
+encumbrance_resource_logical_id = next(iter(encumbrance_resource_logical_ids))
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4)

14-24: Appropriate imports for new functionality.

The added imports correctly bring in the necessary classes and enums for the new adverse action functionality. The AdverseActionData class, status enums, and updated data model classes align with the PR's objective of supporting adverse actions for licenses and privileges.


924-946: Well-structured helper method for updating privilege status.

This helper method encapsulates the transaction item generation for updating a privilege's administrator status, promoting code reuse and readability. The method handles all required DynamoDB formatting logic correctly.


947-969: Well-structured helper method for updating license eligibility status.

Similar to the privilege status helper, this method correctly encapsulates the transaction item generation for updating a license's compact eligibility status, promoting code reuse and consistency.


978-985: Generic helper for transaction item generation.

This helper method provides a generalized way to create transaction items, simplifying the code in the encumber methods.

backend/compact-connect/docs/api-specification/latest-oas30.json (2)

5-5: Confirm OpenAPI version update.

The version field has been updated to "2025-04-28T20:50:55Z". Ensure this matches the release date of this API spec and is reflected across CDK/API documentation.


1903-1905: Schema deactivationNote requirement added.

The deactivationNote property is now marked as required in SandboLiceno4bc0FEXEiqd. This aligns with the deactivation flow requiring a note. Add tests to validate that missing this field results in a 400 response.

backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (6)

1-16: Well-structured imports with appropriate organization.

The imports are organized logically with standard library imports first, followed by application-specific imports. The noqa directive for star imports is correctly applied to suppress linting warnings for importing all constants.


18-22: Clean class definition with good documentation.

The class documentation clearly explains the purpose of the TestDataGenerator utility class.


24-28: Helper method enhances API response testing.

This utility method correctly converts data class instances into API response format using ResponseEncoder, ensuring test data matches production format.


29-59: Well-documented API event generator with flexible overrides.

The method provides a clean approach to generating test API events with separate controls for authentication context (sub, scope) and general payload overrides. The documentation clearly explains the parameters and purpose.


262-266: Useful configuration accessor for license type abbreviations.

This helper method provides a clean interface to retrieve license type abbreviations from configuration, supporting the new adverse action functionality.


344-348: Appropriate handling of date overrides for testing.

The method correctly handles the special case of overriding dateOfUpdate fields on data class instances, which are typically immutable at runtime. The noqa comment properly justifies accessing a protected attribute.

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 (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

1079-1087: ⚠️ Potential issue

Incorrect docstring in encumber_license method.

The docstring incorrectly refers to updating a privilege record instead of a license record, and describes the wrong status change. License encumbrance updates the compactEligibility to ineligible, not the administratorSetStatus to inactive.

Apply this fix:

def encumber_license(self, adverse_action: AdverseActionData) -> None:
    """
    Adds an adverse action record for a privilege for a provider in a jurisdiction.

-   This will update the privilege record to have a administratorSetStatus of 'inactive'.
+   This will update the license record to have a compactEligibility of 'ineligible'.

    :param AdverseActionData adverse_action: The details of the adverse action to be added to the records
-   :raises CCNotFoundException: If the privilege record is not found
+   :raises CCNotFoundException: If the license record is not found
    """
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (1)

573-607: ⚠️ Potential issue

Missing encryption key grant for encumbrance handler.

The encumbrance handler needs access to decrypt the provider data table's encryption key, like other handler implementations such as _get_provider_handler and _query_providers_handler. Without this grant, the Lambda function may encounter access denied errors when reading encrypted attributes.

Apply this fix:

def _add_provider_encumbrance_handler(
    self,
    provider_data_table: ProviderTable,
    staff_users_table: Table,
    event_bus: EventBus,
    lambda_environment: dict,
) -> PythonFunction:
    """Create and configure the Lambda handler for encumbering a provider's privilege or license."""
    encumbrance_handler = PythonFunction(
        self.resource,
        'ProviderEncumbranceHandler',
        description='Provider encumbrance handler',
        lambda_dir='provider-data-v1',
        index=os.path.join('handlers', 'encumbrance.py'),
        handler='encumbrance_handler',
        environment=lambda_environment,
        alarm_topic=self.api.alarm_topic,
    )
    provider_data_table.grant_read_write_data(encumbrance_handler)
+   self.api.shared_encryption_key.grant_decrypt(encumbrance_handler)
    staff_users_table.grant_read_data(encumbrance_handler)
    event_bus.grant_put_events_to(encumbrance_handler)

Make sure to pass shared_encryption_key (or the correct key reference) when calling this helper method.

🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

986-994: Consider adding a return type annotation.

The method signature doesn't specify a return type, although the implementation doesn't return anything. Adding a return type annotation would improve code clarity.

-def encumber_privilege(self, adverse_action: AdverseActionData) -> None:
+def encumber_privilege(self, adverse_action: AdverseActionData) -> None:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0eb4c1c and 84fcae1.

📒 Files selected for processing (2)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/compact-connect/stacks/api_stack/v1_api/provider_management.py (7)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • provider_table (86-87)
backend/compact-connect/stacks/persistent_stack/provider_table.py (1)
  • ProviderTable (8-80)
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/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (28-36)
backend/compact-connect/stacks/api_stack/cc_api.py (1)
  • parameter_body_validator (262-263)
backend/compact-connect/stacks/api_stack/v1_api/api_model.py (3)
  • post_privilege_encumbrance_request_model (381-411)
  • message_response_model (21-39)
  • post_license_encumbrance_request_model (414-444)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (12)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (7)

924-946: Well-structured helper method for privilege status updates.

This helper method effectively abstracts the creation of DynamoDB transaction items for updating privilege statuses, promoting code reuse and maintainability.


947-969: Well-structured helper method for license eligibility updates.

This helper method effectively abstracts the creation of DynamoDB transaction items for updating license compact eligibility status, promoting code reuse and maintainability.


970-977: Clean implementation of adverse action item generator.

The method effectively encapsulates the logic for creating a DynamoDB transaction item to add an adverse action record, promoting reusability.


978-985: Good generic transaction item generator.

This helper abstracts the common pattern of serializing and putting items in DynamoDB transactions, reducing code duplication across the other transaction item generators.


1005-1013: Comprehensive privilege record retrieval with error handling.

The code properly handles the case when a privilege record is not found, providing a detailed error message that includes the jurisdiction.


1019-1027: Smart logic to avoid unnecessary updates.

The code checks if the privilege is already inactive before attempting to update its status, which prevents unnecessary write operations and improves efficiency.


1051-1075: Robust atomic transaction implementation.

The code uses DynamoDB transactions to ensure that all database operations (adding adverse action, creating history record, and updating the privilege status) are performed atomically, maintaining data consistency even in case of failures.

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

34-37: Renamed class to better reflect expanded responsibilities.

Renamed from QueryProviders to ProviderManagement to better represent the class's broadened scope, which now includes provider encumbrance functionality.


75-89: Well-structured resource hierarchy for privileges and licenses.

Creating these nested resources at the instance level improves organization and makes the code more maintainable, especially with the addition of new endpoints like encumbrance.


120-129: Clear initialization of encumbrance functionality.

The encumbrance handler is properly initialized and stored as an instance variable, and the methods to add encumbrance endpoints are called in a logical order.


608-633: Complete API endpoint configuration for privilege encumbrance.

The privilege encumbrance endpoint is properly configured with appropriate request validator, request models, response models, and authorization. The integration with the encumbrance handler is cleanly implemented.


634-656: Complete API endpoint configuration for license encumbrance.

The license encumbrance endpoint is properly configured with appropriate request validator, request models, response models, and authorization. The integration with the encumbrance handler is cleanly implemented.

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: 5

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

93-96: Hard-coded 'slp' abbreviation – see earlier discussion

The literal 'slp' is intentionally left to “fail loud” when core test constants change, per maintainer feedback.
No further action needed.

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

313-317: Variable mis-named after refactor

loaded_privilege_data actually holds a LicenseData instance. Renaming improves readability and avoids confusion when grepping tests.

-        loaded_privilege_data = LicenseData.from_database_record(item)
+        loaded_license_data = LicenseData.from_database_record(item)
...
-            loaded_privilege_data.to_dict(),
+            loaded_license_data.to_dict(),
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

970-977: Potentially unnecessary helper method.

This helper method is only used once in the current implementation. Consider whether it provides enough value to justify its existence as a separate method compared to using the more generic _generate_put_item_transaction.

- def _generate_put_adverse_action_item(self, adverse_action: AdverseActionData):
-     return {
-         'Put': {
-             'TableName': self.config.provider_table.name,
-             'Item': TypeSerializer().serialize(adverse_action.serialize_to_database_record())['M'],
-         },
-     }

986-1078: Comprehensive implementation of privilege encumbrance with return type mismatch.

The encumber_privilege method correctly implements:

  1. Finding the privilege record
  2. Checking if it's already inactive (avoiding unnecessary updates)
  3. Creating an update record for audit history
  4. Performing a transactional write to ensure data consistency

However, the method has no return type annotation but also doesn't return any value (implicitly returns None).

- def encumber_privilege(self, adverse_action: AdverseActionData) -> None:
+ def encumber_privilege(self, adverse_action: AdverseActionData) -> None:

And update the docstring to clarify the return expectation:

    """
    Adds an adverse action record for a privilege for a provider in a jurisdiction.

    This will update the privilege record to have a administratorSetStatus of 'inactive', and add a privilege update
    record to show the encumbrance event.

    :param AdverseActionData adverse_action: The details of the adverse action to be added to the records
    :raises CCNotFoundException: If the privilege record is not found
+   :return: None
    """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4de02f and de01d28.

📒 Files selected for processing (12)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.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/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (1 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 (1 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 (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/init.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/schema/adverse_action/init.py
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/init.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (3)

924-946: Well-structured helper method for privilege status updates.

This method nicely encapsulates the DynamoDB transaction item generation for updating a privilege's administrator set status, making the transaction creation more maintainable and readable.


947-969: Well-structured helper method for license eligibility updates.

This helper method cleanly abstracts the creation of a DynamoDB transaction item for updating a license's compact eligibility status.


978-985: Good abstraction for DynamoDB put operations.

This generic helper method simplifies the creation of put transactions and is reused effectively throughout the new code.

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

🔭 Outside diff range comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1)

125-199: 🛠️ Refactor suggestion

PrivilegeUpdateData is missing required construction flag.

According to a previous review, this class should not be instantiated empty, but it's missing the flag that enforces this constraint.

# Define the record schema at the class level
_record_schema = PrivilegeUpdateRecordSchema()

+# Prevent empty instantiation since this class represents a concrete update event
+_requires_data_at_construction = True
♻️ Duplicate comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1)

110-115: 🛠️ Refactor suggestion

Potential serialization issue with enum value in administrator_set_status setter.

The property returns an ActiveInactiveStatus enum, but the setter doesn't handle conversion when storing the value, which might cause serialization issues when the enum instance is stored directly.

@administrator_set_status.setter
def administrator_set_status(self, value: ActiveInactiveStatus) -> None:
-    self._data['administratorSetStatus'] = value
+    # Store the enum's value string, not the enum instance itself
+    self._data['administratorSetStatus'] = value.value if isinstance(value, ActiveInactiveStatus) else value
🧹 Nitpick comments (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

970-977: Unused helper method.

This method is defined but never used in the implementation. The code instead uses _generate_put_item_transaction with adverse_action.serialize_to_database_record() in both encumbrance methods.

Either remove this unused method or refactor the encumbrance methods to use it:

-                # Add the adverse action record for the privilege
-                self._generate_put_item_transaction(adverse_action.serialize_to_database_record()),
+                # Add the adverse action record for the privilege
+                self._generate_put_adverse_action_item(adverse_action),

1153-1154: Comment references privilege record instead of license record.

There's an inconsistency in the comment which references "privilege record's administratorSetStatus" when describing a license record update.

-                # Set the privilege record's administratorSetStatus to inactive and update the dateOfUpdate
+                # Set the license record's compactEligibility to ineligible and update the dateOfUpdate
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de01d28 and 3b29b65.

📒 Files selected for processing (2)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (6)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (5)

924-946: Well-structured utility method for privilege status updates.

This helper method nicely encapsulates the creation of transaction items for updating privilege status, improving code reusability across both new encumbrance methods.


947-969: Well-structured utility method for license eligibility updates.

The method properly abstracts the creation of transaction items for updating license compact eligibility status, maintaining consistency with the privilege counterpart.


978-985: Generic transaction item helper is well implemented.

This utility method abstracts the common pattern of creating a "Put" transaction item, reducing code duplication across the encumbrance methods.


986-1077: Comprehensive implementation of privilege encumbrance.

The method correctly implements the following workflow:

  1. Finds the privilege record
  2. Checks if it's already inactive (avoiding unnecessary updates)
  3. Creates an update record for audit history
  4. Performs a transactional write to ensure data consistency

The implementation properly follows the learned pattern for CCDataClass instantiation using from_database_record() rather than direct constructor instantiation.


1078-1167: License encumbrance implementation is complete and correct.

The method properly:

  1. Fetches the license record
  2. Verifies if it's already ineligible
  3. Creates a license update record for history/audit
  4. Executes all changes in a single transaction for data consistency

The implementation follows a consistent pattern with the privilege encumbrance method, which makes the code more maintainable.

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

11-123: Well-structured PrivilegeData class with properly typed properties.

The implementation provides clean accessor methods with appropriate type hints. The class correctly uses the CCDataClass pattern for serialization and deserialization.

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 (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (3)

966-973: Unused helper method.

The _generate_put_adverse_action_item method is defined but never used. The encumbrance methods use _generate_put_item_transaction with adverse_action.serialize_to_database_record() instead.

Consider either:

  1. Using this specific method in the encumbrance methods, or
  2. Removing this unused method to avoid confusion
- def _generate_put_adverse_action_item(self, adverse_action: AdverseActionData):
-     return {
-         'Put': {
-             'TableName': self.config.provider_table.name,
-             'Item': TypeSerializer().serialize(adverse_action.serialize_to_database_record())['M'],
-         },
-     }

1071-1157: Solid implementation of license encumbrance with minor documentation issue.

The implementation correctly handles license encumbrance similar to privilege encumbrance, following the same patterns for consistency.

However, there's an incorrect comment at line 1146-1147:

- # Set the privilege record's administratorSetStatus to inactive and update the dateOfUpdate
+ # Set the license record's compactEligibility to ineligible and update the dateOfUpdate

1098-1099: Use f-strings consistently for error messages.

The error message concatenation style is inconsistent with the rest of the codebase.

- raise CCNotFoundException(f'{message} {adverse_action.jurisdiction}') from e
+ raise CCNotFoundException(f'License not found for jurisdiction {adverse_action.jurisdiction}') from e

This matches the style used in the privilege method at line 1010.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b29b65 and a4a8d5d.

📒 Files selected for processing (2)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/init.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

924-981: Well-designed helper methods for DynamoDB transaction items.

These helper methods effectively abstract DynamoDB transaction construction, making the encumbrance methods cleaner and more maintainable. They follow a consistent pattern and handle serialization appropriately.


982-1070: Robust implementation of privilege encumbrance.

The method correctly implements:

  1. Privilege record retrieval
  2. Status check to avoid unnecessary updates
  3. Creation of an audit update record
  4. Transactional writes for atomicity

The error handling, logging, and context management follow best practices.

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 (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

1063-1149: Fix comments in the license encumbrance method.

The implementation is correct, but several comments still incorrectly refer to privileges instead of licenses.

Apply these changes to fix the comments:

-# Update the privilege record and create history record
+# Update the license record and create history record
 logger.info('Encumbering license')
-# we add the adverse action record for the license,
-# the license update record, and update the license record to ineligible if it is not already ineligible
+# we add the adverse action record for the license,
+# the license update record, and update the license record to ineligible if it is not already ineligible
 transact_items = [
     # Create a history record, reflecting this change
     self._generate_put_transaction_item(license_update_record),
-    # Add the adverse action record for the privilege
+    # Add the adverse action record for the license
     self._generate_put_transaction_item(adverse_action.serialize_to_database_record()),
 ]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4a8d5d and 9974207.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (4)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (4)

924-944: Well-designed utility method for generating privilege status update transactions.

This helper method cleanly abstracts the process of creating DynamoDB transaction items for updating privilege status, promoting code reuse and consistency.


945-965: Good abstraction for license compact eligibility status updates.

Similar to the privilege helper method, this provides a clean abstraction for license-specific status updates, improving code maintainability.


966-973: Clean utility method to reduce transaction boilerplate.

This simple helper method effectively reduces code duplication across the transaction generation process.


974-1062: Comprehensive implementation of privilege encumbrance.

The method correctly:

  1. Retrieves the privilege record
  2. Checks if it's already inactive (avoiding unnecessary updates)
  3. Creates an update record for audit history
  4. Performs a transactional write to ensure data consistency

The context manager for logging is a good practice that ensures consistent log context.

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 (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2)

18-18: Remove unused import LicenseUpdateRecordSchema.

The LicenseUpdateRecordSchema is imported but never used in the code. The implementation uses LicenseUpdateData.create_new() instead at line 1107.

-from cc_common.data_model.schema.license import LicenseData, LicenseUpdateRecordSchema, LicenseUpdateData
+from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData
🧰 Tools
🪛 Ruff (0.8.2)

18-18: cc_common.data_model.schema.license.LicenseUpdateRecordSchema imported but unused

Remove unused import: cc_common.data_model.schema.license.LicenseUpdateRecordSchema

(F401)


1063-1072: Add return type annotation to method signature for consistency.

The method docstring should explicitly specify the return type as None to be consistent with the encumber_privilege method.

-def encumber_license(self, adverse_action: AdverseActionData) -> None:
+def encumber_license(self, adverse_action: AdverseActionData) -> None:
    """
    Adds an adverse action record for a license for a provider in a jurisdiction.

    This will update the license record to have a compactEligibility of 'ineligible', and add a license update
    record to show the encumbrance event.

    :param AdverseActionData adverse_action: The details of the adverse action to be added to the records
    :raises CCNotFoundException: If the license record is not found
+   :return: None
    """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9974207 and 0e798b3.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py

18-18: cc_common.data_model.schema.license.LicenseUpdateRecordSchema imported but unused

Remove unused import: cc_common.data_model.schema.license.LicenseUpdateRecordSchema

(F401)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (5)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (5)

924-944: Well-designed helper method for privilege status updates.

This helper method effectively abstracts the DynamoDB update operation for privilege administrator status, following the DRY principle and improving code maintainability.


945-965: Well-designed helper method for license eligibility updates.

This helper method effectively abstracts the DynamoDB update operation for license compact eligibility status, consistent with the privilege helper method structure.


966-972: Clean implementation of a reusable transaction item generator.

This small helper method reduces code duplication in the encumbrance methods and improves readability by abstracting the DynamoDB Put operation creation.


974-1062: Comprehensive implementation of privilege encumbrance with atomic operations.

The encumber_privilege method successfully implements the encumbrance workflow:

  1. Retrieves the privilege record
  2. Intelligently checks if it's already inactive to avoid unnecessary updates
  3. Creates an update record to maintain audit history
  4. Performs a transactional write to ensure data consistency

The implementation properly uses the context manager for logging and follows the established patterns for the codebase.


1073-1149: Comprehensive implementation of license encumbrance with atomic operations.

The encumber_license method successfully implements the encumbrance workflow:

  1. Retrieves the license record
  2. Intelligently checks if it's already ineligible to avoid unnecessary updates
  3. Creates an update record to maintain audit history
  4. Performs a transactional write to ensure data consistency

The implementation properly uses the context manager for logging and follows the established patterns for the codebase.

@landonshumway-ia
Copy link
Collaborator Author

@jusdino I've wrapped up all the code rabbit related fixes, so this should now be fully ready for a human review, thanks.

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 great! Only minor nits other than one update: we ended up deciding to not include any toggles in an encumbrance, since all encumbrances prevent purchasing of new privileges, now.

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: 2

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

292-320: Fix method name to accurately reflect test purpose

The method name test_license_encumbrance_handler_sets_privilege_record_to_inactive_in_provider_data_table is inaccurate. This test is verifying the license record status change, not the privilege record.

Rename the method to better reflect its purpose:

-def test_license_encumbrance_handler_sets_privilege_record_to_inactive_in_provider_data_table(self):
+def test_license_encumbrance_handler_sets_license_record_to_ineligible_in_provider_data_table(self):

Also, line 316 should use a more accurately named variable:

-loaded_privilege_data = LicenseData.from_database_record(item)
+loaded_license_data = LicenseData.from_database_record(item)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8caf19a and 2f9cec5.

📒 Files selected for processing (13)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py (5 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 (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (5 hunks)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.json (1 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/provider-data-v1/handlers/encumbrance.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1 hunks)
  • backend/compact-connect/stacks/api_stack/v1_api/api_model.py (3 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)
✅ Files skipped from review due to trivial changes (1)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • backend/compact-connect/lambdas/python/common/tests/resources/api/adverse-action-post.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/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/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.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/schema/base_record.py
🧰 Additional context used
🪛 Biome (1.9.4)
backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json

[error] 18-18: Expected an array, an object, or a literal but instead found ']'.

Expected an array, an object, or a literal here.

(parse)

backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json

[error] 18-18: Expected an array, an object, or a literal but instead found ']'.

Expected an array, an object, or a literal here.

(parse)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (6)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (6)

38-39: Property patching approach looks good

The direct patching of cc_common.config._Config.current_standard_datetime with a datetime value is correct since it's a property, not a method. This ensures consistent timestamps during tests.


94-95: Hardcoded license type abbreviation is intentional

The hardcoded 'slp' abbreviation is intentional according to previous comments. This design ensures that if test constants change, tests will deliberately fail, requiring conscious updates by developers.


129-130: Hardcoded license type abbreviation is intentional

The hardcoded 'slp' abbreviation is intentional according to previous comments. This design ensures that if test constants change, tests will deliberately fail, requiring conscious updates by developers.


237-238: Hardcoded license type abbreviation is intentional

The hardcoded 'slp' abbreviation is intentional according to previous comments. This design ensures that if test constants change, tests will deliberately fail, requiring conscious updates by developers.


273-274: Hardcoded license type abbreviation is intentional

The hardcoded 'slp' abbreviation is intentional according to previous comments. This design ensures that if test constants change, tests will deliberately fail, requiring conscious updates by developers.


1-321: Comprehensive test coverage for encumbrance functionality

The test suite thoroughly covers the critical functionality of the new encumbrance endpoints, testing both API responses and database state changes. It validates the creation of adverse action records, the updates to privileges/licenses, and the status changes that occur during encumbrance.

The test structure follows best practices with clear setup, execution, and verification steps.

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: 3

🧹 Nitpick comments (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1)

25-51: Consider using direct dictionary access for required properties

Since the class requires data at construction, required properties like providerId and compact should use direct dictionary access rather than .get(), which is typically used for optional values. Using direct access would fail fast with a clear KeyError if required data is missing.

@property
def providerId(self) -> UUID:
-    return self._data.get('providerId')
+    return self._data['providerId']

@property
def compact(self) -> str:
-    return self._data.get('compact')
+    return self._data['compact']

While .get() is appropriate for optional properties (like the collections with default values), using direct access for required fields provides stronger guarantees and clearer errors.

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

293-293: Method name is inconsistent with what it's actually testing.

The method name test_license_encumbrance_handler_sets_privilege_record_to_inactive_in_provider_data_table suggests it's testing a privilege record, but it's actually testing a license record.

Consider renaming the method to correctly reflect what it's testing:

-def test_license_encumbrance_handler_sets_privilege_record_to_inactive_in_provider_data_table(self):
+def test_license_encumbrance_handler_sets_license_record_to_ineligible_in_provider_data_table(self):

316-316: Variable name is inconsistent with the type of data it contains.

The variable name loaded_privilege_data is misleading as it's actually holding license data, not privilege data.

-        loaded_privilege_data = LicenseData.from_database_record(item)
+        loaded_license_data = LicenseData.from_database_record(item)

Also update the assertion to use the renamed variable:

         self.assertEqual(
             expected_license_data.to_dict(),
-            loaded_privilege_data.to_dict(),
+            loaded_license_data.to_dict(),
         )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9cec5 and e4c9346.

📒 Files selected for processing (16)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.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/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (1 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 (1 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 (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py (7 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)
✅ Files skipped from review due to trivial changes (1)
  • backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/home_jurisdiction/init.py
  • backend/compact-connect/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/init.py
  • backend/compact-connect/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
🧰 Additional context used
🧠 Learnings (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#769
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py:87-90
Timestamp: 2025-04-29T21:09:04.831Z
Learning: In the CCDataClass implementation, validation is performed during both the dump() and load() processes in Marshmallow, so calling dump() first still results in ValidationError if the provided data is invalid.
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py (1)
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#769
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py:40-43
Timestamp: 2025-04-29T02:18:11.089Z
Learning: In the CompactConnect codebase, API design follows a pattern where fields are either provided with valid values or omitted entirely. Null values are not allowed, even for optional fields. This applies to Marshmallow schemas where fields are configured with `required=False, allow_none=False`.
🪛 Ruff (0.8.2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py

5-5: marshmallow.fields.Boolean imported but unused

Remove unused import: marshmallow.fields.Boolean

(F401)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (9)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/military_affiliation/__init__.py (1)

22-23: Good implementation of empty construction prevention

Setting _requires_data_at_construction = True ensures that the class will fail fast with a clear error message when initialization is attempted without providing valid data. This is appropriate for a read-only data class that doesn't provide setters.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)

15-140: Excellent base class implementation for data models

The CCDataClass implementation provides a robust foundation for data models with clear factory methods, validation, and serialization utilities. The documentation is comprehensive, and the code handles edge cases like empty data and validation errors well.

Particularly good design points:

  • Factory method pattern with create_new and from_database_record
  • Constructor-level validation to prevent misuse
  • Configurable validation of empty initialization via _requires_data_at_construction
  • Proper deepcopy usage to prevent mutation of the internal data
  • Clear separation between database serialization and dict representation

210-226: Well-defined enums for adverse action functionality

The addition of AdverseActionAgainstEnum and extension of UpdateCategory with new values like ENCUMBRANCE provides a clean type-safe way to model the different entities that can have adverse actions and the types of updates. This design enables static type checking and IDE auto-completion, improving developer experience.


243-254: Comprehensive enum for clinical privilege action categories

The ClinicalPrivilegeActionCategory enum provides a well-documented set of values based on NPDB standards. Including the source reference in the docstring is excellent practice. The enum values are descriptive and match real-world categories.

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

94-94: Hardcoded license type abbreviation 'slp' in query.

Following project conventions discussed in previous reviews, the hardcoded 'slp' value is deliberately used instead of a dynamic lookup to ensure tests fail if the default test data changes.


38-38: Correctly patching the datetime property.

This patch correctly supplies a direct datetime value rather than a lambda because current_standard_datetime is implemented as a property in the CompactConnect codebase.


1-26: Well-structured test setup with clear constants.

The import section and constants are well-organized, making the test setup clear and maintainable. Good job defining the endpoint resources as constants for reuse.


42-64: Well-designed test helper for test data preparation.

The _when_testing_valid_privilege_encumbrance helper method effectively encapsulates test setup logic, making the test cases more readable and maintainable.


28-35: Good factoring of test body generation.

Extracting the test body generation into a separate function improves reusability and readability by avoiding duplication between privilege and license test cases.

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 (1)
backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py (1)

37-62: Critical test for compact eligibility with encumbered licenses.

This test properly verifies the business rule that providers with any encumbered license should be ineligible for compact privileges. The test clearly demonstrates how the PROVIDER_ENCUMBERED status impacts compact eligibility calculations.

However, I recommend adding additional test cases to cover other encumbrance statuses (ENCUMBERED, UNENCUMBERED) to ensure complete coverage of the eligibility determination logic.

Consider adding these additional test cases:

def test_compact_eligibility_with_different_encumbrance_statuses(self):
    from cc_common.data_model.schema.base_record import CalculatedStatusRecordSchema
    from cc_common.data_model.schema.common import (
        ActiveInactiveStatus,
        CompactEligibilityStatus,
        LicenseEncumberedStatusEnum,
    )
    
    schema = CalculatedStatusRecordSchema()
    base_provider = {
        'jurisdictionUploadedLicenseStatus': ActiveInactiveStatus.ACTIVE,
        'jurisdictionUploadedCompactEligibility': CompactEligibilityStatus.ELIGIBLE,
        'dateOfExpiration': '2050-01-01',
        'pk': 'COMPACT#AZ#PROVIDER#1234567890',
        'sk': 'COMPACT#AZ#PROVIDER#1234567890',
        'type': 'provider',
        'dateOfUpdate': '2025-01-01T00:00:00Z',
    }
    
    # Test with directly encumbered license
    provider_encumbered = {**base_provider, 'encumberedStatus': LicenseEncumberedStatusEnum.ENCUMBERED}
    result = schema.load(provider_encumbered)
    self.assertEqual(CompactEligibilityStatus.INELIGIBLE.value, result['compactEligibility'])
    
    # Test with unencumbered license
    provider_unencumbered = {**base_provider, 'encumberedStatus': LicenseEncumberedStatusEnum.UNENCUMBERED}
    result = schema.load(provider_unencumbered)
    self.assertEqual(CompactEligibilityStatus.ELIGIBLE.value, result['compactEligibility'])
    
    # Test with no encumbrance status (should default to UNENCUMBERED)
    provider_no_encumbrance = {**base_provider}
    result = schema.load(provider_no_encumbrance)
    self.assertEqual(CompactEligibilityStatus.ELIGIBLE.value, result['compactEligibility'])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5575b92 and 5508a28.

📒 Files selected for processing (8)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (7 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (4 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.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/base_record.py
🧰 Additional context used
🧠 Learnings (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#769
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py:87-90
Timestamp: 2025-04-29T21:09:04.831Z
Learning: In the CCDataClass implementation, validation is performed during both the dump() and load() processes in Marshmallow, so calling dump() first still results in ValidationError if the provided data is invalid.
🧬 Code Graph Analysis (2)
backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (1)
  • PrivilegeRecordSchema (53-143)
backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py (3)
backend/compact-connect/lambdas/python/common/tests/__init__.py (1)
  • TstLambdas (9-51)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (1)
  • CalculatedStatusRecordSchema (91-151)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • ActiveInactiveStatus (228-230)
  • CompactEligibilityStatus (233-235)
  • LicenseEncumberedStatusEnum (238-244)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (9)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (6)

15-141: Excellent implementation of the base data class abstraction.

The CCDataClass provides a clean abstraction layer between data models and database schemas, with well-defined factory methods that enforce proper instantiation patterns. The separation of concerns between data validation, serialization, and database interaction is well-implemented.

The prevention of direct instantiation through an internal flag and comprehensive docstrings make this a maintainable foundation for your data layer.


82-85: Improved guard against incorrect instantiation.

The check for database keys in the data is now correctly implemented using membership check (in) rather than truthiness evaluation. This properly prevents falsy values like empty strings or None from bypassing the guard.


210-217: Well-defined enum for adverse action targets.

The AdverseActionAgainstEnum cleanly defines the scope of records that can be affected by adverse actions, making it easy to extend in the future if additional record types need to be supported.


225-226: Clear distinction of "encumbrance" as a separate update category.

Adding ENCUMBRANCE to the UpdateCategory enum creates a clear separation between encumbrance updates and other types of updates, which will help with tracking and event handling of adverse actions.


238-252: Comprehensive encumbrance status modeling for both licenses and privileges.

The LicenseEncumberedStatusEnum and PrivilegeEncumberedStatusEnum provide a complete model for representing encumbrance states, with well-documented special statuses:

  1. PROVIDER_ENCUMBERED - captures the cascading effect where a provider's other licenses are marked when any license is encumbered
  2. LICENSE_ENCUMBERED - handles the case where a privilege is indirectly affected by an encumbered license

This modeling enables accurate propagation of encumbrance effects throughout the provider's profile.


259-270: Properly sourced clinical privilege action categories.

The ClinicalPrivilegeActionCategory enum correctly implements standardized NPDB categories with the source citation, ensuring alignment with industry standards for adverse action reporting.

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

100-111: Good coverage for encumbered privilege status logic.

This test correctly verifies that privileges with an encumbered status are marked inactive regardless of expiration date, which is a critical aspect of the encumbrance feature.


112-123: Complete test coverage with explicit unencumbered case.

This test complements the previous one by explicitly verifying that privileges with an unencumbered status remain active (when not expired), ensuring the positive case is also covered.


124-135: Proper default behavior testing for backward compatibility.

This test ensures that existing records without an encumberedStatus field default to active status, maintaining backward compatibility with the existing data model. This is crucial for a smooth rollout of the encumbrance feature.

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 (1)
backend/compact-connect/lambdas/python/common/cc_common/utils.py (1)

229-229: Remove whitespace from blank line.

There's unnecessary whitespace on this blank line, which is flagged by static analysis tools.

-    
+
🧰 Tools
🪛 Ruff (0.8.2)

229-229: Blank line contains whitespace

Remove whitespace from blank line

(W293)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5508a28 and c129f6b.

📒 Files selected for processing (3)
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py (1 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 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
🧰 Additional context used
🪛 Ruff (0.8.2)
backend/compact-connect/lambdas/python/common/cc_common/utils.py

229-229: Blank line contains whitespace

Remove whitespace from blank line

(W293)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (1)
backend/compact-connect/lambdas/python/common/cc_common/utils.py (1)

231-268: Well-implemented decorator for state-level authorization.

The new authorize_state_level_only_action decorator is correctly implemented and follows the same pattern as existing authorization decorators. It properly extracts compact and jurisdiction from path parameters, verifies the caller's scopes, and raises appropriate exceptions for missing parameters or unauthorized access.

This addition supports the new provider encumbrance API endpoints that require state-level authorization to perform adverse action encumbrance on privileges and licenses.

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: 2

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

1091-1213: 🛠️ Refactor suggestion

Inconsistent update type in the license encumbrance method.

While the encumber_privilege method uses the UpdateCategory.ENCUMBRANCE enum, the encumber_license method uses a string literal 'encumbrance'. This inconsistency could lead to issues if the enum values change.

Additionally, the return value is missing from the docstring.

def encumber_license(self, adverse_action: AdverseActionData) -> None:
    """
    Adds an adverse action record for a license for a provider in a jurisdiction.

    This will update the license record to have a compactEligibility of 'ineligible', and add a license update
    record to show the encumbrance event.

    :param AdverseActionData adverse_action: The details of the adverse action to be added to the records
    :raises CCNotFoundException: If the license record is not found
+   :return: None
    """
    # ...code continues...

    license_update_record = LicenseUpdateData.create_new(
        {
            'type': 'licenseUpdate',
-           'updateType': 'encumbrance',
+           'updateType': UpdateCategory.ENCUMBRANCE,
            'providerId': adverse_action.providerId,
            # ...rest of the code...
🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (1)

1002-1090: Well-implemented privilege encumbrance functionality.

The implementation correctly:

  1. Retrieves the privilege record
  2. Checks if it's already encumbered
  3. Creates an update record
  4. Performs a transactional write

The return value is missing from the docstring. Consider adding :return: None to complete the documentation.

def encumber_privilege(self, adverse_action: AdverseActionData) -> None:
    """
    Adds an adverse action record for a privilege for a provider in a jurisdiction.

    This will update the privilege record to have a administratorSetStatus of 'inactive', and add a privilege update
    record to show the encumbrance event.

    :param AdverseActionData adverse_action: The details of the adverse action to be added to the records
    :raises CCNotFoundException: If the privilege record is not found
+   :return: None
    """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c129f6b and 05fc706.

📒 Files selected for processing (6)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py (1 hunks)
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/init.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (8)
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/__init__.py (1)
  • TstFunction (22-394)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3)
  • ClinicalPrivilegeActionCategory (259-269)
  • PrivilegeEncumberedStatusEnum (247-251)
  • LicenseEncumberedStatusEnum (238-244)
backend/compact-connect/lambdas/python/common/common_test/test_data_generator.py (10)
  • put_default_privilege_record_in_provider_table (252-258)
  • generate_test_api_event (30-58)
  • generate_default_adverse_action (79-98)
  • generate_default_privilege_update (267-292)
  • generate_default_privilege (221-243)
  • put_default_provider_record_in_provider_table (329-341)
  • put_default_license_record_in_provider_table (186-192)
  • generate_default_license_update (195-218)
  • generate_default_license (149-183)
  • generate_default_provider (295-326)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (11)
  • jurisdiction (37-38)
  • jurisdiction (165-166)
  • compact (33-34)
  • compact (161-162)
  • providerId (29-30)
  • providerId (157-158)
  • licenseType (41-42)
  • licenseType (169-170)
  • encumberedStatus (133-134)
  • LicenseUpdateData (137-182)
  • LicenseData (10-134)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (20)
  • jurisdiction (40-41)
  • jurisdiction (44-45)
  • jurisdiction (170-171)
  • jurisdiction (174-175)
  • compact (32-33)
  • compact (36-37)
  • compact (162-163)
  • compact (166-167)
  • providerId (24-25)
  • providerId (28-29)
  • providerId (154-155)
  • providerId (158-159)
  • licenseType (56-57)
  • licenseType (60-61)
  • licenseType (178-179)
  • licenseType (182-183)
  • PrivilegeUpdateData (137-210)
  • PrivilegeData (13-134)
  • encumberedStatus (122-123)
  • encumberedStatus (126-127)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py (9)
  • jurisdiction (38-39)
  • jurisdiction (42-43)
  • compact (22-23)
  • compact (26-27)
  • providerId (30-31)
  • providerId (34-35)
  • licenseType (54-55)
  • licenseType (58-59)
  • AdverseActionData (9-121)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (4)
  • compact (28-29)
  • providerId (24-25)
  • encumberedStatus (130-131)
  • ProviderData (12-131)
backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py (1)
  • encumbrance_handler (28-36)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (15)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (2)

12-22: Well-designed class constructor requirements.

The ProviderData class properly enforces the presence of valid data at construction time with _requires_data_at_construction = True. This approach prevents potential KeyError exceptions when accessing required properties later.


129-132: LGTM: Well-implemented encumbered status property for providers.

The encumberedStatus property implementation is clean and correctly handles the optional nature of the field by using .get() to return None when not present. This addition is important for supporting the new adverse actions functionality.

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

1-27: LGTM: Proper imports and constants definition.

The imports are well organized and the constants for endpoint resources are clearly defined. The test data constants are properly imported from the test_constants module.


28-35: Good use of shared test data generation.

The _generate_test_body helper function provides consistent test body data across tests. This promotes maintainability as any changes to the required request body structure only need to be updated in one place.


37-41: Proper mocking setup for deterministic tests.

The test class correctly uses @mock_aws from moto and patches the datetime property. As discussed in previous feedback, the datetime property is correctly patched with a direct value rather than a lambda since current_standard_datetime is implemented as a property, not a method.


42-64: LGTM: Well-implemented test setup helper.

The _when_testing_valid_privilege_encumbrance helper method creates a realistic test environment with a privilege record and generates a properly structured API event with appropriate scopes, path parameters and request body. This promotes test readability and reduces duplication.


65-78: LGTM: Clean basic functionality test.

This test properly verifies the happy path for privilege encumbrance, checking that a valid request returns a 200 status code and the expected success message.


94-95: Hardcoded license type abbreviation in query is intentional.

The hardcoded 'slp' in the key condition is deliberate. As noted in previous feedback, this approach ensures the test will fail if the default license type changes, requiring developers to consciously update related tests.


79-113: Comprehensive verification of adverse action record creation.

The test thoroughly verifies that an adverse action record is properly created in the provider data table with the expected fields. It uses the TestDataGenerator to create a reference adverse action for comparison, which helps ensure consistent expectations across tests.


114-147: Good verification of privilege update record creation.

The test appropriately verifies that a privilege update record is created with the expected update type and values. This ensures the system maintains a proper audit trail of encumbrance actions.


148-180: Thorough verification of privilege record update.

The test correctly verifies that the privilege record itself is updated with the encumbered status, ensuring the handler not only creates records but also updates the original privilege's state.


181-196: Important authorization test.

The test correctly verifies that compact admins cannot perform encumbrance actions, which should be restricted to state admins. This is an important security check to ensure proper access control.


198-227: Well-structured license encumbrance test setup.

Similar to the privilege encumbrance tests, this test class uses a helper method to set up the test environment, including creating both a provider record and a license record. This setup correctly mimics the production environment where licenses are associated with providers.


343-372: Important provider-level encumbrance test.

This test verifies a critical aspect of the encumbrance system - that when a license is encumbered, the provider record is also updated with an encumbered status. This cascading update is important for the overall encumbrance system to work correctly.


373-389: Clear authorization boundary testing.

Like the privilege encumbrance tests, this test verifies that compact admins cannot encumber licenses. The descriptive comment "Verifying that only state admins are allowed to encumber licenses" adds helpful context to the test.

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/schema/privilege/__init__.py (1)

136-144: ⚠️ Potential issue

Missing _requires_data_at_construction flag for PrivilegeUpdateData

The PrivilegeUpdateData class docstring says it "requires valid data when created - it cannot be instantiated empty and populated later" (lines 139-140), but the class doesn't set the _requires_data_at_construction = True flag to enforce this constraint. This is inconsistent with the documented behavior.

# Define the record schema at the class level
_record_schema = PrivilegeUpdateRecordSchema()

+# Disallow empty construction – this class represents a concrete update event
+_requires_data_at_construction = True

Without this flag, create_new() will happily return an empty instance, contrary to the note in the docstring.

🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1)

111-113: Correction: Return type annotation should be enum, not string

The administratorSetStatus property getter returns the raw value from the data dictionary, but the type annotation indicates it returns a string. However, based on the setter implementation (which correctly handles both enum instances and string values), the getter should return an enum instance to be consistent with the typing contract.

@property
-def administratorSetStatus(self) -> str:
+def administratorSetStatus(self) -> ActiveInactiveStatus:
    return self._data['administratorSetStatus']

This would make the getter's return type consistent with the setter's parameter type, and matches the pattern seen in other enums in the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7c003b and 5936647.

📒 Files selected for processing (15)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (7 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (3 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/record.py (4 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/record.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py (1 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 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/record.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_privilege.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.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/provider/record.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/provider/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/init.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py
🧰 Additional context used
🧠 Learnings (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (1)
Learnt from: landonshumway-ia
PR: csg-org/CompactConnect#769
File: backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py:87-90
Timestamp: 2025-04-29T21:09:04.831Z
Learning: In the CCDataClass implementation, validation is performed during both the dump() and load() processes in Marshmallow, so calling dump() first still results in ValidationError if the provided data is invalid.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (13)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/privilege/__init__.py (2)

116-119: Well-implemented setter ensures enum value serialization

This implementation correctly handles both enum instances and string values, ensuring the internal dictionary contains serializable string values instead of enum objects. This is important for preventing serialization issues when the data is stored in the database.


121-128: Good implementation of the new encumberedStatus property

The new encumberedStatus property is well-implemented with appropriate nullable return type and straightforward getter/setter methods. This addition properly supports the adverse action feature being implemented.

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

28-34: LGTM: Well-structured test helper function

The _generate_test_body helper function properly creates test request bodies with the required fields for encumbrance operations. This promotes code reuse and maintains consistency across tests.


38-39: Correct approach for patching datetime property

The patching of cc_common.config._Config.current_standard_datetime with a fixed datetime value is correctly implemented. Since current_standard_datetime is a property (not a method), directly supplying a datetime value is the right approach for deterministic testing.


42-63: LGTM: Clean test setup with reusable helper method

This helper method properly creates and returns both the test event and test privilege record, making it reusable across multiple test cases. The approach of setting up the test data once and reusing it improves test maintainability.


94-95: Intentional use of hardcoded license type abbreviation

The SK prefix embeds '.../slp#ADVERSE_ACTION' using a hardcoded license type abbreviation. This is a deliberate design choice that makes tests fail loud if the default test license type changes, requiring developers to consciously update related tests.


181-195: Well-structured compact admin authorization test

This test correctly verifies that only state admins (not compact admins) can encumber privileges, which is an important security constraint. The test modifies only the scope claim while reusing the standard test setup, clearly isolating the variable being tested.


343-371: LGTM: Thorough verification of provider record update

This test comprehensively verifies that the provider record is updated with the encumbered status when a license is encumbered. The test correctly asserts both the specific encumbrance status value and the complete provider record content.

backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py (5)

15-141: Excellent implementation of base CCDataClass

The CCDataClass implementation provides a robust foundation for data model classes with several key benefits:

  1. Factory methods enforce proper instantiation patterns
  2. Validation is performed during serialization/deserialization
  3. Deepcopy ensures data safety during serialization
  4. Common properties are standardized
  5. Error handling is clear and helpful

This abstraction layer between data model and database schema makes it easier to work with typed data objects throughout the codebase while ensuring consistency in validation.


210-217: Good addition of AdverseActionAgainstEnum

This new enum clearly identifies the types of records that adverse actions can target (privileges or licenses). The enum values match the resource paths used in the API endpoints, maintaining consistency across the codebase.


238-245: Well-designed LicenseEncumberedStatusEnum with provider context

The LicenseEncumberedStatusEnum is well-designed with the additional PROVIDER_ENCUMBERED status that indicates a license belonging to a provider who has at least one encumbered license. This status propagation mechanism allows the system to track encumbrance relationships across related records.


247-252: Good parallel structure with PrivilegeEncumberedStatusEnum

The PrivilegeEncumberedStatusEnum maintains a parallel structure to LicenseEncumberedStatusEnum with its own contextual status (LICENSE_ENCUMBERED) that indicates when a privilege is associated with an encumbered license. This consistent approach makes the code more predictable and easier to understand.


259-269: Well-documented ClinicalPrivilegeActionCategory enum

The ClinicalPrivilegeActionCategory enum is properly documented with reference to the NPDB standards. The category names are descriptive and match the official terminology, facilitating data exchange with external systems.

We determined that we do not need to
update the statuses of other licenses
which were not encumbered, since currently
whenever we check the status of the license we have
access to the provider data as well, which we are now
tracking the encumbrance status of.
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 work!

@landonshumway-ia landonshumway-ia requested a review from jlkravitz May 1, 2025 20:13
@landonshumway-ia
Copy link
Collaborator Author

@jlkravitz This is ready for your review. Thanks.

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 great! just a few questions/comments.

love the refactor, really helps make the tests feel cohesive and more readable. great work.

We have just verified that in the case of privilege
encumbrance, the provider cannot purchase any more
privileges, so we need to set the encumbrance status on
the provider record in this case as well.
Fixed comments and introduced license util class to simplify
logic for getting license types.
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 (3)
backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (3)

1039-1134: Comprehensive implementation of privilege encumbrance with atomic transactions.

The encumber_privilege method properly implements a transactional approach to ensure data consistency when encumbering a privilege. The method:

  1. Retrieves the privilege record
  2. Checks if it's already encumbered to avoid unnecessary updates
  3. Creates an update record for audit history
  4. Performs a transactional write that includes all related records

One minor issue: the method's docstring states it returns None, but there's no explicit return statement. While Python functions implicitly return None when reaching the end without a return statement, an explicit return would improve clarity.

def encumber_privilege(self, adverse_action: AdverseActionData) -> None:
    # ... implementation ...
+    return None

1135-1228: Comprehensive implementation of license encumbrance with proper error handling.

The encumber_license method correctly implements the encumbrance workflow for licenses, similar to the privilege implementation. It includes proper error handling, logging, and atomic transactions to ensure data consistency.

As with the privilege method, an explicit return statement would improve clarity:

def encumber_license(self, adverse_action: AdverseActionData) -> None:
    # ... implementation ...
+    return None

1181-1183: String literal used instead of enum for update type.

In the privilege encumbrance method you're using UpdateCategory.ENCUMBRANCE enum, but here you're using a string literal 'encumbrance'. For consistency, use the enum in both places.

-                    'updateType': 'encumbrance',
+                    'updateType': UpdateCategory.ENCUMBRANCE,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e964fca and d2ccb2c.

📒 Files selected for processing (7)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/license_util.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/common_test/test_constants.py (1 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_data_model/test_schema/test_base_record.py (1 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 (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/compact-connect/lambdas/python/common/common_test/test_constants.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_base_record.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/compact-connect/lambdas/python/common/cc_common/license_util.py (2)
webroot/src/plugins/EnvConfig/envConfig.plugin.ts (1)
  • config (41-60)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • license_type_abbreviations_for_compact (149-150)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: TestPython
🔇 Additional comments (18)
backend/compact-connect/lambdas/python/common/cc_common/license_util.py (1)

1-35: Clean and well-designed utility for license type lookups.

This new utility provides a solid abstraction for looking up license types by abbreviation within a compact. The code:

  • Uses a dataclass for clean representation of license types
  • Performs case-insensitive matching with proper error handling
  • Gracefully handles missing compact configurations via try/except
  • Returns None instead of raising exceptions when no match is found
backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py (12)

28-35: LGTM - Well-structured test body generation function.

The test body generator provides the required fields for encumbrance operations with clear values.


38-39: Correct pattern for patching datetime property.

Using the correct approach to patch the datetime property with a fixed value for deterministic testing.


42-65: LGTM - Comprehensive test setup with proper data generation.

This helper method creates the necessary test data and API event for privilege encumbrance tests. It:

  • Sets up a provider and privilege record in the database
  • Creates an API Gateway event with proper pathParameters
  • Uses the TestDataGenerator to consistently format license type abbreviations
  • Sets the appropriate admin scope for authorization

66-79: LGTM - Verifies basic successful response.

Good test coverage for the happy path, verifying that the handler returns a 200 status code and expected response body.


80-114: LGTM - Thorough verification of adverse action record creation.

This test comprehensively verifies that:

  • The adverse action record is created in the database
  • The record contains the expected values
  • The query to find the record uses the correct key pattern

Note: The hardcoded "slp" in the key pattern is intentional per team convention to make tests fail if default test data changes.


115-148: LGTM - Thorough verification of privilege update record creation.

This test properly verifies the privilege update record is created with the expected values including the updateType of "encumbrance" and encumberedStatus change to "encumbered".


149-181: LGTM - Thorough verification of privilege record status update.

This test effectively verifies that:

  • The privilege record's encumberedStatus is updated to "encumbered"
  • Other fields remain unchanged
  • The dateOfUpdate is updated to the mocked datetime

182-211: LGTM - Verifies provider record encumbrance propagation.

Good test to ensure that when a privilege is encumbered, the provider's overall encumberedStatus is also updated.


212-227: Well-designed authorization enforcement test.

Proper test for authorization boundary - verifying that compact admins cannot encumber privileges (only state admins can).


229-231: Consistent test structure for license encumbrance.

Following the same pattern as the privilege encumbrance tests promotes code consistency.


234-258: LGTM - Comprehensive test setup for license encumbrance.

This helper follows the same pattern as the privilege helper, with appropriate adjustments for license-specific logic.


404-420: Clear documentation for license admin-only permission.

The test comment clearly explains that only state admins (not compact admins) are allowed to encumber licenses.

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

931-949: Well-designed generic helper for DynamoDB update transactions.

This private helper method efficiently creates transaction items for updating encumbered status fields. It follows the DRY principle by providing a common implementation for different record types.


950-979: Good use of type-specific wrappers around the generic helper.

These specialized methods add type safety while reusing the common encumbrance update logic for different entity types (privilege, license, provider). The consistent pattern makes the code more maintainable.


981-987: Simple utility for DynamoDB put operations.

This helper method abstracts away the details of creating a DynamoDB put transaction item, which reduces code duplication across the encumbrance methods.


989-1038: Robust conditional provider encumbrance with appropriate error handling.

This helper method correctly checks if a provider is already encumbered before adding an update transaction item, which prevents unnecessary database operations. The error handling and logging are thorough.


1083-1087: Use of enum for update category.

Good use of the UpdateCategory.ENCUMBRANCE enum value for the privilege update record type. This follows the reviewer's recommendation to use the enum instead of string literals.

@landonshumway-ia landonshumway-ia requested a review from jlkravitz May 2, 2025 20:23
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.

OK- this is good to go! @isabeleliassen

@isabeleliassen isabeleliassen self-requested a review May 2, 2025 22:32
@isabeleliassen isabeleliassen merged commit 7a293d6 into csg-org:development May 2, 2025
3 checks passed
This was referenced May 23, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jun 19, 2025
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 - State admins can upload AA against provider's licenses/privileges
4 participants