diff --git a/backend/common-cdk/common_constructs/stack.py b/backend/common-cdk/common_constructs/stack.py index 74224427c..c1422d037 100644 --- a/backend/common-cdk/common_constructs/stack.py +++ b/backend/common-cdk/common_constructs/stack.py @@ -97,6 +97,14 @@ def __init__(self, *args, environment_context: dict, environment_name: str, **kw self.environment_context = environment_context self.environment_name = environment_name + + # Guard: all pipeline environments (test, beta, prod) MUST have a domain_name configured + if environment_name in ('test', 'beta', 'prod') and not environment_context.get('domain_name'): + raise ValueError( + f"Pipeline environments (test, beta, prod) require 'domain_name' to be configured. " + f"Environment '{environment_name}' is missing this required configuration." + ) + # We only set the API_BASE_URL common env var if the API_DOMAIN_NAME is set # The API_BASE_URL is used by the feature flag client to call the flag check endpoint if self.api_domain_name: @@ -130,6 +138,12 @@ def search_api_domain_name(self) -> str | None: @property def ui_domain_name(self) -> str | None: + # Allow explicit override via environment context for cases where the UI is hosted + # on a different domain than the backend's hosted zone (e.g. cosmetology backend uses + # cosmetology.compactconnect.org but the UI is at app.compactconnect.org) + override = self.environment_context.get('ui_domain_name_override') + if override is not None: + return override if self.hosted_zone is not None: return f'app.{self.hosted_zone.zone_name}' return None @@ -137,7 +151,7 @@ def ui_domain_name(self) -> str | None: @property def allowed_origins(self) -> list[str]: allowed_origins = [] - if self.hosted_zone is not None: + if self.ui_domain_name is not None: allowed_origins.append(f'https://{self.ui_domain_name}') if self.environment_context.get('allow_local_ui', False): diff --git a/backend/compact-connect/common_constructs/cc_api.py b/backend/compact-connect/common_constructs/cc_api.py index 4ad409a61..7a503d758 100644 --- a/backend/compact-connect/common_constructs/cc_api.py +++ b/backend/compact-connect/common_constructs/cc_api.py @@ -17,6 +17,7 @@ MethodLoggingLevel, ResponseType, RestApi, + SecurityPolicy, StageOptions, ) from aws_cdk.aws_certificatemanager import Certificate, CertificateValidation @@ -89,7 +90,14 @@ def __init__( validation=CertificateValidation.from_dns(hosted_zone=stack.hosted_zone), subject_alternative_names=[stack.hosted_zone.zone_name], ) - domain_kwargs = {'domain_name': DomainNameOptions(certificate=certificate, domain_name=domain_name)} + domain_kwargs = { + 'domain_name': DomainNameOptions( + certificate=certificate, + domain_name=domain_name, + # this resource defaults to TLS_1_2, but we will explicitly set this anyway + security_policy=SecurityPolicy.TLS_1_2, + ) + } access_log_group = LogGroup(scope, 'ApiAccessLogGroup', retention=RetentionDays.ONE_MONTH) NagSuppressions.add_resource_suppressions( @@ -103,10 +111,14 @@ def __init__( ], ) + # Disable the default execute-api endpoint for all pipeline environments so traffic must use the custom domain. + disable_execute_api_endpoint = environment_name in ('test', 'beta', 'prod') + super().__init__( scope, construct_id, cloud_watch_role=True, + disable_execute_api_endpoint=disable_execute_api_endpoint, deploy_options=StageOptions( # NOTE: If we are ever updating our pipeline architecture which requires a change to the pipeline stack # name, the domain base path mapping for the API will fail to deploy unless we change the name of the diff --git a/backend/compact-connect/lambdas/nodejs/package.json b/backend/compact-connect/lambdas/nodejs/package.json index 6771d8d53..edf67cfdf 100644 --- a/backend/compact-connect/lambdas/nodejs/package.json +++ b/backend/compact-connect/lambdas/nodejs/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "type": "commonjs", "description": "NodeJS lambdas for Compact Connect", + "resolutions": { + "fast-xml-parser": "5.3.6" + }, "scripts": { "build": "tsc", "watch": "tsc -w", diff --git a/backend/compact-connect/lambdas/nodejs/yarn.lock b/backend/compact-connect/lambdas/nodejs/yarn.lock index ecc333362..89d05f0d0 100644 --- a/backend/compact-connect/lambdas/nodejs/yarn.lock +++ b/backend/compact-connect/lambdas/nodejs/yarn.lock @@ -3453,12 +3453,12 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-xml-parser@5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz#06f39aafffdbc97bef0321e626c7ddd06a043ecf" - integrity sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA== +fast-xml-parser@5.3.4, fast-xml-parser@5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz#85a69117ca156b1b3c52e426495b6de266cb6a4b" + integrity sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA== dependencies: - strnum "^2.1.0" + strnum "^2.1.2" fb-watchman@^2.0.0: version "2.0.2" @@ -5116,7 +5116,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^2.1.0: +strnum@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.2.tgz#a5e00ba66ab25f9cafa3726b567ce7a49170937a" integrity sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ== diff --git a/backend/compact-connect/tests/app/base.py b/backend/compact-connect/tests/app/base.py index 3ea316664..1d83261e6 100644 --- a/backend/compact-connect/tests/app/base.py +++ b/backend/compact-connect/tests/app/base.py @@ -552,6 +552,15 @@ def _inspect_api_stack(self, api_stack: ApiStack): }, ) + # When a custom domain is configured, verify the API Gateway domain uses TLS 1.2 + if api_stack.hosted_zone is not None: + api_template.has_resource_properties( + 'AWS::ApiGateway::DomainName', + { + 'SecurityPolicy': 'TLS_1_2', + }, + ) + def _check_no_stack_annotations(self, stack: Stack): with self.subTest(f'Security Rules: {stack.stack_name}'): errors = Annotations.from_stack(stack).find_error('*', Match.string_like_regexp('.*')) diff --git a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py index 135d42dda..1bb4fa3a3 100644 --- a/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/compact-connect/tests/common_constructs/test_cognito_user_backup.py @@ -31,12 +31,13 @@ class TestCognitoUserBackup(TestCase): def setUpClass(cls): """Set up test infrastructure.""" cls.app = App() - # The persistent stack and layer are required for CognitoUserBackup, as an internal lambda depends on it + # The persistent stack and layer are required for CognitoUserBackup, as an internal lambda depends on it. + # Use a non-pipeline environment name so domain_name is not required (avoids HostedZone.from_lookup in tests). common_stack = AppStack( cls.app, 'CommonStack', environment_context={}, - environment_name='test', + environment_name='sandbox', standard_tags=StandardTags(project='compact-connect', service='compact-connect', environment='test'), ) # Create common lambda layers diff --git a/backend/compact-connect/tests/common_constructs/test_data_migration.py b/backend/compact-connect/tests/common_constructs/test_data_migration.py index aa002bd35..9b8627036 100644 --- a/backend/compact-connect/tests/common_constructs/test_data_migration.py +++ b/backend/compact-connect/tests/common_constructs/test_data_migration.py @@ -17,12 +17,13 @@ def test_data_migration_synthesizes(self): from common_constructs.python_common_layer_versions import PythonCommonLayerVersions app = App() - # The persistent stack and layer are required for DataMigration, as an internal lambda depends on it + # The persistent stack and layer are required for DataMigration, as an internal lambda depends on it. + # Use a non-pipeline environment name so domain_name is not required (avoids HostedZone.from_lookup in tests). common_stack = AppStack( app, 'CommonStack', environment_context={}, - environment_name='test', + environment_name='sandbox', standard_tags=StandardTags(project='compact-connect', service='compact-connect', environment='test'), ) # Create common lambda layers diff --git a/backend/cosmetology-app/README.md b/backend/cosmetology-app/README.md index 02f5612d7..d2a90b263 100644 --- a/backend/cosmetology-app/README.md +++ b/backend/cosmetology-app/README.md @@ -44,6 +44,23 @@ The `cdk.json` file tells the CDK Toolkit how to execute your app, including con deployment. You can add local configuration that will be merged into the `cdk.json['context']` values with a `cdk.context.json` file that you will not check in. +### `ui_domain_name_override` + +**Important:** Because the cosmetology backend is hosted on a different domain than the shared frontend UI application (e.g. the +backend hosted zone is `cosmetology.compactconnect.org` but the UI lives at `app.compactconnect.org`), each +environment's context must include a `ui_domain_name_override` field that specifies the correct UI domain name. Without +this override, the UI domain would be incorrectly derived from the backend's hosted zone (e.g. +`app.cosmetology.compactconnect.org` instead of `app.compactconnect.org`). This value is used for CORS allowed origins, +Cognito callback/logout URLs, and email template links. + +Example: +```json +{ + "domain_name": "cosmetology.compactconnect.org", + "ui_domain_name_override": "app.compactconnect.org" +} +``` + This project is set up like a standard Python project. To use it, create and activate a python virtual environment using the tools of your choice (`pyenv` and `venv` are common). diff --git a/backend/cosmetology-app/app_clients/README.md b/backend/cosmetology-app/app_clients/README.md index e4c9c6bd9..89cbc85ae 100644 --- a/backend/cosmetology-app/app_clients/README.md +++ b/backend/cosmetology-app/app_clients/README.md @@ -56,9 +56,7 @@ The following scopes are available at the jurisdiction level: ``` Currently, the most common scope needed by app clients is `{jurisdiction}/{compact}.write`, which allows uploading -license data for a jurisdiction/compact combination. Scopes that expose PII (e.g., `.readSSN`, `.readPrivate`) should -be granted sparingly and will require valid request signatures once a signing public key is configured for the -jurisdiction. +license data for a jurisdiction/compact combination. ### 3. Create App Client Using Interactive Python Script @@ -108,143 +106,6 @@ link that you'll generate separately. As part of the email message sent to the consuming team, be sure to include the onboarding instructions document from the `it_staff_onboarding_instructions/` directory. -## Managing API Signing Public Keys - -### Overview - -Signature-based authentication provides an additional layer of security for API access to sensitive licensure data. Each -compact/state combination can have multiple SIGNATURE public keys configured to support key rotation and zero-downtime -deployments. - -### Authorization Requirements - -**⚠️ CRITICAL SECURITY NOTICE:** Due to the sensitivity of the data protected by SIGNATURE authentication (including -partial Social Security Numbers, personal addresses, and professional license details), configuration of new SIGNATURE -public keys in production environments **MUST** include explicit authorization from the state board executive director. - - -### Creating SIGNATURE Public Keys - -Once a state configures a public key, they will be able to access the SIGNATURE-required API endpoints. API endpoints with -_optional_ SIGNATURE support will also begin to enforce SIGNATURE signatures for that combination of compact and state. **This -means that, once a compact/state has a public key configured, they will be denied access to SIGNATURE-Optional endpoints, -such as the `POST license` endpoint, unless they have also implemented SIGNATURE signatures there as well.** Be sure that -the representative is advised that they should begin signing those requests _before_ CompactConnect has a configured -public key. - -#### 1. Prerequisites - -Before creating a new SIGNATURE public key, ensure you have: -- **Production Authorization**: Explicit approval from the state board executive director for production environments -- Validated the identity of the individual providing the public key to you -- Jurisdiction and compact information confirmed -- Contact information for the state IT representative -- The public key file (`.pub` format) from the state IT representative (copy it to the same directory you are running the script from). The name of the file must match the key id. -- AWS credentials configured with permissions to write to the compact configuration table -- Python 3.10+ installed with boto3 dependency (`pip install boto3`) - -#### 2. Key ID Naming Convention - -The state IT department should provide an identifier; however, you can recommend a descriptive key ID that includes: -- Environment indicator (if applicable) -- Version or date suffix - -Examples: -- `prod-key-001` -- `beta-key-2024-01` - -#### 3. Create SIGNATURE Public Key Using Interactive Python Script - -**Use the provided Python script in the bin directory for streamlined SIGNATURE key management:** - -```bash -python3 bin/manage_signature_keys.py create -t -``` - -**Interactive Process:** -The script will prompt you for: -- Compact (cosm) -- State postal abbreviation (e.g., "ky", "la") -- Key ID (e.g., "client-org-prod-key-001") - -**File Reading:** -The script will: -- Notify you that it will read the public key from `.pub` -- Validate the PEM format of the public key -- Check for existing keys with the same ID -- Write the key to the compact configuration database - -**⚠️NOTICE:** Once the public key has been successfully stored, remove the `.pub` file from the directory to ensure it -is never accidentally checked into the project. - -#### 4. Database Schema - -SIGNATURE keys are stored in the compact configuration table with the following schema: -- **Primary Key (pk)**: `{compact}#SIGNATURE_KEYS#{state}` -- **Sort Key (sk)**: `{compact}#JURISDICTION#{jurisdiction}#{key_id}` -- **Additional Fields**: - - `publicKey`: PEM-encoded public key content - - `compact`: Compact abbreviation - - `jurisdiction`: Jurisdiction abbreviation - - `keyId`: Key identifier - - `createdAt`: Creation timestamp - -### Deleting SIGNATURE Public Keys - -#### 1. Prerequisites - -Before deleting a SIGNATURE public key, ensure you have: -- Confirmation that the key is no longer in use by the state IT department -- Confirmation of the key id to be deleted -- Understanding of the impact on API access for the compact/state combination - -#### 2. Delete SIGNATURE Public Key Using Interactive Python Script - -```bash -python3 bin/manage_signature_keys.py delete -t -``` - -**Interactive Process:** -The script will: -- Prompt for compact and state -- List all existing keys for the compact/state combination -- Allow you to select the specific key ID to delete -- Require typing "DELETE" to confirm the deletion -- Remove the key from the compact configuration database - -### Key Rotation Best Practices - -#### 1. Planning - -- Coordinate with the State IT representative well in advance -- Plan for zero-downtime deployment - -#### 2. Implementation - -- Create new keys before removing old ones -- Allow both keys to be active during the transition period -- Monitor API access and authentication success rates -- Remove old keys only after confirming new keys are working correctly - -#### 3. Documentation - -- Document key rotation dates and reasons -- Maintain audit trail of all key management activities - -### Security Considerations - -#### 1. Key Storage - -- Public keys are stored in DynamoDB with appropriate access controls -- Private keys should never be stored in CompactConnect systems -- State IT departments are responsible for secure private key management - -#### 2. Access Control - -- Only authorized technical staff should have access to key management resources -- All key management activities should be logged and audited -- Production key creation requires executive director approval - ## Rotating App Client Credentials Unfortunately, AWS Cognito does not support rotating app client credentials for an existing app client. The only way diff --git a/backend/cosmetology-app/bin/generate_mock_license_csv_upload_file.py b/backend/cosmetology-app/bin/generate_mock_license_csv_upload_file.py index f6b639b93..d7554ad41 100755 --- a/backend/cosmetology-app/bin/generate_mock_license_csv_upload_file.py +++ b/backend/cosmetology-app/bin/generate_mock_license_csv_upload_file.py @@ -44,7 +44,6 @@ FIELDS = ( 'ssn', - 'npi', 'licenseNumber', 'licenseType', 'licenseStatus', @@ -146,10 +145,8 @@ def get_mock_license( license_data = { # |Zero padded 4 digit int| 'ssn': f'{ssn_prefix}-{(i // 10_000) % 100:02}-{(i % 10_000):04}', - # Some have NPI, some don't - 'npi': str(randint(1_000_000_000, 9_999_999_999)) if choice([True, False]) else None, - # Some have License number, some don't - 'licenseNumber': generate_mock_license_number() if choice([True, False]) else None, + # licenseNumber is required + 'licenseNumber': generate_mock_license_number(), 'licenseType': choice(LICENSE_TYPES[compact]), 'givenName': name_faker.first_name(), 'middleName': name_faker.first_name(), diff --git a/backend/cosmetology-app/bin/generate_mock_transactions.py b/backend/cosmetology-app/bin/generate_mock_transactions.py deleted file mode 100755 index da43e47a5..000000000 --- a/backend/cosmetology-app/bin/generate_mock_transactions.py +++ /dev/null @@ -1,186 +0,0 @@ -# ruff: noqa: T201 we use print statements for local scripts -#!/usr/bin/env python3 -# Script to generate mock transaction data for test environments -# it spreads the transactions across a range of dates and providers to simulate real-world data. -# -# Run from 'backend/compact-connect' like: -# bin/generate_mock_transactions.py --compact cosm --start_date 01/01/2024 --end_date 03/01/2024 --count 100 - -import argparse -import asyncio -import json -import os -import random -import sys -from datetime import UTC, datetime - -import boto3 -from botocore.config import Config - -# Add the provider data lambda runtime to our pythonpath -provider_data_path = os.path.join('lambdas', 'python', 'common') -sys.path.append(provider_data_path) - -with open('cdk.json') as context_file: - _context = json.load(context_file)['context'] -COMPACTS = _context['compacts'] - - -def parse_date(date_str: str) -> datetime: - """Parse date string in mm/dd/yyyy format to datetime object.""" - return datetime.strptime(date_str, '%m/%d/%Y').replace(tzinfo=UTC) - - -def get_random_timestamp(start_date: datetime, end_date: datetime) -> datetime: - """Generate a random timestamp between start and end dates.""" - time_diff = end_date.timestamp() - start_date.timestamp() - random_time = start_date.timestamp() + random.random() * time_diff - return datetime.fromtimestamp(random_time, tz=UTC) - - -def get_table_by_pattern(tables: list[dict], pattern: str) -> str: - """Find table name that contains the given pattern.""" - matching_tables = [t['TableName'] for t in tables if pattern in t['TableName']] - if not matching_tables: - raise ValueError(f'No table found containing pattern: {pattern}') - return matching_tables[0] - - -async def get_provider_ids(provider_table, compact: str) -> set: - """Get all provider IDs for the given compact.""" - provider_ids = set() - scan_kwargs = {'FilterExpression': boto3.dynamodb.conditions.Key('sk').eq(f'{compact}#PROVIDER')} - - done = False - start_key = None - while not done: - if start_key: - scan_kwargs['ExclusiveStartKey'] = start_key - response = provider_table.scan(**scan_kwargs) - for item in response.get('Items', []): - if 'providerId' in item: - provider_ids.add(item['providerId']) - start_key = response.get('LastEvaluatedKey', None) - done = start_key is None - - print(f'Found {len(provider_ids)} providers for compact {compact}') - return provider_ids - - -def generate_transaction(compact: str, start_date: datetime, end_date: datetime, provider_id: str) -> dict: - """Generate a single mock transaction.""" - settlement_time = get_random_timestamp(start_date, end_date) - submit_time = get_random_timestamp(start_date, settlement_time) - transaction_id = str(random.randint(100000000000, 999999999999)) - batch_id = '15867123' - epoch_timestamp = int(settlement_time.timestamp()) - month_key = settlement_time.strftime('%Y-%m') - - return { - 'pk': f'COMPACT#{compact}#TRANSACTIONS#MONTH#{month_key}', - 'sk': f'COMPACT#{compact}#TIME#{epoch_timestamp}#BATCH#{batch_id}#TX#{transaction_id}', - 'batch': { - 'batchId': batch_id, - 'settlementState': 'settledSuccessfully', - 'settlementTimeLocal': settlement_time.strftime('%Y-%m-%dT%H:%M:%S'), - 'settlementTimeUTC': settlement_time.strftime('%Y-%m-%dT%H:%M:%S.000Z'), - }, - 'compact': compact, - 'licenseeId': provider_id, - 'lineItems': [ - { - 'description': 'Compact Privilege for Ohio', - 'itemId': f'{compact}-oh', - 'name': 'Ohio Compact Privilege', - 'quantity': '1.0', - 'taxable': 'False', - 'unitPrice': '75.0', - }, - { - 'description': 'Compact fee applied for each privilege purchased', - 'itemId': f'{compact}-compact-fee', - 'name': f'{compact.upper()} Compact Fee', - 'quantity': '1.0', - 'taxable': 'False', - 'unitPrice': '10.0', - }, - ], - 'responseCode': '1', - 'settleAmount': '85.0', - 'submitTimeUTC': submit_time.strftime('%Y-%m-%dT%H:%M:%S.000Z'), - 'transactionId': transaction_id, - 'transactionProcessor': 'authorize.net', - 'transactionStatus': 'settledSuccessfully', - 'transactionType': 'authCaptureTransaction', - } - - -async def write_transactions_batch(transaction_table, batch: list[dict]): - """Write a batch of transactions to DynamoDB.""" - try: - with transaction_table.batch_writer() as batch_writer: - for transaction in batch: - batch_writer.put_item(Item=transaction) - # Give other tasks a chance to run - await asyncio.sleep(0) - except Exception as e: - print(f'Error writing batch: {str(e)}') - raise - - -async def main(): - parser = argparse.ArgumentParser(description='Generate mock transaction data') - parser.add_argument('--compact', required=True, choices=COMPACTS, help='The compact to generate transactions for') - parser.add_argument('--start_date', required=True, help='Start date in mm/dd/yyyy format') - parser.add_argument('--end_date', required=True, help='End date in mm/dd/yyyy format') - parser.add_argument('--count', type=int, required=True, help='Number of transactions to generate') - - args = parser.parse_args() - - # Parse dates - start_date = parse_date(args.start_date) - end_date = parse_date(args.end_date) - - # Initialize DynamoDB resource - config = Config(retries=dict(max_attempts=10)) - dynamodb = boto3.resource('dynamodb', config=config) - - # Get list of tables - client = boto3.client('dynamodb') - tables = client.list_tables()['TableNames'] - tables = [{'TableName': t} for t in tables] - - # Get table resources - provider_table = dynamodb.Table(get_table_by_pattern(tables, 'ProviderTable')) - transaction_table = dynamodb.Table(get_table_by_pattern(tables, 'TransactionHistoryTable')) - - # Get provider IDs - provider_ids = await get_provider_ids(provider_table, args.compact) - if not provider_ids: - raise ValueError(f'No providers found for compact {args.compact}') - - # Convert provider_ids to list for random selection - provider_ids = list(provider_ids) - - # Generate transactions - transactions = [] - for _ in range(args.count): - provider_id = provider_ids[_ % len(provider_ids)] - transaction = generate_transaction(args.compact, start_date, end_date, provider_id) - transactions.append(transaction) - - # Split transactions into batches for parallel processing - batch_size = 25 # DynamoDB batch_writer handles up to 25 items - transaction_batches = [transactions[i : i + batch_size] for i in range(0, len(transactions), batch_size)] - - # Create tasks for parallel batch writing - tasks = [write_transactions_batch(transaction_table, batch) for batch in transaction_batches] - - # Execute all batch writes concurrently - await asyncio.gather(*tasks) - - print(f'Successfully wrote {len(transactions)} transactions to the database') - - -if __name__ == '__main__': - asyncio.run(main()) diff --git a/backend/cosmetology-app/cdk.context.beta-example.json b/backend/cosmetology-app/cdk.context.beta-example.json index 1545fe7d9..0e83b9438 100644 --- a/backend/cosmetology-app/cdk.context.beta-example.json +++ b/backend/cosmetology-app/cdk.context.beta-example.json @@ -12,6 +12,7 @@ "account_id": "222233334444", "region": "us-east-1", "domain_name": "beta.cosmetology.compactconnect.org", + "ui_domain_name_override": "app.beta.compactconnect.org", "backup_enabled": false, "notifications": { "ses_operations_support_email": "justin@example.com", diff --git a/backend/cosmetology-app/cdk.context.prod-example.json b/backend/cosmetology-app/cdk.context.prod-example.json index 5a9815d67..a76f0a814 100644 --- a/backend/cosmetology-app/cdk.context.prod-example.json +++ b/backend/cosmetology-app/cdk.context.prod-example.json @@ -12,6 +12,7 @@ "account_id": "000011112222", "region": "us-east-1", "domain_name": "cosmetology.compactconnect.org", + "ui_domain_name_override": "app.compactconnect.org", "backup_enabled": true, "notifications": { "ses_operations_support_email": "justin@example.com", diff --git a/backend/cosmetology-app/cdk.context.sandbox-example.json b/backend/cosmetology-app/cdk.context.sandbox-example.json index 158141c44..6d62cd01a 100644 --- a/backend/cosmetology-app/cdk.context.sandbox-example.json +++ b/backend/cosmetology-app/cdk.context.sandbox-example.json @@ -12,6 +12,7 @@ "account_id": "111122223333", "region": "us-east-1", "domain_name": "justin.cosmetology.compactconnect.org", + "ui_domain_name_override": "app.justin.compactconnect.org", "backup_enabled": false, "allow_local_ui": true, "security_profile": "VULNERABLE", diff --git a/backend/cosmetology-app/cdk.context.test-example.json b/backend/cosmetology-app/cdk.context.test-example.json index da02730bd..949d336b8 100644 --- a/backend/cosmetology-app/cdk.context.test-example.json +++ b/backend/cosmetology-app/cdk.context.test-example.json @@ -12,6 +12,7 @@ "account_id": "111122223333", "region": "us-east-1", "domain_name": "test.cosmetology.compactconnect.org", + "ui_domain_name_override": "app.test.compactconnect.org", "backup_enabled": true, "allow_local_ui": true, "notifications": { diff --git a/backend/cosmetology-app/common_constructs/cc_api.py b/backend/cosmetology-app/common_constructs/cc_api.py index 4ad409a61..7a503d758 100644 --- a/backend/cosmetology-app/common_constructs/cc_api.py +++ b/backend/cosmetology-app/common_constructs/cc_api.py @@ -17,6 +17,7 @@ MethodLoggingLevel, ResponseType, RestApi, + SecurityPolicy, StageOptions, ) from aws_cdk.aws_certificatemanager import Certificate, CertificateValidation @@ -89,7 +90,14 @@ def __init__( validation=CertificateValidation.from_dns(hosted_zone=stack.hosted_zone), subject_alternative_names=[stack.hosted_zone.zone_name], ) - domain_kwargs = {'domain_name': DomainNameOptions(certificate=certificate, domain_name=domain_name)} + domain_kwargs = { + 'domain_name': DomainNameOptions( + certificate=certificate, + domain_name=domain_name, + # this resource defaults to TLS_1_2, but we will explicitly set this anyway + security_policy=SecurityPolicy.TLS_1_2, + ) + } access_log_group = LogGroup(scope, 'ApiAccessLogGroup', retention=RetentionDays.ONE_MONTH) NagSuppressions.add_resource_suppressions( @@ -103,10 +111,14 @@ def __init__( ], ) + # Disable the default execute-api endpoint for all pipeline environments so traffic must use the custom domain. + disable_execute_api_endpoint = environment_name in ('test', 'beta', 'prod') + super().__init__( scope, construct_id, cloud_watch_role=True, + disable_execute_api_endpoint=disable_execute_api_endpoint, deploy_options=StageOptions( # NOTE: If we are ever updating our pipeline architecture which requires a change to the pipeline stack # name, the domain base path mapping for the API will fail to deploy unless we change the name of the diff --git a/backend/cosmetology-app/disaster_recovery/LICENSE_UPLOAD_ROLLBACK.md b/backend/cosmetology-app/disaster_recovery/LICENSE_UPLOAD_ROLLBACK.md index 02d4f5fe0..6786dbd8a 100644 --- a/backend/cosmetology-app/disaster_recovery/LICENSE_UPLOAD_ROLLBACK.md +++ b/backend/cosmetology-app/disaster_recovery/LICENSE_UPLOAD_ROLLBACK.md @@ -183,7 +183,6 @@ The system queries each month in the time range and collects unique provider IDs For each successfully reverted provider, the system publishes events to the EventBridge event bus: - `license.reverted` events for each reverted license -- `privilege.reverted` events for each reactivated privilege These events include: - The rollback reason diff --git a/backend/cosmetology-app/docs/README.md b/backend/cosmetology-app/docs/README.md index 273706fa2..c4fa0d772 100644 --- a/backend/cosmetology-app/docs/README.md +++ b/backend/cosmetology-app/docs/README.md @@ -49,7 +49,7 @@ leave the field entirely empty. If some of your licenses are missing a required | homeAddressPostalCode* | Postal/ZIP code of provider's home address | String (5-7 chars) | 12345 | | homeAddressState* | State/province of provider's home address | String (max 100 chars) | IL | | homeAddressStreet1* | First line of provider's street address | String (max 100 chars) | 123 Main St | -| licenseNumber** | License number | String (max 100 chars) | OT12345 | +| licenseNumber* | License number | String (max 100 chars) | OT12345 | | licenseType* | Type of professional license. Types you provide must be associated with the compact you are uploading for. | One of: `cosmetologist`, `esthetician` | cosmetologist | | ssn* | Social Security Number | Format: XXX-XX-XXXX | 123-45-6789 | | licenseStatus* | Current status of the license. "active" means they are allowed to practice their profession. *Note: licenses will automatically be displayed as `inactive` after their date of expiration, even if the last upload still showed them as `active`.* | One of: `active`, `inactive` | active | @@ -60,7 +60,6 @@ leave the field entirely empty. If some of your licenses are missing a required | middleName | Provider's middle name (optional) | String (max 100 chars) | Robert | | phoneNumber | Provider's phone number (optional) | [ITU-T E.164 format](https://www.itu.int/rec/T-REC-E.164-201011-I/en) (must include country code, no spaces or dashes) | +12025550123 | | suffix | Provider's name suffix (optional) | String (max 100 chars) | Jr. | -** This field is required by compact commission rule, however, to avoid making a breaking change for states that are already integrated, the API does not enforce this rule. States are responsible for enforcing the compact rule themselves. #### Example CSV ```csv dateOfIssuance,licenseNumber,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,licenseStatus,licenseStatusName,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal @@ -121,12 +120,6 @@ The following license fields are publicly visible through CompactConnect's publi - Phone numbers - Date of birth -### What if I want to receive CompactConnect data back to my state IT system? -State IT systems can retrieve data via the CompactConnect State-API, which provides HTTP endpoints for querying the -system for compact privilege data for a respective state. See [Retrieving Data from CompactConnect](it_staff_onboarding_instructions.md#retrieving-data-from-compactconnect) - -Note that CompactConnect has additional security requirements for automatic API retrieval of data from the system. Please see the [Client Signature Authentication documentation](./client_signature_auth.md) for detailed information about implementing request signing for secure data retrieval. - ## Open API Specification [Back to top](#compact-connect---technical-user-guide) diff --git a/backend/cosmetology-app/docs/api-specification/latest-oas30.json b/backend/cosmetology-app/docs/api-specification/latest-oas30.json index ea26e0cb5..a1d0e94a3 100644 --- a/backend/cosmetology-app/docs/api-specification/latest-oas30.json +++ b/backend/cosmetology-app/docs/api-specification/latest-oas30.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "StateApi", - "version": "2026-01-20T16:27:14Z" + "version": "2026-02-16T16:29:16Z" }, "servers": [ { @@ -10,498 +10,6 @@ } ], "paths": { - "/v1/compacts/{compact}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers/query": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboStateqo3EcVdnHD77" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboStateJhKaDwYhmzd5" - } - } - } - } - }, - "security": [ - { - "SandboxStateAPIStackStateApiStateAuthAuthorizer7F83A6D3": [ - "cosm/readGeneral" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers/{providerId}": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboStateXI4qTdgg4Ox6" - } - } - } - } - }, - "security": [ - { - "SandboxStateAPIStackStateApiStateAuthAuthorizer7F83A6D3": [ - "cosm/readGeneral" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses/bulk-upload": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboStateFikQUwwQv2cd" - } - } - } - } - }, - "security": [ - { - "SandboxStateAPIStackStateApiStateAuthAuthorizer7F83A6D3": [ - "cosm/write", - "az/cosm.write" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses": { "post": { "parameters": [ @@ -534,7 +42,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboStatemf7yMQ9QaLNG" + "$ref": "#/components/schemas/SandboStateLyTv9k6jxTZK" } } }, @@ -546,7 +54,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboStateaZm5y6RXXcvn" + "$ref": "#/components/schemas/SandboStateDr9HAH5blAWv" } } } @@ -556,7 +64,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboStatei8JT7BNRmyzL" + "$ref": "#/components/schemas/SandboStatexWoPOpVnKEI9" } } } @@ -570,125 +78,29 @@ ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses/bulk-upload": { + "get": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } }, { - "name": "jurisdiction", + "name": "compact", "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions": { - "options": { - "parameters": [ + }, { - "name": "compact", + "name": "jurisdiction", "in": "path", "required": true, "schema": { @@ -697,39 +109,55 @@ } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboStateMW9czXwgYk67" } } - }, - "content": {} + } } - } + }, + "security": [ + { + "SandboxStateAPIStackStateApiStateAuthAuthorizer7F83A6D3": [ + "cosm/write", + "az/cosm.write" + ] + } + ] } } }, "components": { "schemas": { - "SandboStatemf7yMQ9QaLNG": { + "SandboStateDr9HAH5blAWv": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message indicating success or failure" + }, + "errors": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "array", + "description": "List of error messages for a field", + "items": { + "type": "string" + } + }, + "description": "Errors for a specific record" + }, + "description": "Validation errors by record index" + } + } + }, + "SandboStateLyTv9k6jxTZK": { "maxItems": 100, "type": "array", "items": { @@ -744,6 +172,7 @@ "homeAddressPostalCode", "homeAddressState", "homeAddressStreet1", + "licenseNumber", "licenseStatus", "licenseType", "ssn" @@ -755,10 +184,6 @@ "minLength": 1, "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "homeAddressPostalCode": { "maxLength": 7, "minLength": 5, @@ -804,8 +229,8 @@ "licenseType": { "type": "string", "enum": [ - "cosmetology", - "esthetics" + "cosmetologist", + "esthetician" ] }, "emailAddress": { @@ -869,230 +294,7 @@ "additionalProperties": false } }, - "SandboStateJhKaDwYhmzd5": { - "required": [ - "pagination", - "providers" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "sorting": { - "type": "object", - "properties": { - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" - }, - "providers": { - "maxItems": 100, - "type": "array", - "items": { - "required": [ - "birthMonthDay", - "compact", - "compactEligibility", - "dateOfExpiration", - "dateOfUpdate", - "familyName", - "givenName", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "licenseJurisdiction", - "licenseStatus", - "privilegeJurisdictions", - "providerId", - "type" - ], - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ] - }, - "compact": { - "type": "string", - "enum": [ - "cosm" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "birthMonthDay": { - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string", - "format": "date" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - } - } - } - } - } - }, - "SandboStateaZm5y6RXXcvn": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Message indicating success or failure" - }, - "errors": { - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "type": "array", - "description": "List of error messages for a field", - "items": { - "type": "string" - } - }, - "description": "Errors for a specific record" - }, - "description": "Validation errors by record index" - } - } - }, - "SandboStateFikQUwwQv2cd": { + "SandboStateMW9czXwgYk67": { "required": [ "upload" ], @@ -1118,288 +320,7 @@ } } }, - "SandboStateqo3EcVdnHD77": { - "required": [ - "query" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - }, - "additionalProperties": false - }, - "query": { - "required": [ - "endDateTime", - "startDateTime" - ], - "type": "object", - "properties": { - "startDateTime": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$", - "type": "string", - "format": "date-time" - }, - "endDateTime": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$", - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "description": "The query parameters" - }, - "sorting": { - "type": "object", - "properties": { - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" - } - }, - "additionalProperties": false - }, - "SandboStateXI4qTdgg4Ox6": { - "required": [ - "privileges", - "providerUIUrl" - ], - "type": "object", - "properties": { - "privileges": { - "type": "array", - "items": { - "required": [ - "compact", - "compactEligibility", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "familyName", - "givenName", - "jurisdiction", - "licenseJurisdiction", - "licenseStatus", - "licenseType", - "privilegeId", - "providerId", - "status", - "type" - ], - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ] - }, - "compact": { - "type": "string", - "enum": [ - "cosm" - ] - }, - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ] - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "statePrivilege" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseType": { - "type": "string", - "enum": [ - "cosmetology", - "esthetics" - ] - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" - }, - "privilegeId": { - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfUpdate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - } - }, - "providerUIUrl": { - "type": "string", - "description": "URL to the provider UI page", - "format": "uri" - } - } - }, - "SandboStatei8JT7BNRmyzL": { + "SandboStatexWoPOpVnKEI9": { "required": [ "message" ], diff --git a/backend/cosmetology-app/docs/client_signature_auth.md b/backend/cosmetology-app/docs/client_signature_auth.md deleted file mode 100644 index 86928cb10..000000000 --- a/backend/cosmetology-app/docs/client_signature_auth.md +++ /dev/null @@ -1,187 +0,0 @@ -# Client Signature Authentication - -## Overview - -The CompactConnect State-API implements a dual-authentication system for API access to sensitive licensure data. In -*addition* to OAuth2 client credentials authentication via Cognito User Pools, clients must also implement request -signing using ECDSA public/private key pairs. - -## Purpose and Justification - -### Why Dual Authentication? - -The licensure data shared through CompactConnect contains highly sensitive personal information including: -- Last four of Social Security Numbers -- Personal addresses and contact information -- Professional license details -- Disciplinary actions - -A single authentication mechanism creates a single point of failure. If OAuth2 credentials are compromised, an attacker -could potentially access protected data. The signature authentication layer provides: - -- **Defense in depth**: Two independent authentication mechanisms must be compromised -- **Request integrity**: Each request is cryptographically signed, preventing tampering -- **Non-repudiation**: Only the holder of the private key could have signed the request -- **Replay attack prevention**: Timestamps and nonces prevent request reuse - -## Authentication Modes - -CompactConnect supports two authentication modes, which are implemented *in addition to the normal Oauth2 -authentication, which is required on all State-API endpoints*: - -1. **Required Signature Authentication**: Endpoints that always require valid signatures. If a state has no configured - public key, they will not have access to these endpoints. -2. **Optional Signature Authentication**: Endpoints that require signatures only when a public key is configured for - the compact/state combination (i.e. the POST license endpoint). If a state has no configured public key, they will - still have access to this endpoint, however, once the state configures a public key, they will be required to include - signatures to requests on these endpoints as well. - -### Required Signature Authentication - -State-API endpoints that read data _out_ of CompactConnect require signature authentication as an additional auth -factor. For endpoints that require signature authentication: - -- A public key will need to be configured for each compact/state combination, before these endpoints will be accessible. -- All requests to these endpoints will be denied unless a public key is configured and valid signatures are present - on the requests. - -### Optional Signature Authentication - -Other API endpoints in the State-API support optional signature authentication. For these endpoints: - -- If no public key is configured for the compact/state combination, requests may proceed without signature validation -- If any public key is configured for the client's compact/state, signature authentication is enforced -- Optional signature auth allows for gradual rollout of signature authentication across different compacts and states -- Note that, when you are preparing to adopt signature authentication, you will want to start signing requests with - _optional_ signatures _before_ you configure the public key with CompactConnect, because signature auth will be - enforced as soon as the public key is configured in the system. - -## Implementation of Signature Authentication - -Signature authentication will require creating a private/public key pair, implementing the signing algorithm for all -requests to CompactConnect, then configuring the public key with CompactConnect, so that signatures can be validated. - -### 1. Key Pair Generation - -Generate an ECDSA key pair using the P-256 curve for your client (you may need to install openssl first, depending on -your operating system): - -```bash -# Generate private key -openssl ecparam -genkey -name prime256v1 -noout -out client_private_key.pem - -# Extract public key -openssl ec -in client_private_key.pem -pubout -out client_public_key.pub -``` - -#### Private Key Security -- Store private keys securely (HSM, key vault, encrypted storage) -- Implement key rotation procedures -- Never log or transmit private keys - -**Important**: You will provide only the **public** key to CompactConnect during client registration. - -### 2. Request Signing Process - -For each API request, you must: - -1. **Generate required values**: - - Timestamp (ISO 8601 format): `2024-01-15T10:30:00Z` or `2024-01-15T10:30:00+00:00` - - Nonce (unique UUID4 or random string): `550e8400-e29b-41d4-a716-446655440000` - -2. **Create signature string** by joining these components with newlines (`\n`): - ```text - HTTP_METHOD - REQUEST_PATH - SORTED_QUERY_PARAMETERS - TIMESTAMP - NONCE - KEY_ID - ``` - -#### Canonical query string - -- Percent-encode keys and values per RFC 3986 (space as %20, not +; do not encode unreserved characters). -- Sort first by key, then by value, using byte-order of the percent-encoded strings. -- Join as `key=value` pairs with `&` as the separator. -- If there are no query parameters, include an empty line (only `\n` in the signature string). - -3. **Sign the string** using ECDSA with SHA-256. The signature format MUST be ASN.1 DER (most libraries produce DER by - default). -4. **Base64-encode** the DER signature (do not hex-encode). -5. **Add required headers** to the request. - -#### Required Headers - -Every request to protected endpoints must include: - -```http -Authorization: Bearer -X-Algorithm: ECDSA-SHA256 -X-Timestamp: -X-Nonce: -X-Key-Id: -X-Signature: -``` - -### 3. Configure the Public Key with CompactConnect - -To configure your public key, contact the CompactConnect technical support team with: -- The **state and compact** this public key is for -- The **environment** the key is for -- The **public key data** in PEM format (it should start with `-----BEGIN PUBLIC KEY-----`). Sharing this key via normal - communications channels is fine, since the _public_ key is not sensitive. *Do not share your **private** key with - CompactConnect staff*. -- The **key id** you will reference this key with in your requests (and when coordinating future key rotations). - -Once CompactConnect staff enter your public key into the system, signature validation will be enabled (and enforced -for *both required and optional signature auth endpoints*). - -### Example Signature Implementation - -We maintain an example implementation, which we use to test and validate our own authentication mechanism -[here](../lambdas/python/common/common_test/sign_request.py) and some example HTTP request data in a text file -[here](./signature_auth_examples.txt). You can use this as a reference for your -own implementation. - -### Key Management - -CompactConnect supports key rotation to allow clients to update their signing keys without downtime: - -- **Key IDs**: Each public key is associated with a unique key ID that clients must include in the `X-Key-Id` header -- **Multiple Keys**: Clients can have multiple active keys simultaneously during rotation periods -- **Key Rollover**: When rotating keys, clients can continue using old keys while new keys are being validated - -## Troubleshooting - -### Timestamp Validation -- The API compares the signed timestamp header with server time as part of signature validation. -- Requests must be made within 1 minute of the signed timestamp, when received by the API. -- Use UTC time in ISO 8601 format (`2024-01-15T10:30:00Z` or `2024-01-15T10:30:00+00:00`). -- Ensure your system clock is synchronized and accurate as clock skew can cause validation to fail. - -### Nonce Management -- Generate a unique nonce for each request -- UUIDs or cryptographically random strings are recommended -- **Never reuse nonces** - each nonce must be unique across requests -- Nonces can only contain alphanumeric characters (a-z, A-Z, 0-9) and hyphens (-) -- Nonces cannot be longer than 256 characters - -### Other Common Issues -- **Query parameter ordering**: Parameters must be sorted alphabetically by key -- **Newline characters**: Use `\n` (LF) not `\r\n` (CRLF) in signature string -- **Encoding**: Use UTF-8 encoding for all string operations -- **Signature format**: Ensure proper ECDSA with SHA-256 -- **Required headers**: Ensure you are including all required headers - -### Testing Your Implementation - -Use the provided beta environment to validate your signature implementation before production deployment. - -## Support - -For technical assistance with signature authentication implementation, contact the CompactConnect technical support team -with: -- Your client ID -- Sample signature strings (without private keys) -- Error messages or response codes diff --git a/backend/cosmetology-app/docs/design/README.md b/backend/cosmetology-app/docs/design/README.md index 5bef14f17..d08ee0d98 100644 --- a/backend/cosmetology-app/docs/design/README.md +++ b/backend/cosmetology-app/docs/design/README.md @@ -431,14 +431,6 @@ POST /v1/compacts/{compact}/providers/search Returns provider records matching the query. Response includes the full provider document with licenses and privileges. -#### Privilege CSV Export -``` -POST /v1/compacts/{compact}/privileges/export -``` - -Returns flattened privilege records. This endpoint queries the same provider index but extracts and flattens -privileges, combining privilege data with license data to provide a denormalized list of objects which are then exported to a CSV file for downloading. - ### Document Indexing #### Initial Population / Re-indexing diff --git a/backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json b/backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json index f3b93c610..919d40efc 100644 --- a/backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json +++ b/backend/cosmetology-app/docs/internal/api-specification/latest-oas30.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "LicenseApi", - "version": "2026-01-07T22:16:05Z" + "version": "2026-02-16T17:28:47Z" }, "servers": [ { @@ -10,8 +10,8 @@ } ], "paths": { - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { - "post": { + "/v1/compacts/{compact}": { + "get": { "parameters": [ { "name": "Authorization", @@ -28,25 +28,40 @@ "schema": { "type": "string" } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenqza9dE2UnEU1" + } + } } - }, + } + }, + "security": [ { - "name": "jurisdiction", - "in": "path", + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/readGeneral" + ] + } + ] + }, + "put": { + "parameters": [ + { + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } }, { - "name": "licenseType", + "name": "compact", "in": "path", "required": true, "schema": { @@ -58,7 +73,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen9VEPwkhZhKem" + "$ref": "#/components/schemas/SandboLicenABWdja2fpGrf" } } }, @@ -70,7 +85,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -79,62 +94,67 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" + "cosm/admin", + "az/cosm.admin" ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/jurisdictions": { + "get": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } }, { - "name": "providerId", + "name": "compact", "in": "path", "required": true, "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenmIdUa45H3yV3" + } + } + } + } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/readGeneral" + ] + } + ] + } + }, + "/v1/compacts/{compact}/jurisdictions/{jurisdiction}": { + "get": { + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } }, { - "name": "jurisdiction", + "name": "compact", "in": "path", "required": true, "schema": { @@ -142,7 +162,7 @@ } }, { - "name": "licenseType", + "name": "jurisdiction", "in": "path", "required": true, "schema": { @@ -151,42 +171,57 @@ } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenVzNQc01P5WRr" } } - }, - "content": {} + } } - } - } - }, - "/v1/provider-users/initiateRecovery": { - "post": { + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/readGeneral" + ] + } + ] + }, + "put": { + "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenJt67zBFIGGPS" + "$ref": "#/components/schemas/SandboLicenAZYObIVGD6AE" } } }, @@ -198,47 +233,33 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } } - } - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } - } + ] } }, - "/v1/compacts/{compact}/providers/{providerId}/licenses": { - "options": { + "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses/bulk-upload": { + "get": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -248,7 +269,7 @@ } }, { - "name": "providerId", + "name": "jurisdiction", "in": "path", "required": true, "schema": { @@ -257,37 +278,29 @@ } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicendw4AUcBtsulc" } } - }, - "content": {} + } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/write", + "az/cosm.write" + ] + } + ] } }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses/bulk-upload": { - "get": { + "/v1/compacts/{compact}/providers/query": { + "post": { "parameters": [ { "name": "Authorization", @@ -304,23 +317,25 @@ "schema": { "type": "string" } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenXTP0xFadIMHH" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenjfa9vGqBChQd" + "$ref": "#/components/schemas/SandboLicenQjARLDBg8yqp" } } } @@ -329,44 +344,23 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/write", - "al/aslp.write", - "ak/aslp.write", - "ar/aslp.write", - "co/aslp.write", - "de/aslp.write", - "ky/aslp.write", - "la/aslp.write", - "me/aslp.write", - "md/aslp.write", - "mn/aslp.write", - "ms/aslp.write", - "mo/aslp.write", - "ne/aslp.write", - "oh/aslp.write", - "octp/write", - "al/octp.write", - "ar/octp.write", - "ky/octp.write", - "la/octp.write", - "ms/octp.write", - "ne/octp.write", - "oh/octp.write", - "coun/write", - "al/coun.write", - "ar/coun.write", - "fl/coun.write", - "ga/coun.write", - "ky/coun.write", - "ne/coun.write", - "oh/coun.write", - "ut/coun.write" + "cosm/readGeneral" ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/providers/{providerId}": { + "get": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -376,7 +370,7 @@ } }, { - "name": "jurisdiction", + "name": "providerId", "in": "path", "required": true, "schema": { @@ -385,70 +379,37 @@ } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicengNz6BQmOzv9N" } } - }, - "content": {} + } } - } - } - }, - "/v1/public/jurisdictions": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/readGeneral" + ] } - } + ] } }, - "/v1/public/compacts/{compact}/jurisdictions": { - "get": { + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { + "post": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -456,68 +417,26 @@ "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenLnkOp52kvwLg" - } - } + }, + { + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" } - } - } - }, - "options": { - "parameters": [ + }, { - "name": "compact", + "name": "jurisdiction", "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/purchases/privileges": { - "post": { - "parameters": [ + }, { - "name": "Authorization", - "in": "header", + "name": "licenseType", + "in": "path", "required": true, "schema": { "type": "string" @@ -528,7 +447,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicendZ26vvlCKCbW" + "$ref": "#/components/schemas/SandboLicen905yAh4VGw9c" } } }, @@ -540,7 +459,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenVHq6DcHpnqp0" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -548,43 +467,16 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } } }, - "/v1/provider-users/me": { - "get": { + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { + "patch": { "parameters": [ { "name": "Authorization", @@ -593,15 +485,65 @@ "schema": { "type": "string" } + }, + { + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "encumbranceId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenY31VDvsVuYXJ" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenJlHz6gimzgVV" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -609,43 +551,16 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } } }, - "/v1/compacts/{compact}/providers/{providerId}/ssn": { - "get": { + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { + "post": { "parameters": [ { "name": "Authorization", @@ -670,15 +585,41 @@ "schema": { "type": "string" } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicennxxmBbSCJcel" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenx7ouhX772atw" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -687,128 +628,58 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readSSN", - "al/aslp.readSSN", - "ak/aslp.readSSN", - "ar/aslp.readSSN", - "co/aslp.readSSN", - "de/aslp.readSSN", - "ky/aslp.readSSN", - "la/aslp.readSSN", - "me/aslp.readSSN", - "md/aslp.readSSN", - "mn/aslp.readSSN", - "ms/aslp.readSSN", - "mo/aslp.readSSN", - "ne/aslp.readSSN", - "oh/aslp.readSSN", - "octp/readSSN", - "al/octp.readSSN", - "ar/octp.readSSN", - "ky/octp.readSSN", - "la/octp.readSSN", - "ms/octp.readSSN", - "ne/octp.readSSN", - "oh/octp.readSSN", - "coun/readSSN", - "al/coun.readSSN", - "ar/coun.readSSN", - "fl/coun.readSSN", - "ga/coun.readSSN", - "ky/coun.readSSN", - "ne/coun.readSSN", - "oh/coun.readSSN", - "ut/coun.readSSN" + "cosm/admin", + "az/cosm.admin" ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { + "patch": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } }, { - "name": "providerId", + "name": "compact", "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/query": { - "post": { - "parameters": [ + }, { - "name": "compact", + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "investigationId", "in": "path", "required": true, "schema": { @@ -820,7 +691,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenyuZlweRzUTEW" + "$ref": "#/components/schemas/SandboLicenTiUhKamH8kWG" } } }, @@ -832,57 +703,33 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenf1YSNMeYKlGD" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } } - } - }, - "options": { + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] + } + ] + } + }, + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { + "post": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}": { - "options": { - "parameters": [ + }, { "name": "compact", "in": "path", @@ -916,49 +763,51 @@ } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicentzGiICqenA1I" + } + } + }, + "required": true + }, "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } - }, - "content": {} + } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] + } + ] } }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType": { - "options": { + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { + "patch": { "parameters": [ { - "name": "compact", - "in": "path", + "name": "Authorization", + "in": "header", "required": true, "schema": { "type": "string" } }, { - "name": "providerId", + "name": "compact", "in": "path", "required": true, "schema": { @@ -966,57 +815,23 @@ } }, { - "name": "jurisdiction", + "name": "providerId", "in": "path", "required": true, "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/attestations/{attestationId}": { - "get": { - "parameters": [ + }, { - "name": "Authorization", - "in": "header", + "name": "jurisdiction", + "in": "path", "required": true, "schema": { "type": "string" } }, { - "name": "compact", + "name": "licenseType", "in": "path", "required": true, "schema": { @@ -1024,7 +839,7 @@ } }, { - "name": "attestationId", + "name": "encumbranceId", "in": "path", "required": true, "schema": { @@ -1032,13 +847,23 @@ } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenTdkUw59snX0Q" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen0TA7m9cBJLpz" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -1046,12 +871,25 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { + "post": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1061,7 +899,23 @@ } }, { - "name": "attestationId", + "name": "providerId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "jurisdiction", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "licenseType", "in": "path", "required": true, "schema": { @@ -1069,55 +923,23 @@ } } ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenBxCMDP0yz42u" } - }, - "content": {} - } - } - } - }, - "/v1/purchases/privileges/options": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" } - } - ], + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenaiHAuDR162f2" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -1125,44 +947,25 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } } }, - "/v1/public/compacts/{compact}": { - "options": { + "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { + "patch": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -1170,44 +973,10 @@ "schema": { "type": "string" } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { - "get": { - "parameters": [ + }, { - "name": "Authorization", - "in": "header", + "name": "providerId", + "in": "path", "required": true, "schema": { "type": "string" @@ -1228,15 +997,33 @@ "schema": { "type": "string" } + }, + { + "name": "investigationId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenuVRFe60q6S7D" + } + } + }, + "required": true + }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicen0XcLtKpj7p28" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -1244,60 +1031,15 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } ] - }, - "options": { - "parameters": [ - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } } }, - "/v1/compacts/{compact}/providers/{providerId}": { + "/v1/compacts/{compact}/providers/{providerId}/ssn": { "get": { "parameters": [ { @@ -1331,7 +1073,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenJlHz6gimzgVV" + "$ref": "#/components/schemas/SandboLicenGJ2Vrxb2wvlG" } } } @@ -1340,14 +1082,15 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" + "cosm/readSSN", + "az/cosm.readSSN" ] } ] - }, - "options": { + } + }, + "/v1/compacts/{compact}/staff-users": { + "get": { "parameters": [ { "name": "compact", @@ -1356,107 +1099,41 @@ "schema": { "type": "string" } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], "responses": { - "204": { - "description": "204 response", + "200": { + "description": "200 response", "headers": { "Access-Control-Allow-Origin": { "schema": { "type": "string" } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } } }, - "content": {} - } - } - } - }, - "/v1/public/jurisdictions/live": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenEPhGnRpsTFzc" + "$ref": "#/components/schemas/SandboLicengSyqbaS5PyUI" } } } } - } - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } - } - } - }, - "/v1/provider-users/me/home-jurisdiction": { - "put": { + ] + }, + "post": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", "required": true, "schema": { "type": "string" @@ -1467,7 +1144,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenJVUAEniGDz2F" + "$ref": "#/components/schemas/SandboLicenMQ8w3yNRTuci" } } }, @@ -1476,10 +1153,17 @@ "responses": { "200": { "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicenjPULg4TeFeF1" } } } @@ -1487,43 +1171,16 @@ }, "security": [ { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] } - } + ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges": { - "options": { + "/v1/compacts/{compact}/staff-users/{userId}": { + "get": { "parameters": [ { "name": "compact", @@ -1534,7 +1191,7 @@ } }, { - "name": "providerId", + "name": "userId", "in": "path", "required": true, "schema": { @@ -1543,104 +1200,55 @@ } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "404": { + "description": "404 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/verifyRecovery": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen5bneP2wVdz6l" - } } }, - "required": true - }, - "responses": { "200": { "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - } - }, - "options": { - "responses": { - "204": { - "description": "204 response", "headers": { "Access-Control-Allow-Origin": { "schema": { "type": "string" } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + } + }, + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenjPULg4TeFeF1" } } - }, - "content": {} + } } - } - } - }, - "/v1/compacts/{compact}/providers/query": { - "post": { + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] + } + ] + }, + "delete": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", "required": true, "schema": { "type": "string" } }, { - "name": "compact", + "name": "userId", "in": "path", "required": true, "schema": { @@ -1648,23 +1256,23 @@ } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenyuZlweRzUTEW" + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" + } } } }, - "required": true - }, - "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenDTjDt3roB2dM" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } } } @@ -1673,14 +1281,13 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" + "cosm/admin", + "az/cosm.admin" ] } ] }, - "options": { + "patch": { "parameters": [ { "name": "compact", @@ -1689,49 +1296,68 @@ "schema": { "type": "string" } + }, + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicenTD8eYiNU2b3J" + } + } + }, + "required": true + }, "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { + "404": { + "description": "404 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } - }, - "Vary": { + } + } + }, + "200": { + "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { "schema": { "type": "string" } - }, - "Access-Control-Allow-Headers": { + } + }, + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenjPULg4TeFeF1" } } - }, - "content": {} + } } - } + }, + "security": [ + { + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] + } + ] } }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation": { + "/v1/compacts/{compact}/staff-users/{userId}/reinvite": { "post": { "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, { "name": "compact", "in": "path", @@ -1741,23 +1367,51 @@ } }, { - "name": "providerId", + "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" + } + ], + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" + } + } } }, + "200": { + "description": "200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" + } + } + } + } + }, + "security": [ { - "name": "licenseType", + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "cosm/admin", + "az/cosm.admin" + ] + } + ] + } + }, + "/v1/flags/{flagId}/check": { + "post": { + "parameters": [ + { + "name": "flagId", "in": "path", "required": true, "schema": { @@ -1769,7 +1423,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenKbxrVseriPZY" + "$ref": "#/components/schemas/SandboLicenxdosUrB9q9s6" } } }, @@ -1781,52 +1435,16 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicenVVd6s0tk4Osi" } } } } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { + } + } + }, + "/v1/public/compacts/{compact}/jurisdictions": { + "get": { "parameters": [ { "name": "compact", @@ -1835,68 +1453,28 @@ "schema": { "type": "string" } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenmIdUa45H3yV3" } } - }, - "content": {} + } } } } }, - "/v1/provider-users/me/military-affiliation": { + "/v1/public/compacts/{compact}/providers/query": { "post": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", "required": true, "schema": { "type": "string" @@ -1907,7 +1485,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenoBgekYzIk0Uy" + "$ref": "#/components/schemas/SandboLicenXTP0xFadIMHH" } } }, @@ -1919,229 +1497,147 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenSFWw1LC3Pl63" + "$ref": "#/components/schemas/SandboLicenMplIbIWayHWa" } } } } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } } - }, - "patch": { + } + }, + "/v1/public/compacts/{compact}/providers/{providerId}": { + "get": { "parameters": [ { - "name": "Authorization", - "in": "header", + "name": "compact", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "providerId", + "in": "path", "required": true, "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenztG3aZP1J9M3" - } - } - }, - "required": true - }, "responses": { "200": { "description": "200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicen7muYupt1OyB4" } } } } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] + } } }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { - "options": { + "/v1/public/jurisdictions/live": { + "get": { "parameters": [ { "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "encumbranceId", - "in": "path", - "required": true, + "in": "query", "schema": { "type": "string" } } ], "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { + "200": { + "description": "200 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenPUwIzAMSlfRh" } - }, - "Access-Control-Allow-Methods": { + } + } + } + } + } + }, + "/v1/staff-users/me": { + "get": { + "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" } - }, - "Vary": { + } + } + }, + "200": { + "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { "schema": { "type": "string" } - }, - "Access-Control-Allow-Headers": { + } + }, + "content": { + "application/json": { "schema": { - "type": "string" + "$ref": "#/components/schemas/SandboLicenjPULg4TeFeF1" } } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" } - }, + } + }, + "security": [ { - "name": "encumbranceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } + "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ + "profile" + ] } - ], + ] + }, + "patch": { "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenFqy1VQNhsjvi" + "$ref": "#/components/schemas/SandboLicenMcATit6vegoa" } } }, "required": true }, "responses": { + "404": { + "description": "404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboLicentIcqTWOgUIGm" + } + } + } + }, "200": { "description": "200 response", + "headers": { + "Access-Control-Allow-Origin": { + "schema": { + "type": "string" + } + } + }, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" + "$ref": "#/components/schemas/SandboLicenjPULg4TeFeF1" } } } @@ -2150,7497 +1646,85 @@ "security": [ { "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" + "profile" ] } ] } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/flags/{flagId}/check": { - "post": { - "parameters": [ - { - "name": "flagId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenpl2UadOfjgNJ" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen113ZVRfz1NW8" - } - } - } - } - } + } + }, + "components": { + "schemas": { + "SandboLicenBxCMDP0yz42u": { + "type": "object", + "properties": {} }, - "options": { - "parameters": [ - { - "name": "flagId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/flags/{flagId}": { - "options": { - "parameters": [ - { - "name": "flagId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } + "SandboLicen7muYupt1OyB4": { + "required": [ + "compact", + "dateOfUpdate", + "familyName", + "givenName", + "licenseJurisdiction", + "providerId", + "type" ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenv4avK8ok4P45" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "investigationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "investigationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicennuxBDueZ6Trv" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - } - }, - "/": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen0XcLtKpj7p28" - } - } - } - } - } - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/deactivate": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenG4CMW3C4eZNN" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/jurisdiction/{jurisdiction}": { - "options": { - "parameters": [ - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/investigation/{investigationId}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "investigationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "investigationId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenUONaWXXjz4K6" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - } - }, - "/v1/compacts/{compact}/staff-users/{userId}/reinvite": { - "post": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/staff-users": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenxXSBED7FZUQN" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "post": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenPfWQpg9qdCvm" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2qPPtuQWh8hv" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/staff-users": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/staff-users/me": { - "get": { - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2qPPtuQWh8hv" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "profile" - ] - } - ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicendz0gMqUAh7qN" - } - } - }, - "required": true - }, - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2qPPtuQWh8hv" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "profile" - ] - } - ] - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/staff-users/{userId}": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2qPPtuQWh8hv" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "delete": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicengqvhPQz7ywKX" - } - } - }, - "required": true - }, - "responses": { - "404": { - "description": "404 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - }, - "200": { - "description": "200 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen2qPPtuQWh8hv" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - } - }, - "/v1/compacts/{compact}/jurisdictions/{jurisdiction}": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLiceniWnENMKPoAG6" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" - ] - } - ] - }, - "put": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenJwiQTQPNiltz" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/purchases": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/email": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenudbEF4n02FXU" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - } - }, - "/v1/compacts/{compact}/credentials": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/jurisdiction": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/jurisdictions": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenLnkOp52kvwLg" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenRy0ye4gsVdf9" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" - ] - } - ] - }, - "put": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenZx6a73xGIfzu" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/flags": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}": { - "options": { - "parameters": [ - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/registration": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenmftfBe6vPEA8" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - } - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenmKXS1L8tLsGF" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1": { - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history": { - "get": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen0XcLtKpj7p28" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/readGeneral", - "octp/readGeneral", - "coun/readGeneral" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}": { - "get": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicennv4iZSNKxEXN" - } - } - } - } - } - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType": { - "options": { - "parameters": [ - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/provider-users/me/email/verify": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenlu9HVFJNEZQz" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": [] - } - ] - }, - "options": { - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/attestations": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/licenses/jurisdiction/{jurisdiction}/licenseType/{licenseType}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/credentials/payment-processor": { - "post": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenmMTXPta5fldR" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicen9dv1jZzfaVo8" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - }, - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - }, - "/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/encumbrance/{encumbranceId}": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "encumbranceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - }, - "patch": { - "parameters": [ - { - "name": "Authorization", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "providerId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "jurisdiction", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "licenseType", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "encumbranceId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenSo5EafP3rZhM" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SandboLicenTMQQKAeKTKQR" - } - } - } - } - }, - "security": [ - { - "SandboxAPIStackLicenseApiStaffUsersPoolAuthorizer14A84A9B": [ - "aslp/admin", - "al/aslp.admin", - "ak/aslp.admin", - "ar/aslp.admin", - "co/aslp.admin", - "de/aslp.admin", - "ky/aslp.admin", - "la/aslp.admin", - "me/aslp.admin", - "md/aslp.admin", - "mn/aslp.admin", - "ms/aslp.admin", - "mo/aslp.admin", - "ne/aslp.admin", - "oh/aslp.admin", - "octp/admin", - "al/octp.admin", - "ar/octp.admin", - "ky/octp.admin", - "la/octp.admin", - "ms/octp.admin", - "ne/octp.admin", - "oh/octp.admin", - "coun/admin", - "al/coun.admin", - "ar/coun.admin", - "fl/coun.admin", - "ga/coun.admin", - "ky/coun.admin", - "ne/coun.admin", - "oh/coun.admin", - "ut/coun.admin" - ] - } - ] - } - }, - "/v1/compacts/{compact}/providers": { - "options": { - "parameters": [ - { - "name": "compact", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "204 response", - "headers": { - "Access-Control-Allow-Origin": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Methods": { - "schema": { - "type": "string" - } - }, - "Vary": { - "schema": { - "type": "string" - } - }, - "Access-Control-Allow-Headers": { - "schema": { - "type": "string" - } - } - }, - "content": {} - } - } - } - } - }, - "components": { - "schemas": { - "SandboLicenudbEF4n02FXU": { - "required": [ - "newEmailAddress" - ], - "type": "object", - "properties": { - "newEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "description": "The new email address to set for the provider", - "format": "email" - } - }, - "additionalProperties": false - }, - "SandboLicenmKXS1L8tLsGF": { - "required": [ - "clinicalPrivilegeActionCategories", - "encumbranceEffectiveDate", - "encumbranceType" - ], - "type": "object", - "properties": { - "encumbranceEffectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date of the encumbrance", - "format": "date" - }, - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "encumbranceType": { - "type": "string", - "description": "The type of encumbrance", - "enum": [ - "fine", - "reprimand", - "required supervision", - "completion of continuing education", - "public reprimand", - "probation", - "injunctive action", - "suspension", - "revocation", - "denial", - "surrender of license", - "modification of previous action-extension", - "modification of previous action-reduction", - "other monitoring", - "other adjudicated action not listed" - ] - } - }, - "additionalProperties": false, - "description": "Encumbrance data to create" - }, - "SandboLicennuxBDueZ6Trv": { - "required": [ - "action" - ], - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "close" - ] - }, - "encumbrance": { - "required": [ - "clinicalPrivilegeActionCategories", - "encumbranceEffectiveDate", - "encumbranceType" - ], - "type": "object", - "properties": { - "encumbranceEffectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date of the encumbrance", - "format": "date" - }, - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "encumbranceType": { - "type": "string", - "description": "The type of encumbrance", - "enum": [ - "fine", - "reprimand", - "required supervision", - "completion of continuing education", - "public reprimand", - "probation", - "injunctive action", - "suspension", - "revocation", - "denial", - "surrender of license", - "modification of previous action-extension", - "modification of previous action-reduction", - "other monitoring", - "other adjudicated action not listed" - ] - } - }, - "additionalProperties": false, - "description": "Encumbrance data to create" - } - } - }, - "SandboLicen2qPPtuQWh8hv": { - "required": [ - "attributes", - "permissions", - "status", - "userId" - ], - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "write": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } - }, - "additionalProperties": false - } - }, - "attributes": { - "required": [ - "email", - "familyName", - "givenName" - ], - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string" - } - }, - "additionalProperties": false - }, - "userId": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - }, - "additionalProperties": false - }, - "SandboLicendz0gMqUAh7qN": { - "type": "object", - "properties": { - "attributes": { - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "SandboLicenztG3aZP1J9M3": { - "required": [ - "status" - ], - "type": "object", - "properties": { - "status": { - "type": "string", - "description": "The status to set the military affiliation to.", - "enum": [ - "inactive" - ] - } - }, - "additionalProperties": false - }, - "SandboLicenKbxrVseriPZY": { - "type": "object", - "properties": {} - }, - "SandboLicenpl2UadOfjgNJ": { - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "userId": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "Optional user ID for feature flag evaluation" - }, - "customAttributes": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Optional custom attributes for feature flag evaluation" - } - }, - "additionalProperties": false, - "description": "Optional context for feature flag evaluation" - } - }, - "additionalProperties": false - }, - "SandboLicen9dv1jZzfaVo8": { - "required": [ - "message" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the request" - } - } - }, - "SandboLicenSFWw1LC3Pl63": { - "required": [ - "affiliationType", - "dateOfUpdate", - "dateOfUpload", - "documentUploadFields", - "status" - ], - "type": "object", - "properties": { - "dateOfUpload": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The date the document was uploaded", - "format": "date" - }, - "affiliationType": { - "type": "string", - "description": "The type of military affiliation", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "fileNames": { - "type": "array", - "description": "List of military affiliation file names", - "items": { - "type": "string", - "description": "The name of the file being uploaded" - } - }, - "dateOfUpdate": { - "type": "string", - "description": "The date the document was last updated", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "The status of the military affiliation" - }, - "documentUploadFields": { - "type": "array", - "description": "The fields used to upload documents", - "items": { - "type": "object", - "properties": { - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "The form fields used to upload the document" - }, - "url": { - "type": "string", - "description": "The url to upload the document to" - } - }, - "description": "The fields used to upload a specific document" - } - } - } - }, - "SandboLicenf1YSNMeYKlGD": { - "required": [ - "pagination", - "providers" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "query": { - "type": "object", - "properties": { - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string", - "description": "Internal UUID for the provider" - }, - "jurisdiction": { - "type": "string", - "description": "Filter for providers with privilege/license in a jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "givenName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a given name" - }, - "familyName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a family name" - } - } - }, - "sorting": { - "required": [ - "key" - ], - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "The key to sort results by", - "enum": [ - "dateOfUpdate", - "familyName" - ] - }, - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" - }, - "providers": { - "maxLength": 100, - "type": "array", - "items": { - "required": [ - "compact", - "familyName", - "givenName", - "licenseJurisdiction", - "privilegeJurisdictions", - "providerId", - "type" - ], - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { - "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "SandboLicen0TA7m9cBJLpz": { - "type": "object", - "properties": { - "dateCreated": { - "type": "string", - "format": "date-time" - }, - "attestationId": { - "type": "string" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "text": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "attestation" - ] - }, - "locale": { - "type": "string" - }, - "version": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - }, - "SandboLicenEPhGnRpsTFzc": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - } - }, - "SandboLicenx7ouhX772atw": { - "required": [ - "ssn" - ], - "type": "object", - "properties": { - "ssn": { - "pattern": "^[0-9]{3}-[0-9]{2}-[0-9]{4}$", - "type": "string", - "description": "The provider's social security number" - } - } - }, - "SandboLicenFqy1VQNhsjvi": { - "required": [ - "effectiveLiftDate" - ], - "type": "object", - "properties": { - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date when the encumbrance will be lifted", - "format": "date" - } - }, - "additionalProperties": false - }, - "SandboLicenJt67zBFIGGPS": { - "required": [ - "compact", - "dob", - "familyName", - "givenName", - "jurisdiction", - "licenseType", - "partialSocial", - "password", - "recaptchaToken", - "username" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string", - "description": "Type of license", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "password": { - "maxLength": 256, - "minLength": 12, - "type": "string", - "description": "Provider's current password" - }, - "compact": { - "type": "string", - "description": "Compact abbreviation", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "dob": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "Date of birth in YYYY-MM-DD format", - "format": "date" - }, - "jurisdiction": { - "type": "string", - "description": "Two-letter jurisdiction code", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "givenName": { - "maxLength": 200, - "minLength": 1, - "type": "string", - "description": "Provider's given name" - }, - "familyName": { - "maxLength": 200, - "minLength": 1, - "type": "string", - "description": "Provider's family name" - }, - "recaptchaToken": { - "minLength": 1, - "type": "string", - "description": "ReCAPTCHA token for verification" - }, - "partialSocial": { - "pattern": "^[0-9]{4}$", - "type": "string", - "description": "Last 4 digits of SSN" - }, - "username": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "description": "Provider's email address (username)", - "format": "email" - } - }, - "additionalProperties": false - }, - "SandboLicenZx6a73xGIfzu": { - "required": [ - "compactAdverseActionsNotificationEmails", - "compactCommissionFee", - "compactOperationsTeamEmails", - "compactSummaryReportNotificationEmails", - "configuredStates", - "licenseeRegistrationEnabled" - ], - "type": "object", - "properties": { - "configuredStates": { - "type": "array", - "description": "List of states that have submitted configurations and their live status", - "items": { - "required": [ - "isLive", - "postalAbbreviation" - ], - "type": "object", - "properties": { - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "isLive": { - "type": "boolean", - "description": "Whether the state is live and available for registrations." - } - }, - "additionalProperties": false - } - }, - "compactCommissionFee": { - "required": [ - "feeAmount", - "feeType" - ], - "type": "object", - "properties": { - "feeAmount": { - "minimum": 0, - "type": "number" - }, - "feeType": { - "type": "string", - "enum": [ - "FLAT_RATE" - ] - } - }, - "additionalProperties": false - }, - "compactSummaryReportNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "compactAdverseActionsNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" - }, - "transactionFeeConfiguration": { - "type": "object", - "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "minimum": 0, - "type": "number", - "description": "The amount to charge per privilege purchased" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "compactOperationsTeamEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - } - }, - "additionalProperties": false - }, - "SandboLiceniWnENMKPoAG6": { - "required": [ - "compact", - "jurisdictionAdverseActionsNotificationEmails", - "jurisdictionName", - "jurisdictionOperationsTeamEmails", - "jurisdictionSummaryReportNotificationEmails", - "jurisprudenceRequirements", - "licenseeRegistrationEnabled", - "postalAbbreviation", - "privilegeFees" - ], - "type": "object", - "properties": { - "privilegeFees": { - "type": "array", - "description": "The fees for the privileges by license type", - "items": { - "required": [ - "amount", - "licenseTypeAbbreviation" - ], - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "militaryRate": { - "description": "Optional military rate for the privilege fee.", - "oneOf": [ - { - "minimum": 0, - "type": "number" - }, - null - ] - }, - "licenseTypeAbbreviation": { - "type": "string", - "enum": [ - "aud", - "slp", - "ot", - "ota", - "lpc" - ] - } - } - } - }, - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction" - }, - "jurisdictionAdverseActionsNotificationEmails": { - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "jurisdictionOperationsTeamEmails": { - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "compact": { - "type": "string", - "description": "The compact this jurisdiction configuration belongs to", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" - } - } - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" - }, - "jurisdictionName": { - "type": "string", - "description": "The name of the jurisdiction" - }, - "jurisdictionSummaryReportNotificationEmails": { - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } - } - } - }, - "SandboLicenJwiQTQPNiltz": { - "required": [ - "jurisdictionAdverseActionsNotificationEmails", - "jurisdictionOperationsTeamEmails", - "jurisdictionSummaryReportNotificationEmails", - "jurisprudenceRequirements", - "licenseeRegistrationEnabled", - "privilegeFees" - ], - "type": "object", - "properties": { - "privilegeFees": { - "type": "array", - "description": "The fees for the privileges by license type", - "items": { - "required": [ - "amount", - "licenseTypeAbbreviation" - ], - "type": "object", - "properties": { - "amount": { - "minimum": 0, - "type": "number" - }, - "militaryRate": { - "description": "Optional military rate for the privilege fee.", - "oneOf": [ - { - "minimum": 0, - "type": "number" - }, - null - ] - }, - "licenseTypeAbbreviation": { - "type": "string", - "enum": [ - "aud", - "slp", - "ot", - "ota", - "lpc" - ] - } - }, - "additionalProperties": false - } - }, - "jurisdictionAdverseActionsNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "jurisdictionOperationsTeamEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" - } - }, - "additionalProperties": false - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" - }, - "jurisdictionSummaryReportNotificationEmails": { - "maxItems": 10, - "minItems": 1, - "uniqueItems": true, - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } - } - }, - "additionalProperties": false - }, - "SandboLicenTMQQKAeKTKQR": { - "required": [ - "message" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the request" - } - } - }, - "SandboLicenlu9HVFJNEZQz": { - "required": [ - "verificationCode" - ], - "type": "object", - "properties": { - "verificationCode": { - "pattern": "^[0-9]{4}$", - "type": "string", - "description": "4-digit verification code" - } - }, - "additionalProperties": false - }, - "SandboLicen0XcLtKpj7p28": { - "required": [ - "compact", - "events", - "jurisdiction", - "licenseType", - "privilegeId", - "providerId" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "events": { - "type": "array", - "items": { - "required": [ - "createDate", - "dateOfUpdate", - "effectiveDate", - "type", - "updateType" - ], - "type": "object", - "properties": { - "note": { - "type": "string" - }, - "npdbCategories": { - "type": "array", - "description": "The categories of clinical privilege action for encumbrance events", - "items": { - "type": "string" - } - }, - "type": { - "type": "string", - "enum": [ - "privilegeUpdate" - ] - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "effectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "updateType": { - "type": "string", - "enum": [ - "deactivation", - "expiration", - "issuance", - "other", - "renewal", - "encumbrance", - "homeJurisdictionChange", - "registration", - "lifting_encumbrance", - "licenseDeactivation", - "emailChange" - ] - }, - "createDate": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "SandboLicenVHq6DcHpnqp0": { - "required": [ - "transactionId" - ], - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "A message about the transaction" - }, - "transactionId": { - "type": "string", - "description": "The transaction id for the purchase" - } - } - }, - "SandboLicen9VEPwkhZhKem": { - "type": "object", - "properties": {} - }, - "SandboLicenJlHz6gimzgVV": { - "required": [ - "birthMonthDay", - "compact", - "dateOfExpiration", - "dateOfUpdate", - "familyName", - "givenName", - "licenseJurisdiction", - "licenses", - "militaryAffiliations", - "privilegeJurisdictions", - "privileges", - "providerId", - "type" - ], - "type": "object", - "properties": { - "privileges": { - "type": "array", - "items": { - "required": [ - "administratorSetStatus", - "attestations", - "compact", - "compactTransactionId", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "history", - "jurisdiction", - "licenseJurisdiction", - "licenseType", - "privilegeId", - "providerId", - "status", - "type" - ], - "type": "object", - "properties": { - "investigationStatus": { - "type": "string", - "description": "Status indicating if the privilege is under investigation", - "enum": [ - "underInvestigation" - ] - }, - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, - "investigations": { - "type": "array", - "items": { - "required": [ - "compact", - "creationDate", - "dateOfUpdate", - "investigationId", - "jurisdiction", - "licenseType", - "providerId", - "submittingUser", - "type" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string" - }, - "investigationId": { - "type": "string" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "submittingUser": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "investigation" - ] - }, - "creationDate": { - "type": "string", - "format": "date-time" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - } - }, - "history": { - "type": "array", - "items": { - "required": [ - "compact", - "dateOfUpdate", - "jurisdiction", - "previous", - "type", - "updateType" - ], - "type": "object", - "properties": { - "removedValues": { - "type": "array", - "description": "List of field names that were present in the previous record but removed in the update", - "items": { - "type": "string" - } - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "previous": { - "required": [ - "administratorSetStatus", - "attestations", - "compactTransactionId", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "licenseJurisdiction", - "privilegeId" - ], - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "compactTransactionId": { - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "updatedValues": { - "type": "object", - "properties": { - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "compactTransactionId": { - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilegeUpdate" - ] - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "updateType": { - "type": "string", - "enum": [ - "deactivation", - "expiration", - "issuance", - "other", - "renewal", - "encumbrance", - "homeJurisdictionChange", - "registration", - "lifting_encumbrance", - "licenseDeactivation", - "emailChange" - ] - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "privilege" - ] - }, - "compactTransactionId": { - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "administratorSetStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "privilegeId": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "adverseActions": { - "type": "array", - "items": { - "required": [ - "actionAgainst", - "adverseActionId", - "compact", - "creationDate", - "dateOfUpdate", - "effectiveStartDate", - "encumbranceType", - "jurisdiction", - "licenseType", - "licenseTypeAbbreviation", - "providerId", - "type" - ], - "type": "object", - "properties": { - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "licenseTypeAbbreviation": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] - }, - "creationDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "actionAgainst": { - "type": "string" - }, - "licenseType": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "effectiveStartDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "adverseActionId": { - "type": "string" - }, - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "encumbranceType": { - "type": "string" - }, - "liftingUser": { - "type": "string" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - } - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - } - }, - "licenseJurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { - "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "licenses": { + "type": "object", + "properties": { + "privileges": { "type": "array", "items": { "required": [ + "administratorSetStatus", "compact", - "compactEligibility", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", "dateOfUpdate", - "familyName", - "givenName", - "history", - "homeAddressCity", - "homeAddressPostalCode", - "homeAddressState", - "homeAddressStreet1", "jurisdiction", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "licenseStatus", + "licenseJurisdiction", "licenseType", - "middleName", + "privilegeId", "providerId", + "status", "type" ], "type": "object", - "properties": { - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "investigations": { - "type": "array", - "items": { - "required": [ - "compact", - "creationDate", - "dateOfUpdate", - "investigationId", - "jurisdiction", - "licenseType", - "providerId", - "submittingUser", - "type" - ], - "type": "object", - "properties": { - "licenseType": { - "type": "string" - }, - "investigationId": { - "type": "string" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "submittingUser": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "investigation" - ] - }, - "creationDate": { - "type": "string", - "format": "date-time" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - } - }, - "type": { - "type": "string", - "enum": [ - "license-home" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "licenseType": { - "type": "string", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "dateOfRenewal": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "investigationStatus": { - "type": "string", - "description": "Status indicating if the license is under investigation", - "enum": [ - "underInvestigation" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "compactEligibility": { + "properties": { + "licenseJurisdiction": { "type": "string", "enum": [ - "eligible", - "ineligible" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "jurisdictionUploadedCompactEligibility": { + "compact": { "type": "string", "enum": [ - "eligible", - "ineligible" + "cosm" ] }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { + "jurisdiction": { "type": "string", "enum": [ - "active", - "inactive" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, "history": { @@ -9650,357 +1734,158 @@ "compact", "dateOfUpdate", "jurisdiction", + "licenseType", "previous", + "providerId", "type", - "updateType" + "updateType", + "updatedValues" ], "type": "object", "properties": { - "removedValues": { - "type": "array", - "description": "List of field names that were present in the previous record but removed in the update", - "items": { - "type": "string" - } - }, "licenseType": { "type": "string", "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" + "cosmetologist", + "esthetician" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "previous": { "required": [ + "administratorSetStatus", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", - "familyName", - "givenName", - "homeAddressCity", - "homeAddressPostalCode", - "homeAddressState", - "homeAddressStreet1", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "middleName" + "dateOfUpdate", + "licenseJurisdiction", + "privilegeId" ], "type": "object", "properties": { - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { + "administratorSetStatus": { "type": "string", "enum": [ - "eligible", - "ineligible" + "active", + "inactive" ] }, - "dateOfBirth": { + "dateOfExpiration": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "jurisdictionUploadedLicenseStatus": { + "licenseJurisdiction": { "type": "string", "enum": [ - "active", - "inactive" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, - "suffix": { - "maxLength": 100, - "minLength": 1, + "privilegeId": { "type": "string" }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { + "dateOfRenewal": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "dateOfRenewal": { + "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "licenseStatus": { + "dateOfUpdate": { "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "format": "date-time" } } }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "updatedValues": { "type": "object", "properties": { - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfIssuance": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "emailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] }, - "dateOfRenewal": { + "dateOfExpiration": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "licenseStatus": { + "licenseJurisdiction": { "type": "string", "enum": [ - "active", - "inactive" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, + "privilegeId": { "type": "string" }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" + "dateOfUpdate": { + "type": "string", + "format": "date-time" } } }, "type": { "type": "string", "enum": [ - "licenseUpdate" + "privilegeUpdate" ] }, "dateOfUpdate": { @@ -10016,22 +1901,496 @@ "other", "renewal", "encumbrance", - "homeJurisdictionChange", - "registration", "lifting_encumbrance", - "licenseDeactivation", - "emailChange" + "licenseDeactivation" + ] + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "privilege" + ] + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseType": { + "type": "string", + "enum": [ + "cosmetologist", + "esthetician" + ] + }, + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "adverseActions": { + "type": "array", + "items": { + "required": [ + "actionAgainst", + "adverseActionId", + "compact", + "creationDate", + "dateOfUpdate", + "effectiveStartDate", + "jurisdiction", + "licenseType", + "licenseTypeAbbreviation", + "providerId", + "type" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "effectiveStartDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "type": { + "type": "string", + "enum": [ + "adverseAction" ] + }, + "creationDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "actionAgainst": { + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } } } }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + } + } + } + }, + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "provider" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + }, + "SandboLicenxdosUrB9q9s6": { + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "userId": { + "maxLength": 100, + "minLength": 1, + "type": "string", + "description": "Optional user ID for feature flag evaluation" + }, + "customAttributes": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Optional custom attributes for feature flag evaluation" + } + }, + "additionalProperties": false, + "description": "Optional context for feature flag evaluation" + } + }, + "additionalProperties": false + }, + "SandboLicenXTP0xFadIMHH": { + "required": [ + "query" + ], + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + }, + "additionalProperties": false + }, + "query": { + "type": "object", + "properties": { + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string", + "description": "Internal UUID for the provider" + }, + "jurisdiction": { + "type": "string", + "description": "Filter for providers with license in a jurisdiction", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "givenName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a given name (familyName is required if givenName is provided)" + }, + "familyName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a family name" + } + }, + "additionalProperties": false, + "description": "The query parameters" + }, + "sorting": { + "required": [ + "key" + ], + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The key to sort results by", + "enum": [ + "dateOfUpdate", + "familyName" + ] + }, + "direction": { + "type": "string", + "description": "Direction to sort results by", + "enum": [ + "ascending", + "descending" + ] + } + }, + "description": "How to sort results" + } + }, + "additionalProperties": false + }, + "SandboLicendw4AUcBtsulc": { + "required": [ + "upload" + ], + "type": "object", + "properties": { + "upload": { + "required": [ + "fields", + "url" + ], + "type": "object", + "properties": { + "fields": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "url": { + "type": "string" + } + } + } + } + }, + "SandboLicenQjARLDBg8yqp": { + "required": [ + "pagination", + "providers" + ], + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "prevLastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + } + }, + "sorting": { + "required": [ + "key" + ], + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The key to sort results by", + "enum": [ + "dateOfUpdate", + "familyName" + ] + }, + "direction": { + "type": "string", + "description": "Direction to sort results by", + "enum": [ + "ascending", + "descending" + ] + } + }, + "description": "How to sort results" + }, + "providers": { + "maxLength": 100, + "type": "array", + "items": { + "required": [ + "birthMonthDay", + "compact", + "compactEligibility", + "dateOfExpiration", + "dateOfUpdate", + "familyName", + "givenName", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "licenseJurisdiction", + "licenseStatus", + "providerId", + "type" + ], + "type": "object", + "properties": { + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "type": { + "type": "string", + "enum": [ + "provider" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, "ssnLastFour": { "pattern": "^[0-9]{4}$", "type": "string" }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, "licenseStatus": { @@ -10041,157 +2400,20 @@ "inactive" ] }, - "middleName": { + "familyName": { "maxLength": 100, "minLength": 1, "type": "string" }, - "licenseStatusName": { + "middleName": { "maxLength": 100, "minLength": 1, "type": "string" }, - "adverseActions": { - "type": "array", - "items": { - "required": [ - "actionAgainst", - "adverseActionId", - "compact", - "creationDate", - "dateOfUpdate", - "effectiveStartDate", - "encumbranceType", - "jurisdiction", - "licenseType", - "licenseTypeAbbreviation", - "providerId", - "type" - ], - "type": "object", - "properties": { - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "jurisdiction": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "licenseTypeAbbreviation": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] - }, - "creationDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "actionAgainst": { - "type": "string" - }, - "licenseType": { - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "effectiveStartDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "adverseActionId": { - "type": "string" - }, - "effectiveLiftDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "encumbranceType": { - "type": "string" - }, - "liftingUser": { - "type": "string" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - } + "birthMonthDay": { + "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", + "type": "string", + "format": "date" }, "dateOfUpdate": { "type": "string", @@ -10199,88 +2421,486 @@ } } } + } + } + }, + "SandboLicenVzNQc01P5WRr": { + "required": [ + "compact", + "jurisdictionAdverseActionsNotificationEmails", + "jurisdictionName", + "jurisdictionOperationsTeamEmails", + "jurisdictionSummaryReportNotificationEmails", + "jurisprudenceRequirements", + "licenseeRegistrationEnabled", + "postalAbbreviation" + ], + "type": "object", + "properties": { + "postalAbbreviation": { + "type": "string", + "description": "The postal abbreviation of the jurisdiction" + }, + "jurisdictionAdverseActionsNotificationEmails": { + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "jurisdictionOperationsTeamEmails": { + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compact": { + "type": "string", + "description": "The compact this jurisdiction configuration belongs to", + "enum": [ + "cosm" + ] + }, + "jurisprudenceRequirements": { + "required": [ + "required" + ], + "type": "object", + "properties": { + "linkToDocumentation": { + "description": "Optional link to jurisprudence documentation", + "oneOf": [ + { + "type": "string" + }, + null + ] + }, + "required": { + "type": "boolean", + "description": "Whether jurisprudence requirements exist" + } + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "jurisdictionName": { + "type": "string", + "description": "The name of the jurisdiction" + }, + "jurisdictionSummaryReportNotificationEmails": { + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + } + } + }, + "SandboLicenMcATit6vegoa": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "SandboLicenuVRFe60q6S7D": { + "required": [ + "action" + ], + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "close" + ] + }, + "encumbrance": { + "required": [ + "clinicalPrivilegeActionCategories", + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object", + "properties": { + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date of the encumbrance", + "format": "date" + }, + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" + } + } + }, + "SandboLicenTdkUw59snX0Q": { + "required": [ + "effectiveLiftDate" + ], + "type": "object", + "properties": { + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date when the encumbrance will be lifted", + "format": "date" + } + }, + "additionalProperties": false + }, + "SandboLicenTiUhKamH8kWG": { + "required": [ + "action" + ], + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "close" + ] + }, + "encumbrance": { + "required": [ + "clinicalPrivilegeActionCategories", + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object", + "properties": { + "encumbranceEffectiveDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "description": "The effective date of the encumbrance", + "format": "date" + }, + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" + } + } + }, + "SandboLicenAZYObIVGD6AE": { + "required": [ + "jurisdictionAdverseActionsNotificationEmails", + "jurisdictionOperationsTeamEmails", + "jurisdictionSummaryReportNotificationEmails", + "jurisprudenceRequirements", + "licenseeRegistrationEnabled" + ], + "type": "object", + "properties": { + "jurisdictionAdverseActionsNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" + "jurisdictionOperationsTeamEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } }, - "dateOfExpiration": { + "jurisprudenceRequirements": { + "required": [ + "required" + ], + "type": "object", + "properties": { + "linkToDocumentation": { + "description": "Optional link to jurisprudence documentation", + "oneOf": [ + { + "type": "string" + }, + null + ] + }, + "required": { + "type": "boolean", + "description": "Whether jurisprudence requirements exist" + } + }, + "additionalProperties": false + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "jurisdictionSummaryReportNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + } + }, + "additionalProperties": false + }, + "SandboLicen905yAh4VGw9c": { + "required": [ + "clinicalPrivilegeActionCategories", + "encumbranceEffectiveDate", + "encumbranceType" + ], + "type": "object", + "properties": { + "encumbranceEffectiveDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", + "description": "The effective date of the encumbrance", "format": "date" }, - "militaryAffiliations": { + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } + }, + "encumbranceType": { + "type": "string", + "description": "The type of encumbrance", + "enum": [ + "fine", + "reprimand", + "required supervision", + "completion of continuing education", + "public reprimand", + "probation", + "injunctive action", + "suspension", + "revocation", + "denial", + "surrender of license", + "modification of previous action-extension", + "modification of previous action-reduction", + "other monitoring", + "other adjudicated action not listed" + ] + } + }, + "additionalProperties": false, + "description": "Encumbrance data to create" + }, + "SandboLicenVVd6s0tk4Osi": { + "required": [ + "enabled" + ], + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the feature flag is enabled" + } + } + }, + "SandboLicengSyqbaS5PyUI": { + "type": "object", + "properties": { + "pagination": { + "type": "object", + "properties": { + "prevLastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "lastKey": { + "maxLength": 1024, + "minLength": 1, + "type": "object" + }, + "pageSize": { + "maximum": 100, + "minimum": 5, + "type": "integer" + } + } + }, + "users": { "type": "array", "items": { "required": [ - "affiliationType", - "compact", - "dateOfUpdate", - "dateOfUpload", - "fileNames", - "providerId", + "attributes", + "permissions", "status", - "type" + "userId" ], "type": "object", "properties": { - "dateOfUpload": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "downloadLinks": { - "type": "array", - "items": { - "required": [ - "fileName", - "url" - ], + "permissions": { + "type": "object", + "additionalProperties": { "type": "object", "properties": { - "fileName": { - "type": "string" + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } }, - "url": { - "type": "string" + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } } - } + }, + "additionalProperties": false } }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "affiliationType": { - "type": "string", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "type": { - "type": "string", - "enum": [ - "militaryAffiliation" - ] - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" + "attributes": { + "required": [ + "email", + "familyName", + "givenName" + ], + "type": "object", + "properties": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string" + } + }, + "additionalProperties": false }, - "fileNames": { - "type": "array", - "items": { - "type": "string" - } + "userId": { + "type": "string" }, "status": { "type": "string", @@ -10289,212 +2909,122 @@ "inactive" ] } - } + }, + "additionalProperties": false } - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "birthMonthDay": { - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string", - "format": "date" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" } - } + }, + "additionalProperties": false }, - "SandboLicenjfa9vGqBChQd": { + "SandboLicenGJ2Vrxb2wvlG": { "required": [ - "upload" + "ssn" ], "type": "object", "properties": { - "upload": { - "required": [ - "fields", - "url" - ], - "type": "object", - "properties": { - "fields": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "url": { - "type": "string" - } - } + "ssn": { + "pattern": "^[0-9]{3}-[0-9]{2}-[0-9]{4}$", + "type": "string", + "description": "The provider's social security number" } } }, - "SandboLicenyuZlweRzUTEW": { + "SandboLicenMQ8w3yNRTuci": { "required": [ - "query" + "attributes", + "permissions" ], "type": "object", "properties": { - "pagination": { - "type": "object", - "properties": { - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - }, - "additionalProperties": false - }, - "query": { + "permissions": { "type": "object", - "properties": { - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string", - "description": "Internal UUID for the provider" - }, - "jurisdiction": { - "type": "string", - "description": "Filter for providers with privilege/license in a jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "givenName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a given name (familyName is required if givenName is provided)" + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } + }, + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } }, - "familyName": { - "maxLength": 100, - "type": "string", - "description": "Filter for providers with a family name" - } - }, - "additionalProperties": false, - "description": "The query parameters" + "additionalProperties": false + } }, - "sorting": { + "attributes": { "required": [ - "key" + "email", + "familyName", + "givenName" ], "type": "object", "properties": { - "key": { - "type": "string", - "description": "The key to sort results by", - "enum": [ - "dateOfUpdate", - "familyName" - ] + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" }, - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "email": { + "maxLength": 100, + "minLength": 5, + "type": "string" } }, - "description": "How to sort results" + "additionalProperties": false } }, "additionalProperties": false }, - "SandboLicennv4iZSNKxEXN": { + "SandboLicengNz6BQmOzv9N": { "required": [ + "birthMonthDay", "compact", + "dateOfExpiration", "dateOfUpdate", "familyName", "givenName", "licenseJurisdiction", - "privilegeJurisdictions", + "licenses", + "privileges", "providerId", "type" ], @@ -10506,10 +3036,12 @@ "required": [ "administratorSetStatus", "compact", + "compactTransactionId", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", "dateOfUpdate", + "history", "jurisdiction", "licenseJurisdiction", "licenseType", @@ -10520,167 +3052,153 @@ ], "type": "object", "properties": { + "investigationStatus": { + "type": "string", + "description": "Status indicating if the privilege is under investigation", + "enum": [ + "underInvestigation" + ] + }, "licenseJurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, - "history": { + "investigations": { "type": "array", "items": { "required": [ "compact", + "creationDate", "dateOfUpdate", + "investigationId", "jurisdiction", "licenseType", - "previous", "providerId", + "submittingUser", + "type" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "submittingUser": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "investigation" + ] + }, + "creationDate": { + "type": "string", + "format": "date-time" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + } + }, + "history": { + "type": "array", + "items": { + "required": [ + "compact", + "dateOfUpdate", + "jurisdiction", + "previous", "type", - "updateType", - "updatedValues" + "updateType" ], "type": "object", "properties": { + "removedValues": { + "type": "array", + "description": "List of field names that were present in the previous record but removed in the update", + "items": { + "type": "string" + } + }, "licenseType": { "type": "string", "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" + "cosmetologist", + "esthetician" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "previous": { "required": [ "administratorSetStatus", + "compactTransactionId", "dateOfExpiration", "dateOfIssuance", "dateOfRenewal", @@ -10690,85 +3208,76 @@ ], "type": "object", "properties": { - "administratorSetStatus": { + "licenseJurisdiction": { "type": "string", "enum": [ - "active", - "inactive" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "compact": { "type": "string", - "format": "date" + "enum": [ + "cosm" + ] }, - "licenseJurisdiction": { + "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, - "privilegeId": { + "type": { + "type": "string", + "enum": [ + "privilege" + ] + }, + "compactTransactionId": { "type": "string" }, - "dateOfRenewal": { + "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "dateOfIssuance": { + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" @@ -10776,153 +3285,104 @@ "dateOfUpdate": { "type": "string", "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } } }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "updatedValues": { "type": "object", "properties": { - "administratorSetStatus": { + "licenseJurisdiction": { "type": "string", "enum": [ - "active", - "inactive" + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" ] }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "compact": { "type": "string", - "format": "date" + "enum": [ + "cosm" + ] }, - "licenseJurisdiction": { + "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, - "privilegeId": { + "type": { + "type": "string", + "enum": [ + "privilege" + ] + }, + "compactTransactionId": { "type": "string" }, - "dateOfRenewal": { + "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "dateOfIssuance": { + "administratorSetStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "privilegeId": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" @@ -10930,6 +3390,13 @@ "dateOfUpdate": { "type": "string", "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } } }, @@ -10952,11 +3419,8 @@ "other", "renewal", "encumbrance", - "homeJurisdictionChange", - "registration", "lifting_encumbrance", - "licenseDeactivation", - "emailChange" + "licenseDeactivation" ] } } @@ -10968,6 +3432,9 @@ "privilege" ] }, + "compactTransactionId": { + "type": "string" + }, "dateOfIssuance": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", @@ -10976,11 +3443,8 @@ "licenseType": { "type": "string", "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" + "cosmetologist", + "esthetician" ] }, "administratorSetStatus": { @@ -11017,6 +3481,7 @@ "creationDate", "dateOfUpdate", "effectiveStartDate", + "encumbranceType", "jurisdiction", "licenseType", "licenseTypeAbbreviation", @@ -11025,107 +3490,75 @@ ], "type": "object", "properties": { - "licenseType": { - "type": "string" + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" + } }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, - "effectiveStartDate": { + "licenseTypeAbbreviation": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "adverseAction" + ] + }, + "creationDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "licenseTypeAbbreviation": { + "actionAgainst": { "type": "string" }, - "adverseActionId": { + "licenseType": { "type": "string" }, - "effectiveLiftDate": { + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "effectiveStartDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "type": { - "type": "string", - "enum": [ - "adverseAction" - ] + "adverseActionId": { + "type": "string" }, - "creationDate": { + "effectiveLiftDate": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", "format": "date" }, - "actionAgainst": { + "encumbranceType": { + "type": "string" + }, + "liftingUser": { "type": "string" }, "dateOfUpdate": { @@ -11153,960 +3586,813 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "givenName": { "maxLength": 100, "minLength": 1, "type": "string" }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" - ] - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { - "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - } - } - }, - "SandboLicen5bneP2wVdz6l": { - "required": [ - "compact", - "providerId", - "recaptchaToken", - "recoveryToken" - ], - "type": "object", - "properties": { - "compact": { - "type": "string", - "description": "Compact abbreviation", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string", - "description": "Provider UUID" - }, - "recaptchaToken": { - "minLength": 1, - "type": "string", - "description": "ReCAPTCHA token for verification" - }, - "recoveryToken": { - "maxLength": 256, - "minLength": 1, - "type": "string", - "description": "Recovery token from the email link" - } - }, - "additionalProperties": false - }, - "SandboLicenRy0ye4gsVdf9": { - "required": [ - "compactAbbr", - "compactAdverseActionsNotificationEmails", - "compactCommissionFee", - "compactName", - "compactOperationsTeamEmails", - "compactSummaryReportNotificationEmails", - "configuredStates", - "licenseeRegistrationEnabled" - ], - "type": "object", - "properties": { - "configuredStates": { - "type": "array", - "description": "List of states that have submitted configurations and their live status", - "items": { - "required": [ - "isLive", - "postalAbbreviation" - ], - "type": "object", - "properties": { - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "isLive": { - "type": "boolean", - "description": "Whether the state is live and available for registrations." - } - } - } - }, - "compactCommissionFee": { - "required": [ - "feeAmount", - "feeType" - ], - "type": "object", - "properties": { - "feeAmount": { - "type": "number" - }, - "feeType": { - "type": "string", - "enum": [ - "FLAT_RATE" - ] - } - } - }, - "compactSummaryReportNotificationEmails": { - "type": "array", - "description": "List of email addresses for summary report notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "compactAdverseActionsNotificationEmails": { - "type": "array", - "description": "List of email addresses for adverse actions notifications", - "items": { - "type": "string", - "format": "email" - } - }, - "licenseeRegistrationEnabled": { - "type": "boolean", - "description": "Denotes whether licensee registration is enabled" - }, - "compactAbbr": { - "type": "string", - "description": "The abbreviation of the compact" - }, - "transactionFeeConfiguration": { - "type": "object", - "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "type": "number", - "description": "The amount to charge per privilege purchased" - } - } - } - } - }, - "compactName": { - "type": "string", - "description": "The full name of the compact" - }, - "compactOperationsTeamEmails": { - "type": "array", - "description": "List of email addresses for operations team notifications", - "items": { - "type": "string", - "format": "email" - } - } - } - }, - "SandboLicenmftfBe6vPEA8": { - "required": [ - "compact", - "dob", - "email", - "familyName", - "givenName", - "jurisdiction", - "licenseType", - "partialSocial", - "token" - ], - "type": "object", - "properties": { - "licenseType": { - "maxLength": 500, - "type": "string", - "description": "Type of license", - "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" - ] - }, - "compact": { - "maxLength": 100, - "type": "string", - "description": "Compact name" - }, - "dob": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "Date of birth in YYYY-MM-DD format" - }, - "givenName": { - "maxLength": 200, - "type": "string", - "description": "Provider's given name" - }, - "familyName": { - "maxLength": 200, - "type": "string", - "description": "Provider's family name" - }, - "jurisdiction": { - "maxLength": 2, - "minLength": 2, + "compactEligibility": { "type": "string", - "description": "Two-letter jurisdiction code", "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "eligible", + "ineligible" ] }, - "partialSocial": { - "maxLength": 4, - "minLength": 4, - "type": "string", - "description": "Last 4 digits of SSN" - }, - "email": { - "maxLength": 100, - "minLength": 5, + "jurisdictionUploadedCompactEligibility": { "type": "string", - "description": "Provider's email address", - "format": "email" + "enum": [ + "eligible", + "ineligible" + ] }, - "token": { - "type": "string", - "description": "ReCAPTCHA token" - } - } - }, - "SandboLicenSo5EafP3rZhM": { - "required": [ - "effectiveLiftDate" - ], - "type": "object", - "properties": { - "effectiveLiftDate": { + "dateOfBirth": { "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "description": "The effective date when the encumbrance will be lifted", "format": "date" - } - }, - "additionalProperties": false - }, - "SandboLicendZ26vvlCKCbW": { - "required": [ - "attestations", - "licenseType", - "orderInformation", - "selectedJurisdictions" - ], - "type": "object", - "properties": { - "licenseType": { + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "type": { "type": "string", - "description": "The type of license the provider is purchasing a privilege for.", "enum": [ - "audiologist", - "speech-language pathologist", - "occupational therapist", - "occupational therapy assistant", - "licensed professional counselor" + "provider" ] }, - "attestations": { + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenses": { "type": "array", - "description": "List of attestations that the user has agreed to", "items": { "required": [ - "attestationId", - "version" + "compact", + "compactEligibility", + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", + "dateOfUpdate", + "familyName", + "givenName", + "history", + "homeAddressCity", + "homeAddressPostalCode", + "homeAddressState", + "homeAddressStreet1", + "jurisdiction", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "licenseStatus", + "licenseType", + "middleName", + "providerId", + "type" ], "type": "object", "properties": { - "attestationId": { - "maxLength": 100, + "compact": { "type": "string", - "description": "The ID of the attestation" + "enum": [ + "cosm" + ] + }, + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" }, - "version": { - "maxLength": 10, - "pattern": "^\\d+$", + "jurisdiction": { "type": "string", - "description": "The version of the attestation" - } - } - } - }, - "orderInformation": { - "required": [ - "opaqueData" - ], - "type": "object", - "properties": { - "opaqueData": { - "required": [ - "dataDescriptor", - "dataValue" - ], - "type": "object", - "properties": { - "dataValue": { - "maxLength": 1000, - "type": "string", - "description": "The opaque data value token returned by Authorize.Net Accept UI" - }, - "dataDescriptor": { - "maxLength": 100, - "type": "string", - "description": "The opaque data descriptor returned by Authorize.Net Accept UI" - } - } - } - } - }, - "selectedJurisdictions": { - "maxLength": 20, - "type": "array", - "items": { - "type": "string", - "description": "Jurisdictions a provider has selected to purchase privileges in.", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - } - } - }, - "SandboLicengqvhPQz7ywKX": { - "type": "object", - "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "investigations": { + "type": "array", + "items": { + "required": [ + "compact", + "creationDate", + "dateOfUpdate", + "investigationId", + "jurisdiction", + "licenseType", + "providerId", + "submittingUser", + "type" + ], + "type": "object", + "properties": { + "licenseType": { + "type": "string" + }, + "investigationId": { + "type": "string" + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "submittingUser": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "investigation" + ] + }, + "creationDate": { + "type": "string", + "format": "date-time" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } } } }, - "jurisdictions": { - "type": "object", - "additionalProperties": { + "type": { + "type": "string", + "enum": [ + "license-home" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseType": { + "type": "string", + "enum": [ + "cosmetologist", + "esthetician" + ] + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "investigationStatus": { + "type": "string", + "description": "Status indicating if the license is under investigation", + "enum": [ + "underInvestigation" + ] + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "history": { + "type": "array", + "items": { + "required": [ + "compact", + "dateOfUpdate", + "jurisdiction", + "previous", + "type", + "updateType" + ], "type": "object", "properties": { - "actions": { + "removedValues": { + "type": "array", + "description": "List of field names that were present in the previous record but removed in the update", + "items": { + "type": "string" + } + }, + "licenseType": { + "type": "string", + "enum": [ + "cosmetologist", + "esthetician" + ] + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "previous": { + "required": [ + "dateOfExpiration", + "dateOfIssuance", + "dateOfRenewal", + "familyName", + "givenName", + "homeAddressCity", + "homeAddressPostalCode", + "homeAddressState", + "homeAddressStreet1", + "jurisdictionUploadedCompactEligibility", + "jurisdictionUploadedLicenseStatus", + "middleName" + ], "type": "object", "properties": { - "readPrivate": { - "type": "boolean" + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + } + } + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "updatedValues": { + "type": "object", + "properties": { + "homeAddressStreet2": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressPostalCode": { + "maxLength": 7, + "minLength": 5, + "type": "string" + }, + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressStreet1": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "compactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "jurisdictionUploadedCompactEligibility": { + "type": "string", + "enum": [ + "eligible", + "ineligible" + ] + }, + "dateOfBirth": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" }, - "admin": { - "type": "boolean" + "jurisdictionUploadedLicenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] }, - "write": { - "type": "boolean" + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" }, - "readSSN": { - "type": "boolean" + "dateOfIssuance": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "emailAddress": { + "maxLength": 100, + "minLength": 5, + "type": "string", + "format": "email" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "homeAddressState": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "dateOfRenewal": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "homeAddressCity": { + "maxLength": 100, + "minLength": 2, + "type": "string" + }, + "licenseNumber": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" } - }, - "additionalProperties": false + } + }, + "type": { + "type": "string", + "enum": [ + "licenseUpdate" + ] + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + }, + "updateType": { + "type": "string", + "enum": [ + "deactivation", + "expiration", + "issuance", + "other", + "renewal", + "encumbrance", + "lifting_encumbrance", + "licenseDeactivation" + ] } } } - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "SandboLicenaiHAuDR162f2": { - "required": [ - "items", - "pagination" - ], - "type": "object", - "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "items": { - "maxLength": 100, - "type": "array", - "items": { - "type": "object", - "oneOf": [ - { - "required": [ - "compactAbbr", - "compactCommissionFee", - "compactName", - "isSandbox", - "paymentProcessorPublicFields", - "transactionFeeConfiguration", - "type" - ], - "type": "object", - "properties": { - "compactCommissionFee": { - "required": [ - "feeAmount", - "feeType" - ], - "type": "object", - "properties": { - "feeAmount": { - "type": "number" - }, - "feeType": { - "type": "string", - "enum": [ - "FLAT_RATE" - ] - } - } - }, - "compactAbbr": { - "type": "string", - "description": "The abbreviation of the compact" - }, - "paymentProcessorPublicFields": { - "required": [ - "apiLoginId", - "publicClientKey" - ], - "type": "object", - "properties": { - "publicClientKey": { - "type": "string", - "description": "The public client key for the payment processor" - }, - "apiLoginId": { - "type": "string", - "description": "The API login ID for the payment processor" - } - } - }, - "type": { - "type": "string", - "enum": [ - "compact" - ] - }, - "transactionFeeConfiguration": { - "required": [ - "licenseeCharges" - ], - "type": "object", - "properties": { - "licenseeCharges": { - "required": [ - "active", - "chargeAmount", - "chargeType" - ], - "type": "object", - "properties": { - "chargeType": { - "type": "string", - "description": "The type of transaction fee charge", - "enum": [ - "FLAT_FEE_PER_PRIVILEGE" - ] - }, - "active": { - "type": "boolean", - "description": "Whether the compact is charging licensees transaction fees" - }, - "chargeAmount": { - "type": "number", - "description": "The amount to charge per privilege purchased" - } - } + }, + "ssnLastFour": { + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "phoneNumber": { + "pattern": "^\\+[0-9]{8,15}$", + "type": "string" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "licenseStatusName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "adverseActions": { + "type": "array", + "items": { + "required": [ + "actionAgainst", + "adverseActionId", + "compact", + "creationDate", + "dateOfUpdate", + "effectiveStartDate", + "encumbranceType", + "jurisdiction", + "licenseType", + "licenseTypeAbbreviation", + "providerId", + "type" + ], + "type": "object", + "properties": { + "clinicalPrivilegeActionCategories": { + "type": "array", + "description": "The categories of clinical privilege action", + "items": { + "type": "string" } + }, + "compact": { + "type": "string", + "enum": [ + "cosm" + ] + }, + "jurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "licenseTypeAbbreviation": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "adverseAction" + ] + }, + "creationDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "actionAgainst": { + "type": "string" + }, + "licenseType": { + "type": "string" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "effectiveStartDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "adverseActionId": { + "type": "string" + }, + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "encumbranceType": { + "type": "string" + }, + "liftingUser": { + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } - }, - "isSandbox": { - "type": "boolean", - "description": "Whether the compact is in sandbox mode" - }, - "compactName": { - "type": "string", - "description": "The full name of the compact" } } }, - { - "required": [ - "jurisdictionName", - "jurisprudenceRequirements", - "postalAbbreviation", - "privilegeFees", - "type" - ], - "type": "object", - "properties": { - "postalAbbreviation": { - "type": "string", - "description": "The postal abbreviation of the jurisdiction" - }, - "jurisprudenceRequirements": { - "required": [ - "required" - ], - "type": "object", - "properties": { - "linkToDocumentation": { - "description": "Optional link to jurisprudence documentation", - "oneOf": [ - { - "type": "string" - }, - null - ] - }, - "required": { - "type": "boolean", - "description": "Whether jurisprudence requirements exist" - } - } - }, - "jurisdictionName": { - "type": "string", - "description": "The name of the jurisdiction" - }, - "type": { - "type": "string", - "enum": [ - "jurisdiction" - ] - } - } + "dateOfUpdate": { + "type": "string", + "format": "date-time" } - ] + } } + }, + "ssnLastFour": { + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "dateOfExpiration": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", + "type": "string", + "format": "date" + }, + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string" + }, + "licenseStatus": { + "type": "string", + "enum": [ + "active", + "inactive" + ] + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "birthMonthDay": { + "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", + "type": "string", + "format": "date" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" + } + } + }, + "SandboLicenPUwIzAMSlfRh": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] } } }, - "SandboLicenv4avK8ok4P45": { + "SandboLicentzGiICqenA1I": { "required": [ "clinicalPrivilegeActionCategories", "encumbranceEffectiveDate", @@ -12152,442 +4438,179 @@ "additionalProperties": false, "description": "Encumbrance data to create" }, - "SandboLicenDTjDt3roB2dM": { + "SandboLicenqza9dE2UnEU1": { "required": [ - "pagination", - "providers" + "compactAbbr", + "compactAdverseActionsNotificationEmails", + "compactName", + "compactOperationsTeamEmails", + "compactSummaryReportNotificationEmails", + "configuredStates", + "licenseeRegistrationEnabled" ], "type": "object", "properties": { - "pagination": { - "type": "object", - "properties": { - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "object" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - } - }, - "sorting": { - "required": [ - "key" - ], - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "The key to sort results by", - "enum": [ - "dateOfUpdate", - "familyName" - ] - }, - "direction": { - "type": "string", - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ] - } - }, - "description": "How to sort results" - }, - "providers": { - "maxLength": 100, + "configuredStates": { "type": "array", + "description": "List of states that have submitted configurations and their live status", "items": { "required": [ - "birthMonthDay", - "compact", - "compactEligibility", - "dateOfExpiration", - "dateOfUpdate", - "familyName", - "givenName", - "jurisdictionUploadedCompactEligibility", - "jurisdictionUploadedLicenseStatus", - "licenseJurisdiction", - "licenseStatus", - "privilegeJurisdictions", - "providerId", - "type" + "isLive", + "postalAbbreviation" ], "type": "object", "properties": { - "licenseJurisdiction": { + "postalAbbreviation": { "type": "string", + "description": "The postal abbreviation of the jurisdiction", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "compactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "jurisdictionUploadedCompactEligibility": { - "type": "string", - "enum": [ - "eligible", - "ineligible" - ] - }, - "dateOfBirth": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "jurisdictionUploadedLicenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, - "type": { - "type": "string", - "enum": [ - "provider" + "wa" ] }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "currentHomeJurisdiction": { + "isLive": { + "type": "boolean", + "description": "Whether the state is live and available for registrations." + } + } + } + }, + "compactSummaryReportNotificationEmails": { + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compactAdverseActionsNotificationEmails": { + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "compactAbbr": { + "type": "string", + "description": "The abbreviation of the compact" + }, + "compactName": { + "type": "string", + "description": "The full name of the compact" + }, + "compactOperationsTeamEmails": { + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } + } + } + }, + "SandboLicenABWdja2fpGrf": { + "required": [ + "compactAdverseActionsNotificationEmails", + "compactOperationsTeamEmails", + "compactSummaryReportNotificationEmails", + "configuredStates", + "licenseeRegistrationEnabled" + ], + "type": "object", + "properties": { + "configuredStates": { + "type": "array", + "description": "List of states that have submitted configurations and their live status", + "items": { + "required": [ + "isLive", + "postalAbbreviation" + ], + "type": "object", + "properties": { + "postalAbbreviation": { "type": "string", - "description": "The current jurisdiction postal abbreviation if known.", + "description": "The postal abbreviation of the jurisdiction", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other", - "unknown" - ] - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "dateOfExpiration": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "licenseStatus": { - "type": "string", - "enum": [ - "active", - "inactive" + "wa" ] }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "birthMonthDay": { - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string", - "format": "date" - }, - "compactConnectRegisteredEmailAddress": { - "maxLength": 100, - "minLength": 5, - "type": "string", - "format": "email" - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" + "isLive": { + "type": "boolean", + "description": "Whether the state is live and available for registrations." } - } + }, + "additionalProperties": false } - } - } - }, - "SandboLicenUONaWXXjz4K6": { - "required": [ - "action" - ], - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "close" - ] }, - "encumbrance": { - "required": [ - "clinicalPrivilegeActionCategories", - "encumbranceEffectiveDate", - "encumbranceType" - ], - "type": "object", - "properties": { - "encumbranceEffectiveDate": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "description": "The effective date of the encumbrance", - "format": "date" - }, - "clinicalPrivilegeActionCategories": { - "type": "array", - "description": "The categories of clinical privilege action", - "items": { - "type": "string" - } - }, - "encumbranceType": { - "type": "string", - "description": "The type of encumbrance", - "enum": [ - "fine", - "reprimand", - "required supervision", - "completion of continuing education", - "public reprimand", - "probation", - "injunctive action", - "suspension", - "revocation", - "denial", - "surrender of license", - "modification of previous action-extension", - "modification of previous action-reduction", - "other monitoring", - "other adjudicated action not listed" - ] - } - }, - "additionalProperties": false, - "description": "Encumbrance data to create" - } - } - }, - "SandboLicenG4CMW3C4eZNN": { - "required": [ - "deactivationNote" - ], - "type": "object", - "properties": { - "deactivationNote": { - "maxLength": 256, - "type": "string", - "description": "Note describing why the privilege is being deactivated" + "compactSummaryReportNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for summary report notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "compactAdverseActionsNotificationEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for adverse actions notifications", + "items": { + "type": "string", + "format": "email" + } + }, + "licenseeRegistrationEnabled": { + "type": "boolean", + "description": "Denotes whether licensee registration is enabled" + }, + "compactOperationsTeamEmails": { + "maxItems": 10, + "minItems": 1, + "uniqueItems": true, + "type": "array", + "description": "List of email addresses for operations team notifications", + "items": { + "type": "string", + "format": "email" + } } }, "additionalProperties": false }, - "SandboLicenPfWQpg9qdCvm": { + "SandboLicenjPULg4TeFeF1": { "required": [ "attributes", - "permissions" + "permissions", + "status", + "userId" ], "type": "object", "properties": { @@ -12665,11 +4688,25 @@ } }, "additionalProperties": false + }, + "userId": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "active", + "inactive" + ] } }, "additionalProperties": false }, - "SandboLicenxXSBED7FZUQN": { + "SandboLicenMplIbIWayHWa": { + "required": [ + "pagination", + "providers" + ], "type": "object", "properties": { "pagination": { @@ -12690,208 +4727,163 @@ "minimum": 5, "type": "integer" } - } + } + }, + "query": { + "type": "object", + "properties": { + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", + "type": "string", + "description": "Internal UUID for the provider" + }, + "jurisdiction": { + "type": "string", + "description": "Filter for providers with privilege/license in a jurisdiction", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] + }, + "givenName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a given name" + }, + "familyName": { + "maxLength": 100, + "type": "string", + "description": "Filter for providers with a family name" + } + } + }, + "sorting": { + "required": [ + "key" + ], + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The key to sort results by", + "enum": [ + "dateOfUpdate", + "familyName" + ] + }, + "direction": { + "type": "string", + "description": "Direction to sort results by", + "enum": [ + "ascending", + "descending" + ] + } + }, + "description": "How to sort results" }, - "users": { + "providers": { + "maxLength": 100, "type": "array", "items": { "required": [ - "attributes", - "permissions", - "status", - "userId" + "compact", + "familyName", + "givenName", + "licenseJurisdiction", + "providerId", + "type" ], "type": "object", "properties": { - "permissions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - } - }, - "jurisdictions": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "actions": { - "type": "object", - "properties": { - "readPrivate": { - "type": "boolean" - }, - "admin": { - "type": "boolean" - }, - "write": { - "type": "boolean" - }, - "readSSN": { - "type": "boolean" - } - }, - "additionalProperties": false - } - } - } - } - }, - "additionalProperties": false - } + "licenseJurisdiction": { + "type": "string", + "enum": [ + "al", + "az", + "co", + "ks", + "ky", + "md", + "oh", + "tn", + "va", + "wa" + ] }, - "attributes": { - "required": [ - "email", - "familyName", - "givenName" - ], - "type": "object", - "properties": { - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "email": { - "maxLength": 100, - "minLength": 5, - "type": "string" - } - }, - "additionalProperties": false + "compact": { + "type": "string", + "enum": [ + "cosm" + ] }, - "userId": { + "providerId": { + "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "status": { + "givenName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "familyName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "middleName": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "type": { "type": "string", "enum": [ - "active", - "inactive" + "provider" ] + }, + "suffix": { + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "dateOfUpdate": { + "type": "string", + "format": "date-time" } - }, - "additionalProperties": false + } } } - }, - "additionalProperties": false + } }, - "SandboLicenoBgekYzIk0Uy": { - "required": [ - "affiliationType", - "fileNames" - ], + "SandboLicennxxmBbSCJcel": { "type": "object", - "properties": { - "affiliationType": { - "type": "string", - "description": "The type of military affiliation", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "fileNames": { - "type": "array", - "description": "List of military affiliation file names", - "items": { - "maxLength": 150, - "type": "string", - "description": "The name of the file being uploaded" - } - } - }, - "additionalProperties": false + "properties": {} }, - "SandboLicenJVUAEniGDz2F": { + "SandboLicenY31VDvsVuYXJ": { "required": [ - "jurisdiction" + "effectiveLiftDate" ], "type": "object", "properties": { - "jurisdiction": { + "effectiveLiftDate": { + "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", "type": "string", - "description": "The jurisdiction postal abbreviation to set as home jurisdiction", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy", - "other" - ] + "description": "The effective date when the encumbrance will be lifted", + "format": "date" } }, "additionalProperties": false }, - "SandboLicenLnkOp52kvwLg": { + "SandboLicenmIdUa45H3yV3": { "type": "array", "items": { "required": [ @@ -12915,45 +4907,70 @@ } } }, - "SandboLicenmMTXPta5fldR": { - "required": [ - "apiLoginId", - "processor", - "transactionKey" - ], + "SandboLicenTD8eYiNU2b3J": { "type": "object", "properties": { - "apiLoginId": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "The api login id for the payment processor" - }, - "transactionKey": { - "maxLength": 100, - "minLength": 1, - "type": "string", - "description": "The transaction key for the payment processor" - }, - "processor": { - "type": "string", - "description": "The type of payment processor", - "enum": [ - "authorize.net" - ] + "permissions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + } + }, + "jurisdictions": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "readPrivate": { + "type": "boolean" + }, + "admin": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "readSSN": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } + }, + "additionalProperties": false + } } }, "additionalProperties": false }, - "SandboLicen113ZVRfz1NW8": { + "SandboLicentIcqTWOgUIGm": { "required": [ - "enabled" + "message" ], "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Whether the feature flag is enabled" + "message": { + "type": "string", + "description": "A message about the request" } } } @@ -12964,12 +4981,6 @@ "name": "Authorization", "in": "header", "x-amazon-apigateway-authtype": "cognito_user_pools" - }, - "SandboxAPIStackLicenseApiProviderUsersPoolAuthorizerEB7523BA": { - "type": "apiKey", - "name": "Authorization", - "in": "header", - "x-amazon-apigateway-authtype": "cognito_user_pools" } } }, diff --git a/backend/cosmetology-app/docs/internal/postman/postman-collection.json b/backend/cosmetology-app/docs/internal/postman/postman-collection.json index 5c35dd33a..10abcdb04 100644 --- a/backend/cosmetology-app/docs/internal/postman/postman-collection.json +++ b/backend/cosmetology-app/docs/internal/postman/postman-collection.json @@ -10,7 +10,7 @@ "type": "bearer" }, "info": { - "_postman_id": "7cb454e4-7ab1-426a-9dda-b7b0ed228704", + "_postman_id": "a6229398-9b7a-430d-b4a9-f743702a7032", "description": { "content": "", "type": "text/plain" @@ -401,7 +401,7 @@ "item": [ { "event": [], - "id": "c1b1d849-13b2-4f67-baac-6234dcd7b77c", + "id": "a3e97d23-e1e0-4d99-8580-e1043f7d46b5", "name": "/v1/compacts/:compact", "protocolProfileBehavior": { "disableBodyPruning": true @@ -444,7 +444,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"compactAbbr\": \"\",\n \"compactAdverseActionsNotificationEmails\": [\n \"\",\n \"\"\n ],\n \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactName\": \"\",\n \"compactOperationsTeamEmails\": [\n \"\",\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\",\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"wa\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"va\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}", + "body": "{\n \"compactAbbr\": \"\",\n \"compactAdverseActionsNotificationEmails\": [\n \"\",\n \"\"\n ],\n \"compactName\": \"\",\n \"compactOperationsTeamEmails\": [\n \"\",\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\",\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"va\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"va\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -453,7 +453,7 @@ "value": "application/json" } ], - "id": "3cd40a5c-9778-49c5-ac94-0a9bc9d477c5", + "id": "b6751790-4e01-4521-b09c-b2a3fb8e3a5b", "name": "200 response", "originalRequest": { "body": {}, @@ -491,7 +491,7 @@ }, { "event": [], - "id": "3568af2c-3a8b-4a0a-992a-f90b1f5b6685", + "id": "3840383a-1995-457d-866b-faffe472558f", "name": "/v1/compacts/:compact", "protocolProfileBehavior": { "disableBodyPruning": true @@ -505,7 +505,7 @@ "language": "json" } }, - "raw": "{\n \"compactAdverseActionsNotificationEmails\": [\n \"\"\n ],\n \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ar\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"sd\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}" + "raw": "{\n \"compactAdverseActionsNotificationEmails\": [\n \"\"\n ],\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ks\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"va\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\"\n}" }, "description": {}, "header": [ @@ -556,7 +556,7 @@ "value": "application/json" } ], - "id": "f50a3dc3-9641-4ae0-8050-d33c19c7970e", + "id": "3b1f1f51-d572-4d81-ae6a-56c6eb3124ea", "name": "200 response", "originalRequest": { "body": { @@ -567,7 +567,7 @@ "language": "json" } }, - "raw": "{\n \"compactAdverseActionsNotificationEmails\": [\n \"\"\n ],\n \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ar\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"sd\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\",\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n }\n}" + "raw": "{\n \"compactAdverseActionsNotificationEmails\": [\n \"\"\n ],\n \"compactOperationsTeamEmails\": [\n \"\"\n ],\n \"compactSummaryReportNotificationEmails\": [\n \"\"\n ],\n \"configuredStates\": [\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"ks\"\n },\n {\n \"isLive\": \"\",\n \"postalAbbreviation\": \"va\"\n }\n ],\n \"licenseeRegistrationEnabled\": \"\"\n}" }, "header": [ { @@ -605,260 +605,12 @@ } ] }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "event": [], - "id": "e1d7d1b3-0caa-44e1-aab8-0b0d9520f54d", - "name": "/v1/compacts/:compact/attestations/:attestationId", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/attestations/:attestationId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "attestations", - ":attestationId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "attestationId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"dateCreated\": \"\",\n \"attestationId\": \"\",\n \"compact\": \"aslp\",\n \"text\": \"\",\n \"type\": \"attestation\",\n \"locale\": \"\",\n \"version\": \"\",\n \"required\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "c61b5da2-6143-456f-80ba-71cd6300bbcd", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "attestations", - ":attestationId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "{attestationId}" - } - ], - "name": "attestations" - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "event": [], - "id": "784d7a36-d743-4499-83a8-9a35635bd4f7", - "name": "/v1/compacts/:compact/credentials/payment-processor", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"apiLoginId\": \"\",\n \"processor\": \"authorize.net\",\n \"transactionKey\": \"\"\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/compacts/:compact/credentials/payment-processor", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "credentials", - "payment-processor" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "49cc093e-9b56-4bef-9ade-555b3398672b", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"apiLoginId\": \"\",\n \"processor\": \"authorize.net\",\n \"transactionKey\": \"\"\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "credentials", - "payment-processor" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "payment-processor" - } - ], - "name": "credentials" - }, { "description": "", "item": [ { "event": [], - "id": "edd82d48-8d82-4b01-9b7a-248727deabee", + "id": "77f80d2c-f50b-426d-a47a-80b093fbf17c", "name": "/v1/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -911,7 +663,7 @@ "value": "application/json" } ], - "id": "a16b33dd-17fe-4be5-91da-8bbc43fbe565", + "id": "024f30ae-4b62-47dc-bf27-534c5ba54102", "name": "200 response", "originalRequest": { "body": {}, @@ -984,7 +736,7 @@ } } ], - "id": "19fef01d-8597-415b-80d2-16cc5c6b328f", + "id": "f6796408-87f8-465b-add1-bfeec0226f10", "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses/bulk-upload", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1041,7 +793,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"upload\": {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n}", + "body": "{\n \"upload\": {\n \"fields\": {\n \"nostrud8\": \"\",\n \"non4_\": \"\"\n },\n \"url\": \"\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -1050,7 +802,7 @@ "value": "application/json" } ], - "id": "02ec708c-bdb8-4064-b7fd-37a790997853", + "id": "389331cf-6e11-414f-a556-4196929e219b", "name": "200 response", "originalRequest": { "body": {}, @@ -1110,7 +862,7 @@ "item": [ { "event": [], - "id": "2974d272-c532-41ce-91cc-90f24da34f00", + "id": "a6575a47-ddad-4c6c-b71c-bed898cb2e61", "name": "/v1/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1124,7 +876,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"36fe3faf-ce25-4c63-b5f3-abb953a85e3f\",\n \"jurisdiction\": \"nm\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"descending\"\n }\n}" + "raw": "{\n \"query\": {\n \"providerId\": \"c733437a-07c0-4155-821e-6f2589725b0d\",\n \"jurisdiction\": \"wa\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" }, "description": {}, "header": [ @@ -1168,7 +920,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"birthMonthDay\": \"06-08\",\n \"compact\": \"octp\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2228-11-24\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"sd\",\n \"licenseStatus\": \"active\",\n \"privilegeJurisdictions\": [\n \"in\",\n \"vi\"\n ],\n \"providerId\": \"20e8b0b3-84c6-4d64-b855-6b97fa2b5ef2\",\n \"type\": \"provider\",\n \"npi\": \"0740262699\",\n \"dateOfBirth\": \"1644-12-06\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"oh\",\n \"ssnLastFour\": \"2699\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"04-35\",\n \"compact\": \"coun\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1568-12-26\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"hi\",\n \"licenseStatus\": \"inactive\",\n \"privilegeJurisdictions\": [\n \"ct\",\n \"ri\"\n ],\n \"providerId\": \"b7479a54-1456-45d2-bf17-09867f597430\",\n \"type\": \"provider\",\n \"npi\": \"5726477063\",\n \"dateOfBirth\": \"2412-08-06\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"oh\",\n \"ssnLastFour\": \"9449\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}", + "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"birthMonthDay\": \"14-10\",\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2925-05-28\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"al\",\n \"licenseStatus\": \"active\",\n \"providerId\": \"56b825c5-abaf-41d8-ae6a-e7fcd0f33f1a\",\n \"type\": \"provider\",\n \"dateOfBirth\": \"2935-03-02\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"6179\",\n \"middleName\": \"\"\n },\n {\n \"birthMonthDay\": \"10-15\",\n \"compact\": \"cosm\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1602-11-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"ks\",\n \"licenseStatus\": \"active\",\n \"providerId\": \"023b5736-46ce-49bf-874c-43254e8b0e3b\",\n \"type\": \"provider\",\n \"dateOfBirth\": \"1933-08-07\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"5588\",\n \"middleName\": \"\"\n }\n ],\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"ascending\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -1177,7 +929,7 @@ "value": "application/json" } ], - "id": "3c87e5d0-6363-4641-8a9a-681c6a5e6f21", + "id": "8649176e-ca38-432b-8157-7502859d9086", "name": "200 response", "originalRequest": { "body": { @@ -1188,7 +940,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"36fe3faf-ce25-4c63-b5f3-abb953a85e3f\",\n \"jurisdiction\": \"nm\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"descending\"\n }\n}" + "raw": "{\n \"query\": {\n \"providerId\": \"c733437a-07c0-4155-821e-6f2589725b0d\",\n \"jurisdiction\": \"wa\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" }, "header": [ { @@ -1236,7 +988,7 @@ "item": [ { "event": [], - "id": "afbfff17-6d80-4e06-bd96-42ab7d331349", + "id": "ddce847f-142f-4d9e-afb3-41387f567fc3", "name": "/v1/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1291,7 +1043,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"birthMonthDay\": \"08-15\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2385-12-29\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2936-05-05\",\n \"dateOfIssuance\": \"2254-10-17\",\n \"dateOfRenewal\": \"2835-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1800-04-06\",\n \"dateOfIssuance\": \"2972-08-07\",\n \"dateOfRenewal\": \"2438-09-14\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6466950304\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2691-05-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+855527173535\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8394405807\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1842-07-31\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2300-08-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2863-08-03\",\n \"phoneNumber\": \"+3528187300\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2856-05-08\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"1918-10-05\",\n \"dateOfIssuance\": \"1476-12-15\",\n \"dateOfRenewal\": \"1145-11-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"0596273100\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1028-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9136425909\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2808925314\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1326-05-06\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2399-06-25\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2473-11-30\",\n \"phoneNumber\": \"+1013464869\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2257-10-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"hi\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"9b0bf21b-92f8-41d9-9df4-eaabd84adeee\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"providerId\": \"ad1db718-9d5c-4842-8b45-9b34ee4e0cca\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"\",\n \"providerId\": \"844db6f5-44af-4fc9-9221-3015ed96cbef\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2619765297\",\n \"dateOfBirth\": \"1768-09-08\",\n \"ssnLastFour\": \"4600\",\n \"phoneNumber\": \"+59112126684\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1777-09-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2708-03-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"aeca0267-7733-48d0-a71c-d884e4754f14\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2396-01-09\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2958-05-22\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2261-06-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba21be91-43db-4c7a-98de-607d32eb34b6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1185-12-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1169-12-27\",\n \"dateOfIssuance\": \"2302-07-11\",\n \"dateOfRenewal\": \"1624-03-08\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"dateOfExpiration\": \"2804-01-30\",\n \"dateOfIssuance\": \"1778-03-30\",\n \"dateOfRenewal\": \"1452-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8839374275\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2408-12-29\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+25666997428056\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3527141483\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1539-11-04\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2033-10-28\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2946-02-27\",\n \"phoneNumber\": \"+11739899598\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1036-10-30\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"dc\",\n \"previous\": {\n \"dateOfExpiration\": \"2132-01-04\",\n \"dateOfIssuance\": \"2948-11-30\",\n \"dateOfRenewal\": \"1580-10-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3443777389\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2208-08-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+69171972\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2199245845\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2289-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2595-12-09\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2635-11-09\",\n \"phoneNumber\": \"+538742548588950\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1656-08-16\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ky\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapist\",\n \"middleName\": \"\",\n \"providerId\": \"481ba68e-ab28-427f-befb-fba6bf3b3bae\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"f1c029aa-dae1-4ce3-a43c-1bf2320b7642\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wv\",\n \"licenseType\": \"\",\n \"providerId\": \"8c92261b-fea9-470a-8fe8-dcaca7ca8697\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"1690493112\",\n \"dateOfBirth\": \"1751-01-02\",\n \"ssnLastFour\": \"8079\",\n \"phoneNumber\": \"+106891529\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1394-05-20\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1355-11-25\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6146bf62-a9f2-4c96-9d67-8c32cf05de02\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1150-11-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2103-02-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2104-10-21\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bd5c9993-b19c-49a9-a241-6b67768bb1a5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1567-09-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2814-11-05\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"a6dad0d8-c0c7-4274-9d1f-a530a54209d2\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2214-04-11\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"ca2d993d-8064-4a9a-9db0-f22f39e37118\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n }\n ],\n \"privilegeJurisdictions\": [\n \"az\",\n \"ga\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1994-11-05\",\n \"dateOfIssuance\": \"2425-09-15\",\n \"dateOfRenewal\": \"1310-11-08\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ms\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2993-03-27\",\n \"dateOfIssuance\": \"2072-10-31\",\n \"dateOfRenewal\": \"2114-11-25\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"providerId\": \"4d5725cf-ad64-45fe-b60c-2224209edc4a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"mn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"la\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1994-03-26\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1228-07-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"38f48d47-72b6-4acd-9e61-da8cd93b2ad3\",\n \"dateOfRenewal\": \"2374-01-04\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ak\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2781-12-27\",\n \"dateOfIssuance\": \"1187-05-12\",\n \"dateOfRenewal\": \"2763-09-21\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mo\",\n \"type\": \"privilege\",\n \"providerId\": \"a821d267-5653-49bd-97a2-bfc59170f24c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"in\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"id\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1819-11-12\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2038-12-26\",\n \"privilegeId\": \"\",\n \"providerId\": \"ad9f72d8-19d3-4b8e-b7b7-618dfe5e9e58\",\n \"dateOfRenewal\": \"1113-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"vt\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"90bc19aa-772d-44ad-9cd6-3983304f2328\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"mi\",\n \"licenseType\": \"\",\n \"providerId\": \"2dc8351a-6416-47e7-9b6e-2a34491c71c7\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ri\",\n \"licenseType\": \"\",\n \"providerId\": \"fe1216ae-9c2c-4b40-8c29-1d6b42028e71\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1061-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1895-11-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"pr\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2712681d-bded-4d93-905c-69072cc9ad3e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2321-10-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1298-05-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2743-07-01\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c166bbe2-1f54-44d6-87ec-baf461c0b04e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2562-12-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1763-12-31\",\n \"dateOfIssuance\": \"1282-02-31\",\n \"dateOfRenewal\": \"1775-03-02\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"me\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1299-03-12\",\n \"dateOfIssuance\": \"2556-09-24\",\n \"dateOfRenewal\": \"2525-12-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"privilegeId\": \"\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"sc\",\n \"type\": \"privilege\",\n \"providerId\": \"fcee0893-045b-4e62-993b-2e1a0d5b9a6a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"or\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2189-04-21\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2561-09-02\",\n \"privilegeId\": \"\",\n \"providerId\": \"0e368060-5ad8-4526-a4d4-b3bb0ae375ee\",\n \"dateOfRenewal\": \"1197-12-28\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nc\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2675-12-14\",\n \"dateOfIssuance\": \"2405-08-11\",\n \"dateOfRenewal\": \"2288-09-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"hi\",\n \"type\": \"privilege\",\n \"providerId\": \"b442dc00-b160-49c2-9e00-adc648fe8e78\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"ms\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1632-10-06\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2989-04-09\",\n \"privilegeId\": \"\",\n \"providerId\": \"f3981490-3938-4976-b957-7123142c46de\",\n \"dateOfRenewal\": \"1356-01-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"fl\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"11ec1f36-39ef-411d-9ed6-0218a17bde45\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"f78a6f18-b3bd-4937-9a6c-b48d88245048\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"bf956261-d85b-4589-9cdb-446467415ef6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1572-08-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1663-10-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"nd\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"edea6027-5ae7-4c1f-a412-a29bed40d673\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1688-10-12\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1780-08-11\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2812-12-13\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f488d06a-d37c-4736-ba3a-c9d02cead4fa\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2996-12-11\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"9b5458d5-9c52-41cd-b3b1-95f5c4affc2c\",\n \"type\": \"provider\",\n \"npi\": \"2535279913\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1940-02-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"in\",\n \"ssnLastFour\": \"5671\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", + "body": "{\n \"birthMonthDay\": \"16-35\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"1462-05-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"wa\",\n \"licenses\": [\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2533-08-12\",\n \"dateOfIssuance\": \"2069-12-03\",\n \"dateOfRenewal\": \"2356-10-01\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"previous\": {\n \"dateOfExpiration\": \"1396-10-07\",\n \"dateOfIssuance\": \"2694-09-02\",\n \"dateOfRenewal\": \"2974-11-13\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2598-03-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+581255669743\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1785-12-29\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2865-10-10\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1237-12-04\",\n \"phoneNumber\": \"+71110021081766\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1584-11-31\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"2682-10-03\",\n \"dateOfIssuance\": \"1748-12-16\",\n \"dateOfRenewal\": \"1182-07-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2622-07-14\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+17840958826794\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"other\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1755-10-30\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2394-10-01\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2627-04-01\",\n \"phoneNumber\": \"+5817570795726\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2936-10-09\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ky\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"esthetician\",\n \"middleName\": \"\",\n \"providerId\": \"499a6dac-9338-4659-a048-5b6a7b2775a8\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"providerId\": \"f1a6b79c-2eaf-4337-a83d-f436143fcfc3\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"b0339ecf-aa61-4acc-98aa-422645bcbb94\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"1183-03-31\",\n \"ssnLastFour\": \"3368\",\n \"phoneNumber\": \"+731528995\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2690-03-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2668-10-09\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6a536306-3733-45a3-9a13-fb714de11179\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2933-07-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2051-10-15\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2989-06-10\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"3b0113fa-f6cf-49df-a897-96cd68454e2e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2587-03-03\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"1511-01-25\",\n \"dateOfIssuance\": \"2396-11-24\",\n \"dateOfRenewal\": \"1160-09-20\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"previous\": {\n \"dateOfExpiration\": \"1835-12-30\",\n \"dateOfIssuance\": \"1145-10-19\",\n \"dateOfRenewal\": \"2324-12-12\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2145-12-04\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+56765706547998\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2585-10-09\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"1078-02-06\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1396-04-02\",\n \"phoneNumber\": \"+48336052486\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1855-05-25\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"tn\",\n \"previous\": {\n \"dateOfExpiration\": \"1719-06-31\",\n \"dateOfIssuance\": \"1248-10-30\",\n \"dateOfRenewal\": \"1337-07-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2048-09-31\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+02704267067519\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"cosmetologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1909-11-30\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2249-12-10\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"1838-09-30\",\n \"phoneNumber\": \"+85056454\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1887-11-31\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"md\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"esthetician\",\n \"middleName\": \"\",\n \"providerId\": \"01d60477-de77-4b46-9fb0-950cf4d4be0f\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"0ec0d784-cdbc-4a57-853e-8cd5ceaeab59\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"b110f7ac-4674-4fa3-82d2-4ffbf9ec8160\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"dateOfBirth\": \"2857-05-30\",\n \"ssnLastFour\": \"7205\",\n \"phoneNumber\": \"+931906372280128\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1123-03-11\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1122-01-22\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"8c552b87-3d8c-41ca-b243-d4922749f909\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2317-06-15\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1853-02-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2064-01-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"65859c1c-b86a-4149-b355-d92f3fc7339d\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2897-09-25\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"cosm\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1655-10-29\",\n \"dateOfIssuance\": \"1532-08-16\",\n \"dateOfRenewal\": \"1180-05-02\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1381-06-07\",\n \"dateOfIssuance\": \"2000-11-30\",\n \"dateOfRenewal\": \"2628-11-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ky\",\n \"privilegeId\": \"\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"al\",\n \"type\": \"privilege\",\n \"providerId\": \"f81ab023-84e9-4ab3-a5bc-4a7e5b5699b6\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"cosmetologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"az\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2165-11-21\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2007-11-15\",\n \"privilegeId\": \"\",\n \"providerId\": \"224806be-935b-41d7-a4e6-69010e40f24d\",\n \"dateOfRenewal\": \"2778-10-15\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1073-10-31\",\n \"dateOfIssuance\": \"2674-11-08\",\n \"dateOfRenewal\": \"2238-07-30\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"al\",\n \"privilegeId\": \"\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"providerId\": \"6cd0dfb0-fd4f-4db7-987d-515bff688277\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"ks\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"va\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2455-11-29\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1634-03-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"09382156-6267-4a1d-9193-d4d38f1301e5\",\n \"dateOfRenewal\": \"1996-01-18\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"oh\",\n \"licenseJurisdiction\": \"ks\",\n \"licenseType\": \"cosmetologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"91e26c69-faab-489e-ad96-3a4076e28717\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"0def2db1-80ac-4d18-ae4b-a47930309576\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"0121aec7-addb-4fed-80cf-68837eddd892\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1177-11-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2531-08-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"49e78710-4cfa-4a23-addc-9ea5b2dc77f8\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1295-02-30\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2187-08-08\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2902-11-20\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"87c8aced-5983-4eca-871b-81671d9e328c\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1483-01-30\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"cosm\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1536-05-31\",\n \"dateOfIssuance\": \"2362-11-19\",\n \"dateOfRenewal\": \"2098-03-07\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1121-11-09\",\n \"dateOfIssuance\": \"2134-12-21\",\n \"dateOfRenewal\": \"2842-04-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"va\",\n \"privilegeId\": \"\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"providerId\": \"454e3d59-e849-4489-a370-153c5f9e97e1\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"co\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1840-05-31\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1665-12-01\",\n \"privilegeId\": \"\",\n \"providerId\": \"49e3f870-1e71-417e-8c16-a1fe3114d78d\",\n \"dateOfRenewal\": \"1869-03-18\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1845-09-31\",\n \"dateOfIssuance\": \"2979-06-07\",\n \"dateOfRenewal\": \"2669-06-18\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ks\",\n \"privilegeId\": \"\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"providerId\": \"062e24b1-3097-42b7-9c90-2998b263ff08\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"esthetician\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"co\",\n \"compact\": \"cosm\",\n \"jurisdiction\": \"oh\",\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1948-05-14\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1968-02-30\",\n \"privilegeId\": \"\",\n \"providerId\": \"b66d6289-0426-4ae8-894f-6dfb3506d784\",\n \"dateOfRenewal\": \"2297-10-26\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"co\",\n \"licenseType\": \"esthetician\",\n \"privilegeId\": \"\",\n \"providerId\": \"fed2552a-6e57-49c7-808a-d7f7dc0b4660\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"fdd60fe1-08a0-44f9-aae4-53fe1710811e\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"providerId\": \"ec6c374d-f871-4c88-8201-20808e0d7d15\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1279-10-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1390-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"1b9249d8-5e6c-461a-99e8-3138261589b2\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1932-11-06\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1969-12-13\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1670-10-21\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"663e02ee-ffb3-4664-bbb9-69a7303060b3\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1420-11-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"233e5ab2-09b1-4ec4-8d6c-81ba8dadd29e\",\n \"type\": \"provider\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2000-05-09\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"ssnLastFour\": \"8224\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -1300,7 +1052,7 @@ "value": "application/json" } ], - "id": "b7cc6c87-9d25-473b-81fd-e94664497fc3", + "id": "065c3515-44d6-4044-bcc2-00c7d9fce9a0", "name": "200 response", "originalRequest": { "body": {}, @@ -1358,7 +1110,7 @@ "item": [ { "event": [], - "id": "c8eaee6b-941a-4ba6-b5c2-86650cb9d0c7", + "id": "b391ff44-fac4-4ae6-8d37-78d1d2f05a4e", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1372,7 +1124,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"1622-02-27\",\n \"encumbranceType\": \"other adjudicated action not listed\"\n}" }, "description": {}, "header": [ @@ -1461,7 +1213,7 @@ "value": "application/json" } ], - "id": "1d4911ea-8ff0-4972-89ec-1328421603d7", + "id": "f8d70f9e-a674-4d4d-b771-b6fc5e9f5e58", "name": "200 response", "originalRequest": { "body": { @@ -1472,7 +1224,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"1622-02-27\",\n \"encumbranceType\": \"other adjudicated action not listed\"\n}" }, "header": [ { @@ -1523,7 +1275,7 @@ "item": [ { "event": [], - "id": "9cd0e9d6-8c28-4bd0-a03c-e498f58bd8d3", + "id": "d4c181a1-b783-4419-a866-2a3ddbaba3b6", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1537,7 +1289,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1027-05-03\"\n}" }, "description": {}, "header": [ @@ -1637,7 +1389,7 @@ "value": "application/json" } ], - "id": "83eea677-31cb-483b-a142-c5017facf781", + "id": "0a4ee00f-defa-483d-98fd-5a58ddb07848", "name": "200 response", "originalRequest": { "body": { @@ -1648,7 +1400,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"effectiveLiftDate\": \"1027-05-03\"\n}" }, "header": [ { @@ -1706,7 +1458,7 @@ "item": [ { "event": [], - "id": "95b5f3e9-5d82-4af1-9975-01b3fc520a7e", + "id": "29e1fa59-029b-4154-b9e7-4d77f22400a7", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1809,7 +1561,7 @@ "value": "application/json" } ], - "id": "c0b8a977-b11c-4ea6-9e30-cc2d9343e62a", + "id": "435ba2ca-a584-4233-a6cd-b4413440360b", "name": "200 response", "originalRequest": { "body": { @@ -1871,7 +1623,7 @@ "item": [ { "event": [], - "id": "bde6e1f5-2b85-42d4-9af4-b096c2990722", + "id": "d338e106-c0cc-409b-8e11-22ae388187d4", "name": "/v1/compacts/:compact/providers/:providerId/licenses/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -1885,7 +1637,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"2232-06-24\",\n \"encumbranceType\": \"fine\"\n }\n}" }, "description": {}, "header": [ @@ -1985,7 +1737,7 @@ "value": "application/json" } ], - "id": "6eb0f49a-cccf-469c-a8a6-8a4939443611", + "id": "bf70329a-667d-4238-917c-feb550860be5", "name": "200 response", "originalRequest": { "body": { @@ -1996,7 +1748,7 @@ "language": "json" } }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"2232-06-24\",\n \"encumbranceType\": \"fine\"\n }\n}" }, "header": [ { @@ -2084,8 +1836,8 @@ "item": [ { "event": [], - "id": "d74f25ec-cfee-4d03-b1f8-d3822c2be6dd", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "id": "02403b91-ca01-4bf7-be26-a6937cea8e3c", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -2098,7 +1850,7 @@ "language": "json" } }, - "raw": "{\n \"deactivationNote\": \"\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"1622-02-27\",\n \"encumbranceType\": \"other adjudicated action not listed\"\n}" }, "description": {}, "header": [ @@ -2112,7 +1864,7 @@ } ], "method": "POST", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/deactivate", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", "url": { "host": [ "{{baseUrl}}" @@ -2128,7 +1880,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "deactivate" + "encumbrance" ], "query": [], "variable": [ @@ -2187,7 +1939,7 @@ "value": "application/json" } ], - "id": "cff5ab64-b4a4-4921-8f90-2e694131358f", + "id": "da855993-bfe1-4f85-8474-1257a8f34d4b", "name": "200 response", "originalRequest": { "body": { @@ -2198,7 +1950,7 @@ "language": "json" } }, - "raw": "{\n \"deactivationNote\": \"\"\n}" + "raw": "{\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"1622-02-27\",\n \"encumbranceType\": \"other adjudicated action not listed\"\n}" }, "header": [ { @@ -2234,7 +1986,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "deactivate" + "encumbrance" ], "query": [], "variable": [] @@ -2243,31 +1995,211 @@ "status": "OK" } ] - } - ], - "name": "deactivate" - }, - { - "description": "", - "item": [ + }, { - "event": [], - "id": "37217a24-2f76-4243-8b84-2864dcff6606", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } + "description": "", + "item": [ + { + "event": [], + "id": "124bcca3-cceb-4e6d-9b59-d77f3c261ad4", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "protocolProfileBehavior": { + "disableBodyPruning": true }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" - }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"effectiveLiftDate\": \"1027-05-03\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "PATCH", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance", + ":encumbranceId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "licenseType", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "encumbranceId", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "73f75473-e1da-45ab-baad-4dd371062f06", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"effectiveLiftDate\": \"1027-05-03\"\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "PATCH", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + ":providerId", + "privileges", + "jurisdiction", + ":jurisdiction", + "licenseType", + ":licenseType", + "encumbrance", + ":encumbranceId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "{encumbranceId}" + } + ], + "name": "encumbrance" + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "3e16ca21-3459-421b-9e4d-2e73c8315875", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{}" + }, "description": {}, "header": [ { @@ -2280,7 +2212,7 @@ } ], "method": "POST", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", "url": { "host": [ "{{baseUrl}}" @@ -2296,7 +2228,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance" + "investigation" ], "query": [], "variable": [ @@ -2355,7 +2287,7 @@ "value": "application/json" } ], - "id": "2d0a8450-d518-4f25-adab-9d9c30782586", + "id": "ed4f228c-bfff-4496-a82c-fbcd8205630a", "name": "200 response", "originalRequest": { "body": { @@ -2366,7 +2298,7 @@ "language": "json" } }, - "raw": "{\n \"encumbranceEffectiveDate\": \"1213-12-01\",\n \"encumbranceType\": \"modification of previous action-extension\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n}" + "raw": "{}" }, "header": [ { @@ -2402,7 +2334,7 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance" + "investigation" ], "query": [], "variable": [] @@ -2417,8 +2349,8 @@ "item": [ { "event": [], - "id": "76f8edac-8274-4279-9db7-0ff6e00edb27", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "id": "42510da4-7d74-4373-a3e3-6a23d457cca2", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -2431,7 +2363,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"2232-06-24\",\n \"encumbranceType\": \"fine\"\n }\n}" }, "description": {}, "header": [ @@ -2445,7 +2377,7 @@ } ], "method": "PATCH", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/encumbrance/:encumbranceId", + "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", "url": { "host": [ "{{baseUrl}}" @@ -2461,8 +2393,8 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance", - ":encumbranceId" + "investigation", + ":investigationId" ], "query": [], "variable": [ @@ -2512,7 +2444,7 @@ "type": "text/plain" }, "disabled": false, - "key": "encumbranceId", + "key": "investigationId", "type": "any", "value": "" } @@ -2531,7 +2463,7 @@ "value": "application/json" } ], - "id": "16981b5d-ab36-4364-9f2c-a0ec8435e010", + "id": "26d5ae70-0360-47a1-b5e6-a8a9fc134eab", "name": "200 response", "originalRequest": { "body": { @@ -2542,7 +2474,7 @@ "language": "json" } }, - "raw": "{\n \"effectiveLiftDate\": \"1104-03-15\"\n}" + "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"encumbranceEffectiveDate\": \"2232-06-24\",\n \"encumbranceType\": \"fine\"\n }\n}" }, "header": [ { @@ -2578,8 +2510,8 @@ ":jurisdiction", "licenseType", ":licenseType", - "encumbrance", - ":encumbranceId" + "investigation", + ":investigationId" ], "query": [], "variable": [] @@ -2590,1385 +2522,33 @@ ] } ], - "name": "{encumbranceId}" + "name": "{investigationId}" } ], - "name": "encumbrance" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "a3a9a8fc-e963-444d-ad98-28bbd589e0ff", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "f4fa57e2-59fc-4d08-9064-9c463bdf3623", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "history" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "de5fed5f-96c8-4f50-ad5c-db3229e0be7d", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "investigation" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "48143152-8047-4218-9dc4-9ffe347769fc", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "investigation" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "ed605afe-0ca3-4ee9-9ec9-6f76a41f2824", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "name": "/v1/compacts/:compact/providers/:providerId/privileges/jurisdiction/:jurisdiction/licenseType/:licenseType/investigation/:investigationId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "investigation", - ":investigationId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "investigationId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "3d3b31c8-15e4-4652-9ced-ff23024a7cd0", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"action\": \"close\",\n \"encumbrance\": {\n \"encumbranceEffectiveDate\": \"2673-09-10\",\n \"encumbranceType\": \"public reprimand\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"clinicalPrivilegeActionCategory\": \"\"\n }\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "privileges", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "investigation", - ":investigationId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "{investigationId}" - } - ], - "name": "investigation" - } - ], - "name": "{licenseType}" - } - ], - "name": "licenseType" - } - ], - "name": "{jurisdiction}" - } - ], - "name": "jurisdiction" - } - ], - "name": "privileges" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "d369e9fc-9a44-4e34-9f10-f4b7d700268b", - "name": "/v1/compacts/:compact/providers/:providerId/ssn", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/providers/:providerId/ssn", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "ssn" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"ssn\": \"227-14-4166\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "e4d02317-6458-4453-901b-b355ecea038e", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "providers", - ":providerId", - "ssn" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "ssn" - } - ], - "name": "{providerId}" - } - ], - "name": "providers" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "c631e2a7-bf4c-49da-8ebe-44c63db91758", - "name": "/v1/compacts/:compact/staff-users", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/staff-users", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"users\": [\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n },\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n }\n ]\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "description": { - "content": "", - "type": "text/plain" - }, - "disabled": false, - "key": "Access-Control-Allow-Origin", - "value": "" - } - ], - "id": "f82015f7-677a-410b-b0fa-a0b2a610cd86", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "event": [], - "id": "29d97a42-4046-4876-8a17-84064d3ddd00", - "name": "/v1/compacts/:compact/staff-users", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/compacts/:compact/staff-users", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "description": { - "content": "", - "type": "text/plain" - }, - "disabled": false, - "key": "Access-Control-Allow-Origin", - "value": "" - } - ], - "id": "f6195ee8-b64e-4893-a4a6-6d3a6dc4a12f", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "b2755dc9-d52a-4cff-a777-90f4b620047d", - "name": "/v1/compacts/:compact/staff-users/:userId", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/compacts/:compact/staff-users/:userId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "userId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "description": { - "content": "", - "type": "text/plain" - }, - "disabled": false, - "key": "Access-Control-Allow-Origin", - "value": "" - } - ], - "id": "ff17dff0-146d-45cb-a8da-d8ae414cb769", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - }, - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 404, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "33b13941-da68-4139-b292-78d80a65e7dc", - "name": "404 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [] - } - }, - "status": "Not Found" - } - ] - }, - { - "event": [], - "id": "1b4f0101-5b64-45c0-80c4-a1cd49535c02", - "name": "/v1/compacts/:compact/staff-users/:userId", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "DELETE", - "name": "/v1/compacts/:compact/staff-users/:userId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "userId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "ecd839cc-ba61-4083-95bd-aa680c301748", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "DELETE", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - }, - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 404, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "65646a0b-ca6d-4a2d-8126-cd2f25299dc8", - "name": "404 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "DELETE", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [] - } - }, - "status": "Not Found" - } - ] - }, - { - "event": [], - "id": "cbf95d6f-f5b2-4583-8610-e41700a748d6", - "name": "/v1/compacts/:compact/staff-users/:userId", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "name": "/v1/compacts/:compact/staff-users/:userId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "userId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "description": { - "content": "", - "type": "text/plain" - }, - "disabled": false, - "key": "Access-Control-Allow-Origin", - "value": "" - } - ], - "id": "b9442f93-4e9e-43c8-aa18-4306a45625ea", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - }, - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 404, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "858ab0b2-7cba-4745-b11d-d0e2a111cae5", - "name": "404 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId" + "name": "investigation" + } + ], + "name": "{licenseType}" + } + ], + "name": "licenseType" + } ], - "query": [], - "variable": [] + "name": "{jurisdiction}" } - }, - "status": "Not Found" + ], + "name": "jurisdiction" } - ] + ], + "name": "privileges" }, { "description": "", "item": [ { "event": [], - "id": "997abfd5-71da-47e6-acc9-f610c951a189", - "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", + "id": "ee66bd70-a141-45bb-af17-f72bd312451f", + "name": "/v1/compacts/:compact/providers/:providerId/ssn", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -3981,8 +2561,8 @@ "value": "application/json" } ], - "method": "POST", - "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", + "method": "GET", + "name": "/v1/compacts/:compact/providers/:providerId/ssn", "url": { "host": [ "{{baseUrl}}" @@ -3991,9 +2571,9 @@ "v1", "compacts", ":compact", - "staff-users", - ":userId", - "reinvite" + "providers", + ":providerId", + "ssn" ], "query": [], "variable": [ @@ -4013,7 +2593,7 @@ "type": "text/plain" }, "disabled": false, - "key": "userId", + "key": "providerId", "type": "any", "value": "" } @@ -4023,7 +2603,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", + "body": "{\n \"ssn\": \"241-00-9096\"\n}", "code": 200, "cookie": [], "header": [ @@ -4032,7 +2612,7 @@ "value": "application/json" } ], - "id": "aa1266af-7d68-42d7-bec9-d0eb27d1e8e9", + "id": "7b4f1c60-8ffc-4fe6-ae53-46b436c21682", "name": "200 response", "originalRequest": { "body": {}, @@ -4050,7 +2630,7 @@ "value": "" } ], - "method": "POST", + "method": "GET", "url": { "host": [ "{{baseUrl}}" @@ -4059,133 +2639,57 @@ "v1", "compacts", ":compact", - "staff-users", - ":userId", - "reinvite" + "providers", + ":providerId", + "ssn" ], "query": [], "variable": [] } }, "status": "OK" - }, - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 404, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "ea46b05f-ab59-4d7a-bfe5-439fc806a761", - "name": "404 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "compacts", - ":compact", - "staff-users", - ":userId", - "reinvite" - ], - "query": [], - "variable": [] - } - }, - "status": "Not Found" } ] } ], - "name": "reinvite" + "name": "ssn" } ], - "name": "{userId}" + "name": "{providerId}" } ], - "name": "staff-users" - } - ], - "name": "{compact}" - } - ], - "name": "compacts" - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ + "name": "providers" + }, { "description": "", "item": [ { "event": [], - "id": "4297e674-e814-426c-abf4-ca1c66004bde", - "name": "/v1/flags/:flagId/check", + "id": "1843493c-d44d-412a-80a2-b31a372acc8e", + "name": "/v1/compacts/:compact/staff-users", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { - "auth": { - "type": "noauth" - }, - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" - }, + "body": {}, "description": {}, "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "method": "POST", - "name": "/v1/flags/:flagId/check", + "method": "GET", + "name": "/v1/compacts/:compact/staff-users", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "flags", - ":flagId", - "check" + "compacts", + ":compact", + "staff-users" ], "query": [], "variable": [ @@ -4195,7 +2699,7 @@ "type": "text/plain" }, "disabled": false, - "key": "flagId", + "key": "compact", "type": "any", "value": "" } @@ -4205,48 +2709,52 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"enabled\": \"\"\n}", + "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"users\": [\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"tempor_8\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"laboris0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"eiusmod_01e\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"Ut_35\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"aliquip8\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n },\n {\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"dolore_97_\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"et1e6\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore2f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"in_450\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"pariatur_453\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"occaecat8\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"inactive\",\n \"userId\": \"\"\n }\n ]\n}", "code": 200, "cookie": [], "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "Access-Control-Allow-Origin", + "value": "" } ], - "id": "f6caa750-5414-4442-a2fa-0ef078eef64a", + "id": "e57c9b66-e024-4fba-892b-6ac2c10f0bd7", "name": "200 response", "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"key_0\": \"\",\n \"key_1\": \"\"\n }\n }\n}" - }, + "body": {}, "header": [ { - "key": "Content-Type", + "key": "Accept", "value": "application/json" }, { - "key": "Accept", - "value": "application/json" + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" } ], - "method": "POST", + "method": "GET", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "flags", - ":flagId", - "check" + "compacts", + ":compact", + "staff-users" ], "query": [], "variable": [] @@ -4255,94 +2763,15 @@ "status": "OK" } ] - } - ], - "name": "check" - } - ], - "name": "{flagId}" - } - ], - "name": "flags" - }, - { - "auth": { - "bearer": [ - { - "key": "token", - "type": "string", - "value": "{{idToken}}" - } - ], - "type": "bearer" - }, - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "event": [], - "id": "e21d0d72-bd18-47cc-8b5b-33b4352dc8b7", - "name": "/v1/provider-users/initiateRecovery", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"compact\": \"octp\",\n \"dob\": \"1928-01-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"occupational therapy assistant\",\n \"partialSocial\": \"4620\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/provider-users/initiateRecovery", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "initiateRecovery" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "aab9dc02-e8ff-4f4a-8dcf-8c53a2d3588d", - "name": "200 response", - "originalRequest": { + { + "event": [], + "id": "0edf07ff-3e0f-4575-99f2-84b99077cb7e", + "name": "/v1/compacts/:compact/staff-users", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { "body": { "mode": "raw", "options": { @@ -4351,8 +2780,9 @@ "language": "json" } }, - "raw": "{\n \"compact\": \"octp\",\n \"dob\": \"1928-01-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"occupational therapy assistant\",\n \"partialSocial\": \"4620\",\n \"password\": \"\",\n \"recaptchaToken\": \"\",\n \"username\": \"\"\n}" + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"in2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ad_4d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"pariatur_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"in24\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"deserunt15\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"ut_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, + "description": {}, "header": [ { "key": "Content-Type", @@ -4364,225 +2794,421 @@ } ], "method": "POST", + "name": "/v1/compacts/:compact/staff-users", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "provider-users", - "initiateRecovery" + "compacts", + ":compact", + "staff-users" ], "query": [], - "variable": [] + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + } + ] } }, - "status": "OK" - } - ] - } - ], - "name": "initiateRecovery" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "a764b1b7-b990-4ab1-8ed1-dd8e23abb02c", - "name": "/v1/provider-users/me", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/provider-users/me", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"birthMonthDay\": \"08-15\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"2385-12-29\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"licenses\": [\n {\n \"compact\": \"octp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2936-05-05\",\n \"dateOfIssuance\": \"2254-10-17\",\n \"dateOfRenewal\": \"2835-10-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"md\",\n \"previous\": {\n \"dateOfExpiration\": \"1800-04-06\",\n \"dateOfIssuance\": \"2972-08-07\",\n \"dateOfRenewal\": \"2438-09-14\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"6466950304\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2691-05-23\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+855527173535\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8394405807\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1842-07-31\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2300-08-05\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2863-08-03\",\n \"phoneNumber\": \"+3528187300\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2856-05-08\",\n \"licenseStatus\": \"inactive\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"mt\",\n \"previous\": {\n \"dateOfExpiration\": \"1918-10-05\",\n \"dateOfIssuance\": \"1476-12-15\",\n \"dateOfRenewal\": \"1145-11-31\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"0596273100\",\n \"compactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1028-12-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+9136425909\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2808925314\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1326-05-06\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2399-06-25\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2473-11-30\",\n \"phoneNumber\": \"+1013464869\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"2257-10-20\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"hi\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"occupational therapy assistant\",\n \"middleName\": \"\",\n \"providerId\": \"9b0bf21b-92f8-41d9-9df4-eaabd84adeee\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"providerId\": \"ad1db718-9d5c-4842-8b45-9b34ee4e0cca\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"octp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"vt\",\n \"licenseType\": \"\",\n \"providerId\": \"844db6f5-44af-4fc9-9221-3015ed96cbef\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"2619765297\",\n \"dateOfBirth\": \"1768-09-08\",\n \"ssnLastFour\": \"4600\",\n \"phoneNumber\": \"+59112126684\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1777-09-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2708-03-02\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ms\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"aeca0267-7733-48d0-a71c-d884e4754f14\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2396-01-09\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"2958-05-22\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2261-06-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba21be91-43db-4c7a-98de-607d32eb34b6\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1185-12-14\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"aslp\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"1169-12-27\",\n \"dateOfIssuance\": \"2302-07-11\",\n \"dateOfRenewal\": \"1624-03-08\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"hi\",\n \"previous\": {\n \"dateOfExpiration\": \"2804-01-30\",\n \"dateOfIssuance\": \"1778-03-30\",\n \"dateOfRenewal\": \"1452-12-01\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"8839374275\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2408-12-29\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+25666997428056\",\n \"licenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3527141483\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1539-11-04\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2033-10-28\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2946-02-27\",\n \"phoneNumber\": \"+11739899598\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1036-10-30\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"dc\",\n \"previous\": {\n \"dateOfExpiration\": \"2132-01-04\",\n \"dateOfIssuance\": \"2948-11-30\",\n \"dateOfRenewal\": \"1580-10-30\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"middleName\": \"\",\n \"homeAddressStreet2\": \"\",\n \"npi\": \"3443777389\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"2208-08-30\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+69171972\",\n \"licenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatusName\": \"\"\n },\n \"type\": \"licenseUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"homeAddressStreet2\": \"\",\n \"npi\": \"2199245845\",\n \"homeAddressPostalCode\": \"\",\n \"givenName\": \"\",\n \"homeAddressStreet1\": \"\",\n \"compactEligibility\": \"eligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"2289-12-31\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"dateOfIssuance\": \"2595-12-09\",\n \"emailAddress\": \"\",\n \"dateOfExpiration\": \"2635-11-09\",\n \"phoneNumber\": \"+538742548588950\",\n \"homeAddressState\": \"\",\n \"dateOfRenewal\": \"1656-08-16\",\n \"licenseStatus\": \"active\",\n \"familyName\": \"\",\n \"homeAddressCity\": \"\",\n \"licenseNumber\": \"\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n }\n ],\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ky\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"occupational therapist\",\n \"middleName\": \"\",\n \"providerId\": \"481ba68e-ab28-427f-befb-fba6bf3b3bae\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"f1c029aa-dae1-4ce3-a43c-1bf2320b7642\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wv\",\n \"licenseType\": \"\",\n \"providerId\": \"8c92261b-fea9-470a-8fe8-dcaca7ca8697\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"licenseNumber\": \"\",\n \"investigationStatus\": \"underInvestigation\",\n \"npi\": \"1690493112\",\n \"dateOfBirth\": \"1751-01-02\",\n \"ssnLastFour\": \"8079\",\n \"phoneNumber\": \"+106891529\",\n \"licenseStatusName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1394-05-20\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1355-11-25\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"mn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"6146bf62-a9f2-4c96-9d67-8c32cf05de02\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1150-11-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2103-02-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2104-10-21\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"bd5c9993-b19c-49a9-a241-6b67768bb1a5\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1567-09-31\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"militaryAffiliations\": [\n {\n \"affiliationType\": \"militaryMember\",\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2814-11-05\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"a6dad0d8-c0c7-4274-9d1f-a530a54209d2\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n },\n {\n \"affiliationType\": \"militaryMemberSpouse\",\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"2214-04-11\",\n \"fileNames\": [\n \"\",\n \"\"\n ],\n \"providerId\": \"ca2d993d-8064-4a9a-9db0-f22f39e37118\",\n \"status\": \"active\",\n \"type\": \"militaryAffiliation\",\n \"downloadLinks\": [\n {\n \"fileName\": \"\",\n \"url\": \"\"\n },\n {\n \"fileName\": \"\",\n \"url\": \"\"\n }\n ]\n }\n ],\n \"privilegeJurisdictions\": [\n \"az\",\n \"ga\"\n ],\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1994-11-05\",\n \"dateOfIssuance\": \"2425-09-15\",\n \"dateOfRenewal\": \"1310-11-08\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ms\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2993-03-27\",\n \"dateOfIssuance\": \"2072-10-31\",\n \"dateOfRenewal\": \"2114-11-25\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"mn\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"md\",\n \"type\": \"privilege\",\n \"providerId\": \"4d5725cf-ad64-45fe-b60c-2224209edc4a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"lifting_encumbrance\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"speech-language pathologist\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"mn\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"la\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1994-03-26\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1228-07-13\",\n \"privilegeId\": \"\",\n \"providerId\": \"38f48d47-72b6-4acd-9e61-da8cd93b2ad3\",\n \"dateOfRenewal\": \"2374-01-04\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ak\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2781-12-27\",\n \"dateOfIssuance\": \"1187-05-12\",\n \"dateOfRenewal\": \"2763-09-21\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"mo\",\n \"type\": \"privilege\",\n \"providerId\": \"a821d267-5653-49bd-97a2-bfc59170f24c\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"in\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"id\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1819-11-12\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2038-12-26\",\n \"privilegeId\": \"\",\n \"providerId\": \"ad9f72d8-19d3-4b8e-b7b7-618dfe5e9e58\",\n \"dateOfRenewal\": \"1113-11-05\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n }\n ],\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"vt\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"90bc19aa-772d-44ad-9cd6-3983304f2328\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"mi\",\n \"licenseType\": \"\",\n \"providerId\": \"2dc8351a-6416-47e7-9b6e-2a34491c71c7\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ri\",\n \"licenseType\": \"\",\n \"providerId\": \"fe1216ae-9c2c-4b40-8c29-1d6b42028e71\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1061-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1895-11-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"pr\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2712681d-bded-4d93-905c-69072cc9ad3e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2321-10-30\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1298-05-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2743-07-01\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"or\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"c166bbe2-1f54-44d6-87ec-baf461c0b04e\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2562-12-05\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compact\": \"coun\",\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1763-12-31\",\n \"dateOfIssuance\": \"1282-02-31\",\n \"dateOfRenewal\": \"1775-03-02\",\n \"dateOfUpdate\": \"\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"me\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"1299-03-12\",\n \"dateOfIssuance\": \"2556-09-24\",\n \"dateOfRenewal\": \"2525-12-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"nc\",\n \"privilegeId\": \"\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"sc\",\n \"type\": \"privilege\",\n \"providerId\": \"fcee0893-045b-4e62-993b-2e1a0d5b9a6a\",\n \"status\": \"active\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"deactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"licensed professional counselor\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"or\",\n \"compact\": \"octp\",\n \"jurisdiction\": \"oh\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"2189-04-21\",\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2561-09-02\",\n \"privilegeId\": \"\",\n \"providerId\": \"0e368060-5ad8-4526-a4d4-b3bb0ae375ee\",\n \"dateOfRenewal\": \"1197-12-28\",\n \"dateOfUpdate\": \"\",\n \"status\": \"active\"\n }\n },\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"nc\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"dateOfExpiration\": \"2675-12-14\",\n \"dateOfIssuance\": \"2405-08-11\",\n \"dateOfRenewal\": \"2288-09-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\",\n \"compact\": \"coun\",\n \"jurisdiction\": \"hi\",\n \"type\": \"privilege\",\n \"providerId\": \"b442dc00-b160-49c2-9e00-adc648fe8e78\",\n \"status\": \"inactive\"\n },\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"removedValues\": [\n \"\",\n \"\"\n ],\n \"licenseType\": \"occupational therapy assistant\",\n \"updatedValues\": {\n \"licenseJurisdiction\": \"md\",\n \"compact\": \"aslp\",\n \"jurisdiction\": \"ms\",\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"\"\n }\n ],\n \"type\": \"privilege\",\n \"compactTransactionId\": \"\",\n \"dateOfIssuance\": \"1632-10-06\",\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2989-04-09\",\n \"privilegeId\": \"\",\n \"providerId\": \"f3981490-3938-4976-b957-7123142c46de\",\n \"dateOfRenewal\": \"1356-01-30\",\n \"dateOfUpdate\": \"\",\n \"status\": \"inactive\"\n }\n }\n ],\n \"jurisdiction\": \"ky\",\n \"licenseJurisdiction\": \"fl\",\n \"licenseType\": \"occupational therapist\",\n \"privilegeId\": \"\",\n \"providerId\": \"11ec1f36-39ef-411d-9ed6-0218a17bde45\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"aslp\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"f78a6f18-b3bd-4937-9a6c-b48d88245048\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"coun\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"bf956261-d85b-4589-9cdb-446467415ef6\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"1572-08-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1663-10-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"nd\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"edea6027-5ae7-4c1f-a412-a29bed40d673\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1688-10-12\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"1780-08-11\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2812-12-13\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"f488d06a-d37c-4736-ba3a-c9d02cead4fa\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2996-12-11\",\n \"clinicalPrivilegeActionCategory\": \"\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"providerId\": \"9b5458d5-9c52-41cd-b3b1-95f5c4affc2c\",\n \"type\": \"provider\",\n \"npi\": \"2535279913\",\n \"compactEligibility\": \"ineligible\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"dateOfBirth\": \"1940-02-02\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"in\",\n \"ssnLastFour\": \"5671\",\n \"licenseStatus\": \"inactive\",\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ + "response": [ { - "key": "Content-Type", - "value": "application/json" + "_postman_previewlanguage": "json", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"ut_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"sint_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "Access-Control-Allow-Origin", + "value": "" + } + ], + "id": "559a260d-e052-489a-9950-63417a19a386", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"in2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"ad_4d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"pariatur_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"in24\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"deserunt15\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"ut_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" } - ], - "id": "2e29babb-949f-4960-b5cd-e641b2923e21", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" + ] + }, + { + "description": "", + "item": [ + { + "event": [], + "id": "4db34d3f-b006-4268-9476-f7d64d7d4bd4", + "name": "/v1/compacts/:compact/staff-users/:userId", + "protocolProfileBehavior": { + "disableBodyPruning": true }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" + "request": { + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "name": "/v1/compacts/:compact/staff-users/:userId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "userId", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"ut_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"sint_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "Access-Control-Allow-Origin", + "value": "" + } + ], + "id": "870f50a7-6d00-4f24-a5f4-b5d67292985e", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "GET", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "154faaa4-1c24-44e2-869e-f2862cca561c", - "name": "/v1/provider-users/me/email", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 404, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "b8110e99-3e98-4c48-ab29-d49f8a192d7a", + "name": "404 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "GET", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [] + } + }, + "status": "Not Found" } - }, - "raw": "{\n \"newEmailAddress\": \"\"\n}" + ] }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PATCH", - "name": "/v1/provider-users/me/email", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "email" - ], - "query": [], - "variable": [] - } - }, - "response": [ { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" + "event": [], + "id": "c8861bed-f262-46ff-a87e-e5d2b84bd431", + "name": "/v1/compacts/:compact/staff-users/:userId", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "DELETE", + "name": "/v1/compacts/:compact/staff-users/:userId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "userId", + "type": "any", + "value": "" + } + ] } - ], - "id": "334aee2c-a6ef-48a3-acf9-695813214900", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "cd81303c-fb93-47d0-884b-a2a8ef489a32", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "DELETE", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + }, + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 404, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "8c2b95ff-0be7-4233-80c6-94b695f41c7d", + "name": "404 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "DELETE", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [] } }, - "raw": "{\n \"newEmailAddress\": \"\"\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PATCH", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "email" - ], - "query": [], - "variable": [] + "status": "Not Found" } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ + ] + }, { "event": [], - "id": "1a449848-b642-4d2b-91f3-bce7b7f8f0f8", - "name": "/v1/provider-users/me/email/verify", + "id": "6eec9d2f-bab5-4cf1-ab88-ecf89c235830", + "name": "/v1/compacts/:compact/staff-users/:userId", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -4595,7 +3221,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"6671\"\n}" + "raw": "{\n \"permissions\": {\n \"Lorem4e3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitatione94\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"velit_f4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"sit67\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure_e95\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"incididunt_d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"minim_c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"culpa_96\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "description": {}, "header": [ @@ -4608,36 +3234,66 @@ "value": "application/json" } ], - "method": "POST", - "name": "/v1/provider-users/me/email/verify", + "method": "PATCH", + "name": "/v1/compacts/:compact/staff-users/:userId", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "provider-users", - "me", - "email", - "verify" + "compacts", + ":compact", + "staff-users", + ":userId" ], "query": [], - "variable": [] + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "userId", + "type": "any", + "value": "" + } + ] } }, "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"ut_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"sint_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "description": { + "content": "", + "type": "text/plain" + }, + "disabled": false, + "key": "Access-Control-Allow-Origin", + "value": "" } ], - "id": "63d859da-d627-49e6-84b5-0f85687795b4", + "id": "6ad281bf-49db-4a8e-bd14-c3d2cf3e8466", "name": "200 response", "originalRequest": { "body": { @@ -4648,7 +3304,7 @@ "language": "json" } }, - "raw": "{\n \"verificationCode\": \"6671\"\n}" + "raw": "{\n \"permissions\": {\n \"Lorem4e3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitatione94\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"velit_f4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"sit67\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure_e95\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"incididunt_d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"minim_c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"culpa_96\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" }, "header": [ { @@ -4668,403 +3324,278 @@ "value": "" } ], - "method": "POST", + "method": "PATCH", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "provider-users", - "me", - "email", - "verify" + "compacts", + ":compact", + "staff-users", + ":userId" ], "query": [], "variable": [] } }, "status": "OK" - } - ] - } - ], - "name": "verify" - } - ], - "name": "email" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "0e3cf433-7bf5-4222-a18c-e7370b5b0af6", - "name": "/v1/provider-users/me/home-jurisdiction", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"jurisdiction\": \"ia\"\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "PUT", - "name": "/v1/provider-users/me/home-jurisdiction", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "home-jurisdiction" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ + }, { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "b3a68e61-c492-42b9-a62d-d2c3ffe4caac", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 404, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - "raw": "{\n \"jurisdiction\": \"ia\"\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "PUT", - "url": { - "host": [ - "{{baseUrl}}" ], - "path": [ - "v1", - "provider-users", - "me", - "home-jurisdiction" - ], - "query": [], - "variable": [] + "id": "985f4152-e5aa-47bb-bf99-eaa8bb7bf91a", + "name": "404 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"permissions\": {\n \"Lorem4e3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"exercitatione94\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"velit_f4\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"sit67\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"irure_e95\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"incididunt_d\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"minim_c\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"culpa_96\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n }\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "PATCH", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId" + ], + "query": [], + "variable": [] + } + }, + "status": "Not Found" } - }, - "status": "OK" - } - ] - } - ], - "name": "home-jurisdiction" - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ + ] + }, { "description": "", "item": [ { - "description": "", - "item": [ - { - "description": "", - "item": [ + "event": [], + "id": "b845d68b-acb6-4566-8cea-7160e1b6f63a", + "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "name": "/v1/compacts/:compact/staff-users/:userId/reinvite", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId", + "reinvite" + ], + "query": [], + "variable": [ { - "event": [], - "id": "a5573123-c770-472d-aea9-e0fdc266d466", - "name": "/v1/provider-users/me/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "protocolProfileBehavior": { - "disableBodyPruning": true + "description": { + "content": "(Required) ", + "type": "text/plain" }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/provider-users/me/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - } - ] - } + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "f187e63c-a81d-4da7-88fb-af6a9b178320", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] + "disabled": false, + "key": "userId", + "type": "any", + "value": "" } - ], - "name": "history" - } - ], - "name": "{licenseType}" - } - ], - "name": "licenseType" - } - ], - "name": "{jurisdiction}" - } - ], - "name": "jurisdiction" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "257b92af-e83f-4677-9b8f-8bf35a995060", - "name": "/v1/provider-users/me/military-affiliation", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/provider-users/me/military-affiliation", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "military-affiliation" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"dateOfUpdate\": \"\",\n \"dateOfUpload\": \"1541-01-20\",\n \"documentUploadFields\": [\n {\n \"fields\": {\n \"key_0\": \"\",\n \"key_1\": \"\",\n \"key_2\": \"\"\n },\n \"url\": \"\"\n },\n {\n \"fields\": {\n \"key_0\": \"\"\n },\n \"url\": \"\"\n }\n ],\n \"status\": \"\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "dd95b21d-fb30-4eb7-8368-9074c44dff0b", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" + ] } }, - "raw": "{\n \"affiliationType\": \"militaryMemberSpouse\",\n \"fileNames\": [\n \"\",\n \"\"\n ]\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "12889872-2439-419b-bfe6-f4ac5296d7e7", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId", + "reinvite" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "military-affiliation" - ], - "query": [], - "variable": [] + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 404, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "a989bef5-59c8-4010-860c-6a04eb46262f", + "name": "404 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "staff-users", + ":userId", + "reinvite" + ], + "query": [], + "variable": [] + } + }, + "status": "Not Found" + } + ] } - }, - "status": "OK" + ], + "name": "reinvite" } - ] - }, + ], + "name": "{userId}" + } + ], + "name": "staff-users" + } + ], + "name": "{compact}" + } + ], + "name": "compacts" + }, + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ { "event": [], - "id": "e6755b3d-9e1c-4153-872d-fd3529b1237e", - "name": "/v1/provider-users/me/military-affiliation", + "id": "2dc2c237-6b12-492d-a3dc-d4e4246e9fca", + "name": "/v1/flags/:flagId/check", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { + "auth": { + "type": "noauth" + }, "body": { "mode": "raw", "options": { @@ -5073,7 +3604,7 @@ "language": "json" } }, - "raw": "{\n \"status\": \"inactive\"\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"enim_a52\": \"\"\n }\n }\n}" }, "description": {}, "header": [ @@ -5086,26 +3617,37 @@ "value": "application/json" } ], - "method": "PATCH", - "name": "/v1/provider-users/me/military-affiliation", + "method": "POST", + "name": "/v1/flags/:flagId/check", "url": { "host": [ "{{baseUrl}}" ], "path": [ "v1", - "provider-users", - "me", - "military-affiliation" + "flags", + ":flagId", + "check" ], "query": [], - "variable": [] + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "flagId", + "type": "any", + "value": "" + } + ] } }, "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", + "body": "{\n \"enabled\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -5114,7 +3656,7 @@ "value": "application/json" } ], - "id": "00c89a32-e0f2-4a3f-97f2-9539bc3e468c", + "id": "bbbc9e74-c0a9-4c47-a771-23047f9eba8a", "name": "200 response", "originalRequest": { "body": { @@ -5125,7 +3667,7 @@ "language": "json" } }, - "raw": "{\n \"status\": \"inactive\"\n}" + "raw": "{\n \"context\": {\n \"userId\": \"\",\n \"customAttributes\": {\n \"enim_a52\": \"\"\n }\n }\n}" }, "header": [ { @@ -5135,255 +3677,35 @@ { "key": "Accept", "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" } ], - "method": "PATCH", + "method": "POST", "url": { "host": [ "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "me", - "military-affiliation" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "military-affiliation" - } - ], - "name": "me" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "7a395ce2-3d57-4e53-8f28-bbafb685d43e", - "name": "/v1/provider-users/registration", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"compact\": \"\",\n \"dob\": \"2895-11-06\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/provider-users/registration", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "registration" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "148da7ca-0bc4-4cde-9d70-d6ebf187f8f2", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"compact\": \"\",\n \"dob\": \"2895-11-06\",\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"speech-language pathologist\",\n \"partialSocial\": \"\",\n \"token\": \"\"\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "registration" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "registration" - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "1ae7f606-a7b2-40f3-a0ac-cfe256c74a18", - "name": "/v1/provider-users/verifyRecovery", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/provider-users/verifyRecovery", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "verifyRecovery" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "19213d8a-2ffe-4638-9219-38ed3bf87dd8", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" + ], + "path": [ + "v1", + "flags", + ":flagId", + "check" + ], + "query": [], + "variable": [] } }, - "raw": "{\n \"compact\": \"octp\",\n \"providerId\": \"4ea47a54-7e2f-4daf-972c-3ccb4e0769d5\",\n \"recaptchaToken\": \"\",\n \"recoveryToken\": \"\"\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "provider-users", - "verifyRecovery" - ], - "query": [], - "variable": [] + "status": "OK" } - }, - "status": "OK" + ] } - ] + ], + "name": "check" } ], - "name": "verifyRecovery" + "name": "{flagId}" } ], - "name": "provider-users" + "name": "flags" }, { "description": "", @@ -5399,7 +3721,7 @@ "item": [ { "event": [], - "id": "a59f2d74-1c6e-4a97-8738-97cb1be17b72", + "id": "b6131286-d4e6-4d53-9719-769ba39fddd4", "name": "/v1/public/compacts/:compact/jurisdictions", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5456,7 +3778,7 @@ "value": "application/json" } ], - "id": "72e88f34-ee26-44ac-a566-c164190a4b7e", + "id": "6561b204-026c-42f0-8f26-bfedb6d0bce4", "name": "200 response", "originalRequest": { "body": {}, @@ -5497,7 +3819,7 @@ "item": [ { "event": [], - "id": "41c7aac2-b45e-459d-a159-1c6fd4ae71b5", + "id": "24b84b65-0295-48c4-9347-82ba87948f16", "name": "/v1/public/compacts/:compact/providers/query", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5514,7 +3836,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"36fe3faf-ce25-4c63-b5f3-abb953a85e3f\",\n \"jurisdiction\": \"nm\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"descending\"\n }\n}" + "raw": "{\n \"query\": {\n \"providerId\": \"c733437a-07c0-4155-821e-6f2589725b0d\",\n \"jurisdiction\": \"wa\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" }, "description": {}, "header": [ @@ -5559,7 +3881,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"compact\": \"aslp\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"vi\",\n \"privilegeJurisdictions\": [\n \"la\",\n \"ga\"\n ],\n \"providerId\": \"2e8a387a-728c-4578-8f51-75b06d14524f\",\n \"type\": \"provider\",\n \"npi\": \"7333815991\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"tn\",\n \"dateOfUpdate\": \"\"\n },\n {\n \"compact\": \"coun\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"ny\",\n \"privilegeJurisdictions\": [\n \"nc\",\n \"in\"\n ],\n \"providerId\": \"f06facba-de4b-4655-b9bb-174711a35f80\",\n \"type\": \"provider\",\n \"npi\": \"3402315844\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"ks\",\n \"dateOfUpdate\": \"\"\n }\n ],\n \"query\": {\n \"providerId\": \"c3e96162-5b8f-403a-bc61-06f2eb9664e6\",\n \"jurisdiction\": \"ct\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"ascending\"\n }\n}", + "body": "{\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n },\n \"providers\": [\n {\n \"compact\": \"cosm\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"tn\",\n \"providerId\": \"81f5f680-c5fe-4751-abf7-b58eafecc781\",\n \"type\": \"provider\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"dateOfUpdate\": \"\"\n },\n {\n \"compact\": \"cosm\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"providerId\": \"ece09f14-4973-4a2c-89d7-697ea8b35a09\",\n \"type\": \"provider\",\n \"middleName\": \"\",\n \"suffix\": \"\",\n \"dateOfUpdate\": \"\"\n }\n ],\n \"query\": {\n \"providerId\": \"03e0d1e9-573f-4dfb-8408-53c5a5db09a7\",\n \"jurisdiction\": \"tn\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}", "code": 200, "cookie": [], "header": [ @@ -5568,7 +3890,7 @@ "value": "application/json" } ], - "id": "8997fc38-2c70-4bc4-a3bc-ebe512fd7df8", + "id": "6dee8d37-f8db-4b56-ae2b-2c12e429abf5", "name": "200 response", "originalRequest": { "body": { @@ -5579,7 +3901,7 @@ "language": "json" } }, - "raw": "{\n \"query\": {\n \"providerId\": \"36fe3faf-ce25-4c63-b5f3-abb953a85e3f\",\n \"jurisdiction\": \"nm\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"familyName\",\n \"direction\": \"descending\"\n }\n}" + "raw": "{\n \"query\": {\n \"providerId\": \"c733437a-07c0-4155-821e-6f2589725b0d\",\n \"jurisdiction\": \"wa\",\n \"givenName\": \"\",\n \"familyName\": \"\"\n },\n \"pagination\": {\n \"lastKey\": \"\",\n \"pageSize\": \"\"\n },\n \"sorting\": {\n \"key\": \"dateOfUpdate\",\n \"direction\": \"descending\"\n }\n}" }, "header": [ { @@ -5620,7 +3942,7 @@ "item": [ { "event": [], - "id": "591e37e5-f41b-43f7-ab3b-275447a86d24", + "id": "aba4e8f3-284a-46b2-94a6-70d7fe028150", "name": "/v1/public/compacts/:compact/providers/:providerId", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5638,247 +3960,86 @@ } ], "method": "GET", - "name": "/v1/public/compacts/:compact/providers/:providerId", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "public", - "compacts", - ":compact", - "providers", - ":providerId" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"al\",\n \"privilegeJurisdictions\": [\n \"ct\",\n \"nc\"\n ],\n \"providerId\": \"b8e9a88a-6649-48c9-9c4d-39a0256d5dde\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"octp\",\n \"dateOfExpiration\": \"1986-10-20\",\n \"dateOfIssuance\": \"1662-10-30\",\n \"dateOfRenewal\": \"2946-10-06\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"ct\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"3b950d69-722f-4c6a-8d96-f988b8a8fad0\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"coun\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ct\",\n \"licenseType\": \"licensed professional counselor\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2147-05-30\",\n \"dateOfIssuance\": \"1588-06-19\",\n \"dateOfRenewal\": \"2566-04-30\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ny\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"02e716d2-a904-4f81-8c22-8bfdebb4bec2\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"licenseDeactivation\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2930-09-23\",\n \"licenseJurisdiction\": \"nm\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2362-09-15\",\n \"dateOfIssuance\": \"2685-12-30\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"occupational therapy assistant\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1953-10-07\",\n \"dateOfIssuance\": \"2767-07-04\",\n \"dateOfRenewal\": \"1924-05-31\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"dc\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"9867acc2-b67e-43fa-addd-4432e61d6be9\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1648-11-14\",\n \"licenseJurisdiction\": \"la\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"2787-03-16\",\n \"dateOfIssuance\": \"2519-09-24\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1625-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1012-11-28\",\n \"jurisdiction\": \"nm\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"e06b09b2-88aa-421c-83e3-22297d0f8de4\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2114-10-03\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"octp\",\n \"creationDate\": \"2560-11-14\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1129-02-10\",\n \"jurisdiction\": \"ut\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ba43fcc4-1327-4e36-a033-7380ee054b98\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2025-05-08\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"coun\",\n \"dateOfExpiration\": \"1991-06-30\",\n \"dateOfIssuance\": \"1735-11-23\",\n \"dateOfRenewal\": \"1886-12-22\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"sd\",\n \"licenseJurisdiction\": \"in\",\n \"licenseType\": \"audiologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"1cf5fc6f-c5d3-4324-ba23-b164fc54ee2c\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"octp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"sc\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2252-07-16\",\n \"dateOfIssuance\": \"2133-12-30\",\n \"dateOfRenewal\": \"1267-10-14\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"tn\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"348f1664-5e02-42d7-a17f-0e493a567cf2\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"homeJurisdictionChange\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1846-02-17\",\n \"licenseJurisdiction\": \"nh\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1052-04-04\",\n \"dateOfIssuance\": \"1717-10-30\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"aslp\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"occupational therapist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1435-11-01\",\n \"dateOfIssuance\": \"1058-12-02\",\n \"dateOfRenewal\": \"1021-07-10\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ak\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"863e4569-58f7-4fa4-a7c7-2e97779e1cd9\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1516-11-31\",\n \"licenseJurisdiction\": \"de\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1017-09-30\",\n \"dateOfIssuance\": \"2217-12-20\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"aslp\",\n \"creationDate\": \"1258-11-25\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2168-12-30\",\n \"jurisdiction\": \"il\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"4487cfc6-97ad-46d7-a942-433ce98423bc\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"1268-09-30\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"coun\",\n \"creationDate\": \"2019-04-04\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1250-06-30\",\n \"jurisdiction\": \"mt\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"9ca3a75b-ca10-47db-8884-b9f1df71ae62\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2878-11-30\"\n }\n ]\n }\n ],\n \"npi\": \"1820740143\",\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"pr\",\n \"middleName\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "78220f24-ba91-48b4-bca6-26cc84183cd3", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "public", - "compacts", - ":compact", - "providers", - ":providerId" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "event": [], - "id": "fd716311-dce7-4a37-979f-304a141f0b43", - "name": "/v1/public/compacts/:compact/providers/:providerId/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/public/compacts/:compact/providers/:providerId/jurisdiction/:jurisdiction/licenseType/:licenseType/history", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "public", - "compacts", - ":compact", - "providers", - ":providerId", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [ - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "compact", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "providerId", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "jurisdiction", - "type": "any", - "value": "" - }, - { - "description": { - "content": "(Required) ", - "type": "text/plain" - }, - "disabled": false, - "key": "licenseType", - "type": "any", - "value": "" - } - ] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"compact\": \"octp\",\n \"events\": [\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"1230-11-04\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n },\n {\n \"createDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"effectiveDate\": \"2425-11-31\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"registration\",\n \"note\": \"\"\n }\n ],\n \"jurisdiction\": \"in\",\n \"licenseType\": \"speech-language pathologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"d89a7efe-5041-4146-96a6-3f76cf1296f6\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "231c170a-79c9-4426-9ab8-9a76fb6dbd78", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "public", - "compacts", - ":compact", - "providers", - ":providerId", - "jurisdiction", - ":jurisdiction", - "licenseType", - ":licenseType", - "history" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "history" - } - ], - "name": "{licenseType}" - } - ], - "name": "licenseType" + "name": "/v1/public/compacts/:compact/providers/:providerId", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "public", + "compacts", + ":compact", + "providers", + ":providerId" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "providerId", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"licenseJurisdiction\": \"oh\",\n \"providerId\": \"5dc5f036-0f82-4d65-acdd-c673852d655c\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"2181-12-31\",\n \"dateOfIssuance\": \"2093-09-26\",\n \"dateOfRenewal\": \"2045-11-30\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseJurisdiction\": \"ks\",\n \"licenseType\": \"esthetician\",\n \"privilegeId\": \"\",\n \"providerId\": \"6c1d26d9-1789-46bb-af1a-7ef7f3b7b1bb\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"cosmetologist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2901-03-30\",\n \"dateOfIssuance\": \"1539-10-30\",\n \"dateOfRenewal\": \"2221-12-04\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"ky\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"9ae6d942-4633-4721-8c48-02327c647d4c\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1649-03-17\",\n \"licenseJurisdiction\": \"co\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1709-04-07\",\n \"dateOfIssuance\": \"2660-01-03\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"cosmetologist\",\n \"previous\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2407-11-06\",\n \"dateOfIssuance\": \"1350-10-31\",\n \"dateOfRenewal\": \"2063-02-10\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"md\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"11e5feb5-92e5-48c9-8df8-ca958b9b6f11\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"expiration\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"1079-04-04\",\n \"licenseJurisdiction\": \"tn\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1272-12-31\",\n \"dateOfIssuance\": \"2944-04-25\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1235-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2007-07-30\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"821c1b90-12be-4961-9b22-4ef5a77b2967\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2566-11-30\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2815-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1674-06-30\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"717394d0-91d7-485b-a816-b01a876e2a10\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2082-12-05\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"2146-08-30\",\n \"dateOfIssuance\": \"2718-02-02\",\n \"dateOfRenewal\": \"2840-07-24\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseJurisdiction\": \"tn\",\n \"licenseType\": \"cosmetologist\",\n \"privilegeId\": \"\",\n \"providerId\": \"107adedf-f12e-4cc8-b98a-2dd121dec8b9\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"history\": [\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"esthetician\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2469-10-30\",\n \"dateOfIssuance\": \"2950-12-30\",\n \"dateOfRenewal\": \"1215-11-06\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"50dd9756-57e9-484b-bef5-72b418f5e06b\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"renewal\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"1633-10-30\",\n \"licenseJurisdiction\": \"tn\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1006-04-12\",\n \"dateOfIssuance\": \"1337-01-08\",\n \"dateOfUpdate\": \"\"\n }\n },\n {\n \"compact\": \"cosm\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"cosmetologist\",\n \"previous\": {\n \"administratorSetStatus\": \"active\",\n \"dateOfExpiration\": \"2831-10-06\",\n \"dateOfIssuance\": \"2198-09-07\",\n \"dateOfRenewal\": \"2471-05-03\",\n \"dateOfUpdate\": \"\",\n \"licenseJurisdiction\": \"az\",\n \"privilegeId\": \"\"\n },\n \"providerId\": \"c89d6707-693d-42f2-8d1e-e0589dbc2d54\",\n \"type\": \"privilegeUpdate\",\n \"updateType\": \"other\",\n \"updatedValues\": {\n \"administratorSetStatus\": \"inactive\",\n \"dateOfExpiration\": \"2419-06-28\",\n \"licenseJurisdiction\": \"ky\",\n \"privilegeId\": \"\",\n \"dateOfRenewal\": \"1946-12-02\",\n \"dateOfIssuance\": \"2261-12-14\",\n \"dateOfUpdate\": \"\"\n }\n }\n ],\n \"adverseActions\": [\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2096-11-27\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1080-03-02\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"2617c414-9b0a-43fc-a835-0812e75eec7b\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2616-11-31\"\n },\n {\n \"actionAgainst\": \"\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2819-09-13\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1476-07-31\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"300a82a6-1932-493d-8c32-0217d7a5eeeb\",\n \"type\": \"adverseAction\",\n \"effectiveLiftDate\": \"2782-07-30\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"suffix\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" } ], - "name": "{jurisdiction}" + "id": "d1f8f3ed-d8b8-49db-b8b5-fcb629bf8b28", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "public", + "compacts", + ":compact", + "providers", + ":providerId" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" } - ], - "name": "jurisdiction" + ] } ], "name": "{providerId}" @@ -5900,7 +4061,7 @@ "item": [ { "event": [], - "id": "52dc84cd-5caa-447f-a9c6-0f67edb88947", + "id": "d2ac2ab2-f405-48eb-a33c-17315188070c", "name": "/v1/public/jurisdictions/live", "protocolProfileBehavior": { "disableBodyPruning": true @@ -5946,7 +4107,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"key_0\": [\n \"ut\",\n \"wv\"\n ]\n}", + "body": "{\n \"incididunt_6\": [\n \"az\",\n \"md\"\n ],\n \"eu_df\": [\n \"al\",\n \"md\"\n ]\n}", "code": 200, "cookie": [], "header": [ @@ -5955,7 +4116,7 @@ "value": "application/json" } ], - "id": "c7228003-70c9-4263-8f12-b132ff502951", + "id": "2cd3043d-b8c1-4b1e-9021-fc8479b992fe", "name": "200 response", "originalRequest": { "body": {}, @@ -6011,211 +4172,7 @@ "item": [ { "event": [], - "id": "77058b43-efa8-4c30-bba6-f9d1edf387db", - "name": "/v1/purchases/privileges", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"978179715\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"96663263\"\n }\n ],\n \"licenseType\": \"occupational therapist\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vt\",\n \"mt\"\n ]\n}" - }, - "description": {}, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "POST", - "name": "/v1/purchases/privileges", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "purchases", - "privileges" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"transactionId\": \"\",\n \"message\": \"\"\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "6d02f8ec-d2a0-43e6-a831-016fadbf70fc", - "name": "200 response", - "originalRequest": { - "body": { - "mode": "raw", - "options": { - "raw": { - "headerFamily": "json", - "language": "json" - } - }, - "raw": "{\n \"attestations\": [\n {\n \"attestationId\": \"\",\n \"version\": \"978179715\"\n },\n {\n \"attestationId\": \"\",\n \"version\": \"96663263\"\n }\n ],\n \"licenseType\": \"occupational therapist\",\n \"orderInformation\": {\n \"opaqueData\": {\n \"dataDescriptor\": \"\",\n \"dataValue\": \"\"\n }\n },\n \"selectedJurisdictions\": [\n \"vt\",\n \"mt\"\n ]\n}" - }, - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "POST", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "purchases", - "privileges" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - }, - { - "description": "", - "item": [ - { - "event": [], - "id": "44420427-e1a1-4f6a-8c55-c446bd397150", - "name": "/v1/purchases/privileges/options", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "body": {}, - "description": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "method": "GET", - "name": "/v1/purchases/privileges/options", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "purchases", - "privileges", - "options" - ], - "query": [], - "variable": [] - } - }, - "response": [ - { - "_postman_previewlanguage": "json", - "body": "{\n \"items\": [\n {\n \"compactAbbr\": \"\",\n \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactName\": \"\",\n \"isSandbox\": \"\",\n \"paymentProcessorPublicFields\": {\n \"apiLoginId\": \"\",\n \"publicClientKey\": \"\"\n },\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n },\n \"type\": \"compact\"\n },\n {\n \"compactAbbr\": \"\",\n \"compactCommissionFee\": {\n \"feeAmount\": \"\",\n \"feeType\": \"FLAT_RATE\"\n },\n \"compactName\": \"\",\n \"isSandbox\": \"\",\n \"paymentProcessorPublicFields\": {\n \"apiLoginId\": \"\",\n \"publicClientKey\": \"\"\n },\n \"transactionFeeConfiguration\": {\n \"licenseeCharges\": {\n \"active\": \"\",\n \"chargeAmount\": \"\",\n \"chargeType\": \"FLAT_FEE_PER_PRIVILEGE\"\n }\n },\n \"type\": \"compact\"\n }\n ],\n \"pagination\": {\n \"prevLastKey\": {},\n \"lastKey\": {},\n \"pageSize\": \"\"\n }\n}", - "code": 200, - "cookie": [], - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "id": "0211181c-f18a-4ee5-974a-3ffd066af744", - "name": "200 response", - "originalRequest": { - "body": {}, - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "description": { - "content": "Added as a part of security scheme: apikey", - "type": "text/plain" - }, - "key": "Authorization", - "value": "" - } - ], - "method": "GET", - "url": { - "host": [ - "{{baseUrl}}" - ], - "path": [ - "v1", - "purchases", - "privileges", - "options" - ], - "query": [], - "variable": [] - } - }, - "status": "OK" - } - ] - } - ], - "name": "options" - } - ], - "name": "privileges" - } - ], - "name": "purchases" - }, - { - "description": "", - "item": [ - { - "description": "", - "item": [ - { - "event": [], - "id": "eaef01d3-0498-4681-8a8b-8f61c1fa42bb", + "id": "c2329ec3-90ae-4a65-9c64-e408ebd0c5be", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6247,7 +4204,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"ut_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"sint_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -6265,7 +4222,7 @@ "value": "" } ], - "id": "50509350-3bf2-4d40-9a12-ca779d159cd6", + "id": "c186378c-c02d-452d-9b7d-263296604eda", "name": "200 response", "originalRequest": { "body": {}, @@ -6310,7 +4267,7 @@ "value": "application/json" } ], - "id": "3d2c2a65-b4d5-4c77-aadc-794c9e50f644", + "id": "3c20f6cf-6901-4fa8-b954-8743726f42dc", "name": "404 response", "originalRequest": { "body": {}, @@ -6348,7 +4305,7 @@ }, { "event": [], - "id": "0c887909-602d-46f4-9d1c-b55d4c08c53e", + "id": "629604d2-4fa5-4c22-9398-afe5db943ffe", "name": "/v1/staff-users/me", "protocolProfileBehavior": { "disableBodyPruning": true @@ -6393,7 +4350,7 @@ "response": [ { "_postman_previewlanguage": "json", - "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"key_1\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n },\n \"key_3\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"key_0\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", + "body": "{\n \"attributes\": {\n \"email\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\"\n },\n \"permissions\": {\n \"ut_b\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"readSSN\": \"\"\n },\n \"jurisdictions\": {\n \"sint_f\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n },\n \"dolore_2\": {\n \"actions\": {\n \"readPrivate\": \"\",\n \"admin\": \"\",\n \"write\": \"\",\n \"readSSN\": \"\"\n }\n }\n }\n }\n },\n \"status\": \"active\",\n \"userId\": \"\"\n}", "code": 200, "cookie": [], "header": [ @@ -6411,7 +4368,7 @@ "value": "" } ], - "id": "fec1dfd7-6449-47c0-a6b6-d0757a6f4f48", + "id": "1de4832e-dd0c-4d2c-8006-810835aa1338", "name": "200 response", "originalRequest": { "body": { @@ -6469,7 +4426,7 @@ "value": "application/json" } ], - "id": "f6663f03-5203-4ec2-a071-fd93d1b627aa", + "id": "d722ab4c-7de5-4ea2-83dc-16c0e0feb411", "name": "404 response", "originalRequest": { "body": { diff --git a/backend/cosmetology-app/docs/it_staff_onboarding_instructions.md b/backend/cosmetology-app/docs/it_staff_onboarding_instructions.md index 0efb59ad5..d11a40a69 100644 --- a/backend/cosmetology-app/docs/it_staff_onboarding_instructions.md +++ b/backend/cosmetology-app/docs/it_staff_onboarding_instructions.md @@ -123,7 +123,7 @@ curl --location --request POST 'https://state-api.beta.compactconnect.org/v1/com "licenseStatusName":"Active", "licenseStatus":"active", "compactEligibility":"eligible", - "licenseType":"cosmetology", + "licenseType":"cosmetologist", "givenName":"Jane", "middleName":"Marie", "familyName":"Smith", @@ -137,8 +137,7 @@ curl --location --request POST 'https://state-api.beta.compactconnect.org/v1/com "homeAddressState":"KY", "homeAddressPostalCode":"40202", "emailAddress":"jane.smith@example.com", - "phoneNumber":"+15555551234", - "npi":"1234567890" + "phoneNumber":"+15555551234" } ]' ``` @@ -296,162 +295,6 @@ validation errors in the response body. > **Note**: 200 status code means your request passed synchronous validation and was accepted for processing. Ingest and > downstream processing are asynchronous and may take several minutes. -## Retrieving Data from CompactConnect - -In addition to uploading license data, state IT departments can retrieve privilege data from CompactConnect to integrate with their own systems. The State API provides two primary endpoints for data retrieval: a query endpoint for bulk retrieval of updated records, and a get endpoint for retrieving details about specific providers. - -> **Important**: Data retrieval endpoints require additional security through request signature authentication. Please see the [Client Signature Authentication documentation](./client_signature_auth.md) for detailed information about implementing request signing before attempting to use these endpoints. - -### Required OAuth Scope for Data Retrieval - -To retrieve data from the CompactConnect API, you must request the `/readGeneral` OAuth scope when generating your access token. This scope grants read access to provider and privilege data for your jurisdiction. - -If your integration needs both to upload license data AND retrieve privilege data within the same access token, you can request multiple OAuth scopes at the same time by separating them with a space in the scope parameter: - -```bash -curl --location --request POST '' \ ---header 'Content-Type: application/x-www-form-urlencoded' \ ---header 'Accept: application/json' \ ---data-urlencode 'grant_type=client_credentials' \ ---data-urlencode 'client_id=' \ ---data-urlencode 'client_secret=' \ ---data-urlencode 'scope=/.write /readGeneral' -``` - -Replace: -- `` with your client ID -- `` with your client secret -- `` with your lower-cased two-letter state code (e.g., `ky`) -- `` with the lower-cased compact abbreviation (`cosm`) - -**OAuth Scope Reference**: -- `/.write` - Required for uploading license data -- `/readGeneral` - Required for retrieving provider and privilege data - -The access token you receive will be authorized for both operations. If this request fails, your credentials may have not been granted the ability to grant the read scope for the specific compact when they were created. Reach out to your CompactConnect representative to request this permission if needed. - -### Query Providers Endpoint - -The query endpoint allows you to retrieve a list of providers who have privileges in your jurisdiction, filtered by when their records were last updated. This is useful for identifying which providers have had changes that need to be synchronized to your systems. - -**Endpoint**: `POST /v1/compacts//jurisdictions//providers/query` - -**Request Body**: -```json -{ - "query": { - "startDateTime": "2024-10-01T00:00:00Z", - "endDateTime": "2024-10-07T23:59:59Z" - } -} -``` - -**Example Response**: -```json -{ - "query": { - "startDateTime": "2024-10-01T00:00:00Z", - "endDateTime": "2024-10-07T23:59:59Z" - }, - "sorting": { - "direction": "ascending" - }, - "providers": [ - { - "providerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "type": "provider", - "compact": "cosm", - "dateOfUpdate": "2024-10-05T14:32:10.123Z", - "licenseJurisdiction": "ky", - "licenseStatus": "active", - "compactEligibility": "eligible", - "givenName": "Jane", - "middleName": "Marie", - "familyName": "Smith", - "birthMonthDay": "05-20", - "dateOfExpiration": "2025-01-14", - "jurisdictionUploadedLicenseStatus": "active", - "jurisdictionUploadedCompactEligibility": "eligible", - "privilegeJurisdictions": ["oh", "tn", "va"] - } - ], - "pagination": { - "lastKey": "eyJwayI6InByb3ZpZGVyI..." - } -} -``` - -**Response Fields**: -- `providerId`: Unique identifier for the provider in CompactConnect -- `dateOfUpdate`: When the provider record was last updated -- `privilegeJurisdictions`: Array of jurisdictions where the provider currently has active privileges -- `pagination.lastKey`: Token to use for retrieving the next page of results (include in next request's `pagination.lastKey` field) - -#### Pagination - -When querying for providers, results are paginated. To retrieve additional pages: - -1. Check the response for the `pagination.lastKey` field -2. If present, include it in your next request's `pagination.lastKey` to get the next page -3. Continue until `pagination.lastKey` is not present in the response - -**Example of retrieving next page**: -```json -{ - "query": { - "startDateTime": "2024-10-01T00:00:00Z", - "endDateTime": "2024-10-07T23:59:59Z" - }, - "pagination": { - "lastKey": "eyJwayI6InByb3ZpZGVyI..." - } -} -``` - -### Get Provider Details Endpoint - -The get provider endpoint returns detailed information about a specific provider's privileges in your jurisdiction. - -**Endpoint**: `GET /v1/compacts//jurisdictions//providers/{providerId}` - -**Example Response**: -```json -{ - "privileges": [ - { - "type": "statePrivilege", - "providerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "privilegeId": "priv_xyz123", - "compact": "cosm", - "jurisdiction": "oh", - "licenseType": "cosmetology", - "status": "active", - "compactEligibility": "eligible", - "dateOfIssuance": "2023-01-15", - "dateOfRenewal": "2023-01-15", - "dateOfExpiration": "2025-01-14", - "dateOfUpdate": "2024-10-05T14:32:10.123Z", - "licenseJurisdiction": "ky", - "licenseStatus": "active", - "givenName": "Jane", - "middleName": "Marie", - "familyName": "Smith", - "npi": "1234567890", - "licenseNumber": "LIC123456", - "licenseStatusName": "Active" - } - ], - "providerUIUrl": "https://app.beta.cosmetology.compactconnect.org/cosm/Licensing/a1b2c3d4-e5f6-7890-abcd-ef1234567890" -} -``` - -**Response Fields**: -- `privileges`: Array of privilege records for this provider in your jurisdiction (typically one per license type) -- `providerUIUrl`: Direct link to view this provider's details in the CompactConnect web interface -- Each privilege record combines data from both the privilege and the underlying license record - -**Note**: A provider may have multiple privileges in your jurisdiction if they hold multiple license types. - ## Troubleshooting Common Issues ### 1. "Unknown error parsing request body" diff --git a/backend/cosmetology-app/docs/postman/postman-collection.json b/backend/cosmetology-app/docs/postman/postman-collection.json new file mode 100644 index 000000000..1e465c6b0 --- /dev/null +++ b/backend/cosmetology-app/docs/postman/postman-collection.json @@ -0,0 +1,824 @@ +{ + "auth": { + "bearer": [ + { + "key": "token", + "type": "string", + "value": "{{accessToken}}" + } + ], + "type": "bearer" + }, + "info": { + "_postman_id": "6fb7c971-8f65-4951-bbe4-51fdb423628b", + "description": { + "content": "", + "type": "text/plain" + }, + "name": "CompactConnect API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "item": [ + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "client-credentials-grant", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "client_credentials" + }, + { + "key": "client_id", + "value": "{{m2mClientId}}" + }, + { + "key": "client_secret", + "value": "{{m2mClientSecret}}" + }, + { + "key": "scope", + "value": "{{m2mScopes}}" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=client_credentials&client_id={{m2mClientId}}&client_secret={{m2mClientSecret}}&scope={{m2mScopes}}" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});", + "", + "pm.test('Identity token returned', () => {", + " var id_token = pm.response.json().id_token;", + " pm.expect(id_token).not.to.be.empty;", + " pm.environment.set(\"idToken\", id_token);", + " console.log('id token: ' + id_token);", + "});", + "", + "pm.test('Refresh token returned', () => {", + " var refresh_token = pm.response.json().refresh_token;", + " pm.expect(refresh_token).not.to.be.empty;", + " pm.environment.set(\"refreshToken\", refresh_token);", + " console.log('refresh token: ' + refresh_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-grant-token", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "authorization_code" + }, + { + "key": "code", + "value": "f23723c3-1d21-40e1-89ec-64807d2d658d" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "scope", + "value": "openid" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=authorization_code&code=f23723c3-1d21-40e1-89ec-64807d2d658d&client_id={{clientId}}&scope=openid&redirect_uri=http://localhost:3018/auth/callback" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-authorize", + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "disabled": true, + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "disabled": true, + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "authorize" + ], + "query": [ + { + "key": "response_type", + "value": "code" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + }, + { + "key": "scope", + "value": "openid" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/authorize?response_type=code&client_id={{clientId}}&redirect_uri=http://localhost:3018/auth/callback&scope=openid" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-login", + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "authorize" + ], + "query": [ + { + "key": "scope", + "value": "openid" + }, + { + "key": "response_type", + "value": "code" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + }, + { + "key": "identity_provider", + "value": "COGNITO" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/authorize?scope=openid&response_type=code&client_id={{clientId}}&redirect_uri=http://localhost:3018/auth/callback&identity_provider=COGNITO" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});", + "", + "pm.test('Identity token returned', () => {", + " var id_token = pm.response.json().id_token;", + " pm.environment.set(\"idToken\", id_token);", + " console.log('id token: ' + id_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "refresh-token", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "refresh_token" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "refresh_token", + "value": "{{refreshToken}}" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=refresh_token&client_id={{clientId}}&refresh_token={{refreshToken}}" + } + }, + "response": [] + } + ], + "name": "Staff-Auth" + }, + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "event": [], + "id": "baf591ab-5362-4397-9ee9-02584f1325dc", + "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1316-06-30\",\n \"dateOfExpiration\": \"2363-08-17\",\n \"dateOfIssuance\": \"1788-09-12\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"880-95-8789\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+174115348190029\",\n \"dateOfRenewal\": \"1117-10-19\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1037-01-08\",\n \"dateOfExpiration\": \"1103-12-26\",\n \"dateOfIssuance\": \"2013-04-24\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"936-05-5969\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+54522060278\",\n \"dateOfRenewal\": \"1817-03-09\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "jurisdictions", + ":jurisdiction", + "licenses" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "46229a89-2285-4824-91a0-667c289dfb26", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1316-06-30\",\n \"dateOfExpiration\": \"2363-08-17\",\n \"dateOfIssuance\": \"1788-09-12\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"880-95-8789\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+174115348190029\",\n \"dateOfRenewal\": \"1117-10-19\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1037-01-08\",\n \"dateOfExpiration\": \"1103-12-26\",\n \"dateOfIssuance\": \"2013-04-24\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"936-05-5969\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+54522060278\",\n \"dateOfRenewal\": \"1817-03-09\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "jurisdictions", + ":jurisdiction", + "licenses" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + }, + { + "_postman_previewlanguage": "json", + "body": "{\n \"message\": \"\",\n \"errors\": {\n \"do_f\": {\n \"Lorem9c5\": [\n \"\",\n \"\"\n ]\n }\n }\n}", + "code": 400, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "8c3f185e-a8b0-4263-ac24-609512fc878b", + "name": "400 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "[\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1316-06-30\",\n \"dateOfExpiration\": \"2363-08-17\",\n \"dateOfIssuance\": \"1788-09-12\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"880-95-8789\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+174115348190029\",\n \"dateOfRenewal\": \"1117-10-19\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n },\n {\n \"compactEligibility\": \"ineligible\",\n \"dateOfBirth\": \"1037-01-08\",\n \"dateOfExpiration\": \"1103-12-26\",\n \"dateOfIssuance\": \"2013-04-24\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"cosmetologist\",\n \"ssn\": \"936-05-5969\",\n \"homeAddressStreet2\": \"\",\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"phoneNumber\": \"+54522060278\",\n \"dateOfRenewal\": \"1817-03-09\",\n \"middleName\": \"\",\n \"licenseStatusName\": \"\"\n }\n]" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "jurisdictions", + ":jurisdiction", + "licenses" + ], + "query": [], + "variable": [] + } + }, + "status": "Bad Request" + } + ] + }, + { + "description": "", + "item": [ + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('URL returned', () => {", + " var url = pm.response.json().upload.url;", + " pm.expect(url).not.to.be.empty;", + " pm.environment.set(\"docUrl\", url);", + " console.log('Upload url: ' + url);", + "});", + "", + "pm.test('Fields returned', () => {", + " var fields = pm.response.json().upload.fields;", + " pm.expect(fields).not.to.be.empty;", + " for (const [key, value] of Object.entries(fields)) {", + " pm.environment.set(`docField-${key}`, value);", + " console.log(`Doc field \"${key}\": ${value}`);", + " }", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "id": "74350c29-68f5-4cb4-a3ea-89ad9a4d1638", + "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses/bulk-upload", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": {}, + "description": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "name": "/v1/compacts/:compact/jurisdictions/:jurisdiction/licenses/bulk-upload", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "jurisdictions", + ":jurisdiction", + "licenses", + "bulk-upload" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + }, + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "jurisdiction", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"upload\": {\n \"fields\": {\n \"magnaa\": \"\",\n \"amet_2\": \"\"\n },\n \"url\": \"\"\n }\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "dc357548-6831-48a7-9240-8ff1d811a806", + "name": "200 response", + "originalRequest": { + "body": {}, + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "GET", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "jurisdictions", + ":jurisdiction", + "licenses", + "bulk-upload" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "bulk-upload" + } + ], + "name": "licenses" + } + ], + "name": "{jurisdiction}" + } + ], + "name": "jurisdictions" + } + ], + "name": "{compact}" + } + ], + "name": "compacts" + } + ], + "name": "v1" + }, + { + "name": "Upload Document", + "request": { + "auth": { + "type": "noauth" + }, + "body": { + "formdata": [ + { + "key": "key", + "type": "text", + "value": "{{docField-key}}" + }, + { + "key": "x-amz-algorithm", + "type": "text", + "value": "{{docField-x-amz-algorithm}}" + }, + { + "key": "x-amz-credential", + "type": "text", + "value": "{{docField-x-amz-credential}}" + }, + { + "key": "x-amz-date", + "type": "text", + "value": "{{docField-x-amz-date}}" + }, + { + "key": "x-amz-signature", + "type": "text", + "value": "{{docField-x-amz-signature}}" + }, + { + "key": "x-amz-security-token", + "type": "text", + "value": "{{docField-x-amz-security-token}}" + }, + { + "key": "policy", + "type": "text", + "value": "{{docField-policy}}" + }, + { + "key": "file", + "src": "AmVhGkArk/octp-nc-mock-data.csv", + "type": "file" + } + ], + "mode": "formdata" + }, + "header": [], + "method": "POST", + "url": { + "host": [ + "{{docUrl}}" + ], + "raw": "{{docUrl}}" + } + }, + "response": [] + } + ] +} diff --git a/backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json b/backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json index c1c006228..cdaf79b33 100644 --- a/backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json +++ b/backend/cosmetology-app/docs/search-internal/api-specification/latest-oas30.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "SearchApi", - "version": "2025-12-02T19:49:45Z" + "version": "2026-02-16T17:23:42Z" }, "servers": [ { @@ -13,6 +13,14 @@ "/v1/compacts/{compact}/providers/search": { "post": { "parameters": [ + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "compact", "in": "path", @@ -26,7 +34,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboSearctZ4sfzliddmr" + "$ref": "#/components/schemas/SandboSearcFlaGc2hNzmIy" } } }, @@ -38,18 +46,60 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SandboSearcRcmFGOzNZ5TZ" + "$ref": "#/components/schemas/SandboSearcEzLOjtUlVHxz" } } } } - } + }, + "security": [ + { + "SandboxSearchAPIStackSearchApiStaffUsersPoolAuthorizer469875BC": [ + "cosm/readGeneral" + ] + } + ] } } }, "components": { "schemas": { - "SandboSearcRcmFGOzNZ5TZ": { + "SandboSearcFlaGc2hNzmIy": { + "required": [ + "query" + ], + "type": "object", + "properties": { + "search_after": { + "type": "array", + "description": "Sort values from the last hit of the previous page for cursor-based pagination" + }, + "size": { + "maximum": 100, + "minimum": 1, + "type": "integer", + "description": "Number of results to return" + }, + "query": { + "type": "object", + "description": "The OpenSearch query body" + }, + "from": { + "minimum": 0, + "type": "integer", + "description": "Starting document offset for pagination" + }, + "sort": { + "type": "array", + "description": "Sort order for results (required for search_after pagination)", + "items": { + "type": "object" + } + } + }, + "additionalProperties": false + }, + "SandboSearcEzLOjtUlVHxz": { "required": [ "providers", "total" @@ -91,7 +141,6 @@ "jurisdictionUploadedLicenseStatus", "licenseJurisdiction", "licenseStatus", - "privilegeJurisdictions", "providerId", "type" ], @@ -127,146 +176,38 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, - "attestations": { - "type": "array", - "items": { - "required": [ - "attestationId", - "version" - ], - "type": "object", - "properties": { - "attestationId": { - "maxLength": 100, - "type": "string" - }, - "version": { - "maxLength": 100, - "type": "string" - } - } - } - }, "investigations": { "type": "array", "items": { @@ -292,9 +233,7 @@ "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "providerId": { @@ -305,58 +244,15 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "submittingUser": { @@ -408,11 +304,6 @@ "type": "string", "format": "date" }, - "activeSince": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, "privilegeId": { "type": "string" }, @@ -454,67 +345,22 @@ "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "licenseTypeAbbreviation": { @@ -592,72 +438,23 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "givenName": { "maxLength": 100, "type": "string" @@ -683,67 +480,6 @@ "inactive" ] }, - "privilegeJurisdictions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ] - } - }, "type": { "type": "string", "enum": [ @@ -758,58 +494,15 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "licenses": { @@ -830,6 +523,7 @@ "jurisdiction", "jurisdictionUploadedCompactEligibility", "jurisdictionUploadedLicenseStatus", + "licenseNumber", "licenseStatus", "licenseType", "providerId", @@ -840,9 +534,7 @@ "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "homeAddressStreet2": { @@ -854,58 +546,15 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "homeAddressStreet1": { @@ -938,9 +587,7 @@ "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "providerId": { @@ -951,58 +598,15 @@ "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "submittingUser": { @@ -1077,6 +681,7 @@ }, "licenseNumber": { "maxLength": 100, + "minLength": 1, "type": "string" }, "investigationStatus": { @@ -1085,10 +690,6 @@ "underInvestigation" ] }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "homeAddressPostalCode": { "maxLength": 7, "minLength": 5, @@ -1167,67 +768,22 @@ "compact": { "type": "string", "enum": [ - "aslp", - "octp", - "coun" + "cosm" ] }, "jurisdiction": { "type": "string", "enum": [ "al", - "ak", "az", - "ar", - "ca", "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", "ks", "ky", - "la", - "me", "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", "tn", - "tx", - "ut", - "vt", "va", - "vi", - "wa", - "wv", - "wi", - "wy" + "wa" ] }, "licenseTypeAbbreviation": { @@ -1298,71 +854,6 @@ "type": "string", "format": "date" }, - "militaryAffiliations": { - "type": "array", - "items": { - "required": [ - "affiliationType", - "compact", - "dateOfUpdate", - "dateOfUpload", - "fileNames", - "providerId", - "status", - "type" - ], - "type": "object", - "properties": { - "dateOfUpload": { - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string", - "format": "date" - }, - "compact": { - "type": "string", - "enum": [ - "aslp", - "octp", - "coun" - ] - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "affiliationType": { - "type": "string", - "enum": [ - "militaryMember", - "militaryMemberSpouse" - ] - }, - "type": { - "type": "string", - "enum": [ - "militaryAffiliation" - ] - }, - "dateOfUpdate": { - "type": "string", - "format": "date-time" - }, - "fileNames": { - "type": "array", - "items": { - "type": "string" - } - }, - "status": { - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - } - }, "providerId": { "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" @@ -1398,41 +889,14 @@ } } } - }, - "SandboSearctZ4sfzliddmr": { - "required": [ - "query" - ], - "type": "object", - "properties": { - "search_after": { - "type": "array", - "description": "Sort values from the last hit of the previous page for cursor-based pagination" - }, - "size": { - "maximum": 100, - "minimum": 1, - "type": "integer", - "description": "Number of results to return" - }, - "query": { - "type": "object", - "description": "The OpenSearch query body" - }, - "from": { - "minimum": 0, - "type": "integer", - "description": "Starting document offset for pagination" - }, - "sort": { - "type": "array", - "description": "Sort order for results (required for search_after pagination)", - "items": { - "type": "object" - } - } - }, - "additionalProperties": false + } + }, + "securitySchemes": { + "SandboxSearchAPIStackSearchApiStaffUsersPoolAuthorizer469875BC": { + "type": "apiKey", + "name": "Authorization", + "in": "header", + "x-amazon-apigateway-authtype": "cognito_user_pools" } } }, diff --git a/backend/cosmetology-app/docs/search-internal/postman/postman-collection.json b/backend/cosmetology-app/docs/search-internal/postman/postman-collection.json new file mode 100644 index 000000000..f4a20a99a --- /dev/null +++ b/backend/cosmetology-app/docs/search-internal/postman/postman-collection.json @@ -0,0 +1,544 @@ +{ + "auth": { + "bearer": [ + { + "key": "token", + "type": "string", + "value": "{{accessToken}}" + } + ], + "type": "bearer" + }, + "info": { + "_postman_id": "346a56fb-ef7b-4c96-9658-4199f9e1c223", + "description": { + "content": "", + "type": "text/plain" + }, + "name": "CompactConnect API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "item": [ + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "client-credentials-grant", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "client_credentials" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "client_secret", + "value": "{{clientSecret}}" + }, + { + "key": "scope", + "value": "{{jurisdiction}}/{{compact}}.write" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=client_credentials&client_id={{clientId}}&client_secret={{clientSecret}}&scope={{jurisdiction}}/{{compact}}.write" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});", + "", + "pm.test('Identity token returned', () => {", + " var id_token = pm.response.json().id_token;", + " pm.expect(id_token).not.to.be.empty;", + " pm.environment.set(\"idToken\", id_token);", + " console.log('id token: ' + id_token);", + "});", + "", + "pm.test('Refresh token returned', () => {", + " var refresh_token = pm.response.json().refresh_token;", + " pm.expect(refresh_token).not.to.be.empty;", + " pm.environment.set(\"refreshToken\", refresh_token);", + " console.log('refresh token: ' + refresh_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-grant-token", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "authorization_code" + }, + { + "key": "code", + "value": "f23723c3-1d21-40e1-89ec-64807d2d658d" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "scope", + "value": "openid" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=authorization_code&code=f23723c3-1d21-40e1-89ec-64807d2d658d&client_id={{clientId}}&scope=openid&redirect_uri=http://localhost:3018/auth/callback" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-authorize", + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "disabled": true, + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "disabled": true, + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "authorize" + ], + "query": [ + { + "key": "response_type", + "value": "code" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + }, + { + "key": "scope", + "value": "openid" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/authorize?response_type=code&client_id={{clientId}}&redirect_uri=http://localhost:3018/auth/callback&scope=openid" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.expect(access_token).not.to.be.empty;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "authorization-code-login", + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "GET", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "authorize" + ], + "query": [ + { + "key": "scope", + "value": "openid" + }, + { + "key": "response_type", + "value": "code" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "redirect_uri", + "value": "http://localhost:3018/auth/callback" + }, + { + "key": "identity_provider", + "value": "COGNITO" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/authorize?scope=openid&response_type=code&client_id={{clientId}}&redirect_uri=http://localhost:3018/auth/callback&identity_provider=COGNITO" + } + }, + "response": [] + }, + { + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Access token returned', () => {", + " var access_token = pm.response.json().access_token;", + " pm.environment.set(\"accessToken\", access_token);", + " console.log('Access token: ' + access_token);", + "});", + "", + "pm.test('Identity token returned', () => {", + " var id_token = pm.response.json().id_token;", + " pm.environment.set(\"idToken\", id_token);", + " console.log('id token: ' + id_token);", + "});" + ], + "packages": {}, + "type": "text/javascript" + } + } + ], + "name": "refresh-token", + "request": { + "auth": { + "type": "noauth" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "url": { + "host": [ + "{{staffUserPoolUrl}}" + ], + "path": [ + "oauth2", + "token" + ], + "query": [ + { + "key": "grant_type", + "value": "refresh_token" + }, + { + "key": "client_id", + "value": "{{clientId}}" + }, + { + "key": "refresh_token", + "value": "{{refreshToken}}" + } + ], + "raw": "{{staffUserPoolUrl}}/oauth2/token?grant_type=refresh_token&client_id={{clientId}}&refresh_token={{refreshToken}}" + } + }, + "response": [] + } + ], + "name": "Staff-Auth" + }, + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "description": "", + "item": [ + { + "event": [], + "id": "4fe4ff32-f855-41db-8e6e-43494234f517", + "name": "/v1/compacts/:compact/providers/search", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"query\": {},\n \"search_after\": \"\",\n \"size\": \"\",\n \"from\": \"\"\n}" + }, + "description": {}, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "method": "POST", + "name": "/v1/compacts/:compact/providers/search", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + "search" + ], + "query": [], + "variable": [ + { + "description": { + "content": "(Required) ", + "type": "text/plain" + }, + "disabled": false, + "key": "compact", + "type": "any", + "value": "" + } + ] + } + }, + "response": [ + { + "_postman_previewlanguage": "json", + "body": "{\n \"providers\": [\n {\n \"birthMonthDay\": \"17-26\",\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseJurisdiction\": \"tn\",\n \"licenseStatus\": \"active\",\n \"providerId\": \"bc58ac41-eb79-44a8-bced-8e1dcb7ca810\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"active\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"1725-11-29\",\n \"dateOfIssuance\": \"1703-12-06\",\n \"dateOfRenewal\": \"2449-10-30\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseJurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"privilegeId\": \"\",\n \"providerId\": \"5cff14d7-9fee-437d-aed2-8eb70f9afbeb\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"9be12268-9c73-41e4-9076-d0fb0533d3ae\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"38047ace-f2b2-4a2b-b3f1-6c6281d377ff\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2928-06-30\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2907-11-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d499b24c-f5d3-4467-85ad-3e99a4f2542a\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1734-11-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2970-10-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1080-11-26\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d83fb980-d3d4-409a-96cf-14a0b5fc4f36\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2471-12-07\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"1877-09-16\",\n \"dateOfIssuance\": \"2521-08-31\",\n \"dateOfRenewal\": \"1486-12-12\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseJurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"privilegeId\": \"\",\n \"providerId\": \"1332ee6f-68da-4291-8a9a-dc8a30ef56c7\",\n \"status\": \"active\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"providerId\": \"2dada957-ff02-4420-9a10-0aed5cc8a931\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"850af4c9-ad40-4677-bb53-319102069aaf\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2385-10-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2716-10-05\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"acbfbdf9-d14c-418d-87ba-51d64aa286f5\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1552-10-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2877-01-06\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2986-10-31\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"fccb5d14-b4c3-4dfb-87aa-89782cf8eab1\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1974-12-02\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"al\",\n \"licenses\": [\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2333-12-10\",\n \"dateOfIssuance\": \"1222-04-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"az\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"e32f14d1-aba0-4e8d-882b-295a1d26e6de\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"aa88e4f2-27d7-4ac2-9d5d-a1b6b9f2d295\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"providerId\": \"47e0cfc1-48a5-4e78-b33f-c82802d936cb\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1083-10-24\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+622561379015809\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1122-12-09\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2695-12-09\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"fc221325-4aac-4d8e-913e-907eec254968\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1094-07-25\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2292-05-03\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1955-10-01\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"37ee95e6-31f4-429d-8acf-e1c9cb649145\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1018-07-05\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2306-10-21\",\n \"dateOfIssuance\": \"1903-11-31\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"az\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"inactive\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"active\",\n \"licenseType\": \"\",\n \"providerId\": \"06909a66-68d7-4d39-98fb-9476c7d4a31c\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"fe3b2a7d-cdac-46ae-91b8-a92c87c96bf7\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"providerId\": \"28f51c83-259b-4acb-99b9-46f95678e030\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"1919-01-31\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+1142031333\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2599-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2910-08-18\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"e0a23cc2-17c3-4e1a-aef0-5ff8dec9a6c1\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2718-04-14\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2075-12-31\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2302-12-19\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"md\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"572ac763-0960-4427-a1e8-dcec3bd3de0b\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2859-06-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n },\n {\n \"birthMonthDay\": \"11-30\",\n \"compact\": \"cosm\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseJurisdiction\": \"ky\",\n \"licenseStatus\": \"inactive\",\n \"providerId\": \"5aec06cd-f100-4b7e-8da4-59ec35a4e385\",\n \"type\": \"provider\",\n \"privileges\": [\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"2137-11-18\",\n \"dateOfIssuance\": \"1279-05-13\",\n \"dateOfRenewal\": \"1969-07-09\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseJurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"privilegeId\": \"\",\n \"providerId\": \"f336b4dc-3bb1-4414-830e-144294445ed3\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"38dca002-3b97-4697-abfe-f7d046634319\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"4d971f7a-6048-4536-8433-35a758d9e8ae\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2643-02-21\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2556-12-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ks\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"60b4f7b1-4ecc-4609-898b-b5ca42a76512\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1893-12-27\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1236-07-13\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2930-11-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"5e944eca-31ed-4445-ad5c-2f7439d6f9ae\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1433-10-09\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"administratorSetStatus\": \"inactive\",\n \"compact\": \"cosm\",\n \"dateOfExpiration\": \"2719-05-23\",\n \"dateOfIssuance\": \"2898-10-23\",\n \"dateOfRenewal\": \"2441-07-19\",\n \"dateOfUpdate\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseJurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"privilegeId\": \"\",\n \"providerId\": \"e7605b3b-410c-4b74-a05e-4b32f89b773d\",\n \"status\": \"inactive\",\n \"type\": \"privilege\",\n \"investigationStatus\": \"underInvestigation\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"734a4639-ef4e-4cd5-8bf4-50aa958c7338\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"providerId\": \"0722dc79-0997-41a0-afee-3dce591601bf\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"compactTransactionId\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2598-06-01\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2542-03-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"16bb01b1-1135-4787-85ac-cec7d4f3ec88\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"1558-05-04\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2289-07-23\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2815-01-07\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"ky\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d0d763d5-ab26-41bd-b86d-eca4148feccc\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2738-11-12\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"suffix\": \"\",\n \"currentHomeJurisdiction\": \"tn\",\n \"licenses\": [\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"eligible\",\n \"dateOfExpiration\": \"2422-01-07\",\n \"dateOfIssuance\": \"1581-12-30\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"az\",\n \"jurisdictionUploadedCompactEligibility\": \"ineligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"2ac7ccc8-9102-44dc-9027-38e1eb04c3cb\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"tn\",\n \"licenseType\": \"\",\n \"providerId\": \"bec77ef3-0012-41eb-9141-b978f16ac58d\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"al\",\n \"licenseType\": \"\",\n \"providerId\": \"3a298fbc-0949-44db-be13-90c1e5755864\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"2357-11-12\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+7090227913770\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2214-10-07\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1136-12-07\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"055ce9ff-55b5-42c3-bac3-df3c6b7f61e9\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2477-09-31\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"2842-12-18\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"2236-11-30\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"wa\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"d6fabc0e-127a-4833-b8f2-3a0e6caadec1\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2290-08-31\",\n \"liftingUser\": \"\"\n }\n ]\n },\n {\n \"compact\": \"cosm\",\n \"compactEligibility\": \"ineligible\",\n \"dateOfExpiration\": \"2792-02-04\",\n \"dateOfIssuance\": \"1075-01-13\",\n \"dateOfUpdate\": \"\",\n \"familyName\": \"\",\n \"givenName\": \"\",\n \"homeAddressCity\": \"\",\n \"homeAddressPostalCode\": \"\",\n \"homeAddressState\": \"\",\n \"homeAddressStreet1\": \"\",\n \"jurisdiction\": \"ks\",\n \"jurisdictionUploadedCompactEligibility\": \"eligible\",\n \"jurisdictionUploadedLicenseStatus\": \"active\",\n \"licenseNumber\": \"\",\n \"licenseStatus\": \"inactive\",\n \"licenseType\": \"\",\n \"providerId\": \"4cf96a94-b48d-47ee-806f-cc68af1fbbea\",\n \"type\": \"license-home\",\n \"homeAddressStreet2\": \"\",\n \"investigations\": [\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"oh\",\n \"licenseType\": \"\",\n \"providerId\": \"272fbeef-4ce8-4801-97a5-40058ab0f7ea\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n },\n {\n \"compact\": \"cosm\",\n \"creationDate\": \"\",\n \"dateOfUpdate\": \"\",\n \"investigationId\": \"\",\n \"jurisdiction\": \"va\",\n \"licenseType\": \"\",\n \"providerId\": \"402956de-0f77-481b-9b14-80b761e26f21\",\n \"submittingUser\": \"\",\n \"type\": \"investigation\"\n }\n ],\n \"suffix\": \"\",\n \"emailAddress\": \"\",\n \"dateOfRenewal\": \"2932-03-25\",\n \"investigationStatus\": \"underInvestigation\",\n \"phoneNumber\": \"+08318847\",\n \"licenseStatusName\": \"\",\n \"middleName\": \"\",\n \"adverseActions\": [\n {\n \"actionAgainst\": \"privilege\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1424-03-05\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1695-10-18\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"co\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"ac30bbc5-e621-478a-b485-feaa5519bd3e\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2850-09-06\",\n \"liftingUser\": \"\"\n },\n {\n \"actionAgainst\": \"license\",\n \"adverseActionId\": \"\",\n \"compact\": \"cosm\",\n \"creationDate\": \"1352-12-06\",\n \"dateOfUpdate\": \"\",\n \"effectiveStartDate\": \"1631-01-03\",\n \"encumbranceType\": \"\",\n \"jurisdiction\": \"az\",\n \"licenseType\": \"\",\n \"licenseTypeAbbreviation\": \"\",\n \"providerId\": \"adf02bbe-77c0-4065-9d80-1972d2a4aa0e\",\n \"submittingUser\": \"\",\n \"type\": \"adverseAction\",\n \"clinicalPrivilegeActionCategories\": [\n \"\",\n \"\"\n ],\n \"effectiveLiftDate\": \"2189-12-31\",\n \"liftingUser\": \"\"\n }\n ]\n }\n ],\n \"middleName\": \"\",\n \"compactConnectRegisteredEmailAddress\": \"\"\n }\n ],\n \"total\": {\n \"value\": \"\",\n \"relation\": \"gte\"\n },\n \"lastSort\": \"\"\n}", + "code": 200, + "cookie": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "id": "a7409fb2-8304-49f7-b2fd-381e62c32d05", + "name": "200 response", + "originalRequest": { + "body": { + "mode": "raw", + "options": { + "raw": { + "headerFamily": "json", + "language": "json" + } + }, + "raw": "{\n \"query\": {},\n \"search_after\": \"\",\n \"size\": \"\",\n \"from\": \"\"\n}" + }, + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "description": { + "content": "Added as a part of security scheme: apikey", + "type": "text/plain" + }, + "key": "Authorization", + "value": "" + } + ], + "method": "POST", + "url": { + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "compacts", + ":compact", + "providers", + "search" + ], + "query": [], + "variable": [] + } + }, + "status": "OK" + } + ] + } + ], + "name": "search" + } + ], + "name": "providers" + } + ], + "name": "{compact}" + } + ], + "name": "compacts" + } + ], + "name": "v1" + } + ] +} diff --git a/backend/cosmetology-app/docs/signature_auth_examples.txt b/backend/cosmetology-app/docs/signature_auth_examples.txt deleted file mode 100644 index 7d00430a4..000000000 --- a/backend/cosmetology-app/docs/signature_auth_examples.txt +++ /dev/null @@ -1,149 +0,0 @@ -================================================================================ -Signature Authentication Examples -================================================================================ - -This document provides example HTTP requests demonstrating the -CompactConnect signature authentication scheme. - -Each example includes: - 1. The raw HTTP request with signature headers - 2. The plaintext string that was signed - 3. The base64-encoded string that was signed - -================================================================================ - -Example 1: GET /v1/compacts/cosm/jurisdictions/al/providers/query --------------------------------------------------------------------------------- - -Raw HTTP Request: - -GET /v1/compacts/cosm/jurisdictions/al/providers/query?limit=10&offset=0&status=active HTTP/1.1 -Host: api.example.com -Content-Type: application/json -User-Agent: CompactConnect-Client/1.0 -Authorization: Bearer -X-Algorithm: ECDSA-SHA256 -X-Timestamp: 2025-11-19T21:21:21.242166Z -X-Nonce: d34b90dd39e64c739f9b22070d0433bf -X-Key-Id: test-key-001 -X-Signature: MEYCIQDxGk8KYQskZaiD2XCCvZJBlLz7TXPM8nW7BqcfTTO5ygIhAPtLd+oBgdMHfskhlCjFf/dc2fFzz9jcgaxsxXdA5Ddg - - -Plaintext String to Sign: - -GET -/v1/compacts/cosm/jurisdictions/al/providers/query -limit=10&offset=0&status=active -2025-11-19T21:21:21.242166Z -d34b90dd39e64c739f9b22070d0433bf -test-key-001 - - -Base64-Encoded String to Sign: - -R0VUCi92MS9jb21wYWN0cy9jb3NtL2p1cmlzZGljdGlvbnMvYWwvcHJvdmlkZXJzL3F1ZXJ5CmxpbWl0PTEwJm9mZnNldD0wJnN0YXR1cz1hY3RpdmUKMjAyNS0xMS0xOVQyMToyMToyMS4yNDIxNjZaCmQzNGI5MGRkMzllNjRjNzM5ZjliMjIwNzBkMDQzM2JmCnRlc3Qta2V5LTAwMQ== - - -================================================================================ - -Example 2: POST /v1/compacts/cosm/jurisdictions/al/providers --------------------------------------------------------------------------------- - -Raw HTTP Request: - -POST /v1/compacts/cosm/jurisdictions/al/providers?validate=true HTTP/1.1 -Host: api.example.com -Content-Type: application/json -User-Agent: CompactConnect-Client/1.0 -Authorization: Bearer -X-Algorithm: ECDSA-SHA256 -X-Timestamp: 2025-11-19T21:21:21.245504Z -X-Nonce: 26cd4a44b74f425d8630d1ea9c98127e -X-Key-Id: test-key-002 -X-Signature: MEUCIQDoIo1XqJo6X6HTt2CbZTWN1RI5Jex0EFwb9MoLXrKVnQIgV883LXq3fKdiv1hwU98Kt7hBQKO+2hyt8D3bL6GJlDw= - - -Plaintext String to Sign: - -POST -/v1/compacts/cosm/jurisdictions/al/providers -validate=true -2025-11-19T21:21:21.245504Z -26cd4a44b74f425d8630d1ea9c98127e -test-key-002 - - -Base64-Encoded String to Sign: - -UE9TVAovdjEvY29tcGFjdHMvY29zbS9qdXJpc2RpY3Rpb25zL2FsL3Byb3ZpZGVycwp2YWxpZGF0ZT10cnVlCjIwMjUtMTEtMTlUMjE6MjE6MjEuMjQ1NTA0WgoyNmNkNGE0NGI3NGY0MjVkODYzMGQxZWE5Yzk4MTI3ZQp0ZXN0LWtleS0wMDI= - - -================================================================================ - -Example 3: GET /v1/compacts/cosm/jurisdictions/al/providers/12345 --------------------------------------------------------------------------------- - -Raw HTTP Request: - -GET /v1/compacts/cosm/jurisdictions/al/providers/12345 HTTP/1.1 -Host: api.example.com -Content-Type: application/json -User-Agent: CompactConnect-Client/1.0 -Authorization: Bearer -X-Algorithm: ECDSA-SHA256 -X-Timestamp: 2025-11-19T21:21:21.245591Z -X-Nonce: 3e1aa862e0ee4c1e94e44f2ce35a89a7 -X-Key-Id: test-key-003 -X-Signature: MEYCIQChYjYNjARVQZx53V551i2x6acWcvOF7ipe8pu/tHYwKQIhAISUX+oxCtPBKLOc2tqpJg6FTKX7pR8ULWXSWKO2Ira5 - - -Plaintext String to Sign: - -GET -/v1/compacts/cosm/jurisdictions/al/providers/12345 - -2025-11-19T21:21:21.245591Z -3e1aa862e0ee4c1e94e44f2ce35a89a7 -test-key-003 - - -Base64-Encoded String to Sign: - -R0VUCi92MS9jb21wYWN0cy9jb3NtL2p1cmlzZGljdGlvbnMvYWwvcHJvdmlkZXJzLzEyMzQ1CgoyMDI1LTExLTE5VDIxOjIxOjIxLjI0NTU5MVoKM2UxYWE4NjJlMGVlNGMxZTk0ZTQ0ZjJjZTM1YTg5YTcKdGVzdC1rZXktMDAz - - -================================================================================ - -Example 4: POST /path --------------------------------------------------------------------------------- - -Raw HTTP Request: - -POST /path?a=1&b=value%20two HTTP/1.1 -Host: api.example.com -Content-Type: application/json -User-Agent: CompactConnect-Client/1.0 -Authorization: Bearer -X-Algorithm: ECDSA-SHA256 -X-Timestamp: 2025-11-11T19:09:53Z -X-Nonce: 54ebdc56-4eae-4627-94e1-11ff27a3ec88 -X-Key-Id: eLicenseKey -X-Signature: MEQCIFed8UTChmWcKS6yNtjn5KRNVXbRgwn3RC6NZBMUMKOoAiB2xtyQlPft8Dq24rjz28rK8D7hwsZ3BDy4SYQZrmeeTw== - - -Plaintext String to Sign: - -POST -/path -a=1&b=value%20two -2025-11-11T19:09:53Z -54ebdc56-4eae-4627-94e1-11ff27a3ec88 -eLicenseKey - - -Base64-Encoded String to Sign: - -UE9TVAovcGF0aAphPTEmYj12YWx1ZSUyMHR3bwoyMDI1LTExLTExVDE5OjA5OjUzWgo1NGViZGM1Ni00ZWFlLTQ2MjctOTRlMS0xMWZmMjdhM2VjODgKZUxpY2Vuc2VLZXk= - - -================================================================================ diff --git a/backend/cosmetology-app/lambdas/nodejs/email-notification-service/README.md b/backend/cosmetology-app/lambdas/nodejs/email-notification-service/README.md index 79f63f3ca..92294d2cd 100644 --- a/backend/cosmetology-app/lambdas/nodejs/email-notification-service/README.md +++ b/backend/cosmetology-app/lambdas/nodejs/email-notification-service/README.md @@ -7,7 +7,7 @@ be rendered consistently across email clients. The lambda is intended to be invoked directly, rather than through an API endpoint. It uses the following payload structure: ``` { - template: string; // Name of the template to use (ie transactionBatchSettlementFailure) + template: string; // Name of the template to use (e.g. licenseEncumbranceProviderNotification) recipientType: // must be one of the following | 'COMPACT_OPERATIONS_TEAM' // compactOperationsTeamEmails | 'COMPACT_ADVERSE_ACTIONS' // compactAdverseActionsNotificationEmails diff --git a/backend/cosmetology-app/lambdas/nodejs/email-notification-service/lambda.ts b/backend/cosmetology-app/lambdas/nodejs/email-notification-service/lambda.ts index 5e520187d..fb8f7e7a7 100644 --- a/backend/cosmetology-app/lambdas/nodejs/email-notification-service/lambda.ts +++ b/backend/cosmetology-app/lambdas/nodejs/email-notification-service/lambda.ts @@ -7,7 +7,7 @@ import { Context } from 'aws-lambda'; import { EnvironmentVariablesService } from '../lib/environment-variables-service'; import { CompactConfigurationClient } from '../lib/compact-configuration-client'; import { JurisdictionClient } from '../lib/jurisdiction-client'; -import { EmailNotificationService, EncumbranceNotificationService, InvestigationNotificationService } from '../lib/email'; +import { EncumbranceNotificationService, InvestigationNotificationService } from '../lib/email'; import { EmailNotificationEvent, EmailNotificationResponse } from '../lib/models/email-notification-service-event'; const environmentVariables = new EnvironmentVariablesService(); @@ -19,7 +19,6 @@ interface LambdaProperties { } export class Lambda implements LambdaInterface { - private readonly emailService: EmailNotificationService; private readonly encumbranceService: EncumbranceNotificationService; private readonly investigationService: InvestigationNotificationService; @@ -34,13 +33,6 @@ export class Lambda implements LambdaInterface { dynamoDBClient: props.dynamoDBClient, }); - this.emailService = new EmailNotificationService({ - logger: logger, - sesClient: props.sesClient, - compactConfigurationClient: compactConfigurationClient, - jurisdictionClient: jurisdictionClient - }); - this.encumbranceService = new EncumbranceNotificationService({ logger: logger, sesClient: props.sesClient, @@ -79,32 +71,6 @@ export class Lambda implements LambdaInterface { } switch (event.template) { - case 'transactionBatchSettlementFailure': - await this.emailService.sendTransactionBatchSettlementFailureEmail( - event.compact, - event.recipientType, - event.specificEmails, - event.templateVariables.batchFailureErrorMessage - ); - break; - case 'privilegeDeactivationJurisdictionNotification': - if (!event.jurisdiction) { - throw new Error('Missing required jurisdiction field.'); - } - if (!event.templateVariables.privilegeId - || !event.templateVariables.providerFirstName - || !event.templateVariables.providerLastName) { - throw new Error('Missing required template variables for privilegeDeactivationJurisdictionNotification template.'); - } - await this.emailService.sendPrivilegeDeactivationJurisdictionNotificationEmail( - event.compact, - event.jurisdiction, - event.recipientType, - event.templateVariables.privilegeId, - event.templateVariables.providerFirstName, - event.templateVariables.providerLastName - ); - break; case 'licenseEncumbranceProviderNotification': if (!event.templateVariables.providerFirstName || !event.templateVariables.providerLastName diff --git a/backend/cosmetology-app/lambdas/nodejs/lib/email/email-notification-service.ts b/backend/cosmetology-app/lambdas/nodejs/lib/email/email-notification-service.ts deleted file mode 100644 index 9f1165d51..000000000 --- a/backend/cosmetology-app/lambdas/nodejs/lib/email/email-notification-service.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { BaseEmailService } from './base-email-service'; -import { RecipientType } from '../models/email-notification-service-event'; -/** - * Email service for handling email notifications - */ -export class EmailNotificationService extends BaseEmailService { - private async getCompactRecipients( - compact: string, - recipientType: RecipientType, - specificEmails?: string[] - ): Promise { - if (recipientType === 'SPECIFIC') { - if (specificEmails) return specificEmails; - - throw new Error(`SPECIFIC recipientType requested but no specific email addresses provided`); - } - - const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); - - switch (recipientType) { - case 'COMPACT_OPERATIONS_TEAM': - return compactConfig.compactOperationsTeamEmails; - case 'COMPACT_SUMMARY_REPORT': - return compactConfig.compactSummaryReportNotificationEmails; - default: - throw new Error(`Unsupported recipient type for compact configuration: ${recipientType}`); - } - } - - private async getJurisdictionRecipients( - compact: string, - jurisdiction: string, - recipientType: RecipientType, - specificEmails?: string[] - ): Promise { - if (recipientType === 'SPECIFIC') { - if (specificEmails) return specificEmails; - - throw new Error(`SPECIFIC recipientType requested but no specific email addresses provided`); - } - - const jurisdictionConfig = await this.jurisdictionClient.getJurisdictionConfiguration(compact, jurisdiction); - - switch (recipientType) { - case 'JURISDICTION_SUMMARY_REPORT': - return jurisdictionConfig.jurisdictionSummaryReportNotificationEmails; - default: - throw new Error(`Unsupported recipient type for compact configuration: ${recipientType}`); - } - } - - public async sendTransactionBatchSettlementFailureEmail( - compact: string, - recipientType: RecipientType, - specificEmails?: string[], - batchFailureErrorMessage?: string - ): Promise { - this.logger.info('Sending transaction batch settlement failure email', { compact: compact }); - const recipients = await this.getCompactRecipients(compact, recipientType, specificEmails); - - if (recipients.length === 0) { - throw new Error(`No recipients found for compact ${compact} with recipient type ${recipientType}`); - } - - const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); - const report = this.getNewEmailTemplate(); - const subject = `Transactions Failed to Settle for ${compactConfig.compactName} Payment Processor`; - - let bodyText = 'A transaction settlement error was detected within the payment processing account for the compact. ' + - 'Please reach out to your payment processing representative if needed to determine the cause. '; - - // Include detailed error message if provided - if (batchFailureErrorMessage) { - try { - const errorDetails = JSON.parse(batchFailureErrorMessage); - - bodyText += '\n\nDetailed Error Information:\n'; - - if (errorDetails.message) { - bodyText += `Error Message: ${errorDetails.message}\n`; - } - - if (errorDetails.failedTransactionIds && errorDetails.failedTransactionIds.length > 0) { - bodyText += `Failed Transaction IDs: ${errorDetails.failedTransactionIds.join(', ')}\n`; - } - - if (errorDetails.unsettledTransactionIds && errorDetails.unsettledTransactionIds.length > 0) { - bodyText += `Unsettled Transaction IDs (older than 48 hours): ${errorDetails.unsettledTransactionIds.join(', ')}\n`; - } - } catch { - // If JSON parsing fails, include the raw message - bodyText += `\n\nError Details: ${batchFailureErrorMessage}`; - } - } - - this.insertHeader(report, subject); - this.insertBody(report, bodyText, 'center', true); - this.insertFooter(report); - - const htmlContent = this.renderTemplate(report); - - await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send transaction batch settlement failure email' }); - } - - /** - * Sends an email notification to the jurisdiction report notification emails when a privilege is deactivated - * @param compact - The compact name for which the privilege was deactivated - * @param jurisdiction - The jurisdiction for which the privilege was deactivated - * @param privilegeId - The privilege ID of the privilege that was deactivated - * @param providerFirstName - The first name of the provider whose privilege was deactivated - * @param providerLastName - The last name of the provider whose privilege was deactivated - */ - public async sendPrivilegeDeactivationJurisdictionNotificationEmail( - compact: string, - jurisdiction: string, - recipientType: RecipientType, - privilegeId: string, - providerFirstName: string, - providerLastName: string - ): Promise { - - this.logger.info('Sending privilege deactivation jurisdiction notification email', { compact: compact, jurisdiction: jurisdiction }); - - const recipients = await this.getJurisdictionRecipients( - compact, - jurisdiction, - recipientType - ); - - if (recipients?.length === 0) { - throw new Error(`No recipients found for jurisdiction ${jurisdiction} in compact ${compact}`); - } - - const compactConfig = await this.compactConfigurationClient.getCompactConfiguration(compact); - const report = this.getNewEmailTemplate(); - const subject = `A Privilege was Deactivated in the ${compactConfig.compactName} Compact`; - const bodyText = `This message is to notify you that privilege ${privilegeId} held by ${providerFirstName} ${providerLastName} was deactivated and can no longer be used to practice.`; - - this.insertHeader(report, subject); - this.insertBody(report, bodyText); - this.insertFooter(report); - - const htmlContent = this.renderTemplate(report); - - await this.sendEmail({ htmlContent, subject, recipients, errorMessage: 'Unable to send privilege deactivation state notification email' }); - } -} diff --git a/backend/cosmetology-app/lambdas/nodejs/lib/email/index.ts b/backend/cosmetology-app/lambdas/nodejs/lib/email/index.ts index 66e5ba1e8..001ba8e05 100644 --- a/backend/cosmetology-app/lambdas/nodejs/lib/email/index.ts +++ b/backend/cosmetology-app/lambdas/nodejs/lib/email/index.ts @@ -1,5 +1,4 @@ export { CognitoEmailService } from './cognito-email-service'; -export { EmailNotificationService } from './email-notification-service'; export { EncumbranceNotificationService } from './encumbrance-notification-service'; export { InvestigationNotificationService } from './investigation-notification-service'; export { IngestEventEmailService } from './ingest-event-email-service'; diff --git a/backend/cosmetology-app/lambdas/nodejs/package.json b/backend/cosmetology-app/lambdas/nodejs/package.json index 21cd12237..bd05c7b09 100644 --- a/backend/cosmetology-app/lambdas/nodejs/package.json +++ b/backend/cosmetology-app/lambdas/nodejs/package.json @@ -4,7 +4,7 @@ "type": "commonjs", "description": "NodeJS lambdas for Compact Connect", "resolutions": { - "@aws-sdk/client-sesv2": "^3.901.0" + "fast-xml-parser": "5.3.6" }, "scripts": { "build": "tsc", diff --git a/backend/cosmetology-app/lambdas/nodejs/tests/email-notification-service.test.ts b/backend/cosmetology-app/lambdas/nodejs/tests/email-notification-service.test.ts index a47336e53..cd6e72ebb 100644 --- a/backend/cosmetology-app/lambdas/nodejs/tests/email-notification-service.test.ts +++ b/backend/cosmetology-app/lambdas/nodejs/tests/email-notification-service.test.ts @@ -7,10 +7,17 @@ import { EmailNotificationEvent } from '../lib/models/email-notification-service import { describe, it, beforeAll, beforeEach, jest } from '@jest/globals'; const SAMPLE_EVENT: EmailNotificationEvent = { - template: 'transactionBatchSettlementFailure', - recipientType: 'COMPACT_OPERATIONS_TEAM', + template: 'licenseEncumbranceProviderNotification', + recipientType: 'SPECIFIC', compact: 'cosm', - templateVariables: {} + specificEmails: ['provider@example.com'], + templateVariables: { + providerFirstName: 'John', + providerLastName: 'Doe', + encumberedJurisdiction: 'OH', + licenseType: 'Audiologist', + effectiveStartDate: 'January 15, 2024' + } }; const SAMPLE_COMPACT_CONFIGURATION = { @@ -111,103 +118,6 @@ describe('EmailNotificationServiceLambda', () => { expect(mockSESClient).not.toHaveReceivedAnyCommand(); }); - it('should successfully send transaction batch settlement failure email', async () => { - const response = await lambda.handler(SAMPLE_EVENT, {} as any); - - expect(response).toEqual({ - message: 'Email message sent' - }); - - // Verify DynamoDB was queried for compact configuration - expect(mockDynamoDBClient).toHaveReceivedCommandWith(GetItemCommand, { - TableName: 'compact-table', - Key: { - 'pk': { S: 'cosm#CONFIGURATION' }, - 'sk': { S: 'cosm#CONFIGURATION' } - } - }); - - // Verify email was sent with correct parameters - expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { - Destination: { - ToAddresses: ['operations@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('A transaction settlement error was detected') - } - }, - Subject: { - Charset: 'UTF-8', - Data: 'Transactions Failed to Settle for Audiology and Speech Language Pathology Payment Processor' - } - } - }, - FromEmailAddress: 'Compact Connect ' - }); - }); - - it('should include detailed error information for failed transactions', async () => { - const eventWithFailedTransactions: EmailNotificationEvent = { - ...SAMPLE_EVENT, - templateVariables: { - batchFailureErrorMessage: JSON.stringify({ - message: 'Settlement errors detected in one or more transactions.', - failedTransactionIds: ['tx-123', 'tx-456', 'tx-789'] - }) - } - }; - - const response = await lambda.handler(eventWithFailedTransactions, {} as any); - - expect(response).toEqual({ - message: 'Email message sent' - }); - - // Get the actual HTML content for detailed validation - const emailCall = mockSESClient.commandCalls(SendEmailCommand)[0]; - const htmlContent = emailCall.args[0].input.Content?.Simple?.Body?.Html?.Data; - - expect(htmlContent).toBeDefined(); - expect(htmlContent).toContain('A transaction settlement error was detected within the payment processing account for the compact.'); - expect(htmlContent).toContain('Please reach out to your payment processing representative if needed to determine the cause.'); - expect(htmlContent).toContain('Detailed Error Information:'); - expect(htmlContent).toContain('Error Message: Settlement errors detected in one or more transactions.'); - expect(htmlContent).toContain('Failed Transaction IDs: tx-123, tx-456, tx-789'); - }); - - it('should include detailed error information for unsettled transactions', async () => { - const eventWithUnsettledTransactions: EmailNotificationEvent = { - ...SAMPLE_EVENT, - templateVariables: { - batchFailureErrorMessage: JSON.stringify({ - message: 'One or more transactions have not settled in over 48 hours.', - unsettledTransactionIds: ['unsettled-tx-001', 'unsettled-tx-002'] - }) - } - }; - - const response = await lambda.handler(eventWithUnsettledTransactions, {} as any); - - expect(response).toEqual({ - message: 'Email message sent' - }); - - // Get the actual HTML content for detailed validation - const emailCall = mockSESClient.commandCalls(SendEmailCommand)[0]; - const htmlContent = emailCall.args[0].input.Content?.Simple?.Body?.Html?.Data; - - expect(htmlContent).toBeDefined(); - expect(htmlContent).toContain('A transaction settlement error was detected within the payment processing account for the compact.'); - expect(htmlContent).toContain('Please reach out to your payment processing representative if needed to determine the cause.'); - expect(htmlContent).toContain('Detailed Error Information:'); - expect(htmlContent).toContain('Error Message: One or more transactions have not settled in over 48 hours.'); - expect(htmlContent).toContain('Unsettled Transaction IDs (older than 48 hours): unsettled-tx-001, unsettled-tx-002'); - }); - it('should throw error for unsupported template', async () => { const event: EmailNotificationEvent = { ...SAMPLE_EVENT, @@ -223,61 +133,6 @@ describe('EmailNotificationServiceLambda', () => { expect(mockSESClient).not.toHaveReceivedAnyCommand(); }); - describe('Privilege Deactivation Jurisdiction Notification', () => { - const SAMPLE_PRIVILEGE_DEACTIVATION_JURISDICTION_NOTIFICATION_EVENT: EmailNotificationEvent = { - template: 'privilegeDeactivationJurisdictionNotification', - recipientType: 'JURISDICTION_SUMMARY_REPORT', - compact: 'cosm', - jurisdiction: 'oh', - templateVariables: { - privilegeId: '123', - providerFirstName: 'John', - providerLastName: 'Doe' - } - }; - - it('should successfully send privilege deactivation jurisdiction notification email', async () => { - const response = await lambda.handler( - SAMPLE_PRIVILEGE_DEACTIVATION_JURISDICTION_NOTIFICATION_EVENT, {} as any); - - expect(response).toEqual({ - message: 'Email message sent' - }); - - // Verify DynamoDB was queried for jurisdiction configuration - expect(mockDynamoDBClient).toHaveReceivedCommandWith(GetItemCommand, { - TableName: 'compact-table', - Key: { - 'pk': { S: 'cosm#CONFIGURATION' }, - 'sk': { S: 'cosm#JURISDICTION#oh' } - } - }); - - // Verify email was sent with correct parameters - expect(mockSESClient).toHaveReceivedCommandWith(SendEmailCommand, { - Destination: { - ToAddresses: ['ohio@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('') - } - }, - Subject: { - Charset: 'UTF-8', - Data: 'A Privilege was Deactivated in the Audiology and Speech Language Pathology Compact' - } - } - }, - FromEmailAddress: 'Compact Connect ' - } - ); - }); - }); - describe('License Encumbrance Provider Notification', () => { const SAMPLE_LICENSE_ENCUMBRANCE_PROVIDER_NOTIFICATION_EVENT: EmailNotificationEvent = { template: 'licenseEncumbranceProviderNotification', diff --git a/backend/cosmetology-app/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts b/backend/cosmetology-app/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts deleted file mode 100644 index 91ec14698..000000000 --- a/backend/cosmetology-app/lambdas/nodejs/tests/lib/email/email-notification-service.test.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { mockClient } from 'aws-sdk-client-mock'; -import 'aws-sdk-client-mock-jest'; -import { Logger } from '@aws-lambda-powertools/logger'; -import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; -import * as nodemailer from 'nodemailer'; -import { EmailNotificationService } from '../../../lib/email'; -import { CompactConfigurationClient } from '../../../lib/compact-configuration-client'; -import { JurisdictionClient } from '../../../lib/jurisdiction-client'; -import { EmailTemplateCapture } from '../../utils/email-template-capture'; -import { TReaderDocument } from '@csg-org/email-builder'; -import { describe, it, beforeEach, beforeAll, afterAll, jest } from '@jest/globals'; - -jest.mock('nodemailer'); - -const SAMPLE_COMPACT_CONFIG = { - pk: 'cosm#CONFIGURATION', - sk: 'cosm#CONFIGURATION', - compactAdverseActionsNotificationEmails: ['adverse@example.com'], - compactCommissionFee: { - feeAmount: 3.5, - feeType: 'FLAT_RATE' - }, - compactAbbr: 'cosm', - compactName: 'Audiology and Speech Language Pathology', - compactOperationsTeamEmails: ['operations@example.com'], - compactSummaryReportNotificationEmails: ['summary@example.com'], - dateOfUpdate: '2024-12-10T19:27:28+00:00', - type: 'compact' -}; - -const SAMPLE_JURISDICTION_CONFIG = { - pk: 'cosm#CONFIGURATION', - sk: 'cosm#JURISDICTION#OH', - jurisdictionName: 'Ohio', - postalAbbreviation: 'OH', - compact: 'cosm', - jurisdictionOperationsTeamEmails: ['oh-ops@example.com'], - jurisdictionAdverseActionsNotificationEmails: ['oh-adverse@example.com'], - jurisdictionSummaryReportNotificationEmails: ['oh-summary@example.com'] -}; - -const asSESClient = (mock: ReturnType) => - mock as unknown as SESv2Client; - -const MOCK_TRANSPORT = { - sendMail: jest.fn().mockImplementation(async () => ({ messageId: 'test-message-id' })) -}; - -describe('EmailNotificationService', () => { - let emailService: EmailNotificationService; - let mockSESClient: ReturnType; - let mockCompactConfigurationClient: jest.Mocked; - let mockJurisdictionClient: jest.Mocked; - - beforeAll(() => { - // Mock the renderTemplate method if template capture is enabled - if (EmailTemplateCapture.isEnabled()) { - const original = (EmailNotificationService.prototype as any).renderTemplate; - - jest.spyOn(EmailNotificationService.prototype as any, 'renderTemplate').mockImplementation(function (this: any, ...args: any[]) { - const [template, options] = args as [TReaderDocument, any]; - - EmailTemplateCapture.captureTemplate(template); - const html = original.apply(this, args); - - EmailTemplateCapture.captureHtml(html, template, options); - return html; - }); - } - }); - - afterAll(() => { - jest.restoreAllMocks(); - }); - - beforeEach(() => { - jest.clearAllMocks(); - mockSESClient = mockClient(SESv2Client); - mockCompactConfigurationClient = { - getCompactConfiguration: jest.fn() - } as any; - mockJurisdictionClient = { - getJurisdictionConfigurations: jest.fn(), - getJurisdictionConfiguration: jest.fn() - } as any; - - // Reset environment variables - process.env.FROM_ADDRESS = 'noreply@example.org'; - process.env.UI_BASE_PATH_URL = 'https://app.test.compactconnect.org'; - process.env.TRANSACTION_REPORTS_BUCKET_NAME = 'test-transaction-reports-bucket'; - - // Set up default successful responses - mockSESClient.on(SendEmailCommand).resolves({ - MessageId: 'message-id-123' - }); - - // Note: SESv2 with nodemailer 7.0.7 uses SendEmailCommand for all email sending - - (nodemailer.createTransport as jest.Mock).mockReturnValue(MOCK_TRANSPORT); - - emailService = new EmailNotificationService({ - logger: new Logger({ serviceName: 'test' }), - sesClient: asSESClient(mockSESClient), - compactConfigurationClient: mockCompactConfigurationClient, - jurisdictionClient: mockJurisdictionClient - }); - }); - - describe('Transaction Batch Settlement Failure', () => { - it('should send email using compact operations team emails', async () => { - mockCompactConfigurationClient.getCompactConfiguration.mockResolvedValue(SAMPLE_COMPACT_CONFIG); - - await emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'COMPACT_OPERATIONS_TEAM' - ); - - expect(mockSESClient).toHaveReceivedCommandWith( - SendEmailCommand, - { - Destination: { - ToAddresses: ['operations@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('') - } - }, - Subject: { - Charset: 'UTF-8', - Data: 'Transactions Failed to Settle for Audiology and Speech Language Pathology Payment Processor' - } - } - }, - FromEmailAddress: 'Compact Connect ' - } - ); - }); - - it('should send email using specific emails', async () => { - mockCompactConfigurationClient.getCompactConfiguration.mockResolvedValue(SAMPLE_COMPACT_CONFIG); - - await emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'SPECIFIC', - ['specific@example.com'] - ); - - expect(mockSESClient).toHaveReceivedCommandWith( - SendEmailCommand, - { - Destination: { - ToAddresses: ['specific@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('') - } - }, - Subject: { - Charset: 'UTF-8', - Data: 'Transactions Failed to Settle for Audiology and Speech Language Pathology Payment Processor' - } - } - }, - FromEmailAddress: 'Compact Connect ' - } - ); - }); - - it('should throw error when no recipients found', async () => { - mockCompactConfigurationClient.getCompactConfiguration.mockResolvedValue({ - ...SAMPLE_COMPACT_CONFIG, - compactOperationsTeamEmails: [] - }); - - await expect(emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'COMPACT_OPERATIONS_TEAM' - )).rejects.toThrow('No recipients found for compact cosm with recipient type COMPACT_OPERATIONS_TEAM'); - }); - - it('should throw error for specific recipient type without emails', async () => { - await expect(emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'SPECIFIC' - )).rejects.toThrow('SPECIFIC recipientType requested but no specific email addresses provided'); - }); - - it('should throw error for unsupported recipient type', async () => { - await expect(emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'JURISDICTION_OPERATIONS_TEAM' - )).rejects.toThrow('Unsupported recipient type for compact configuration: JURISDICTION_OPERATIONS_TEAM'); - }); - - it('should include logo in email', async () => { - mockCompactConfigurationClient.getCompactConfiguration.mockResolvedValue(SAMPLE_COMPACT_CONFIG); - - await emailService.sendTransactionBatchSettlementFailureEmail( - 'cosm', - 'COMPACT_OPERATIONS_TEAM' - ); - - expect(mockSESClient).toHaveReceivedCommandWith( - SendEmailCommand, - { - Destination: { - ToAddresses: ['operations@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('src=\"https://app.test.compactconnect.org/img/email/compact-connect-logo-final.png\"') - } - }, - Subject: { - Charset: 'UTF-8', - Data: 'Transactions Failed to Settle for Audiology and Speech Language Pathology Payment Processor' - } - } - }, - FromEmailAddress: 'Compact Connect ' - } - ); - }); - }); - - describe('Privilege Deactivation Jurisdiction Notification', () => { - it('should send jurisdiction privilege deactivation notification email with expected subject', async () => { - mockCompactConfigurationClient.getCompactConfiguration.mockResolvedValue(SAMPLE_COMPACT_CONFIG); - mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue(SAMPLE_JURISDICTION_CONFIG); - - await emailService.sendPrivilegeDeactivationJurisdictionNotificationEmail( - 'cosm', - 'oh', - 'JURISDICTION_SUMMARY_REPORT', - 'some-privilege-id', - 'John', - 'Doe' - ); - - expect(mockSESClient).toHaveReceivedCommandWith( - SendEmailCommand, - { - Destination: { - ToAddresses: ['oh-summary@example.com'] - }, - Content: { - Simple: { - Body: { - Html: { - Charset: 'UTF-8', - Data: expect.stringContaining('') - } - }, - Subject: { - Charset: 'UTF-8', - Data: `A Privilege was Deactivated in the Audiology and Speech Language Pathology Compact` - } - } - }, - FromEmailAddress: 'Compact Connect ' - } - ); - }); - - it('should throw error when no recipients found for jurisdiction privilege deactivation notification email', async () => { - mockJurisdictionClient.getJurisdictionConfiguration.mockResolvedValue({ - ...SAMPLE_JURISDICTION_CONFIG, - jurisdictionSummaryReportNotificationEmails: [] - }); - - await expect(emailService.sendPrivilegeDeactivationJurisdictionNotificationEmail( - 'cosm', - 'oh', - 'JURISDICTION_SUMMARY_REPORT', - 'some-privilege-id', - 'John', - 'Doe' - )).rejects.toThrow('No recipients found for jurisdiction oh in compact cosm'); - }); - }); -}); diff --git a/backend/cosmetology-app/lambdas/nodejs/yarn.lock b/backend/cosmetology-app/lambdas/nodejs/yarn.lock index 9a5f70474..f707d671d 100644 --- a/backend/cosmetology-app/lambdas/nodejs/yarn.lock +++ b/backend/cosmetology-app/lambdas/nodejs/yarn.lock @@ -3453,12 +3453,12 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-xml-parser@5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz#06f39aafffdbc97bef0321e626c7ddd06a043ecf" - integrity sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA== +fast-xml-parser@5.3.4, fast-xml-parser@5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz#85a69117ca156b1b3c52e426495b6de266cb6a4b" + integrity sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA== dependencies: - strnum "^2.1.0" + strnum "^2.1.2" fb-watchman@^2.0.0: version "2.0.2" @@ -5116,7 +5116,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^2.1.0: +strnum@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.2.tgz#a5e00ba66ab25f9cafa3726b567ce7a49170937a" integrity sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ== diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/compact_configuration_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/compact_configuration_client.py index beae0ae9a..0b5a7bd3b 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/compact_configuration_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/compact_configuration_client.py @@ -1,14 +1,9 @@ -from boto3.dynamodb.conditions import Key - from cc_common.config import _Config, logger from cc_common.data_model.schema.compact import CompactConfigurationData -from cc_common.data_model.schema.compact.common import COMPACT_TYPE from cc_common.data_model.schema.compact.record import CompactRecordSchema from cc_common.data_model.schema.jurisdiction import JurisdictionConfigurationData -from cc_common.data_model.schema.jurisdiction.common import JURISDICTION_TYPE from cc_common.data_model.schema.jurisdiction.record import JurisdictionRecordSchema from cc_common.exceptions import CCInternalException, CCNotFoundException -from cc_common.utils import logger_inject_kwargs class CompactConfigurationClient: @@ -259,84 +254,6 @@ def _ensure_jurisdiction_in_configured_states_if_registration_enabled( ) raise CCInternalException(message) from e - @logger_inject_kwargs(logger, 'compact') - def get_privilege_purchase_options(self, *, compact: str): - logger.info('Getting privilege purchase options for compact') - - # Get all compact configurations (both compact and jurisdiction records) - # Use pagination to ensure we get all records - all_items = [] - query_params = { - 'Select': 'ALL_ATTRIBUTES', - 'KeyConditionExpression': Key('pk').eq(f'{compact}#CONFIGURATION'), - } - - while True: - response = self.config.compact_configuration_table.query(**query_params) - all_items.extend(response.get('Items', [])) - - # Check if there are more records to fetch - last_evaluated_key = response.get('LastEvaluatedKey') - if not last_evaluated_key: - break - - # Set up for next page - query_params['ExclusiveStartKey'] = last_evaluated_key - - logger.info( - 'Retrieved all configuration records', - total_items=len(all_items), - ) - - # Get the compact configuration from the response items to access configuredStates - compact_config_item = next((item for item in all_items if item['type'] == COMPACT_TYPE), None) - - if compact_config_item and compact_config_item.get('configuredStates'): - live_jurisdictions = { - state['postalAbbreviation'] for state in compact_config_item['configuredStates'] if state.get('isLive') - } - - if not live_jurisdictions: - logger.info('No live jurisdictions found for compact. Returning empty list') - # in this case, there is nothing to return, so we return an empty list, and let the caller decide to - # raise an exception or not. - return {'items': []} - - logger.info( - 'Filtering privilege purchase options by live jurisdictions', - live_jurisdictions=list(live_jurisdictions), - ) - # Filter jurisdictions to only include live ones - filtered_items = [ - item - for item in all_items - if item.get('type') == COMPACT_TYPE - or ( - item.get('type') == JURISDICTION_TYPE - and item.get('postalAbbreviation', '').lower() in live_jurisdictions - ) - ] - - # Return in the expected format for backward compatibility - return { - 'items': filtered_items, - 'pagination': { - 'pageSize': len(filtered_items), - 'prevLastKey': None, - 'lastKey': None, - }, - } - - message = 'Compact configuration not found or has no configuredStates when filtering privilege purchase options' - logger.info( - message, - compact_config_found=compact_config_item is not None, - configured_states=compact_config_item.get('configuredStates') if compact_config_item else None, - ) - # in this case, there is nothing to return, so we return an empty list, and let the caller decide to raise - # an exception or not. - return {'items': []} - def update_compact_configured_states(self, compact: str, configured_states: list[dict]) -> None: """ Update the configuredStates field for a compact configuration using DynamoDB UPDATE operation. diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py index b1d23d067..a1008c0fb 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py @@ -4,12 +4,11 @@ from urllib.parse import quote from uuid import UUID, uuid4 -from aws_lambda_powertools.metrics import MetricUnit from boto3.dynamodb.conditions import Attr, Key from boto3.dynamodb.types import TypeDeserializer, TypeSerializer from botocore.exceptions import ClientError -from cc_common.config import _Config, config, logger, metrics +from cc_common.config import _Config, config, logger from cc_common.data_model.provider_record_util import ( ProviderRecordType, ProviderUserRecords, @@ -18,7 +17,6 @@ from cc_common.data_model.schema.adverse_action import AdverseActionData from cc_common.data_model.schema.base_record import SSNIndexRecordSchema from cc_common.data_model.schema.common import ( - ActiveInactiveStatus, CCDataClass, InvestigationAgainstEnum, InvestigationStatusEnum, @@ -30,7 +28,6 @@ from cc_common.data_model.schema.investigation import InvestigationData from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData from cc_common.data_model.schema.privilege import PrivilegeData, PrivilegeUpdateData -from cc_common.data_model.schema.privilege.record import PrivilegeUpdateRecordSchema from cc_common.data_model.schema.provider import ProviderData from cc_common.data_model.update_tier_enum import UpdateTierEnum from cc_common.exceptions import ( @@ -220,7 +217,6 @@ def get_providers_sorted_by_family_name( provider_name: tuple[str, str] | None = None, # (familyName, givenName) jurisdiction: str | None = None, scan_forward: bool = True, - exclude_providers_without_privileges: bool = False, ): logger.info('Getting providers by family name') @@ -241,20 +237,10 @@ def get_providers_sorted_by_family_name( # Create a jurisdiction filter expression if a jurisdiction is provided if jurisdiction is not None: - filter_expression = Attr('licenseJurisdiction').eq(jurisdiction) | Attr('privilegeJurisdictions').contains( - jurisdiction, - ) + filter_expression = Attr('licenseJurisdiction').eq(jurisdiction) else: filter_expression = None - # Add filter for providers with privileges if requested - if exclude_providers_without_privileges: - privilege_filter = Attr('privilegeJurisdictions').exists() - if filter_expression is not None: - filter_expression = filter_expression & privilege_filter - else: - filter_expression = privilege_filter - return config.provider_table.query( IndexName=config.fam_giv_mid_index_name, Select='ALL_ATTRIBUTES', @@ -273,33 +259,12 @@ def get_providers_sorted_by_updated( dynamo_pagination: dict, jurisdiction: str | None = None, scan_forward: bool = True, - only_providers_with_privileges: bool = False, - only_providers_with_privileges_in_jurisdiction: bool = False, start_date_time: str | None = None, end_date_time: str | None = None, ): logger.info('Getting providers by date updated') - if jurisdiction is None and only_providers_with_privileges_in_jurisdiction: - raise RuntimeError('jurisdiction is required when only_providers_with_privileges_in_jurisdiction is True') - - if only_providers_with_privileges_in_jurisdiction: - # only_providers_with_privileges_in_jurisdiction works _with_ jurisdiction - filter_expression = Attr('privilegeJurisdictions').contains(jurisdiction) - # Ignore only_providers_with_privileges if only_providers_with_privileges_in_jurisdiction is True - else: - # only_providers_with_privileges and jurisdiction are independent filters that can be combined - filter_expression = None - if only_providers_with_privileges: - filter_expression = Attr('privilegeJurisdictions').exists() - if jurisdiction is not None: - jurisdiction_condition = Attr('licenseJurisdiction').eq(jurisdiction) | Attr( - 'privilegeJurisdictions' - ).contains(jurisdiction) - if filter_expression is not None: - filter_expression = filter_expression & jurisdiction_condition - else: - filter_expression = jurisdiction_condition + filter_expression = Attr('licenseJurisdiction').eq(jurisdiction) if jurisdiction is not None else None # Build key condition expression with optional date range key_condition = Key('sk').eq(f'{compact}#PROVIDER') @@ -321,322 +286,6 @@ def get_providers_sorted_by_updated( **dynamo_pagination, ) - def _generate_privilege_record( - self, - compact: str, - provider_id: str, - jurisdiction_postal_abbreviation: str, - license_expiration_date: date, - license_type: str, - license_jurisdiction: str, - original_privilege: PrivilegeData | None = None, - ) -> PrivilegeData: - current_datetime = config.current_standard_datetime - try: - license_type_abbreviation = self.config.license_type_abbreviations[compact][license_type] - except KeyError as e: - # This shouldn't happen, since license type comes from a validated record, but we'll check - # anyway, in case of miss-configuration. - logger.warning('License type abbreviation not found', exc_info=e) - raise CCInvalidRequestException(f'Compact or license type not supported: {e}') from e - - if original_privilege: - # Copy over the original issuance date and privilege id - date_of_issuance = original_privilege.dateOfIssuance - privilege_id = original_privilege.privilegeId - else: - date_of_issuance = current_datetime - # Claim a privilege number for this jurisdiction - # Note that this number claim is not rolled back on failure, which can result in gaps - # in the privilege numbers. Having gaps in the privilege numbers was deemed acceptable - # for exceptional circumstances like errors in this flow. - privilege_number = self.claim_privilege_number(compact=compact) - logger.info('Claimed a new privilege number', privilege_number=privilege_number) - privilege_id = '-'.join( - (license_type_abbreviation.upper(), jurisdiction_postal_abbreviation.upper(), str(privilege_number)) - ) - - return PrivilegeData.create_new( - { - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': jurisdiction_postal_abbreviation.lower(), - 'licenseJurisdiction': license_jurisdiction.lower(), - 'licenseType': license_type, - 'dateOfIssuance': date_of_issuance, - 'dateOfRenewal': current_datetime, - 'dateOfExpiration': license_expiration_date, - 'privilegeId': privilege_id, - 'administratorSetStatus': ActiveInactiveStatus.ACTIVE, - } - ) - - @logger_inject_kwargs(logger, 'compact', 'provider_id') - def create_provider_privileges( - self, - compact: str, - provider_id: str, - jurisdiction_postal_abbreviations: list[str], - license_expiration_date: date, - provider_record: ProviderData, - existing_privileges_for_license: list[PrivilegeData], - license_type: str, - ): - """ - Create privilege records for a provider in the database. - - This is a transactional operation. If any of the records fail to be created, - the entire transaction will be rolled back. As this is usually performed after a provider has purchased - one or more privileges, it is important that all records are created successfully. - - :param compact: The compact name - :param provider_id: The provider id - :param jurisdiction_postal_abbreviations: The list of jurisdiction postal codes - :param license_expiration_date: The license expiration date - :param provider_record: The original provider record - :param existing_privileges_for_license: The list of existing privileges for the specified license. - Used to track the original issuance date of the privilege. - :param license_type: The type of license (e.g. esthetician, cosmetologist) - """ - logger.info( - 'Creating provider privileges', - privilege_jurisdictions=jurisdiction_postal_abbreviations, - ) - - license_jurisdiction = provider_record.licenseJurisdiction - - privileges = [] - - try: - # We'll collect all the record changes into a transaction to protect data consistency - transactions = [] - processed_transactions = [] - privilege_update_records = [] - - for postal_abbreviation in jurisdiction_postal_abbreviations: - # get the original privilege issuance date from an existing privilege record if it exists - original_privilege = next( - ( - record - for record in existing_privileges_for_license - if record.jurisdiction.lower() == postal_abbreviation.lower() - and record.licenseType == license_type - ), - None, - ) - - privilege_record: PrivilegeData = self._generate_privilege_record( - compact=compact, - provider_id=provider_id, - jurisdiction_postal_abbreviation=postal_abbreviation, - license_expiration_date=license_expiration_date, - license_type=license_type, - license_jurisdiction=license_jurisdiction, - original_privilege=original_privilege, - ) - - privileges.append(privilege_record) - - now = config.current_standard_datetime - - # Create privilege update record if this is updating an existing privilege - if original_privilege: - update_record = PrivilegeUpdateData.create_new( - { - 'type': ProviderRecordType.PRIVILEGE_UPDATE, - 'updateType': UpdateCategory.RENEWAL, - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': postal_abbreviation.lower(), - 'licenseType': license_type, - 'previous': original_privilege.to_dict(), - 'effectiveDate': now, - 'createDate': now, - 'updatedValues': { - 'dateOfRenewal': privilege_record.dateOfRenewal, - 'dateOfExpiration': privilege_record.dateOfExpiration, - 'privilegeId': privilege_record.privilegeId, - **( - {'administratorSetStatus': ActiveInactiveStatus.ACTIVE} - if original_privilege.administratorSetStatus == ActiveInactiveStatus.INACTIVE - else {} - ), - }, - } - ) - # if this privilege was previously deactivated due to a home jurisdiction change - # or license deactivation, we remove those deactivation values when the privilege is renewed. - # We add those existing fields to the removedValues which will be stored with the update record. - removed_values = [] - if original_privilege.homeJurisdictionChangeStatus is not None: - removed_values.append('homeJurisdictionChangeStatus') - if original_privilege.licenseDeactivatedStatus is not None: - removed_values.append('licenseDeactivatedStatus') - - if removed_values: - update_record.update({'removedValues': removed_values}) - - privilege_update_records.append(update_record) - transactions.append( - { - 'Put': { - 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(update_record.serialize_to_database_record())['M'], - } - } - ) - - transactions.append( - { - 'Put': { - 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(privilege_record.serialize_to_database_record())['M'], - } - } - ) - - # We save this update till last so that it is least likely to be changed in the event of a failure in - # one of the other transactions. - transactions.append( - { - 'Update': { - 'TableName': self.config.provider_table_name, - 'Key': { - 'pk': {'S': f'{compact}#PROVIDER#{provider_id}'}, - 'sk': {'S': f'{compact}#PROVIDER'}, - }, - 'UpdateExpression': 'ADD #privilegeJurisdictions :newJurisdictions', - 'ExpressionAttributeNames': {'#privilegeJurisdictions': 'privilegeJurisdictions'}, - 'ExpressionAttributeValues': {':newJurisdictions': {'SS': jurisdiction_postal_abbreviations}}, - } - } - ) - - # Unfortunately, we can't guarantee that the number of transactions is below the 100 action limit - # for extremely large purchases. To handle those large purchases, we will have to break our transactions - # up and handle a multi-transaction roll-back on failure. - # We'll collect data for sizes, just so we can keep an eye on them and understand user behavior - metrics.add_metric( - name='privilege-purchase-transaction-size', unit=MetricUnit.Count, value=len(transactions) - ) - metrics.add_metric( - name='privileges-purchased', unit=MetricUnit.Count, value=len(jurisdiction_postal_abbreviations) - ) - # 100 is the maximum transaction size - batch_size = 100 - # Iterate over the transactions until they are empty - while transaction_batch := transactions[:batch_size]: - self.config.dynamodb_client.transact_write_items(TransactItems=transaction_batch) - processed_transactions.extend(transaction_batch) - transactions = transactions[batch_size:] - if transactions: - logger.info( - 'Breaking privilege updates into multiple transactions', - privilege_jurisdictions=jurisdiction_postal_abbreviations, - ) - - except ClientError as e: - message = 'Unable to create all provider privileges. Rolling back transaction.' - logger.info(message, error=str(e)) - self._rollback_privilege_transactions( - processed_transactions=processed_transactions, - provider_record=provider_record, - license_type=license_type, - existing_privileges_for_license_type=existing_privileges_for_license, - ) - raise CCAwsServiceException(message) from e - - return privileges - - def _rollback_privilege_transactions( - self, - processed_transactions: list[dict], - provider_record: ProviderData, - license_type: str, - existing_privileges_for_license_type: list[PrivilegeData], - ): - """Roll back successful privilege transactions after a failure.""" - rollback_transactions = [] - - # Create a lookup of existing privileges by jurisdiction - # as a safety precaution, we must ensure that every privilege in the list matches the license type that we - # attempted to change privilege for - existing_privileges_by_jurisdiction = { - privilege.jurisdiction: privilege - for privilege in existing_privileges_for_license_type - if privilege.licenseType == license_type - } - - # Delete all privilege update records and handle privilege records appropriately - for transaction in processed_transactions: - if transaction.get('Put'): - item = TypeDeserializer().deserialize({'M': transaction['Put']['Item']}) - if item.get('type') == ProviderRecordType.PRIVILEGE_UPDATE: - # Always delete update records as they are always new - rollback_transactions.append( - { - 'Delete': { - 'TableName': self.config.provider_table_name, - 'Key': { - 'pk': {'S': item['pk']}, - 'sk': {'S': item['sk']}, - }, - } - } - ) - elif item.get('type') == ProviderRecordType.PRIVILEGE: - # For privilege records, check if it was an update or new creation - original_privilege = existing_privileges_by_jurisdiction.get(item['jurisdiction']) - if original_privilege: - logger.info('Restoring original privilege record', jurisdiction=item['jurisdiction']) - # If it was an update, restore the original record - rollback_transactions.append( - { - 'Put': { - 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize( - original_privilege.serialize_to_database_record() - )['M'], - } - } - ) - else: - # If it was a new creation, delete it - logger.info('Deleting new privilege record', jurisdiction=item['jurisdiction']) - rollback_transactions.append( - { - 'Delete': { - 'TableName': self.config.provider_table_name, - 'Key': { - 'pk': {'S': item['pk']}, - 'sk': {'S': item['sk']}, - }, - } - } - ) - - # Restore the original provider record - rollback_transactions.append( - { - 'Put': { - 'TableName': self.config.provider_table_name, - 'Item': TypeSerializer().serialize(provider_record.serialize_to_database_record())['M'], - } - } - ) - - # Execute rollback in batches of 100 - batch_size = 100 - while rollback_batch := rollback_transactions[:batch_size]: - try: - logger.info('Submitting rollback transaction') - self.config.dynamodb_client.transact_write_items(TransactItems=rollback_batch) - rollback_transactions = rollback_transactions[batch_size:] - except ClientError as e: - logger.error('Failed to roll back privilege transactions', error=str(e)) - raise CCAwsServiceException('Failed to roll back privilege transactions') from e - logger.info('Privilege rollback complete') - @logger_inject_kwargs(logger, 'compact', 'provider_ids') def batch_get_providers_by_id(self, compact: str, provider_ids: list[str]) -> list[dict]: """ @@ -693,31 +342,6 @@ def batch_get_providers_by_id(self, compact: str, provider_ids: list[str]) -> li return providers - @logger_inject_kwargs(logger, 'compact') - def claim_privilege_number(self, compact: str) -> int: - """ - Claim a unique privilege number for a compact by atomically incrementing the privilege counter. - If the counter doesn't exist yet, it will be created with an initial value of 1. - """ - logger.info('Claiming privilege number') - resp = self.config.provider_table.update_item( - Key={ - 'pk': f'{compact}#PRIVILEGE_COUNT', - 'sk': f'{compact}#PRIVILEGE_COUNT', - }, - UpdateExpression='ADD #count :increment', - ExpressionAttributeNames={ - '#count': 'privilegeCount', - }, - ExpressionAttributeValues={ - ':increment': 1, - }, - ReturnValues='UPDATED_NEW', - ) - privilege_count = resp['Attributes']['privilegeCount'] - logger.info('Claimed privilege number', privilege_count=privilege_count) - return privilege_count - @logger_inject_kwargs(logger, 'compact', 'provider_id') def get_provider_top_level_record(self, *, compact: str, provider_id: str) -> ProviderData: """Get the top level provider record for a provider. @@ -881,91 +505,6 @@ def get_privilege_data( return result - @logger_inject_kwargs(logger, 'compact', 'provider_id', 'jurisdiction', 'license_type_abbr') - def deactivate_privilege( - self, *, compact: str, provider_id: str, jurisdiction: str, license_type_abbr: str, deactivation_details: dict - ) -> None: - """ - Deactivate a privilege for a provider in a jurisdiction. - - This will update the privilege record to have a administratorSetStatus of 'inactive'. - - :param str compact: The compact to deactivate the privilege for - :param str provider_id: The provider to deactivate the privilege for - :param str jurisdiction: The jurisdiction to deactivate the privilege for - :param str license_type_abbr: The license type abbreviation to deactivate the privilege for - :param dict deactivation_details: The details of the deactivation to be added to the history record - :raises CCNotFoundException: If the privilege record is not found - """ - # Get the privilege record - - privilege_data = self.get_privilege_data( - compact=compact, provider_id=provider_id, jurisdiction=jurisdiction, license_type_abbr=license_type_abbr - ) - - privilege_record = privilege_data[0] - - # If already inactive, do nothing - if privilege_record.get('administratorSetStatus', ActiveInactiveStatus.ACTIVE) == ActiveInactiveStatus.INACTIVE: - logger.info('Provider already inactive. Doing nothing.') - raise CCInvalidRequestException('Privilege already deactivated') - - now = config.current_standard_datetime - - # Create the update record - # Use the schema to generate the update record with proper pk/sk - privilege_update_record = PrivilegeUpdateRecordSchema().dump( - { - 'type': ProviderRecordType.PRIVILEGE_UPDATE, - 'updateType': UpdateCategory.DEACTIVATION, - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': jurisdiction, - 'createDate': now, - 'effectiveDate': now, - 'licenseType': privilege_record['licenseType'], - 'deactivationDetails': deactivation_details, - 'previous': { - # We're relying on the schema to trim out unneeded fields - **privilege_record, - }, - 'updatedValues': { - 'administratorSetStatus': ActiveInactiveStatus.INACTIVE, - }, - } - ) - - # Update the privilege record and create history record - logger.info('Deactivating privilege') - self.config.dynamodb_client.transact_write_items( - TransactItems=[ - # Set the privilege record's administratorSetStatus to inactive and update the dateOfUpdate - { - 'Update': { - 'TableName': self.config.provider_table.name, - 'Key': { - 'pk': {'S': f'{compact}#PROVIDER#{provider_id}'}, - 'sk': {'S': f'{compact}#PROVIDER#privilege/{jurisdiction}/{license_type_abbr}#'}, - }, - 'UpdateExpression': 'SET administratorSetStatus = :status, dateOfUpdate = :dateOfUpdate', - 'ExpressionAttributeValues': { - ':status': {'S': ActiveInactiveStatus.INACTIVE}, - ':dateOfUpdate': {'S': self.config.current_standard_datetime.isoformat()}, - }, - }, - }, - # Create a history record, reflecting this change - { - 'Put': { - 'TableName': self.config.provider_table.name, - 'Item': TypeSerializer().serialize(privilege_update_record)['M'], - }, - }, - ], - ) - - return privilege_record - def _generate_encumbered_status_update_item( self, data: CCDataClass, diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py index c5fc97a57..f69c397ac 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/provider_record_util.py @@ -1,14 +1,9 @@ from collections.abc import Callable, Iterable -from datetime import ( - UTC, - date, - datetime, - timedelta, -) +from datetime import date from enum import StrEnum from uuid import UUID -from cc_common.config import config, logger +from cc_common.config import logger from cc_common.data_model.schema.adverse_action import AdverseActionData from cc_common.data_model.schema.common import ( ActiveInactiveStatus, @@ -19,7 +14,6 @@ from cc_common.data_model.schema.investigation import InvestigationData from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData from cc_common.data_model.schema.privilege import PrivilegeData, PrivilegeUpdateData -from cc_common.data_model.schema.privilege.api import PrivilegeHistoryResponseSchema from cc_common.data_model.schema.provider import ProviderData, ProviderUpdateData from cc_common.exceptions import CCInternalException, CCNotFoundException @@ -141,63 +135,20 @@ def find_best_license(cls, license_records: Iterable[dict], home_jurisdiction: s return latest_licenses[0] @staticmethod - def calculate_privilege_active_since_date( - privilege_record: PrivilegeData, privilege_updates: list[PrivilegeUpdateData] - ) -> datetime | None: - """ - Determine how long a privilege has been continuously active. - - :param privilege_record: The privilege record. - :param privilege_updates: The list of updates for this privilege record. - :return: The oldest datetime this privilege has been continuously active if still active, else None - """ - - if privilege_record.status == ActiveInactiveStatus.INACTIVE: - # privilege is inactive, no date to calculate - return None - - # start with dateOfIssuance as active date - active_since = privilege_record.dateOfIssuance - # sort privilege updates by their effective dates - sorted_updates = sorted(privilege_updates, key=lambda x: x.effectiveDate) - # iterate through privilege updates - for update in sorted_updates: - # We check for the following cases: - # 1. If the updateType is found in the list of deactivation update types, we set active_since to None, - # since the privilege is no longer active as a result of this update. - # 2. If the updateType is a home jurisdiction change, we need to check the updatedValues to see if the - # privilege was deactivated as a result of this update (if there is either a encumberedStatus - # or homeJurisdictionChangeStatus) - # 3. If the updateType is a renewal, and the `active_since` field is None, we set active_since to the - # effective date of the renewal. - if update.updateType in DEACTIVATION_EVENT_TYPES: - active_since = None - elif update.updateType == UpdateCategory.RENEWAL and active_since is None: - active_since = update.updatedValues['dateOfRenewal'] - - return active_since - - @staticmethod - def populate_provider_record( - current_provider_record: ProviderData | None, license_record: dict, privilege_records: list[dict] - ) -> ProviderData: + def populate_provider_record(current_provider_record: ProviderData | None, license_record: dict) -> ProviderData: """ - Create a provider record from a license record and privilege records. + Create a provider record from a license record. :param current_provider_record: The current provider record to update if it currently exists. :param license_record: The license record to use as a basis for the provider record - :param privilege_records: List of privilege records :return: A provider record ready to be persisted """ - privilege_jurisdictions = {record['jurisdiction'] for record in privilege_records} if current_provider_record is None: return ProviderData.create_new( { 'providerId': license_record['providerId'], 'compact': license_record['compact'], 'licenseJurisdiction': license_record['jurisdiction'], - # We can't put an empty string set to DynamoDB, so we'll only add the field if it is not empty - **({'privilegeJurisdictions': privilege_jurisdictions} if privilege_jurisdictions else {}), **license_record, } ) @@ -209,184 +160,11 @@ def populate_provider_record( **current_provider_record.to_dict(), # update the license jurisdiction to match the new license 'licenseJurisdiction': license_record['jurisdiction'], - # We can't put an empty string set to DynamoDB, so we'll only add the field if it is not empty - **({'privilegeJurisdictions': privilege_jurisdictions} if privilege_jurisdictions else {}), # now override the key values on the current provider record with the new license record **license_record, } ) - @staticmethod - def get_enriched_history_with_synthetic_updates_from_privilege( - privilege: dict, - history: list[dict], - ) -> list[dict]: - """ - Enrich the privilege history with 'synthetic updates'. - Synthetic updates are pieces of history that are not explicitly recorded in the data - system, because they occur passively, such as when a privilege expires or because they are redundant. - These 'synthetic updates' do not have a corresponding record in the database, but we can deduce their - existence based on the privilege's other data. Because these events are - 'synthetic', they have no actual changes in record values associated with them. - Example issuance event: - { - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'providerId': , - 'compact': , - 'jurisdiction': , - 'licenseType': , - 'effectiveDate': , - 'createDate': - 'dateOfUpdate': , - 'previous': {}, - 'updatedValues': {}, - } - :param privilege: The privilege record whose history we intend to construct - :param history: The raw history records we intend to extrapolate from - :return: The enriched privilege history - """ - - # We don't ever serve investigation updates via the API - they're only for internal change history tracking - history_without_investigations = [ - update - for update in history - if update['updateType'] not in (UpdateCategory.INVESTIGATION, UpdateCategory.CLOSING_INVESTIGATION) - ] - create_date_sorted_original_history = sorted(history_without_investigations, key=lambda x: x['createDate']) - - # Inject issuance event - enriched_history = [ - { - 'type': 'privilegeUpdate', - 'updateType': UpdateCategory.ISSUANCE, - 'providerId': privilege['providerId'], - 'compact': privilege['compact'], - 'jurisdiction': privilege['jurisdiction'], - 'licenseType': privilege['licenseType'], - 'effectiveDate': privilege['dateOfIssuance'], - 'createDate': privilege['dateOfIssuance'], - 'previous': {}, - 'updatedValues': {}, - 'dateOfUpdate': privilege['dateOfIssuance'], - } - ] + create_date_sorted_original_history - - renewal_updates = list(filter(lambda x: x['updateType'] == UpdateCategory.RENEWAL, enriched_history)) - - now = config.current_standard_datetime - - # Inject expiration events that occurred between events - for update in renewal_updates: - date_of_expiration = update['previous']['dateOfExpiration'] - day_after_expiration = date_of_expiration + timedelta(days=1) - datetime_of_expiration_trigger = datetime.combine( - day_after_expiration, datetime.min.time(), tzinfo=config.expiration_resolution_timezone - ) - effective_date_time = datetime.combine( - update['effectiveDate'], datetime.min.time(), tzinfo=config.expiration_resolution_timezone - ) - if datetime_of_expiration_trigger <= effective_date_time: - # We have assigned the maximum time in the day at UTC-4:00 because the expiration event happens at the - # first second of the date of expiration's passing. However, we want the expiration events to display as - # occurring on their expiration date and also have any events that occurred during that day come before - # the expiration chronologically. Putting the datetime of expiration as the max time in the day on the - # date of expiration best achieves those goals - effective_datetime_of_expiration = datetime.combine( - date_of_expiration, datetime.max.time(), tzinfo=config.expiration_resolution_timezone - ) - enriched_history.append( - { - 'type': 'privilegeUpdate', - 'updateType': UpdateCategory.EXPIRATION, - 'providerId': privilege['providerId'], - 'compact': privilege['compact'], - 'jurisdiction': privilege['jurisdiction'], - 'licenseType': privilege['licenseType'], - 'effectiveDate': effective_datetime_of_expiration.astimezone(UTC), - 'createDate': datetime_of_expiration_trigger.astimezone(UTC), - 'previous': {}, - 'updatedValues': {}, - 'dateOfUpdate': datetime_of_expiration_trigger.astimezone(UTC), - } - ) - # Inject expiration event if currently expired - privilege_date_of_expiration = privilege['dateOfExpiration'] - - privilege_day_after_expiration = privilege_date_of_expiration + timedelta(days=1) - privilege_datetime_of_expiration_trigger = datetime.combine( - privilege_day_after_expiration, datetime.min.time(), tzinfo=config.expiration_resolution_timezone - ) - - if privilege_datetime_of_expiration_trigger <= now.astimezone(config.expiration_resolution_timezone): - # We have assigned the maximum time in the day at UTC-4:00 because the expiration event happens at the - # first second of the date of expiration's passing. However, we want the expiration events to display as - # occurring on their expiration date and also have any events that occurred during that day come before - # the expiration chronologically. Putting the datetime of expiration as the max time in the day on the - # date of expiration best achieves those goals - effective_datetime_of_expiration = datetime.combine( - privilege_date_of_expiration, datetime.max.time(), tzinfo=config.expiration_resolution_timezone - ) - enriched_history.append( - { - 'type': 'privilegeUpdate', - 'updateType': UpdateCategory.EXPIRATION, - 'providerId': privilege['providerId'], - 'compact': privilege['compact'], - 'jurisdiction': privilege['jurisdiction'], - 'licenseType': privilege['licenseType'], - 'effectiveDate': effective_datetime_of_expiration.astimezone(UTC), - 'createDate': privilege_datetime_of_expiration_trigger.astimezone(UTC), - 'previous': {}, - 'updatedValues': {}, - 'dateOfUpdate': privilege_datetime_of_expiration_trigger.astimezone(UTC), - } - ) - - return sorted(enriched_history, key=lambda x: x['effectiveDate']) - - @staticmethod - def construct_simplified_privilege_history_object( - privilege_data: list[dict], should_include_encumbrance_details: bool = True - ) -> dict: - """ - Construct a simplified list of history events to be easily consumed by the front end - :param privilege_data: All of the records associated with the privilege: - the privilege, updates, and adverse actions - :param should_include_encumbrance_details: Whether the response should include verbose information on privilege - encumbrances - :return: The simplified and enriched privilege history - """ - privilege = list(filter(lambda x: x['type'] == 'privilege', privilege_data))[0] - history = list(filter(lambda x: x['type'] == 'privilegeUpdate', privilege_data)) - - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Collect notes on event types that have notes if user should see those notes - for event in enriched_history: - if ( - event['updateType'] == UpdateCategory.ENCUMBRANCE - and event.get('encumbranceDetails') - and should_include_encumbrance_details - ): - # In the case of encumbrances, we return the list of npdb categories associated with it - event['npdbCategories'] = event['encumbranceDetails']['clinicalPrivilegeActionCategories'] - elif event['updateType'] == UpdateCategory.DEACTIVATION and event.get('deactivationDetails'): - event['note'] = event['deactivationDetails']['note'] - - unsanitized_history = { - 'providerId': privilege['providerId'], - 'compact': privilege['compact'], - 'jurisdiction': privilege['jurisdiction'], - 'licenseType': privilege['licenseType'], - 'privilegeId': privilege['privilegeId'], - 'events': enriched_history, - } - history_schema = PrivilegeHistoryResponseSchema() - return history_schema.load(unsanitized_history) - class ProviderUserRecords: """ @@ -850,9 +628,6 @@ def generate_api_response_object(self) -> dict: # Build privileges dict with investigations and adverseActions for privilege_record in self._privilege_records: privilege_dict = privilege_record.to_dict() - privilege_updates = self.get_update_records_for_privilege( - privilege_record.jurisdiction, privilege_record.licenseType - ) privilege_dict['adverseActions'] = [ rec.to_dict() @@ -866,12 +641,6 @@ def generate_api_response_object(self) -> dict: privilege_record.jurisdiction, privilege_record.licenseTypeAbbreviation ) ] - active_since = ProviderRecordUtility.calculate_privilege_active_since_date( - privilege_record, privilege_updates - ) - # we only include this value if the privilege is currently active - if active_since: - privilege_dict['activeSince'] = active_since privileges.append(privilege_dict) provider['licenses'] = licenses diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py index ff73f21f3..7ea74c8d9 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/data_event/api.py @@ -4,23 +4,7 @@ Compact, Jurisdiction, ) -from marshmallow.fields import UUID, Date, DateTime, Email, List, Nested, String -from marshmallow.validate import Length - - -class PrivilegeEventPrivilegeSchema(ForgivingSchema): - compact = String(required=True, allow_none=False) - providerId = UUID(required=True, allow_none=False) - jurisdiction = String(required=True, allow_none=False) - licenseTypeAbbrev = String(required=True, allow_none=False) - privilegeId = String(required=True, allow_none=False) - - -class PrivilegeEventLineItemSchema(ForgivingSchema): - name = String(required=True, allow_none=False) - description = String(required=True, allow_none=False) - quantity = String(required=True, allow_none=False) - unitPrice = String(required=True, allow_none=False) +from marshmallow.fields import UUID, Date, DateTime, String class DataEventDetailBaseSchema(ForgivingSchema): @@ -29,23 +13,6 @@ class DataEventDetailBaseSchema(ForgivingSchema): eventTime = DateTime(required=True, allow_none=False) -class PrivilegePurchaseEventDetailSchema(DataEventDetailBaseSchema): - providerEmail = Email(required=False, allow_none=False) - privileges = List(Nested(PrivilegeEventPrivilegeSchema(), required=True, allow_none=False), validate=Length(1, 100)) - totalCost = String(required=True, allow_none=False) - costLineItems = List( - Nested(PrivilegeEventLineItemSchema(), required=True, allow_none=False), validate=Length(1, 300) - ) - - -class PrivilegeIssuanceDetailSchema(DataEventDetailBaseSchema): - providerEmail = Email(required=False, allow_none=False) - - -class PrivilegeRenewalDetailSchema(DataEventDetailBaseSchema): - providerEmail = Email(required=False, allow_none=False) - - class EncumbranceEventDetailSchema(DataEventDetailBaseSchema): providerId = UUID(required=True, allow_none=False) adverseActionId = UUID(required=False, allow_none=False) @@ -75,12 +42,3 @@ class LicenseRevertDetailSchema(DataEventDetailBaseSchema): startTime = DateTime(required=True, allow_none=False) endTime = DateTime(required=True, allow_none=False) rollbackExecutionName = String(required=True, allow_none=False) - - -class PrivilegeRevertDetailSchema(DataEventDetailBaseSchema): - providerId = UUID(required=True, allow_none=False) - licenseType = String(required=True, allow_none=False) - rollbackReason = String(required=True, allow_none=False) - startTime = DateTime(required=True, allow_none=False) - endTime = DateTime(required=True, allow_none=False) - rollbackExecutionName = String(required=True, allow_none=False) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py index 9da8acc5e..465e08ebd 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/__init__.py @@ -41,12 +41,8 @@ def licenseType(self) -> str: return self._data['licenseType'] @property - def npi(self) -> str | None: - return self._data.get('npi') - - @property - def licenseNumber(self) -> str | None: - return self._data.get('licenseNumber') + def licenseNumber(self) -> str: + return self._data['licenseNumber'] @property def ssnLastFour(self) -> str: diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/api.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/api.py index 2a1d39d56..de42a9d7f 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/api.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/api.py @@ -20,7 +20,6 @@ InvestigationStatusField, ITUTE164PhoneNumber, Jurisdiction, - NationalProviderIdentifier, SocialSecurityNumber, ) from cc_common.data_model.schema.investigation.api import InvestigationGeneralResponseSchema @@ -71,8 +70,7 @@ class LicensePostRequestSchema(CCRequestSchema, StrictSchema): """ ssn = SocialSecurityNumber(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) licenseStatusName = String(required=False, allow_none=False, validate=Length(1, 100)) # Note that the two fields below, `licenseStatus` and `compactEligibility`, are stored # in the database as `jurisdictionUploadedLicenseStatus` and `jurisdictionUploadedCompactEligibility`. @@ -138,8 +136,7 @@ class LicenseReportResponseSchema(ForgivingSchema): jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) @@ -168,8 +165,7 @@ class LicenseGeneralResponseSchema(LicenseExpirationStatusMixin, ForgivingSchema jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) @@ -209,8 +205,7 @@ class LicenseReadPrivateResponseSchema(LicenseExpirationStatusMixin, ForgivingSc jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/ingest.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/ingest.py index 056dfd636..9f7e205f9 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/ingest.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/ingest.py @@ -10,7 +10,6 @@ Compact, CompactEligibility, Jurisdiction, - NationalProviderIdentifier, ) from cc_common.data_model.schema.license.common import LicenseCommonSchema @@ -25,8 +24,7 @@ class LicenseIngestSchema(LicenseCommonSchema): ssnLastFour = String(required=True, allow_none=False, validate=Length(equal=4)) providerId = UUID(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) # This is used to calculate the actual 'licenseStatus' used by the system in addition # to the expiration date of the license. jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/record.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/record.py index 1cbbed27c..376bd911d 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/record.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/license/record.py @@ -27,7 +27,6 @@ ITUTE164PhoneNumber, Jurisdiction, LicenseEncumberedStatusField, - NationalProviderIdentifier, UpdateType, ) from cc_common.data_model.schema.investigation.record import InvestigationDetailsSchema @@ -58,8 +57,7 @@ class LicenseRecordSchema(BaseRecordSchema, LicenseCommonSchema): firstUploadDate = DateTime(required=False, allow_none=False) # Provided fields - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) ssnLastFour = String(required=True, allow_none=False) # optional field for setting encumbrance status @@ -172,8 +170,7 @@ class LicenseUpdateRecordPreviousSchema(ForgivingSchema): DB -> load() -> Python """ - npi = NationalProviderIdentifier(required=False, allow_none=False) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) + licenseNumber = String(required=True, allow_none=False, validate=Length(1, 100)) ssnLastFour = String(required=True, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py index ad5a55eeb..72340609f 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py @@ -1,19 +1,17 @@ # ruff: noqa: N801, N815, ARG002 invalid-name unused-argument from marshmallow.fields import List, Nested, Raw, String -from marshmallow.validate import ContainsNoneOf, Length +from marshmallow.validate import Length from cc_common.data_model.schema.adverse_action.api import ( AdverseActionGeneralResponseSchema, AdverseActionPublicResponseSchema, ) from cc_common.data_model.schema.base_record import ForgivingSchema -from cc_common.data_model.schema.common import UpdateCategory from cc_common.data_model.schema.fields import ( ActiveInactive, Compact, InvestigationStatusField, Jurisdiction, - UpdateType, ) from cc_common.data_model.schema.investigation.api import InvestigationGeneralResponseSchema @@ -62,9 +60,6 @@ class PrivilegeGeneralResponseSchema(ForgivingSchema): # the human-friendly identifier for this privilege privilegeId = String(required=True, allow_none=False) status = ActiveInactive(required=True, allow_none=False) - # This field shows how long the privilege have been continuously active according to - # its history - activeSince = Raw(required=False, allow_none=False) # This field is only set if the privilege is under investigation investigationStatus = InvestigationStatusField(required=False, allow_none=False) @@ -93,9 +88,6 @@ class PrivilegeReadPrivateResponseSchema(ForgivingSchema): # the human-friendly identifier for this privilege privilegeId = String(required=True, allow_none=False) status = ActiveInactive(required=True, allow_none=False) - # This field shows how long the privilege have been continuously active according to - # its history - activeSince = Raw(required=False, allow_none=False) # This field is only set if the privilege is under investigation investigationStatus = InvestigationStatusField(required=False, allow_none=False) @@ -127,44 +119,3 @@ class PrivilegePublicResponseSchema(ForgivingSchema): # the human-friendly identifier for this privilege privilegeId = String(required=True, allow_none=False) status = ActiveInactive(required=True, allow_none=False) - # This field shows how long the privilege have been continuously active according to - # its history - activeSince = Raw(required=False, allow_none=False) - - -class PrivilegeHistoryEventResponseSchema(ForgivingSchema): - """ - Privilege history event object fields, as seen by the public lookup endpoint. - Serialization direction: - Python -> load() -> API - """ - - type = String(required=True, allow_none=False) - # We specifically prohibit returning investigation updates as a backup protection from accidental - # disclosure via the API - updateType = UpdateType( - required=True, - allow_none=False, - validate=ContainsNoneOf((UpdateCategory.INVESTIGATION, UpdateCategory.CLOSING_INVESTIGATION)), - ) - dateOfUpdate = Raw(required=True, allow_none=False) - effectiveDate = Raw(required=True, allow_none=False) - createDate = Raw(required=True, allow_none=False) - note = String(required=False, allow_none=True) - # in the case of encumbrance events, we return the list of categories rather than a note - npdbCategories = List(String(), required=False, allow_none=True) - - -class PrivilegeHistoryResponseSchema(ForgivingSchema): - """ - Privilege history object fields, as seen by the public lookup endpoint. - Serialization direction: - Python -> load() -> API - """ - - providerId = Raw(required=True, allow_none=False) - compact = Compact(required=True, allow_none=False) - jurisdiction = Jurisdiction(required=True, allow_none=False) - licenseType = String(required=True, allow_none=False) - privilegeId = String(required=True, allow_none=False) - events = List(Nested(PrivilegeHistoryEventResponseSchema(), required=False, allow_none=False)) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py index e78580a10..8f4cdf9d3 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/__init__.py @@ -45,10 +45,6 @@ def jurisdictionUploadedCompactEligibility(self) -> str: def ssnLastFour(self) -> str: return self._data['ssnLastFour'] - @property - def npi(self) -> str | None: - return self._data.get('npi') - @property def givenName(self) -> str: return self._data['givenName'] @@ -77,10 +73,6 @@ def dateOfBirth(self) -> date: def birthMonthDay(self) -> str | None: return self._data.get('birthMonthDay') - @property - def privilegeJurisdictions(self) -> set[str]: - return self._data.get('privilegeJurisdictions', set()) - @property def encumberedStatus(self) -> str | None: return self._data.get('encumberedStatus') diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py index 95844c70b..fd823febb 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py @@ -1,9 +1,7 @@ # ruff: noqa: N801, N815, ARG002 invalid-name unused-argument -from datetime import timedelta - from marshmallow import ValidationError, validates_schema -from marshmallow.fields import DateTime, Email, Integer, List, Nested, Raw, String -from marshmallow.validate import Length, OneOf, Range, Regexp +from marshmallow.fields import Integer, List, Nested, Raw, String +from marshmallow.validate import Length, Range, Regexp from cc_common.data_model.schema.base_record import ForgivingSchema from cc_common.data_model.schema.common import CCRequestSchema @@ -12,8 +10,6 @@ Compact, CompactEligibility, Jurisdiction, - NationalProviderIdentifier, - Set, SocialSecurityNumber, ) from cc_common.data_model.schema.license.api import ( @@ -95,7 +91,6 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): licenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) @@ -107,7 +102,6 @@ class ProviderReadPrivateResponseSchema(ForgivingSchema): jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) - privilegeJurisdictions = Set(String, required=False, allow_none=False, load_default=set()) providerFamGivMid = String(required=False, allow_none=False, validate=Length(2, 400)) providerDateOfUpdate = Raw(required=False, allow_none=False) birthMonthDay = String(required=True, allow_none=False, validate=Regexp('^[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}')) @@ -145,7 +139,6 @@ class ProviderGeneralResponseSchema(ForgivingSchema): licenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) @@ -156,7 +149,6 @@ class ProviderGeneralResponseSchema(ForgivingSchema): jurisdictionUploadedLicenseStatus = ActiveInactive(required=True, allow_none=False) jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) - privilegeJurisdictions = Set(String, required=False, allow_none=False, load_default=set()) providerFamGivMid = String(required=False, allow_none=False, validate=Length(2, 400)) providerDateOfUpdate = Raw(required=False, allow_none=False) birthMonthDay = String(required=True, allow_none=False, validate=Regexp('^[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}')) @@ -189,13 +181,11 @@ class ProviderPublicResponseSchema(ForgivingSchema): licenseJurisdiction = Jurisdiction(required=True, allow_none=False) licenseStatus = ActiveInactive(required=True, allow_none=False) compactEligibility = CompactEligibility(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) suffix = String(required=False, allow_none=False, validate=Length(1, 100)) - privilegeJurisdictions = Set(String, required=False, allow_none=False, load_default=set()) # Unlike the internal provider search endpoints used by staff users, which return license data in addition to # privilege data for a provider, we only return privilege data for a provider from the public GET provider endpoint privileges = List(Nested(PrivilegePublicResponseSchema(), required=False, allow_none=False)) @@ -244,141 +234,6 @@ class SortingSchema(ForgivingSchema): sorting = Nested(SortingSchema, required=False, allow_none=False) -class QueryJurisdictionProvidersRequestSchema(CCRequestSchema): - """ - Schema for jurisdiction-specific query providers requests. - - This schema is used to validate incoming requests to the jurisdiction-specific query providers API endpoint. - It supports time window filtering by dateOfUpdate through startDateTime and endDateTime query parameters. - - Serialization direction: - API -> load() -> Python - """ - - class QuerySchema(CCRequestSchema): - """ - Nested schema for the query object within the request. - """ - - startDateTime = DateTime(required=True, allow_none=False) - endDateTime = DateTime(required=True, allow_none=False) - - class PaginationSchema(ForgivingSchema): - """ - Nested schema for the pagination object within the request. - """ - - lastKey = String(required=False, allow_none=False, validate=Length(min=1, max=1024)) - pageSize = Integer(required=False, allow_none=False) - - class SortingSchema(ForgivingSchema): - """ - Nested schema for the sorting object within the request. - """ - - direction = String(required=False, allow_none=False) - - @validates_schema - def validate_query(self, data, **kwargs): - """ - Time filter cannot be larger than 7 days. - """ - if data['query']['endDateTime'] - data['query']['startDateTime'] > timedelta(days=7): - raise ValidationError('Time filter cannot be larger than 7 days.') - - query = Nested(QuerySchema, required=True, allow_none=False) - pagination = Nested(PaginationSchema, required=False, allow_none=False) - sorting = Nested(SortingSchema, required=False, allow_none=False) - - -class StatePrivilegeGeneralResponseSchema(ForgivingSchema): - """ - Schema for flattened state privilege responses with general (non-private) fields only. - - This schema combines privilege and license data into a single flattened record - for external state IT system consumption, excluding private/sensitive fields. - - Serialization direction: - Python -> load() -> API - """ - - type = String(required=True, allow_none=False, validate=OneOf(['statePrivilege'])) - providerId = Raw(required=True, allow_none=False) - compact = Compact(required=True, allow_none=False) - jurisdiction = Jurisdiction(required=True, allow_none=False) - licenseType = String(required=True, allow_none=False) - privilegeId = String(required=True, allow_none=False) - status = ActiveInactive(required=True, allow_none=False) - compactEligibility = CompactEligibility(required=True, allow_none=False) - dateOfExpiration = Raw(required=True, allow_none=False) - dateOfIssuance = Raw(required=True, allow_none=False) - dateOfRenewal = Raw(required=True, allow_none=False) - dateOfUpdate = Raw(required=True, allow_none=False) - familyName = String(required=True, allow_none=False, validate=Length(1, 100)) - givenName = String(required=True, allow_none=False, validate=Length(1, 100)) - licenseJurisdiction = Jurisdiction(required=True, allow_none=False) - licenseStatus = ActiveInactive(required=True, allow_none=False) - - # Optional non-private fields - middleName = String(required=False, allow_none=False, validate=Length(1, 100)) - suffix = String(required=False, allow_none=False, validate=Length(1, 100)) - licenseStatusName = String(required=False, allow_none=False, validate=Length(1, 100)) - licenseNumber = String(required=False, allow_none=False, validate=Length(1, 100)) - npi = NationalProviderIdentifier(required=False, allow_none=False) - - -class StatePrivilegePrivateResponseSchema(StatePrivilegeGeneralResponseSchema): - """ - Schema for flattened state privilege responses including private/sensitive fields. - - Extends the general schema to include private fields like SSN, addresses, etc. - - Serialization direction: - Python -> load() -> API - """ - - # Private fields - ssnLastFour = String(required=False, allow_none=False, validate=Length(min=4, max=4)) - emailAddress = Email(required=False, allow_none=False) - dateOfBirth = Raw(required=False, allow_none=False) - homeAddressStreet1 = String(required=False, allow_none=False, validate=Length(2, 100)) - homeAddressStreet2 = String(required=False, allow_none=False, validate=Length(1, 100)) - homeAddressCity = String(required=False, allow_none=False, validate=Length(2, 100)) - homeAddressState = String(required=False, allow_none=False, validate=Length(2, 100)) - homeAddressPostalCode = String(required=False, allow_none=False, validate=Length(5, 7)) - phoneNumber = String(required=False, allow_none=False, validate=Regexp(r'^\+[0-9]{8,15}$')) - - -class StateProviderDetailPrivateResponseSchema(ForgivingSchema): - """ - Schema for state provider detail response. - - This schema is used for the state API GET provider endpoint that returns - a simplified, flattened view of provider data for external state IT systems. - - Serialization direction: - Python -> load() -> API - """ - - privileges = List(Nested(StatePrivilegePrivateResponseSchema, required=True, allow_none=False)) - providerUIUrl = String(required=True, allow_none=False) - - -class StateProviderDetailGeneralResponseSchema(ForgivingSchema): - """ - Schema for state provider detail response. - - This schema is used for the state API GET provider endpoint that returns - a simplified, flattened view of provider data for external state IT systems. - - Serialization direction: - Python -> load() -> API - """ - - privileges = List(Nested(StatePrivilegeGeneralResponseSchema, required=True, allow_none=False)) - providerUIUrl = String(required=True, allow_none=False) - - class SearchProvidersRequestSchema(CCRequestSchema): """ Schema for advanced search providers requests. @@ -425,27 +280,3 @@ def validate_no_cross_index_queries(self, data, **kwargs): - More like this with external docs: {"more_like_this": {"like": [{"_index": "other_index"}]}} """ _validate_no_cross_index_keys(data.get('query', {})) - - -class ExportPrivilegesRequestSchema(CCRequestSchema): - """ - Schema for Exporting list of privileges into CSV file. - - This schema is used to validate incoming requests to the advanced search providers API endpoint. - It accepts an OpenSearch DSL query body for flexible querying of the provider index. - - Serialization direction: - API -> load() -> Python - """ - - # The OpenSearch query body - we use Raw to allow the full flexibility of OpenSearch queries - query = Raw(required=True, allow_none=False) - - @validates_schema - def validate_no_cross_index_queries(self, data, **kwargs): - """ - Validate that the query does not contain cross-index lookup attempts. - - This is a defense-in-depth security measure. See SearchProvidersRequestSchema for details. - """ - _validate_no_cross_index_keys(data.get('query', {})) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/record.py b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/record.py index 7d28d86fb..c250b5d08 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/record.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/data_model/schema/provider/record.py @@ -20,8 +20,6 @@ CompactEligibility, Jurisdiction, LicenseEncumberedStatusField, - NationalProviderIdentifier, - Set, UpdateType, ) from cc_common.data_model.update_tier_enum import UpdateTierEnum @@ -55,7 +53,6 @@ class ProviderRecordSchema(BaseRecordSchema): encumberedStatus = LicenseEncumberedStatusField(required=False, allow_none=False) ssnLastFour = String(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) @@ -67,7 +64,6 @@ class ProviderRecordSchema(BaseRecordSchema): # Generated fields birthMonthDay = String(required=False, allow_none=False, validate=Regexp('^[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}')) - privilegeJurisdictions = Set(String, required=False, allow_none=False, load_default=set()) providerFamGivMid = String(required=False, allow_none=False, validate=Length(2, 400)) providerDateOfUpdate = DateTime(required=True, allow_none=False) @@ -149,13 +145,6 @@ def populate_fam_giv_mid(self, in_data, **kwargs): # noqa: ARG001 unused-argume ) return in_data - @pre_dump - def remove_empty_privilege_jurisdictions(self, in_data, **kwargs): # noqa: ARG001 unused-argument - # DynamoDB doesn't accept empty sets, so remove privilegeJurisdictions if empty - if 'privilegeJurisdictions' in in_data and not in_data['privilegeJurisdictions']: - del in_data['privilegeJurisdictions'] - return in_data - @post_load def drop_fam_giv_mid(self, in_data, **kwargs): # noqa: ARG001 unused-argument del in_data['providerFamGivMid'] @@ -177,7 +166,6 @@ class ProviderUpdatePreviousRecordSchema(ForgivingSchema): jurisdictionUploadedCompactEligibility = CompactEligibility(required=True, allow_none=False) encumberedStatus = LicenseEncumberedStatusField(required=False, allow_none=False) ssnLastFour = String(required=True, allow_none=False) - npi = NationalProviderIdentifier(required=False, allow_none=False) givenName = String(required=True, allow_none=False, validate=Length(1, 100)) middleName = String(required=False, allow_none=False, validate=Length(1, 100)) familyName = String(required=True, allow_none=False, validate=Length(1, 100)) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py index a1a972d4c..c53d3e29e 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py @@ -91,39 +91,6 @@ def _invoke_lambda(self, payload: dict[str, Any]) -> dict[str, Any]: self._logger.error(error_message, payload=payload, exception=str(e)) raise CCInternalException(error_message) from e - def send_jurisdiction_privilege_deactivation_email( - self, - compact: str, - jurisdiction: str, - privilege_id: str, - provider_first_name: str, - provider_last_name: str, - ) -> dict[str, str]: - """ - Send a privilege deactivation notification email to jurisdiction. - - :param compact: Compact name - :param jurisdiction: Jurisdiction name - :param privilege_id: ID of the privilege being deactivated - :param provider_first_name: First name of the provider whose privilege was deactivated - :param provider_last_name: Last name of the provider whose privilege was deactivated - :return: Response from the email notification service - """ - payload = { - 'compact': compact, - 'jurisdiction': jurisdiction, - 'template': 'privilegeDeactivationJurisdictionNotification', - # for now, we send this notification to the same contacts as the summary report recipients - 'recipientType': 'JURISDICTION_SUMMARY_REPORT', - 'templateVariables': { - 'privilegeId': privilege_id, - 'providerFirstName': provider_first_name, - 'providerLastName': provider_last_name, - }, - } - - return self._invoke_lambda(payload) - def send_license_encumbrance_provider_notification_email( self, *, diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/event_bus_client.py b/backend/cosmetology-app/lambdas/python/common/cc_common/event_bus_client.py index fe6af52b7..03349c3ad 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/event_bus_client.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/event_bus_client.py @@ -11,10 +11,6 @@ InvestigationEventDetailSchema, LicenseDeactivationDetailSchema, LicenseRevertDetailSchema, - PrivilegeIssuanceDetailSchema, - PrivilegePurchaseEventDetailSchema, - PrivilegeRenewalDetailSchema, - PrivilegeRevertDetailSchema, ) from cc_common.event_batch_writer import EventBatchWriter from cc_common.utils import ResponseEncoder @@ -86,93 +82,6 @@ def generate_license_deactivation_event( 'EventBusName': config.event_bus_name, } - def publish_privilege_purchase_event( - self, - source: str, - jurisdiction: str, - compact: str, - provider_email: str, - privileges: list[dict], - total_cost: str, - cost_line_items: list[dict], - event_batch_writer: EventBatchWriter | None = None, - ): - event_detail = { - 'jurisdiction': jurisdiction, - 'compact': compact, - 'providerEmail': provider_email, - 'privileges': privileges, - 'totalCost': total_cost, - 'costLineItems': cost_line_items, - 'eventTime': config.current_standard_datetime.isoformat(), - } - - privilege_purchase_detail_schema = PrivilegePurchaseEventDetailSchema() - - loaded_detail = privilege_purchase_detail_schema.load(event_detail) - deserialized_detail = privilege_purchase_detail_schema.dump(loaded_detail) - - self._publish_event( - source=source, - detail_type='privilege.purchase', - detail=deserialized_detail, - event_batch_writer=event_batch_writer, - ) - - def publish_privilege_issued_event( - self, - source: str, - jurisdiction: str, - compact: str, - provider_email: str, - event_batch_writer: EventBatchWriter | None = None, - ): - event_detail = { - 'jurisdiction': jurisdiction, - 'compact': compact, - 'providerEmail': provider_email, - 'eventTime': config.current_standard_datetime.isoformat(), - } - - privilege_issued_detail_schema = PrivilegeIssuanceDetailSchema() - - loaded_detail = privilege_issued_detail_schema.load(event_detail) - deserialized_detail = privilege_issued_detail_schema.dump(loaded_detail) - - self._publish_event( - source=source, - detail_type='privilege.issued', - detail=deserialized_detail, - event_batch_writer=event_batch_writer, - ) - - def publish_privilege_renewed_event( - self, - source: str, - jurisdiction: str, - compact: str, - provider_email: str, - event_batch_writer: EventBatchWriter | None = None, - ): - event_detail = { - 'jurisdiction': jurisdiction, - 'compact': compact, - 'providerEmail': provider_email, - 'eventTime': config.current_standard_datetime.isoformat(), - } - - privilege_renewal_detail_schema = PrivilegeRenewalDetailSchema() - - loaded_detail = privilege_renewal_detail_schema.load(event_detail) - deserialized_detail = privilege_renewal_detail_schema.dump(loaded_detail) - - self._publish_event( - source=source, - detail_type='privilege.renewed', - detail=deserialized_detail, - event_batch_writer=event_batch_writer, - ) - def publish_license_encumbrance_event( self, source: str, @@ -493,55 +402,3 @@ def publish_license_revert_event( detail=deserialized_detail, event_batch_writer=event_batch_writer, ) - - def publish_privilege_revert_event( - self, - source: str, - compact: str, - provider_id: str, - jurisdiction: str, - license_type: str, - rollback_reason: str, - start_time: datetime, - end_time: datetime, - execution_name: str, - event_batch_writer: EventBatchWriter | None = None, - ): - """ - Publish a privilege revert event to the event bus. - - :param source: The source of the event - :param compact: The compact name - :param provider_id: The provider ID - :param jurisdiction: The jurisdiction of the privilege - :param license_type: The license type - :param rollback_reason: The reason for the rollback - :param start_time: The start time of the rollback window - :param end_time: The end time of the rollback window - :param execution_name: The execution name for the rollback operation - :param event_batch_writer: Optional EventBatchWriter for efficient batch publishing - """ - event_detail = { - 'compact': compact, - 'providerId': provider_id, - 'jurisdiction': jurisdiction, - 'licenseType': license_type, - 'rollbackReason': rollback_reason, - 'startTime': start_time, - 'endTime': end_time, - 'rollbackExecutionName': execution_name, - 'eventTime': config.current_standard_datetime, - } - - privilege_revert_detail_schema = PrivilegeRevertDetailSchema() - deserialized_detail = privilege_revert_detail_schema.dump(event_detail) - validation_errors = privilege_revert_detail_schema.validate(deserialized_detail) - if validation_errors: - raise ValidationError(message=validation_errors) - - self._publish_event( - source=source, - detail_type='privilege.revert', - detail=deserialized_detail, - event_batch_writer=event_batch_writer, - ) diff --git a/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py b/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py index be05eaf25..dce8c4dcd 100644 --- a/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py +++ b/backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py @@ -1,8 +1,7 @@ """ Signature Authentication Module -This module provides decorators for validating ECDSA-based request signatures as described in the -client_signature_auth.md documentation. +This module provides decorators for validating ECDSA-based request signatures. """ import base64 diff --git a/backend/cosmetology-app/lambdas/python/common/common_test/sign_request.py b/backend/cosmetology-app/lambdas/python/common/common_test/sign_request.py index 8297c1308..2d7634e67 100644 --- a/backend/cosmetology-app/lambdas/python/common/common_test/sign_request.py +++ b/backend/cosmetology-app/lambdas/python/common/common_test/sign_request.py @@ -7,7 +7,6 @@ The sign_request function in this module is tested and validated against the actual authentication system, making it a reliable reference for client implementations. -For complete documentation, see: docs/client_signature_auth.md """ import base64 diff --git a/backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py b/backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py index 107dcaeea..84508d460 100644 --- a/backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py +++ b/backend/cosmetology-app/lambdas/python/common/common_test/test_data_generator.py @@ -220,7 +220,6 @@ def generate_default_license(value_overrides: dict | None = None) -> LicenseData 'type': LICENSE_RECORD_TYPE, 'jurisdiction': DEFAULT_LICENSE_JURISDICTION, 'licenseType': DEFAULT_LICENSE_TYPE, - 'npi': DEFAULT_NPI, 'licenseNumber': DEFAULT_LICENSE_NUMBER, 'ssnLastFour': DEFAULT_SSN_LAST_FOUR, 'givenName': DEFAULT_GIVEN_NAME, @@ -403,11 +402,9 @@ def generate_default_provider(value_overrides: dict | None = None) -> ProviderDa 'compact': DEFAULT_COMPACT, 'type': PROVIDER_RECORD_TYPE, 'licenseJurisdiction': DEFAULT_LICENSE_JURISDICTION, - 'privilegeJurisdictions': {DEFAULT_PRIVILEGE_JURISDICTION}, 'jurisdictionUploadedLicenseStatus': DEFAULT_LICENSE_STATUS, 'jurisdictionUploadedCompactEligibility': DEFAULT_COMPACT_ELIGIBILITY, 'ssnLastFour': DEFAULT_SSN_LAST_FOUR, - 'npi': DEFAULT_NPI, 'givenName': DEFAULT_GIVEN_NAME, 'middleName': DEFAULT_MIDDLE_NAME, 'familyName': DEFAULT_FAMILY_NAME, diff --git a/backend/cosmetology-app/lambdas/python/common/tests/function/__init__.py b/backend/cosmetology-app/lambdas/python/common/tests/function/__init__.py index b408deb0d..3a4bfa3ec 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/function/__init__.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/function/__init__.py @@ -245,14 +245,9 @@ def _load_provider_data(self) -> str: """Use the canned test resources to load a basic provider to the DB""" test_resources = glob('../common/tests/resources/dynamo/provider.json') - def privilege_jurisdictions_to_set(obj: dict): - if obj.get('type') == 'provider' and 'privilegeJurisdictions' in obj: - obj['privilegeJurisdictions'] = set(obj['privilegeJurisdictions']) - return obj - for resource in test_resources: with open(resource) as f: - record = json.load(f, object_hook=privilege_jurisdictions_to_set, parse_float=Decimal) + record = json.load(f, parse_float=Decimal) logger.debug('Loading resource, %s: %s', resource, str(record)) self._provider_table.put_item(Item=record) diff --git a/backend/cosmetology-app/lambdas/python/common/tests/function/test_data_client.py b/backend/cosmetology-app/lambdas/python/common/tests/function/test_data_client.py index 98f4dbc55..fedd5f5f7 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/function/test_data_client.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/function/test_data_client.py @@ -5,7 +5,6 @@ from boto3.dynamodb.conditions import Key from cc_common.data_model.update_tier_enum import UpdateTierEnum -from cc_common.exceptions import CCInvalidRequestException from moto import mock_aws from tests.function import TstFunction @@ -67,7 +66,6 @@ def _load_provider_data(self) -> UUID: with open('tests/resources/dynamo/provider.json') as f: provider_record = json.load(f) provider_id = UUID(provider_record['providerId']) - provider_record['privilegeJurisdictions'] = set(provider_record['privilegeJurisdictions']) self._provider_table.put_item(Item=provider_record) with open('tests/resources/dynamo/privilege.json') as f: @@ -84,74 +82,6 @@ def _load_provider_data(self) -> UUID: return provider_id - def test_data_client_create_privilege_record_invalid_license_type(self): - from cc_common.data_model.data_client import DataClient - from cc_common.exceptions import CCInvalidRequestException - - test_data_client = DataClient(self.config) - - with self.assertRaises(CCInvalidRequestException): - test_data_client.create_provider_privileges( - compact='cosm', - provider_id='test_provider_id', - jurisdiction_postal_abbreviations=['ca'], - license_expiration_date=date.fromisoformat('2024-10-31'), - provider_record=self.test_data_generator.generate_default_provider(), - existing_privileges_for_license=[], - license_type='not-supported-license-type', - ) - - def test_claim_privilege_id_creates_counter_if_not_exists(self): - """Test that claiming a privilege id creates the counter if it doesn't exist""" - from cc_common.data_model.data_client import DataClient - - client = DataClient(self.config) - - # First claim should create the counter and return 1 - privilege_count = client.claim_privilege_number(compact='cosm') - self.assertEqual(1, privilege_count) - - # Verify the counter was created with the correct value - counter_record = self.config.provider_table.get_item( - Key={ - 'pk': 'cosm#PRIVILEGE_COUNT', - 'sk': 'cosm#PRIVILEGE_COUNT', - } - )['Item'] - self.assertEqual(1, counter_record['privilegeCount']) - - def test_claim_privilege_id_increments_existing_counter(self): - """Test that claiming a privilege id increments an existing counter""" - from cc_common.data_model.data_client import DataClient - - client = DataClient(self.config) - - # Create initial counter record - self.config.provider_table.put_item( - Item={ - 'pk': 'cosm#PRIVILEGE_COUNT', - 'sk': 'cosm#PRIVILEGE_COUNT', - 'type': 'privilegeCount', - 'compact': 'cosm', - 'privilegeCount': 42, - } - ) - - # Claim should increment the counter and return 43 - privilege_count = client.claim_privilege_number(compact='cosm') - self.assertEqual(43, privilege_count) - - # Verify the counter was incremented - counter_record = self.config.provider_table.get_item( - Key={ - 'pk': 'cosm#PRIVILEGE_COUNT', - 'sk': 'cosm#PRIVILEGE_COUNT', - } - )['Item'] - self.assertEqual(43, counter_record['privilegeCount']) - self.assertEqual('privilegeCount', counter_record['type']) - self.assertEqual('cosm', counter_record['compact']) - def test_get_ssn_by_provider_id_returns_ssn_if_provider_id_exists(self): """Test that get_ssn_by_provider_id returns the SSN if the provider ID exists""" from cc_common.data_model.data_client import DataClient @@ -198,127 +128,6 @@ def test_get_ssn_by_provider_id_raises_exception_multiple_records_found(self): with self.assertRaises(CCInternalException): client.get_ssn_by_provider_id(compact='cosm', provider_id='89a6377e-c3a5-40e5-bca5-317ec854c570') - def test_deactivate_privilege_raises_if_privilege_not_found(self): - from cc_common.data_model.data_client import DataClient - from cc_common.exceptions import CCNotFoundException - - test_data_client = DataClient(self.config) - - # We haven't created any providers or privileges but we'll try to deactivate a privilege - with self.assertRaises(CCNotFoundException): - test_data_client.deactivate_privilege( - compact='cosm', - provider_id='some-provider-id', - jurisdiction='ne', - license_type_abbr='cos', - deactivation_details={ - 'note': 'test deactivation note', - 'deactivatedByStaffUserId': 'a4182428-d061-701c-82e5-a3d1d547d797', - 'deactivatedByStaffUserName': 'John Doe', - }, - ) - - def test_deactivate_privilege_on_inactive_privilege_raises_exception(self): - from cc_common.data_model.data_client import DataClient - from cc_common.data_model.provider_record_util import ProviderUserRecords - - provider_id = self._load_provider_data() - - # Remove 'ne' from privilegeJurisdictions - self._provider_table.update_item( - Key={'pk': f'cosm#PROVIDER#{provider_id}', 'sk': 'cosm#PROVIDER'}, - UpdateExpression='DELETE privilegeJurisdictions :jurisdiction', - ExpressionAttributeValues={':jurisdiction': {'ne'}}, - ) - - # Create the first privilege - original_privilege = { - 'pk': f'cosm#PROVIDER#{provider_id}', - 'sk': 'cosm#PROVIDER#privilege/ne/cos#', - 'type': 'privilege', - 'providerId': str(provider_id), - 'compact': 'cosm', - 'jurisdiction': 'ne', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'administratorSetStatus': 'inactive', - 'dateOfIssuance': '2023-11-08T23:59:59+00:00', - 'dateOfRenewal': '2023-11-08T23:59:59+00:00', - 'dateOfExpiration': '2024-10-31', - 'dateOfUpdate': '2023-11-08T23:59:59+00:00', - 'privilegeId': 'COS-NE-1', - } - self._provider_table.put_item(Item=original_privilege) - # We'll create it as if it were already deactivated - original_history = { - 'pk': f'cosm#PROVIDER#{provider_id}', - 'sk': 'cosm#UPDATE#1#privilege/ne/cos/2024-11-08T23:59:59+00:00/6dd62fb59bbae8bd55720ae1491fa87a', - 'type': 'privilegeUpdate', - 'updateType': 'renewal', - 'providerId': str(provider_id), - 'compact': 'cosm', - 'licenseType': 'cosmetologist', - 'createDate': '2024-11-08T23:59:59+00:00', - 'effectiveDate': '2024-11-08T23:59:59+00:00', - 'jurisdiction': 'ne', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'previous': { - 'dateOfIssuance': '2023-11-08T23:59:59+00:00', - 'dateOfRenewal': '2023-11-08T23:59:59+00:00', - 'dateOfExpiration': '2024-10-31', - 'dateOfUpdate': '2023-11-08T23:59:59+00:00', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-NE-1', - }, - 'updatedValues': { - 'administratorSetStatus': 'inactive', - }, - } - self._provider_table.put_item(Item=original_history) - - test_data_client = DataClient(self.config) - - # Now, deactivate the privilege - with self.assertRaises(CCInvalidRequestException) as context: - test_data_client.deactivate_privilege( - compact='cosm', - provider_id=provider_id, - jurisdiction='ne', - license_type_abbr='cos', - deactivation_details={ - 'note': 'test deactivation note', - 'deactivatedByStaffUserId': 'a4182428-d061-701c-82e5-a3d1d547d797', - 'deactivatedByStaffUserName': 'John Doe', - }, - ) - self.assertEqual('Privilege already deactivated', context.exception.message) - - # Verify that the privilege record was unchanged - provider_user_records: ProviderUserRecords = self.config.data_client.get_provider_user_records( - compact='cosm', provider_id=provider_id - ) - - new_privilege = provider_user_records.get_specific_privilege_record( - jurisdiction='ne', license_abbreviation='cos' - ) - self.assertIsNotNone(new_privilege, 'Privilege record not found') - serialized_record = new_privilege.serialize_to_database_record() - # the serialize_to_database_record() call automatically generates a new dateOfUpdate stamp, - # setting it back to the original timestamp for comparison - serialized_record['dateOfUpdate'] = original_privilege['dateOfUpdate'] - self.assertEqual(original_privilege, serialized_record) - - # Verify the update record is unchanged using test_data_generator - update_records = self.test_data_generator.query_privilege_update_records_for_given_record_from_database( - new_privilege - ) - self.assertEqual(1, len(update_records), 'Expected 1 update record') - self.assertEqual(original_history, update_records[0].serialize_to_database_record()) - - # 'ne' should still be removed from privilegeJurisdictions - provider = provider_user_records.get_provider_record() - self.assertEqual(set(), provider.privilegeJurisdictions) - def test_get_provider_user_records_correctly_handles_pagination(self): """Test that get_provider_user_records correctly handles pagination by returning all records. @@ -393,8 +202,6 @@ def mock_query(**kwargs): # Verify we have all privileges from all jurisdictions privilege_records = provider_records.get_privilege_records() self.assertEqual(30, len(privilege_records)) - privilege_jurisdictions = {priv.jurisdiction for priv in privilege_records} - self.assertEqual(set(jurisdictions), privilege_jurisdictions) # Verify we have all license records license_records = provider_records.get_license_records() @@ -594,7 +401,6 @@ def test_create_license_investigation_success(self): 'createDate': investigation.creationDate.isoformat(), 'effectiveDate': investigation.creationDate.isoformat(), 'previous': { - 'npi': '0608337260', 'licenseNumber': 'A0608337260', 'ssnLastFour': '1234', 'givenName': 'Björk', @@ -927,7 +733,6 @@ def test_close_license_investigation_success(self): 'createDate': investigation.creationDate.isoformat(), 'effectiveDate': investigation.creationDate.isoformat(), 'previous': { - 'npi': '0608337260', 'licenseNumber': 'A0608337260', 'ssnLastFour': '1234', 'givenName': 'Björk', diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/license-post.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/license-post.json index 2058acdfa..afe3aa150 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/license-post.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/license-post.json @@ -1,6 +1,5 @@ { "ssn": "123-12-1234", - "npi": "0608337260", "licenseNumber": "A0608337260", "givenName": "Björk", "middleName": "Gunnar", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-detail-response.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-detail-response.json index d215a5bb8..8c669d0b3 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-detail-response.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-detail-response.json @@ -1,7 +1,6 @@ { "type": "provider", "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", - "npi": "0608337260", "ssnLastFour": "1234", "givenName": "Björk", "middleName": "Gunnar", @@ -12,7 +11,6 @@ "compactEligibility": "eligible", "compact": "cosm", "licenseJurisdiction": "oh", - "privilegeJurisdictions": ["ne"], "dateOfBirth": "1985-06-06", "dateOfUpdate": "2024-07-08T23:59:59+00:00", "dateOfExpiration": "2025-04-04", @@ -24,7 +22,6 @@ "compact": "cosm", "jurisdiction": "oh", "ssnLastFour": "1234", - "npi": "0608337260", "licenseNumber": "A0608337260", "licenseType": "cosmetologist", "jurisdictionUploadedLicenseStatus": "active", @@ -60,7 +57,6 @@ "licenseType": "cosmetologist", "licenseJurisdiction": "oh", "status": "active", - "activeSince": "2016-05-05T12:59:59+00:00", "dateOfIssuance": "2016-05-05T12:59:59+00:00", "dateOfRenewal": "2020-05-05T12:59:59+00:00", "dateOfExpiration": "2025-04-04", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-response.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-response.json index 50ab20f3a..f2758cbb9 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-response.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/provider-response.json @@ -1,18 +1,16 @@ { "type": "provider", "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", - "npi": "0608337260", - "givenName": "Björk", + "givenName": "Bj\u00f6rk", "middleName": "Gunnar", - "familyName": "Guðmundsdóttir", + "familyName": "Gu\u00f0mundsd\u00f3ttir", "licenseStatus": "active", "compactEligibility": "eligible", "jurisdictionUploadedLicenseStatus": "active", "jurisdictionUploadedCompactEligibility": "eligible", "compact": "cosm", "licenseJurisdiction": "oh", - "privilegeJurisdictions": ["ne"], "dateOfUpdate": "2024-07-08T23:59:59+00:00", "dateOfExpiration": "2025-04-04", "birthMonthDay": "06-06" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/state-provider-detail-response.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/api/state-provider-detail-response.json deleted file mode 100644 index 2dbe206f0..000000000 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/api/state-provider-detail-response.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "privileges": [ - { - "type": "statePrivilege", - "providerId": "123e4567-e89b-12d3-a456-426614174000", - "compact": "cosm", - "jurisdiction": "ne", - "ssnLastFour": "1234", - "licenseType": "cosmetologist", - "privilegeId": "COS-NE-1", - "licenseNumber": "S0608337260", - "npi": "0608337260", - "status": "active", - "compactEligibility": "eligible", - "emailAddress": "björk@example.com", - "dateOfBirth": "1985-06-06", - "dateOfExpiration": "2025-04-04", - "dateOfIssuance": "2010-06-06", - "dateOfRenewal": "2020-04-04", - "familyName": "Guðmundsdóttir", - "middleName": "Gunnar", - "givenName": "Björk", - "homeAddressCity": "Columbus", - "homeAddressPostalCode": "43004", - "homeAddressState": "oh", - "homeAddressStreet1": "123 A St.", - "homeAddressStreet2": "Apt 321", - "licenseJurisdiction": "oh", - "licenseStatus": "active", - "licenseStatusName": "DEFINITELY_A_HUMAN", - "phoneNumber": "+13213214321" - }, - { - "type": "statePrivilege", - "providerId": "123e4567-e89b-12d3-a456-426614174000", - "compact": "cosm", - "jurisdiction": "ne", - "ssnLastFour": "1234", - "licenseType": "esthetician", - "privilegeId": "ESTH-NE-1", - "status": "active", - "compactEligibility": "eligible", - "dateOfBirth": "1985-06-06", - "dateOfExpiration": "2025-04-04", - "dateOfIssuance": "2010-06-06", - "dateOfRenewal": "2020-04-04", - "familyName": "Guðmundsdóttir", - "middleName": "Gunnar", - "givenName": "Björk", - "homeAddressCity": "Columbus", - "homeAddressPostalCode": "43004", - "homeAddressState": "oh", - "homeAddressStreet1": "123 A St.", - "homeAddressStreet2": "Apt 321", - "licenseJurisdiction": "oh", - "licenseStatus": "active" - } - ], - "providerUIUrl": "https://app.compactconnect.org/cosm/Licensing/860b8fb8-5d0f-4b66-ab6b-1c83d28258ea" -} diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license-update.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license-update.json index 189443eb3..db90daab3 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license-update.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license-update.json @@ -1,6 +1,6 @@ { "pk": "cosm#PROVIDER#89a6377e-c3a5-40e5-bca5-317ec854c570", - "sk": "cosm#UPDATE#3#license/oh/cos/2024-11-08T23:59:59+00:00/34702de3dc08e64922605a6b18f3838b", + "sk": "cosm#UPDATE#3#license/oh/cos/2024-11-08T23:59:59+00:00/6dc76fbfc9426d23fdff44f260747b74", "licenseUploadDateGSIPK": "C#cosm#J#oh#D#2024-11", "licenseUploadDateGSISK": "TIME#1731110399#LT#cos#PID#89a6377e-c3a5-40e5-bca5-317ec854c570", "type": "licenseUpdate", @@ -13,7 +13,6 @@ "licenseType": "cosmetologist", "dateOfUpdate": "2020-04-07T12:59:59+00:00", "previous": { - "npi": "0608337260", "licenseNumber": "A0608337260", "ssnLastFour": "1234", "givenName": "Björk", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license.json index 424ca2cf3..56d6f49c3 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/license.json @@ -6,7 +6,6 @@ "compact": "cosm", "jurisdiction": "oh", "ssnLastFour": "1234", - "npi": "0608337260", "licenseNumber": "A0608337260", "licenseType": "cosmetologist", "givenName": "Björk", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/provider.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/provider.json index 7b69d0597..a5d0ffe8c 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/provider.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/dynamo/provider.json @@ -6,17 +6,15 @@ "type": "provider", "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", "compact": "cosm", - "npi": "0608337260", "ssnLastFour": "1234", - "givenName": "Björk", + "givenName": "Bj\u00f6rk", "middleName": "Gunnar", - "familyName": "Guðmundsdóttir", + "familyName": "Gu\u00f0mundsd\u00f3ttir", "licenseJurisdiction": "oh", - "privilegeJurisdictions": ["ne"], "jurisdictionUploadedLicenseStatus": "active", "jurisdictionUploadedCompactEligibility": "eligible", "dateOfExpiration": "2025-04-04", "dateOfBirth": "1985-06-06", "dateOfUpdate": "2024-07-08T23:59:59+00:00", "birthMonthDay": "06-06" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/event-bridge-message.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/event-bridge-message.json index 01b810ddc..b397720e5 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/event-bridge-message.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/event-bridge-message.json @@ -11,7 +11,6 @@ "eventTime": "2024-07-11T19:57:45Z", "ssnLastFour": "1234", "providerId": "89a6377e-c3a5-40e5-bca5-317ec854c570", - "npi": "0608337260", "licenseNumber": "A0608337260", "givenName": "Björk", "middleName": "Gunnar", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/preprocessor-sqs-message.json b/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/preprocessor-sqs-message.json index d997ca831..08d65fe5f 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/preprocessor-sqs-message.json +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/ingest/preprocessor-sqs-message.json @@ -1,7 +1,6 @@ { "eventTime": "2024-07-11T19:57:45Z", "ssn": "123-12-1234", - "npi": "0608337260", "licenseNumber": "A0608337260", "givenName": "Björk", "middleName": "Gunnar", diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses-invalid-records.csv b/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses-invalid-records.csv index cffe69f08..a146da8f2 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses-invalid-records.csv +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses-invalid-records.csv @@ -1,6 +1,6 @@ -dateOfIssuance,npi,licenseNumber,foo,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,licenseStatus,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal -2024-06-30,0608337260,A0608337260,1,2024-06-30,cosmetologist,Guðmundsdóttir,Birmingham,Gunnar,active,eligible,529-31-5408,123 A St.,Apt 321,2024-06-30,oh,35004,Björk,2024-06-30 -2024-06-30,0608337260,A0608337260,,2024-06-30,esthetician,Scott,Huntsville,Patricia,active,eligible,529-31-5409,321 B St.,,2024-06-30,oh,35005,Elizabeth,2024-06-30 -2024-06-30,0608337260,A0608337260,,2024-06-30,plumber,毛,Hoover,泽,active,eligible,529-31-5410,10101 Binary Ave.,,2024-06-30,oh,35006,覃,2024-06-30 -2024-06-30,0608337260,A0608337260,,2024-06-30,esthetician,Adams,Tuscaloosa,Michael,translucent,eligible,529-31-5411,1AB3 Hex Blvd.,,2024-06-31,oh,35007,John,2024-06-30 -2024-06-30,0608337260,A0608337260,,2024-06-30,cosmetologist,Carreño Quiñones,Montgomery,José,active,eligible,529-31-5412,10 Main St.,,2024-06-30,oh,35008,María,2024-13-32 +dateOfIssuance,licenseNumber,foo,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,licenseStatus,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal +2024-06-30,A0608337260,1,2024-06-30,cosmetologist,Guðmundsdóttir,Birmingham,Gunnar,active,eligible,529-31-5408,123 A St.,Apt 321,2024-06-30,oh,35004,Björk,2024-06-30 +2024-06-30,A0608337260,,2024-06-30,esthetician,Scott,Huntsville,Patricia,active,eligible,529-31-5409,321 B St.,,2024-06-30,oh,35005,Elizabeth,2024-06-30 +2024-06-30,A0608337260,,2024-06-30,plumber,毛,Hoover,泽,active,eligible,529-31-5410,10101 Binary Ave.,,2024-06-30,oh,35006,覃,2024-06-30 +2024-06-30,A0608337260,,2024-06-30,esthetician,Adams,Tuscaloosa,Michael,translucent,eligible,529-31-5411,1AB3 Hex Blvd.,,2024-06-31,oh,35007,John,2024-06-30 +2024-06-30,A0608337260,,2024-06-30,cosmetologist,Carreño Quiñones,Montgomery,José,active,eligible,529-31-5412,10 Main St.,,2024-06-30,oh,35008,María,2024-13-32 diff --git a/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses.csv b/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses.csv index 8d3598a1a..f3ff9f9fd 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses.csv +++ b/backend/cosmetology-app/lambdas/python/common/tests/resources/licenses.csv @@ -1,6 +1,6 @@ -dateOfIssuance,npi,licenseNumber,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,licenseStatus,licenseStatusName,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal -2024-06-30,0608337260,A0608337260,2024-06-30,cosmetologist,Guðmundsdóttir,Birmingham,Gunnar,active,ACTIVE,eligible,529-31-5408,123 A St.,Apt 321,2024-06-30,oh,35004,Björk,2024-06-30 -2024-06-30,0608337260,B0608337260,2024-06-30,esthetician,Scott,Huntsville,Patricia,active,ACTIVE,eligible,529-31-5409,321 B St.,,2024-06-30,oh,35005,Elizabeth,2024-06-30 -2024-06-30,0608337260,C0608337260,2024-06-30,cosmetologist,毛,Hoover,泽,active,ACTIVE,eligible,529-31-5410,10101 Binary Ave.,,2024-06-30,oh,35006,覃,2024-06-30 -2024-06-30,0608337260,D0608337260,2024-06-30,cosmetologist,Adams,Tuscaloosa,Michael,inactive,EXPIRED,ineligible,529-31-5411,1AB3 Hex Blvd.,,2024-06-30,oh,35007,John,2024-06-30 -2024-06-30,0608337260,E0608337260,2024-06-30,cosmetologist,Carreño Quiñones,Montgomery,José,active,ACTIVE_IN_RENEWAL,eligible,529-31-5412,10 Main St.,,2024-06-30,oh,35008,María,2024-06-30 +dateOfIssuance,licenseNumber,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,licenseStatus,licenseStatusName,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal +2024-06-30,A0608337260,2024-06-30,cosmetologist,Guðmundsdóttir,Birmingham,Gunnar,active,ACTIVE,eligible,529-31-5408,123 A St.,Apt 321,2024-06-30,oh,35004,Björk,2024-06-30 +2024-06-30,B0608337260,2024-06-30,esthetician,Scott,Huntsville,Patricia,active,ACTIVE,eligible,529-31-5409,321 B St.,,2024-06-30,oh,35005,Elizabeth,2024-06-30 +2024-06-30,C0608337260,2024-06-30,cosmetologist,毛,Hoover,泽,active,ACTIVE,eligible,529-31-5410,10101 Binary Ave.,,2024-06-30,oh,35006,覃,2024-06-30 +2024-06-30,D0608337260,2024-06-30,cosmetologist,Adams,Tuscaloosa,Michael,inactive,EXPIRED,ineligible,529-31-5411,1AB3 Hex Blvd.,,2024-06-30,oh,35007,John,2024-06-30 +2024-06-30,E0608337260,2024-06-30,cosmetologist,Carreño Quiñones,Montgomery,José,active,ACTIVE_IN_RENEWAL,eligible,529-31-5412,10 Main St.,,2024-06-30,oh,35008,María,2024-06-30 diff --git a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py index 2b9de2dfd..039c0892f 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py @@ -332,6 +332,7 @@ def _make_license_data(self, *, license_status='active', date_of_expiration='210 'homeAddressCity': 'Columbus', 'homeAddressState': 'OH', 'homeAddressPostalCode': '43215', + 'licenseNumber': 'LIC12345' } def test_expired_license_status_corrected_to_inactive(self): diff --git a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py index 51de67950..54f1cef87 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_provider.py @@ -14,8 +14,6 @@ def test_serde(self): with open('tests/resources/dynamo/provider.json') as f: expected_provider_record = json.load(f) - # Convert this to the expected type coming out of the DB - expected_provider_record['privilegeJurisdictions'] = set(expected_provider_record['privilegeJurisdictions']) schema = ProviderRecordSchema() loaded_record = schema.load(expected_provider_record.copy()) diff --git a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_email_service_client.py b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_email_service_client.py index 23c215222..520b1868a 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_email_service_client.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_email_service_client.py @@ -1,13 +1,7 @@ -import json -from unittest.mock import MagicMock - from cc_common.config import logger from tests import TstLambdas -TEST_COMPACT = 'cosm' -TEST_JURISDICTION = 'oh' - class TestEmailServiceClient(TstLambdas): def _generate_test_model(self, mock_lambda_client): @@ -23,31 +17,3 @@ def _generate_test_model(self, mock_lambda_client): return EmailServiceClient( lambda_client=mock_lambda_client, email_notification_service_lambda_name='test-lambda-name', logger=logger ) - - def test_privilege_deactivation_jurisdiction_notification_should_invoke_lambda_client_with_expected_parameters( - self, - ): - mock_lambda_client = MagicMock() - test_model = self._generate_test_model(mock_lambda_client) - - test_model.send_jurisdiction_privilege_deactivation_email( - compact=TEST_COMPACT, - jurisdiction=TEST_JURISDICTION, - privilege_id='123', - provider_first_name='John', - provider_last_name='Doe', - ) - - mock_lambda_client.invoke.assert_called_once_with( - FunctionName='test-lambda-name', - InvocationType='RequestResponse', - Payload=json.dumps( - { - 'compact': TEST_COMPACT, - 'jurisdiction': TEST_JURISDICTION, - 'template': 'privilegeDeactivationJurisdictionNotification', - 'recipientType': 'JURISDICTION_SUMMARY_REPORT', - 'templateVariables': {'privilegeId': '123', 'providerFirstName': 'John', 'providerLastName': 'Doe'}, - } - ), - ) diff --git a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_provider_record_util.py b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_provider_record_util.py index 937a1e161..86ae7633f 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_provider_record_util.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_provider_record_util.py @@ -1,6 +1,3 @@ -from datetime import date, datetime -from unittest.mock import patch - from tests import TstLambdas @@ -158,1065 +155,3 @@ def test_find_best_license_complex_scenario(self): best_license = ProviderRecordUtility.find_best_license(licenses) self.assertEqual(best_license['dateOfIssuance'], '2024-01-01') self.assertEqual(best_license['compactEligibility'], CompactEligibilityStatus.ELIGIBLE) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-03-15T00:00:00+00:00')) - def test_enrich_privilege_history_with_synthetic_issuance(self): - """Test that enrich_license_history_with_synthetic_updates adds an issuance update.""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2025-01-01'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_issuance_update = { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - } - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual([expected_issuance_update], enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-03-15T00:00:00+00:00')) - def test_enrich_privilege_history_with_expiration_event_if_expired(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege adds an expiration if expired""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2024-02-01'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-02-02T04:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-02-02T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-02-02T03:59:59.999999+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'expiration', - 'updatedValues': {}, - }, - ] - - # Check that the history contains updates with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-03-16T00:00:00+04:00')) - def test_enrich_privilege_history_with_expiration_event_if_first_second_of_expiration(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege adds an expiration if first second - of expired""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2024-02-01'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-02-02T04:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-02-02T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-02-02T03:59:59.999999+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'expiration', - 'updatedValues': {}, - }, - ] - - # Check that the history contains updates with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-03-15T01:00:00+04:00')) - def test_enrich_privilege_history_does_not_add_expiration_if_day_of_expiration(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege does not add expiration on day of - expiration.""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2024-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - } - ] - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-03-15T23:59:00+04:00')) - def test_enrich_privilege_history_does_not_add_expiration_if_minute_before_expiration(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege does not add if minute before expiration - cut off""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2024-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - } - ] - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2026-03-15T00:00:00+00:00')) - def test_enrich_privilege_history_adds_expiration_events_in_correct_spots(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege adds expiration and issuance events - correctly""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-07-17T15:27:35+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:27:35+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-05-01T23:59:00+00:00'), - 'jurisdiction': 'ky', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'active', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'encumberedStatus': 'unencumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-KY-26', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'licenseEncumbered'}, - 'updateType': 'encumbrance', - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-07-15T23:59:00+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'inactive', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-05-28T19:50:44+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-05-28T19:50:44+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:04:05+00:00'), - 'encumberedStatus': 'licenseEncumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-NE-25', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'unencumbered'}, - 'updateType': 'lifting_encumbrance', - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'createDate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-08-15T15:24:01+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-07-17T15:27:35+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:27:35+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-05-01T23:59:00+00:00'), - 'jurisdiction': 'ky', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'active', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'encumberedStatus': 'unencumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-KY-26', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'licenseEncumbered'}, - 'updateType': 'encumbrance', - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T03:59:59.999999+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'expiration', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-07-15T23:59:00+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'inactive', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-05-28T19:50:44+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-05-28T19:50:44+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:04:05+00:00'), - 'encumberedStatus': 'licenseEncumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-NE-25', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'unencumbered'}, - 'updateType': 'lifting_encumbrance', - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'createDate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-08-15T15:24:01+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Check that the history contains updates with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2026-03-15T00:00:00+00:00')) - def test_enrich_privilege_history_does_not_inject_expiration_if_renewed_in_last_minute(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege does not injection expiration event if - renewed last minute - """ - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [ - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'createDate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'createDate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-15T23:59:00+04:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Check that the history contains updates with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2027-03-18T00:00:00+04:00')) - def test_enrich_privilege_history_does_inject_expiration_if_renewed_on_second_of_expiration(self): - """Test that get_enriched_history_with_synthetic_updates_from_privilege adds expiration if privilege renewed - one second over expiration cutoff""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # Create a privilege with no history - privilege = { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - } - - history = [ - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'createDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Enrich the privilege history - enriched_history = ProviderRecordUtility.get_enriched_history_with_synthetic_updates_from_privilege( - privilege, history - ) - - # Define the expected issuance update - expected_updates = [ - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'createDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T03:59:59.999999+00:00'), - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'previous': {}, - 'providerId': 'test-provider-id', - 'type': 'privilegeUpdate', - 'updateType': 'expiration', - 'updatedValues': {}, - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'createDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T04:00:00+00:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'previous': { - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-06-15'), - 'dateOfIssuance': datetime.fromisoformat('2025-03-19T21:51:26+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2022-08-19T19:03:56+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2022-03-19T22:02:17+00:00'), - 'licenseJurisdiction': 'ky', - 'privilegeId': 'COS-NE-10', - }, - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updatedValues': { - 'dateOfExpiration': date.fromisoformat('2028-02-12'), - 'dateOfRenewal': datetime.fromisoformat('2025-03-25T19:03:56+00:00'), - 'privilegeId': 'COS-NE-10', - }, - 'updateType': 'renewal', - }, - ] - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_updates, enriched_history) - - def test_construct_simplified_privilege_history_object_returns_deactivation_notes_properly(self): - """Test that construct_simplified_privilege_history_object extracts the deactivation note successfully""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # mock_privilege_data_return - privilege_data = [ - { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - }, - { - 'compact': 'cosm', - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'providerId': 'aa2e057d-6972-4a68-a55d-aad1c3d05278', - 'type': 'privilegeUpdate', - 'updateType': 'deactivation', - 'deactivationDetails': { - 'note': 'test deactivation note', - 'deactivatedByStaffUserId': 'a4182428-d061-701c-82e5-a3d1d547d797', - 'deactivatedByStaffUserName': 'John Doe', - }, - 'previous': { - 'dateOfIssuance': '2023-11-08T23:59:59+00:00', - 'dateOfRenewal': '2023-11-08T23:59:59+00:00', - 'dateOfExpiration': '2024-10-31', - 'dateOfUpdate': '2023-11-08T23:59:59+00:00', - 'administratorSetStatus': 'active', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-NE-1', - }, - 'updatedValues': { - 'administratorSetStatus': 'inactive', - }, - }, - ] - - # Enrich the privilege history - history = ProviderRecordUtility.construct_simplified_privilege_history_object(privilege_data) - - # Define the expected issuance update - expected_history = { - 'compact': 'cosm', - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-AL-12', - 'providerId': 'test-provider-id', - 'events': [ - { - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'type': 'privilegeUpdate', - 'updateType': 'deactivation', - 'note': 'test deactivation note', - }, - ], - } - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_history, history) - - def test_construct_simplified_privilege_history_object_returns_encumbrance_notes_if_requested(self): - """Test that construct_simplified_privilege_history_object extracts the encumbrance notes successfully""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # mock_privilege_data_return - privilege_data = [ - { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'jurisdiction': 'ky', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'active', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'encumberedStatus': 'unencumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-KY-26', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'licenseEncumbered'}, - 'updateType': 'encumbrance', - 'encumbranceDetails': { - 'clinicalPrivilegeActionCategories': ['Non-compliance With Requirements'], - 'licenseJurisdiction': 'oh', - }, - }, - ] - - # Enrich the privilege history - history = ProviderRecordUtility.construct_simplified_privilege_history_object(privilege_data) - - # Define the expected issuance update - expected_history = { - 'compact': 'cosm', - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-AL-12', - 'providerId': 'test-provider-id', - 'events': [ - { - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'type': 'privilegeUpdate', - 'updateType': 'encumbrance', - 'npdbCategories': ['Non-compliance With Requirements'], - }, - ], - } - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_history, history) - - def test_construct_simplified_privilege_history_object_does_not_return_encumbrance_notes_if_not_requested(self): - """Test that construct_simplified_privilege_history_object does not extract the encumbrance notes if - it should not""" - from cc_common.data_model.provider_record_util import ProviderRecordUtility - - # mock_privilege_data_return - privilege_data = [ - { - **self.base_privilege, - 'providerId': 'test-provider-id', - 'dateOfIssuance': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfExpiration': date.fromisoformat('2028-03-15'), - 'dateOfRenewal': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - }, - { - 'compact': 'cosm', - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'jurisdiction': 'ky', - 'licenseType': 'cosmetologist', - 'previous': { - 'administratorSetStatus': 'active', - 'attestations': [], - 'dateOfExpiration': date.fromisoformat('2025-12-11'), - 'dateOfIssuance': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfRenewal': datetime.fromisoformat('2025-06-23T07:46:19+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-07-17T15:24:01+00:00'), - 'encumberedStatus': 'unencumbered', - 'licenseJurisdiction': 'oh', - 'privilegeId': 'COS-KY-26', - }, - 'providerId': '1b6bcfa2-28ad-4f9a-acf4-bba771f6cc11', - 'type': 'privilegeUpdate', - 'updatedValues': {'encumberedStatus': 'licenseEncumbered'}, - 'updateType': 'encumbrance', - 'encumbranceDetails': { - 'note': 'Non-compliance With Requirements', - 'licenseJurisdiction': 'oh', - }, - }, - ] - - # Enrich the privilege history - history = ProviderRecordUtility.construct_simplified_privilege_history_object(privilege_data, False) - - # Define the expected issuance update - expected_history = { - 'compact': 'cosm', - 'jurisdiction': 'al', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-AL-12', - 'providerId': 'test-provider-id', - 'events': [ - { - 'createDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'dateOfUpdate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'effectiveDate': datetime.fromisoformat('2024-01-01T00:00:00+00:00'), - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'dateOfUpdate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'effectiveDate': datetime.fromisoformat('2025-06-16T00:00:00+04:00'), - 'type': 'privilegeUpdate', - 'updateType': 'encumbrance', - }, - ], - } - - # Check that the history contains exactly one update with the expected values - self.maxDiff = None - self.assertEqual(expected_history, history) - - -class TestProviderRecordUtilityActiveSinceCalculation(TstLambdas): - def setUp(self): - from cc_common.data_model.provider_record_util import ProviderRecordUtility - from common_test.test_data_generator import TestDataGenerator - - self.test_data_generator = TestDataGenerator - self.test_model = ProviderRecordUtility - - def test_calculation_returns_none_if_privilege_not_active(self): - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2025-04-04')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[] - ) - - self.assertEqual(None, active_since) - - def test_calculation_returns_issuance_date_if_no_deactivation_events(self): - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[] - ) - - self.assertEqual(test_privilege.dateOfIssuance, active_since) - - def test_calculation_returns_renewal_date_if_privilege_expired_and_was_then_renewed(self): - from cc_common.data_model.schema.common import UpdateCategory - - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record expired before renewal. - # the privilege is then renewed some time later. - test_expiration_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.EXPIRATION, - 'effectiveDate': datetime.fromisoformat('2045-04-04T12:59:59+00:00'), - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[test_expiration_event, test_renewal_update] - ) - - self.assertEqual(test_renewal_update.updatedValues.get('dateOfRenewal'), active_since) - - def test_calculation_returns_renewal_date_if_privilege_deactivated_and_was_then_renewed(self): - from cc_common.data_model.schema.common import UpdateCategory - - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record was deactivated by a compact admin. - # the privilege is then renewed some time later. - test_deactivation_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.DEACTIVATION, - 'effectiveDate': datetime.fromisoformat('2045-04-04T12:59:59+00:00'), - 'deactivationDetails': { - 'deactivatedByStaffUserId': '89a6377e-c3a5-40e5-bca5-317ec854c123', - 'deactivatedByStaffUserName': 'some-user-name', - }, - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[test_deactivation_event, test_renewal_update] - ) - - self.assertEqual(test_renewal_update.updatedValues.get('dateOfRenewal'), active_since) - - def test_calculation_returns_renewal_date_if_privilege_was_encumbered_and_then_renewed(self): - from cc_common.data_model.schema.common import UpdateCategory - - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record was encumbered. - # the privilege is then renewed some time later after the having the encumbrance lifted. - test_encumbrance_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.ENCUMBRANCE, - 'effectiveDate': datetime.fromisoformat('2045-04-04T12:59:59+00:00'), - } - ) - test_encumbrance_lifting_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.LIFTING_ENCUMBRANCE, - 'effectiveDate': datetime.fromisoformat('2047-04-04T12:59:59+00:00'), - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, - privilege_updates=[test_encumbrance_event, test_encumbrance_lifting_event, test_renewal_update], - ) - - self.assertEqual(test_renewal_update.updatedValues.get('dateOfRenewal'), active_since) - - def test_calculation_returns_privilege_renewal_date_if_license_was_deactivated_then_privilege_renewed(self): - from cc_common.data_model.schema.common import UpdateCategory - - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record was deactivated due to a license deactivation. - # the privilege is then renewed some time later after the license record was made active again. - test_deactivation_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.LICENSE_DEACTIVATION, - 'effectiveDate': datetime.fromisoformat('2045-04-04T12:59:59+00:00'), - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[test_deactivation_event, test_renewal_update] - ) - - self.assertEqual(test_renewal_update.updatedValues.get('dateOfRenewal'), active_since) - - def test_calculation_returns_issued_date_if_privilege_renewed_before_expiration(self): - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record was renewed before it expired. - # The default privilege update record generated by the test data generator is a renewal event - test_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00')} - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, privilege_updates=[test_renewal_update] - ) - - self.assertEqual(test_privilege.dateOfIssuance, active_since) - - def test_calculation_returns_oldest_renewal_date_if_privilege_expired_and_then_was_renewed_multiple_times(self): - from cc_common.data_model.schema.common import UpdateCategory - - test_privilege = self.test_data_generator.generate_default_privilege( - value_overrides={'dateOfExpiration': date.fromisoformat('2100-04-04')} - ) - # this simulates the scenario where a privilege record expired before renewal. - # the privilege is then renewed some time later, and then renewed again before expiration - # the oldest renewal date should be returned in this case - test_expiration_event = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'updateType': UpdateCategory.EXPIRATION, - 'effectiveDate': datetime.fromisoformat('2045-04-04T12:59:59+00:00'), - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_first_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'effectiveDate': datetime.fromisoformat('2098-04-04T12:59:59+00:00'), - 'updatedValues': {'dateOfRenewal': datetime.fromisoformat('2098-04-04T12:59:59+00:00')}, - } - ) - # The default privilege update record generated by the test data generator is a renewal event - test_second_renewal_update = self.test_data_generator.generate_default_privilege_update( - value_overrides={ - 'effectiveDate': datetime.fromisoformat('2099-04-04T12:59:59+00:00'), - 'updatedValues': {'dateOfRenewal': datetime.fromisoformat('2099-04-04T12:59:59+00:00')}, - } - ) - active_since = self.test_model.calculate_privilege_active_since_date( - privilege_record=test_privilege, - privilege_updates=[test_expiration_event, test_first_renewal_update, test_second_renewal_update], - ) - - self.assertEqual(datetime.fromisoformat('2098-04-04T12:59:59+00:00'), active_since) diff --git a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_sanitize_provider_data.py b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_sanitize_provider_data.py index 91d853f6a..6f46f5fbd 100644 --- a/backend/cosmetology-app/lambdas/python/common/tests/unit/test_sanitize_provider_data.py +++ b/backend/cosmetology-app/lambdas/python/common/tests/unit/test_sanitize_provider_data.py @@ -16,9 +16,6 @@ def when_expecting_full_provider_record_returned(self, scopes: set[str]): resp = sanitize_provider_data_based_on_caller_scopes(compact='cosm', provider=test_provider, scopes=scopes) - # cast to set to match schema - expected_provider['privilegeJurisdictions'] = set(expected_provider['privilegeJurisdictions']) - self.assertEqual(expected_provider, resp) def test_full_provider_record_returned_if_caller_has_compact_read_private_permissions(self): @@ -63,8 +60,6 @@ def when_testing_general_provider_info_returned(self, scopes: set[str]): # also remove the ssn from the license record del expected_provider['licenses'][0]['ssnLastFour'] del expected_provider['licenses'][0]['dateOfBirth'] - # cast to set to match schema - expected_provider['privilegeJurisdictions'] = set(expected_provider['privilegeJurisdictions']) self.assertEqual(expected_provider, resp) diff --git a/backend/cosmetology-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py b/backend/cosmetology-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py index f8dc9a440..52d422746 100644 --- a/backend/cosmetology-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py +++ b/backend/cosmetology-app/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py @@ -8,10 +8,8 @@ from botocore.exceptions import ClientError from cc_common.config import config, logger from cc_common.data_model.provider_record_util import ProviderRecordUtility, ProviderUserRecords -from cc_common.data_model.schema.common import LICENSE_UPLOAD_UPDATE_CATEGORIES, UpdateCategory +from cc_common.data_model.schema.common import LICENSE_UPLOAD_UPDATE_CATEGORIES from cc_common.data_model.schema.license import LicenseData -from cc_common.data_model.schema.license.record import LicenseRecordSchema -from cc_common.data_model.schema.privilege import PrivilegeData from cc_common.data_model.schema.provider import ProviderData from cc_common.data_model.update_tier_enum import UpdateTierEnum from cc_common.event_batch_writer import EventBatchWriter @@ -23,9 +21,6 @@ # it can be modified if needed MAX_ROLLBACK_WINDOW_SECONDS = 7 * 24 * 60 * 60 -# Privilege update category for license deactivations -PRIVILEGE_LICENSE_DEACTIVATION_CATEGORY = UpdateCategory.LICENSE_DEACTIVATION - class ProviderRollbackFailedException(Exception): """Custom exception that is thrown when a provider fails to rollback""" @@ -40,7 +35,7 @@ def __init__(self, message: str): class IneligibleUpdate: """Represents an update that makes a provider ineligible for rollback.""" - record_type: str # 'licenseUpdate' or 'privilegeUpdate' + record_type: str # 'licenseUpdate' type_of_update: str update_time: str reason: str @@ -73,22 +68,12 @@ class RevertedLicense: action: str -@dataclass -class RevertedPrivilege: - """Details of a reverted privilege for event publishing.""" - - jurisdiction: str - license_type: str - action: str - - @dataclass class ProviderRevertedSummary: """Summary for a provider that was successfully reverted.""" provider_id: str licenses_reverted: list[RevertedLicense] = field(default_factory=list) - privileges_reverted: list[RevertedPrivilege] = field(default_factory=list) updates_deleted: list[str] = field(default_factory=list) # List of SKs for deleted update records @@ -140,14 +125,6 @@ def to_dict(self) -> dict: } for license_record in summary.licenses_reverted ], - 'privilegesReverted': [ - { - 'jurisdiction': privilege.jurisdiction, - 'licenseType': privilege.license_type, - 'action': privilege.action, - } - for privilege in summary.privileges_reverted - ], 'updatesDeleted': summary.updates_deleted, } for summary in self.reverted_provider_summaries @@ -194,14 +171,6 @@ def from_dict(cls, data: dict) -> 'RollbackResults': ) for reverted_license in summary.get('licensesReverted', []) ], - privileges_reverted=[ - RevertedPrivilege( - jurisdiction=reverted_privilege['jurisdiction'], - license_type=reverted_privilege['licenseType'], - action=reverted_privilege['action'], - ) - for reverted_privilege in summary.get('privilegesReverted', []) - ], updates_deleted=summary.get('updatesDeleted', []), ) for summary in data.get('revertedProviderSummaries', []) @@ -626,13 +595,12 @@ def _build_and_execute_revert_transactions( Returns either a summary of what was reverted or details about why the provider was skipped. """ - # Split transaction lists into first tier/second tier lists (license/privilege/provider first tier, updates second) + # Split transaction lists into first tier/second tier lists (license/provider first tier, updates second) # then merge the two lists into a single list of transaction items - primary_record_transaction_items = [] # License, privilege, and provider records - update_record_transactions_items = [] # Update records (license updates, privilege updates) + primary_record_transaction_items = [] # License and provider records + update_record_transactions_items = [] # Update records (license updates) table_name = config.provider_table_name reverted_licenses = [] - reverted_privileges = [] updates_deleted_sks = [] # List of SKs for deleted update records ineligible_updates: list[IneligibleUpdate] = [] @@ -694,102 +662,7 @@ def add_delete(pk: str, sk: str, update_record: bool): # Step 2: Process each license record for the jurisdiction license_records = provider_records.get_license_records(filter_condition=lambda x: x.jurisdiction == jurisdiction) - reverted_licenses_dict = [] - for license_record in license_records: - privileges_associated_with_license = provider_records.get_privileges_associated_with_license( - license_jurisdiction=license_record.jurisdiction, - license_type_abbreviation=license_record.licenseTypeAbbreviation, - ) - - # Check if any privileges were issued for this license since the upload start date - for privilege in privileges_associated_with_license: - if privilege.dateOfIssuance >= upload_window_start_datetime: - ineligible_updates.append( - IneligibleUpdate( - record_type='privilegeUpdate', - type_of_update='Issuance', - update_time=privilege.dateOfIssuance.isoformat(), - license_type=license_record.licenseType, - reason=f"Privilege in jurisdiction '{privilege.jurisdiction}' issued after license upload. " - 'Manual review required.', - ) - ) - # Check updates associated with this privilege that are after the start_datetime - privilege_updates_after_start_time = provider_records.get_update_records_for_privilege( - jurisdiction=privilege.jurisdiction, - license_type=privilege.licenseType, - filter_condition=lambda x: x.createDate >= upload_window_start_datetime, - ) - - # Check privilege updates for eligibility - for privilege_update in privilege_updates_after_start_time: - if privilege_update.updateType != PRIVILEGE_LICENSE_DEACTIVATION_CATEGORY: - # Non-license-deactivation privilege update makes provider ineligible for rollback - ineligible_updates.append( - IneligibleUpdate( - record_type='privilegeUpdate', - type_of_update=privilege_update.updateType, - update_time=privilege_update.createDate.isoformat(), - license_type=privilege_update.licenseType, - # include privilege jurisdiction in reason - reason=f"Privilege in jurisdiction '{privilege_update.jurisdiction}' was updated " - f'with a change unrelated to license upload. Manual review required.', - ) - ) - elif privilege_update.createDate > upload_window_end_datetime: - # privilege update after upload window makes provider ineligible - ineligible_updates.append( - IneligibleUpdate( - record_type='privilegeUpdate', - type_of_update=privilege_update.updateType, - update_time=privilege_update.createDate.isoformat(), - license_type=privilege_update.licenseType, - # include privilege jurisdiction in reason - reason=f"Privilege in jurisdiction '{privilege_update.jurisdiction}' was deactivated " - 'after rollback end time. Manual review required.', - ) - ) - else: - # License deactivation within window cause privilege deactivation - revert the deactivation - serialized_privilege_update = privilege_update.serialize_to_database_record() - add_delete(serialized_privilege_update['pk'], serialized_privilege_update['sk'], update_record=True) - updates_deleted_sks.append(serialized_privilege_update['sk']) - logger.info('Will delete privilege deactivation update record if provider is eligible for rollback') - - # Reactivate the privilege - privilege_record = provider_records.get_specific_privilege_record( - jurisdiction=privilege_update.jurisdiction, - license_abbreviation=license_record.licenseTypeAbbreviation, - ) - if privilege_record: - logger.info( - 'privilege record found associated with deactivation, reactivating privilege', - provider_id=provider_id, - privilege_jurisdiction=privilege_record.jurisdiction, - license_type=privilege_record.licenseType, - ) - # Remove the licenseDeactivatedStatus field to reactivate using UPDATE operation - serialized_privilege = privilege_record.serialize_to_database_record() - primary_record_transaction_items.append( - { - 'Update': { - 'TableName': table_name, - 'Key': {'pk': serialized_privilege['pk'], 'sk': serialized_privilege['sk']}, - 'UpdateExpression': 'REMOVE licenseDeactivatedStatus', - } - } - ) - logger.info('Will reactivate privilege record if provider is eligible for rollback') - - reverted_privileges.append( - RevertedPrivilege( - jurisdiction=privilege_record.jurisdiction, - license_type=privilege_record.licenseType, - action='REACTIVATED', - ) - ) - # Get license updates for this license after start_datetime license_updates_after_start = provider_records.get_update_records_for_license( jurisdiction=license_record.jurisdiction, @@ -863,10 +736,6 @@ def add_delete(pk: str, sk: str, update_record: bool): add_put(serialized_reverted_license, update_record=False) logger.info('Reverting license record to pre-upload state') - # Track for provider record regeneration - license_schema = LicenseRecordSchema() - reverted_licenses_dict.append(license_schema.load(serialized_reverted_license)) - reverted_licenses.append( RevertedLicense( jurisdiction=license_record.jurisdiction, @@ -927,12 +796,10 @@ def add_delete(pk: str, sk: str, update_record: bool): primary_record_transaction_items.clear() try: - privilege_records: list[PrivilegeData] = provider_records_after_rollback.get_privilege_records() best_license = provider_records_after_rollback.find_best_license_in_current_known_licenses() updated_provider_record = ProviderRecordUtility.populate_provider_record( current_provider_record=top_level_provider_record, license_record=best_license.to_dict(), - privilege_records=[privilege.to_dict() for privilege in privilege_records], ) add_put(updated_provider_record.serialize_to_database_record(), update_record=False) except CCNotFoundException: @@ -959,13 +826,11 @@ def add_delete(pk: str, sk: str, update_record: bool): 'Completed rollback for provider', provider_id=provider_id, licenses_reverted=reverted_licenses, - privileges_reverted=reverted_privileges, updates_deleted=updates_deleted_sks, ) return ProviderRevertedSummary( provider_id=provider_id, licenses_reverted=reverted_licenses, - privileges_reverted=reverted_privileges, updates_deleted=updates_deleted_sks, ) @@ -979,7 +844,7 @@ def _publish_revert_events( execution_name: str, ): """ - Publish revert events for all reverted licenses and privileges. + Publish revert events for all reverted licenses. :param revert_summary: Summary of reverted provider records :param compact: The compact name @@ -1018,35 +883,6 @@ def _publish_revert_events( error=str(e), ) - # Publish privilege revert events - for reverted_privilege in revert_summary.privileges_reverted: - try: - config.event_bus_client.publish_privilege_revert_event( - source='org.compactconnect.disaster-recovery', - compact=compact, - provider_id=revert_summary.provider_id, - jurisdiction=reverted_privilege.jurisdiction, - license_type=reverted_privilege.license_type, - rollback_reason=rollback_reason, - start_time=start_datetime, - end_time=end_datetime, - execution_name=execution_name, - event_batch_writer=event_writer, - ) - except Exception as e: # noqa BLE001 - # this event publishing is not business critical, so we log the error and move on - logger.error( - 'Unable to publish privilege revert event', - compact=compact, - provider_id=revert_summary.provider_id, - jurisdiction=reverted_privilege.jurisdiction, - license_type=reverted_privilege.license_type, - rollback_reason=rollback_reason, - start_time=start_datetime, - end_time=end_datetime, - error=str(e), - ) - def _load_results_from_s3(key: str, execution_name: str) -> RollbackResults: """Load existing results from S3.""" diff --git a/backend/cosmetology-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py b/backend/cosmetology-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py index d586bb860..5d69d0493 100644 --- a/backend/cosmetology-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py +++ b/backend/cosmetology-app/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py @@ -295,98 +295,6 @@ def _when_license_was_updated_twice(self, provider_id: str = None): return existing_update, original_license, first_update, second_update, final_license - def _when_provider_had_privilege_deactivated_from_upload(self, upload_datetime: datetime = None): - """ - Set up a scenario where a provider's privilege was deactivated due to license deactivation during upload. - Returns the privilege and its update record. - """ - from cc_common.data_model.schema.common import LicenseDeactivatedStatusEnum - - if upload_datetime is None: - upload_datetime = self.default_upload_datetime - - # provider has privilege in Nebraska that was deactivated by upload - privilege = self.test_data_generator.put_default_privilege_record_in_provider_table( - { - 'providerId': self.provider_id, - 'compact': self.compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': self.license_jurisdiction, - 'dateOfUpdate': self.default_start_datetime - timedelta(days=30), - 'licenseDeactivatedStatus': LicenseDeactivatedStatusEnum.LICENSE_DEACTIVATED, - 'dateOfExpiration': datetime.fromisoformat(MOCK_DATETIME_STRING), - } - ) - - # Create deactivation update record - privilege_update = self.test_data_generator.put_default_privilege_update_record_in_provider_table( - { - 'providerId': self.provider_id, - 'compact': self.compact, - 'jurisdiction': 'ne', - 'licenseType': privilege.licenseType, - 'updateType': self.update_categories.LICENSE_DEACTIVATION, - 'createDate': upload_datetime, - 'effectiveDate': upload_datetime, - 'previous': {**privilege.to_dict()}, - 'updatedValues': { - 'licenseDeactivatedStatus': LicenseDeactivatedStatusEnum.LICENSE_DEACTIVATED, - }, - } - ) - - return privilege, privilege_update - - def _when_provider_had_privilege_issued_during_upload(self): - """ - Set up a scenario where a provider had a non-upload-related privilege update AFTER the upload window. - This makes them ineligible for automatic rollback. - Returns the privilege and its update record. - """ - - return self.test_data_generator.put_default_privilege_record_in_provider_table( - { - 'providerId': self.provider_id, - 'compact': self.compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': self.license_jurisdiction, - 'dateOfIssuance': self.default_upload_datetime, - } - ) - - def _when_provider_had_privilege_update_after_upload(self, after_upload_datetime: datetime = None): - """ - Set up a scenario where a provider had a non-upload-related privilege update AFTER the upload window. - This makes them ineligible for automatic rollback. - Returns the privilege and its update record. - """ - if after_upload_datetime is None: - after_upload_datetime = self.default_end_datetime + timedelta(hours=1) - - privilege = self.test_data_generator.put_default_privilege_record_in_provider_table( - { - 'providerId': self.provider_id, - 'compact': self.compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': self.license_jurisdiction, - } - ) - - # Create a non-upload-related update (e.g., renewal) after the window - privilege_update = self.test_data_generator.put_default_privilege_update_record_in_provider_table( - { - 'providerId': self.provider_id, - 'compact': self.compact, - 'jurisdiction': 'ne', - 'licenseType': privilege.licenseType, - 'updateType': self.update_categories.RENEWAL, # Not LICENSE_DEACTIVATION - 'createDate': after_upload_datetime, - 'effectiveDate': after_upload_datetime, - } - ) - - return privilege, privilege_update - def _when_provider_had_license_update_after_upload(self, after_upload_datetime: datetime = None): """ Set up a scenario where a provider had a non-upload-related license update AFTER the upload window. @@ -567,48 +475,6 @@ def test_provider_license_record_reverted_to_earliest_update_previous_values_whe existing_update.serialize_to_database_record(), license_updates[0].serialize_to_database_record() ) - def test_provider_privilege_record_reactivated_when_upload_reverted(self): - """Test that privilege is reactivated when license deactivation is reverted.""" - from handlers.rollback_license_upload import rollback_license_upload - - # Setup: Privilege was deactivated during upload due to license deactivation - # license was uploaded before rollback window - self._when_provider_had_license_updated_from_upload( - license_upload_datetime=self.default_start_datetime - timedelta(hours=1) - ) - self._when_provider_had_privilege_deactivated_from_upload() - - # Execute: Perform rollback - event = self._generate_test_event() - - result = rollback_license_upload(event, Mock()) - - # Assert: Rollback completed successfully - self.assertEqual(result['rollbackStatus'], 'COMPLETE') - self.assertEqual(result['providersReverted'], 1) - - # Verify: Privilege has been reactivated (status should be 'active') - provider_records = self.config.data_client.get_provider_user_records( - compact=self.compact, - provider_id=self.provider_id, - include_update_tier=UpdateTierEnum.TIER_THREE, - ) - privileges = provider_records.get_privilege_records() - self.assertEqual(len(privileges), 1) - privilege_record = privileges[0] - self.assertEqual(privilege_record.status, 'active', 'Privilege should be reactivated') - self.assertIsNone(privilege_record.licenseDeactivatedStatus) - - # Verify: Privilege update record has been deleted - privilege_updates = provider_records.get_all_privilege_update_records() - self.assertEqual(len(privilege_updates), 0, 'Privilege update records should be deleted') - - # make sure license record was reactivated as well - license_record = provider_records.get_specific_license_record( - jurisdiction=self.license_jurisdiction, license_abbreviation=privilege_record.licenseTypeAbbreviation - ) - self.assertEqual('active', license_record.licenseStatus) - def test_provider_license_updates_and_license_record_within_time_period_removed_when_upload_reverted(self): """Test that license update records and license record within the time window are deleted.""" from handlers.rollback_license_upload import rollback_license_upload @@ -675,35 +541,6 @@ def test_provider_skipped_if_license_updates_detected_after_end_of_time_window_w license_updates = provider_records.get_all_license_update_records() self.assertEqual(2, len(license_updates), 'License updates should still exist') - def test_provider_skipped_if_privilege_updates_detected_after_time_period_when_upload_reverted(self): - """Test that provider is skipped if non-upload-related privilege updates exist after time window.""" - from handlers.rollback_license_upload import rollback_license_upload - - # Setup: Provider had privilege update after upload window - self._when_provider_had_license_updated_from_upload() - self._when_provider_had_privilege_update_after_upload() - - # Execute: Perform rollback - event = self._generate_test_event() - - result = rollback_license_upload(event, Mock()) - - # Assert: Rollback completed but provider was skipped - self.assertEqual(result['rollbackStatus'], 'COMPLETE') - self.assertEqual(1, result['providersSkipped']) - self.assertEqual(0, result['providersReverted']) - - # Verify: Privilege record and update still exist (not rolled back) - provider_records = self.config.data_client.get_provider_user_records( - compact=self.compact, - provider_id=self.provider_id, - include_update_tier=UpdateTierEnum.TIER_THREE, - ) - privileges = provider_records.get_privilege_records() - self.assertEqual(1, len(privileges), 'Privilege should still exist') - privilege_updates = provider_records.get_all_privilege_update_records() - self.assertEqual(1, len(privilege_updates), 'Privilege update should still exist') - # Validation tests def test_rollback_validates_datetime_format(self): from handlers.rollback_license_upload import rollback_license_upload @@ -779,11 +616,10 @@ def test_expected_s3_object_stored_when_provider_license_record_reset_to_prior_v 'licenseType': original_license.licenseType, } ], - 'privilegesReverted': [], 'providerId': self.provider_id, # NOTE: if the test update data is modified, the sha here will need to be updated 'updatesDeleted': [ - 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/d92450a96739428f1a77c051dce9d4a6' + 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/ecd7b0d5fbe7c32dff89c9864ebb8daf' ], } ], @@ -812,7 +648,6 @@ def test_expected_s3_object_stored_when_provider_license_record_deleted_from_rol 'licenseType': new_license.licenseType, } ], - 'privilegesReverted': [], 'providerId': self.provider_id, 'updatesDeleted': [], } @@ -822,50 +657,6 @@ def test_expected_s3_object_stored_when_provider_license_record_deleted_from_rol results_data, ) - def test_expected_s3_object_stored_when_provider_privilege_record_reactivated_from_rollback(self): - # Setup: Privilege was deactivated during upload due to license deactivation - # license was uploaded before rollback window - self._when_provider_had_license_updated_from_upload( - license_upload_datetime=self.default_start_datetime - timedelta(hours=1) - ) - privilege, privilege_update = self._when_provider_had_privilege_deactivated_from_upload() - - results_data = self._perform_rollback_and_get_s3_object() - - # Verify the structure of the results - self.assertEqual( - { - 'executionName': MOCK_EXECUTION_NAME, - 'failedProviderDetails': [], - 'revertedProviderSummaries': [ - { - 'licensesReverted': [ - { - 'action': 'REVERT', - 'jurisdiction': self.license_jurisdiction, - 'licenseType': privilege.licenseType, - } - ], - 'privilegesReverted': [ - { - 'action': 'REACTIVATED', - 'jurisdiction': privilege.jurisdiction, - 'licenseType': privilege.licenseType, - } - ], - 'providerId': self.provider_id, - # NOTE: if the test update data is modified, the shas here will need to be updated - 'updatesDeleted': [ - 'cosm#UPDATE#1#privilege/ne/cos/2025-10-23T07:15:00+00:00/9cdcf1f7841f3fe3461a0a0bff7e545d', - 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/d92450a96739428f1a77c051dce9d4a6', - ], - } - ], - 'skippedProviderDetails': [], - }, - results_data, - ) - def test_expected_s3_object_stored_when_provider_skipped_due_to_extra_license_updates(self): # Setup: Provider had valid license before upload, and update occurred during upload window original_license, license_update, updated_license = self._when_provider_had_license_updated_from_upload( @@ -908,83 +699,6 @@ def test_expected_s3_object_stored_when_provider_skipped_due_to_extra_license_up results_data, ) - def test_expected_s3_object_stored_when_provider_skipped_due_to_privilege_issuance(self): - # Setup: Provider had privilege update after upload window - self._when_provider_had_license_updated_from_upload() - privilege = self._when_provider_had_privilege_issued_during_upload() - - results_data = self._perform_rollback_and_get_s3_object() - - # Verify the structure of the results - expected_reason_message = ( - f"Privilege in jurisdiction '{privilege.jurisdiction}' issued after license upload. Manual review required." - ) - self.assertEqual( - { - 'executionName': MOCK_EXECUTION_NAME, - 'failedProviderDetails': [], - 'revertedProviderSummaries': [], - 'skippedProviderDetails': [ - { - 'ineligibleUpdates': [ - { - 'updateTime': privilege.dateOfIssuance.isoformat(), - 'licenseType': privilege.licenseType, - 'reason': expected_reason_message, - 'recordType': 'privilegeUpdate', - 'typeOfUpdate': 'Issuance', - } - ], - 'providerId': MOCK_PROVIDER_ID, - 'reason': 'Provider has updates that are either ' - 'unrelated to license upload or ' - 'occurred after rollback end time. ' - 'Manual review required.', - } - ], - }, - results_data, - ) - - def test_expected_s3_object_stored_when_provider_skipped_due_to_extra_privilege_updates(self): - # Setup: Provider had privilege update after upload window - self._when_provider_had_license_updated_from_upload() - privilege, privilege_update = self._when_provider_had_privilege_update_after_upload() - - results_data = self._perform_rollback_and_get_s3_object() - - # Verify the structure of the results - expected_reason_message = ( - "Privilege in jurisdiction 'ne' was updated with a change unrelated to license upload. " - 'Manual review required.' - ) - self.assertEqual( - { - 'executionName': MOCK_EXECUTION_NAME, - 'failedProviderDetails': [], - 'revertedProviderSummaries': [], - 'skippedProviderDetails': [ - { - 'ineligibleUpdates': [ - { - 'updateTime': privilege_update.createDate.isoformat(), - 'licenseType': privilege.licenseType, - 'reason': expected_reason_message, - 'recordType': 'privilegeUpdate', - 'typeOfUpdate': privilege_update.updateType, - } - ], - 'providerId': MOCK_PROVIDER_ID, - 'reason': 'Provider has updates that are either ' - 'unrelated to license upload or ' - 'occurred after rollback end time. ' - 'Manual review required.', - } - ], - }, - results_data, - ) - def test_expected_s3_object_stored_when_provider_schema_validation_fails_during_rollback(self): """Test that failed provider details are correctly stored in S3 results when a validation exception occurs.""" # Setup: License was updated during upload, but one update record has invalid field @@ -1022,12 +736,10 @@ def test_rollback_handles_loading_existing_s3_results_and_appends_new_data(self) existing_reverted_provider_id = str(uuid4()) existing_failed_provider_id = str(uuid4()) - # Setup: Create existing provider with license that will be reverted - # This provider will have a privilege that gets reactivated + # Setup: Create provider with license that will be reverted self._when_provider_had_license_updated_from_upload( license_upload_datetime=self.default_start_datetime - timedelta(hours=1) ) - self._when_provider_had_privilege_deactivated_from_upload() # Create initial S3 results with data in all fields s3_key = f'licenseUploadRollbacks/{MOCK_EXECUTION_NAME}/results.json' @@ -1066,7 +778,6 @@ def test_rollback_handles_loading_existing_s3_results_and_appends_new_data(self) 'action': 'REVERT', } ], - 'privilegesReverted': [], 'updatesDeleted': ['existing-update-sha-1'], } ], @@ -1117,7 +828,6 @@ def test_rollback_handles_loading_existing_s3_results_and_appends_new_data(self) 'action': 'REVERT', } ], - 'privilegesReverted': [], 'updatesDeleted': ['existing-update-sha-1'], }, { @@ -1129,13 +839,6 @@ def test_rollback_handles_loading_existing_s3_results_and_appends_new_data(self) 'licenseType': ANY, } ], - 'privilegesReverted': [ - { - 'action': 'REACTIVATED', - 'jurisdiction': 'ne', - 'licenseType': ANY, - } - ], 'updatesDeleted': ANY, }, ], @@ -1219,10 +922,9 @@ def test_rollback_handles_pagination_when_provider_id_present_in_event_input(sel 'licenseType': 'cosmetologist', } ], - 'privilegesReverted': [], 'providerId': mock_first_provider_id, 'updatesDeleted': [ - 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/d92450a96739428f1a77c051dce9d4a6' + 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/ecd7b0d5fbe7c32dff89c9864ebb8daf' ], }, { @@ -1233,10 +935,9 @@ def test_rollback_handles_pagination_when_provider_id_present_in_event_input(sel 'licenseType': 'cosmetologist', } ], - 'privilegesReverted': [], 'providerId': mock_second_provider_id, 'updatesDeleted': [ - 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/d92450a96739428f1a77c051dce9d4a6' + 'cosm#UPDATE#3#license/oh/cos/2025-10-23T07:15:00+00:00/ecd7b0d5fbe7c32dff89c9864ebb8daf' ], }, ], @@ -1246,16 +947,15 @@ def test_rollback_handles_pagination_when_provider_id_present_in_event_input(sel ) @patch('handlers.rollback_license_upload.config.event_bus_client') - def test_event_bus_client_called_with_expected_arguments_for_revert_events(self, mock_event_bus_client): - """Test that event bus client methods are called with expected arguments when publishing revert events.""" + def test_event_bus_client_called_with_expected_arguments_for_license_revert_events(self, mock_event_bus_client): + """Test that only license revert event is published (privilege reactivation not supported).""" from handlers.rollback_license_upload import rollback_license_upload - # Setup: License was updated during upload and privilege was deactivated - # This scenario will trigger both license and privilege revert events + # Setup: License was updated during upload + # This scenario will trigger license revert event original_license, license_update, updated_license = self._when_provider_had_license_updated_from_upload( license_upload_datetime=self.default_start_datetime - timedelta(hours=1) ) - privilege, privilege_update = self._when_provider_had_privilege_deactivated_from_upload() # Execute: Perform rollback event = self._generate_test_event() @@ -1280,34 +980,14 @@ def test_event_bus_client_called_with_expected_arguments_for_revert_events(self, } mock_event_bus_client.publish_license_revert_event.assert_called_once_with(**expected_license_kwargs) - # Verify: publish_privilege_revert_event was called with expected arguments - expected_privilege_kwargs = { - 'source': 'org.compactconnect.disaster-recovery', - 'compact': self.compact, - 'provider_id': self.provider_id, - 'jurisdiction': privilege.jurisdiction, - 'license_type': privilege.licenseType, - 'rollback_reason': 'Test rollback', - 'start_time': self.default_start_datetime, - 'end_time': self.default_end_datetime, - 'execution_name': MOCK_EXECUTION_NAME, - 'event_batch_writer': ANY, - } - mock_event_bus_client.publish_privilege_revert_event.assert_called_once_with(**expected_privilege_kwargs) - def test_transaction_failure_is_logged_and_provider_marked_as_failed(self): """Test that transaction failures are properly logged and the provider is marked as failed.""" from botocore.exceptions import ClientError - # Setup: Create a scenario with privilege deactivation which will have PUT, DELETE, and UPDATE operations - # - License update (DELETE of update record) - # - Privilege update (DELETE of update record) - # - Privilege reactivation (UPDATE to remove licenseDeactivatedStatus) - # - Provider record update (PUT) + # Setup: License updated during upload (revert will perform DELETE of update record and PUT of reverted license) self._when_provider_had_license_updated_from_upload( license_upload_datetime=self.default_start_datetime - timedelta(hours=1) ) - self._when_provider_had_privilege_deactivated_from_upload() # Mock the transaction to fail with a ClientError mock_error = ClientError( diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/ingest.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/ingest.py index 03006d2db..dbd30e470 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/ingest.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/ingest.py @@ -134,12 +134,6 @@ def ingest_license_message(message: dict): licenses_organized.setdefault(record['jurisdiction'], {}) licenses_organized[record['jurisdiction']][record['licenseType']] = record - # Get all privilege jurisdictions, directly from privilege records - privilege_records = ProviderRecordUtility.get_records_of_type( - provider_records, - ProviderRecordType.PRIVILEGE, - ) - # Get the home jurisdiction selection, if it exists current_provider_record = ProviderData.create_new( ProviderRecordUtility.get_provider_record(provider_records) @@ -147,7 +141,6 @@ def ingest_license_message(message: dict): except CCNotFoundException: licenses_organized = {} - privilege_records = [] current_provider_record = None # Set (or replace) the posted license for its jurisdiction @@ -199,9 +192,7 @@ def ingest_license_message(message: dict): logger.info('Updating provider data') provider_record = ProviderRecordUtility.populate_provider_record( - current_provider_record=current_provider_record, - license_record=posted_license_record, - privilege_records=privilege_records, + current_provider_record=current_provider_record, license_record=posted_license_record ) # Update our provider data dynamo_transactions.append( diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privilege_history.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privilege_history.py deleted file mode 100644 index 5cf9c8c4e..000000000 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privilege_history.py +++ /dev/null @@ -1,103 +0,0 @@ -from aws_lambda_powertools.utilities.typing import LambdaContext -from cc_common.config import config -from cc_common.data_model.provider_record_util import ProviderRecordUtility -from cc_common.exceptions import CCInvalidRequestException -from cc_common.utils import api_handler, get_provider_user_attributes_from_authorizer_claims - - -@api_handler -def privilege_history_handler(event: dict, context: LambdaContext): - """ - Main entry point for provider users API. - Routes to the appropriate handler based on the HTTP method and resource path. - - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param context: Lambda context - """ - # Extract the HTTP method and resource path - http_method = event.get('httpMethod') - resource_path = event.get('resource') - - # Route to the appropriate handler - api_method = (http_method, resource_path) - match api_method: - case ('GET', '/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history'): - return _get_privilege_history_provider_user_me(event, context) - case ( - 'GET', - '/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history', - ): - return _get_privilege_history_public(event) - case ( - 'GET', - '/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history', - ): - return _get_privilege_history_staff(event) - - # If we get here, the method/resource combination is not supported - raise CCInvalidRequestException(f'Unsupported method or resource: {http_method} {resource_path}') - - -def _get_privilege_history_staff(event: dict): - """Return the enriched and simplified privilege history for staff user front end consumption - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] - jurisdiction = event['pathParameters']['jurisdiction'] - license_type_abbr = event['pathParameters']['licenseType'] - - privilege_data = config.data_client.get_privilege_data( - compact=compact, - provider_id=provider_id, - detail=True, - jurisdiction=jurisdiction, - license_type_abbr=license_type_abbr, - ) - - return ProviderRecordUtility.construct_simplified_privilege_history_object(privilege_data) - - -def _get_privilege_history_public(event: dict): - """Return the enriched and simplified privilege history for public front end consumption - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] - jurisdiction = event['pathParameters']['jurisdiction'] - license_type_abbr = event['pathParameters']['licenseType'] - - privilege_data = config.data_client.get_privilege_data( - compact=compact, - provider_id=provider_id, - detail=True, - jurisdiction=jurisdiction, - license_type_abbr=license_type_abbr, - ) - - return ProviderRecordUtility.construct_simplified_privilege_history_object( - privilege_data, should_include_encumbrance_details=False - ) - - -def _get_privilege_history_provider_user_me(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument - """Return the enriched and simplified privilege history for provider - for front end consumption - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - compact, provider_id = get_provider_user_attributes_from_authorizer_claims(event) - jurisdiction = event['pathParameters']['jurisdiction'] - license_type_abbr = event['pathParameters']['licenseType'] - - privilege_data = config.data_client.get_privilege_data( - compact=compact, - provider_id=provider_id, - detail=True, - jurisdiction=jurisdiction, - license_type_abbr=license_type_abbr, - ) - - return ProviderRecordUtility.construct_simplified_privilege_history_object(privilege_data) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privileges.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privileges.py deleted file mode 100644 index da8f41870..000000000 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/privileges.py +++ /dev/null @@ -1,101 +0,0 @@ -import json - -from aws_lambda_powertools.metrics import MetricUnit -from aws_lambda_powertools.utilities.typing import LambdaContext -from cc_common.config import config, logger, metrics -from cc_common.data_model.schema.common import CCPermissionsAction -from cc_common.event_batch_writer import EventBatchWriter -from cc_common.exceptions import CCInternalException, CCInvalidRequestException -from cc_common.utils import api_handler, authorize_compact_level_only_action - - -@api_handler -@authorize_compact_level_only_action(action=CCPermissionsAction.ADMIN) -def deactivate_privilege(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument - """Deactivate a provider's privilege for a specific jurisdiction. - This endpoint requires admin permissions for the compact. - - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - compact = event['pathParameters']['compact'] - provider_id = event['pathParameters']['providerId'] - jurisdiction = event['pathParameters']['jurisdiction'] - license_type_abbr = event['pathParameters']['licenseType'] - - # Get deactivation note from request body - try: - body = json.loads(event['body']) - deactivation_note = body['deactivationNote'] - except KeyError as e: - raise CCInvalidRequestException('Invalid request body') from e - - with logger.append_context_keys( - compact=compact, provider_id=provider_id, jurisdiction=jurisdiction, license_type=license_type_abbr - ): - # Validate the license type is a supported abbreviation - if license_type_abbr not in config.license_type_abbreviations[compact].values(): - logger.warning('Invalid license type abbreviation') - raise CCInvalidRequestException(f'Invalid license type abbreviation: {license_type_abbr}') - - staff_user_id = event['requestContext']['authorizer']['claims']['sub'] - staff_user = config.user_client.get_user_in_compact(compact=compact, user_id=staff_user_id) - - deactivation_details = { - 'note': deactivation_note, - 'deactivatedByStaffUserId': staff_user_id, - 'deactivatedByStaffUserName': f'{staff_user["attributes"]["givenName"]} {staff_user["attributes"]["familyName"]}', # noqa: E501 - } - - logger.info('Deactivating privilege') - deactivated_privilege_record = config.data_client.deactivate_privilege( - compact=compact, - provider_id=provider_id, - jurisdiction=jurisdiction, - license_type_abbr=license_type_abbr, - deactivation_details=deactivation_details, - ) - with EventBatchWriter(config.events_client) as event_writer: - event_writer.put_event( - Entry={ - 'Source': 'org.compactconnect.provider-data', - 'DetailType': 'privilege.deactivation', - 'Detail': json.dumps( - { - 'eventTime': config.current_standard_datetime.isoformat(), - 'compact': compact, - 'jurisdiction': jurisdiction, - 'providerId': provider_id, - } - ), - 'EventBusName': config.event_bus_name, - } - ) - - # Send email notifications for privilege deactivation - failed_to_send_notification = False - deactivated_privilege_id = deactivated_privilege_record['privilegeId'] - # Get provider information to retrieve name - provider = config.data_client.get_provider(compact=compact, provider_id=provider_id, detail=False)['items'][0] - try: - # Send notification to the jurisdiction - logger.info('Sending privilege deactivation notification to jurisdiction', jurisdiction=jurisdiction) - provider_first_name = provider['givenName'] - provider_last_name = provider['familyName'] - config.email_service_client.send_jurisdiction_privilege_deactivation_email( - compact=compact, - jurisdiction=jurisdiction, - privilege_id=deactivated_privilege_id, - provider_first_name=provider_first_name, - provider_last_name=provider_last_name, - ) - except CCInternalException as e: - # Log the error but don't fail the deactivation process - logger.error('Failed to send jurisdiction privilege deactivation notifications', exception=str(e)) - failed_to_send_notification = True - - if failed_to_send_notification: - logger.error('One or more deactivation notifications failed to send. Pushing metric to fire alert.') - metrics.add_metric(name='privilege-deactivation-notification-failed', unit=MetricUnit.Count, value=1) - - return {'message': 'OK'} diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py index 085b7063a..6f4fafdbd 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/public_lookup.py @@ -78,7 +78,6 @@ def public_query_providers(event: dict, context: LambdaContext): # noqa: ARG001 jurisdiction=jurisdiction, provider_name=provider_name, scan_forward=scan_forward, - exclude_providers_without_privileges=True, pagination=body.get('pagination'), ), } @@ -94,7 +93,6 @@ def public_query_providers(event: dict, context: LambdaContext): # noqa: ARG001 compact=compact, jurisdiction=jurisdiction, scan_forward=scan_forward, - only_providers_with_privileges=True, pagination=body.get('pagination'), ), } diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/state_api.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/state_api.py index 157f243e7..cc752f5ff 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/state_api.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/state_api.py @@ -1,208 +1,10 @@ -from functools import partial - from aws_lambda_powertools.utilities.typing import LambdaContext -from cc_common.config import config, logger -from cc_common.data_model.schema.common import CCPermissionsAction -from cc_common.data_model.schema.license import LicenseData -from cc_common.data_model.schema.privilege import PrivilegeData -from cc_common.data_model.schema.provider.api import ( - ProviderGeneralResponseSchema, - QueryJurisdictionProvidersRequestSchema, - StateProviderDetailGeneralResponseSchema, - StateProviderDetailPrivateResponseSchema, -) -from cc_common.data_model.update_tier_enum import UpdateTierEnum -from cc_common.exceptions import CCInternalException, CCInvalidRequestException, CCNotFoundException -from cc_common.signature_auth import optional_signature_auth, required_signature_auth -from cc_common.utils import ( - _user_has_read_private_access_for_provider, - api_handler, - authorize_compact, - authorize_compact_jurisdiction, - get_event_scopes, -) -from marshmallow import ValidationError +from cc_common.signature_auth import optional_signature_auth +from cc_common.utils import api_handler, authorize_compact_jurisdiction from handlers.bulk_upload import _bulk_upload_url_handler -@api_handler -@required_signature_auth -@authorize_compact(action=CCPermissionsAction.READ_GENERAL) -def query_jurisdiction_providers(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument - """Query providers with privileges in a specific jurisdiction. This endpoint is used by state IT systems to query - providers for their own jurisdiction. - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - jurisdiction = event['pathParameters']['jurisdiction'] - compact = event['pathParameters']['compact'] - - # Parse and validate the request body using the schema to strip whitespace - try: - schema = QueryJurisdictionProvidersRequestSchema() - body = schema.loads(event['body']) - except ValidationError as e: - logger.warning('Invalid request body', errors=e.messages) - raise CCInvalidRequestException(f'Invalid request: {e.messages}') from e - - # For jurisdiction-specific queries, we always filter by the jurisdiction from the path - # and sort by date of update - sort_direction = body.get('sorting', {}).get('direction', 'ascending') - scan_forward = sort_direction == 'ascending' - - # Extract query parameters for time window filtering - query = body.get('query', {}) - start_date_time = query.get('startDateTime') - end_date_time = query.get('endDateTime') - - # Convert datetime objects to ISO format strings if present - if start_date_time is not None: - start_date_time = start_date_time.isoformat() - if end_date_time is not None: - end_date_time = end_date_time.isoformat() - - # For jurisdiction-specific queries, we always sort by date of update - client_resp = config.data_client.get_providers_sorted_by_updated( - compact=compact, - jurisdiction=jurisdiction, - scan_forward=scan_forward, - pagination=body.get('pagination'), - only_providers_with_privileges_in_jurisdiction=True, - start_date_time=start_date_time, - end_date_time=end_date_time, - ) - - # Convert generic field to more specific one for this API and sanitize data - unsanitized_providers = client_resp.pop('items', []) - # for the query endpoint, we only return generally available data, regardless of the caller's scopes - general_schema = ProviderGeneralResponseSchema() - sanitized_providers = [general_schema.load(provider) for provider in unsanitized_providers] - - return { - 'query': query, - 'sorting': {'direction': sort_direction}, - 'providers': sanitized_providers, - 'pagination': client_resp['pagination'], - } - - -def _create_flattened_privilege(privilege: PrivilegeData, license_record: LicenseData) -> dict: - """ - Create a flattened privilege record by combining privilege and license data. - - :param privilege: Privilege record - :param license_record: Matching license record - :return: Flattened privilege record with combined data - """ - # Start with privilege data and set type - flattened = privilege.to_dict() - flattened['type'] = 'statePrivilege' - - # Remove fields from license that would conflict with privilege fields - license_copy = license_record.to_dict() - conflicting_fields = { - 'providerId', - 'compact', - 'jurisdiction', - 'licenseType', - 'type', - 'pk', - 'sk', - 'dateOfIssuance', - 'dateOfRenewal', - 'dateOfUpdate', - 'dateOfExpiration', - 'status', - 'administratorSetStatus', - } - for field in conflicting_fields: - license_copy.pop(field, None) - - # Merge license data into flattened record using ** operator to detect conflicts - # This will raise an exception if there are any unexpected duplicate keys - return dict(**flattened, **license_copy) - - -@api_handler -@required_signature_auth -@authorize_compact(action=CCPermissionsAction.READ_GENERAL) -def get_provider(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument - """Return one provider's data, greatly simplified (flattened) for state IT system consumption - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - """ - try: - compact = event['pathParameters']['compact'] - jurisdiction = event['pathParameters']['jurisdiction'] - # Schema validation for url parameters - provider_id = event['pathParameters']['providerId'] - except KeyError as e: - # This shouldn't happen without miss-configuring the API, but we'll handle it, anyway - logger.error(f'Missing parameter: {e}') - raise CCInvalidRequestException('Missing required field') from e - - with logger.append_context_keys(compact=compact, provider_id=provider_id, jurisdiction=jurisdiction): - # Collect all main provider records and privilege update records, which are included in tier one. - provider_user_records = config.data_client.get_provider_user_records( - compact=compact, provider_id=provider_id, include_update_tier=UpdateTierEnum.TIER_ONE - ) - - # Get caller's scopes to determine private data access - scopes = get_event_scopes(event) - has_private_access = _user_has_read_private_access_for_provider( - compact=compact, provider_information=provider_user_records.generate_api_response_object(), scopes=scopes - ) - - # Filter privileges to only those in the requested jurisdiction - jurisdiction_privileges: list[PrivilegeData] = provider_user_records.get_privilege_records( - filter_condition=lambda lic: lic.jurisdiction == jurisdiction - ) - - if not jurisdiction_privileges: - logger.info('No privileges found for provider in jurisdiction', jurisdiction=jurisdiction) - raise CCNotFoundException('Provider has no privileges in the requested jurisdiction') - - # Create flattened privilege records - flattened_privileges = [] - - def _license_matches_privilege(license_data: LicenseData, privilege_data: PrivilegeData): - return ( - license_data.jurisdiction == privilege_data.licenseJurisdiction - and license_data.licenseType == privilege_data.licenseType - ) - - for privilege in jurisdiction_privileges: - with logger.append_context_keys( - privilege_jurisdiction=privilege.jurisdiction, license_type=privilege.licenseType - ): - matching_license: list[LicenseData] = provider_user_records.get_license_records( - filter_condition=partial(_license_matches_privilege, privilege_data=privilege) - ) - match_count = len(matching_license) - if match_count != 1: - logger.error('Expected to find exactly one matching license', matches=match_count) - raise CCInternalException('Error matching license to privilege') - - flattened_privilege = _create_flattened_privilege(privilege, matching_license[0]) - flattened_privileges.append(flattened_privilege) - - # Select appropriate schema based on access level - if has_private_access: - response_schema = StateProviderDetailPrivateResponseSchema() - else: - response_schema = StateProviderDetailGeneralResponseSchema() - - # Construct provider UI URL - provider_ui_url = f'{config.api_base_url}/{compact}/Licensing/{provider_id}' - - # Create response - response_data = {'privileges': flattened_privileges, 'providerUIUrl': provider_ui_url} - - # Sanitization happens on the way out, via schema load - return response_schema.load(response_data) - - @api_handler @optional_signature_auth @authorize_compact_jurisdiction(action='write') diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/__init__.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/__init__.py index 58552b183..48628a375 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/__init__.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/__init__.py @@ -1,7 +1,7 @@ import json import logging import os -from datetime import UTC, date, datetime, timedelta +from datetime import UTC, datetime, timedelta from decimal import Decimal from glob import glob from random import randint @@ -230,17 +230,12 @@ def _load_provider_data(self): """Use the canned test resources to load a basic provider to the DB""" test_resources = glob('../common/tests/resources/dynamo/*.json') - def privilege_jurisdictions_to_set(obj: dict): - if obj.get('type') == 'provider' and 'privilegeJurisdictions' in obj: - obj['privilegeJurisdictions'] = set(obj['privilegeJurisdictions']) - return obj - for resource in test_resources: with open(resource) as f: if resource.endswith('user.json'): # skip the staff user test data, as it is not stored in the provider table continue - record = json.load(f, object_hook=privilege_jurisdictions_to_set, parse_float=Decimal) + record = json.load(f, parse_float=Decimal) logger.debug('Loading resource, %s: %s', resource, str(record)) if record['type'] == 'provider-ssn': @@ -252,19 +247,16 @@ def _generate_providers( self, *, home: str, - privilege_jurisdiction: str | None, start_serial: int, names: tuple[tuple[str, str]] = (), date_of_update: datetime | None = None, ): - """Generate 10 providers with one license and one privilege + """Generate 10 providers with one license. :param home: The jurisdiction for the license - :param privilege_jurisdiction: The jurisdiction for the privilege :param start_serial: Starting number for last portion of the provider's SSN :param names: A list of tuples, each containing a family name and given name :param date_of_update: Fixed date to use for provider updates, if None uses random dates """ - from cc_common.data_model.data_client import DataClient from handlers.ingest import ingest_license_message, preprocess_license_ingest with open('../common/tests/resources/ingest/preprocessor-sqs-message.json') as f: @@ -274,9 +266,7 @@ def _generate_providers( ingest_message = json.load(f) name_faker = Faker(['en_US', 'ja_JP', 'es_MX']) - data_client = DataClient(self.config) - - # Generate 10 providers, each with a license and a privilege + # Generate 10 providers, each with a license for name_idx, ssn_serial in enumerate(range(start_serial, start_serial - 10, -1)): # So we can mutate top-level fields without messing up subsequent iterations preprocessing_sqs_message_copy = json.loads(json.dumps(preprocessing_sqs_message)) @@ -370,19 +360,3 @@ def _generate_providers( {'Records': [{'messageId': '123', 'body': json.dumps(ingest_message_copy)}]}, self.mock_context, ) - - # Add a privilege - provider_user_records = data_client.get_provider_user_records( - compact='cosm', - provider_id=provider_id, - ) - if privilege_jurisdiction: - data_client.create_provider_privileges( - compact='cosm', - provider_id=provider_id, - provider_record=provider_user_records.get_provider_record(), - jurisdiction_postal_abbreviations=[privilege_jurisdiction], - license_expiration_date=date(2050, 6, 6), - existing_privileges_for_license=[], - license_type='cosmetologist', - ) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_client.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_client.py index 08a34344f..ff2c47d05 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_client.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_client.py @@ -11,20 +11,20 @@ class TestClient(TstFunction): def test_get_providers_sorted_by_family_name(self): from cc_common.data_model.data_client import DataClient - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9989) - self._generate_providers(home='ne', privilege_jurisdiction='co', start_serial=9979) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9989) + self._generate_providers(home='ne', start_serial=9979) client = DataClient(self.config) - # We expect to see 20 providers: 10 have privileges in oh, 10 have licenses in oh + # We expect to see 10 providers with licenses in oh resp = client.get_providers_sorted_by_family_name( compact='cosm', jurisdiction='oh', - pagination={'pageSize': 10}, + pagination={'pageSize': 5}, ) first_provider_ids = {item['providerId'] for item in resp['items']} first_items = resp['items'] - self.assertEqual(10, len(resp['items'])) + self.assertEqual(5, len(resp['items'])) self.assertIsInstance(resp['pagination']['lastKey'], str) last_key = resp['pagination']['lastKey'] @@ -33,7 +33,7 @@ def test_get_providers_sorted_by_family_name(self): jurisdiction='oh', pagination={'lastKey': last_key, 'pageSize': 100}, ) - self.assertEqual(10, len(resp['items'])) + self.assertEqual(5, len(resp['items'])) self.assertIsNone(resp['pagination']['lastKey']) second_provider_ids = {item['providerId'] for item in resp['items']} @@ -48,7 +48,7 @@ def test_get_providers_sorted_by_family_name(self): def test_get_providers_sorted_by_family_name_descending(self): from cc_common.data_model.data_client import DataClient - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) + self._generate_providers(home='oh', start_serial=9999) client = DataClient(self.config) resp = client.get_providers_sorted_by_family_name( @@ -68,7 +68,6 @@ def test_get_providers_by_family_name(self): # We'll provide names, so we know we'll have one record for our friends, Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=( ('Testerly', 'Tess'), @@ -95,7 +94,6 @@ def test_get_providers_by_family_and_given_name(self): # We'll provide names, so we know we'll have one record for our friends, Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=( ('Testerly', 'Tess'), @@ -120,20 +118,20 @@ def test_get_providers_by_family_and_given_name(self): def test_get_providers_sorted_by_date_updated(self): from cc_common.data_model.data_client import DataClient - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9989) - self._generate_providers(home='ne', privilege_jurisdiction='ky', start_serial=9979) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9989) + self._generate_providers(home='ne', start_serial=9979) client = DataClient(self.config) - # We expect to see 20 providers: 10 have privileges in oh, 10 have licenses in oh + # We expect to see 10 providers with licenses in oh resp = client.get_providers_sorted_by_updated( compact='cosm', jurisdiction='oh', - pagination={'pageSize': 10}, + pagination={'pageSize': 5}, ) first_provider_ids = {item['providerId'] for item in resp['items']} first_provider_items = resp['items'] - self.assertEqual(10, len(resp['items'])) + self.assertEqual(5, len(resp['items'])) self.assertIsInstance(resp['pagination']['lastKey'], str) last_key = resp['pagination']['lastKey'] @@ -142,7 +140,7 @@ def test_get_providers_sorted_by_date_updated(self): jurisdiction='oh', pagination={'lastKey': last_key, 'pageSize': 10}, ) - self.assertEqual(10, len(resp['items'])) + self.assertEqual(5, len(resp['items'])) self.assertIsNone(resp['pagination']['lastKey']) second_provider_ids = {item['providerId'] for item in resp['items']} @@ -157,7 +155,7 @@ def test_get_providers_sorted_by_date_updated(self): def test_get_providers_sorted_by_date_of_update_descending(self): from cc_common.data_model.data_client import DataClient - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) + self._generate_providers(home='oh', start_serial=9999) client = DataClient(self.config) resp = client.get_providers_sorted_by_updated( @@ -171,52 +169,10 @@ def test_get_providers_sorted_by_date_of_update_descending(self): dates_of_update = [item['dateOfUpdate'] for item in resp['items']] self.assertListEqual(sorted(dates_of_update, reverse=True), dates_of_update) - def test_get_providers_sorted_by_updated_privileges_in_jurisdiction_requires_jurisdiction(self): - from cc_common.data_model.data_client import DataClient - - client = DataClient(self.config) - - # Verify that an exception is raised when jurisdiction is None and - # only_providers_with_privileges_in_jurisdiction is True - with self.assertRaises(RuntimeError) as context: - client.get_providers_sorted_by_updated( - compact='cosm', - jurisdiction=None, - only_providers_with_privileges_in_jurisdiction=True, - ) - - self.assertEqual( - 'jurisdiction is required when only_providers_with_privileges_in_jurisdiction is True', - str(context.exception), - ) - - def test_get_providers_sorted_by_updated_with_jurisdiction_and_only_providers_with_privileges_in_jurisdiction(self): - from cc_common.data_model.data_client import DataClient - - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) - client = DataClient(self.config) - - # Verify that the method works correctly when jurisdiction is provided and - # only_providers_with_privileges_in_jurisdiction is True - resp = client.get_providers_sorted_by_updated( - compact='cosm', - jurisdiction='ne', - only_providers_with_privileges_in_jurisdiction=True, - ) - - # Should return providers that have privileges in the specified jurisdiction - self.assertIsInstance(resp['items'], list) - # All 10 created providers should be returned - self.assertEqual(10, len(resp['items'])) - # All returned providers should have privileges in 'ne' jurisdiction - for provider in resp['items']: - self.assertIn('ne', provider['privilegeJurisdictions']) - def _load_provider_data(self) -> str: with open('../common/tests/resources/dynamo/provider.json') as f: provider_record = json.load(f) provider_id = provider_record['providerId'] - provider_record['privilegeJurisdictions'] = set(provider_record['privilegeJurisdictions']) self._provider_table.put_item(Item=provider_record) with open('../common/tests/resources/dynamo/privilege.json') as f: diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py index 271d06a76..51fdc97fd 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_data_model/test_provider_transformations.py @@ -1,5 +1,5 @@ import json -from datetime import date, datetime +from datetime import datetime from unittest.mock import patch from cc_common.data_model.update_tier_enum import UpdateTierEnum @@ -20,8 +20,6 @@ def test_transformations(self, mock_license_preprocessing_queue): database, then returned via the API. We will specifically test that chain, end to end, to make sure the transformations all happen as expected. """ - from cc_common.data_model.provider_record_util import ProviderUserRecords - # Before we get started, we'll pre-set the SSN/providerId association we expect with open('../common/tests/resources/dynamo/provider-ssn.json') as f: provider_ssn = json.load(f) @@ -83,49 +81,26 @@ def test_transformations(self, mock_license_preprocessing_queue): # This should fully ingest the license, which will result in it being written to the DB ingest_license_message(event, self.mock_context) - from cc_common.data_model.data_client import DataClient - # We'll fetch the provider id from the ssn table - client = DataClient(self.config) provider_id = self._ssn_table.get_item(Key={'pk': f'cosm#SSN#{license_ssn}', 'sk': f'cosm#SSN#{license_ssn}'})[ 'Item' ]['providerId'] self.assertEqual(expected_provider_id, provider_id) - provider_user_records: ProviderUserRecords = client.get_provider_user_records( - compact='cosm', provider_id=provider_id - ) - - # Expected representation of each record in the database - with open('../common/tests/resources/dynamo/provider.json') as f: - expected_provider = json.load(f) - # this should be set during the registration flow - # provider should be active and compact eligible - expected_provider['licenseStatus'] = 'active' - expected_provider['compactEligibility'] = 'eligible' - - # Add a privilege to practice in Nebraska - client.create_provider_privileges( - compact='cosm', - provider_id=provider_id, - provider_record=provider_user_records.get_provider_record(), - # using values in expected privilege json file - jurisdiction_postal_abbreviations=['ne'], - license_expiration_date=date(2025, 4, 4), - existing_privileges_for_license=[], - license_type='cosmetologist', - ) # Get the provider and all update records straight from the table, to inspect them - provider_user_records: ProviderUserRecords = self.config.data_client.get_provider_user_records( + provider_user_records = self.config.data_client.get_provider_user_records( compact='cosm', provider_id=provider_id, include_update_tier=UpdateTierEnum.TIER_THREE ) - # One record for each of: provider, license, and privilege - self.assertEqual(3, len(provider_user_records.provider_records)) + # One record for each of: provider and license (no privileges in cosmetology model) + self.assertEqual(2, len(provider_user_records.provider_records)) records = {item['type']: item for item in provider_user_records.provider_records} - # Convert this to the data type expected from DynamoDB - expected_provider['privilegeJurisdictions'] = set(expected_provider['privilegeJurisdictions']) + # Expected representation of each record in the database + with open('../common/tests/resources/dynamo/provider.json') as f: + expected_provider = json.load(f) + expected_provider['licenseStatus'] = 'active' + expected_provider['compactEligibility'] = 'eligible' with open('../common/tests/resources/dynamo/license.json') as f: expected_license = json.load(f) @@ -137,33 +112,16 @@ def test_transformations(self, mock_license_preprocessing_queue): expected_license['licenseUploadDateGSISK'] = ( 'TIME#1731110399#LT#cos#PID#89a6377e-c3a5-40e5-bca5-317ec854c570' ) - with open('../common/tests/resources/dynamo/privilege.json') as f: - expected_privilege = json.load(f) - # privilege status should be active - expected_privilege['status'] = 'active' # each record has a dynamic dateOfUpdate field that we'll remove for comparison - for record in [ - expected_provider, - expected_license, - expected_privilege, - *records.values(), - ]: - # Drop dynamic field + for record in [expected_provider, expected_license, *records.values()]: del record['dateOfUpdate'] - # These fields will be dynamic, so we'll remove them from comparison del expected_provider['providerDateOfUpdate'] del records['provider']['providerDateOfUpdate'] - del expected_privilege['dateOfIssuance'] - del expected_privilege['dateOfRenewal'] - # removing dynamic fields - del records['privilege']['dateOfIssuance'] - del records['privilege']['dateOfRenewal'] # Make sure each is represented the way we expect, in the db self.assertEqual(expected_provider, records['provider']) self.assertEqual(expected_license, records['license']) - self.assertEqual(expected_privilege, records['privilege']) from handlers.providers import get_provider @@ -187,21 +145,14 @@ def test_transformations(self, mock_license_preprocessing_queue): # Force the provider id to match expected_provider['providerId'] = provider_id + # privileges tied to active states + expected_provider['privileges'] = [] # Drop dynamic fields from comparison del provider_data['dateOfUpdate'] del provider_data['licenses'][0]['dateOfUpdate'] - del provider_data['privileges'][0]['dateOfUpdate'] - del provider_data['privileges'][0]['dateOfIssuance'] - del provider_data['privileges'][0]['dateOfRenewal'] del expected_provider['dateOfUpdate'] del expected_provider['licenses'][0]['dateOfUpdate'] - del expected_provider['privileges'][0]['dateOfUpdate'] - del expected_provider['privileges'][0]['dateOfIssuance'] - del expected_provider['privileges'][0]['dateOfRenewal'] - - # in this case, this should be set to the privilege issued date, which is the mock time used by this test - expected_provider['privileges'][0]['activeSince'] = '2024-11-08T23:59:59+00:00' # Phew! We've loaded the data all the way in via the ingest chain and back out via the API! self.maxDiff = None diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py index f64bdf6dd..f628cd369 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py @@ -118,11 +118,11 @@ def test_bulk_upload_strips_whitespace_from_string_fields(self): # Create CSV content with whitespace in string fields csv_content = ( - 'ssn,npi,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' + 'ssn,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' ',dateOfRenewal,dateOfExpiration,licenseStatus,compactEligibility,homeAddressStreet1' ',homeAddressStreet2,homeAddressCity,homeAddressState,homeAddressPostalCode' ',emailAddress,phoneNumber,licenseType,licenseStatusName\n' - '123-45-6789,1234567890,' + '123-45-6789,' ' LICENSE123 ,' ' John ,' ' Middle ,' @@ -187,7 +187,6 @@ def test_bulk_upload_strips_whitespace_from_string_fields(self): self.assertEqual('cosm', message_data['compact']) self.assertEqual('oh', message_data['jurisdiction']) self.assertEqual('123-45-6789', message_data['ssn']) - self.assertEqual('1234567890', message_data['npi']) self.assertEqual('active', message_data['licenseStatus']) self.assertEqual('eligible', message_data['compactEligibility']) @@ -198,11 +197,11 @@ def test_bulk_upload_prevents_compact_jurisdiction_overwrites(self): # Create CSV content that includes compact and jurisdiction fields # These should NOT be allowed to overwrite the values from the URL path csv_content = ( - 'ssn,npi,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' + 'ssn,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' ',dateOfRenewal,dateOfExpiration,licenseStatus,compactEligibility,homeAddressStreet1' ',homeAddressStreet2,homeAddressCity,homeAddressState,homeAddressPostalCode' ',emailAddress,phoneNumber,licenseType,licenseStatusName,compact,jurisdiction\n' - '123-45-6789,1234567890,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' + '123-45-6789,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' 'eligible,123 Main St,Apt 1,Columbus,OH,43215,test@example.com,+15551234567,esthetician,Active,' 'malicious_compact,malicious_jurisdiction' ) @@ -253,7 +252,6 @@ def test_bulk_upload_prevents_compact_jurisdiction_overwrites(self): 'licenseStatusName': 'Active', 'licenseStatus': 'active', 'compactEligibility': 'eligible', - 'npi': '1234567890', 'licenseNumber': 'LICENSE123', 'givenName': 'John', 'middleName': 'Middle', @@ -278,13 +276,13 @@ def test_bulk_upload_prevents_repeated_ssns_within_the_same_file_upload(self): # Create CSV content that includes duplicate SSNs # Rows that duplicate the same SSN will be considered an error and not processed csv_content = ( - 'ssn,npi,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' + 'ssn,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' ',dateOfRenewal,dateOfExpiration,licenseStatus,compactEligibility,homeAddressStreet1' ',homeAddressStreet2,homeAddressCity,homeAddressState,homeAddressPostalCode' ',emailAddress,phoneNumber,licenseType,licenseStatusName\n' - '123-45-6789,1234567890,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' + '123-45-6789,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' 'eligible,123 Main St,Apt 1,Columbus,OH,43215,test@example.com,+15551234567,cosmetologist,Active\n' - '123-45-6789,1234567890,LICENSE456,Jane,Middle,Smith,,1995-01-01,2023-01-01,2025-01-01,2026-01-01,active,' + '123-45-6789,LICENSE456,Jane,Middle,Smith,,1995-01-01,2023-01-01,2025-01-01,2026-01-01,active,' 'eligible,123 Main St,Apt 1,Columbus,OH,43215,test@example.com,+15551234567,cosmetologist,Active' ) @@ -333,7 +331,6 @@ def test_bulk_upload_prevents_repeated_ssns_within_the_same_file_upload(self): 'licenseStatusName': 'Active', 'licenseStatus': 'active', 'compactEligibility': 'eligible', - 'npi': '1234567890', 'licenseNumber': 'LICENSE456', 'givenName': 'Jane', 'middleName': 'Middle', @@ -362,13 +359,13 @@ def test_bulk_upload_allows_repeated_ssns_for_different_license_types(self): # Create CSV content that includes duplicate SSNs but different license types csv_content = ( - 'ssn,npi,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' + 'ssn,licenseNumber,givenName,middleName,familyName,suffix,dateOfBirth,dateOfIssuance' ',dateOfRenewal,dateOfExpiration,licenseStatus,compactEligibility,homeAddressStreet1' ',homeAddressStreet2,homeAddressCity,homeAddressState,homeAddressPostalCode' ',emailAddress,phoneNumber,licenseType,licenseStatusName\n' - '123-45-6789,1234567890,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' + '123-45-6789,LICENSE123,John,Middle,Doe,Jr.,1990-01-01,2020-01-01,2021-01-01,2023-01-01,active,' 'eligible,123 Main St,Apt 1,Columbus,OH,43215,test@example.com,+15551234567,cosmetologist,Active\n' - '123-45-6789,1234567890,LICENSE456,John,Middle,Doe,Jr.,1990-01-01,2023-01-01,2025-01-01,2026-01-01,active,' + '123-45-6789,LICENSE456,John,Middle,Doe,Jr.,1990-01-01,2023-01-01,2025-01-01,2026-01-01,active,' 'eligible,123 Main St,Apt 1,Columbus,OH,43215,test@example.com,+15551234567,esthetician,' 'Active' ) @@ -412,10 +409,10 @@ def test_bulk_upload_handles_bom_character(self): # Create CSV content without BOM in the string (BOM will be added during encoding) csv_content = ( - 'dateOfIssuance,npi,licenseNumber,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,' + 'dateOfIssuance,licenseNumber,dateOfBirth,licenseType,familyName,homeAddressCity,middleName,' 'licenseStatus,licenseStatusName,compactEligibility,ssn,homeAddressStreet1,homeAddressStreet2,' 'dateOfExpiration,homeAddressState,homeAddressPostalCode,givenName,dateOfRenewal\n' - '2024-06-30,0608337260,BOM0608337260,2024-06-30,esthetician,TestFamily,Columbus,' + '2024-06-30,BOM0608337260,2024-06-30,esthetician,TestFamily,Columbus,' 'TestMiddle,active,ACTIVE,eligible,529-31-5413,123 BOM Test St.,Apt 1,2024-06-30,oh,43215,' 'TestGiven,2024-06-30' ) @@ -458,7 +455,6 @@ def test_bulk_upload_handles_bom_character(self): self.assertEqual('ACTIVE', message_data['licenseStatusName']) self.assertEqual('eligible', message_data['compactEligibility']) self.assertEqual('529-31-5413', message_data['ssn']) - self.assertEqual('0608337260', message_data['npi']) # Verify injected fields self.assertEqual('cosm', message_data['compact']) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py index 94577c1f0..51aac1233 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py @@ -12,9 +12,8 @@ class TestIngest(TstFunction): @staticmethod def _set_provider_data_to_empty_values(expected_provider: dict) -> dict: - # The canned response resource assumes that the provider will be given a privilege, - # home state selection, and one license renewal. We didn't do any of that here, so we'll reset that data - expected_provider['privilegeJurisdictions'] = [] + # The canned response resource assumes that the provider will be given + # one license renewal. We didn't do any of that here, so we'll reset that data expected_provider['privileges'] = [] return expected_provider @@ -168,8 +167,6 @@ def test_existing_provider_deactivation(self, mock_event_writer): expected_provider['licenses'][0]['licenseStatus'] = 'inactive' expected_provider['compactEligibility'] = 'ineligible' expected_provider['licenses'][0]['compactEligibility'] = 'ineligible' - # ensure the privilege record is also set to inactive - expected_provider['privileges'][0]['status'] = 'inactive' provider_data = self._get_provider_via_api(provider_id) @@ -506,56 +503,6 @@ def test_preprocess_license_returns_batch_item_failure_if_error_occurs(self): resp = preprocess_license_ingest(event, self.mock_context) self.assertEqual({'batchItemFailures': [{'itemIdentifier': '123'}]}, resp) - def test_inactive_privileges_included_in_privilege_jurisdictions(self): - """ - Test that inactive privileges are included in the privilegeJurisdictions list. - This test verifies that we include all jurisdictions a user has privileges in, - regardless of whether they are active or not. - """ - from handlers.ingest import ingest_license_message - - # The test resource provider has a license in oh and active privilege in ne - self._load_provider_data() - with open('../common/tests/resources/dynamo/provider-ssn.json') as f: - provider_id = json.load(f)['providerId'] - - # Add an inactive privilege record for this provider in a different jurisdiction (ky) - inactive_privilege = { - 'pk': f'cosm#PROVIDER#{provider_id}', - 'sk': 'cosm#PROVIDER#privilege/ky#', - 'type': 'privilege', - 'providerId': provider_id, - 'compact': 'cosm', - 'jurisdiction': 'ky', - 'licenseType': 'cosmetologist', - 'licenseJurisdiction': 'oh', - 'dateOfIssuance': '2023-01-01', - 'dateOfRenewal': '2023-01-01', - 'dateOfExpiration': '2025-01-01', - 'dateOfUpdate': '2025-01-01T12:59:59+00:00', - 'privilegeId': 'test-privilege-id', - 'administratorSetStatus': 'inactive', # This privilege is inactive - } - self.config.provider_table.put_item(Item=inactive_privilege) - - # Now ingest a new license to trigger the provider record update - with open('../common/tests/resources/ingest/event-bridge-message.json') as f: - message = json.load(f) - - # Make a small change to trigger an update - message['detail']['phoneNumber'] = '+19876543210' - - event = {'Records': [{'messageId': '123', 'body': json.dumps(message)}]} - resp = ingest_license_message(event, self.mock_context) - self.assertEqual({'batchItemFailures': []}, resp) - - # Get the provider data and verify that the inactive privilege jurisdiction is included - provider_data = self._get_provider_via_api(provider_id) - - # The privilegeJurisdictions should include both the active privilege from the test setup - # and the inactive privilege we just added - self.assertEqual({'ky', 'ne'}, set(provider_data['privilegeJurisdictions'])) - def test_multiple_license_types_same_jurisdiction(self): """ Test that multiple license types in the same jurisdiction are handled correctly. diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py index dbeda34c6..12dcc4298 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py @@ -233,6 +233,7 @@ def test_post_licenses_handles_empty_license_object(self): 'homeAddressPostalCode': ['Missing data for required field.'], 'homeAddressState': ['Missing data for required field.'], 'homeAddressStreet1': ['Missing data for required field.'], + 'licenseNumber': ['Missing data for required field.'], 'licenseStatus': ['Missing data for required field.'], 'licenseType': ['Missing data for required field.'], 'ssn': ['Missing data for required field.'], diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py deleted file mode 100644 index 483c2cbc9..000000000 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privilege_history.py +++ /dev/null @@ -1,266 +0,0 @@ -import json -from datetime import datetime -from unittest.mock import patch - -from common_test.test_constants import DEFAULT_ADVERSE_ACTION_ID, DEFAULT_DATE_OF_UPDATE_TIMESTAMP -from moto import mock_aws - -from .. import TstFunction - -TEST_COMPACT = 'cosm' -MOCK_SSN = '123-12-1234' - - -@mock_aws -@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat(DEFAULT_DATE_OF_UPDATE_TIMESTAMP)) -class TestGetProvider(TstFunction): - def setUp(self): - super().setUp() - - def _when_testing_provider_user_event_with_custom_claims(self): - self._load_provider_data() - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_privilege_update_record_in_provider_table( - value_overrides={ - 'updateType': 'encumbrance', - 'encumbranceDetails': { - 'clinicalPrivilegeActionCategories': ['Non-compliance With Requirements'], - 'licenseJurisdiction': 'oh', - 'adverseActionId': DEFAULT_ADVERSE_ACTION_ID, - }, - 'createDate': datetime.fromisoformat('2023-05-05T12:59:59+00:00'), - 'effectiveDate': datetime.fromisoformat('2022-05-05T12:59:59+00:00'), - } - ) - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - event['httpMethod'] = 'GET' - event['resource'] = '/v1/provider-users/me/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history' - event['requestContext']['authorizer']['claims']['custom:providerId'] = test_provider.providerId - event['requestContext']['authorizer']['claims']['custom:compact'] = test_provider.compact - event['pathParameters'] = {'jurisdiction': 'ne', 'licenseType': 'cos'} - - return event - - def _when_testing_public_endpoint(self): - self._load_provider_data() - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_privilege_update_record_in_provider_table( - value_overrides={ - 'updateType': 'encumbrance', - 'encumbranceDetails': { - 'clinicalPrivilegeActionCategories': ['Non-compliance With Requirements'], - 'licenseJurisdiction': 'oh', - 'adverseActionId': DEFAULT_ADVERSE_ACTION_ID, - }, - 'createDate': datetime.fromisoformat('2023-05-05T12:59:59+00:00'), - 'effectiveDate': datetime.fromisoformat('2022-05-05T12:59:59+00:00'), - } - ) - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - event['httpMethod'] = 'GET' - event['resource'] = ( - '/v1/public/compacts/{compact}/providers/{providerId}/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history' - ) - event['pathParameters'] = { - 'jurisdiction': 'ne', - 'licenseType': 'cos', - 'compact': test_provider.compact, - 'providerId': test_provider.providerId, - } - - return event - - def _when_testing_staff_endpoint(self): - self._load_provider_data() - test_provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_privilege_update_record_in_provider_table( - value_overrides={ - 'updateType': 'encumbrance', - 'encumbranceDetails': { - 'clinicalPrivilegeActionCategories': ['Non-compliance With Requirements', 'Misconduct or Abuse'], - 'licenseJurisdiction': 'oh', - 'adverseActionId': DEFAULT_ADVERSE_ACTION_ID, - }, - 'createDate': datetime.fromisoformat('2023-05-05T12:59:59+00:00'), - 'effectiveDate': datetime.fromisoformat('2022-05-05T12:59:59+00:00'), - } - ) - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - event['httpMethod'] = 'GET' - event['resource'] = ( - '/v1/compacts/{compact}/providers/{providerId}/privileges/jurisdiction/{jurisdiction}/licenseType/{licenseType}/history' - ) - event['pathParameters'] = { - 'jurisdiction': 'ne', - 'licenseType': 'cos', - 'compact': test_provider.compact, - 'providerId': test_provider.providerId, - } - - return event - - # Test not found privilege throws exception - def test_privilege_not_found_returns_404_provider_user_me(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_provider_user_event_with_custom_claims() - - event['pathParameters'] = {'jurisdiction': 'ma', 'licenseType': 'cos'} - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(404, resp['statusCode']) - - def test_privilege_not_found_returns_404_public(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_public_endpoint() - event['pathParameters']['jurisdiction'] = 'ma' - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(404, resp['statusCode']) - - def test_privilege_not_found_returns_404_staff(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_staff_endpoint() - event['pathParameters']['jurisdiction'] = 'ma' - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(404, resp['statusCode']) - - def test_get_privilege_history_users_me_returns_expected_history(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_provider_user_event_with_custom_claims() - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - history_data = json.loads(resp['body']) - - expected_history = { - 'compact': 'cosm', - 'events': [ - { - 'createDate': '2016-05-05T12:59:59+00:00', - 'dateOfUpdate': '2016-05-05T12:59:59+00:00', - 'effectiveDate': '2016-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': '2020-05-05T12:59:59+00:00', - 'dateOfUpdate': '2020-05-05T12:59:59+00:00', - 'effectiveDate': '2020-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'renewal', - }, - { - 'createDate': '2023-05-05T12:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'effectiveDate': '2022-05-05T12:59:59+00:00', - 'npdbCategories': ['Non-compliance With Requirements'], - 'type': 'privilegeUpdate', - 'updateType': 'encumbrance', - }, - ], - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-NE-1', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - } - - self.assertEqual(expected_history, history_data) - - def test_get_privilege_history_public_returns_expected_history(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_public_endpoint() - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - history_data = json.loads(resp['body']) - - expected_history = { - 'compact': 'cosm', - 'events': [ - { - 'createDate': '2016-05-05T12:59:59+00:00', - 'dateOfUpdate': '2016-05-05T12:59:59+00:00', - 'effectiveDate': '2016-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': '2020-05-05T12:59:59+00:00', - 'dateOfUpdate': '2020-05-05T12:59:59+00:00', - 'effectiveDate': '2020-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'renewal', - }, - { - 'createDate': '2023-05-05T12:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'effectiveDate': '2022-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'encumbrance', - }, - ], - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-NE-1', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - } - - self.assertEqual(expected_history, history_data) - - def test_get_privilege_history_staff_returns_expected_history(self): - from handlers.privilege_history import privilege_history_handler - - event = self._when_testing_staff_endpoint() - - resp = privilege_history_handler(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - history_data = json.loads(resp['body']) - - expected_history = { - 'compact': 'cosm', - 'events': [ - { - 'createDate': '2016-05-05T12:59:59+00:00', - 'dateOfUpdate': '2016-05-05T12:59:59+00:00', - 'effectiveDate': '2016-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'issuance', - }, - { - 'createDate': '2020-05-05T12:59:59+00:00', - 'dateOfUpdate': '2020-05-05T12:59:59+00:00', - 'effectiveDate': '2020-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'renewal', - }, - { - 'createDate': '2023-05-05T12:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'effectiveDate': '2022-05-05T12:59:59+00:00', - 'type': 'privilegeUpdate', - 'updateType': 'encumbrance', - 'npdbCategories': ['Non-compliance With Requirements', 'Misconduct or Abuse'], - }, - ], - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-NE-1', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - } - - self.assertEqual(expected_history, history_data) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privileges.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privileges.py deleted file mode 100644 index 5175541a2..000000000 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_privileges.py +++ /dev/null @@ -1,241 +0,0 @@ -import json -from datetime import datetime -from unittest.mock import patch - -from aws_lambda_powertools.metrics import MetricUnit -from boto3.dynamodb.types import TypeDeserializer -from cc_common.exceptions import CCInternalException -from moto import mock_aws - -from .. import TstFunction - -TEST_STAFF_USER_ID = 'a4182428-d061-701c-82e5-a3d1d547d797' -TEST_STAFF_USER_EMAIL = 'test-staff-user@example.com' -TEST_STAFF_USER_FIRST_NAME = 'Joe' -TEST_STAFF_USER_LAST_NAME = 'Dokes' -TEST_NOTE = 'User does not like having this privilege.' - -DEACTIVATION_EVENT = { - 'type': 'privilegeUpdate', - 'updateType': 'deactivation', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - 'compact': 'cosm', - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'createDate': '2024-11-08T23:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'effectiveDate': '2024-11-08T23:59:59+00:00', - 'deactivationDetails': { - 'note': TEST_NOTE, - 'deactivatedByStaffUserId': TEST_STAFF_USER_ID, - 'deactivatedByStaffUserName': f'{TEST_STAFF_USER_FIRST_NAME} {TEST_STAFF_USER_LAST_NAME}', - }, - 'previous': { - 'attestations': [{'attestationId': 'jurisprudence-confirmation', 'version': '1'}], - 'dateOfIssuance': '2016-05-05T12:59:59+00:00', - 'dateOfRenewal': '2020-05-05T12:59:59+00:00', - 'dateOfExpiration': '2025-04-04', - 'dateOfUpdate': '2020-05-05T12:59:59+00:00', - 'privilegeId': 'COS-NE-1', - 'administratorSetStatus': 'active', - 'licenseJurisdiction': 'oh', - }, - 'updatedValues': {'administratorSetStatus': 'inactive'}, -} - - -@mock_aws -@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-11-08T23:59:59+00:00')) -class TestDeactivatePrivilege(TstFunction): - def setUp(self): - super().setUp() - # add test staff user to staff user dynamodb table - with open('../common/tests/resources/dynamo/user.json') as f: - staff_user = TypeDeserializer().deserialize({'M': json.load(f)}) - # swap out the default test values with our constants - staff_user.update( - { - 'pk': f'USER#{TEST_STAFF_USER_ID}', - 'userId': TEST_STAFF_USER_ID, - 'attributes': { - 'email': TEST_STAFF_USER_EMAIL, - 'givenName': TEST_STAFF_USER_FIRST_NAME, - 'familyName': TEST_STAFF_USER_LAST_NAME, - }, - } - ) - # This item is saved in its serialized form, so we have to deserialize it first - self.config.users_table.put_item(Item=staff_user) - - def _assert_the_privilege_was_deactivated(self): - from handlers.providers import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - with open('../common/tests/resources/api/provider-detail-response.json') as f: - expected_provider = json.load(f) - - # The user has read permission for cosm - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral ne/cosm.readPrivate' - event['pathParameters'] = { - 'compact': 'cosm', - 'providerId': expected_provider['providerId'], - 'licenseType': 'cos', - } - - resp = get_provider(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - # Update expected provider data to include the deactivation - expected_provider['privileges'][0]['administratorSetStatus'] = 'inactive' - expected_provider['privileges'][0]['status'] = 'inactive' - expected_provider['privileges'][0]['dateOfUpdate'] = '2024-11-08T23:59:59+00:00' - # remove activeSince Field, since the privilege in this case would not be active - del expected_provider['privileges'][0]['activeSince'] - - body = json.loads(resp['body']) - self.assertEqual(expected_provider, body) - - def _request_deactivation_with_scopes(self, scopes: str): - from handlers.privileges import deactivate_privilege - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = scopes - event['requestContext']['authorizer']['claims']['sub'] = TEST_STAFF_USER_ID - event['pathParameters'] = { - 'compact': 'cosm', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - 'jurisdiction': 'ne', - 'licenseType': 'cos', - } - event['body'] = json.dumps({'deactivationNote': TEST_NOTE}) - - return deactivate_privilege(event, self.mock_context) - - @patch('cc_common.config._Config.email_service_client') - @patch('handlers.privileges.EventBatchWriter', autospec=True) - def test_compact_admin_can_deactivate_privilege(self, mock_event_writer, mock_email_service_client): # noqa: ARG002 unused-argument - """ - If a compact admin has admin permission in the compact, they can deactivate a privilege - """ - self._load_provider_data() - - # The user has admin permission for cosm - resp = self._request_deactivation_with_scopes('openid email cosm/admin cosm/admin') - self.assertEqual(200, resp['statusCode']) - self.assertEqual({'message': 'OK'}, json.loads(resp['body'])) - - self._assert_the_privilege_was_deactivated() - mock_event_writer.return_value.__enter__.return_value.put_event.assert_called_once() - call_kwargs = mock_event_writer.return_value.__enter__.return_value.put_event.call_args.kwargs - self.assertEqual( - call_kwargs, - { - 'Entry': { - 'Source': 'org.compactconnect.provider-data', - 'DetailType': 'privilege.deactivation', - 'Detail': json.dumps( - { - 'eventTime': '2024-11-08T23:59:59+00:00', - 'compact': 'cosm', - 'jurisdiction': 'ne', - 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570', - } - ), - 'EventBusName': 'license-data-events', - } - }, - ) - - @patch('cc_common.config._Config.email_service_client') - def test_board_admin_cannot_deactivate_privilege(self, mock_email_service_client): # noqa: ARG002 unused-argument - """ - If a board admin has admin permission in the privilege jurisdiction, they can deactivate a privilege - """ - self._load_provider_data() - - # The user has admin permission for ne - resp = self._request_deactivation_with_scopes('openid email ne/cosm.admin') - self.assertEqual(403, resp['statusCode']) - self.assertEqual({'message': 'Access denied'}, json.loads(resp['body'])) - - def test_deactivate_privilege_handler_sends_expected_email_notifications(self): - """ - If a board admin has admin permission in the privilege jurisdiction, they can deactivate a privilege - """ - self._load_provider_data() - - # The user has admin permission for ne - with patch('handlers.privileges.config.email_service_client') as mock_email_service_client: - resp = self._request_deactivation_with_scopes('openid email cosm/admin') - - self.assertEqual(200, resp['statusCode']) - self.assertEqual({'message': 'OK'}, json.loads(resp['body'])) - - mock_email_service_client.send_jurisdiction_privilege_deactivation_email.assert_called_once_with( - compact='cosm', - jurisdiction='ne', - privilege_id='COS-NE-1', - provider_first_name='Björk', - provider_last_name='Guðmundsdóttir', - ) - - def test_invalid_request_exception_raised_if_privilege_already_deactivated(self): - """ - If a board admin does not have admin permission in the privilege jurisdiction, they cannot deactivate a - privilege. - """ - self._load_provider_data() - with open('../common/tests/resources/dynamo/privilege.json') as f: - privilege = json.load(f) - # set persisted status to deactivated - privilege['administratorSetStatus'] = 'inactive' - self.config.provider_table.put_item(Item=privilege) - # calling deactivation on privilege that is already deactivated - resp = self._request_deactivation_with_scopes('openid email cosm/admin') - - self.assertEqual(400, resp['statusCode']) - self.assertEqual({'message': 'Privilege already deactivated'}, json.loads(resp['body'])) - - @patch('handlers.privileges.metrics') - def test_deactivate_privilege_handler_pushes_custom_metric_if_state_notification_failed_to_send(self, mock_metrics): - """ - If the deactivation state notification fails to send, ensure we raise an exception. - """ - self._load_provider_data() - - with patch('handlers.privileges.config.email_service_client') as mock_email_service_client: - ( - mock_email_service_client.send_jurisdiction_privilege_deactivation_email - ).side_effect = CCInternalException('email failed to send') - # We expect the handler to still return a 200, since the privilege was deactivated - resp = self._request_deactivation_with_scopes('openid email cosm/admin') - - self.assertEqual(200, resp['statusCode']) - - # assert metric was sent - mock_metrics.add_metric.assert_called_once_with( - name='privilege-deactivation-notification-failed', unit=MetricUnit.Count, value=1 - ) - - def test_non_admin_cannot_deactivate_privilege(self): - """ - If a non-admin user attempts to deactivate a privilege, the response should be a 403 - """ - self._load_provider_data() - - # The user has read permission for cosm - resp = self._request_deactivation_with_scopes('openid email cosm/readGeneral ne/cosm.readPrivate') - self.assertEqual(403, resp['statusCode']) - - def test_deactivate_privilege_not_found(self): - """ - If a privilege is not found, the response should be a 404 - """ - # Note lack of self._load_provider_data() here - we're _not_ loading the provider in this case - resp = self._request_deactivation_with_scopes('openid email cosm/admin') - self.assertEqual(404, resp['statusCode']) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py index 06b70ff99..82b801700 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_providers.py @@ -45,9 +45,9 @@ def test_query_by_provider_id_sanitizes_data_even_with_read_private_permission(s def test_query_providers_updated_sorting(self): from handlers.providers import query_providers - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses in oh (two batches) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='oh', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -74,8 +74,7 @@ def test_query_providers_updated_sorting(self): def test_query_providers_family_name_sorting(self): from handlers.providers import query_providers - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - # We'll force the first 10 names, to be a set of values we know are challenging characters + # 20 providers with licenses in oh (two batches); first 10 have challenging name characters names = [ ('山田', '1'), ('後藤', '2'), @@ -88,10 +87,8 @@ def test_query_providers_family_name_sorting(self): ('Figueroa', '9'), ('Frías', '10'), ] - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999, names=names) - # We'll leave the last 10 names to be randomly generated to let the Faker data set come up with some - # interesting values, to leave the door open to identify new edge cases. - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + self._generate_providers(home='oh', start_serial=9999, names=names) + self._generate_providers(home='oh', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -119,10 +116,9 @@ def test_query_providers_family_name_sorting(self): def test_query_providers_by_family_name(self): from handlers.providers import query_providers - # 10 providers, licenses in oh, and privileges in ne, including a Tess and Ted Testerly + # 10 providers with licenses in oh, including Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -151,10 +147,9 @@ def test_query_providers_by_family_name(self): def test_query_providers_given_name_only_not_allowed(self): from handlers.providers import query_providers - # 10 providers, licenses in oh, and privileges in ne, including a Tess and Ted Testerly + # 10 providers with licenses in oh, including Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -181,9 +176,9 @@ def test_query_providers_default_sorting(self): """If sorting is not specified, familyName is default""" from handlers.providers import query_providers - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses (10 in oh, 10 in ne) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -209,9 +204,9 @@ def test_query_providers_default_sorting(self): def test_query_providers_invalid_sorting(self): from handlers.providers import query_providers - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses (10 in oh, 10 in ne) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -233,7 +228,6 @@ def test_query_providers_strips_whitespace_from_query_fields(self): # Create providers with known names for testing self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -318,17 +312,11 @@ def test_get_provider_with_compact_level_read_private_access(self): ) def test_get_provider_with_matching_license_jurisdiction_level_read_private_access(self): - # test provider has a license in oh and a privilege in ne + # test provider has a license in oh self._when_testing_get_provider_with_read_private_access( scopes='openid email cosm/readGeneral oh/cosm.readPrivate' ) - def test_get_provider_with_matching_privilege_jurisdiction_level_read_private_access(self): - # test provider has a license in oh and a privilege in ne - self._when_testing_get_provider_with_read_private_access( - scopes='openid email cosm/readGeneral ne/cosm.readPrivate' - ) - def test_get_provider_missing_provider_id(self): from handlers.providers import get_provider @@ -432,29 +420,9 @@ def test_get_provider_ssn_returns_ssn_if_caller_has_read_ssn_license_jurisdictio self.assertEqual(200, resp['statusCode']) self.assertEqual({'ssn': '123-12-1234'}, json.loads(resp['body'])) - def test_get_provider_ssn_returns_ssn_if_caller_has_read_ssn_privilege_jurisdiction_scope(self): - """ - The provider has a privilege in ne, and the caller has readSSN permission for ne. - """ - self._load_provider_data() - - from handlers.providers import get_provider_ssn - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - # The user has read permission for cosm - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral ne/cosm.readSSN' - event['pathParameters'] = {'compact': 'cosm', 'providerId': '89a6377e-c3a5-40e5-bca5-317ec854c570'} - - resp = get_provider_ssn(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - self.assertEqual({'ssn': '123-12-1234'}, json.loads(resp['body'])) - def test_get_provider_ssn_forbidden_without_correct_jurisdiction_level_scope(self): """ - The provider has no license or privilege in ky, and the caller has readSSN permission for ky. + The provider has no license in ky, and the caller has readSSN permission for ky. """ self._load_provider_data() diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py index 6208f328b..2b6fc8e70 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py @@ -59,9 +59,9 @@ def test_public_query_providers_updated_sorting(self): self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses in oh (two batches) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='oh', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -85,20 +85,15 @@ def test_public_query_providers_updated_sorting(self): dates_of_update = [provider['dateOfUpdate'] for provider in body['providers']] self.assertListEqual(sorted(dates_of_update), dates_of_update) - def test_public_query_providers_updated_sorting_only_returns_matching_providers_which_have_any_privileges(self): - """Tests that the public endpoint only returns providers with privileges.""" + def test_public_query_providers_updated_sorting_returns_providers_with_license_in_jurisdiction(self): + """Tests that the public endpoint returns providers with a license in the requested jurisdiction.""" from handlers.public_lookup import public_query_providers self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 30 providers: - # - 10 with licenses in oh and which have privileges - # - 10 with privileges in oh - # - 10 with licenses in oh, but which have no privileges - # We expect only 20 of the 30 to be returned, as we do not return providers which don't have any privileges - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9999) - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9899) - self._generate_providers(home='oh', privilege_jurisdiction=None, start_serial=9899) + # 20 providers with licenses in oh (two batches) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='oh', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -125,8 +120,7 @@ def test_public_query_providers_family_name_sorting(self): self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - # We'll force the first 10 names, to be a set of values we know are challenging characters + # 20 providers with licenses in oh (two batches); first 10 have challenging name characters names = [ ('山田', '1'), ('後藤', '2'), @@ -139,10 +133,8 @@ def test_public_query_providers_family_name_sorting(self): ('Figueroa', '9'), ('Frías', '10'), ] - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999, names=names) - # We'll leave the last 10 names to be randomly generated to let the Faker data set come up with some - # interesting values, to leave the door open to identify new edge cases. - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + self._generate_providers(home='oh', start_serial=9999, names=names) + self._generate_providers(home='oh', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -172,10 +164,9 @@ def test_public_query_providers_by_family_name(self): self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 10 providers, licenses in oh, and privileges in ne, including a Tess and Ted Testerly + # 10 providers with licenses in oh, including Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -208,7 +199,6 @@ def test_public_query_returns_empty_results_if_jurisdiction_is_not_live(self): # for providers self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -242,56 +232,14 @@ def test_public_query_returns_empty_results_if_jurisdiction_is_not_live(self): body, ) - def test_public_query_providers_by_family_name_filters_providers_without_privileges(self): - from handlers.public_lookup import public_query_providers - - self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - - # 10 providers, licenses in oh, and privileges in ne, including a Tess and Ted Testerly - self._generate_providers( - home='oh', - privilege_jurisdiction='ne', - start_serial=9999, - names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), - ) - # 10 more providers without privileges, licenses in oh, and privileges in ne, including a Tess and Ted Testerly - self._generate_providers( - home='oh', - privilege_jurisdiction=None, - start_serial=9999, - names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), - ) - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - # public endpoint does not have authorizer - del event['requestContext']['authorizer'] - event['pathParameters'] = {'compact': 'cosm'} - event['body'] = json.dumps( - { - 'sorting': {'key': 'familyName'}, - 'query': {'jurisdiction': 'oh', 'familyName': 'Testerly'}, - 'pagination': {'pageSize': 10}, - }, - ) - - resp = public_query_providers(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - self.assertEqual(2, len(body['providers'])) - def test_public_query_providers_given_name_only_not_allowed(self): from handlers.public_lookup import public_query_providers self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 10 providers, licenses in oh, and privileges in ne, including a Tess and Ted Testerly + # 10 providers with licenses in oh, including Tess and Ted Testerly self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) @@ -320,9 +268,9 @@ def test_query_providers_default_sorting(self): self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses (10 in oh, 10 in ne) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -350,9 +298,9 @@ def test_public_query_providers_invalid_sorting(self): self._when_jurisdiction_is_live_in_compact(jurisdiction='oh') - # 20 providers, 10 with licenses in oh, 10 with privileges in oh - self._generate_providers(home='ne', privilege_jurisdiction='oh', start_serial=9999) - self._generate_providers(home='oh', privilege_jurisdiction='ne', start_serial=9899) + # 20 providers with licenses (10 in oh, 10 in ne) + self._generate_providers(home='oh', start_serial=9999) + self._generate_providers(home='ne', start_serial=9899) with open('../common/tests/resources/api-event.json') as f: event = json.load(f) @@ -376,7 +324,6 @@ def test_public_query_providers_strips_whitespace_from_query_fields(self): # Create providers with known names for testing self._generate_providers( home='oh', - privilege_jurisdiction='ne', start_serial=9999, names=(('Testerly', 'Tess'), ('Testerly', 'Ted')), ) diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_state_api.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_state_api.py index b407c3222..0d7b03eeb 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_state_api.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_state_api.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timedelta +from datetime import datetime from unittest.mock import patch from uuid import uuid4 @@ -68,627 +68,6 @@ def _create_signed_event(self, event: dict) -> dict: return event -@mock_aws -@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-11-08T23:59:59+00:00')) -class TestQueryJurisdictionProviders(SignatureTestBase): - def _generate_multiple_providers_with_privileges( - self, - count: int, - privilege_jurisdiction: str, - license_jurisdiction: str, - date_of_update: datetime, - start_serial: int = 9999, - ) -> list[str]: - """Helper method to generate multiple providers with privileges in a specific jurisdiction.""" - provider_ids = [] - - for i in range(count): - # Generate unique provider ID for each provider - provider_id = str(uuid4()) - provider_ids.append(provider_id) - - # Create provider record with privilegeJurisdictions already set correctly - self.test_data_generator.put_default_provider_record_in_provider_table( - value_overrides={ - 'providerId': provider_id, - 'licenseJurisdiction': license_jurisdiction, - # Set the jurisdiction where we'll create privileges - 'privilegeJurisdictions': {privilege_jurisdiction}, - 'ssnLastFour': str(start_serial - i), - 'npi': f'{start_serial - i:010d}', - 'givenName': f'Provider{i}', - 'familyName': f'TestFamily{i}', - }, - date_of_update_override=date_of_update.isoformat(), - ) - - # Create license record - self.test_data_generator.put_default_license_record_in_provider_table( - value_overrides={ - 'providerId': provider_id, - 'jurisdiction': license_jurisdiction, - 'ssnLastFour': str(start_serial - i), - 'npi': f'{start_serial - i:010d}', - 'licenseNumber': f'TEST-{start_serial - i}', - 'givenName': f'Provider{i}', - 'familyName': f'TestFamily{i}', - }, - date_of_update_override=date_of_update.isoformat(), - ) - - # Create privilege record directly using TestDataGenerator - self.test_data_generator.put_default_privilege_record_in_provider_table( - value_overrides={ - 'providerId': provider_id, - 'jurisdiction': privilege_jurisdiction, - 'licenseJurisdiction': license_jurisdiction, - 'privilegeId': f'COS-{privilege_jurisdiction.upper()}-{start_serial - i}', - }, - date_of_update_override=date_of_update.isoformat(), - ) - - return provider_ids - - def test_query_jurisdiction_providers_success(self): - # Use a specific date for our test data - date_of_update = datetime.fromisoformat('2024-11-08T12:00:00+00:00') - - # Generate 10 providers with privileges in 'oh' (home='ne', privilege_jurisdiction='oh') - self._generate_multiple_providers_with_privileges( - count=10, - privilege_jurisdiction='oh', - license_jurisdiction='ne', - date_of_update=date_of_update, - start_serial=9999, - ) - - # Generate 20 providers with licenses in 'oh' but privileges in 'ne' (not the target jurisdiction) - # These should NOT be returned since the jurisdiction API only returns providers with privileges in the - # requested jurisdiction. - self._generate_multiple_providers_with_privileges( - count=20, - privilege_jurisdiction='ne', # Privileges in 'ne', not target 'oh' - license_jurisdiction='oh', - date_of_update=date_of_update, - start_serial=9989, - ) - - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - query = {'startDateTime': date_of_update.isoformat(), 'endDateTime': date_of_update.isoformat()} - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - {'query': query, 'pagination': {'pageSize': 30}, 'sorting': {'direction': 'ascending'}} - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - # Should only return the 10 providers with privileges in 'oh', not the 20 with licenses in 'oh' - self.assertEqual(10, len(body['providers'])) - self.assertEqual({'providers', 'pagination', 'query', 'sorting'}, body.keys()) - self.assertEqual(query, body['query']) - self.assertEqual({'direction': 'ascending'}, body['sorting']) - # Check we're actually sorted by date of update - dates_of_update = [provider['dateOfUpdate'] for provider in body['providers']] - self.assertListEqual(sorted(dates_of_update), dates_of_update) - - def test_query_jurisdiction_providers_invalid_request_body(self): - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps({'invalid': 'field'}) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(400, resp['statusCode']) - - def test_query_jurisdiction_providers_with_start_date_time_filter_not_supported(self): - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - { - 'query': {'startDateTime': '2024-11-09T12:00:00+00:00'}, - 'pagination': {'pageSize': 20}, - 'sorting': {'direction': 'ascending'}, - } - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(400, resp['statusCode']) - - def test_query_jurisdiction_providers_with_end_date_time_filter_not_supported(self): - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - { - 'query': {'endDateTime': '2024-11-08T12:00:00+00:00'}, - 'pagination': {'pageSize': 20}, - 'sorting': {'direction': 'ascending'}, - } - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(400, resp['statusCode']) - - def test_query_jurisdiction_providers_with_date_range_filter(self): - # Generate providers with different update dates - middle_date = datetime.fromisoformat('2024-11-08T23:59:59+00:00') - early_date = middle_date - timedelta(hours=2) - late_date = middle_date + timedelta(hours=2) - - # Providers with early date (should be excluded) - self._generate_multiple_providers_with_privileges( - count=10, - privilege_jurisdiction='oh', - license_jurisdiction='ne', - date_of_update=early_date, - start_serial=9999, - ) - - # Providers with middle date (should be included) - self._generate_multiple_providers_with_privileges( - count=10, - privilege_jurisdiction='oh', - license_jurisdiction='ne', - date_of_update=middle_date, - start_serial=9989, - ) - - # Providers with late date (should be excluded) - self._generate_multiple_providers_with_privileges( - count=10, - privilege_jurisdiction='oh', - license_jurisdiction='ne', - date_of_update=late_date, - start_serial=9979, - ) - - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - { - 'query': { - 'startDateTime': (middle_date - timedelta(hours=1)).isoformat(), - 'endDateTime': (middle_date + timedelta(hours=1)).isoformat(), - }, - 'pagination': {'pageSize': 30}, - 'sorting': {'direction': 'ascending'}, - } - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - # Should only return the 10 providers updated within the date range - self.assertEqual(10, len(body['providers'])) - self.assertEqual({'providers', 'pagination', 'query', 'sorting'}, body.keys()) - self.assertEqual( - { - 'startDateTime': (middle_date - timedelta(hours=1)).isoformat(), - 'endDateTime': (middle_date + timedelta(hours=1)).isoformat(), - }, - body['query'], - ) - # All returned providers should have dateOfUpdate from the middle date - for provider in body['providers']: - self.assertEqual(provider['dateOfUpdate'], middle_date.isoformat()) - - def test_query_jurisdiction_providers_with_invalid_date_format(self): - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - { - 'query': {'startDateTime': 'invalid-date-format', 'endDateTime': 'invalid-date-format'}, - 'pagination': {'pageSize': 10}, - } - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(400, resp['statusCode']) - - def test_query_jurisdiction_providers_date_filter_larger_than_7_days_not_allowed(self): - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps( - { - 'query': {'startDateTime': '2024-11-08T12:00:00+00:00', 'endDateTime': '2024-11-15T12:01:00+00:00'}, - 'pagination': {'pageSize': 10}, - 'sorting': {'direction': 'ascending'}, - } - ) - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = query_jurisdiction_providers(event, self.mock_context) - self.assertEqual(400, resp['statusCode']) - - def test_query_jurisdiction_providers_missing_signature_rejected(self): - """Test that query jurisdiction providers is rejected when signature authentication is missing.""" - from handlers.state_api import query_jurisdiction_providers - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh'} - event['body'] = json.dumps({'query': {}, 'pagination': {'pageSize': 30}, 'sorting': {'direction': 'ascending'}}) - - # Do NOT add signature authentication headers - this should cause the request to be rejected - # since @required_signature_auth is used - - resp = query_jurisdiction_providers(event, self.mock_context) - - self.assertEqual(401, resp['statusCode']) - - body = json.loads(resp['body']) - self.assertIn('message', body) - # The error message should indicate missing required signature authentication headers - self.assertIn('x-key-id', body['message'].lower()) - - -@mock_aws -@patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-11-08T23:59:59+00:00')) -@patch('cc_common.config._Config.api_base_url', 'https://app.compactconnect.org') -class TestGetProvider(SignatureTestBase): - def _generate_provider_with_privilege_in_jurisdiction( - self, privilege_jurisdiction: str, license_jurisdiction: str - ) -> str: - """Helper method to generate a provider with a privilege in a specific jurisdiction.""" - - # Create a provider with privileges in the specified jurisdictions using test_data_generator - provider = self.test_data_generator.put_default_provider_record_in_provider_table( - value_overrides={ - 'licenseJurisdiction': license_jurisdiction, - # Set the jurisdiction where we'll create privileges - 'privilegeJurisdictions': {privilege_jurisdiction}, - } - ) - - self.test_data_generator.put_default_license_record_in_provider_table( - value_overrides={ - 'providerId': str(provider.providerId), - 'jurisdiction': license_jurisdiction, - } - ) - - # Create privilege record directly using TestDataGenerator - self.test_data_generator.put_default_privilege_record_in_provider_table( - value_overrides={ - 'providerId': str(provider.providerId), - 'jurisdiction': privilege_jurisdiction, - 'licenseJurisdiction': license_jurisdiction, - 'privilegeId': f'COS-{privilege_jurisdiction.upper()}-1', - } - ) - - return str(provider.providerId) - - def test_get_provider_success_with_general_permissions(self): - """Test successful provider retrieval with general read permissions.""" - # Create a provider with privileges in 'ne' jurisdiction (matches test data) - provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_license_record_in_provider_table() - self.test_data_generator.put_default_privilege_record_in_provider_table() - provider_id = str(provider.providerId) - - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': provider_id} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - - # Prepare expected response based on test data - expected_response = { - 'privileges': [ - { - 'type': 'statePrivilege', - 'providerId': provider_id, - 'compact': 'cosm', - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-NE-1', - 'licenseNumber': 'A0608337260', - 'npi': '0608337260', - 'status': 'active', - 'compactEligibility': 'eligible', - 'dateOfExpiration': '2025-04-04', - 'dateOfIssuance': '2016-05-05T12:59:59+00:00', - 'dateOfRenewal': '2020-05-05T12:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'familyName': 'Guðmundsdóttir', - 'givenName': 'Björk', - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'middleName': 'Gunnar', - 'licenseStatusName': 'DEFINITELY_A_HUMAN', - # Private fields should NOT be present - } - ], - 'providerUIUrl': f'https://app.compactconnect.org/cosm/Licensing/{provider_id}', - } - - self.maxDiff = None - self.assertEqual(expected_response, body) - - # Explicitly assert private fields are not present - privilege = body['privileges'][0] - self.assertNotIn('ssnLastFour', privilege) - self.assertNotIn('emailAddress', privilege) - self.assertNotIn('dateOfBirth', privilege) - self.assertNotIn('homeAddressStreet1', privilege) - self.assertNotIn('phoneNumber', privilege) - - def test_get_provider_success_with_private_permissions(self): - """Test successful provider retrieval with private read permissions.""" - # Create a provider with privileges in 'ne' jurisdiction - # Create a provider with privileges in 'ne' jurisdiction (matches test data) - provider = self.test_data_generator.put_default_provider_record_in_provider_table() - self.test_data_generator.put_default_license_record_in_provider_table() - self.test_data_generator.put_default_privilege_record_in_provider_table() - provider_id = str(provider.providerId) - - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - # Grant private read permissions - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral cosm/readPrivate' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': provider_id} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - - # Prepare expected response with private fields included - expected_response = { - 'privileges': [ - { - 'type': 'statePrivilege', - 'providerId': provider_id, - 'compact': 'cosm', - 'jurisdiction': 'ne', - 'licenseType': 'cosmetologist', - 'privilegeId': 'COS-NE-1', - 'status': 'active', - 'compactEligibility': 'eligible', - 'dateOfExpiration': '2025-04-04', - 'dateOfIssuance': '2016-05-05T12:59:59+00:00', - 'dateOfRenewal': '2020-05-05T12:59:59+00:00', - 'dateOfUpdate': '2024-11-08T23:59:59+00:00', - 'familyName': 'Guðmundsdóttir', - 'givenName': 'Björk', - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'middleName': 'Gunnar', - 'licenseStatusName': 'DEFINITELY_A_HUMAN', - # Private fields should be included - 'ssnLastFour': '1234', - 'emailAddress': 'björk@example.com', - 'dateOfBirth': '1985-06-06', - 'homeAddressStreet1': '123 A St.', - 'homeAddressStreet2': 'Apt 321', - 'homeAddressCity': 'Columbus', - 'homeAddressState': 'oh', - 'homeAddressPostalCode': '43004', - 'phoneNumber': '+13213214321', - 'npi': '0608337260', - 'licenseNumber': 'A0608337260', - } - ], - 'providerUIUrl': f'https://app.compactconnect.org/cosm/Licensing/{provider_id}', - } - - self.maxDiff = None - self.assertEqual(expected_response, body) - - def test_get_provider_success_with_jurisdiction_specific_private_permissions(self): - """Test successful provider retrieval with jurisdiction-specific private read permissions.""" - # Create a provider with privileges in 'ne' jurisdiction - provider_id = self._generate_provider_with_privilege_in_jurisdiction( - privilege_jurisdiction='ne', license_jurisdiction='oh' - ) - - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - # Grant jurisdiction-specific private read permissions for 'oh' (license jurisdiction) - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral oh/cosm.readPrivate' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': provider_id} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - - # Should include private fields due to jurisdiction-specific permissions - self.assertIn('ssnLastFour', body['privileges'][0]) - self.assertIn('emailAddress', body['privileges'][0]) - - def test_get_provider_with_privilege_in_jurisdiction(self): - """Test provider with a privilege in the requested jurisdiction.""" - # Create a provider with a privilege in 'ne' - provider_id = self._generate_provider_with_privilege_in_jurisdiction( - privilege_jurisdiction='ne', license_jurisdiction='oh' - ) - - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': provider_id} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(200, resp['statusCode']) - - body = json.loads(resp['body']) - # Should have the privilege in the requested jurisdiction - self.assertEqual(len(body['privileges']), 1) - - # All privileges should be in the requested jurisdiction - for privilege in body['privileges']: - self.assertEqual('ne', privilege['jurisdiction']) - - def test_get_provider_no_privileges_in_jurisdiction(self): - """Test provider with no privileges in the requested jurisdiction.""" - # Create a provider with privileges only in 'ne', not 'oh' - provider_id = self._generate_provider_with_privilege_in_jurisdiction( - privilege_jurisdiction='ne', license_jurisdiction='oh' - ) - - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'oh', 'providerId': provider_id} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(404, resp['statusCode']) - - def test_get_provider_nonexistent_provider(self): - """Test requesting a nonexistent provider.""" - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': 'nonexistent-provider'} - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(404, resp['statusCode']) - - def test_get_provider_missing_parameters(self): - """Test request with missing required parameters.""" - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm'} # Missing jurisdiction and providerId - - # Add signature authentication headers - event = self._create_signed_event(event) - - resp = get_provider(event, self.mock_context) - - self.assertEqual(400, resp['statusCode']) - - def test_get_provider_missing_signature_rejected(self): - """Test that get provider is rejected when signature authentication is missing.""" - from handlers.state_api import get_provider - - with open('../common/tests/resources/api-event.json') as f: - event = json.load(f) - - event['requestContext']['authorizer']['claims']['scope'] = 'openid email cosm/readGeneral' - event['pathParameters'] = {'compact': 'cosm', 'jurisdiction': 'ne', 'providerId': 'test-provider-id'} - - # Do NOT add signature authentication headers - this should cause the request to be rejected - # since @required_signature_auth is used - - resp = get_provider(event, self.mock_context) - - self.assertEqual(401, resp['statusCode']) - - body = json.loads(resp['body']) - self.assertIn('message', body) - # The error message should indicate missing required signature authentication headers - self.assertIn('x-key-id', body['message'].lower()) - - @mock_aws @patch('cc_common.config._Config.current_standard_datetime', datetime.fromisoformat('2024-11-08T23:59:59+00:00')) class TestBulkUploadUrlHandler(SignatureTestBase): diff --git a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py index a96f702b7..89b7d2981 100644 --- a/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py +++ b/backend/cosmetology-app/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_bulk_upload_unit.py @@ -153,9 +153,9 @@ def test_bad_data(self, mock_send_licenses_to_preprocessing_queue, mock_config): f.seek(0) csv_data = [line.split(',') for line in f] # SSN of line 3 - csv_data[2][8] = '1234' + csv_data[2][7] = '1234' # License type of line 5 - csv_data[4][3] = '' + csv_data[4][2] = '' mangled_rows = [','.join(row) for row in csv_data] mangled_data = '\n'.join(mangled_rows).encode('utf-8') diff --git a/backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py b/backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py index b6e8b948b..9950038d3 100644 --- a/backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py +++ b/backend/cosmetology-app/lambdas/python/search/handlers/manage_opensearch_indices.py @@ -237,7 +237,6 @@ def _get_provider_index_mapping(self, number_of_shards: int, number_of_replicas: 'jurisdictionUploadedLicenseStatus': {'type': 'keyword'}, 'compactEligibility': {'type': 'keyword'}, 'jurisdictionUploadedCompactEligibility': {'type': 'keyword'}, - 'npi': {'type': 'keyword'}, 'licenseNumber': {'type': 'keyword'}, 'givenName': { 'type': 'text', @@ -292,7 +291,6 @@ def _get_provider_index_mapping(self, number_of_shards: int, number_of_replicas: 'compactTransactionId': {'type': 'keyword'}, 'privilegeId': {'type': 'keyword'}, 'status': {'type': 'keyword'}, - 'activeSince': {'type': 'date'}, 'investigationStatus': {'type': 'keyword'}, } @@ -326,7 +324,6 @@ def _get_provider_index_mapping(self, number_of_shards: int, number_of_replicas: 'licenseJurisdiction': {'type': 'keyword'}, 'licenseStatus': {'type': 'keyword'}, 'compactEligibility': {'type': 'keyword'}, - 'npi': {'type': 'keyword'}, 'givenName': { 'type': 'text', 'analyzer': 'custom_ascii_analyzer', @@ -346,7 +343,6 @@ def _get_provider_index_mapping(self, number_of_shards: int, number_of_replicas: 'dateOfExpiration': {'type': 'date'}, 'jurisdictionUploadedLicenseStatus': {'type': 'keyword'}, 'jurisdictionUploadedCompactEligibility': {'type': 'keyword'}, - 'privilegeJurisdictions': {'type': 'keyword'}, 'providerFamGivMid': {'type': 'keyword'}, 'providerDateOfUpdate': {'type': 'date'}, 'birthMonthDay': {'type': 'keyword'}, diff --git a/backend/cosmetology-app/lambdas/python/search/handlers/search.py b/backend/cosmetology-app/lambdas/python/search/handlers/search.py index ec812563d..41f3f3df8 100644 --- a/backend/cosmetology-app/lambdas/python/search/handlers/search.py +++ b/backend/cosmetology-app/lambdas/python/search/handlers/search.py @@ -1,55 +1,17 @@ -import csv -import io - from aws_lambda_powertools.utilities.typing import LambdaContext -from cc_common.config import config, logger +from cc_common.config import logger from cc_common.data_model.schema.common import CCPermissionsAction from cc_common.data_model.schema.provider.api import ( - ExportPrivilegesRequestSchema, ProviderGeneralResponseSchema, SearchProvidersRequestSchema, - StatePrivilegeGeneralResponseSchema, -) -from cc_common.exceptions import ( - CCInternalException, - CCInvalidRequestCustomResponseException, - CCInvalidRequestException, - CCNotFoundException, ) +from cc_common.exceptions import CCInvalidRequestException from cc_common.utils import api_handler, authorize_compact_level_only_action from marshmallow import ValidationError from opensearch_client import OpenSearchClient # Default and maximum page sizes for search results MAX_PROVIDER_PAGE_SIZE = 100 -MAX_MATCH_TOTAL_ALLOWED = 10000 - -# Presigned URL expiration time in seconds (1 minute) -PRESIGNED_URL_EXPIRATION_SECONDS = 60 - -# CSV field names for privilege export -PRIVILEGE_CSV_FIELDS = [ - 'type', - 'providerId', - 'compact', - 'jurisdiction', - 'licenseType', - 'privilegeId', - 'status', - 'compactEligibility', - 'dateOfExpiration', - 'dateOfIssuance', - 'dateOfRenewal', - 'familyName', - 'givenName', - 'middleName', - 'suffix', - 'licenseJurisdiction', - 'licenseStatus', - 'licenseStatusName', - 'licenseNumber', - 'npi', -] # Instantiate the OpenSearch client outside of the handler to cache connection between invocations @@ -76,8 +38,6 @@ def search_api_handler(event: dict, context: LambdaContext): match api_method: case ('POST', '/v1/compacts/{compact}/providers/search'): return _search_providers(event, context) - case ('POST', '/v1/compacts/{compact}/privileges/export'): - return _export_privileges(event, context) # If we get here, the method/resource combination is not supported raise CCInvalidRequestException(f'Unsupported method or resource: {http_method} {resource_path}') @@ -165,158 +125,6 @@ def _search_providers(event: dict, context: LambdaContext): # noqa: ARG001 unus return response_body -def _export_privileges(event: dict, context: LambdaContext): # noqa: ARG001 unused-argument - """ - Export privileges to a CSV file in S3 and return a presigned URL for download. - - This endpoint accepts an OpenSearch DSL query body, retrieves all matching privilege records, - converts them to CSV format, stores the file in S3, and returns a presigned URL for download. - - If the query includes a nested query on privileges with `inner_hits`, only the matched - privileges will be returned. Otherwise, all privileges for matching providers are returned. - See https://docs.opensearch.org/latest/search-plugins/searching-data/inner-hits/ for more information - about inner_hits. - - Example nested query with inner_hits: - { - "query": { - "nested": { - "path": "privileges", - "query": { "term": { "privileges.jurisdiction": "ky" } }, - "inner_hits": {} - } - } - } - - :param event: Standard API Gateway event, API schema documented in the CDK ApiStack - :param LambdaContext context: - :return: Dictionary with fileUrl containing presigned URL to download the CSV file - """ - compact = event['pathParameters']['compact'] - - # Get the caller's cognito user id - caller_user_id = _get_caller_user_id(event) - - # Parse and validate the request body using the schema - body = _parse_and_validate_export_request_body(event) - - # Build the OpenSearch search body - search_body = _build_export_search_body(body) - - # Build the index name for this compact - index_name = f'compact_{compact}_providers' - - logger.info('Executing OpenSearch privilege export', compact=compact, index_name=index_name) - - # Execute the search - response = opensearch_client.search(index_name=index_name, body=search_body) - - # Extract hits from the response - hits_data = response.get('hits', {}) - hits = hits_data.get('hits', []) - total = hits_data['total'] - - if total['value'] >= MAX_MATCH_TOTAL_ALLOWED: - logger.info('request scope too large for current implementation, returning 400 with custom response') - raise CCInvalidRequestCustomResponseException( - response_body={ - 'message': 'Search scope too broad. Please narrow your search.', - } - ) - - # Extract and flatten privileges from provider records - flattened_privileges = [] - privilege_schema = StatePrivilegeGeneralResponseSchema() - - for hit in hits: - provider = hit.get('_source', {}) - # Check if inner_hits are present for privileges - # If so, use only the matched privileges; otherwise, use all privileges - # see https://docs.opensearch.org/latest/search-plugins/searching-data/inner-hits/ for more information - # about inner_hits. - inner_hits = hit.get('inner_hits', {}) - privileges_inner_hits = inner_hits.get('privileges', {}).get('hits', {}).get('hits', []) - - if privileges_inner_hits: - # Use only the privileges that matched the nested query - matched_privileges = [ih.get('_source', {}) for ih in privileges_inner_hits] - provider_privileges = _extract_flattened_privileges_from_list( - privileges=matched_privileges, - licenses=provider.get('licenses', []), - provider=provider, - ) - else: - # No inner_hits, return all privileges for this provider - provider_privileges = _extract_flattened_privileges(provider) - - for flattened_privilege in provider_privileges: - try: - # Sanitize using StatePrivilegeGeneralResponseSchema - sanitized_privilege = privilege_schema.load(flattened_privilege) - # Verify compact matches path parameter - if sanitized_privilege.get('compact') != compact: - logger.error( - 'Privilege compact field does not match path parameter', - # This case is most likely the result of abuse or misconfiguration. - # We log the request body for triaging purposes. We redact the leaf values - # from the request body to obscure PII. - request_body=_redact_leaf_values(body), - provider_id=provider.get('providerId'), - privilege_id=flattened_privilege.get('privilegeId'), - privilege_compact=sanitized_privilege.get('compact'), - path_compact=compact, - ) - # do not include the privilege in the results - continue - flattened_privileges.append(sanitized_privilege) - except ValidationError as e: - logger.error( - 'Failed to sanitize flattened privilege record', - provider_id=provider.get('providerId'), - privilege_id=flattened_privilege.get('privilegeId'), - errors=e.messages, - ) - # We don't want to return partial privilege reports - # If we experience a failure here we need to exit - raise CCInternalException('Failed to process privilege results') from e - - logger.info('Found privileges to export', count=len(flattened_privileges)) - - # If no privileges were found, return 404 - if not flattened_privileges: - raise CCNotFoundException('The search parameters did not match any privileges.') - - # Generate CSV content from the flattened privileges - csv_content = _generate_csv_content(flattened_privileges) - - # Generate S3 key path - request_datetime = config.current_standard_datetime.isoformat() - s3_key = f'compact/{compact}/privilegeSearch/caller/{caller_user_id}/time/{request_datetime}/export.csv' - - # Upload CSV to S3 - logger.info('Uploading CSV to S3', bucket=config.export_results_bucket_name, key=s3_key) - config.s3_client.put_object( - Bucket=config.export_results_bucket_name, - Key=s3_key, - Body=csv_content.encode('utf-8'), - ContentType='text/csv', - ) - - # Generate presigned URL for download - presigned_url = config.s3_client.generate_presigned_url( - 'get_object', - Params={ - 'Bucket': config.export_results_bucket_name, - 'Key': s3_key, - }, - ExpiresIn=PRESIGNED_URL_EXPIRATION_SECONDS, - ) - - logger.info('Generated presigned URL for export', url_expires_in=PRESIGNED_URL_EXPIRATION_SECONDS) - - return {'fileUrl': presigned_url} - - def _parse_and_validate_request_body(event: dict) -> dict: """ Parse and validate the request body using the SearchProvidersRequestSchema. @@ -333,41 +141,6 @@ def _parse_and_validate_request_body(event: dict) -> dict: raise CCInvalidRequestException(f'Invalid request: {e.messages}') from e -def _parse_and_validate_export_request_body(event: dict) -> dict: - """ - Parse and validate the request body for export endpoints. - - Export endpoints only accept the query parameter, no pagination. - - :param event: API Gateway event - :return: Validated request body with query - :raises CCInvalidRequestException: If the request body is invalid - """ - try: - schema = ExportPrivilegesRequestSchema() - return schema.loads(event.get('body', '{}')) - except ValidationError as e: - logger.warning('Invalid request body', errors=e.messages) - raise CCInvalidRequestException(f'Invalid request: {e.messages}') from e - - -def _get_caller_user_id(event: dict) -> str: - """ - Get the caller's cognito user id from the event. - - :param event: API Gateway event - :return: The caller's user id (sub claim from cognito token) - :raises CCInvalidRequestException: If user id cannot be extracted - """ - try: - return event['requestContext']['authorizer']['claims']['sub'] - except (KeyError, TypeError) as e: - # the api auth wrapper should have detected this earlier, so if get here there is an issue with the - # setup. Raise an internal exception - logger.error('Could not extract user id from event', error=str(e)) - raise CCInternalException('Could not determine caller id for privilege report export') from e - - def _redact_leaf_values(data: dict | list | str | int | bool | None) -> dict | list | str: """ Recursively redact all leaf field values in a data structure. @@ -421,168 +194,3 @@ def _build_opensearch_search_body(body: dict, size_override: int) -> dict: raise CCInvalidRequestException('sort is required when using search_after pagination') return search_body - - -def _build_export_search_body(body: dict) -> dict: - """ - Build the OpenSearch search body for export requests. - - Export requests retrieve all matching results in a single request, up to MAX_MATCH_TOTAL_ALLOWED. - OpenSearch's default index.max_result_window is 10,000, which aligns with our limit. - If there are more results than the limit, the export will fail with a 400 error. - - :param body: Validated request body - :return: OpenSearch search body - """ - return { - 'query': body.get('query', {'match_all': {}}), - 'size': MAX_MATCH_TOTAL_ALLOWED, - } - - -def _generate_csv_content(privileges: list[dict]) -> str: - """ - Generate CSV content from a list of privilege records. - - :param privileges: List of flattened privilege records - :return: CSV content as a string - """ - output = io.StringIO() - writer = csv.DictWriter(output, fieldnames=PRIVILEGE_CSV_FIELDS, extrasaction='ignore') - - # Write header row - writer.writeheader() - - # Write data rows - for privilege in privileges: - writer.writerow(privilege) - - return output.getvalue() - - -def _extract_flattened_privileges(provider: dict) -> list[dict]: - """ - Extract and flatten all privileges from a provider document. - - This function combines privilege data with license data to create flattened - privilege records similar to what the state API returns. - - :param provider: Provider document from OpenSearch - :return: List of flattened privilege records - """ - privileges = provider.get('privileges', []) - licenses = provider.get('licenses', []) - - return _extract_flattened_privileges_from_list( - privileges=privileges, - licenses=licenses, - provider=provider, - ) - - -def _extract_flattened_privileges_from_list( - privileges: list[dict], - licenses: list[dict], - provider: dict, -) -> list[dict]: - """ - Flatten a list of privileges by combining with license data. - - This function is used both for extracting all privileges from a provider document - and for processing only the matched privileges from inner_hits. - - :param privileges: List of privilege records to flatten - :param licenses: List of license records from the provider - :param provider: Provider document (for email and provider_id logging) - :return: List of flattened privilege records - """ - if not privileges: - return [] - - flattened_privileges = [] - - for privilege in privileges: - # Find matching license based on licenseJurisdiction and licenseType - matching_license = _find_matching_license( - licenses=licenses, - license_jurisdiction=privilege.get('licenseJurisdiction'), - license_type=privilege.get('licenseType'), - ) - - if matching_license is None: - logger.warning( - 'No matching license found for privilege', - provider_id=provider.get('providerId'), - privilege_id=privilege.get('privilegeId'), - license_jurisdiction=privilege.get('licenseJurisdiction'), - license_type=privilege.get('licenseType'), - ) - # Skip this privilege if no matching license is found - continue - - flattened_privilege = _create_flattened_privilege(privilege, matching_license) - flattened_privileges.append(flattened_privilege) - - return flattened_privileges - - -def _find_matching_license(licenses: list[dict], license_jurisdiction: str, license_type: str) -> dict | None: - """ - Find a license that matches the given jurisdiction and license type. - - :param licenses: List of license records - :param license_jurisdiction: The jurisdiction to match - :param license_type: The license type to match - :return: The matching license or None if not found - """ - for license_record in licenses: - if ( - license_record.get('jurisdiction') == license_jurisdiction - and license_record.get('licenseType') == license_type - ): - return license_record - return None - - -def _create_flattened_privilege(privilege: dict, license_record: dict) -> dict: - """ - Create a flattened privilege record by combining privilege and license data. - - This mirrors the logic in state_api.py _create_flattened_privilege function. - - :param privilege: Privilege record - :param license_record: Matching license record - :return: Flattened privilege record with combined data - """ - # Start with privilege data and set type - flattened = dict(privilege) - flattened['type'] = 'statePrivilege' - - # Remove fields from license that would conflict with privilege fields - license_copy = dict(license_record) - conflicting_fields = { - 'providerId', - 'compact', - 'jurisdiction', - 'licenseType', - 'type', - 'pk', - 'sk', - 'dateOfIssuance', - 'dateOfRenewal', - 'dateOfUpdate', - 'dateOfExpiration', - 'status', - 'administratorSetStatus', - # Also remove nested objects that don't belong in flattened output - 'adverseActions', - 'investigations', - } - for field in conflicting_fields: - license_copy.pop(field, None) - - # Merge license data into flattened record - # License fields like givenName, familyName, npi, etc. get added - flattened.update(license_copy) - - return flattened diff --git a/backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py b/backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py index d058f2d63..03d2fabe5 100644 --- a/backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py +++ b/backend/cosmetology-app/lambdas/python/search/tests/function/test_manage_opensearch_indices.py @@ -218,7 +218,6 @@ def test_on_create_creates_versioned_indices_and_aliases_for_all_compacts_when_n 'fields': {'keyword': {'ignore_above': 256, 'type': 'keyword'}}, 'type': 'text', }, - 'npi': {'type': 'keyword'}, 'phoneNumber': {'type': 'keyword'}, 'providerId': {'type': 'keyword'}, 'suffix': {'type': 'keyword'}, @@ -231,11 +230,8 @@ def test_on_create_creates_versioned_indices_and_aliases_for_all_compacts_when_n 'fields': {'keyword': {'ignore_above': 256, 'type': 'keyword'}}, 'type': 'text', }, - 'npi': {'type': 'keyword'}, - 'privilegeJurisdictions': {'type': 'keyword'}, 'privileges': { 'properties': { - 'activeSince': {'type': 'date'}, 'administratorSetStatus': {'type': 'keyword'}, 'adverseActions': { 'properties': { diff --git a/backend/cosmetology-app/lambdas/python/search/tests/function/test_populate_provider_documents.py b/backend/cosmetology-app/lambdas/python/search/tests/function/test_populate_provider_documents.py index d118b490a..fcf74ca37 100644 --- a/backend/cosmetology-app/lambdas/python/search/tests/function/test_populate_provider_documents.py +++ b/backend/cosmetology-app/lambdas/python/search/tests/function/test_populate_provider_documents.py @@ -95,14 +95,12 @@ def _generate_expected_call_for_document(self, compact): 'currentHomeJurisdiction': 'oh', 'licenseStatus': 'inactive', 'compactEligibility': 'ineligible', - 'npi': '0608337260', 'givenName': f'test{compact}GivenName', 'middleName': 'Gunnar', 'familyName': f'test{compact}FamilyName', 'dateOfExpiration': DEFAULT_LICENSE_EXPIRATION_DATE, 'jurisdictionUploadedLicenseStatus': 'active', 'jurisdictionUploadedCompactEligibility': 'eligible', - 'privilegeJurisdictions': ['ne'], 'birthMonthDay': '06-06', 'licenses': [ { @@ -117,7 +115,6 @@ def _generate_expected_call_for_document(self, compact): 'jurisdictionUploadedLicenseStatus': 'active', 'compactEligibility': 'ineligible', 'jurisdictionUploadedCompactEligibility': 'eligible', - 'npi': '0608337260', 'licenseNumber': 'A0608337260', 'givenName': f'test{compact}GivenName', 'middleName': 'Gunnar', diff --git a/backend/cosmetology-app/lambdas/python/search/tests/function/test_provider_update_ingest.py b/backend/cosmetology-app/lambdas/python/search/tests/function/test_provider_update_ingest.py index 7d65c8261..103a7208e 100644 --- a/backend/cosmetology-app/lambdas/python/search/tests/function/test_provider_update_ingest.py +++ b/backend/cosmetology-app/lambdas/python/search/tests/function/test_provider_update_ingest.py @@ -132,14 +132,12 @@ def _generate_expected_document(self, compact: str, provider_id: str = None) -> 'licenseJurisdiction': 'oh', 'licenseStatus': 'inactive', 'compactEligibility': 'ineligible', - 'npi': '0608337260', 'givenName': f'test{compact}GivenName', 'middleName': 'Gunnar', 'familyName': f'test{compact}FamilyName', 'dateOfExpiration': DEFAULT_LICENSE_EXPIRATION_DATE, 'jurisdictionUploadedLicenseStatus': 'active', 'jurisdictionUploadedCompactEligibility': 'eligible', - 'privilegeJurisdictions': ['ne'], 'birthMonthDay': '06-06', 'licenses': [ { @@ -154,7 +152,6 @@ def _generate_expected_document(self, compact: str, provider_id: str = None) -> 'jurisdictionUploadedLicenseStatus': 'active', 'compactEligibility': 'ineligible', 'jurisdictionUploadedCompactEligibility': 'eligible', - 'npi': '0608337260', 'licenseNumber': 'A0608337260', 'givenName': f'test{compact}GivenName', 'middleName': 'Gunnar', diff --git a/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_privileges.py b/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_privileges.py deleted file mode 100644 index 8c6c6bc7b..000000000 --- a/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_privileges.py +++ /dev/null @@ -1,742 +0,0 @@ -import json -from unittest.mock import patch - -from moto import mock_aws - -from . import TstFunction - - -@mock_aws -class TestExportPrivileges(TstFunction): - """Test suite for search_api_handler - privilege export functionality.""" - - def setUp(self): - super().setUp() - - def _create_api_event( - self, - compact: str, - body: dict = None, - resource_override: str = None, - scopes_override: str = None, - ) -> dict: - """Create a standard API Gateway event for export_privileges.""" - return { - 'resource': '/v1/compacts/{compact}/privileges/export' if not resource_override else resource_override, - 'path': f'/v1/compacts/{compact}/privileges/export', - 'httpMethod': 'POST', - 'headers': { - 'accept': 'application/json', - 'content-type': 'application/json', - 'Content-Type': 'application/json', - 'origin': 'https://example.org', - 'Host': 'api.test.example.com', - }, - 'multiValueHeaders': {}, - 'queryStringParameters': None, - 'pathParameters': {'compact': compact}, - 'requestContext': { - 'resourcePath': '/v1/compacts/{compact}/privileges/export', - 'httpMethod': 'POST', - 'authorizer': { - 'claims': { - 'sub': 'test-user-id', - 'cognito:username': 'test-user', - 'scope': f'openid email {compact}/readGeneral' if not scopes_override else scopes_override, - } - }, - }, - 'body': json.dumps(body) if body else None, - 'isBase64Encoded': False, - } - - def _when_testing_mock_opensearch_client(self, mock_opensearch_client, search_response: dict = None): - """ - Configure the mock OpenSearchClient for testing. - - :param mock_opensearch_client: The patched opensearch_client instance - :param search_response: The response to return from the search method - :return: The mock client instance - """ - if not search_response: - search_response = { - 'hits': { - 'total': {'value': 0, 'relation': 'eq'}, - 'hits': [], - } - } - - # mock_opensearch_client is the patched instance, not the class - mock_opensearch_client.search.return_value = search_response - return mock_opensearch_client - - def _create_mock_provider_hit_with_privileges( - self, - provider_id: str = '00000000-0000-0000-0000-000000000001', - compact: str = 'cosm', - sort_values: list = None, - ) -> dict: - """Create a mock OpenSearch hit for a provider document with privileges and licenses.""" - hit = { - '_index': f'compact_{compact}_providers', - '_id': provider_id, - '_score': 1.0, - '_source': { - 'providerId': provider_id, - 'type': 'provider', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfExpiration': '2025-12-31', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'birthMonthDay': '06-15', - 'licenses': [ - { - 'providerId': provider_id, - 'type': 'license-home', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'jurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfIssuance': '2020-01-01', - 'dateOfRenewal': '2024-01-01', - 'dateOfExpiration': '2025-12-31', - 'npi': '1234567890', - 'licenseNumber': 'COS-12345', - } - ], - 'privileges': [ - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ky', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-01-15', - 'dateOfRenewal': '2024-01-15', - 'dateOfExpiration': '2025-01-15', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-001', - 'status': 'active', - } - ], - }, - } - if sort_values: - hit['sort'] = sort_values - return hit - - @patch('handlers.search.opensearch_client') - def test_privilege_export_returns_presigned_url(self, mock_opensearch_client): - """Test that privilege export returns a presigned URL to a CSV file.""" - from handlers.search import search_api_handler - - # Create a mock response with provider hits containing privileges - mock_hit = self._create_mock_provider_hit_with_privileges() - search_response = { - 'hits': { - 'total': {'value': 1, 'relation': 'eq'}, - 'hits': [mock_hit], - } - } - self._when_testing_mock_opensearch_client(mock_opensearch_client, search_response=search_response) - - event = self._create_api_event('cosm', body={'query': {'match_all': {}}}) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(200, response['statusCode']) - body = json.loads(response['body']) - - # Verify response contains fileUrl - self.assertIn('fileUrl', body) - self.assertIsInstance(body['fileUrl'], str) - # Verify the URL contains expected parts - self.assertIn('test-export-results-bucket', body['fileUrl']) - self.assertIn('compact/cosm/privilegeSearch', body['fileUrl']) - self.assertIn('test-user-id', body['fileUrl']) # caller user id from event - self.assertIn('export.csv', body['fileUrl']) - - # Verify the CSV file was uploaded to S3 by checking the bucket - import boto3 - - s3_client = boto3.client('s3') - response = s3_client.list_objects_v2( - Bucket='test-export-results-bucket', Prefix='compact/cosm/privilegeSearch/caller/test-user-id' - ) - self.assertEqual(1, response['KeyCount']) - - # Get the CSV content and verify it contains the expected data - key = response['Contents'][0]['Key'] - csv_obj = s3_client.get_object(Bucket='test-export-results-bucket', Key=key) - csv_content = csv_obj['Body'].read().decode('utf-8') - - # Verify CSV contains header and data - self.assertIn('type,providerId,compact,jurisdiction', csv_content) - self.assertIn('statePrivilege', csv_content) - self.assertIn('00000000-0000-0000-0000-000000000001', csv_content) - self.assertIn('PRIV-001', csv_content) - - @patch('handlers.search.opensearch_client') - def test_privilege_export_with_empty_results_returns_404(self, mock_opensearch_client): - """Test that privilege export with no results returns a 404 error.""" - from handlers.search import search_api_handler - - search_response = { - 'hits': { - 'total': {'value': 0, 'relation': 'eq'}, - 'hits': [], - } - } - self._when_testing_mock_opensearch_client(mock_opensearch_client, search_response=search_response) - - event = self._create_api_event('cosm', body={'query': {'match_all': {}}}) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(404, response['statusCode']) - body = json.loads(response['body']) - - # Verify response contains error message - self.assertIn('message', body) - self.assertEqual('The search parameters did not match any privileges.', body['message']) - - # Verify no CSV file was uploaded to S3 - import boto3 - - s3_client = boto3.client('s3') - response = s3_client.list_objects_v2( - Bucket='test-export-results-bucket', Prefix='compact/cosm/privilegeSearch/caller/test-user-id' - ) - # Should have no objects - self.assertEqual(0, response.get('KeyCount', 0)) - - @patch('handlers.search.opensearch_client') - def test_privilege_export_skips_provider_without_privileges_returns_404(self, mock_opensearch_client): - """Test that providers without privileges result in a 404 error.""" - from handlers.search import search_api_handler - - # Create a provider hit without privileges - hit = { - '_index': 'compact_cosm_providers', - '_id': 'provider-1', - '_score': 1.0, - '_source': { - 'providerId': 'provider-1', - 'type': 'provider', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': 'cosm', - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'givenName': 'Jane', - 'familyName': 'Smith', - 'dateOfExpiration': '2025-12-31', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'birthMonthDay': '03-20', - 'licenses': [], - 'privileges': [], - }, - } - search_response = { - 'hits': { - 'total': {'value': 1, 'relation': 'eq'}, - 'hits': [hit], - } - } - self._when_testing_mock_opensearch_client(mock_opensearch_client, search_response=search_response) - - event = self._create_api_event('cosm', body={'query': {'match_all': {}}}) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(404, response['statusCode']) - body = json.loads(response['body']) - - # Verify response contains error message - self.assertEqual('The search parameters did not match any privileges.', body['message']) - - # Verify no CSV file was uploaded to S3 - import boto3 - - s3_client = boto3.client('s3') - response = s3_client.list_objects_v2( - Bucket='test-export-results-bucket', Prefix='compact/cosm/privilegeSearch/caller/test-user-id' - ) - # Should have no objects - self.assertEqual(0, response.get('KeyCount', 0)) - - @patch('handlers.search.opensearch_client') - def test_privilege_export_with_multiple_inner_hits_exports_all_matched(self, mock_opensearch_client): - """Test that when inner_hits contains multiple matches, all are exported to CSV. - see https://docs.opensearch.org/latest/search-plugins/searching-data/inner-hits/ for more information - about inner_hits. - """ - from handlers.search import search_api_handler - - provider_id = '00000000-0000-0000-0000-000000000001' - compact = 'cosm' - - # Create a provider with multiple privileges, inner_hits matches two of them - hit = { - '_index': f'compact_{compact}_providers', - '_id': provider_id, - '_score': 1.0, - '_source': { - 'providerId': provider_id, - 'type': 'provider', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfExpiration': '2025-12-31', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'birthMonthDay': '06-15', - 'licenses': [ - { - 'providerId': provider_id, - 'type': 'license-home', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'jurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfIssuance': '2020-01-01', - 'dateOfRenewal': '2024-01-01', - 'dateOfExpiration': '2025-12-31', - 'npi': '1234567890', - 'licenseNumber': 'COS-12345', - } - ], - # Provider has THREE privileges - 'privileges': [ - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ky', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-01-15', - 'dateOfRenewal': '2024-01-15', - 'dateOfExpiration': '2025-01-15', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-KY-001', - 'status': 'active', - }, - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-02-01', - 'dateOfRenewal': '2024-02-01', - 'dateOfExpiration': '2025-02-01', - 'dateOfUpdate': '2024-02-01T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-NE-001', - 'status': 'active', - }, - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'co', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-03-01', - 'dateOfRenewal': '2024-03-01', - 'dateOfExpiration': '2025-03-01', - 'dateOfUpdate': '2024-03-01T10:30:00+00:00', - 'administratorSetStatus': 'inactive', - 'privilegeId': 'PRIV-CO-001', - 'status': 'inactive', - }, - ], - }, - # inner_hits contains TWO active privileges (simulating nested query for status: active) - 'inner_hits': { - 'privileges': { - 'hits': { - 'total': {'value': 2, 'relation': 'eq'}, - 'hits': [ - { - '_index': f'compact_{compact}_providers', - '_id': provider_id, - '_nested': {'field': 'privileges', 'offset': 0}, - '_score': 1.0, - '_source': { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ky', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-01-15', - 'dateOfRenewal': '2024-01-15', - 'dateOfExpiration': '2025-01-15', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-KY-001', - 'status': 'active', - }, - }, - { - '_index': f'compact_{compact}_providers', - '_id': provider_id, - '_nested': {'field': 'privileges', 'offset': 1}, - '_score': 1.0, - '_source': { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-02-01', - 'dateOfRenewal': '2024-02-01', - 'dateOfExpiration': '2025-02-01', - 'dateOfUpdate': '2024-02-01T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-NE-001', - 'status': 'active', - }, - }, - ], - } - } - }, - } - - search_response = { - 'hits': { - 'total': {'value': 1, 'relation': 'eq'}, - 'hits': [hit], - } - } - self._when_testing_mock_opensearch_client(mock_opensearch_client, search_response=search_response) - - event = self._create_api_event('cosm', body={'query': {'match_all': {}}}) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(200, response['statusCode']) - body = json.loads(response['body']) - - # Verify response contains fileUrl - self.assertIn('fileUrl', body) - - # Verify the CSV contains only the 2 matched privileges - import boto3 - - s3_client = boto3.client('s3') - response = s3_client.list_objects_v2( - Bucket='test-export-results-bucket', Prefix='compact/cosm/privilegeSearch/caller/test-user-id' - ) - key = response['Contents'][0]['Key'] - csv_obj = s3_client.get_object(Bucket='test-export-results-bucket', Key=key) - csv_content = csv_obj['Body'].read().decode('utf-8') - - lines = csv_content.strip().split('\n') - self.assertEqual(3, len(lines)) # Header + 2 data rows - self.assertEqual( - 'type,providerId,compact,jurisdiction,licenseType,privilegeId,status,compactEligibility,dateOfExpiration,dateOfIssuance,dateOfRenewal,familyName,givenName,middleName,suffix,licenseJurisdiction,licenseStatus,licenseStatusName,licenseNumber,npi\r', - lines[0], - ) - self.assertEqual( - 'statePrivilege,00000000-0000-0000-0000-000000000001,cosm,ky,cosmetologist,PRIV-KY-001,active,eligible,2025-01-15,2024-01-15,2024-01-15,Doe,John,,,oh,active,,COS-12345,1234567890\r', - lines[1], - ) - self.assertEqual( - 'statePrivilege,00000000-0000-0000-0000-000000000001,cosm,ne,cosmetologist,PRIV-NE-001,active,eligible,2025-02-01,2024-02-01,2024-02-01,Doe,John,,,oh,active,,COS-12345,1234567890', - lines[2], - ) - - @patch('handlers.search.opensearch_client') - def test_privilege_export_without_inner_hits_exports_all_privileges(self, mock_opensearch_client): - """Test that without inner_hits, all privileges for matching providers are exported.""" - from handlers.search import search_api_handler - - provider_id = '00000000-0000-0000-0000-000000000001' - compact = 'cosm' - - # Create a provider with multiple privileges and NO inner_hits - hit = { - '_index': f'compact_{compact}_providers', - '_id': provider_id, - '_score': 1.0, - '_source': { - 'providerId': provider_id, - 'type': 'provider', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'licenseJurisdiction': 'oh', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfExpiration': '2025-12-31', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'birthMonthDay': '06-15', - 'licenses': [ - { - 'providerId': provider_id, - 'type': 'license-home', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'compact': compact, - 'jurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - 'jurisdictionUploadedLicenseStatus': 'active', - 'jurisdictionUploadedCompactEligibility': 'eligible', - 'givenName': 'John', - 'familyName': 'Doe', - 'dateOfIssuance': '2020-01-01', - 'dateOfRenewal': '2024-01-01', - 'dateOfExpiration': '2025-12-31', - 'npi': '1234567890', - 'licenseNumber': 'COS-12345', - } - ], - # Provider has THREE privileges - 'privileges': [ - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ky', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-01-15', - 'dateOfRenewal': '2024-01-15', - 'dateOfExpiration': '2025-01-15', - 'dateOfUpdate': '2024-01-15T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-KY-001', - 'status': 'active', - }, - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'ne', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-02-01', - 'dateOfRenewal': '2024-02-01', - 'dateOfExpiration': '2025-02-01', - 'dateOfUpdate': '2024-02-01T10:30:00+00:00', - 'administratorSetStatus': 'active', - 'privilegeId': 'PRIV-NE-001', - 'status': 'active', - }, - { - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': 'co', - 'licenseJurisdiction': 'oh', - 'licenseType': 'cosmetologist', - 'dateOfIssuance': '2024-03-01', - 'dateOfRenewal': '2024-03-01', - 'dateOfExpiration': '2025-03-01', - 'dateOfUpdate': '2024-03-01T10:30:00+00:00', - 'administratorSetStatus': 'inactive', - 'privilegeId': 'PRIV-CO-001', - 'status': 'inactive', - }, - ], - }, - # No inner_hits - regular query without nested - } - - search_response = { - 'hits': { - 'total': {'value': 1, 'relation': 'eq'}, - 'hits': [hit], - } - } - self._when_testing_mock_opensearch_client(mock_opensearch_client, search_response=search_response) - - event = self._create_api_event('cosm', body={'query': {'match_all': {}}}) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(200, response['statusCode']) - body = json.loads(response['body']) - - # Verify response contains fileUrl - self.assertIn('fileUrl', body) - - # Verify the CSV contains all 3 privileges - import boto3 - - s3_client = boto3.client('s3') - response = s3_client.list_objects_v2( - Bucket='test-export-results-bucket', Prefix='compact/cosm/privilegeSearch/caller/test-user-id' - ) - key = response['Contents'][0]['Key'] - csv_obj = s3_client.get_object(Bucket='test-export-results-bucket', Key=key) - csv_content = csv_obj['Body'].read().decode('utf-8') - - lines = csv_content.strip().split('\n') - self.assertEqual(4, len(lines)) # Header + 3 data rows - self.assertEqual( - 'type,providerId,compact,jurisdiction,licenseType,privilegeId,status,compactEligibility,dateOfExpiration,dateOfIssuance,dateOfRenewal,familyName,givenName,middleName,suffix,licenseJurisdiction,licenseStatus,licenseStatusName,licenseNumber,npi\r', - lines[0], - ) - self.assertEqual( - 'statePrivilege,00000000-0000-0000-0000-000000000001,cosm,ky,cosmetologist,PRIV-KY-001,active,eligible,2025-01-15,2024-01-15,2024-01-15,Doe,John,,,oh,active,,COS-12345,1234567890\r', - lines[1], - ) - self.assertEqual( - 'statePrivilege,00000000-0000-0000-0000-000000000001,cosm,ne,cosmetologist,PRIV-NE-001,active,eligible,2025-02-01,2024-02-01,2024-02-01,Doe,John,,,oh,active,,COS-12345,1234567890\r', - lines[2], - ) - self.assertEqual( - 'statePrivilege,00000000-0000-0000-0000-000000000001,cosm,co,cosmetologist,PRIV-CO-001,inactive,eligible,2025-03-01,2024-03-01,2024-03-01,Doe,John,,,oh,active,,COS-12345,1234567890', - lines[3], - ) - - def test_unsupported_route_returns_400(self): - """Test that unsupported routes return a 400 error.""" - from handlers.search import search_api_handler - - # Create event with unsupported route - event = self._create_api_event(compact='cosm', resource_override='/v1/compacts/cosm/unknown/search') - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(400, response['statusCode']) - body = json.loads(response['body']) - self.assertIn('Unsupported method or resource', body['message']) - - def test_missing_scopes_returns_403(self): - """Test that missing auth scope returns a 403 error.""" - from handlers.search import search_api_handler - - # Create event with unsupported route - event = self._create_api_event(compact='cosm', scopes_override='openid email') - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(403, response['statusCode']) - body = json.loads(response['body']) - self.assertIn('Access denied', body['message']) - - def test_export_query_with_index_key_returns_400(self): - """Test that export queries containing 'index' key are rejected with 400 error.""" - from handlers.search import search_api_handler - - # Test with 'index' key (terms lookup attack pattern) - event = self._create_api_event( - 'cosm', - body={ - 'query': { - 'terms': { - 'providerId': { - 'index': 'compact_cosm_providers', - 'id': 'some-uuid', - 'path': 'providerId', - } - } - } - }, - ) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(400, response['statusCode']) - body = json.loads(response['body']) - self.assertIn('Cross-index queries are not allowed', body['message']) - self.assertIn("'index'", body['message']) - - def test_export_query_with_underscore_index_key_returns_400(self): - """Test that export queries containing '_index' key are rejected with 400 error.""" - from handlers.search import search_api_handler - - # Test with '_index' key (more_like_this attack pattern) - event = self._create_api_event( - 'cosm', - body={ - 'query': { - 'more_like_this': { - 'fields': ['familyName', 'givenName'], - 'like': [ - { - '_index': 'compact_cosm_providers', - '_id': 'target-provider-uuid', - } - ], - } - } - }, - ) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(400, response['statusCode']) - body = json.loads(response['body']) - self.assertIn('Cross-index queries are not allowed', body['message']) - self.assertIn("'_index'", body['message']) - - def test_export_query_with_nested_index_key_returns_400(self): - """Test that export queries with nested 'index' key at any level are rejected.""" - from handlers.search import search_api_handler - - # Test with 'index' key nested deep in the query structure - event = self._create_api_event( - 'cosm', - body={ - 'query': { - 'bool': { - 'should': [ - { - 'terms': { - 'familyName.keyword': { - 'index': 'compact_cosm_providers', - 'id': 'target-uuid', - 'path': 'familyName.keyword', - } - } - } - ] - } - } - }, - ) - - response = search_api_handler(event, self.mock_context) - - self.assertEqual(400, response['statusCode']) - body = json.loads(response['body']) - self.assertIn('Cross-index queries are not allowed', body['message']) - self.assertIn("'index'", body['message']) diff --git a/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_providers.py b/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_providers.py index 258fd2515..0df39642b 100644 --- a/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_providers.py +++ b/backend/cosmetology-app/lambdas/python/search/tests/function/test_search_providers.py @@ -284,7 +284,6 @@ def test_search_returns_sanitized_providers(self, mock_opensearch_client): 'jurisdictionUploadedLicenseStatus': 'active', 'licenseJurisdiction': 'oh', 'licenseStatus': 'active', - 'privilegeJurisdictions': [], 'providerId': '00000000-0000-0000-0000-000000000001', 'type': 'provider', } diff --git a/backend/cosmetology-app/stacks/api_lambda_stack/__init__.py b/backend/cosmetology-app/stacks/api_lambda_stack/__init__.py index 3c07ec8d8..2fb52af30 100644 --- a/backend/cosmetology-app/stacks/api_lambda_stack/__init__.py +++ b/backend/cosmetology-app/stacks/api_lambda_stack/__init__.py @@ -1,16 +1,9 @@ from __future__ import annotations -import os - -from aws_cdk.aws_dynamodb import ITable -from aws_cdk.aws_kms import IKey from aws_cdk.aws_logs import QueryDefinition, QueryString -from aws_cdk.aws_sns import ITopic -from cdk_nag import NagSuppressions from common_constructs.stack import AppStack from constructs import Construct -from common_constructs.python_function import PythonFunction from common_constructs.ssm_parameter_utility import SSMParameterUtility from stacks import persistent_stack as ps @@ -58,13 +51,6 @@ def __init__( persistent_stack=persistent_stack, ) - self.privilege_history_handler = self._privilege_history_handler( - data_encryption_key=persistent_stack.shared_encryption_key, - provider_table=persistent_stack.provider_table, - alarm_topic=persistent_stack.alarm_topic, - ) - self.log_groups.append(self.privilege_history_handler.log_group) - # Bulk upload url lambdas self.bulk_upload_url_lambdas = BulkUploadUrlLambdas( scope=self, @@ -111,41 +97,6 @@ def __init__( # Create the QueryDefinition after all lambda modules have been initialized and added their log groups self._create_runtime_query_definition() - def _privilege_history_handler( - self, - data_encryption_key: IKey, - provider_table: ITable, - alarm_topic: ITopic, - ): - handler = PythonFunction( - self, - 'GetPrivilegeHistory', - description='Get privilege history handler', - lambda_dir='provider-data-v1', - environment={ - 'PROVIDER_TABLE_NAME': provider_table.table_name, - **self.common_env_vars, - }, - index=os.path.join('handlers', 'privilege_history.py'), - handler='privilege_history_handler', - alarm_topic=alarm_topic, - ) - data_encryption_key.grant_decrypt(handler) - provider_table.grant_read_data(handler) - - NagSuppressions.add_resource_suppressions_by_path( - self, - path=f'{handler.role.node.path}/DefaultPolicy/Resource', - suppressions=[ - { - 'id': 'AwsSolutions-IAM5', - 'reason': 'The actions in this policy are specifically what this lambda needs to read ' - 'and is scoped to one table and encryption key.', - }, - ], - ) - return handler - def _create_runtime_query_definition(self): """Create the QueryDefinition for runtime logs after all lambda modules have been initialized.""" QueryDefinition( diff --git a/backend/cosmetology-app/stacks/api_lambda_stack/provider_management.py b/backend/cosmetology-app/stacks/api_lambda_stack/provider_management.py index 0acdd679b..7ee35052c 100644 --- a/backend/cosmetology-app/stacks/api_lambda_stack/provider_management.py +++ b/backend/cosmetology-app/stacks/api_lambda_stack/provider_management.py @@ -52,8 +52,6 @@ def __init__( api_lambda_stack.log_groups.append(self.query_providers_handler.log_group) self.get_provider_ssn_handler = self._get_provider_ssn_handler(lambda_environment) api_lambda_stack.log_groups.append(self.get_provider_ssn_handler.log_group) - self.deactivate_privilege_handler = self._deactivate_privilege_handler(lambda_environment) - api_lambda_stack.log_groups.append(self.deactivate_privilege_handler.log_group) self.provider_encumbrance_handler = self._add_provider_encumbrance_handler(lambda_environment) api_lambda_stack.log_groups.append(self.provider_encumbrance_handler.log_group) @@ -302,39 +300,6 @@ def _get_provider_ssn_handler( return handler - def _deactivate_privilege_handler( - self, - lambda_environment: dict, - ) -> PythonFunction: - """Create and configure the Lambda handler for deactivating a provider's privilege.""" - handler = PythonFunction( - self.scope, - 'DeactivatePrivilegeHandler', - description='Deactivate provider privilege handler', - lambda_dir='provider-data-v1', - index=os.path.join('handlers', 'privileges.py'), - handler='deactivate_privilege', - environment=lambda_environment, - alarm_topic=self.persistent_stack.alarm_topic, - ) - self.persistent_stack.provider_table.grant_read_write_data(handler) - self.persistent_stack.staff_users.user_table.grant_read_data(handler) - self.data_event_bus.grant_put_events_to(handler) - self.persistent_stack.email_notification_service_lambda.grant_invoke(handler) - - NagSuppressions.add_resource_suppressions_by_path( - Stack.of(handler.role), - path=f'{handler.role.node.path}/DefaultPolicy/Resource', - suppressions=[ - { - 'id': 'AwsSolutions-IAM5', - 'reason': 'The actions in this policy are specifically what this lambda needs to read/write ' - 'and is scoped to one table and event bus.', - }, - ], - ) - return handler - def _add_provider_encumbrance_handler( self, lambda_environment: dict, diff --git a/backend/cosmetology-app/stacks/api_stack/v1_api/api.py b/backend/cosmetology-app/stacks/api_stack/v1_api/api.py index 663f223a3..ccffb7b83 100644 --- a/backend/cosmetology-app/stacks/api_stack/v1_api/api.py +++ b/backend/cosmetology-app/stacks/api_stack/v1_api/api.py @@ -87,9 +87,6 @@ def __init__( authorization_scopes=read_ssn_scopes, ) - # Store the privilege history handler for use by multiple modules - privilege_history_handler = api_lambda_stack.privilege_history_handler - # /v1/flags self.flags_resource = self.resource.add_resource('flags') self.feature_flags = FeatureFlagsApi( @@ -115,7 +112,6 @@ def __init__( resource=self.public_compacts_compact_providers_resource, api_model=self.api_model, api_lambda_stack=api_lambda_stack, - privilege_history_function=privilege_history_handler, ) # /v1/compacts @@ -133,7 +129,6 @@ def __init__( ssn_method_options=read_ssn_auth_method_options, api_model=self.api_model, api_lambda_stack=api_lambda_stack, - privilege_history_function=privilege_history_handler, ) # GET /v1/compacts/{compact}/jurisdictions self.jurisdictions_resource = self.compact_resource.add_resource('jurisdictions') diff --git a/backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py b/backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py index c7813140c..65d982d19 100644 --- a/backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py +++ b/backend/cosmetology-app/stacks/api_stack/v1_api/api_model.py @@ -96,7 +96,7 @@ def query_providers_request_model(self) -> Model: ), 'jurisdiction': JsonSchema( type=JsonSchemaType.STRING, - description='Filter for providers with privilege/license in a jurisdiction', + description='Filter for providers with license in a jurisdiction', enum=self.api.node.get_context('jurisdictions'), ), 'givenName': JsonSchema( @@ -351,31 +351,6 @@ def _staff_user_response_schema(self): }, ) - @property - def post_privilege_deactivation_request_model(self) -> Model: - """Return the post privilege deactivation request model, which should only be created once per API""" - if hasattr(self.api, '_v1_post_privilege_deactivation_request_model'): - return self.api._v1_post_privilege_deactivation_request_model - self.api._v1_post_privilege_deactivation_request_model = self.api.add_model( - 'V1PostPrivilegeDeactivationRequestModel', - description='Post privilege deactivation request model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - required=['deactivationNote'], - properties={ - 'deactivationNote': JsonSchema( - type=JsonSchemaType.STRING, - description='Note describing why the privilege is being deactivated', - # setting a max file name length of 256 to prevent abuse - max_length=256, - ), - }, - ), - ) - - return self.api._v1_post_privilege_deactivation_request_model - @property def post_privilege_encumbrance_request_model(self) -> Model: """Return the post privilege encumbrance request model, which should only be created once per API""" @@ -469,7 +444,6 @@ def _providers_response_schema(self): 'jurisdictionUploadedCompactEligibility', 'compact', 'licenseJurisdiction', - 'privilegeJurisdictions', 'dateOfUpdate', 'dateOfExpiration', 'birthMonthDay', @@ -488,7 +462,6 @@ def _provider_detail_response_schema(self): 'familyName', 'compact', 'licenseJurisdiction', - 'privilegeJurisdictions', 'dateOfUpdate', 'dateOfExpiration', 'birthMonthDay', @@ -938,7 +911,6 @@ def _investigation_schema(self) -> JsonSchema: @property def _common_license_properties(self) -> dict: return { - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), @@ -965,8 +937,6 @@ def _common_provider_properties(self) -> dict: return { 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['provider']), 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), - # Derived from a license record - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), 'ssnLastFour': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{4}$'), 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), @@ -985,10 +955,6 @@ def _common_provider_properties(self) -> dict: 'licenseJurisdiction': JsonSchema( type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions') ), - 'privilegeJurisdictions': JsonSchema( - type=JsonSchemaType.ARRAY, - items=JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions')), - ), 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), } @@ -1452,66 +1418,6 @@ def public_query_providers_response_model(self) -> Model: ) return self.api._v1_public_query_providers_response_model - @property - def privilege_history_response_model(self) -> Model: - """Return the privilege history response model, which should only be created once per API""" - if hasattr(self.api, '_v1_privilege_history_response_model'): - return self.api._v1_privilege_history_response_model - - self.api._v1_privilege_history_response_model = self.api.add_model( - 'V1PrivilegeHistoryResponseModel', - description='Privilege history response model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - required=[ - 'providerId', - 'compact', - 'jurisdiction', - 'licenseType', - 'privilegeId', - 'events', - ], - properties={ - 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), - 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts')), - 'jurisdiction': JsonSchema( - type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions') - ), - 'licenseType': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.license_type_names), - 'privilegeId': JsonSchema(type=JsonSchemaType.STRING), - 'events': JsonSchema( - type=JsonSchemaType.ARRAY, - items=JsonSchema( - type=JsonSchemaType.OBJECT, - required=[ - 'type', - 'updateType', - 'dateOfUpdate', - 'effectiveDate', - 'createDate', - ], - properties={ - 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['privilegeUpdate']), - 'updateType': self._update_type_schema, - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), - 'effectiveDate': JsonSchema( - type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT - ), - 'createDate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), - 'note': JsonSchema(type=JsonSchemaType.STRING), - 'npdbCategories': JsonSchema( - type=JsonSchemaType.ARRAY, - description='The categories of clinical privilege action for encumbrance events', - items=JsonSchema(type=JsonSchemaType.STRING), - ), - }, - ), - ), - }, - ), - ) - return self.api._v1_privilege_history_response_model - @property def public_provider_response_model(self) -> Model: """Return the public provider response model, which should only be created once per API""" @@ -1562,7 +1468,6 @@ def _public_provider_detailed_response_schema(self): 'licenseJurisdiction', 'givenName', 'familyName', - 'privilegeJurisdictions', ], properties={ 'privileges': JsonSchema( @@ -1834,7 +1739,6 @@ def _public_providers_response_schema(self): 'familyName', 'compact', 'licenseJurisdiction', - 'privilegeJurisdictions', ], properties=self._common_public_provider_properties, ) @@ -1846,17 +1750,12 @@ def _common_public_provider_properties(self) -> dict: return { 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['provider']), 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'familyName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'suffix': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=stack.node.get_context('compacts')), 'licenseJurisdiction': JsonSchema(type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions')), - 'privilegeJurisdictions': JsonSchema( - type=JsonSchemaType.ARRAY, - items=JsonSchema(type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions')), - ), 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date-time'), } diff --git a/backend/cosmetology-app/stacks/api_stack/v1_api/provider_management.py b/backend/cosmetology-app/stacks/api_stack/v1_api/provider_management.py index df50ec15e..ee13a71d0 100644 --- a/backend/cosmetology-app/stacks/api_stack/v1_api/provider_management.py +++ b/backend/cosmetology-app/stacks/api_stack/v1_api/provider_management.py @@ -30,7 +30,6 @@ def __init__( admin_method_options: MethodOptions, ssn_method_options: MethodOptions, api_model: ApiModel, - privilege_history_function: PythonFunction, api_lambda_stack: ApiLambdaStack, ): super().__init__() @@ -69,10 +68,6 @@ def __init__( method_options=ssn_method_options, get_provider_ssn_handler=api_lambda_stack.provider_management_lambdas.get_provider_ssn_handler, ) - self._add_deactivate_privilege( - method_options=admin_method_options, - deactivate_privilege_handler=api_lambda_stack.provider_management_lambdas.deactivate_privilege_handler, - ) self._add_encumber_privilege( method_options=admin_method_options, @@ -94,11 +89,6 @@ def __init__( investigation_handler=api_lambda_stack.provider_management_lambdas.provider_investigation_handler, ) - self._add_get_privilege_history( - method_options=method_options, - privilege_history_function=privilege_history_function, - ) - def _add_get_provider( self, method_options: MethodOptions, @@ -194,56 +184,6 @@ def _add_get_provider_ssn( ) self.ssn_api_throttling_alarm.add_alarm_action(SnsAction(self.api.alarm_topic)) - def _add_deactivate_privilege( - self, - method_options: MethodOptions, - deactivate_privilege_handler: PythonFunction, - ): - """Add POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} - /licenseType/{licenseType}/deactivate endpoint.""" - deactivate_resource = self.privilege_jurisdiction_license_type_resource.add_resource('deactivate') - - # Create a metric to track privilege deactivation notification failures - privilege_deactivation_notification_failed_metric = Metric( - namespace='compact-connect', - metric_name='privilege-deactivation-notification-failed', - statistic='Sum', - period=Duration.minutes(5), - dimensions_map={'service': 'common'}, - ) - - # Create an alarm that will fire if any privilege deactivation notification fails - self.privilege_deactivation_notification_failed_alarm = Alarm( - self.api, - 'PrivilegeDeactivationNotificationFailedAlarm', - metric=privilege_deactivation_notification_failed_metric, - threshold=1, - evaluation_periods=1, - comparison_operator=ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - treat_missing_data=TreatMissingData.NOT_BREACHING, - alarm_description=f'{self.api.node.path} Privilege deactivation notification failed. ' - f'One or more notifications to providers or jurisdictions failed to send during privilege deactivation. ' - f'Investigation required to ensure all parties have been properly notified.', - ) - self.privilege_deactivation_notification_failed_alarm.add_alarm_action(SnsAction(self.api.alarm_topic)) - - deactivate_resource.add_method( - 'POST', - request_validator=self.api.parameter_body_validator, - request_models={'application/json': self.api_model.post_privilege_deactivation_request_model}, - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.message_response_model}, - ), - ], - integration=LambdaIntegration(deactivate_privilege_handler, timeout=Duration.seconds(29)), - request_parameters={'method.request.header.Authorization': True}, - authorization_type=method_options.authorization_type, - authorizer=method_options.authorizer, - authorization_scopes=method_options.authorization_scopes, - ) - def _add_encumber_privilege( self, method_options: MethodOptions, @@ -427,25 +367,3 @@ def _add_investigation_license( authorizer=method_options.authorizer, authorization_scopes=method_options.authorization_scopes, ) - - def _add_get_privilege_history( - self, - method_options: MethodOptions, - privilege_history_function: PythonFunction, - ): - self.privilege_history_resource = self.privilege_jurisdiction_license_type_resource.add_resource('history') - - self.privilege_history_resource.add_method( - 'GET', - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.privilege_history_response_model}, - ), - ], - integration=LambdaIntegration(privilege_history_function, timeout=Duration.seconds(29)), - request_parameters={'method.request.header.Authorization': True}, - authorization_type=method_options.authorization_type, - authorizer=method_options.authorizer, - authorization_scopes=method_options.authorization_scopes, - ) diff --git a/backend/cosmetology-app/stacks/api_stack/v1_api/public_lookup_api.py b/backend/cosmetology-app/stacks/api_stack/v1_api/public_lookup_api.py index aed3b3686..dc335b2f4 100644 --- a/backend/cosmetology-app/stacks/api_stack/v1_api/public_lookup_api.py +++ b/backend/cosmetology-app/stacks/api_stack/v1_api/public_lookup_api.py @@ -2,7 +2,6 @@ from aws_cdk import Duration from aws_cdk.aws_apigateway import LambdaIntegration, MethodResponse, Resource -from aws_cdk.aws_lambda import IFunction from cdk_nag import NagSuppressions from common_constructs.cc_api import CCApi @@ -18,7 +17,6 @@ def __init__( resource: Resource, api_model: ApiModel, api_lambda_stack: ApiLambdaStack, - privilege_history_function: IFunction, ): super().__init__() @@ -40,9 +38,6 @@ def __init__( self._add_public_get_provider( api_lambda_stack=api_lambda_stack, ) - self._add_public_get_privilege_history( - privilege_history_function=privilege_history_function, - ) def _add_public_get_provider( self, @@ -114,36 +109,3 @@ def _add_public_query_providers( }, ], ) - - def _add_public_get_privilege_history( - self, - privilege_history_function: IFunction, - ): - self.privilege_history_resource = self.provider_jurisdiction_license_type_resource.add_resource('history') - - public_get_privilege_history_method = self.privilege_history_resource.add_method( - 'GET', - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.privilege_history_response_model}, - ), - ], - integration=LambdaIntegration(privilege_history_function, timeout=Duration.seconds(29)), - ) - - # Add suppressions for the public GET endpoint - NagSuppressions.add_resource_suppressions( - public_get_privilege_history_method, - suppressions=[ - { - 'id': 'AwsSolutions-APIG4', - 'reason': 'This is a public endpoint that intentionally does not require authorization', - }, - { - 'id': 'AwsSolutions-COG4', - 'reason': 'This is a public endpoint that intentionally ' - 'does not use a Cognito user pool authorizer', - }, - ], - ) diff --git a/backend/cosmetology-app/stacks/notification_stack.py b/backend/cosmetology-app/stacks/notification_stack.py index fdd9cac90..9394009b1 100644 --- a/backend/cosmetology-app/stacks/notification_stack.py +++ b/backend/cosmetology-app/stacks/notification_stack.py @@ -3,17 +3,13 @@ import os from aws_cdk import Duration -from aws_cdk.aws_cloudwatch import Alarm, ComparisonOperator, Metric, Stats, TreatMissingData -from aws_cdk.aws_cloudwatch_actions import SnsAction -from aws_cdk.aws_events import EventBus, EventPattern, IEventBus, Rule -from aws_cdk.aws_events_targets import SqsQueue +from aws_cdk.aws_events import EventBus from cdk_nag import NagSuppressions from common_constructs.stack import AppStack from constructs import Construct from common_constructs.python_function import PythonFunction from common_constructs.queue_event_listener import QueueEventListener -from common_constructs.queued_lambda_processor import QueuedLambdaProcessor from common_constructs.ssm_parameter_utility import SSMParameterUtility from stacks import event_state_stack as ess from stacks import persistent_stack as ps @@ -42,7 +38,6 @@ def __init__( data_event_bus = SSMParameterUtility.load_data_event_bus_from_ssm_parameter(self) self.event_processors = {} self.event_state_stack = event_state_stack - self._add_privilege_purchase_notification_chain(persistent_stack, data_event_bus) self._add_license_encumbrance_notification_listener( persistent_stack=persistent_stack, data_event_bus=data_event_bus, event_state_stack=event_state_stack ) @@ -68,107 +63,6 @@ def __init__( persistent_stack=persistent_stack, data_event_bus=data_event_bus, event_state_stack=event_state_stack ) - def _add_privilege_purchase_notification_chain( - self, persistent_stack: ps.PersistentStack, data_event_bus: IEventBus - ): - """Add the privilege purchase notification lambda and event rules.""" - # Create the Lambda function handler for privilege purchase messages - privilege_purchase_notification_handler = PythonFunction( - self, - 'PrivilegePurchaseHandler', - description='Privilege purchase notification handler', - lambda_dir='provider-data-v1', - index=os.path.join('handlers', 'privileges.py'), - handler='privilege_purchase_message_handler', - timeout=Duration.minutes(1), - environment={ - 'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name, - 'EMAIL_NOTIFICATION_SERVICE_LAMBDA_NAME': persistent_stack.email_notification_service_lambda.function_name, # noqa: E501 line-too-long - **self.common_env_vars, - }, - alarm_topic=persistent_stack.alarm_topic, - ) - - # Grant necessary permissions - persistent_stack.provider_table.grant_read_data(privilege_purchase_notification_handler) - persistent_stack.email_notification_service_lambda.grant_invoke(privilege_purchase_notification_handler) - - NagSuppressions.add_resource_suppressions_by_path( - self, - f'{privilege_purchase_notification_handler.role.node.path}/DefaultPolicy/Resource', - suppressions=[ - { - 'id': 'AwsSolutions-IAM5', - 'reason': """ - This policy contains wild-carded actions and resources but they are scoped to the - specific actions, KMS key, Table, and Email Identity that this lambda specifically needs access to. - """, - }, - ], - ) - - # Add specific error alarm for this handler - Alarm( - self, - 'PrivilegePurchaseHandlerFailureAlarm', - metric=privilege_purchase_notification_handler.metric_errors(statistic=Stats.SUM), - evaluation_periods=1, - threshold=1, - actions_enabled=True, - alarm_description=f'{privilege_purchase_notification_handler.node.path} failed to process a message batch', - comparison_operator=ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - treat_missing_data=TreatMissingData.NOT_BREACHING, - ).add_alarm_action(SnsAction(persistent_stack.alarm_topic)) - - # Create the QueuedLambdaProcessor - self.privilege_purchase_processor = QueuedLambdaProcessor( - self, - 'PrivilegePurchase', - process_function=privilege_purchase_notification_handler, - visibility_timeout=Duration.minutes(5), - retention_period=Duration.hours(12), - max_batching_window=Duration.seconds(15), - max_receive_count=3, - batch_size=10, - encryption_key=persistent_stack.shared_encryption_key, - alarm_topic=persistent_stack.alarm_topic, - # We want to be aware if any communications failed to send, so we'll set this threshold to 1 - dlq_count_alarm_threshold=1, - ) - - # Create rule to route privilege.purchase events to the SQS queue - self.privilege_purchase_rule = Rule( - self, - 'PrivilegePurchaseEventRule', - event_bus=data_event_bus, - event_pattern=EventPattern(detail_type=['privilege.purchase']), - targets=[ - SqsQueue( - self.privilege_purchase_processor.queue, dead_letter_queue=self.privilege_purchase_processor.dlq - ) - ], - ) - - # Create an alarm for rule delivery failures - Alarm( - self, - 'PrivilegePurchaseRuleFailedInvocations', - metric=Metric( - namespace='AWS/Events', - metric_name='FailedInvocations', - dimensions_map={ - 'EventBusName': data_event_bus.event_bus_name, - 'RuleName': self.privilege_purchase_rule.rule_name, - }, - period=Duration.minutes(5), - statistic='Sum', - ), - evaluation_periods=1, - threshold=1, - comparison_operator=ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - treat_missing_data=TreatMissingData.NOT_BREACHING, - ).add_alarm_action(SnsAction(persistent_stack.alarm_topic)) - def _add_emailer_event_listener( self, construct_id_prefix: str, diff --git a/backend/cosmetology-app/stacks/search_api_stack/v1_api/api.py b/backend/cosmetology-app/stacks/search_api_stack/v1_api/api.py index e4be20e89..72f6e63b5 100644 --- a/backend/cosmetology-app/stacks/search_api_stack/v1_api/api.py +++ b/backend/cosmetology-app/stacks/search_api_stack/v1_api/api.py @@ -3,7 +3,6 @@ from aws_cdk.aws_apigateway import AuthorizationType, IResource, MethodOptions from stacks import persistent_stack, search_persistent_stack -from stacks.search_api_stack.v1_api.privilege_search import PrivilegeSearch from stacks.search_api_stack.v1_api.provider_search import ProviderSearch from .api_model import ApiModel @@ -53,12 +52,3 @@ def __init__( search_persistent_stack=search_persistent_stack, api_model=self.api_model, ) - - # POST /v1/compacts/{compact}/privileges - privileges_resource = self.compact_resource.add_resource('privileges') - self.privilege_search = PrivilegeSearch( - resource=privileges_resource, - method_options=read_auth_method_options, - search_persistent_stack=search_persistent_stack, - api_model=self.api_model, - ) diff --git a/backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py b/backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py index 6c11c0c3d..8cdee024b 100644 --- a/backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py +++ b/backend/cosmetology-app/stacks/search_api_stack/v1_api/api_model.py @@ -73,43 +73,6 @@ def search_providers_request_model(self) -> Model: ) return self.api._v1_search_providers_request_model - @property - def _export_privileges_request_schema(self) -> JsonSchema: - """ - Return the export privileges request schema. - - This schema is similar to the search request schema but without pagination parameters. - The export endpoint does not support pagination - it returns all results as a CSV file. - """ - return JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - required=['query'], - properties={ - 'query': JsonSchema( - type=JsonSchemaType.OBJECT, - description='The OpenSearch query body', - ), - }, - ) - - @property - def search_privileges_request_model(self) -> Model: - """ - Return the export privileges request model, which should only be created once per API. - - This model is used for the privilege export endpoint and does not include - pagination parameters (size, from, search_after). - """ - if hasattr(self.api, '_v1_search_privileges_request_model'): - return self.api._v1_search_privileges_request_model - self.api._v1_search_privileges_request_model = self.api.add_model( - 'V1ExportPrivilegesRequestModel', - description='Export privileges request model - query only, no pagination', - schema=self._export_privileges_request_schema, - ) - return self.api._v1_search_privileges_request_model - @property def _search_response_total_schema(self) -> JsonSchema: """Return the common total hits schema used by search response models""" @@ -148,27 +111,6 @@ def search_providers_response_model(self) -> Model: ) return self.api._v1_search_providers_response_model - @property - def search_privileges_response_model(self) -> Model: - """Return the export privileges response model, which should only be created once per API""" - if hasattr(self.api, '_v1_search_privileges_response_model'): - return self.api._v1_search_privileges_response_model - self.api._v1_search_privileges_response_model = self.api.add_model( - 'V1ExportPrivilegesResponseModel', - description='Export privileges response model with presigned URL to CSV file', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - required=['fileUrl'], - properties={ - 'fileUrl': JsonSchema( - type=JsonSchemaType.STRING, - description='Presigned URL to download the CSV file containing the export results', - ), - }, - ), - ) - return self.api._v1_search_privileges_response_model - @property def _providers_response_schema(self): stack: AppStack = AppStack.of(self.api) @@ -186,7 +128,6 @@ def _providers_response_schema(self): 'jurisdictionUploadedCompactEligibility', 'compact', 'licenseJurisdiction', - 'privilegeJurisdictions', 'dateOfUpdate', 'dateOfExpiration', 'birthMonthDay', @@ -213,10 +154,6 @@ def _providers_response_schema(self): type=JsonSchemaType.STRING, max_length=100, ), - 'npi': JsonSchema( - type=JsonSchemaType.STRING, - pattern='^[0-9]{10}$', - ), 'licenseStatus': JsonSchema( type=JsonSchemaType.STRING, enum=['active', 'inactive'], @@ -245,13 +182,6 @@ def _providers_response_schema(self): type=JsonSchemaType.STRING, enum=stack.node.get_context('jurisdictions'), ), - 'privilegeJurisdictions': JsonSchema( - type=JsonSchemaType.ARRAY, - items=JsonSchema( - type=JsonSchemaType.STRING, - enum=stack.node.get_context('jurisdictions'), - ), - ), 'dateOfUpdate': JsonSchema( type=JsonSchemaType.STRING, format='date-time', @@ -308,6 +238,7 @@ def _license_general_response_schema(self): 'homeAddressCity', 'homeAddressState', 'homeAddressPostalCode', + 'licenseNumber', ], properties={ 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), @@ -325,8 +256,7 @@ def _license_general_response_schema(self): 'jurisdictionUploadedCompactEligibility': JsonSchema( type=JsonSchemaType.STRING, enum=['eligible', 'ineligible'] ), - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), - 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, max_length=100), + 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'givenName': JsonSchema(type=JsonSchemaType.STRING, max_length=100), 'middleName': JsonSchema(type=JsonSchemaType.STRING, max_length=100), 'familyName': JsonSchema(type=JsonSchemaType.STRING, max_length=100), @@ -391,7 +321,6 @@ def _privilege_general_response_schema(self): 'compactTransactionId': JsonSchema(type=JsonSchemaType.STRING), 'privilegeId': JsonSchema(type=JsonSchemaType.STRING), 'status': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), - 'activeSince': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), 'investigationStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['underInvestigation']), }, ) diff --git a/backend/cosmetology-app/stacks/search_api_stack/v1_api/privilege_search.py b/backend/cosmetology-app/stacks/search_api_stack/v1_api/privilege_search.py deleted file mode 100644 index d37dcc9b2..000000000 --- a/backend/cosmetology-app/stacks/search_api_stack/v1_api/privilege_search.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import annotations - -from aws_cdk import Duration -from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource - -from common_constructs.cc_api import CCApi -from stacks import search_persistent_stack - -from .api_model import ApiModel - - -class PrivilegeSearch: - """ - Endpoint related to privilege searching in the OpenSearch domain. - """ - - def __init__( - self, - *, - resource: Resource, - method_options: MethodOptions, - search_persistent_stack: search_persistent_stack.SearchPersistentStack, - api_model: ApiModel, - ): - super().__init__() - - self.resource = resource - self.api: CCApi = resource.api - self.api_model = api_model - - self._add_export_privileges( - method_options=method_options, - search_persistent_stack=search_persistent_stack, - ) - - def _add_export_privileges( - self, - method_options: MethodOptions, - search_persistent_stack: search_persistent_stack.SearchPersistentStack, - ): - export_resource = self.resource.add_resource('export') - - # Get the search handler from the search persistent stack (same handler as provider search) - handler = search_persistent_stack.search_handler.handler - - self.privilege_search_export_endpoint = export_resource.add_method( - 'POST', - request_validator=self.api.parameter_body_validator, - request_models={'application/json': self.api_model.search_privileges_request_model}, - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.search_privileges_response_model}, - ), - ], - integration=LambdaIntegration(handler, timeout=Duration.seconds(29)), - request_parameters={'method.request.header.Authorization': True}, - authorization_type=method_options.authorization_type, - authorizer=method_options.authorizer, - authorization_scopes=method_options.authorization_scopes, - ) diff --git a/backend/cosmetology-app/stacks/state_api_stack/v1_api/api.py b/backend/cosmetology-app/stacks/state_api_stack/v1_api/api.py index 6a5260bc2..6b0e43092 100644 --- a/backend/cosmetology-app/stacks/state_api_stack/v1_api/api.py +++ b/backend/cosmetology-app/stacks/state_api_stack/v1_api/api.py @@ -4,7 +4,6 @@ from stacks import persistent_stack as ps from stacks.state_api_stack.v1_api.bulk_upload_url import BulkUploadUrl -from stacks.state_api_stack.v1_api.provider_management import ProviderManagement from .api_model import ApiModel from .post_licenses import PostLicenses @@ -23,13 +22,9 @@ def __init__(self, root: IResource, persistent_stack: ps.PersistentStack): self.api_model = ApiModel(api=self.api) _active_compacts = persistent_stack.get_list_of_compact_abbreviations() - read_scopes = [] write_scopes = [] # set the compact level scopes for compact in _active_compacts: - # We only set the readGeneral permission scope at the compact level, since users with any permissions - # within a compact are implicitly granted this scope - read_scopes.append(f'{compact}/readGeneral') write_scopes.append(f'{compact}/write') _active_compact_jurisdictions = persistent_stack.get_list_of_active_jurisdictions_for_compact_environment( @@ -42,11 +37,6 @@ def __init__(self, root: IResource, persistent_stack: ps.PersistentStack): for jurisdiction in _active_compact_jurisdictions: write_scopes.append(f'{jurisdiction}/{compact}.write') - read_auth_method_options = MethodOptions( - authorization_type=AuthorizationType.COGNITO, - authorizer=self.api.state_auth_authorizer, - authorization_scopes=read_scopes, - ) write_auth_method_options = MethodOptions( authorization_type=AuthorizationType.COGNITO, authorizer=self.api.state_auth_authorizer, @@ -63,16 +53,6 @@ def __init__(self, root: IResource, persistent_stack: ps.PersistentStack): # /v1/compacts/{compact}/jurisdictions/{jurisdiction} self.compact_jurisdiction_resource = self.compact_jurisdictions_resource.add_resource('{jurisdiction}') - # GET /v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers/{providerId} - # POST /v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers/query - providers_resource = self.compact_jurisdiction_resource.add_resource('providers') - self.provider_management = ProviderManagement( - resource=providers_resource, - method_options=read_auth_method_options, - persistent_stack=persistent_stack, - api_model=self.api_model, - ) - # POST /v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses # GET /v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses/bulk-upload licenses_resource = self.compact_jurisdiction_resource.add_resource('licenses') diff --git a/backend/cosmetology-app/stacks/state_api_stack/v1_api/api_model.py b/backend/cosmetology-app/stacks/state_api_stack/v1_api/api_model.py index d0ca9992f..de39b6c9f 100644 --- a/backend/cosmetology-app/stacks/state_api_stack/v1_api/api_model.py +++ b/backend/cosmetology-app/stacks/state_api_stack/v1_api/api_model.py @@ -71,76 +71,6 @@ def post_licenses_error_response_model(self) -> Model: ) return self.api._v1_post_licenses_response_model - @property - def query_providers_request_model(self) -> Model: - """Return the query providers request model, which should only be created once per API""" - if hasattr(self.api, '_v1_query_providers_request_model'): - return self.api._v1_query_providers_request_model - self.api._v1_query_providers_request_model = self.api.add_model( - 'V1QueryProvidersRequestModel', - description='Query providers request model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - required=['query'], - properties={ - 'query': JsonSchema( - type=JsonSchemaType.OBJECT, - description='The query parameters', - required=['startDateTime', 'endDateTime'], - additional_properties=False, - properties={ - 'startDateTime': JsonSchema( - type=JsonSchemaType.STRING, format='date-time', pattern=cc_api.ISO8601_DATETIME_FORMAT - ), - 'endDateTime': JsonSchema( - type=JsonSchemaType.STRING, format='date-time', pattern=cc_api.ISO8601_DATETIME_FORMAT - ), - }, - ), - 'pagination': self._pagination_request_schema, - 'sorting': self._sorting_schema, - }, - ), - ) - return self.api._v1_query_providers_request_model - - @property - def query_providers_response_model(self) -> Model: - """Return the query providers response model, which should only be created once per API""" - if hasattr(self.api, '_v1_query_providers_response_model'): - return self.api._v1_query_providers_response_model - self.api._v1_query_providers_response_model = self.api.add_model( - 'V1QueryProvidersResponseModel', - description='Query providers response model', - schema=JsonSchema( - type=JsonSchemaType.OBJECT, - required=['providers', 'pagination'], - properties={ - 'providers': JsonSchema( - type=JsonSchemaType.ARRAY, - max_items=100, - items=self._providers_response_schema, - ), - 'pagination': self._pagination_response_schema, - 'sorting': self._sorting_schema, - }, - ), - ) - return self.api._v1_query_providers_response_model - - @property - def provider_response_model(self) -> Model: - """Return the provider response model, which should only be created once per API""" - if hasattr(self.api, '_v1_get_provider_response_model'): - return self.api._v1_get_provider_response_model - self.api._v1_get_provider_response_model = self.api.add_model( - 'V1GetProviderResponseModel', - description='Get provider response model', - schema=self._provider_detail_response_schema, - ) - return self.api._v1_get_provider_response_model - @property def bulk_upload_response_model(self) -> Model: """Return the Bulk Upload Response Model, which should only be created once per API""" @@ -186,6 +116,7 @@ def post_license_model(self) -> Model: type=JsonSchemaType.OBJECT, required=[ 'ssn', + 'licenseNumber', 'givenName', 'familyName', 'dateOfBirth', @@ -217,7 +148,6 @@ def post_license_model(self) -> Model: @property def _common_license_properties(self) -> dict: return { - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), @@ -238,187 +168,3 @@ def _common_license_properties(self) -> dict: 'phoneNumber': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.PHONE_NUMBER_FORMAT), 'suffix': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), } - - @property - def _providers_response_schema(self): - return JsonSchema( - type=JsonSchemaType.OBJECT, - required=[ - 'type', - 'providerId', - 'givenName', - 'familyName', - 'licenseStatus', - 'compactEligibility', - 'jurisdictionUploadedLicenseStatus', - 'jurisdictionUploadedCompactEligibility', - 'compact', - 'licenseJurisdiction', - 'privilegeJurisdictions', - 'dateOfUpdate', - 'dateOfExpiration', - 'birthMonthDay', - ], - properties={ - 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['provider']), - 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), - 'ssnLastFour': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{4}$'), - 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'familyName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'suffix': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'licenseStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), - 'compactEligibility': JsonSchema(type=JsonSchemaType.STRING, enum=['eligible', 'ineligible']), - 'jurisdictionUploadedLicenseStatus': JsonSchema( - type=JsonSchemaType.STRING, enum=['active', 'inactive'] - ), - 'jurisdictionUploadedCompactEligibility': JsonSchema( - type=JsonSchemaType.STRING, enum=['eligible', 'ineligible'] - ), - 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts')), - 'birthMonthDay': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.MD_FORMAT), - 'dateOfBirth': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfExpiration': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'licenseJurisdiction': JsonSchema( - type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions') - ), - 'privilegeJurisdictions': JsonSchema( - type=JsonSchemaType.ARRAY, - items=JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions')), - ), - 'compactConnectRegisteredEmailAddress': JsonSchema( - type=JsonSchemaType.STRING, - format='email', - min_length=5, - max_length=100, - ), - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - }, - ) - - @property - def _provider_detail_response_schema(self): - return JsonSchema( - type=JsonSchemaType.OBJECT, - required=['privileges', 'providerUIUrl'], - properties={ - 'privileges': JsonSchema( - type=JsonSchemaType.ARRAY, - items=self._state_privilege_schema, - ), - 'providerUIUrl': JsonSchema( - type=JsonSchemaType.STRING, - description='URL to the provider UI page', - format='uri', - ), - }, - ) - - @property - def _state_privilege_schema(self): - """Schema for flattened state privilege responses""" - return JsonSchema( - type=JsonSchemaType.OBJECT, - required=[ - 'type', - 'providerId', - 'compact', - 'jurisdiction', - 'licenseType', - 'privilegeId', - 'status', - 'compactEligibility', - 'dateOfExpiration', - 'dateOfIssuance', - 'dateOfRenewal', - 'dateOfUpdate', - 'familyName', - 'givenName', - 'licenseJurisdiction', - 'licenseStatus', - ], - properties={ - 'type': JsonSchema(type=JsonSchemaType.STRING, enum=['statePrivilege']), - 'providerId': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.UUID4_FORMAT), - 'compact': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.node.get_context('compacts')), - 'jurisdiction': JsonSchema( - type=JsonSchemaType.STRING, - enum=self.stack.node.get_context('jurisdictions'), - ), - 'licenseType': JsonSchema(type=JsonSchemaType.STRING, enum=self.stack.license_type_names), - 'privilegeId': JsonSchema(type=JsonSchemaType.STRING), - 'licenseNumber': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'npi': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{10}$'), - 'status': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), - 'compactEligibility': JsonSchema(type=JsonSchemaType.STRING, enum=['eligible', 'ineligible']), - 'ssnLastFour': JsonSchema(type=JsonSchemaType.STRING, pattern='^[0-9]{4}$'), - 'emailAddress': JsonSchema(type=JsonSchemaType.STRING, format='email', min_length=5, max_length=100), - 'compactConnectRegisteredEmailAddress': JsonSchema( - type=JsonSchemaType.STRING, - format='email', - min_length=5, - max_length=100, - ), - 'dateOfBirth': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfExpiration': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfIssuance': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfRenewal': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'dateOfUpdate': JsonSchema(type=JsonSchemaType.STRING, format='date', pattern=cc_api.YMD_FORMAT), - 'familyName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'middleName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'givenName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'suffix': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'homeAddressStreet1': JsonSchema(type=JsonSchemaType.STRING, min_length=2, max_length=100), - 'homeAddressStreet2': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'homeAddressCity': JsonSchema(type=JsonSchemaType.STRING, min_length=2, max_length=100), - 'homeAddressState': JsonSchema(type=JsonSchemaType.STRING, min_length=2, max_length=100), - 'homeAddressPostalCode': JsonSchema(type=JsonSchemaType.STRING, min_length=5, max_length=7), - 'licenseJurisdiction': JsonSchema( - type=JsonSchemaType.STRING, enum=self.stack.node.get_context('jurisdictions') - ), - 'licenseStatus': JsonSchema(type=JsonSchemaType.STRING, enum=['active', 'inactive']), - 'licenseStatusName': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=100), - 'phoneNumber': JsonSchema(type=JsonSchemaType.STRING, pattern=cc_api.PHONE_NUMBER_FORMAT), - }, - ) - - @property - def _sorting_schema(self): - return JsonSchema( - type=JsonSchemaType.OBJECT, - description='How to sort results', - properties={ - 'direction': JsonSchema( - type=JsonSchemaType.STRING, - description='Direction to sort results by', - enum=['ascending', 'descending'], - ), - }, - ) - - @property - def _pagination_request_schema(self): - return JsonSchema( - type=JsonSchemaType.OBJECT, - additional_properties=False, - properties={ - 'lastKey': JsonSchema(type=JsonSchemaType.STRING, min_length=1, max_length=1024), - 'pageSize': JsonSchema(type=JsonSchemaType.INTEGER, minimum=5, maximum=100), - }, - ) - - @property - def _pagination_response_schema(self): - return JsonSchema( - type=JsonSchemaType.OBJECT, - properties={ - 'lastKey': JsonSchema(type=[JsonSchemaType.STRING, JsonSchemaType.NULL], min_length=1, max_length=1024), - 'prevLastKey': JsonSchema( - type=[JsonSchemaType.STRING, JsonSchemaType.NULL], - min_length=1, - max_length=1024, - ), - 'pageSize': JsonSchema(type=JsonSchemaType.INTEGER, minimum=5, maximum=100), - }, - ) diff --git a/backend/cosmetology-app/stacks/state_api_stack/v1_api/provider_management.py b/backend/cosmetology-app/stacks/state_api_stack/v1_api/provider_management.py deleted file mode 100644 index 6472aa071..000000000 --- a/backend/cosmetology-app/stacks/state_api_stack/v1_api/provider_management.py +++ /dev/null @@ -1,221 +0,0 @@ -from __future__ import annotations - -import os - -from aws_cdk import Duration -from aws_cdk.aws_apigateway import LambdaIntegration, MethodOptions, MethodResponse, Resource -from aws_cdk.aws_dynamodb import ITable -from aws_cdk.aws_kms import IKey -from cdk_nag import NagSuppressions -from common_constructs.stack import Stack - -from common_constructs.cc_api import CCApi -from common_constructs.python_function import PythonFunction -from stacks import persistent_stack as ps -from stacks.persistent_stack import ProviderTable - -from .api_model import ApiModel - - -class ProviderManagement: - """ - These endpoints are used by state IT systems to view provider records - """ - - def __init__( - self, - *, - resource: Resource, - method_options: MethodOptions, - persistent_stack: ps.PersistentStack, - api_model: ApiModel, - ): - super().__init__() - - self.resource = resource - self.api: CCApi = resource.api - self.api_model = api_model - - stack: Stack = Stack.of(resource) - - lambda_environment = { - 'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name, - 'PROV_FAM_GIV_MID_INDEX_NAME': persistent_stack.provider_table.provider_fam_giv_mid_index_name, - 'PROV_DATE_OF_UPDATE_INDEX_NAME': persistent_stack.provider_table.provider_date_of_update_index_name, - 'COMPACT_CONFIGURATION_TABLE_NAME': persistent_stack.compact_configuration_table.table_name, - 'RATE_LIMITING_TABLE_NAME': persistent_stack.rate_limiting_table.table_name, - # Default to test environment if no UI domain name is set - 'API_BASE_URL': f'https://{stack.ui_domain_name}' - if stack.ui_domain_name is not None - else 'https://app.test.compactconnect.org', - **stack.common_env_vars, - } - - # Create the nested resources used by endpoints - self.provider_resource = self.resource.add_resource('{providerId}') - - self._add_query_jurisdiction_providers( - method_options=method_options, - data_encryption_key=persistent_stack.shared_encryption_key, - provider_data_table=persistent_stack.provider_table, - compact_configuration_table=persistent_stack.compact_configuration_table, - rate_limiting_table=persistent_stack.rate_limiting_table, - lambda_environment=lambda_environment, - ) - self._add_get_provider( - method_options=method_options, - data_encryption_key=persistent_stack.shared_encryption_key, - provider_data_table=persistent_stack.provider_table, - compact_configuration_table=persistent_stack.compact_configuration_table, - rate_limiting_table=persistent_stack.rate_limiting_table, - lambda_environment=lambda_environment, - ) - - def _add_get_provider( - self, - method_options: MethodOptions, - data_encryption_key: IKey, - provider_data_table: ProviderTable, - compact_configuration_table: ITable, - rate_limiting_table: ITable, - lambda_environment: dict, - ): - self.get_provider_handler = self._get_provider_handler( - data_encryption_key=data_encryption_key, - provider_data_table=provider_data_table, - compact_configuration_table=compact_configuration_table, - rate_limiting_table=rate_limiting_table, - lambda_environment=lambda_environment, - ) - self.api.log_groups.append(self.get_provider_handler.log_group) - - self.provider_resource.add_method( - 'GET', - request_validator=self.api.parameter_body_validator, - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.provider_response_model}, - ), - ], - integration=LambdaIntegration(self.get_provider_handler, timeout=Duration.seconds(29)), - request_parameters={'method.request.header.Authorization': True}, - authorization_type=method_options.authorization_type, - authorizer=method_options.authorizer, - authorization_scopes=method_options.authorization_scopes, - ) - - def _add_query_jurisdiction_providers( - self, - method_options: MethodOptions, - data_encryption_key: IKey, - provider_data_table: ProviderTable, - compact_configuration_table: ITable, - rate_limiting_table: ITable, - lambda_environment: dict, - ): - query_resource = self.resource.add_resource('query') - - handler = self._query_jurisdiction_providers_handler( - data_encryption_key=data_encryption_key, - provider_data_table=provider_data_table, - compact_configuration_table=compact_configuration_table, - rate_limiting_table=rate_limiting_table, - lambda_environment=lambda_environment, - ) - self.api.log_groups.append(handler.log_group) - - query_resource.add_method( - 'POST', - request_validator=self.api.parameter_body_validator, - request_models={'application/json': self.api_model.query_providers_request_model}, - method_responses=[ - MethodResponse( - status_code='200', - response_models={'application/json': self.api_model.query_providers_response_model}, - ), - ], - integration=LambdaIntegration(handler, timeout=Duration.seconds(29)), - request_parameters={'method.request.header.Authorization': True}, - authorization_type=method_options.authorization_type, - authorizer=method_options.authorizer, - authorization_scopes=method_options.authorization_scopes, - ) - - def _get_provider_handler( - self, - data_encryption_key: IKey, - provider_data_table: ProviderTable, - compact_configuration_table: ITable, - rate_limiting_table: ITable, - lambda_environment: dict, - ) -> PythonFunction: - stack = Stack.of(self.resource) - handler = PythonFunction( - self.resource, - 'GetProviderHandler', - description='Get provider handler', - lambda_dir='provider-data-v1', - index=os.path.join('handlers', 'state_api.py'), - handler='get_provider', - environment=lambda_environment, - alarm_topic=self.api.alarm_topic, - ) - data_encryption_key.grant_decrypt(handler) - provider_data_table.grant_read_data(handler) - compact_configuration_table.grant_read_data(handler) - rate_limiting_table.grant_read_write_data(handler) - - NagSuppressions.add_resource_suppressions_by_path( - stack, - path=f'{handler.role.node.path}/DefaultPolicy/Resource', - suppressions=[ - { - 'id': 'AwsSolutions-IAM5', - 'reason': 'The actions in this policy are specifically what this lambda needs to read ' - 'and is scoped to one table and encryption key.', - }, - ], - ) - return handler - - def _query_jurisdiction_providers_handler( - self, - data_encryption_key: IKey, - provider_data_table: ProviderTable, - compact_configuration_table: ITable, - rate_limiting_table: ITable, - lambda_environment: dict, - ) -> PythonFunction: - self.query_jurisdiction_providers_handler = PythonFunction( - self.resource, - 'QueryJurisdictionProvidersHandler', - description='Query jurisdiction providers handler', - lambda_dir='provider-data-v1', - index=os.path.join('handlers', 'state_api.py'), - handler='query_jurisdiction_providers', - environment=lambda_environment, - alarm_topic=self.api.alarm_topic, - ) - data_encryption_key.grant_decrypt(self.query_jurisdiction_providers_handler) - provider_data_table.grant_read_data(self.query_jurisdiction_providers_handler) - compact_configuration_table.grant_read_data(self.query_jurisdiction_providers_handler) - rate_limiting_table.grant_read_write_data(self.query_jurisdiction_providers_handler) - - NagSuppressions.add_resource_suppressions_by_path( - Stack.of(self.query_jurisdiction_providers_handler.role), - path=f'{self.query_jurisdiction_providers_handler.role.node.path}/DefaultPolicy/Resource', - suppressions=[ - { - 'id': 'AwsSolutions-IAM5', - 'appliesTo': [ - 'Action::kms:GenerateDataKey*', - 'Action::kms:ReEncrypt*', - 'Resource::/index/*', - ], - 'reason': 'The actions in this policy are specifically what this lambda needs to read ' - 'and is scoped to one table and encryption key.', - }, - ], - ) - return self.query_jurisdiction_providers_handler diff --git a/backend/cosmetology-app/tests/app/base.py b/backend/cosmetology-app/tests/app/base.py index 344de7df7..60f9be1e3 100644 --- a/backend/cosmetology-app/tests/app/base.py +++ b/backend/cosmetology-app/tests/app/base.py @@ -148,7 +148,7 @@ def _inspect_persistent_stack( self, persistent_stack: PersistentStack, *, - domain_name: str = None, + ui_domain_name: str = None, allow_local_ui: bool = False, local_ui_port: str = None, ): @@ -157,8 +157,8 @@ def _inspect_persistent_stack( persistent_stack_template = Template.from_stack(persistent_stack) callbacks = [] - if domain_name is not None: - callbacks.append(f'https://{domain_name}/auth/callback') + if ui_domain_name is not None: + callbacks.append(f'https://{ui_domain_name}/auth/callback') if allow_local_ui: # 3018 is default local_ui_port = '3018' if not local_ui_port else local_ui_port @@ -493,6 +493,15 @@ def _inspect_api_stack(self, api_stack: ApiStack): }, ) + # When a custom domain is configured, verify the API Gateway domain uses TLS 1.2 + if api_stack.hosted_zone is not None: + api_template.has_resource_properties( + 'AWS::ApiGateway::DomainName', + { + 'SecurityPolicy': 'TLS_1_2', + }, + ) + def _check_no_stack_annotations(self, stack: Stack): with self.subTest(f'Security Rules: {stack.stack_name}'): errors = Annotations.from_stack(stack).find_error('*', Match.string_like_regexp('.*')) diff --git a/backend/cosmetology-app/tests/app/test_api/test_provider_management_api.py b/backend/cosmetology-app/tests/app/test_api/test_provider_management_api.py index 6404f8f76..a3c2ddcba 100644 --- a/backend/cosmetology-app/tests/app/test_api/test_provider_management_api.py +++ b/backend/cosmetology-app/tests/app/test_api/test_provider_management_api.py @@ -473,101 +473,6 @@ def test_synth_generates_get_provider_ssn_alarms(self): overwrite_snapshot=False, ) - def test_synth_generates_deactivate_privilege_endpoint(self): - """Test that the POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} - /licenseType/{licenseType}/deactivate endpoint is configured correctly.""" - api_stack = self.app.sandbox_backend_stage.api_stack - api_stack_template = Template.from_stack(api_stack) - api_lambda_stack = self.app.sandbox_backend_stage.api_lambda_stack - api_lambda_stack_template = Template.from_stack(api_lambda_stack) - - # Ensure the lambda is created with expected code path - api_lambda_stack_template.has_resource_properties( - type=CfnFunction.CFN_RESOURCE_TYPE_NAME, - props={'Handler': 'handlers.privileges.deactivate_privilege'}, - ) - - request_model_logical_id_capture = Capture() - - # Ensure the POST method is configured correctly - api_stack_template.has_resource_properties( - type=CfnMethod.CFN_RESOURCE_TYPE_NAME, - props={ - 'HttpMethod': 'POST', - 'AuthorizerId': { - 'Ref': api_stack.get_logical_id(api_stack.api.staff_users_authorizer.node.default_child), - }, - 'Integration': TestApi.generate_expected_integration_object_for_imported_lambda( - api_lambda_stack, - api_lambda_stack_template, - api_lambda_stack.provider_management_lambdas.deactivate_privilege_handler, - ), - 'RequestModels': { - 'application/json': {'Ref': request_model_logical_id_capture}, - }, - 'MethodResponses': [ - { - 'ResponseModels': { - 'application/json': { - 'Ref': api_stack.get_logical_id( - api_stack.api.v1_api.api_model.message_response_model.node.default_child - ) - } - }, - 'StatusCode': '200', - }, - ], - }, - ) - - # Verify request model schema - request_model = TestApi.get_resource_properties_by_logical_id( - request_model_logical_id_capture.as_string(), - api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), - ) - self.compare_snapshot( - request_model['Schema'], - 'PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA', - overwrite_snapshot=False, - ) - - # Verify the deactivate resource is created correctly - license_type_param_logical_id = self._get_privilege_license_type_param_resource_id( - api_stack_template, api_stack - ) - api_stack_template.has_resource_properties( - type=CfnResource.CFN_RESOURCE_TYPE_NAME, - props={ - 'ParentId': {'Ref': license_type_param_logical_id}, - 'PathPart': 'deactivate', - }, - ) - - def test_synth_generates_deactivate_privilege_alarms(self): - """Test that the alarms are configured correctly for the privilege deactivation endpoint.""" - api_stack = self.app.sandbox_backend_stage.api_stack - api_stack_template = Template.from_stack(api_stack) - - # Ensure the anomaly detection alarm is created - alarms = api_stack_template.find_resources(CfnAlarm.CFN_RESOURCE_TYPE_NAME) - deactivation_notification_failed_alarm = TestApi.get_resource_properties_by_logical_id( - api_stack.get_logical_id( - api_stack.api.v1_api.provider_management.privilege_deactivation_notification_failed_alarm.node.default_child - ), - alarms, - ) - - # The alarm actions ref change depending on sandbox vs pipeline configuration, so we'll just - # make sure there is one action and remove it from the comparison - actions = deactivation_notification_failed_alarm.pop('AlarmActions', []) - self.assertEqual(len(actions), 1) - - self.compare_snapshot( - deactivation_notification_failed_alarm, - 'PRIVILEGE_DEACTIVATION_NOTIFICATION_FAILURE_ALARM_SCHEMA', - overwrite_snapshot=False, - ) - def test_synth_generates_privilege_encumbrance_endpoint(self): """Test that the POST /providers/{providerId}/privileges/jurisdiction/{jurisdiction} /licenseType/{licenseType}/encumbrance endpoint is configured correctly.""" diff --git a/backend/cosmetology-app/tests/app/test_api/test_state_api.py b/backend/cosmetology-app/tests/app/test_api/test_state_api.py index 5a1d542fd..09a3fdd4c 100644 --- a/backend/cosmetology-app/tests/app/test_api/test_state_api.py +++ b/backend/cosmetology-app/tests/app/test_api/test_state_api.py @@ -132,167 +132,6 @@ def test_synth_generates_jurisdiction_param_resource(self): }, ) - def test_synth_generates_providers_resource(self): - """Test that the /v1/compacts/{compact}/jurisdictions/{jurisdiction}/providers resource is created correctly.""" - state_api_stack = self.app.sandbox_backend_stage.state_api_stack - state_api_stack_template = Template.from_stack(state_api_stack) - - # Ensure the providers resource is created with expected path - state_api_stack_template.has_resource_properties( - type=CfnResource.CFN_RESOURCE_TYPE_NAME, - props={ - 'ParentId': { - 'Ref': state_api_stack.get_logical_id( - state_api_stack.api.v1_api.compact_jurisdiction_resource.node.default_child - ), - }, - 'PathPart': 'providers', - }, - ) - - def test_synth_generates_get_provider_endpoint(self): - """Test that the GET /providers/{providerId} endpoint is configured correctly.""" - state_api_stack = self.app.sandbox_backend_stage.state_api_stack - state_api_stack_template = Template.from_stack(state_api_stack) - - # Ensure the {providerId} resource is created with expected path - state_api_stack_template.has_resource_properties( - type=CfnResource.CFN_RESOURCE_TYPE_NAME, - props={ - 'ParentId': { - 'Ref': state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.resource.node.default_child - ), - }, - 'PathPart': '{providerId}', - }, - ) - - # Ensure the lambda is created with expected code path - get_handler = TestApi.get_resource_properties_by_logical_id( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.get_provider_handler.node.default_child - ), - state_api_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), - ) - - self.assertEqual(get_handler['Handler'], 'handlers.state_api.get_provider') - - # Capture model logical ID for verification - response_model_logical_id_capture = Capture() - - # Ensure the GET method is configured correctly - state_api_stack_template.has_resource_properties( - type=CfnMethod.CFN_RESOURCE_TYPE_NAME, - props={ - 'HttpMethod': 'GET', - 'AuthorizerId': { - 'Ref': state_api_stack.get_logical_id(state_api_stack.api.state_auth_authorizer.node.default_child), - }, - 'Integration': TestApi.generate_expected_integration_object( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.get_provider_handler.node.default_child, - ), - ), - 'MethodResponses': [ - { - 'ResponseModels': {'application/json': {'Ref': response_model_logical_id_capture}}, - 'StatusCode': '200', - }, - ], - }, - ) - - # Verify response model schema - response_model = TestApi.get_resource_properties_by_logical_id( - response_model_logical_id_capture.as_string(), - state_api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), - ) - self.compare_snapshot( - response_model['Schema'], - 'STATE_API_GET_PROVIDER_RESPONSE_SCHEMA', - overwrite_snapshot=False, - ) - - def test_synth_generates_query_providers_endpoint(self): - """Test that the POST /providers/query endpoint is configured correctly.""" - state_api_stack = self.app.sandbox_backend_stage.state_api_stack - state_api_stack_template = Template.from_stack(state_api_stack) - - # Ensure the query resource is created with expected path - state_api_stack_template.has_resource_properties( - type=CfnResource.CFN_RESOURCE_TYPE_NAME, - props={ - 'ParentId': { - 'Ref': state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.resource.node.default_child - ), - }, - 'PathPart': 'query', - }, - ) - - # Ensure the lambda is created with expected code path - query_handler = TestApi.get_resource_properties_by_logical_id( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.query_jurisdiction_providers_handler.node.default_child - ), - state_api_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), - ) - - self.assertEqual(query_handler['Handler'], 'handlers.state_api.query_jurisdiction_providers') - - # Capture model logical IDs for verification - request_model_logical_id_capture = Capture() - response_model_logical_id_capture = Capture() - - # Ensure the POST method is configured correctly - state_api_stack_template.has_resource_properties( - type=CfnMethod.CFN_RESOURCE_TYPE_NAME, - props={ - 'HttpMethod': 'POST', - 'AuthorizerId': { - 'Ref': state_api_stack.get_logical_id(state_api_stack.api.state_auth_authorizer.node.default_child), - }, - 'Integration': TestApi.generate_expected_integration_object( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.query_jurisdiction_providers_handler.node.default_child, - ), - ), - 'RequestModels': { - 'application/json': {'Ref': request_model_logical_id_capture}, - }, - 'MethodResponses': [ - { - 'ResponseModels': {'application/json': {'Ref': response_model_logical_id_capture}}, - 'StatusCode': '200', - }, - ], - }, - ) - - # Verify request model schema - request_model = TestApi.get_resource_properties_by_logical_id( - request_model_logical_id_capture.as_string(), - state_api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), - ) - self.compare_snapshot( - request_model['Schema'], - 'STATE_API_QUERY_PROVIDERS_REQUEST_SCHEMA', - overwrite_snapshot=False, - ) - - # Verify response model schema - response_model = TestApi.get_resource_properties_by_logical_id( - response_model_logical_id_capture.as_string(), - state_api_stack_template.find_resources(CfnModel.CFN_RESOURCE_TYPE_NAME), - ) - self.compare_snapshot( - response_model['Schema'], - 'STATE_API_QUERY_PROVIDERS_RESPONSE_SCHEMA', - overwrite_snapshot=False, - ) - def test_synth_generates_licenses_resource(self): """Test that the /v1/compacts/{compact}/jurisdictions/{jurisdiction}/licenses resource is created correctly.""" state_api_stack = self.app.sandbox_backend_stage.state_api_stack @@ -502,38 +341,6 @@ def test_state_api_lambda_environment_variables(self): state_api_stack = self.app.sandbox_backend_stage.state_api_stack state_api_stack_template = Template.from_stack(state_api_stack) - # Check provider management handlers - get_provider_handler = TestApi.get_resource_properties_by_logical_id( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.get_provider_handler.node.default_child - ), - state_api_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), - ) - - # Verify required environment variables are present - env_vars = get_provider_handler['Environment']['Variables'] - required_vars = [ - 'PROVIDER_TABLE_NAME', - 'PROV_FAM_GIV_MID_INDEX_NAME', - 'PROV_DATE_OF_UPDATE_INDEX_NAME', - 'API_BASE_URL', - ] - - for var in required_vars: - self.assertIn(var, env_vars, f'Environment variable {var} should be present in get provider handler') - - # Check query providers handler - query_handler = TestApi.get_resource_properties_by_logical_id( - state_api_stack.get_logical_id( - state_api_stack.api.v1_api.provider_management.query_jurisdiction_providers_handler.node.default_child - ), - state_api_stack_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), - ) - - env_vars = query_handler['Environment']['Variables'] - for var in required_vars: - self.assertIn(var, env_vars, f'Environment variable {var} should be present in query providers handler') - # Check post licenses handler post_licenses_handler = TestApi.get_resource_properties_by_logical_id( state_api_stack.get_logical_id( diff --git a/backend/cosmetology-app/tests/app/test_notification_stack.py b/backend/cosmetology-app/tests/app/test_notification_stack.py index 71785982d..9e2a48bd0 100644 --- a/backend/cosmetology-app/tests/app/test_notification_stack.py +++ b/backend/cosmetology-app/tests/app/test_notification_stack.py @@ -26,102 +26,6 @@ def get_context(cls): context['aws:cdk:bundling-stacks'] = [] return context - def test_privilege_purchase_notification_resources_created(self): - """ - Test that the privilege purchase notification lambda is added with a SQS queue - and an event bridge event rule that listens for 'privilege.purchase' detail types. - """ - notification_stack = self.app.sandbox_backend_stage.notification_stack - notification_template = Template.from_stack(notification_stack) - - # Verify the lambda function is created - privilege_purchase_handler_logical_id = notification_stack.get_logical_id( - notification_stack.privilege_purchase_processor.process_function.node.default_child - ) - privilege_purchase_handler = TestNotificationStack.get_resource_properties_by_logical_id( - privilege_purchase_handler_logical_id, - resources=notification_template.find_resources(CfnFunction.CFN_RESOURCE_TYPE_NAME), - ) - - self.assertEqual( - 'handlers.privileges.privilege_purchase_message_handler', privilege_purchase_handler['Handler'] - ) - - # Verify SQS queue is created for the privilege purchase processor - privilege_purchase_queue_logical_id = notification_stack.get_logical_id( - notification_stack.privilege_purchase_processor.queue.node.default_child - ) - privilege_purchase_queue = TestNotificationStack.get_resource_properties_by_logical_id( - privilege_purchase_queue_logical_id, - resources=notification_template.find_resources(CfnQueue.CFN_RESOURCE_TYPE_NAME), - ) - - dlq_logical_id = notification_stack.get_logical_id( - notification_stack.privilege_purchase_processor.dlq.node.default_child - ) - - # remove dynamic field - del privilege_purchase_queue['KmsMasterKeyId'] - - self.assertEqual( - { - 'MessageRetentionPeriod': 43200, - 'RedrivePolicy': {'deadLetterTargetArn': {'Fn::GetAtt': [dlq_logical_id, 'Arn']}, 'maxReceiveCount': 3}, - 'VisibilityTimeout': 300, - }, - privilege_purchase_queue, - ) - - # Verify EventBridge rule is created with correct detail type - privilege_purchase_rule = TestNotificationStack.get_resource_properties_by_logical_id( - notification_stack.get_logical_id(notification_stack.privilege_purchase_rule.node.default_child), - resources=notification_template.find_resources(CfnRule.CFN_RESOURCE_TYPE_NAME), - ) - - self.assertEqual( - { - 'EventBusName': { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '/', - {'Fn::Select': [5, {'Fn::Split': [':', {'Ref': 'DataEventBusArnParameterParameter'}]}]}, - ] - }, - ] - }, - 'EventPattern': {'detail-type': ['privilege.purchase']}, - 'State': 'ENABLED', - 'Targets': [ - { - 'Arn': {'Fn::GetAtt': [privilege_purchase_queue_logical_id, 'Arn']}, - 'DeadLetterConfig': {'Arn': {'Fn::GetAtt': [dlq_logical_id, 'Arn']}}, - 'Id': 'Target0', - } - ], - }, - privilege_purchase_rule, - ) - - # Verify event source mapping between SQS queue and Lambda function - event_source_mapping = TestNotificationStack.get_resource_properties_by_logical_id( - notification_stack.get_logical_id( - notification_stack.privilege_purchase_processor.event_source_mapping.node.default_child - ), - resources=notification_template.find_resources(CfnEventSourceMapping.CFN_RESOURCE_TYPE_NAME), - ) - self.assertEqual( - { - 'BatchSize': 10, - 'EventSourceArn': {'Fn::GetAtt': [privilege_purchase_queue_logical_id, 'Arn']}, - 'FunctionName': {'Ref': privilege_purchase_handler_logical_id}, - 'FunctionResponseTypes': ['ReportBatchItemFailures'], - 'MaximumBatchingWindowInSeconds': 15, - }, - event_source_mapping, - ) - def test_license_encumbrance_notification_listener_resources_created(self): """ Test that the license encumbrance notification listener lambda is added with a SQS queue diff --git a/backend/cosmetology-app/tests/app/test_pipeline.py b/backend/cosmetology-app/tests/app/test_pipeline.py index bd09a1d07..2bb22c1ae 100644 --- a/backend/cosmetology-app/tests/app/test_pipeline.py +++ b/backend/cosmetology-app/tests/app/test_pipeline.py @@ -57,17 +57,17 @@ def test_synth_pipeline(self): self._inspect_persistent_stack( self.app.test_backend_pipeline_stack.test_stage.persistent_stack, - domain_name='app.test.cosmetology.compactconnect.org', + ui_domain_name='app.test.compactconnect.org', allow_local_ui=True, ) self._inspect_persistent_stack( self.app.beta_backend_pipeline_stack.beta_backend_stage.persistent_stack, - domain_name='app.beta.cosmetology.compactconnect.org', + ui_domain_name='app.beta.compactconnect.org', allow_local_ui=False, ) self._inspect_persistent_stack( self.app.prod_backend_pipeline_stack.prod_stage.persistent_stack, - domain_name='app.cosmetology.compactconnect.org', + ui_domain_name='app.compactconnect.org', ) self._inspect_state_auth_stack( diff --git a/backend/cosmetology-app/tests/app/test_sandbox.py b/backend/cosmetology-app/tests/app/test_sandbox.py index 8b28b161f..93b09f399 100644 --- a/backend/cosmetology-app/tests/app/test_sandbox.py +++ b/backend/cosmetology-app/tests/app/test_sandbox.py @@ -35,7 +35,7 @@ def test_api_stack(self): self._inspect_persistent_stack( self.app.sandbox_backend_stage.persistent_stack, - domain_name='app.justin.cosmetology.compactconnect.org', + ui_domain_name='app.justin.compactconnect.org', allow_local_ui=True, ) @@ -55,8 +55,9 @@ class TestSandboxNoDomain(TstSandbox): def get_context(cls): context = super().get_context() - # Drop domain name to ensure we still handle the optional DNS setup + # Drop domain name and ui_domain_name_override to ensure we still handle the optional DNS setup del context['ssm_context']['environments'][context['environment_name']]['domain_name'] + del context['ssm_context']['environments'][context['environment_name']]['ui_domain_name_override'] return context def test_synth_sandbox_no_domain(self): @@ -80,8 +81,9 @@ class TestSandboxLocalUiPortOverride(TstSandbox): def get_context(cls): context = super().get_context() - # Drop domain name to ensure we still handle the optional DNS setup + # Drop domain name and ui_domain_name_override to ensure we still handle the optional DNS setup del context['ssm_context']['environments'][context['environment_name']]['domain_name'] + del context['ssm_context']['environments'][context['environment_name']]['ui_domain_name_override'] context['ssm_context']['environments'][context['environment_name']]['local_ui_port'] = '5432' return context @@ -116,6 +118,7 @@ def test_synth_no_ui_raises_value_error(self): context['aws:cdk:bundling-stacks'] = [] del context['ssm_context']['environments'][context['environment_name']]['domain_name'] + del context['ssm_context']['environments'][context['environment_name']]['ui_domain_name_override'] del context['ssm_context']['environments'][context['environment_name']]['allow_local_ui'] with self.assertRaises(ValueError): diff --git a/backend/cosmetology-app/tests/common_constructs/test_cognito_user_backup.py b/backend/cosmetology-app/tests/common_constructs/test_cognito_user_backup.py index 8ecbf08f8..103b90254 100644 --- a/backend/cosmetology-app/tests/common_constructs/test_cognito_user_backup.py +++ b/backend/cosmetology-app/tests/common_constructs/test_cognito_user_backup.py @@ -31,12 +31,13 @@ class TestCognitoUserBackup(TestCase): def setUpClass(cls): """Set up test infrastructure.""" cls.app = App() - # The persistent stack and layer are required for CognitoUserBackup, as an internal lambda depends on it + # The persistent stack and layer are required for CognitoUserBackup, as an internal lambda depends on it. + # Use a non-pipeline environment name so domain_name is not required (avoids HostedZone.from_lookup in tests). common_stack = AppStack( cls.app, 'CommonStack', environment_context={}, - environment_name='test', + environment_name='sandbox', standard_tags=StandardTags(project='compact-connect', service='compact-connect', environment='test'), ) # Create common lambda layers diff --git a/backend/cosmetology-app/tests/common_constructs/test_data_migration.py b/backend/cosmetology-app/tests/common_constructs/test_data_migration.py index aa002bd35..9b8627036 100644 --- a/backend/cosmetology-app/tests/common_constructs/test_data_migration.py +++ b/backend/cosmetology-app/tests/common_constructs/test_data_migration.py @@ -17,12 +17,13 @@ def test_data_migration_synthesizes(self): from common_constructs.python_common_layer_versions import PythonCommonLayerVersions app = App() - # The persistent stack and layer are required for DataMigration, as an internal lambda depends on it + # The persistent stack and layer are required for DataMigration, as an internal lambda depends on it. + # Use a non-pipeline environment name so domain_name is not required (avoids HostedZone.from_lookup in tests). common_stack = AppStack( app, 'CommonStack', environment_context={}, - environment_name='test', + environment_name='sandbox', standard_tags=StandardTags(project='compact-connect', service='compact-connect', environment='test'), ) # Create common lambda layers diff --git a/backend/cosmetology-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json index 11a25ae0c..9f701e2e9 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json @@ -147,10 +147,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -284,10 +280,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -581,10 +573,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -1261,10 +1249,6 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "ssnLastFour": { "pattern": "^[0-9]{4}$", "type": "string" @@ -1353,24 +1337,6 @@ ], "type": "string" }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "type": "array" - }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -1383,7 +1349,6 @@ "familyName", "compact", "licenseJurisdiction", - "privilegeJurisdictions", "dateOfUpdate", "dateOfExpiration", "birthMonthDay", @@ -1392,4 +1357,4 @@ ], "type": "object", "$schema": "http://json-schema.org/draft-04/schema#" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_NOTIFICATION_FAILURE_ALARM_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_NOTIFICATION_FAILURE_ALARM_SCHEMA.json deleted file mode 100644 index f541984c4..000000000 --- a/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_NOTIFICATION_FAILURE_ALARM_SCHEMA.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "AlarmDescription": "Sandbox/APIStack/LicenseApi Privilege deactivation notification failed. One or more notifications to providers or jurisdictions failed to send during privilege deactivation. Investigation required to ensure all parties have been properly notified.", - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "Dimensions": [ - { - "Name": "service", - "Value": "common" - } - ], - "EvaluationPeriods": 1, - "MetricName": "privilege-deactivation-notification-failed", - "Namespace": "compact-connect", - "Period": 300, - "Statistic": "Sum", - "Threshold": 1, - "TreatMissingData": "notBreaching" -} diff --git a/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json deleted file mode 100644 index 6cadba228..000000000 --- a/backend/cosmetology-app/tests/resources/snapshots/PRIVILEGE_DEACTIVATION_REQUEST_SCHEMA.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "additionalProperties": false, - "properties": { - "deactivationNote": { - "description": "Note describing why the privilege is being deactivated", - "maxLength": 256, - "type": "string" - } - }, - "required": [ - "deactivationNote" - ], - "type": "object", - "$schema": "http://json-schema.org/draft-04/schema#" -} diff --git a/backend/cosmetology-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json index 8333df532..652e27c64 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json @@ -145,12 +145,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "jurisdiction": { "enum": [ "al", @@ -209,13 +209,13 @@ ], "type": "string" }, - "licenseType": { - "enum": [ - "cosmetologist", - "esthetician" - ], - "type": "string" - }, + "licenseType": { + "enum": [ + "cosmetologist", + "esthetician" + ], + "type": "string" + }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -236,10 +236,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -353,7 +349,8 @@ "homeAddressState", "homeAddressPostalCode", "jurisdictionUploadedLicenseStatus", - "jurisdictionUploadedCompactEligibility" + "jurisdictionUploadedCompactEligibility", + "licenseNumber" ], "type": "object" }, @@ -373,10 +370,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -508,12 +501,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "providerId": { "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" @@ -648,12 +641,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "providerId": { "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" @@ -756,10 +749,6 @@ ], "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "licenseNumber": { "maxLength": 100, "minLength": 1, @@ -868,7 +857,8 @@ "compactEligibility", "jurisdictionUploadedLicenseStatus", "jurisdictionUploadedCompactEligibility", - "history" + "history", + "licenseNumber" ], "type": "object" }, @@ -902,12 +892,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "jurisdiction": { "enum": [ "al", @@ -966,13 +956,13 @@ ], "type": "string" }, - "licenseType": { - "enum": [ - "cosmetologist", - "esthetician" - ], - "type": "string" - }, + "licenseType": { + "enum": [ + "cosmetologist", + "esthetician" + ], + "type": "string" + }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -989,12 +979,12 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "jurisdiction": { "enum": [ "al", @@ -1196,12 +1186,12 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "jurisdiction": { "enum": [ "al", @@ -1416,12 +1406,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "providerId": { "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" @@ -1556,12 +1546,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "providerId": { "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" @@ -1969,10 +1959,6 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "ssnLastFour": { "pattern": "^[0-9]{4}$", "type": "string" @@ -2025,12 +2011,12 @@ ], "type": "string" }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, + "compact": { + "enum": [ + "cosm" + ], + "type": "string" + }, "birthMonthDay": { "format": "date", "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", @@ -2104,67 +2090,6 @@ ], "type": "string" }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "ak", - "az", - "ar", - "ca", - "co", - "ct", - "de", - "dc", - "fl", - "ga", - "hi", - "id", - "il", - "in", - "ia", - "ks", - "ky", - "la", - "me", - "md", - "ma", - "mi", - "mn", - "ms", - "mo", - "mt", - "ne", - "nv", - "nh", - "nj", - "nm", - "ny", - "nc", - "nd", - "oh", - "ok", - "or", - "pa", - "pr", - "ri", - "sc", - "sd", - "tn", - "tx", - "ut", - "vt", - "va", - "vi", - "wa", - "wv", - "wi", - "wy" - ], - "type": "string" - }, - "type": "array" - }, "compactConnectRegisteredEmailAddress": { "format": "email", "maxLength": 100, @@ -2244,7 +2169,6 @@ "familyName", "compact", "licenseJurisdiction", - "privilegeJurisdictions", "dateOfUpdate", "dateOfExpiration", "birthMonthDay", @@ -2254,4 +2178,4 @@ ], "type": "object", "$schema": "http://json-schema.org/draft-04/schema#" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json index 6458997f2..1d53874f6 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json @@ -386,10 +386,6 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "givenName": { "maxLength": 100, "minLength": 1, @@ -431,24 +427,6 @@ ], "type": "string" }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "type": "array" - }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -461,9 +439,8 @@ "compact", "licenseJurisdiction", "givenName", - "familyName", - "privilegeJurisdictions" + "familyName" ], "type": "object", "$schema": "http://json-schema.org/draft-04/schema#" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json index 825d6fb5f..1d3d3eeb7 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_REQUEST_SCHEMA.json @@ -11,7 +11,7 @@ "type": "string" }, "jurisdiction": { - "description": "Filter for providers with privilege/license in a jurisdiction", + "description": "Filter for providers with license in a jurisdiction", "enum": [ "al", "az", diff --git a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json index b8cfbf70d..380303be6 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/PUBLIC_QUERY_PROVIDERS_RESPONSE_SCHEMA.json @@ -13,10 +13,6 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "givenName": { "maxLength": 100, "minLength": 1, @@ -58,24 +54,6 @@ ], "type": "string" }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "type": "array" - }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -87,8 +65,7 @@ "givenName", "familyName", "compact", - "licenseJurisdiction", - "privilegeJurisdictions" + "licenseJurisdiction" ], "type": "object" }, diff --git a/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json index 825d6fb5f..1d3d3eeb7 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_REQUEST_SCHEMA.json @@ -11,7 +11,7 @@ "type": "string" }, "jurisdiction": { - "description": "Filter for providers with privilege/license in a jurisdiction", + "description": "Filter for providers with license in a jurisdiction", "enum": [ "al", "az", diff --git a/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json index b11df4855..90086150c 100644 --- a/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json +++ b/backend/cosmetology-app/tests/resources/snapshots/QUERY_PROVIDERS_RESPONSE_SCHEMA.json @@ -13,10 +13,6 @@ "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", "type": "string" }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, "ssnLastFour": { "pattern": "^[0-9]{4}$", "type": "string" @@ -105,24 +101,6 @@ ], "type": "string" }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "type": "array" - }, "dateOfUpdate": { "format": "date-time", "type": "string" @@ -139,7 +117,6 @@ "jurisdictionUploadedCompactEligibility", "compact", "licenseJurisdiction", - "privilegeJurisdictions", "dateOfUpdate", "dateOfExpiration", "birthMonthDay" @@ -207,4 +184,4 @@ ], "type": "object", "$schema": "http://json-schema.org/draft-04/schema#" -} +} \ No newline at end of file diff --git a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_GET_PROVIDER_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/STATE_API_GET_PROVIDER_RESPONSE_SCHEMA.json deleted file mode 100644 index ebc0a85a0..000000000 --- a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_GET_PROVIDER_RESPONSE_SCHEMA.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "properties": { - "privileges": { - "items": { - "properties": { - "type": { - "enum": [ - "statePrivilege" - ], - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, - "jurisdiction": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "licenseType": { - "enum": [ - "cosmetologist", - "esthetician" - ], - "type": "string" - }, - "privilegeId": { - "type": "string" - }, - "licenseNumber": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "status": { - "enum": [ - "active", - "inactive" - ], - "type": "string" - }, - "compactEligibility": { - "enum": [ - "eligible", - "ineligible" - ], - "type": "string" - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "emailAddress": { - "format": "email", - "maxLength": 100, - "minLength": 5, - "type": "string" - }, - "compactConnectRegisteredEmailAddress": { - "format": "email", - "maxLength": 100, - "minLength": 5, - "type": "string" - }, - "dateOfBirth": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "dateOfExpiration": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "dateOfIssuance": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "dateOfRenewal": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressStreet1": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "homeAddressStreet2": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "homeAddressCity": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "homeAddressState": { - "maxLength": 100, - "minLength": 2, - "type": "string" - }, - "homeAddressPostalCode": { - "maxLength": 7, - "minLength": 5, - "type": "string" - }, - "licenseJurisdiction": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "licenseStatus": { - "enum": [ - "active", - "inactive" - ], - "type": "string" - }, - "licenseStatusName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "phoneNumber": { - "pattern": "^\\+[0-9]{8,15}$", - "type": "string" - } - }, - "required": [ - "type", - "providerId", - "compact", - "jurisdiction", - "licenseType", - "privilegeId", - "status", - "compactEligibility", - "dateOfExpiration", - "dateOfIssuance", - "dateOfRenewal", - "dateOfUpdate", - "familyName", - "givenName", - "licenseJurisdiction", - "licenseStatus" - ], - "type": "object" - }, - "type": "array" - }, - "providerUIUrl": { - "description": "URL to the provider UI page", - "format": "uri", - "type": "string" - } - }, - "required": [ - "privileges", - "providerUIUrl" - ], - "type": "object", - "$schema": "http://json-schema.org/draft-04/schema#" -} diff --git a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_REQUEST_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_REQUEST_SCHEMA.json deleted file mode 100644 index 7e7bc9c69..000000000 --- a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_REQUEST_SCHEMA.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "additionalProperties": false, - "properties": { - "query": { - "additionalProperties": false, - "description": "The query parameters", - "properties": { - "startDateTime": { - "format": "date-time", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$", - "type": "string" - }, - "endDateTime": { - "format": "date-time", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$", - "type": "string" - } - }, - "required": [ - "startDateTime", - "endDateTime" - ], - "type": "object" - }, - "pagination": { - "additionalProperties": false, - "properties": { - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - }, - "type": "object" - }, - "sorting": { - "description": "How to sort results", - "properties": { - "direction": { - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ], - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "query" - ], - "type": "object", - "$schema": "http://json-schema.org/draft-04/schema#" -} diff --git a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_RESPONSE_SCHEMA.json b/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_RESPONSE_SCHEMA.json deleted file mode 100644 index fa6a5eb74..000000000 --- a/backend/cosmetology-app/tests/resources/snapshots/STATE_API_QUERY_PROVIDERS_RESPONSE_SCHEMA.json +++ /dev/null @@ -1,206 +0,0 @@ -{ - "properties": { - "providers": { - "items": { - "properties": { - "type": { - "enum": [ - "provider" - ], - "type": "string" - }, - "providerId": { - "pattern": "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab]{1}[0-9a-f]{3}-[0-9a-f]{12}", - "type": "string" - }, - "npi": { - "pattern": "^[0-9]{10}$", - "type": "string" - }, - "ssnLastFour": { - "pattern": "^[0-9]{4}$", - "type": "string" - }, - "givenName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "middleName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "familyName": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "suffix": { - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "licenseStatus": { - "enum": [ - "active", - "inactive" - ], - "type": "string" - }, - "compactEligibility": { - "enum": [ - "eligible", - "ineligible" - ], - "type": "string" - }, - "jurisdictionUploadedLicenseStatus": { - "enum": [ - "active", - "inactive" - ], - "type": "string" - }, - "jurisdictionUploadedCompactEligibility": { - "enum": [ - "eligible", - "ineligible" - ], - "type": "string" - }, - "compact": { - "enum": [ - "cosm" - ], - "type": "string" - }, - "birthMonthDay": { - "format": "date", - "pattern": "^[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$", - "type": "string" - }, - "dateOfBirth": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "dateOfExpiration": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - }, - "licenseJurisdiction": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "privilegeJurisdictions": { - "items": { - "enum": [ - "al", - "az", - "co", - "ks", - "ky", - "md", - "oh", - "tn", - "va", - "wa" - ], - "type": "string" - }, - "type": "array" - }, - "compactConnectRegisteredEmailAddress": { - "format": "email", - "maxLength": 100, - "minLength": 5, - "type": "string" - }, - "dateOfUpdate": { - "format": "date", - "pattern": "^[12]{1}[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", - "type": "string" - } - }, - "required": [ - "type", - "providerId", - "givenName", - "familyName", - "licenseStatus", - "compactEligibility", - "jurisdictionUploadedLicenseStatus", - "jurisdictionUploadedCompactEligibility", - "compact", - "licenseJurisdiction", - "privilegeJurisdictions", - "dateOfUpdate", - "dateOfExpiration", - "birthMonthDay" - ], - "type": "object" - }, - "maxItems": 100, - "type": "array" - }, - "pagination": { - "properties": { - "lastKey": { - "maxLength": 1024, - "minLength": 1, - "type": [ - "string", - "null" - ] - }, - "prevLastKey": { - "maxLength": 1024, - "minLength": 1, - "type": [ - "string", - "null" - ] - }, - "pageSize": { - "maximum": 100, - "minimum": 5, - "type": "integer" - } - }, - "type": "object" - }, - "sorting": { - "description": "How to sort results", - "properties": { - "direction": { - "description": "Direction to sort results by", - "enum": [ - "ascending", - "descending" - ], - "type": "string" - } - }, - "type": "object" - } - }, - "required": [ - "providers", - "pagination" - ], - "type": "object", - "$schema": "http://json-schema.org/draft-04/schema#" -} diff --git a/backend/cosmetology-app/tests/smoke/license_deactivation_privilege_smoke_tests.py b/backend/cosmetology-app/tests/smoke/license_deactivation_privilege_smoke_tests.py deleted file mode 100644 index 62ee0f5ff..000000000 --- a/backend/cosmetology-app/tests/smoke/license_deactivation_privilege_smoke_tests.py +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env python3 -""" -Smoke tests for license deactivation privilege functionality. - -This module contains end-to-end tests for the license deactivation workflow where privileges -are automatically deactivated when their associated home state license is -deactivated by a jurisdiction. - -The tests create their own test data from scratch and clean up after themselves. -""" - -import time - -from smoke_common import ( - SmokeTestFailureException, - cleanup_test_provider_records, - config, - create_test_privilege_record, - create_test_staff_user, - delete_test_staff_user, - get_staff_user_auth_headers, - load_smoke_test_env, - logger, - upload_license_record, - wait_for_provider_creation, -) - -# Test configuration -TEST_COMPACT = 'cosm' -TEST_JURISDICTION = 'oh' # Home jurisdiction -TEST_PRIVILEGE_JURISDICTION = 'ne' # Where privilege is purchased -TEST_LICENSE_TYPE = 'cosmetologist' -TEST_GIVEN_NAME = 'TestProvider' -TEST_FAMILY_NAME = 'LicenseDeactivation' -TEST_SSN = '999-99-9999' # Test SSN for license uploads - - -def get_provider_details_from_api(staff_headers: dict, compact: str, provider_id: str): - """ - Get provider details from the staff API endpoint. - - :param staff_headers: Authentication headers for staff user - :param compact: The compact abbreviation - :param provider_id: The provider's ID - :returns: Provider details from the API including privileges list - :raises SmokeTestFailureException: If the API request fails - """ - import requests - - response = requests.get( - url=f'{config.api_base_url}/v1/compacts/{compact}/providers/{provider_id}', - headers=staff_headers, - timeout=10, - ) - - if response.status_code != 200: - raise SmokeTestFailureException(f'Failed to get provider details. Response: {response.json()}') - - return response.json() - - -def validate_privilege_deactivation( - staff_headers: dict, - provider_id: str, - compact: str, - license_jurisdiction: str, - license_type: str, - max_wait_time: int = 120, - check_interval: int = 15, -): - """ - Validate that privilege is deactivated due to license deactivation. - - This function polls the API to check if a privilege has been automatically - deactivated after its associated license was deactivated. It retries - multiple times within the specified time window. - - :param staff_headers: Authentication headers for staff user - :param provider_id: The provider's ID - :param compact: The compact abbreviation - :param license_jurisdiction: The license jurisdiction - :param license_type: The license type - :param max_wait_time: Maximum time to wait in seconds (default: 120) - :param check_interval: Time between checks in seconds (default: 15) - :returns: The matching privilege record if validation succeeds - :raises SmokeTestFailureException: If privilege is not properly deactivated within the time limit - """ - logger.info(f'Validating privilege deactivation for provider {provider_id}...') - - start_time = time.time() - attempts = 0 - max_attempts = max_wait_time // check_interval - - while attempts < max_attempts: - attempts += 1 - - try: - provider_data = get_provider_details_from_api(staff_headers, compact, provider_id) - privileges = provider_data.get('privileges', []) - - # Find the privilege that matches our test criteria - matching_privilege = None - for privilege in privileges: - if ( - privilege.get('licenseJurisdiction') == license_jurisdiction - and privilege.get('licenseType') == license_type - ): - matching_privilege = privilege - break - - if not matching_privilege: - logger.warning(f'Attempt {attempts}/{max_attempts}: No matching privilege found') - else: - privilege_status = matching_privilege.get('status') - logger.info(f'Attempt {attempts}/{max_attempts}: privilege status = {privilege_status}') - - # Check if privilege is properly deactivated - if privilege_status == 'inactive': - elapsed_time = time.time() - start_time - logger.info(f'✅ Privilege deactivation validation successful after {elapsed_time:.1f} seconds') - return matching_privilege - - # Wait before next attempt - if attempts < max_attempts: - logger.info(f'Waiting {check_interval} seconds before next check...') - time.sleep(check_interval) - - except Exception as e: # noqa: BLE001 - logger.warning(f'Attempt {attempts}/{max_attempts}: Error checking privilege: {e}') - if attempts < max_attempts: - time.sleep(check_interval) - - # If we get here, validation failed - elapsed_time = time.time() - start_time - raise SmokeTestFailureException( - f'Privilege deactivation validation failed after {elapsed_time:.1f} seconds. ' - f'Expected privilege status to be "inactive" but it was not set within {max_wait_time} seconds.' - ) - - -def test_license_deactivation_privilege_workflow(): - """ - The test validates that when a home state license is deactivated, - any privileges associated with that license are automatically - deactivated as well. - - This test performs the following steps: - - 1. Upload an active license and wait for provider creation - 2. Update provider registration details - 3. Create a test privilege record - 4. Upload the same license with inactive status - 5. Validate that the privilege is automatically deactivated - 6. Clean up all test data - - :raises SmokeTestFailureException: If any step of the workflow fails - """ - logger.info('Starting license deactivation privilege workflow test...') - - provider_id = None - staff_email = None - staff_user_sub = None - - try: - # Create test staff user with permissions to upload licenses - staff_email = f'test-license-deactivation-{TEST_JURISDICTION}@ccSmokeTestFakeEmail.com' - staff_user_sub = create_test_staff_user( - email=staff_email, - compact=TEST_COMPACT, - jurisdiction=TEST_JURISDICTION, - permissions={'actions': {'admin'}, 'jurisdictions': {TEST_JURISDICTION: {'write', 'admin'}}}, - ) - - staff_headers = get_staff_user_auth_headers(staff_email) - - # Step 1: Upload an active license and wait for provider creation - logger.info('Step 1: Uploading active license and waiting for provider creation...') - - upload_license_record( - staff_headers=staff_headers, - compact=TEST_COMPACT, - jurisdiction=TEST_JURISDICTION, - data_overrides={ - 'givenName': TEST_GIVEN_NAME, - 'familyName': TEST_FAMILY_NAME, - 'licenseType': TEST_LICENSE_TYPE, - 'ssn': TEST_SSN, - 'licenseStatus': 'active', - 'compactEligibility': 'eligible', - }, - ) - - # Wait for provider to be created - provider_id = wait_for_provider_creation( - staff_headers=staff_headers, - compact=TEST_COMPACT, - given_name=TEST_GIVEN_NAME, - family_name=TEST_FAMILY_NAME, - max_wait_time=660, # 11 minutes (to account for message batch windows) - ) - - # Step 2: Create a test privilege record - logger.info('Step 2: Creating test privilege record...') - - create_test_privilege_record( - provider_id=provider_id, - compact=TEST_COMPACT, - jurisdiction=TEST_PRIVILEGE_JURISDICTION, - license_jurisdiction=TEST_JURISDICTION, - license_type=TEST_LICENSE_TYPE, - ) - - # Verify privilege was created and is active - provider_data = get_provider_details_from_api(staff_headers, TEST_COMPACT, provider_id) - privileges = provider_data.get('privileges', []) - - test_privilege = None - for privilege in privileges: - if ( - privilege.get('licenseJurisdiction') == TEST_JURISDICTION - and privilege.get('licenseType') == TEST_LICENSE_TYPE - ): - test_privilege = privilege - break - - if not test_privilege: - raise SmokeTestFailureException('Test privilege record was not created successfully') - - if test_privilege.get('status') != 'active': - raise SmokeTestFailureException( - f'Test privilege should have status "active" initially, but found: {test_privilege.get("status")}' - ) - - logger.info('✅ Test privilege record created successfully and is in expected initial state (active status)') - - # Step 3: Upload the same license with inactive status - logger.info('Step 3: Uploading license with inactive status to trigger deactivation...') - - upload_license_record( - staff_headers=staff_headers, - compact=TEST_COMPACT, - jurisdiction=TEST_JURISDICTION, - data_overrides={ - 'givenName': TEST_GIVEN_NAME, - 'familyName': TEST_FAMILY_NAME, - 'licenseType': TEST_LICENSE_TYPE, - 'ssn': TEST_SSN, - 'licenseStatus': 'inactive', - 'compactEligibility': 'ineligible', - }, - ) - - # Step 4: Validate that the privilege is automatically deactivated - logger.info('Step 4: Validating automatic privilege deactivation...') - - validate_privilege_deactivation( - staff_headers=staff_headers, - provider_id=provider_id, - compact=TEST_COMPACT, - license_jurisdiction=TEST_JURISDICTION, - license_type=TEST_LICENSE_TYPE, - max_wait_time=660, # 11 minutes (to account for message batch windows) - check_interval=15, - ) - - logger.info('✅ Privilege was automatically deactivated with status "inactive"') - logger.info('✅ License deactivation privilege workflow test completed successfully!') - - finally: - # Step 5: Clean up all test data - logger.info('Step 5: Cleaning up test data...') - - if provider_id: - cleanup_test_provider_records(provider_id, TEST_COMPACT) - - if staff_email and staff_user_sub: - delete_test_staff_user(staff_email, staff_user_sub, TEST_COMPACT) - - logger.info('✅ Test cleanup completed') - - -def run_license_deactivation_privilege_smoke_tests(): - """ - Run the complete suite of license deactivation privilege smoke tests. - """ - logger.info('Starting license deactivation privilege smoke tests...') - - try: - test_license_deactivation_privilege_workflow() - logger.info('All license deactivation privilege smoke tests completed successfully!') - - except Exception as e: - logger.error(f'License deactivation privilege smoke tests failed: {str(e)}') - raise - - -if __name__ == '__main__': - # Load environment variables from smoke_tests_env.json - load_smoke_test_env() - - # Run the complete test suite - run_license_deactivation_privilege_smoke_tests() diff --git a/backend/cosmetology-app/tests/smoke/license_upload_smoke_tests.py b/backend/cosmetology-app/tests/smoke/license_upload_smoke_tests.py index 1e22badd4..7dbc37c21 100644 --- a/backend/cosmetology-app/tests/smoke/license_upload_smoke_tests.py +++ b/backend/cosmetology-app/tests/smoke/license_upload_smoke_tests.py @@ -72,7 +72,6 @@ def upload_licenses_record(): # Step 1: Upload a license record through the POST '/v1/compacts/cosm/jurisdictions/ne/licenses' endpoint. post_body = [ { - 'npi': '1111111111', 'licenseNumber': 'A0608337260', 'homeAddressPostalCode': '68001', 'givenName': TEST_PROVIDER_GIVEN_NAME, diff --git a/backend/cosmetology-app/tests/smoke/signature_auth_smoke_tests.py b/backend/cosmetology-app/tests/smoke/signature_auth_smoke_tests.py deleted file mode 100755 index 0014af8d0..000000000 --- a/backend/cosmetology-app/tests/smoke/signature_auth_smoke_tests.py +++ /dev/null @@ -1,522 +0,0 @@ -#!/usr/bin/env python3 -# ruff: noqa: T201 we use print statements for smoke testing -import os -import sys -import uuid -from datetime import UTC, datetime, timedelta -from pathlib import Path - -import boto3 -import requests -from botocore.exceptions import ClientError -from config import logger -from smoke_common import ( - SmokeTestFailureException, - config, - load_smoke_test_env, -) - -# Add the common library path to import the sign_request function -common_lib_path = os.path.join('lambdas', 'python', 'common') -sys.path.append(common_lib_path) - -# Import the sign_request function from the common library -from common_test.sign_request import sign_request # noqa: E402 - -COMPACT = 'cosm' -JURISDICTION = 'ne' -TEST_CLIENT_NAME = 'test-signature-auth-client' -TEST_KEY_ID = 'test-signature-key-001' - -# This script can be run locally to test the SIGNATURE authentication flow against a sandbox environment -# of the Compact Connect State API. -# To run this script, create a smoke_tests_env.json file in the same directory as this script using the -# 'smoke_tests_env_example.json' file as a template. - - -def get_compact_configuration_table(): - """Get the compact configuration DynamoDB table.""" - return config.compact_configuration_dynamodb_table - - -def get_state_api_base_url(): - """Get the state API base URL from config.""" - return config.state_api_base_url - - -def get_state_auth_url(): - """Get the state auth URL from config.""" - return config.state_auth_url - - -def create_test_app_client(): - """ - Create a test app client in Cognito for SIGNATURE authentication testing. - - :return: Dictionary containing client_id and client_secret - """ - logger.info(f'Creating test app client: {TEST_CLIENT_NAME}') - - try: - cognito_client = boto3.client('cognito-idp') - - # Create the user pool client - response = cognito_client.create_user_pool_client( - UserPoolId=config.cognito_state_auth_user_pool_id, - ClientName=TEST_CLIENT_NAME, - PreventUserExistenceErrors='ENABLED', - GenerateSecret=True, - TokenValidityUnits={'AccessToken': 'minutes'}, - AccessTokenValidity=15, - AllowedOAuthFlowsUserPoolClient=True, - AllowedOAuthFlows=['client_credentials'], - AllowedOAuthScopes=[f'{COMPACT}/readGeneral', f'{JURISDICTION}/{COMPACT}.write'], - ) - - user_pool_client = response.get('UserPoolClient', {}) - client_id = user_pool_client.get('ClientId') - client_secret = user_pool_client.get('ClientSecret') - - if not client_id or not client_secret: - raise SmokeTestFailureException('Failed to extract client ID or secret from AWS response') - - logger.info(f'Successfully created test app client with ID: {client_id}') - return {'client_id': client_id, 'client_secret': client_secret} - - except ClientError as e: - error_code = e.response['Error']['Code'] - error_message = e.response['Error']['Message'] - logger.error(f'Failed to create app client: {error_code} - {error_message}') - raise SmokeTestFailureException(f'Failed to create app client: {error_code} - {error_message}') from e - - -def delete_test_app_client(client_id: str): - """Delete the test app client from Cognito.""" - try: - cognito_client = boto3.client('cognito-idp') - cognito_client.delete_user_pool_client(UserPoolId=config.cognito_state_auth_user_pool_id, ClientId=client_id) - logger.info(f'Successfully deleted test app client: {client_id}') - except ClientError as e: - logger.error(f'Failed to delete app client {client_id}: {str(e)}') - # Don't raise here as this is cleanup - - -def get_client_credentials_token(client_id: str, client_secret: str): - """ - Get an access token using client credentials flow. - - :param client_id: The client ID - :param client_secret: The client secret - :return: Access token - """ - try: - auth_url = get_state_auth_url() - - # Prepare the request data for client credentials flow - data = { - 'grant_type': 'client_credentials', - 'client_id': client_id, - 'client_secret': client_secret, - 'scope': f'{COMPACT}/readGeneral {JURISDICTION}/{COMPACT}.write', - } - - headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'} - - response = requests.post(auth_url, data=data, headers=headers, timeout=10) - - if response.status_code != 200: - raise SmokeTestFailureException( - f'Failed to get access token. Status: {response.status_code}, Response: {response.text}' - ) - - token_data = response.json() - access_token = token_data.get('access_token') - - if not access_token: - raise SmokeTestFailureException('No access token in response') - - logger.info('Successfully obtained access token using client credentials') - return access_token - - except requests.RequestException as e: - logger.error(f'Failed to get client credentials token: {str(e)}') - raise SmokeTestFailureException(f'Failed to get client credentials token: {str(e)}') from e - - -def get_client_auth_headers(client_id: str, client_secret: str): - """ - Get authentication headers for client credentials flow. - - :param client_id: The client ID - :param client_secret: The client secret - :return: Headers dictionary with Authorization header - """ - access_token = get_client_credentials_token(client_id, client_secret) - return {'Authorization': f'Bearer {access_token}'} - - -def configure_signature_public_key(public_key_pem: str): - """ - Configure a SIGNATURE public key in the compact configuration table. - - :param public_key_pem: PEM-encoded public key content - """ - logger.info(f'Configuring SIGNATURE public key: {TEST_KEY_ID}') - - try: - table = get_compact_configuration_table() - - # Check if key already exists - pk = f'{COMPACT}#SIGNATURE_KEYS#{JURISDICTION}' - sk = f'{COMPACT}#JURISDICTION#{JURISDICTION}#{TEST_KEY_ID}' - - response = table.get_item(Key={'pk': pk, 'sk': sk}) - - if 'Item' in response: - logger.info(f'SIGNATURE key {TEST_KEY_ID} already exists, overwriting') - - # Create the item - item = { - 'pk': pk, - 'sk': sk, - 'publicKey': public_key_pem, - 'compact': COMPACT, - 'jurisdiction': JURISDICTION, - 'keyId': TEST_KEY_ID, - 'createdAt': datetime.now(tz=UTC).isoformat(), - } - - # Write to DynamoDB - table.put_item(Item=item) - - logger.info(f'Successfully configured SIGNATURE public key: {TEST_KEY_ID}') - - except ClientError as e: - logger.error(f'Failed to configure SIGNATURE public key: {str(e)}') - raise SmokeTestFailureException(f'Failed to configure SIGNATURE public key: {str(e)}') from e - - -def remove_signature_public_key(): - """Remove the test SIGNATURE public key from the compact configuration table.""" - try: - table = get_compact_configuration_table() - - pk = f'{COMPACT}#SIGNATURE_KEYS#{JURISDICTION}' - sk = f'{COMPACT}#JURISDICTION#{JURISDICTION}#{TEST_KEY_ID}' - - table.delete_item(Key={'pk': pk, 'sk': sk}) - logger.info(f'Successfully removed SIGNATURE public key: {TEST_KEY_ID}') - - except ClientError as e: - logger.error(f'Failed to remove SIGNATURE public key: {str(e)}') - # Don't raise here as this is cleanup - - -def load_test_keys(): - """ - Load the test private and public keys from the resources directory. - - :return: Tuple of (private_key_pem, public_key_pem) - """ - # Find the resources directory relative to this script - script_dir = Path(__file__).parent - resources_dir = script_dir.parent.parent / 'lambdas' / 'python' / 'common' / 'tests' / 'resources' - - private_key_path = resources_dir / 'client_private_key.pem' - public_key_path = resources_dir / 'client_public_key.pem' - - if not private_key_path.exists(): - raise SmokeTestFailureException(f'Private key file not found: {private_key_path}') - if not public_key_path.exists(): - raise SmokeTestFailureException(f'Public key file not found: {public_key_path}') - - with open(private_key_path) as f: - private_key_pem = f.read() - - with open(public_key_path) as f: - public_key_pem = f.read() - - logger.info('Successfully loaded test keys') - return private_key_pem, public_key_pem - - -def create_signed_headers(method: str, path: str, query_params: dict, private_key_pem: str): - """ - Create SIGNATURE-signed headers for a request. - - :param method: HTTP method (e.g., 'GET', 'POST') - :param path: Request path - :param query_params: Query parameters dictionary - :param private_key_pem: PEM-encoded private key - :return: Headers dictionary with SIGNATURE authentication headers - """ - # Generate current timestamp and nonce - timestamp = datetime.now(tz=UTC).isoformat() - nonce = str(uuid.uuid4()) - - # Sign the request - return sign_request( - method=method, - path=path, - query_params=query_params, - timestamp=timestamp, - nonce=nonce, - key_id=TEST_KEY_ID, - private_key_pem=private_key_pem, - ) - - -def test_bulk_upload_endpoint_without_signature(client_id: str, client_secret: str): - """ - Test the bulk-upload endpoint without SIGNATURE authentication (should succeed when no keys configured). - - :param client_id: The client ID for authentication - :param client_secret: The client secret for authentication - """ - logger.info('Testing bulk-upload endpoint without SIGNATURE authentication') - - headers = get_client_auth_headers(client_id, client_secret) - - response = requests.get( - url=get_state_api_base_url() + f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/licenses/bulk-upload', - headers=headers, - timeout=10, - ) - - if response.status_code != 200: - raise SmokeTestFailureException( - f'Bulk-upload endpoint should succeed without SIGNATURE when no keys configured. ' - f'Response: {response.status_code} - {response.text}' - ) - - logger.info('Bulk-upload endpoint succeeded without SIGNATURE authentication (as expected)') - - -def test_bulk_upload_endpoint_without_signature_after_key_configuration(client_id: str, client_secret: str): - """ - Test the bulk-upload endpoint without SIGNATURE authentication after keys are configured (should fail). - - :param client_id: The client ID for authentication - :param client_secret: The client secret for authentication - """ - logger.info('Testing bulk-upload endpoint without SIGNATURE authentication after key configuration') - - headers = get_client_auth_headers(client_id, client_secret) - - response = requests.get( - url=get_state_api_base_url() + f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/licenses/bulk-upload', - headers=headers, - timeout=10, - ) - - logger.info('Bulk-upload endpoint correctly rejected without SIGNATURE authentication') - if response.status_code != 401: - raise SmokeTestFailureException( - f'Expected 401 without SIGNATURE when keys are configured, got {response.status_code}: {response.text}' - ) - logger.info('Bulk-upload endpoint correctly rejected without SIGNATURE authentication') - - -def test_bulk_upload_endpoint_with_signature(client_id: str, client_secret: str, private_key_pem: str): - """ - Test the bulk-upload endpoint with valid SIGNATURE authentication. - - :param client_id: The client ID for authentication - :param client_secret: The client secret for authentication - :param private_key_pem: PEM-encoded private key for signing - """ - logger.info('Testing bulk-upload endpoint with SIGNATURE authentication') - - # Get client credentials headers - client_headers = get_client_auth_headers(client_id, client_secret) - - # Create SIGNATURE headers - signature_headers = create_signed_headers( - method='GET', - path=f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/licenses/bulk-upload', - query_params={}, - private_key_pem=private_key_pem, - ) - - # Combine headers - headers = {**client_headers, **signature_headers} - - response = requests.get( - url=get_state_api_base_url() + f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/licenses/bulk-upload', - headers=headers, - timeout=10, - ) - - if response.status_code != 200: - raise SmokeTestFailureException( - f'Bulk-upload endpoint should succeed with valid SIGNATURE authentication. ' - f'Response: {response.status_code} - {response.text}' - ) - - # Verify response structure - response_data = response.json() - if 'upload' not in response_data: - raise SmokeTestFailureException('Bulk-upload response missing upload field') - - logger.info('Bulk-upload endpoint succeeded with SIGNATURE authentication') - - -def test_providers_query_endpoint_without_signature(client_id: str, client_secret: str): - """ - Test the providers/query endpoint with valid SIGNATURE authentication. - - :param client_id: The client ID for authentication - :param client_secret: The client secret for authentication - :param private_key_pem: PEM-encoded private key for signing - """ - logger.info('Testing providers/query endpoint with SIGNATURE authentication') - - # Get client credentials headers - client_headers = get_client_auth_headers(client_id, client_secret) - - # Create request body with a 7-day time range - end_time = datetime.now(tz=UTC) - start_time = end_time - timedelta(days=7) - - request_body = { - 'query': { - 'startDateTime': start_time.strftime('%Y-%m-%dT%H:%M:%SZ'), - 'endDateTime': end_time.strftime('%Y-%m-%dT%H:%M:%SZ'), - } - } - - response = requests.post( - url=get_state_api_base_url() + f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/providers/query', - headers={'Content-Type': 'application/json', **client_headers}, - json=request_body, - timeout=10, - ) - - if response.status_code != 401: - raise SmokeTestFailureException( - f'Providers/query endpoint should fail without required signature authentication. ' - f'Response: {response.status_code} - {response.text}' - ) - - logger.info('Providers/query endpoint succeeded with SIGNATURE authentication') - - -def test_providers_query_endpoint_with_signature(client_id: str, client_secret: str, private_key_pem: str): - """ - Test the providers/query endpoint with valid SIGNATURE authentication. - - :param client_id: The client ID for authentication - :param client_secret: The client secret for authentication - :param private_key_pem: PEM-encoded private key for signing - """ - logger.info('Testing providers/query endpoint with SIGNATURE authentication') - - # Get client credentials headers - client_headers = get_client_auth_headers(client_id, client_secret) - - # Create request body with a 7-day time range - end_time = datetime.now(tz=UTC) - start_time = end_time - timedelta(days=7) - - request_body = { - 'query': { - 'startDateTime': start_time.strftime('%Y-%m-%dT%H:%M:%SZ'), - 'endDateTime': end_time.strftime('%Y-%m-%dT%H:%M:%SZ'), - } - } - - # Create SIGNATURE headers - signature_headers = create_signed_headers( - method='POST', - path=f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/providers/query', - query_params={}, - private_key_pem=private_key_pem, - ) - - # Combine headers - headers = {**client_headers, **signature_headers} - headers['Content-Type'] = 'application/json' - - response = requests.post( - url=get_state_api_base_url() + f'/v1/compacts/{COMPACT}/jurisdictions/{JURISDICTION}/providers/query', - headers=headers, - json=request_body, - timeout=10, - ) - - if response.status_code != 200: - raise SmokeTestFailureException( - f'Providers/query endpoint should succeed with valid SIGNATURE authentication. ' - f'Response: {response.status_code} - {response.text}' - ) - - # Verify response structure - response_data = response.json() - if 'providers' not in response_data: - raise SmokeTestFailureException('Providers/query response missing providers field') - - if 'pagination' not in response_data: - raise SmokeTestFailureException('Providers/query response missing pagination field') - - logger.info('Providers/query endpoint succeeded with SIGNATURE authentication') - - -def signature_authentication_smoke_test(): - """ - Comprehensive smoke test for SIGNATURE authentication system. - - This test exercises the SIGNATURE authentication by: - 1. Creating a test app client in Cognito - 2. Testing bulk-upload endpoint without SIGNATURE (should succeed when no keys configured) - 3. Configuring a SIGNATURE public key for the test compact/state - 4. Testing bulk-upload endpoint without SIGNATURE (keys configured - should fail) - 5. Testing bulk-upload endpoint with valid SIGNATURE authentication - 6. Testing providers/query endpoint with valid SIGNATURE authentication - """ - logger.info('Starting SIGNATURE authentication smoke test') - - # Load test keys - private_key_pem, public_key_pem = load_test_keys() - - # Create test app client - client_credentials = create_test_app_client() - client_id = client_credentials['client_id'] - client_secret = client_credentials['client_secret'] - - try: - # Step 1: Test bulk-upload endpoint without SIGNATURE (no keys configured) - test_bulk_upload_endpoint_without_signature(client_id, client_secret) - - # Step 2: Test query endpoint fails without SIGNATURE (required signature) - test_providers_query_endpoint_without_signature(client_id, client_secret) - - # Step 3: Configure SIGNATURE public key - configure_signature_public_key(public_key_pem) - - # Step 4: Test bulk-upload endpoint without SIGNATURE (keys configured - should fail) - test_bulk_upload_endpoint_without_signature_after_key_configuration(client_id, client_secret) - - # Step 5: Test bulk-upload endpoint with valid SIGNATURE authentication - test_bulk_upload_endpoint_with_signature(client_id, client_secret, private_key_pem) - - # Step 6: Test providers/query endpoint with valid SIGNATURE authentication - test_providers_query_endpoint_with_signature(client_id, client_secret, private_key_pem) - - logger.info('SIGNATURE authentication smoke test completed successfully') - - finally: - # Cleanup - logger.info('Cleaning up test resources') - remove_signature_public_key() - delete_test_app_client(client_id) - - -if __name__ == '__main__': - load_smoke_test_env() - - try: - signature_authentication_smoke_test() - logger.info('SIGNATURE authentication smoke test passed') - except SmokeTestFailureException as e: - logger.error(f'SIGNATURE authentication smoke test failed: {str(e)}') - raise diff --git a/backend/cosmetology-app/tests/smoke/smoke_common.py b/backend/cosmetology-app/tests/smoke/smoke_common.py index 6bb54e010..9bec1b3ff 100644 --- a/backend/cosmetology-app/tests/smoke/smoke_common.py +++ b/backend/cosmetology-app/tests/smoke/smoke_common.py @@ -1,8 +1,6 @@ import json import os import sys -import time -import uuid import boto3 import requests @@ -39,7 +37,6 @@ def __init__(self, message): # importing this here so it can be easily referenced in the rollback upload tests from cc_common.data_model.schema.license import LicenseData, LicenseUpdateData # noqa: E402 F401 from cc_common.data_model.schema.user.record import UserRecordSchema # noqa: E402 -from cc_common.data_model.update_tier_enum import UpdateTierEnum # noqa: E402 _TEST_STAFF_USER_PASSWORD = 'TestPass123!' # noqa: S105 test credential for test staff user _TEMP_STAFF_PASSWORD = 'TempPass123!' # noqa: S105 temporary password for creating test staff users @@ -316,7 +313,6 @@ def upload_license_record(staff_headers: dict, compact: str, jurisdiction: str, """ # Default test license data default_license_data = { - 'npi': '1111111111', 'licenseNumber': 'TEST-LIC-123', 'homeAddressPostalCode': '68001', 'givenName': 'TestProvider', @@ -433,113 +429,6 @@ def wait_for_provider_creation( ) -def create_test_privilege_record( - provider_id: str, compact: str, jurisdiction: str, license_jurisdiction: str, license_type: str -): - """Create a test privilege record in the database for testing license deactivation. - - :param provider_id: The provider's ID - :param compact: The compact abbreviation - :param jurisdiction: The privilege jurisdiction - :param license_jurisdiction: The license jurisdiction (home state) - :param license_type: The license type - :return: The created privilege record data - """ - from datetime import UTC, date, datetime - - # Get the license type abbreviation to match the expected sort key format - license_type_abbr = get_license_type_abbreviation(license_type) - if not license_type_abbr: - raise SmokeTestFailureException(f'Could not find abbreviation for license type: {license_type}') - - # Generate a test transaction ID - transaction_id = str(uuid.uuid4()) - - # Create privilege record data with correct sort key format - privilege_data = { - 'pk': f'{compact}#PROVIDER#{provider_id}', - 'sk': f'{compact}#PROVIDER#privilege/{jurisdiction}/{license_type_abbr}#', - 'type': 'privilege', - 'providerId': provider_id, - 'compact': compact, - 'jurisdiction': jurisdiction, - 'licenseJurisdiction': license_jurisdiction, - 'licenseType': license_type, - 'dateOfIssuance': datetime.now(tz=UTC).isoformat(), - 'dateOfRenewal': datetime.now(tz=UTC).isoformat(), - 'dateOfExpiration': date(2050, 1, 1).isoformat(), - 'dateOfUpdate': datetime.now(tz=UTC).isoformat(), - 'compactTransactionId': transaction_id, - 'compactTransactionIdGSIPK': f'COMPACT#{compact}#TX#{transaction_id}#', - 'privilegeId': f'test-privilege-{provider_id}-{jurisdiction}-{license_type_abbr}', - 'administratorSetStatus': 'active', - } - - # Insert the privilege record - config.provider_user_dynamodb_table.put_item(Item=privilege_data) - logger.info( - f'Created test privilege record for provider {provider_id} in jurisdiction ' - f'{jurisdiction} with license type {license_type} ({license_type_abbr})' - ) - - return privilege_data - - -def delete_existing_privilege_records(provider_id: str, compact: str, jurisdiction: str): - """Delete all privilege records and privilege update records for a provider in a specific jurisdiction. - - This function queries for and deletes both privilege records and their associated update records - using the new SK pattern structure. - - :param provider_id: The provider's ID - :param compact: The compact abbreviation - :param jurisdiction: The jurisdiction abbreviation (e.g., 'ne') - """ - dynamodb_table = config.provider_user_dynamodb_table - pk = f'{compact}#PROVIDER#{provider_id}' - - # Query for all privilege records in the specified jurisdiction - original_privilege_records = dynamodb_table.query( - KeyConditionExpression=Key('pk').eq(pk) & Key('sk').begins_with(f'{compact}#PROVIDER#privilege/{jurisdiction}/') - ).get('Items', []) - - # Query for all privilege update records in the specified jurisdiction - privilege_update_sk_prefix = f'{compact}#UPDATE#{UpdateTierEnum.TIER_ONE}#privilege/{jurisdiction}/' - original_privilege_update_records = [] - last_evaluated_key = None - while True: - pagination = {'ExclusiveStartKey': last_evaluated_key} if last_evaluated_key else {} - query_resp = dynamodb_table.query( - KeyConditionExpression=Key('pk').eq(pk) & Key('sk').begins_with(privilege_update_sk_prefix), - **pagination, - ) - original_privilege_update_records.extend(query_resp.get('Items', [])) - last_evaluated_key = query_resp.get('LastEvaluatedKey') - if not last_evaluated_key: - break - - original_privilege_records.extend(original_privilege_update_records) - - # Delete all privilege records - for privilege in original_privilege_records: - privilege_pk = privilege['pk'] - privilege_sk = privilege['sk'] - logger.info(f'Deleting privilege record:\n{privilege_pk}\n{privilege_sk}') - dynamodb_table.delete_item( - Key={ - 'pk': privilege_pk, - 'sk': privilege_sk, - } - ) - # give dynamodb time to propagate - time.sleep(1) - - logger.info( - f'Deleted privilege record and {len(original_privilege_update_records)} privilege update records for ' - f'jurisdiction {jurisdiction}' - ) - - def cleanup_test_provider_records(provider_id: str, compact: str): """Clean up all test records for a provider.