Skip to content

Add support for Cosmetology public search#1317

Merged
isabeleliassen merged 36 commits intocsg-org:mainfrom
InspiringApps:feat/update-cosm-public-search
Apr 10, 2026
Merged

Add support for Cosmetology public search#1317
isabeleliassen merged 36 commits intocsg-org:mainfrom
InspiringApps:feat/update-cosm-public-search

Conversation

@landonshumway-ia
Copy link
Copy Markdown
Collaborator

@landonshumway-ia landonshumway-ia commented Mar 27, 2026

Cosmetology has slightly different requirements for public searching given the expected volume of practitioners with similar names. We need to support additional search parameters, such as license number. In order to accommodate these various patterns, we have determined to leverage the OpenSearch Domain to support public search, while keeping the request schema similar in shape to the public search request body used by JCC.

Closes #1295

Summary by CodeRabbit

  • New Features

    • Public license search endpoint added (query by licenseNumber, jurisdiction, givenName, familyName) with opaque cursor pagination and sorting.
  • Updates

    • Public provider responses now include a licenses array; public privilege items simplified (history/adverseActions removed).
    • Request validation and API schemas tightened (licenseNumber filter, pageSize limits) and public response shapes revised.
    • Public query routed to a dedicated search service and given monitoring; index-reset/config options added.
  • Tests

    • Added comprehensive public-search tests; removed legacy public-query tests.

Also added custom cursor logic for tracking
accurate page size of licenses.
We are updating our OpenSearch indexing design to index one document
per license, rather than per provider. This means that we can use
native OpenSearch pagination and return the exact number of results
the client requests if there are enough matches found. It also reduces
the complexity of only returning licenses that match, rather than
having to map inner hits.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Warning

Rate limit exceeded

@landonshumway-ia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 20 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 20 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc12abf4-0edc-4fe3-9df7-8787c9c44774

📥 Commits

Reviewing files that changed from the base of the PR and between f839d5a and 8c773ba.

📒 Files selected for processing (1)
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
📝 Walkthrough

Walkthrough

Adds a public, unauthenticated OpenSearch-backed license search Lambda and related cursor pagination/sort logic; extends public provider/license schemas and API models to expose per-license fields and licenseNumber filters; adds OpenSearch index/alias management helpers, index reset options, CDK wiring to route the public query to the search stack, and tests/docs updates.

Changes

Cohort / File(s) Summary
Public Search Lambda & tests
backend/cosmetology-app/lambdas/python/search/handlers/public_search.py, backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
New unauthenticated public_search_api_handler: request validation, nested-license OpenSearch queries, sort mapping, base64 cursor pagination, response shaping; comprehensive function tests.
Provider & License schemas
backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py, .../schema/license/api.py, .../schema/privilege/api.py
Added LicensePublicResponseSchema and PublicLicenseSearchResponseSchema; public provider responses now include licenses; added licenseNumber filter; removed adverseActions from public privilege schema.
OpenSearch client & index helpers
backend/cosmetology-app/lambdas/python/search/opensearch_client.py, .../handlers/manage_opensearch_indices.py, .../handlers/populate_provider_documents.py, .../tests/function/test_manage_opensearch_indices.py
Introduced INITIAL_INDEX_VERSION, provider index mapping, alias/index helpers (get_indices_for_alias, delete_index, create_provider_index_with_alias, delete_provider_index_with_alias); manage_opensearch_indices delegates to client; populate supports index reset with shard/replica options; tests adjusted.
Stack & API wiring
backend/cosmetology-app/pipeline/backend_stage.py, backend/cosmetology-app/stacks/api_stack/..., backend/cosmetology-app/stacks/api_lambda_stack/public_lookup_api.py, backend/cosmetology-app/stacks/search_persistent_stack/search_handler.py
Creates SearchPersistentStack earlier, threads it into ApiStack/V1Api/PublicLookupApi; public query endpoint now routes to search_persistent_stack.search_handler.public_handler; new Lambda created with OpenSearch read access and CloudWatch metric/alarm.
API models, snapshots & docs
backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py, backend/cosmetology-app/tests/resources/snapshots/*, backend/cosmetology-app/docs/*, backend/cosmetology-app/docs/search-internal/*, backend/cosmetology-app/docs/postman/*
Added licenseNumber to request/response models and snapshots; provider item schema updated to expose license fields; OpenAPI/Postman/internal search docs refreshed.
Populate & indexing constructs
backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py, backend/cosmetology-app/stacks/search_persistent_stack/populate_provider_documents_handler.py, backend/cosmetology-app/stacks/search_persistent_stack/provider_search_domain.py
Populate handler accepts resetIndexes, numberOfShards, numberOfReplicas with prod-safety check and index reset flow; CDK grants expanded to read/write for alias/index ops.
Legacy handler & tests removed/updated
backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py, backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
Removed legacy public_query_providers handler and its test suite; updated remaining public_get_provider test to inline expected public fields (including licenses).
Test runner & unit tests
backend/cosmetology-app/bin/run_python_tests.py, .../common/tests/unit/test_data_model/test_schema/test_provider.py, .../search/tests/function/test_search_providers.py
Included lambdas/python/search in test runner; added unit tests for licenseNumber validation; minor test formatting tweaks.
Misc: deps & small changes
requirements*, backend/cosmetology-app/tests/smoke/rollback_license_upload_smoke_tests.py, backend/cosmetology-app/bin/generate_mock_license_csv_upload_file.py
Bumped various pinned dependency versions; removed compactTransactionIdGSIPK from a smoke-test helper; updated CLI examples in a script.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant APIGW as API Gateway
    participant Handler as public_search_api_handler
    participant Validator as PublicQueryProvidersRequestSchema
    participant OpenSearch as OpenSearchClient
    participant Mapper as PublicLicenseSearchResponseSchema

    Client->>APIGW: POST /v1/public/compacts/{compact}/providers/query
    APIGW->>Handler: invoke(event)
    Handler->>Validator: validate request body
    alt validation error
        Validator-->>Handler: ValidationError
        Handler-->>APIGW: 400 Bad Request
    else valid
        Handler->>Handler: build nested license query, sort, pagination
        Handler->>OpenSearch: search(index, body)
        OpenSearch-->>Handler: hits with _source & sort
        Handler->>Handler: filter by compact, extract first license
        Handler->>Mapper: sanitize license object
        Mapper-->>Handler: sanitized provider/license
        Handler-->>APIGW: 200 OK (providers, pagination, sorting)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jlkravitz

Poem

🐰 I hopped through indices with a curious twitch,
I counted license numbers, a tidy little stitch,
I threaded stacks and queries with a joyful spin,
I encoded cursors, skipped mismatches with a grin,
Now public searches bloom — go find a license, win! 🥕🔎

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.01% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description addresses the context and rationale but lacks completeness against the template. Missing sections: detailed requirements list, comprehensive testing list (yarn commands verification, API spec updates, CDK tests), and formal checklist items. Expand description to include specific requirements addressed, complete testing verification (yarn test/build commands, OpenAPI spec update confirmation, CDK test updates), and explicitly reference which template items were completed.
Out of Scope Changes check ❓ Inconclusive All code changes directly support public search functionality or necessary infrastructure. Minor out-of-scope items: dependency version bumps (boto3, requests, grpcio, pygments) across multiple requirements files, example command updates in mock CSV generation, and removal of an unrelated smoke test (query_provider_smoke_tests.py). Clarify rationale for dependency version updates and removal of query_provider_smoke_tests.py; confirm whether these changes are necessary for the public search feature or represent unrelated maintenance work.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add support for Cosmetology public search' clearly and directly summarizes the main objective of the changeset: introducing public search functionality leveraging OpenSearch for cosmetology practitioners.
Linked Issues check ✅ Passed Code changes comprehensively address issue #1295 requirements: added license number query support [api.py, public_search.py, test files], implemented OpenSearch-based public search [public_search.py, opensearch_client.py], designed API contract [api.py, api_model.py], included automated tests [test_public_search_providers.py], updated API documentation and schemas, and updated Postman collections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

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

❤️ Share

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

Copy link
Copy Markdown
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 (2)
backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py (1)

113-118: Inconsistent licenseNumber max length between request and response schemas.

The request model allows licenseNumber up to 500 characters (line 116), but the response model's query.licenseNumber echo (line 1349) and _public_license_search_response_schema (line 1664) both specify max length of 100. If a client sends a license number between 101-500 characters, the echoed query in the response would exceed the response schema's constraint.

Consider aligning these values for consistency:

🔧 Suggested alignment
                            'licenseNumber': JsonSchema(
                                type=JsonSchemaType.STRING,
                                min_length=1,
-                               max_length=500,
+                               max_length=100,
                                description='Filter for licenses with a specific license number',
                            ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py` around lines
113 - 118, The request schema for 'licenseNumber' allows up to 500 chars but the
response schemas (the echoed query under 'query.licenseNumber' and the
'_public_license_search_response_schema') are capped at 100, causing an
inconsistent contract; update the response-side JsonSchema definitions for
'licenseNumber' to use the same max_length as the request (or vice versa if you
prefer the smaller limit) so both request and response use the identical
max_length value, adjusting the JsonSchema() instances that reference
'licenseNumber' (including the query echo and
_public_license_search_response_schema) to the chosen consistent max_length and
keep the type/description intact.
backend/cosmetology-app/lambdas/python/search/handlers/public_search.py (1)

15-17: Minor: Comment mentions 20 seconds but timeout is 25 seconds.

📝 Fix comment
 # Instantiate the OpenSearch client outside the handler to cache the connection between invocations
-# Set timeout to 20 seconds to give API gateway time to respond with response
+# Set timeout to 25 seconds to give API Gateway time to respond
 opensearch_client = OpenSearchClient(timeout=25)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`
around lines 15 - 17, The inline comment above the OpenSearch client
instantiation is inconsistent: it says "Set timeout to 20 seconds" while the
actual instantiation uses OpenSearchClient(timeout=25). Update the comment to
match the code (or change the timeout value to 20 if that was intended) so the
comment and the OpenSearchClient(timeout=...) call for opensearch_client are
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`:
- Line 251: The schema field definition for licenseNumber in provider API schema
(the variable/field named licenseNumber in provider api.py) incorrectly uses
Length(max=100); update its validation to Length(min=1, max=500) to match the
published request schema so requests with up to 500 chars are accepted, leaving
required=False and allow_none=False as-is. Ensure you run schema validation
tests after the change.
- Around line 220-223: The schema's licenseJurisdiction field is currently
optional/untyped which allows contract-critical public-search results to lack a
valid jurisdiction; update the field definition(s) for licenseJurisdiction (and
the other occurrence referenced) to require a non-empty, validated jurisdiction
— e.g., set required=True and allow_none=False and add a validation rule
(Length(min=1) or a OneOf(enum_of_allowed_jurisdictions) if you have a canonical
list) matching the project's jurisdiction values; ensure the corresponding
deserialization/validation paths (the schema class that defines jurisdiction,
licenseJurisdiction, and compact) are adjusted so invalid or missing
jurisdictions raise validation errors for public responses.

---

Nitpick comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 15-17: The inline comment above the OpenSearch client
instantiation is inconsistent: it says "Set timeout to 20 seconds" while the
actual instantiation uses OpenSearchClient(timeout=25). Update the comment to
match the code (or change the timeout value to 20 if that was intended) so the
comment and the OpenSearchClient(timeout=...) call for opensearch_client are
consistent.

In `@backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py`:
- Around line 113-118: The request schema for 'licenseNumber' allows up to 500
chars but the response schemas (the echoed query under 'query.licenseNumber' and
the '_public_license_search_response_schema') are capped at 100, causing an
inconsistent contract; update the response-side JsonSchema definitions for
'licenseNumber' to use the same max_length as the request (or vice versa if you
prefer the smaller limit) so both request and response use the identical
max_length value, adjusting the JsonSchema() instances that reference
'licenseNumber' (including the query echo and
_public_license_search_response_schema) to the chosen consistent max_length and
keep the type/description intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b326cb5c-a0b0-4303-b278-f36d62f85b44

📥 Commits

Reviewing files that changed from the base of the PR and between 674ca38 and e41a7d2.

📒 Files selected for processing (17)
  • backend/cosmetology-app/bin/run_python_tests.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/pipeline/backend_stage.py
  • backend/cosmetology-app/stacks/api_lambda_stack/public_lookup_api.py
  • backend/cosmetology-app/stacks/api_stack/__init__.py
  • backend/cosmetology-app/stacks/api_stack/api.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/public_lookup_api.py
  • backend/cosmetology-app/stacks/search_persistent_stack/search_handler.py
  • backend/cosmetology-app/tests/app/test_api/test_public_lookup_api.py
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/smoke/rollback_license_upload_smoke_tests.py
💤 Files with no reviewable changes (1)
  • backend/cosmetology-app/tests/smoke/rollback_license_upload_smoke_tests.py

Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
backend/cosmetology-app/lambdas/python/search/handlers/public_search.py (1)

15-17: Minor: Comment says 20 seconds but timeout is 25.

The comment states "Set timeout to 20 seconds" but the actual timeout is 25 seconds. Consider updating the comment to match.

📝 Suggested fix
 # Instantiate the OpenSearch client outside the handler to cache the connection between invocations
-# Set timeout to 20 seconds to give API gateway time to respond with response
+# Set timeout to 25 seconds to give API gateway time to respond with response
 opensearch_client = OpenSearchClient(timeout=25)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`
around lines 15 - 17, The inline comment above the OpenSearch client
initialization is inconsistent: it says "Set timeout to 20 seconds" while the
code instantiates OpenSearchClient(timeout=25). Update the comment to reflect
the actual timeout (25 seconds) or change the timeout value to 20 to match the
comment; locate the OpenSearchClient instantiation (opensearch_client =
OpenSearchClient(timeout=25)) and make the comment and the timeout value
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 15-17: The inline comment above the OpenSearch client
initialization is inconsistent: it says "Set timeout to 20 seconds" while the
code instantiates OpenSearchClient(timeout=25). Update the comment to reflect
the actual timeout (25 seconds) or change the timeout value to 20 to match the
comment; locate the OpenSearchClient instantiation (opensearch_client =
OpenSearchClient(timeout=25)) and make the comment and the timeout value
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 736471cd-b7e4-460a-a6b8-5a45c274e4c3

📥 Commits

Reviewing files that changed from the base of the PR and between e41a7d2 and c00141f.

📒 Files selected for processing (7)
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json
💤 Files with no reviewable changes (1)
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py
✅ Files skipped from review due to trivial changes (2)
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py (1)

218-224: ⚠️ Potential issue | 🟠 Major

Validate licenseJurisdiction with Jurisdiction, not a bare String.

Making the field required fixed the missing-value case, but this schema still accepts any string. public_search.py relies on this schema’s load() to sanitize OpenSearch hits, while the CDK model already constrains the field to known jurisdictions, so malformed documents can still pass through here.

Suggested fix
-    licenseJurisdiction = String(required=True, allow_none=False)
+    licenseJurisdiction = Jurisdiction(required=True, allow_none=False)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`
around lines 218 - 224, The schema currently defines licenseJurisdiction as a
plain String which allows any value; change it to use the Jurisdiction field
type so only known jurisdictions are accepted: import or reference the
Jurisdiction field/class and replace the line defining licenseJurisdiction (the
symbol licenseJurisdiction in provider/api.py) with licenseJurisdiction =
Jurisdiction(required=True, allow_none=False), preserving other
validators/flags; ensure the Jurisdiction symbol is imported at the top of the
module if not already.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`:
- Around line 206-208: The public provider API model
V1PublicProviderResponseModel must be updated to expose the new sanitized
licenses array that ProviderPublicResponseSchema now produces; locate
V1PublicProviderResponseModel and add a licenses field matching the schema
(array of LicensePublicResponse / appropriate license DTO type,
optional/nullable as ProviderPublicResponseSchema defines) alongside the
existing privileges field, update serialization/validation and any API docs or
OpenAPI model generation for V1PublicProviderResponseModel so the public
contract reflects licenses as returned by ProviderPublicResponseSchema.

---

Duplicate comments:
In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`:
- Around line 218-224: The schema currently defines licenseJurisdiction as a
plain String which allows any value; change it to use the Jurisdiction field
type so only known jurisdictions are accepted: import or reference the
Jurisdiction field/class and replace the line defining licenseJurisdiction (the
symbol licenseJurisdiction in provider/api.py) with licenseJurisdiction =
Jurisdiction(required=True, allow_none=False), preserving other
validators/flags; ensure the Jurisdiction symbol is imported at the top of the
module if not already.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9967c659-1463-4807-9109-aea5185d3080

📥 Commits

Reviewing files that changed from the base of the PR and between ab36ddd and 430e17e.

📒 Files selected for processing (5)
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/stacks/api_lambda_stack/public_lookup_api.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json
✅ Files skipped from review due to trivial changes (1)
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/cosmetology-app/stacks/api_lambda_stack/public_lookup_api.py
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json

Copy link
Copy Markdown
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 (2)
backend/cosmetology-app/docs/postman/postman-collection.json (1)

427-427: Consider using valid placeholder dates in sample data.

The sample request body contains invalid calendar dates (e.g., "2047-09-31" - September has 30 days, "1822-04-31" - April has 30 days, "2511-11-31" - November has 30 days). While these are clearly auto-generated placeholders that testers will replace, using valid dates like "2000-01-15" would improve documentation quality and make the examples more realistic for API consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/docs/postman/postman-collection.json` at line 427,
The sample JSON request body contains invalid dates (e.g., in the "raw" payload
for fields dateOfBirth, dateOfExpiration, dateOfIssuance, dateOfRenewal) such as
"2047-09-31" and "1822-04-31"; update those placeholder values to valid ISO
dates (for example use "2000-01-15" for dateOfBirth, "2025-12-31" for
dateOfExpiration, "1990-06-20" for dateOfIssuance, and "2024-11-30" for
dateOfRenewal) so the example in postman-collection.json is realistic and
parsable.
backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py (1)

212-224: Consider using the Jurisdiction field type for stronger validation.

licenseJurisdiction is defined as String(required=True) which accepts any non-empty string. Since this is a contract-critical field for public search results, using the Jurisdiction field type (already imported) would provide enum validation against allowed jurisdiction values, matching the API Gateway model's enum constraint.

Proposed enhancement
 class PublicLicenseSearchResponseSchema(ForgivingSchema):
     """
     License object fields returned by the public query providers endpoint (OpenSearch-backed).
     Jurisdiction is renamed to licenseJurisdiction for parity with JCC implementation.
     """

     providerId = Raw(required=True, allow_none=False)
     givenName = String(required=True, allow_none=False, validate=Length(1, 100))
     familyName = String(required=True, allow_none=False, validate=Length(1, 100))
-    licenseJurisdiction = String(required=True, allow_none=False)
+    licenseJurisdiction = Jurisdiction(required=True, allow_none=False)
     compact = Compact(required=True, allow_none=False)
     licenseType = String(required=True, allow_none=False)
     licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`
around lines 212 - 224, PublicLicenseSearchResponseSchema currently defines
licenseJurisdiction as a plain String which permits any value; change it to use
the existing Jurisdiction field type to enforce enum validation. In the class
PublicLicenseSearchResponseSchema replace the licenseJurisdiction = String(...)
declaration with licenseJurisdiction = Jurisdiction(required=True,
allow_none=False) so the schema uses the imported Jurisdiction field and matches
the API Gateway enum contract. Ensure required and allow_none flags mirror other
fields for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json`:
- Around line 843-867: The OpenAPI schema currently allows requests with
"search_after" but no "sort"; update the API model in the CDK/CloudFormation
source (not the generated latest-oas30.json) to express the dependency that when
"search_after" is present "sort" is required—for example add a conditional
schema (oneOf / required-set or a JSON Schema dependency) around the request
object that enforces either (a) no "search_after" or (b) both "search_after" and
"sort" present; target the schema that defines the properties "search_after" and
"sort" so the generator will produce the updated latest-oas30.json, then
regenerate the API spec.

In
`@backend/cosmetology-app/docs/search-internal/postman/postman-collection.json`:
- Line 468: The example payload uses string placeholders for numeric/array
pagination fields—replace quoted strings with proper JSON types: change
"total.value" to a numeric (integer) not a quoted string, change "size", "from",
and any "search_after" to numbers/arrays as appropriate, and change "lastSort"
from a quoted string to an actual JSON array (e.g., [] or array of values).
Update the request body in the Postman collection (the JSON "body" value
containing lastSort/total.value/size/from/search_after) so those fields are
unquoted and match their expected types.

---

Nitpick comments:
In `@backend/cosmetology-app/docs/postman/postman-collection.json`:
- Line 427: The sample JSON request body contains invalid dates (e.g., in the
"raw" payload for fields dateOfBirth, dateOfExpiration, dateOfIssuance,
dateOfRenewal) such as "2047-09-31" and "1822-04-31"; update those placeholder
values to valid ISO dates (for example use "2000-01-15" for dateOfBirth,
"2025-12-31" for dateOfExpiration, "1990-06-20" for dateOfIssuance, and
"2024-11-30" for dateOfRenewal) so the example in postman-collection.json is
realistic and parsable.

In
`@backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py`:
- Around line 212-224: PublicLicenseSearchResponseSchema currently defines
licenseJurisdiction as a plain String which permits any value; change it to use
the existing Jurisdiction field type to enforce enum validation. In the class
PublicLicenseSearchResponseSchema replace the licenseJurisdiction = String(...)
declaration with licenseJurisdiction = Jurisdiction(required=True,
allow_none=False) so the schema uses the imported Jurisdiction field and matches
the API Gateway enum contract. Ensure required and allow_none flags mirror other
fields for consistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b3bee02f-fc05-4b44-8505-bcd5e52f01c3

📥 Commits

Reviewing files that changed from the base of the PR and between 430e17e and c0f4c69.

📒 Files selected for processing (11)
  • backend/cosmetology-app/docs/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/internal/postman/postman-collection.json
  • backend/cosmetology-app/docs/postman/postman-collection.json
  • backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/search-internal/postman/postman-collection.json
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json
✅ Files skipped from review due to trivial changes (1)
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py

@landonshumway-ia landonshumway-ia force-pushed the feat/update-cosm-public-search branch from 38c51bb to 2cd2840 Compare March 31, 2026 18:00
Copy link
Copy Markdown
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: 7

🧹 Nitpick comments (1)
backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py (1)

359-362: Keep response category items unconstrained

On Line 359, constraining response values with an enum makes the response model brittle; enum validation belongs on request schemas, not response schemas.

Suggested change
                 'clinicalPrivilegeActionCategories': JsonSchema(
                     type=JsonSchemaType.ARRAY,
                     items=JsonSchema(
-                        type=JsonSchemaType.STRING,
-                        enum=['Fraud', 'Consumer Harm', 'Other'],
+                        type=JsonSchemaType.STRING,
                     ),
                 ),

Based on learnings: In this codebase, request schemas should enforce enums, while response schemas should remain unconstrained for flexibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py` around
lines 359 - 362, The response schema currently constrains the array items using
items=JsonSchema(type=JsonSchemaType.STRING, enum=['Fraud', 'Consumer Harm',
'Other']) which makes responses brittle; remove the enum from the response model
(leave items as an unconstrained string JsonSchema or remove the items enum
field entirely) so that enum validation remains only in request schemas; update
the code around the JsonSchema/item definition referenced by items and
JsonSchemaType.STRING to drop the enum constraint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/cosmetology-app/docs/postman/postman-collection.json`:
- Line 691: The bulk-upload example in the Postman collection is inconsistent
with the later "Upload Document" request: update the example JSON under "body"
so upload.fields uses the same keys consumed by the uploader (docField-key,
docField-policy, docField-x-amz-*), or alternatively update the "Upload
Document" request to read the arbitrary example keys (ut_9c, non_8f); locate and
change the upload example (the "body" JSON containing upload.fields) and the
"Upload Document" request variables/parsing logic so both use the identical
contract (match names like upload.fields →
docField-key/docField-policy/docField-x-amz-* and ensure the postman variables
in the Upload Document request refer to those same field names).
- Line 427: The Postman example JSON uses impossible dates (e.g., "2047-09-31",
"1822-04-31", "2511-11-31") for the license fields "dateOfBirth",
"dateOfExpiration", "dateOfIssuance", and "dateOfRenewal", causing schema parse
errors; update each invalid date occurrence (the shown block and the other two
copies referenced) to real calendar dates (for example "2047-09-30",
"1822-04-30", "2511-11-30" or other valid YYYY-MM-DD values) so the POST
examples pass validation, and ensure you replace the same invalid strings
wherever they appear in the collection.

In
`@backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json`:
- Around line 857-860: The generated OAS lost the "maximum" constraint for the
pagination "from" property causing contract drift with
SearchProvidersRequestSchema which enforces a 9900 cap; update the source API
model/schema used by the generation pipeline (the SearchProvidersRequestSchema
definition) to include "maximum": 9900 for the "from" integer property, then
re-run the API Gateway/OpenAPI generation to produce a new latest-oas30.json
instead of editing
backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json
directly.

In
`@backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py`:
- Around line 95-103: The code always recreates the concrete index as ..._v1
which can roll an alias backwards; change the logic so you first resolve the
alias's currently active concrete index (use the opensearch_client method that
returns the index name for an alias or add one), parse its version suffix (e.g.
_vN) and compute the next concrete index name (or use the existing highest
version if you intend to preserve it) instead of hardcoding
INITIAL_INDEX_VERSION, then create the new concrete index with that computed
name and only delete or swap indices via the alias after the new index exists;
update the block that uses alias_name, index_name, INITIAL_INDEX_VERSION and the
calls to delete_provider_index_with_alias/create_provider_index_with_alias
accordingly.
- Around line 84-108: The handler currently allows resetIndexes together with
resumption params, which can leave earlier compacts unpopulated; add a guard
after reading reset_indexes, starting_compact, and starting_last_key that fails
fast (raise an exception or return a 400-style error) if reset_indexes is True
and either starting_compact or starting_last_key is provided. Update the logic
around the reset branch (referencing reset_indexes, starting_compact,
starting_last_key, config.compacts, INITIAL_INDEX_VERSION, and
opensearch_client.delete_provider_index_with_alias /
create_provider_index_with_alias) so you check and abort before
deleting/recreating indexes to prevent partial repopulation.

In `@backend/cosmetology-app/lambdas/python/search/opensearch_client.py`:
- Around line 140-172: The create_provider_index_with_alias function uses
check-then-act with alias_exists/index_exists then create_index/create_alias and
can race; wrap the create_index and create_alias calls in try/except that
catches OpenSearch RequestError (or the client’s equivalent) and treat "resource
already exists" (status 400 or the specific error code/message) as a benign
outcome: call create_index and create_alias inside a single try block (or
separate try blocks), on RequestError inspect the status/code/message and log
that the index/alias already exists and return/continue, re-raise for other
errors; keep references to create_provider_index_with_alias, alias_exists,
index_exists, create_index, and create_alias to locate and change the code.

In `@backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py`:
- Around line 816-843: The CDK model currently hard-codes the category enum in
_clinical_privilege_action_categories_schema and permits empty arrays, creating
a second source of truth and a looser contract than the runtime
AdverseActionPostRequestSchema; update
_clinical_privilege_action_categories_schema to remove the enum (do not
duplicate allowed values) and instead return an ARRAY of STRINGs with minItems=1
(use JsonSchemaType.ARRAY, items type JsonSchemaType.STRING, and set minItems=1)
so the API model requires a non-empty list but leaves permitted values
enforcement to AdverseActionPostRequestSchema; adjust
_encumbrance_request_schema as needed to reference the updated
_clinical_privilege_action_categories_schema (keep date format
cc_api.YMD_FORMAT).

---

Nitpick comments:
In `@backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py`:
- Around line 359-362: The response schema currently constrains the array items
using items=JsonSchema(type=JsonSchemaType.STRING, enum=['Fraud', 'Consumer
Harm', 'Other']) which makes responses brittle; remove the enum from the
response model (leave items as an unconstrained string JsonSchema or remove the
items enum field entirely) so that enum validation remains only in request
schemas; update the code around the JsonSchema/item definition referenced by
items and JsonSchemaType.STRING to drop the enum constraint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38c31be0-4dc8-4615-85ec-fd0b7530a3ec

📥 Commits

Reviewing files that changed from the base of the PR and between 430e17e and 38c51bb.

📒 Files selected for processing (37)
  • backend/cosmetology-app/docs/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/internal/postman/postman-collection.json
  • backend/cosmetology-app/docs/postman/postman-collection.json
  • backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json
  • backend/cosmetology-app/docs/search-internal/postman/postman-collection.json
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/__init__.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/record.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/fields.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/cosmetology-app/lambdas/python/common/common_test/test_constants.py
  • backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/cosmetology-app/lambdas/python/common/tests/resources/api/adverse-action-post.json
  • backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/encumbrance.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_investigation.py
  • backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/opensearch_client.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py
  • backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py
  • backend/cosmetology-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json
  • backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/cosmetology-app/tests/smoke/encumbrance_smoke_tests.py
  • backend/cosmetology-app/tests/smoke/investigation_smoke_tests.py
💤 Files with no reviewable changes (2)
  • backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/fields.py
✅ Files skipped from review due to trivial changes (2)
  • backend/cosmetology-app/lambdas/python/common/tests/resources/api/adverse-action-post.json
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/cosmetology-app/lambdas/python/search/opensearch_client.py`:
- Around line 208-225: The adverse_action_properties mapping in
opensearch_client.py is missing the encumbranceType field which causes tests to
fail; update the adverse_action_properties dict (the variable named
adverse_action_properties) to include 'encumbranceType': {'type': 'keyword'}
alongside the other keyword fields so the mapping contains the expected field
used in tests and index creation. Ensure you add it in the same style as other
keyword entries (e.g., near related fields like 'actionAgainst' or 'compact') to
keep the mapping consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 324f01e1-9c54-46b7-bfec-05756d72017c

📥 Commits

Reviewing files that changed from the base of the PR and between 38c51bb and 2cd2840.

📒 Files selected for processing (4)
  • backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/cosmetology-app/lambdas/python/search/opensearch_client.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py

These constraints don't apply to OpenSearch
queries, as we can search for all matches
within the system without any search parameters
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 15-17: The comment above the OpenSearch client instantiation is
inaccurate: it says "20 seconds" while the actual timeout passed to
OpenSearchClient is 25; update either the comment to reflect 25 seconds or
change the timeout argument to 20 to match the comment. Locate the
opensearch_client = OpenSearchClient(timeout=25) line and either change the
numeric timeout to 20 or edit the preceding comment to say "Set timeout to 25
seconds" so the comment and the OpenSearchClient(timeout=...) parameter are
consistent.
- Around line 111-120: The _parse_and_validate_public_query_body function
currently only catches ValidationError but json.loads inside schema.loads can
raise json.JSONDecodeError for malformed JSON, causing 500s; update the
try/except to also catch json.JSONDecodeError (import already present) and raise
CCInvalidRequestException with a clear 400-level message including the decode
error details, alongside the existing ValidationError handling for
PublicQueryProvidersRequestSchema.loads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d44e7866-5a15-4c6c-b2f7-c1fc64da8a7d

📥 Commits

Reviewing files that changed from the base of the PR and between dddacd0 and 520fa83.

📒 Files selected for processing (2)
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py

@landonshumway-ia
Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is now ready for your review. Thanks

Copy link
Copy Markdown
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/cosmetology-app/lambdas/python/search/handlers/public_search.py (1)

69-82: Let schema validation handle malformed hits consistently.

Lines 70-73 and Line 78 use direct subscripts / pop() before the ValidationError guard. A single malformed OpenSearch hit can therefore raise KeyError and abort the whole page instead of being logged and skipped like the other bad-hit cases in this loop.

🛠️ Suggested hardening
         license_fields = licenses[0].copy()
-        license_fields['providerId'] = source['providerId']
-        license_fields['compact'] = source['compact']
-        license_fields['givenName'] = source['givenName']
-        license_fields['familyName'] = source['familyName']
+        license_fields['providerId'] = provider_id
+        license_fields['compact'] = source.get('compact')
+        license_fields['givenName'] = source.get('givenName')
+        license_fields['familyName'] = source.get('familyName')
         try:
             # home state is stored under the 'jurisdiction' field on the license record, but
             # the frontend expects this to be labeled 'licenseJurisdiction' for parity with other
             # public search response schemas.
-            license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction')
+            license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction', None)
             sanitized = license_schema.load(license_fields)
             sanitized.pop('jurisdiction', None)
             providers.append(sanitized)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`
around lines 69 - 82, The code is accessing license_fields keys (setting
providerId/compact/givenName/familyName and popping 'jurisdiction') outside the
ValidationError try block which can raise KeyError and abort processing; move
the key manipulations into the try, or use safe accessors (dict.get) before
calling license_schema.load, and ensure the except catches KeyError as well (or
broaden to Exception) so malformed OpenSearch hits are logged and skipped like
other bad-hit cases; reference license_fields, source, licenses,
license_schema.load, ValidationError, and providers when applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 37-49: The handler _public_query_licenses reads compact from event
pathParameters and uses it to construct index_name without validation; add an
allowlist check (e.g., config.allowed_compacts or config.supported_compacts) and
normalize the value before proceeding: validate that compact is one of the
allowed values, return or raise a 400/ValidationError when it is not, and only
then call _build_public_license_search_body and opensearch_client.search with
index_name = f'compact_{compact}_providers'; ensure the validation happens early
in _public_query_licenses so unsupported compacts are rejected at the API
boundary.
- Around line 147-171: The sort implementation in _build_public_opensearch_sort
uses root-level fields (familyName.keyword, givenName.keyword) but the
corresponding search logic still queries nested licenses.familyName and
licenses.givenName; update the search query to use the root-level fields
(familyName, givenName and their .keyword variants for sorting) so queries and
sorting are consistent (replace any references to
licenses.familyName/licenses.givenName in the public search handler with
root-level familyName/givenName), and update the related tests in
test_public_search_providers.py to expect root-level name queries instead of
nested license name queries.

---

Nitpick comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 69-82: The code is accessing license_fields keys (setting
providerId/compact/givenName/familyName and popping 'jurisdiction') outside the
ValidationError try block which can raise KeyError and abort processing; move
the key manipulations into the try, or use safe accessors (dict.get) before
calling license_schema.load, and ensure the except catches KeyError as well (or
broaden to Exception) so malformed OpenSearch hits are logged and skipped like
other bad-hit cases; reference license_fields, source, licenses,
license_schema.load, ValidationError, and providers when applying the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a923e3c6-0288-4778-8e23-aa4a25321735

📥 Commits

Reviewing files that changed from the base of the PR and between dddacd0 and d068ac9.

📒 Files selected for processing (18)
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements.txt
  • backend/cosmetology-app/lambdas/python/common/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements.txt
  • backend/cosmetology-app/lambdas/python/compact-configuration/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/custom-resources/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/data-events/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/disaster-recovery/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/provider-data-v1/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/requirements.txt
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/lambdas/python/staff-user-pre-token/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/staff-users/requirements-dev.txt
  • backend/cosmetology-app/requirements-dev.txt
  • backend/cosmetology-app/requirements.txt
✅ Files skipped from review due to trivial changes (16)
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements.txt
  • backend/cosmetology-app/lambdas/python/provider-data-v1/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/disaster-recovery/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/staff-user-pre-token/requirements-dev.txt
  • backend/cosmetology-app/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/custom-resources/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements.txt
  • backend/cosmetology-app/lambdas/python/staff-users/requirements-dev.txt
  • backend/cosmetology-app/requirements.txt
  • backend/cosmetology-app/lambdas/python/search/requirements.txt
  • backend/cosmetology-app/lambdas/python/data-events/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/compact-configuration/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py

Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
backend/cosmetology-app/lambdas/python/search/handlers/public_search.py (1)

78-81: Redundant pop('jurisdiction') call.

Line 78 already removes jurisdiction from license_fields before schema.load(). The sanitized.pop('jurisdiction', None) at line 80 is a no-op since the field was already removed from the input dict.

🧹 Suggested cleanup
             license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction')
             sanitized = license_schema.load(license_fields)
-            sanitized.pop('jurisdiction', None)
             providers.append(sanitized)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`
around lines 78 - 81, The sanitized.pop('jurisdiction', None) call is redundant
because jurisdiction is already removed from license_fields via
license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction')
before calling license_schema.load(); remove the sanitized.pop('jurisdiction',
None) line in public_search.py (the block using license_fields,
license_schema.load, and providers.append) so the code simply maps and loads the
field and appends sanitized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 78-81: The sanitized.pop('jurisdiction', None) call is redundant
because jurisdiction is already removed from license_fields via
license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction')
before calling license_schema.load(); remove the sanitized.pop('jurisdiction',
None) line in public_search.py (the block using license_fields,
license_schema.load, and providers.append) so the code simply maps and loads the
field and appends sanitized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2fa536c-baee-4950-8c33-3cb6f7ff9de9

📥 Commits

Reviewing files that changed from the base of the PR and between dddacd0 and d068ac9.

📒 Files selected for processing (18)
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements.txt
  • backend/cosmetology-app/lambdas/python/common/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements.txt
  • backend/cosmetology-app/lambdas/python/compact-configuration/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/custom-resources/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/data-events/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/disaster-recovery/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/provider-data-v1/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
  • backend/cosmetology-app/lambdas/python/search/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/requirements.txt
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/cosmetology-app/lambdas/python/staff-user-pre-token/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/staff-users/requirements-dev.txt
  • backend/cosmetology-app/requirements-dev.txt
  • backend/cosmetology-app/requirements.txt
✅ Files skipped from review due to trivial changes (16)
  • backend/cosmetology-app/lambdas/python/staff-user-pre-token/requirements-dev.txt
  • backend/cosmetology-app/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements.txt
  • backend/cosmetology-app/lambdas/python/search/requirements.txt
  • backend/cosmetology-app/lambdas/python/staff-users/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/disaster-recovery/requirements-dev.txt
  • backend/cosmetology-app/requirements.txt
  • backend/cosmetology-app/lambdas/python/data-events/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/compact-configuration/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/custom-resources/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements.txt
  • backend/cosmetology-app/lambdas/python/cognito-backup/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/provider-data-v1/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/common/requirements-dev.txt
  • backend/cosmetology-app/lambdas/python/search/tests/function/test_public_search_providers.py

Copy link
Copy Markdown
Collaborator

@jlkravitz jlkravitz left a comment

Choose a reason for hiding this comment

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

Looks good! Few questions/comments.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/cosmetology-app/lambdas/python/search/handlers/public_search.py`:
- Around line 77-91: The code currently does
license_fields['licenseJurisdiction'] = license_fields.pop('jurisdiction') which
can raise KeyError if 'jurisdiction' is missing; change this to safely get or
pop with a default (e.g., use license_fields.pop('jurisdiction', None) or check
'jurisdiction' in license_fields) before assigning to 'licenseJurisdiction',
then call license_schema.load(license_fields) and append sanitized to providers;
keep the existing ValidationError catch and logging (logger.error with
provider_id and e.messages) and remove the now-redundant
sanitized.pop('jurisdiction', None) if you use pop with default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0cf46d3d-f631-4862-a989-d305fc2fc2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 7f00cf0 and f839d5a.

⛔ Files ignored due to path filters (1)
  • backend/cosmetology-app/lambdas/nodejs/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • backend/cosmetology-app/lambdas/nodejs/package.json
  • backend/cosmetology-app/lambdas/python/search/handlers/public_search.py
✅ Files skipped from review due to trivial changes (1)
  • backend/cosmetology-app/lambdas/nodejs/package.json

Copy link
Copy Markdown
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.

@isabeleliassen This is good to merge.

@isabeleliassen isabeleliassen merged commit c30ba4c into csg-org:main Apr 10, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cosmetology public search - BE

4 participants